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

SpaceNinjaServer

A simple server for a small space ninja game

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

XFEstudio/SpaceNinjaServer

feat: tennobaum (#3952)

Also slightly refactored market stuff in worldState to be more faithful. Closes #3860 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3952 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

0d040313
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

15 个文件 +163 -58
Modified config-vanilla.json +1 -0
@@ -90,6 +90,7 @@
90 90 "plagueStarOverride": null,
91 91 "starDaysOverride": null,
92 92 "saintPatrickOverride": null,
93 "xmasOverride": null,
93 94 "lunarNewYear": null,
94 95 "dogDaysOverride": null,
95 96 "dogDaysRewardsOverride": null,
Modified src/controllers/api/loginController.ts +12 -1
@@ -210,7 +210,18 @@ const createLoginResponse = (request: Request, account: IDatabaseAccountJson, bu
210 210 resp.ForceLogoutVersion = 0;
211 211 }
212 212 if (version_compare(buildLabel, gameToBuildVersion["26.0.0"]) >= 0) {
213 resp.Groups = [];
213 // Example values from live:
214 // - [{"experiment":"landingPageAB2368","experimentGroup":"control"},{"experiment":"InGameMarketRelatedItemsData","experimentGroup":"related_items_priority_control"},{"experiment":"GamesightAB","experimentGroup":"b"}]
215 // - [{"experiment":"LandingPageAB2368","experimentGroup":"control"},{"experiment":"GamesightAB","experimentGroup":"b"},{"experiment":"InGameMarketRelatedItemsData","experimentGroup":"related_items_priority_control"},{"experiment":"InGameMarketRelatedItemsDisplay","experimentGroup":"related_items_preview_control"},{"experiment":"ConsoleBonusPlatLowGDP","experimentGroup":"LowGDP_FewerLargerBonuses"}]
216 // - [{"experiment":"LandingPageAB2368","experimentGroup":"control"},{"experiment":"LandingPageAB9364","experimentGroup":"control"},{"experiment":"GamesightAB","experimentGroup":"b"},{"experiment":"InGameMarketRelatedItemsData","experimentGroup":"related_items_priority_control"},{"experiment":"InGameMarketRelatedItemsDisplay","experimentGroup":"related_items_preview_control"},{"experiment":"MarketDualPack","experimentGroup":"dual_packs_enabled"},{"experiment":"ConsoleBonusPlatLowGDP","experimentGroup":"LowGDP_FewerLargerBonuses"},{"experiment":"ConsoleBonusPlatV2LowGDP","experimentGroup":"LowGDP_LargerBonuses"}]
217 // - [{"experiment":"ConsoleBonusPlatV2LowGDP","experimentGroup":"LowGDP_LargerBonuses"}]
218 resp.Groups = [
219 //{ experiment: "matchmaking_vp_4", experimentGroup: "matchmaking_vp_4" }, // Matchmaking for VorsPrize MissionFour
220 //{ experiment: "matchmaking_vp_all", experimentGroup: "matchmaking_vp_all" }, // Matchmaking for VorsPrize MissionSix
221 //{ experiment: "arsenal_auto_open", experimentGroup: "arsenal_auto_open" }, // Automatically opens the arsenal after installing the ship feature
222 //{ experiment: "igm_new_player_recommended_title", experimentGroup: "igm_new_player_recommended_title" }, // Shows "new players" market section for MR 0-3 players
223 //{ experiment: "quick_buy_visible", experimentGroup: "quick_buy_visible" } // Shows "quick buy" market section for MR 4+ players
224 ];
214 225 }
215 226 if (version_compare(buildLabel, gameToBuildVersion["30.0.0"]) >= 0) {
216 227 resp.DTLS = config.dtls ?? 0; // bit 0 enables DTLS. if enabled, additional bits can be set, e.g. bit 2 to enable logging. on live, the value is 99.
Modified src/services/configService.ts +1 -0
@@ -90,6 +90,7 @@ export interface IConfig {
90 90 plagueStarOverride?: boolean;
91 91 starDaysOverride?: boolean;
92 92 saintPatrickOverride?: boolean;
93 xmasOverride?: boolean;
93 94 lunarNewYear?: string;
94 95 dogDaysOverride?: boolean;
95 96 dogDaysRewardsOverride?: number;
Modified src/services/itemDataService.ts +2 -2
@@ -2084,9 +2084,9 @@ export const getPrice = (
2084 2084 const { FlashSales } = getWorldState(buildLabel);
2085 2085 const flashSale = FlashSales.find(s => s.TypeName == internalName);
2086 2086 if (flashSale) {
2087 if (usePremium && flashSale.PremiumOverride > 0) {
2087 if (usePremium && flashSale.PremiumOverride) {
2088 2088 return flashSale.PremiumOverride * quantity;
2089 } else if (!usePremium && flashSale.RegularOverride > 0) {
2089 } else if (!usePremium && flashSale.RegularOverride) {
2090 2090 return flashSale.RegularOverride * quantity;
2091 2091 }
2092 2092 }
Modified src/services/worldStateService.ts +91 -45
@@ -2505,6 +2505,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
2505 2505
2506 2506 pushFlashSales(worldState, storeItems, activation, expiry, "SEASONAL", buildLabel);
2507 2507 }
2508
2509 const xmasStart = Date.UTC(date.getUTCFullYear(), 11, 1);
2510 const xmasEnd = Date.UTC(date.getUTCFullYear() + 1, 1, 1);
2511 const isXmas = timeMs >= xmasStart && timeMs < xmasEnd;
2512 if (
2513 (config.worldState?.xmasOverride ?? isXmas) &&
2514 (!buildLabel || version_compare(buildLabel, gameToBuildVersion["13.0.0"]) >= 0)
2515 ) {
2516 const activation = config.worldState?.xmasOverride ? 1772467200000 : xmasStart;
2517 const expiry = config.worldState?.xmasOverride ? 2000000000000 : xmasEnd;
2518
2519 // On live, these FlashSales only have TypeName, PremiumOverride, StartDate, EndDate, & ProductExpiryOverride.
2520 const storeItems: (Partial<IFlashSale> & { TypeName: string })[] = [
2521 { TypeName: "/Lotus/Types/StoreItems/Packages/Winter2017GlyphBundle", PremiumOverride: 100 },
2522 { TypeName: "/Lotus/Types/StoreItems/Packages/Winter2018GlyphBundle", PremiumOverride: 80 },
2523 { TypeName: "/Lotus/Types/StoreItems/Packages/Winter2016Bundle", PremiumOverride: 350 },
2524 { TypeName: "/Lotus/Types/StoreItems/Packages/Winter2019GlyphBundle", PremiumOverride: 90 },
2525 { TypeName: "/Lotus/Types/StoreItems/Packages/WinterBundle", PremiumOverride: 600 },
2526 { TypeName: "/Lotus/Types/StoreItems/Packages/WinterGlyphBundle", PremiumOverride: 65 },
2527 { TypeName: "/Lotus/Types/StoreItems/Packages/KavatColorPackXmas", PremiumOverride: 40 },
2528 { TypeName: "/Lotus/Upgrades/Skins/Festivities/XmasSonicor", PremiumOverride: 20 },
2529 { TypeName: "/Lotus/Types/StoreItems/Packages/WinterAccessoriesBundle", PremiumOverride: 280 },
2530 { TypeName: "/Lotus/Upgrades/Skins/Harlequin/MirageXmasSkin", PremiumOverride: 40 },
2531 { TypeName: "/Lotus/Upgrades/Skins/Armor/SetThreeWinged/SolsticeSetThreeArmArmor", PremiumOverride: 50 },
2532 { TypeName: "/Lotus/Upgrades/Skins/Archer/WinterSolsticeSalix", PremiumOverride: 95 },
2533 { TypeName: "/Lotus/Upgrades/Skins/Armor/SetThreeWinged/SolsticeSetThreeChestArmor", PremiumOverride: 45 },
2534 { TypeName: "/Lotus/Upgrades/Skins/Frost/FrostXmasSkin", PremiumOverride: 40 },
2535 { TypeName: "/Lotus/Types/StoreItems/Packages/KubrowColorPackCandyCane", PremiumOverride: 50 },
2536 { TypeName: "/Lotus/Types/StoreItems/Packages/WinterGlyphBundleB", PremiumOverride: 110 },
2537 { TypeName: "/Lotus/Upgrades/Skins/Scarves/HolidayTurtleNeckScarf", PremiumOverride: 50 },
2538 { TypeName: "/Lotus/Upgrades/Skins/Hammer/SolsticeHeliocor", PremiumOverride: 30 },
2539 { TypeName: "/Lotus/Types/StoreItems/Packages/KubrowColorPackReindeer", PremiumOverride: 50 },
2540 { TypeName: "/Lotus/Upgrades/Skins/Fairy/SolsticeFairySkin", PremiumOverride: 40 },
2541 { TypeName: "/Lotus/Types/StoreItems/Packages/SolsticeSetThreeArmorPack", PremiumOverride: 100 },
2542 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2017D", PremiumOverride: 20 },
2543 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2016B", PremiumOverride: 20 },
2544 { TypeName: "/Lotus/Upgrades/Skins/Catbrows/SolsticeCatbrowFur", PremiumOverride: 40 },
2545 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2016C", PremiumOverride: 20 },
2546 { TypeName: "/Lotus/Upgrades/Skins/Kubrows/Fur/SolsticeKubrowFur", PremiumOverride: 40 },
2547 { TypeName: "/Lotus/Upgrades/Skins/Armor/SetThreeWinged/SolsticeSetThreeLegArmor", PremiumOverride: 35 },
2548 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinterB2016D", PremiumOverride: 20 },
2549 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2016D", PremiumOverride: 20 },
2550 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinterB2016E", PremiumOverride: 20 },
2551 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinterB2016A", PremiumOverride: 20 },
2552 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinterB2016B", PremiumOverride: 20 },
2553 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinterB2016C", PremiumOverride: 20 },
2554 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinterB2016G", PremiumOverride: 20 },
2555 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinterB2016F", PremiumOverride: 20 },
2556 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2017B", PremiumOverride: 20 },
2557 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2017A", PremiumOverride: 20 },
2558 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2017E", PremiumOverride: 20 },
2559 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2016A", PremiumOverride: 20 },
2560 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2017C", PremiumOverride: 20 },
2561 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2018E", PremiumOverride: 20 },
2562 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2018B", PremiumOverride: 20 },
2563 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2018A", PremiumOverride: 20 },
2564 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2018C", PremiumOverride: 20 },
2565 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2017F", PremiumOverride: 20 },
2566 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageWinter2018D", PremiumOverride: 20 },
2567 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageGlyphJingleKavat", PremiumOverride: 20 },
2568 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageGlyphCarolingOctavia", PremiumOverride: 20 },
2569 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageGlyphSurpriseIvara", PremiumOverride: 20 },
2570 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageGlyphJollyGrendel", PremiumOverride: 20 },
2571 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageGlyphFestiveFloof", PremiumOverride: 20 },
2572 { TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageGlyphSkiGauss", PremiumOverride: 20 },
2573 { TypeName: "/Lotus/Upgrades/Skins/Festivities/XmasGlaxion", PremiumOverride: 20 }
2574 ];
2575
2576 pushFlashSales(worldState, storeItems, activation, expiry, "SEASONAL", buildLabel);
2577 }
2578
2508 2579 if (config.worldState?.anniversary != undefined) {
2509 2580 // Incompatible with: Use Tag from Warframe Anniversary for old Events, Wolf Hunt, Galleon Of Ghouls, Hallowed Flame, Hallowed Nightmares, Dog Days, Proxy Rebellion, Long Shadow
2510 2581 const goalsByWeek: Partial<IGoal>[][] = [
@@ -5007,17 +5078,6 @@ export const populateFeaturedGuilds = async (worldState: IWorldState): Promise<v
5007 5078 }
5008 5079 };
5009 5080
5010 const storeCategories: Record<string, { Name: string; Icon: string; AddToMenu?: boolean }> = {
5011 NEW_PLAYER: { Name: "/Lotus/Language/Store/NewPlayerCategoryTitle", Icon: "newplayer", AddToMenu: true },
5012 POPULAR: { Name: "/Lotus/Language/Menu/StorePopular", Icon: "popular", AddToMenu: true },
5013 HEIRLOOM: { Name: "/Lotus/Language/Store/HeirloomCategoryTitle", Icon: "heirloom", AddToMenu: true },
5014 SEASONAL: { Name: "/Lotus/Language/Store/SeasonalCategoryTitle", Icon: "seasonal", AddToMenu: true },
5015 SALE: { Name: "/Lotus/Language/Menu/Store_Sale", Icon: "sale", AddToMenu: true },
5016 WISH_LIST: { Name: "/Lotus/Language/Menu/Store_Wishlist", Icon: "wishlist" },
5017 PREMIUM_BUNDLES: { Name: "/Lotus/Language/Store/Store_PremiumBundles", Icon: "premiumbundles" },
5018 QUICK_BUY: { Name: "/Lotus/Language/Store/TopSeller_Title", Icon: "quickbuy" }
5019 };
5020
5021 5081 const pushFlashSales = (
5022 5082 ws: IWorldState,
5023 5083 storeItems: (Partial<IFlashSale> & { TypeName: string })[],
@@ -5028,42 +5088,28 @@ const pushFlashSales = (
5028 5088 ): void => {
5029 5089 ws.FlashSales.push(
5030 5090 ...storeItems.map(item => ({
5031 ...{
5032 StartDate: toMongoDate2(startDate, buildLabel),
5033 EndDate: toMongoDate2(endDate, buildLabel),
5034 ProductExpiryOverride: toMongoDate2(endDate, buildLabel),
5035 ShowInMarket: item.ShowInMarket ?? true,
5036 HideFromMarket: item.HideFromMarket ?? false,
5037 SupporterPack: item.SupporterPack ?? false,
5038 Discount: item.Discount ?? 0,
5039 BogoBuy: item.BogoBuy ?? 0,
5040 BogoGet: item.BogoGet ?? 0,
5041 RegularOverride: item.RegularOverride ?? 0,
5042 PremiumOverride: item.PremiumOverride ?? 0,
5043 Featured: item.Featured ?? category != "POPULAR",
5044 Popular: item.Popular ?? category == "POPULAR",
5045 BannerIndex: item.BannerIndex ?? 10
5046 },
5047 ...item
5091 ...item,
5092 StartDate: toMongoDate2(startDate, buildLabel),
5093 EndDate: toMongoDate2(endDate, buildLabel),
5094 ProductExpiryOverride: toMongoDate2(endDate, buildLabel),
5095 // The following fields are not needed nowadays, but might be needed for Market 2.0 (pre-U25):
5096 ShowInMarket: item.ShowInMarket ?? true,
5097 HideFromMarket: item.HideFromMarket ?? false,
5098 SupporterPack: item.SupporterPack ?? false,
5099 Discount: item.Discount ?? 0,
5100 BogoBuy: item.BogoBuy ?? 0,
5101 BogoGet: item.BogoGet ?? 0,
5102 RegularOverride: item.RegularOverride ?? 0,
5103 PremiumOverride: item.PremiumOverride ?? 0,
5104 Featured: item.Featured ?? category != "POPULAR",
5105 Popular: item.Popular ?? category == "POPULAR",
5106 BannerIndex: item.BannerIndex ?? 10
5048 5107 }))
5049 5108 );
5050 5109
5051 if (category in storeCategories) {
5052 const categoryData = storeCategories[category];
5053 const seasonalCategory = ws.InGameMarket.LandingPage.Categories.find(c => c.CategoryName == category);
5054 const seasonalItems = storeItems.map(item => item.TypeName);
5055
5056 if (seasonalCategory) {
5057 seasonalCategory.Items ??= [];
5058 seasonalCategory.Items.push(...seasonalItems);
5059 } else {
5060 ws.InGameMarket.LandingPage.Categories.push({
5061 ...{
5062 CategoryName: category,
5063 Items: seasonalItems
5064 },
5065 ...categoryData
5066 });
5067 }
5110 const seasonalCategory = ws.InGameMarket.LandingPage.Categories.find(c => c.CategoryName == category);
5111 if (!seasonalCategory) {
5112 throw new Error(`No market category ${seasonalCategory} in static worldState data`);
5068 5113 }
5114 seasonalCategory.Items!.push(...storeItems.map(x => x.TypeName));
5069 5115 };
Modified src/types/worldStateTypes.ts +8 -8
@@ -453,14 +453,14 @@ export type TCircuitGameMode =
453 453
454 454 export interface IFlashSale {
455 455 TypeName: string;
456 ShowInMarket: boolean;
457 HideFromMarket: boolean;
458 SupporterPack: boolean;
459 Discount: number;
460 BogoBuy: number;
461 BogoGet: number;
462 PremiumOverride: number;
463 RegularOverride: number;
456 ShowInMarket?: boolean;
457 HideFromMarket?: boolean;
458 SupporterPack?: boolean;
459 Discount?: number;
460 BogoBuy?: number;
461 BogoGet?: number;
462 PremiumOverride?: number;
463 RegularOverride?: number;
464 464 ProductExpiryOverride?: IMongoDateWithLegacySupport;
465 465 StartDate: IMongoDateWithLegacySupport;
466 466 EndDate: IMongoDateWithLegacySupport;
Modified static/fixed_responses/worldState/worldState.json +33 -2
@@ -38,11 +38,17 @@
38 38 "/Lotus/StoreItems/Types/StoreItems/SlotItems/TwoWeaponSlotItem",
39 39 "/Lotus/StoreItems/Powersuits/Wisp/Wisp",
40 40 "/Lotus/StoreItems/Weapons/Tenno/Shotgun/Shotgun",
41 "/Lotus/StoreItems/Powersuits/Rhino/Rhino",
41 42 "/Lotus/StoreItems/Weapons/Corpus/Pistols/CrpAirPistol/CrpAirPistolArray",
42 "/Lotus/StoreItems/Upgrades/Skins/Scarves/FlameScarf",
43 43 "/Lotus/Types/StoreItems/Boosters/AffinityBooster3DayStoreItem"
44 44 ]
45 45 },
46 {
47 "CategoryName": "NEW",
48 "Name": "/Lotus/Language/Menu/Store_New",
49 "Icon": "new",
50 "Items": []
51 },
46 52 {
47 53 "CategoryName": "POPULAR",
48 54 "Name": "/Lotus/Language/Menu/StorePopular",
@@ -65,13 +71,32 @@
65 71 "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/DaxDuviriHammer/DaxDuviriHammerWeapon"
66 72 ]
67 73 },
74 {
75 "CategoryName": "SEASONAL",
76 "Name": "/Lotus/Language/Store/SeasonalCategoryTitle",
77 "Icon": "seasonal",
78 "AddToMenu": true,
79 "Items": []
80 },
81 {
82 "CategoryName": "COMMUNITY",
83 "Name": "/Lotus/Language/Store/CommunityCategoryTitle",
84 "Icon": "community",
85 "AddToMenu": true,
86 "Items": []
87 },
68 88 {
69 89 "CategoryName": "HEIRLOOM",
70 90 "Name": "/Lotus/Language/Store/HeirloomCategoryTitle",
71 91 "Icon": "heirloom",
72 92 "AddToMenu": true,
73 93 "Items": [
74 "/Lotus/Types/StoreItems/Packages/2025Bundles/RhinoHeirloomPack",
94 "/Lotus/StoreItems/Upgrades/Skins/Berserker/ValkyrHeirloomSkin",
95 "/Lotus/StoreItems/Upgrades/Skins/Crowns/HeirloomValkyrCrown",
96 "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerValkyrHeirloom",
97 "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardValkyrHeirloom",
98 "/Lotus/StoreItems/Types/StoreItems/AvatarImages/HeirloomValkyrGlyph",
99 "/Lotus/StoreItems/Upgrades/Skins/Sigils/HeirloomValkyrSigil",
75 100 "/Lotus/Types/StoreItems/Packages/HeirloomPackRhino",
76 101 "/Lotus/StoreItems/Upgrades/Skins/Rhino/RhinoHeirloomSkin",
77 102 "/Lotus/StoreItems/Upgrades/Skins/Crowns/HeirloomRhinoCrown",
@@ -113,6 +138,12 @@
113 138 "Name": "/Lotus/Language/Menu/Store_Wishlist",
114 139 "Icon": "wishlist",
115 140 "Items": []
141 },
142 {
143 "CategoryName": "QUICK_BUY",
144 "Name": "/Lotus/Language/Store/TopSeller_Title",
145 "Icon": "quickbuy",
146 "Items": ["/Lotus/Types/StoreItems/Packages/FormaPack", "/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst", "/Lotus/StoreItems/Types/Items/MiscItems/WeaponUtilityUnlocker"]
116 147 }
117 148 ]
118 149 }
Modified static/webui/index.html +8 -0
@@ -1490,6 +1490,14 @@
1490 1490 <option value="false" data-loc="disabled"></option>
1491 1491 </select>
1492 1492 </div>
1493 <div class="form-group mt-2">
1494 <label class="form-label" for="worldState.xmasOverride" data-loc="worldState_xmas"></label>
1495 <select class="form-control" id="worldState.xmasOverride" data-default="null">
1496 <option value="null" data-loc="normal"></option>
1497 <option value="true" data-loc="enabled"></option>
1498 <option value="false" data-loc="disabled"></option>
1499 </select>
1500 </div>
1493 1501 <div class="form-group mt-2">
1494 1502 <label class="form-label" for="worldState.lunarNewYear" data-loc="worldState_lunarNewYear"></label>
1495 1503 <select class="form-control" id="worldState.lunarNewYear" data-default="null">
Modified static/webui/translations/de.js +1 -0
@@ -338,6 +338,7 @@ dict = {
338 338 worldState_baroTennoConRelay: `Baros TennoCon-Relais`,
339 339 worldState_starDays: `Sternen-Tage`,
340 340 worldState_saintPatrick: `St. Patrick’s Day`,
341 worldState_xmas: `[UNTRANSLATED] Tennobaum`,
341 342 worldState_lunarNewYear: `Neues Mondjahr`,
342 343 worldState_lunarNewYearEntry: `|VAL|: Jahr |ANIMAL|`,
343 344 worldState_lunarNewYearAnimal1: `der Ratte`,
Modified static/webui/translations/en.js +1 -0
@@ -337,6 +337,7 @@ dict = {
337 337 worldState_baroTennoConRelay: `Baro's TennoCon Relay`,
338 338 worldState_starDays: `Star Days`,
339 339 worldState_saintPatrick: `St. Patrick’s Day`,
340 worldState_xmas: `Tennobaum`,
340 341 worldState_lunarNewYear: `Lunar New Year`,
341 342 worldState_lunarNewYearEntry: `|VAL|: Year of the |ANIMAL|`,
342 343 worldState_lunarNewYearAnimal1: `Rat`,
Modified static/webui/translations/es.js +1 -0
@@ -338,6 +338,7 @@ dict = {
338 338 worldState_baroTennoConRelay: `Repetidor de Baro de la TennoCon`,
339 339 worldState_starDays: `Días estelares`,
340 340 worldState_saintPatrick: `Día de San Patricio`,
341 worldState_xmas: `[UNTRANSLATED] Tennobaum`,
341 342 worldState_lunarNewYear: `Año Nuevo Lunar`,
342 343 worldState_lunarNewYearEntry: `[UNTRANSLATED] |VAL|: Year of the |ANIMAL|`,
343 344 worldState_lunarNewYearAnimal1: `[UNTRANSLATED] Rat`,
Modified static/webui/translations/fr.js +1 -0
@@ -338,6 +338,7 @@ dict = {
338 338 worldState_baroTennoConRelay: `Relais Baro TennoCon`,
339 339 worldState_starDays: `Jours Stellaires`,
340 340 worldState_saintPatrick: `Saint-Patrick`,
341 worldState_xmas: `[UNTRANSLATED] Tennobaum`,
341 342 worldState_lunarNewYear: `Nouvel An Lunaire`,
342 343 worldState_lunarNewYearEntry: `|VAL|: Année |ANIMAL|`,
343 344 worldState_lunarNewYearAnimal1: `du Rat`,
Modified static/webui/translations/ru.js +1 -0
Modified static/webui/translations/uk.js +1 -0
Modified static/webui/translations/zh.js +1 -0