XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFESpaceNinjaServer

feat: tactical alerts (#2607)

Includes all `Tactical Alerts` since Star Chart 3.0 with exception: `Snowday Showdown` `Wolf Hunt (2019)` (couldn't find corresponded `EventNode` for that) `Void Corruption` (that's goes into `Alerts`) All `Warframe's Anniversary` Re #1103 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2607 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>

fd2ec696
AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
提交于

代码差异

17 个文件 +1100 -23
Modified config-vanilla.json +9 -0
@@ -71,10 +71,19 @@
71 71 "affinityBoost": false,
72 72 "resourceBoost": false,
73 73 "tennoLiveRelay": false,
74 "wolfHunt": false,
75 "longShadow": false,
76 "hallowedFlame": false,
77 "hallowedNightmares": false,
78 "hallowedNightmaresRewardsOverride": 0,
79 "proxyRebellion": false,
80 "proxyRebellionRewardsOverride": 0,
74 81 "galleonOfGhouls": 0,
75 82 "ghoulEmergenceOverride": null,
76 83 "plagueStarOverride": null,
77 84 "starDaysOverride": null,
85 "dogDaysOverride": null,
86 "dogDaysRewardsOverride": null,
78 87 "eidolonOverride": "",
79 88 "vallisOverride": "",
80 89 "duviriOverride": "",
Modified src/controllers/api/inboxController.ts +5 -1
@@ -13,7 +13,8 @@ import {
13 13 addItems,
14 14 combineInventoryChanges,
15 15 getEffectiveAvatarImageType,
16 getInventory
16 getInventory,
17 updateCurrency
17 18 } from "@/src/services/inventoryService";
18 19 import { logger } from "@/src/utils/logger";
19 20 import { ExportFlavour } from "warframe-public-export-plus";
@@ -100,6 +101,9 @@ export const inboxController: RequestHandler = async (req, res) => {
100 101 }
101 102 }
102 103 }
104 if (message.RegularCredits) {
105 updateCurrency(inventory, -message.RegularCredits, false, inventoryChanges);
106 }
103 107 await inventory.save();
104 108 res.json({ InventoryChanges: inventoryChanges });
105 109 } else if (latestClientMessageId) {
Modified src/models/inboxModel.ts +3 -1
@@ -47,6 +47,7 @@ export interface IMessage {
47 47 acceptAction?: string;
48 48 declineAction?: string;
49 49 hasAccountAction?: boolean;
50 RegularCredits?: number;
50 51 }
51 52
52 53 export interface Arg {
@@ -139,7 +140,8 @@ const messageSchema = new Schema<IMessageDatabase>(
139 140 contextInfo: String,
140 141 acceptAction: String,
141 142 declineAction: String,
142 hasAccountAction: Boolean
143 hasAccountAction: Boolean,
144 RegularCredits: Number
143 145 },
144 146 { id: false }
145 147 );
Modified src/services/configService.ts +9 -0
@@ -83,10 +83,19 @@ export interface IConfig {
83 83 resourceBoost?: boolean;
84 84 tennoLiveRelay?: boolean;
85 85 baroTennoConRelay?: boolean;
86 wolfHunt?: boolean;
87 longShadow?: boolean;
88 hallowedFlame?: boolean;
89 hallowedNightmares?: boolean;
90 hallowedNightmaresRewardsOverride?: number;
91 proxyRebellion?: boolean;
92 proxyRebellionRewardsOverride?: number;
86 93 galleonOfGhouls?: number;
87 94 ghoulEmergenceOverride?: boolean;
88 95 plagueStarOverride?: boolean;
89 96 starDaysOverride?: boolean;
97 dogDaysOverride?: boolean;
98 dogDaysRewardsOverride?: number;
90 99 eidolonOverride?: string;
91 100 vallisOverride?: string;
92 101 duviriOverride?: string;
Modified src/services/missionInventoryUpdateService.ts +219 -17
@@ -50,7 +50,7 @@ import { getEntriesUnsafe } from "@/src/utils/ts-utils";
50 50 import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
51 51 import { IMissionCredits, IMissionReward } from "@/src/types/missionTypes";
52 52 import { crackRelic } from "@/src/helpers/relicHelper";
53 import { createMessage } from "@/src/services/inboxService";
53 import { createMessage, IMessageCreationTemplate } from "@/src/services/inboxService";
54 54 import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
55 55 import kuriaMessage75 from "@/static/fixed_responses/kuriaMessages/seventyFivePercent.json";
56 56 import kuriaMessage100 from "@/static/fixed_responses/kuriaMessages/oneHundredPercent.json";
@@ -624,37 +624,93 @@ export const addMissionInventoryUpdates = async (
624 624 if (goal && goal.Personal) {
625 625 inventory.PersonalGoalProgress ??= [];
626 626 const goalProgress = inventory.PersonalGoalProgress.find(x => x.goalId.equals(goal._id.$oid));
627 if (goalProgress) {
628 goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
629 goalProgress.Count += uploadProgress.Count;
630 } else {
627 if (!goalProgress) {
631 628 inventory.PersonalGoalProgress.push({
632 629 Best: uploadProgress.Best,
633 630 Count: uploadProgress.Count,
634 631 Tag: goal.Tag,
635 632 goalId: new Types.ObjectId(goal._id.$oid)
636 633 });
634 }
637 635
636 const currentNode = inventoryUpdates.RewardInfo!.node;
637 let currentMissionKey;
638 if (currentNode == goal.Node) {
639 currentMissionKey = goal.MissionKeyName;
640 } else if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
641 for (let i = 0; i < goal.ConcurrentNodes.length; i++) {
642 if (currentNode == goal.ConcurrentNodes[i]) {
643 currentMissionKey = goal.ConcurrentMissionKeyNames[i];
644 break;
645 }
646 }
647 }
648 if (currentMissionKey && currentMissionKey in goalMessagesByKey) {
649 const totalCount = (goalProgress?.Count ?? 0) + uploadProgress.Count;
650 let reward;
651
652 if (goal.InterimGoals && goal.InterimRewards) {
653 for (let i = 0; i < goal.InterimGoals.length; i++) {
654 if (
655 goal.InterimGoals[i] &&
656 goal.InterimGoals[i] <= totalCount &&
657 (!goalProgress || goalProgress.Count < goal.InterimGoals[i]) &&
658 goal.InterimRewards[i]
659 ) {
660 reward = goal.InterimRewards[i];
661 break;
662 }
663 }
664 }
638 665 if (
639 goal.Reward &&
640 goal.Reward.items &&
641 goal.MissionKeyName &&
642 goal.MissionKeyName in goalMessagesByKey
666 !reward &&
667 goal.Goal &&
668 goal.Goal <= totalCount &&
669 (!goalProgress || goalProgress.Count < goal.Goal) &&
670 goal.Reward
643 671 ) {
644 // Send reward via inbox
645 const info = goalMessagesByKey[goal.MissionKeyName];
646 await createMessage(inventory.accountOwnerId, [
647 {
672 reward = goal.Reward;
673 }
674 if (
675 !reward &&
676 goal.BonusGoal &&
677 goal.BonusGoal <= totalCount &&
678 (!goalProgress || goalProgress.Count < goal.BonusGoal) &&
679 goal.BonusReward
680 ) {
681 reward = goal.BonusReward;
682 }
683 if (reward) {
684 if (currentMissionKey in goalMessagesByKey) {
685 // Send reward via inbox
686 const info = goalMessagesByKey[currentMissionKey];
687 const message: IMessageCreationTemplate = {
648 688 sndr: info.sndr,
649 689 msg: info.msg,
650 att: goal.Reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)),
651 690 sub: info.sub,
652 691 icon: info.icon,
653 692 highPriority: true
693 };
694
695 if (reward.items) {
696 message.att = reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x));
654 697 }
655 ]);
698 if (reward.countedItems) {
699 message.countedAtt = reward.countedItems;
700 }
701 if (reward.credits) {
702 message.RegularCredits = reward.credits;
703 }
704
705 await createMessage(inventory.accountOwnerId, [message]);
706 }
656 707 }
657 708 }
709
710 if (goalProgress) {
711 goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
712 goalProgress.Count += uploadProgress.Count;
713 }
658 714 }
659 715 }
660 716 break;
@@ -1011,8 +1067,16 @@ export const addMissionRewards = async (
1011 1067
1012 1068 if (rewardInfo.goalId) {
1013 1069 const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
1014 if (goal?.MissionKeyName) {
1015 levelKeyName = goal.MissionKeyName;
1070 if (goal) {
1071 if (rewardInfo.node == goal.Node && goal.MissionKeyName) levelKeyName = goal.MissionKeyName;
1072 if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
1073 for (let i = 0; i < goal.ConcurrentNodes.length && i < goal.ConcurrentMissionKeyNames.length; i++) {
1074 if (rewardInfo.node == goal.ConcurrentNodes[i]) {
1075 levelKeyName = goal.ConcurrentMissionKeyNames[i];
1076 break;
1077 }
1078 }
1079 }
1016 1080 }
1017 1081 }
1018 1082
@@ -2149,5 +2213,143 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
2149 2213 msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgC",
2150 2214 sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleC",
2151 2215 icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
2216 },
2217 "/Lotus/Types/Keys/TacAlertKeyWaterFightA": {
2218 sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
2219 msg: "/Lotus/Language/Inbox/WaterFightRewardMsgA",
2220 sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectA",
2221 icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
2222 },
2223 "/Lotus/Types/Keys/TacAlertKeyWaterFightB": {
2224 sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
2225 msg: "/Lotus/Language/Inbox/WaterFightRewardMsgB",
2226 sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectB",
2227 icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
2228 },
2229 "/Lotus/Types/Keys/TacAlertKeyWaterFightC": {
2230 sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
2231 msg: "/Lotus/Language/Inbox/WaterFightRewardMsgC",
2232 sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectC",
2233 icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
2234 },
2235 "/Lotus/Types/Keys/TacAlertKeyWaterFightD": {
2236 sndr: "/Lotus/Language/Bosses/BossKelaDeThaym",
2237 msg: "/Lotus/Language/Inbox/WaterFightRewardMsgD",
2238 sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectD",
2239 icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
2240 },
2241 "/Lotus/Types/Keys/WolfTacAlertReduxA": {
2242 sndr: "/Lotus/Language/Bosses/NoraNight",
2243 msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
2244 sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
2245 icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
2246 },
2247 "/Lotus/Types/Keys/WolfTacAlertReduxB": {
2248 sndr: "/Lotus/Language/Bosses/NoraNight",
2249 msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
2250 sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
2251 icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
2252 },
2253 "/Lotus/Types/Keys/WolfTacAlertReduxD": {
2254 sndr: "/Lotus/Language/Bosses/NoraNight",
2255 msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
2256 sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
2257 icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
2258 },
2259 "/Lotus/Types/Keys/WolfTacAlertReduxC": {
2260 sndr: "/Lotus/Language/Bosses/NoraNight",
2261 msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
2262 sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
2263 icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
2264 },
2265 "/Lotus/Types/Keys/LanternEndlessEventKeyA": {
2266 sndr: "/Lotus/Language/Bosses/Lotus",
2267 msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
2268 sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
2269 icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
2270 },
2271 "/Lotus/Types/Keys/LanternEndlessEventKeyB": {
2272 sndr: "/Lotus/Language/Bosses/Lotus",
2273 msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
2274 sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
2275 icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
2276 },
2277 "/Lotus/Types/Keys/LanternEndlessEventKeyD": {
2278 sndr: "/Lotus/Language/Bosses/Lotus",
2279 msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
2280 sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
2281 icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
2282 },
2283 "/Lotus/Types/Keys/LanternEndlessEventKeyC": {
2284 sndr: "/Lotus/Language/Bosses/Lotus",
2285 msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
2286 sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
2287 icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
2288 },
2289 "/Lotus/Types/Keys/TacAlertKeyHalloween": {
2290 sndr: "/Lotus/Language/Bosses/Lotus",
2291 msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusBody",
2292 sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusTitle",
2293 icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
2294 },
2295 "/Lotus/Types/Keys/TacAlertKeyHalloweenBonus": {
2296 sndr: "/Lotus/Language/Bosses/Lotus",
2297 msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
2298 sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
2299 icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
2300 },
2301 "/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack": {
2302 sndr: "/Lotus/Language/Bosses/Lotus",
2303 msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
2304 sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
2305 icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
2306 },
2307 "/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne": {
2308 sndr: "/Lotus/Language/Bosses/Lotus",
2309 msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
2310 sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
2311 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2312 },
2313 "/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo": {
2314 sndr: "/Lotus/Language/Bosses/Lotus",
2315 msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
2316 sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
2317 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2318 },
2319 "/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree": {
2320 sndr: "/Lotus/Language/Bosses/Lotus",
2321 msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
2322 sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
2323 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2324 },
2325 "/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour": {
2326 sndr: "/Lotus/Language/Bosses/Lotus",
2327 msg: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgDesc",
2328 sub: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgTitle",
2329 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2330 },
2331 "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy": {
2332 sndr: "/Lotus/Language/Bosses/Lotus",
2333 msg: "/Lotus/Language/G1Quests/ProjectNightwatchRewardMsgA",
2334 sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionOneTitle",
2335 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2336 },
2337 "/Lotus/Types/Keys/TacAlertKeyProjectNightwatch": {
2338 sndr: "/Lotus/Language/Bosses/Lotus",
2339 msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
2340 sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionTwoTitle",
2341 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2342 },
2343 "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard": {
2344 sndr: "/Lotus/Language/Bosses/Lotus",
2345 msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
2346 sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionThreeTitle",
2347 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2348 },
2349 "/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus": {
2350 sndr: "/Lotus/Language/Bosses/Lotus",
2351 msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
2352 sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle",
2353 icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
2152 2354 }
2153 2355 };
Modified src/types/worldStateTypes.ts +47 -1
@@ -5,12 +5,14 @@ export interface IWorldState {
5 5 Version: number; // for goals
6 6 BuildLabel: string;
7 7 Time: number;
8 InGameMarket: IInGameMarket;
8 9 Goals: IGoal[];
9 10 Alerts: [];
10 11 Sorties: ISortie[];
11 12 LiteSorties: ILiteSortie[];
12 13 SyndicateMissions: ISyndicateMissionInfo[];
13 14 ActiveMissions: IFissure[];
15 FlashSales: IFlashSale[];
14 16 GlobalUpgrades: IGlobalUpgrade[];
15 17 Invasions: IInvasion[];
16 18 NodeOverrides: INodeOverride[];
@@ -39,10 +41,13 @@ export interface IGoal {
39 41 Expiry: IMongoDate;
40 42 Count?: number;
41 43 Goal?: number;
44 InterimGoals?: number[];
45 BonusGoal?: number;
42 46 HealthPct?: number;
43 47 Success?: number;
44 48 Personal?: boolean;
45 Bounty?: boolean;
49 Best?: boolean;
50 Bounty?: boolean; // Tactical Alert
46 51 Faction?: string;
47 52 ClampNodeScores?: boolean;
48 53 Desc: string;
@@ -51,18 +56,28 @@ export interface IGoal {
51 56 InstructionalItem?: string;
52 57 Icon: string;
53 58 Tag: string;
59 PrereqGoalTags?: string[];
54 60 Node?: string;
55 61 VictimNode?: string;
62 ConcurrentMissionKeyNames?: string[];
63 ConcurrentNodeReqs?: number[];
64 ConcurrentNodes?: string[];
56 65 RegionIdx?: number;
57 66 Regions?: number[];
58 67 MissionKeyName?: string;
59 68 Reward?: IMissionReward;
69 InterimRewards?: IMissionReward[];
70 BonusReward?: IMissionReward;
60 71
61 72 JobAffiliationTag?: string;
62 73 Jobs?: ISyndicateJob[];
63 74 PreviousJobs?: ISyndicateJob[];
64 75 JobCurrentVersion?: IOid;
65 76 JobPreviousVersion?: IOid;
77
78 ScoreVar?: string;
79 ScoreMaxTag?: string;
80 NightLevel?: string;
66 81 }
67 82
68 83 export interface ISyndicateJob {
@@ -321,6 +336,37 @@ export type TCircuitGameMode =
321 336 | "Assassination"
322 337 | "Alchemy";
323 338
339 export interface IFlashSale {
340 TypeName: string;
341 ShowInMarket: boolean;
342 HideFromMarket: boolean;
343 SupporterPack: boolean;
344 Discount: number;
345 BogoBuy: number;
346 BogoGet: number;
347 PremiumOverride: number;
348 RegularOverride: number;
349 ProductExpiryOverride?: IMongoDate;
350 StartDate: IMongoDate;
351 EndDate: IMongoDate;
352 }
353
354 export interface IInGameMarket {
355 LandingPage: ILandingPage;
356 }
357
358 export interface ILandingPage {
359 Categories: IGameMarketCategory[];
360 }
361
362 export interface IGameMarketCategory {
363 CategoryName: string;
364 Name: string;
365 Icon: string;
366 AddToMenu?: boolean;
367 Items?: string[];
368 }
369
324 370 export interface ITmp {
325 371 cavabegin: string;
326 372 PurchasePlatformLockEnabled: boolean; // Seems unused
Modified static/webui/index.html +70 -0
@@ -937,8 +937,57 @@
937 937 <input class="form-check-input" type="checkbox" id="worldState.varziaFullyStocked" />
938 938 <label class="form-check-label" for="worldState.varziaFullyStocked" data-loc="worldState_varziaFullyStocked"></label>
939 939 </div>
940 <div class="form-check">
941 <input class="form-check-input" type="checkbox" id="worldState.wolfHunt" />
942 <label class="form-check-label" for="worldState.wolfHunt" data-loc="worldState_wolfHunt"></label>
943 <abbr data-loc-inc="worldState_galleonOfGhouls"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
944 </div>
945 <div class="form-check">
946 <input class="form-check-input" type="checkbox" id="worldState.longShadow" />
947 <label class="form-check-label" for="worldState.longShadow" data-loc="worldState_longShadow"></label>
948 </div>
949 <div class="form-check">
950 <input class="form-check-input" type="checkbox" id="worldState.hallowedFlame" />
951 <label class="form-check-label" for="worldState.hallowedFlame" data-loc="worldState_hallowedFlame"></label>
952 <abbr data-loc-inc="worldState_hallowedNightmares|worldState_dogDays"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
953 </div>
954 <div class="form-group mt-2 d-flex gap-2">
955 <div class="flex-fill">
956 <label class="form-label" for="worldState.hallowedNightmares" data-loc="worldState_hallowedNightmares"></label>
957 <abbr data-loc-inc="worldState_hallowedFlame|worldState_dogDays"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
958 <select class="form-control" id="worldState.hallowedNightmares" data-default="false">
959 <option value="true" data-loc="enabled"></option>
960 <option value="false" data-loc="disabled"></option>
961 </select>
962 </div>
963 <div class="flex-fill">
964 <label class="form-label" for="worldState.hallowedNightmaresRewardsOverride" data-loc="worldState_hallowedNightmaresRewards"></label>
965 <select class="form-control" id="worldState.hallowedNightmaresRewardsOverride" data-default="0">
966 <option value="0" data-loc="worldState_from_year" data-loc-year="2018"></option>
967 <option value="1" data-loc="worldState_from_year" data-loc-year="2016"></option>
968 <option value="2" data-loc="worldState_from_year" data-loc-year="2015"></option>
969 </select>
970 </div>
971 </div>
972 <div class="form-group mt-2 d-flex gap-2">
973 <div class="flex-fill">
974 <label class="form-label" for="worldState.proxyRebellion" data-loc="worldState_proxyRebellion"></label>
975 <select class="form-control" id="worldState.proxyRebellion" data-default="false">
976 <option value="true" data-loc="enabled"></option>
977 <option value="false" data-loc="disabled"></option>
978 </select>
979 </div>
980 <div class="flex-fill">
981 <label class="form-label" for="worldState.proxyRebellionRewardsOverride" data-loc="worldState_proxyRebellionRewards"></label>
982 <select class="form-control" id="worldState.proxyRebellionRewardsOverride" data-default="0">
983 <option value="0" data-loc="worldState_from_year" data-loc-year="2019"></option>
984 <option value="1" data-loc="worldState_from_year" data-loc-year="2018"></option>
985 </select>
986 </div>
987 </div>
940 988 <div class="form-group mt-2">
941 989 <label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label>
990 <abbr data-loc-inc="worldState_wolfHunt"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
942 991 <select class="form-control" id="worldState.galleonOfGhouls" data-default="0">
943 992 <option value="0" data-loc="disabled"></option>
944 993 <option value="1" data-loc="worldState_we1"></option>
@@ -970,6 +1019,27 @@
970 1019 <option value="false" data-loc="disabled"></option>
971 1020 </select>
972 1021 </div>
1022 <div class="form-group mt-2 d-flex gap-2">
1023 <div class="flex-fill">
1024 <label class="form-label" for="worldState.dogDaysOverride" data-loc="worldState_dogDays"></label>
1025 <abbr data-loc-inc="worldState_hallowedFlame|worldState_hallowedNightmares"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
1026 <select class="form-control" id="worldState.dogDaysOverride" data-default="null">
1027 <option value="null" data-loc="normal"></option>
1028 <option value="true" data-loc="enabled"></option>
1029 <option value="false" data-loc="disabled"></option>
1030 </select>
1031 </div>
1032 <div class="flex-fill">
1033 <label class="form-label" for="worldState.dogDaysRewardsOverride" data-loc="worldState_dogDaysRewards"></label>
1034 <select class="form-control" id="worldState.dogDaysRewardsOverride" data-default="null">
1035 <option value="null" data-loc="normal"></option>
1036 <option value="3" data-loc="worldState_from_year" data-loc-year="2025"></option>
1037 <option value="2" data-loc="worldState_from_year" data-loc-year="2024"></option>
1038 <option value="1" data-loc="worldState_from_year" data-loc-year="2023"></option>
1039 <option value="0" data-loc="worldState_pre_year" data-loc-year="2023"></option>
1040 </select>
1041 </div>
1042 </div>
973 1043 <div class="form-group mt-2">
974 1044 <label class="form-label" for="worldState.eidolonOverride" data-loc="worldState_eidolonOverride"></label>
975 1045 <select class="form-control" id="worldState.eidolonOverride" data-default="">
Modified static/webui/script.js +11 -0
@@ -202,6 +202,17 @@ function updateLocElements() {
202 202 document.querySelectorAll("[data-loc-placeholder]").forEach(elm => {
203 203 elm.placeholder = loc(elm.getAttribute("data-loc-placeholder"));
204 204 });
205 document.querySelectorAll("[data-loc-inc]").forEach(elm => {
206 const incWith = elm
207 .getAttribute("data-loc-inc")
208 .split("|")
209 .map(key => loc(key))
210 .join(", ");
211 elm.title = `${loc("worldState_incompatibleWith")} ${incWith}`;
212 });
213 document.querySelectorAll("[data-loc-year]").forEach(elm => {
214 elm.innerHTML = elm.innerHTML.replace("|YEAR|", elm.getAttribute("data-loc-year"));
215 });
205 216 }
206 217
207 218 function setActiveLanguage(lang) {
Modified static/webui/style.css +4 -1
@@ -29,9 +29,12 @@ body:not(.logged-in) .user-dropdown {
29 29 display: none;
30 30 }
31 31
32 td.text-end > a > svg {
32 /* font awesome icons */
33 svg {
33 34 fill: currentColor;
34 35 height: 1em;
36 }
37 td.text-end > a > svg {
35 38 margin-left: 0.5em;
36 39 margin-bottom: 4px; /* to centre the icon */
37 40 }
Modified static/webui/translations/de.js +12 -0
@@ -248,6 +248,18 @@ dict = {
248 248 worldState_galleonOfGhouls: `Galeone der Ghule`,
249 249 worldState_ghoulEmergence: `Ghul Ausrottung`,
250 250 worldState_plagueStar: `Plagenstern`,
251 worldState_dogDays: `Hitzefrei`,
252 worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
253 worldState_wolfHunt: `Wolfsjagd (2025)`,
254 worldState_longShadow: `Lange Schatten`,
255 worldState_hallowedFlame: `Geweihte Flamme`,
256 worldState_hallowedNightmares: `Geweihte Albträume`,
257 worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
258 worldState_proxyRebellion: `Proxy-Rebellion`,
259 worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
260 worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
261 worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
262 worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,
251 263 enabled: `Aktiviert`,
252 264 disabled: `Deaktiviert`,
253 265 worldState_we1: `Wochenende 1`,
Modified static/webui/translations/en.js +12 -0
@@ -247,6 +247,18 @@ dict = {
247 247 worldState_galleonOfGhouls: `Galleon of Ghouls`,
248 248 worldState_ghoulEmergence: `Ghoul Purge`,
249 249 worldState_plagueStar: `Plague Star`,
250 worldState_dogDays: `Dog Days`,
251 worldState_dogDaysRewards: `Dog Days Rewards`,
252 worldState_wolfHunt: `Wolf Hunt (2025)`,
253 worldState_longShadow: `Long Shadow`,
254 worldState_hallowedFlame: `Hallowed Flame`,
255 worldState_hallowedNightmares: `Hallowed Nightmares`,
256 worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`,
257 worldState_proxyRebellion: `Proxy Rebellion`,
258 worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`,
259 worldState_from_year: `from |YEAR|`,
260 worldState_pre_year: `pre |YEAR|`,
261 worldState_incompatibleWith: `Incompatible with:`,
250 262 enabled: `Enabled`,
251 263 disabled: `Disabled`,
252 264 worldState_we1: `Weekend 1`,
Modified static/webui/translations/es.js +12 -0
@@ -248,6 +248,18 @@ dict = {
248 248 worldState_galleonOfGhouls: `Galeón de Gules`,
249 249 worldState_ghoulEmergence: `Purga de Gules`,
250 250 worldState_plagueStar: `Estrella Infestada`,
251 worldState_dogDays: `Canícula`,
252 worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
253 worldState_wolfHunt: `Cacería del Lobo (2025)`,
254 worldState_longShadow: `Sombra Prolongada`,
255 worldState_hallowedFlame: `Llama Sagrada`,
256 worldState_hallowedNightmares: `Pesadillas sagradas`,
257 worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
258 worldState_proxyRebellion: `Rebelión Proxy`,
259 worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
260 worldState_from_year: `[UNTRANSLATED] from |YEAR|`,
261 worldState_pre_year: `[UNTRANSLATED] pre |YEAR|`,
262 worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`,
251 263 enabled: `Activado`,
252 264 disabled: `Desactivado`,
253 265 worldState_we1: `Semana 1`,
Modified static/webui/translations/fr.js +12 -0
Modified static/webui/translations/ru.js +12 -0
Modified static/webui/translations/uk.js +12 -0
Modified static/webui/translations/zh.js +12 -0