From 943574bf3a671b34fdc4b31520298ebabea82755 Mon Sep 17 00:00:00 2001 From: Animan8000 Date: Sun, 8 Jun 2025 11:14:28 -0700 Subject: [PATCH 01/14] chore(webui): update Chinese translation (#2134) Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2134 Co-authored-by: Animan8000 Co-committed-by: Animan8000 --- static/webui/translations/zh.js | 66 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 5b38f847..49f39413 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -1,4 +1,4 @@ -// Chinese translation by meb154 +// Chinese translation by meb154 & bishan178 dict = { general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, general_addButton: `添加`, @@ -28,7 +28,7 @@ dict = { code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`, code_noEquipmentToRankUp: `没有可升级的装备。`, code_succAdded: `已成功添加。`, - code_succRemoved: `[UNTRANSLATED] Successfully removed.`, + code_succRemoved: `已成功移除。`, code_buffsNumber: `增益数量`, code_cursesNumber: `负面数量`, code_rerollsNumber: `洗卡次数`, @@ -44,22 +44,22 @@ dict = { code_gild: `镀金`, code_moa: `恐鸟`, code_zanuka: `猎犬`, - code_stage: `[UNTRANSLATED] Stage`, - code_complete: `[UNTRANSLATED] Complete`, - code_nextStage: `[UNTRANSLATED] Next stage`, - code_prevStage: `[UNTRANSLATED] Previous stage`, - code_reset: `[UNTRANSLATED] Reset`, - code_setInactive: `[UNTRANSLATED] Make the quest inactive`, - code_completed: `[UNTRANSLATED] Completed`, - code_active: `[UNTRANSLATED] Active`, + code_stage: `阶段`, + code_complete: `完成`, + code_nextStage: `下一阶段`, + code_prevStage: `上一阶段`, + code_reset: `重置`, + code_setInactive: `使任务处于未激活状态`, + code_completed: `已完成`, + code_active: `正在执行`, code_pigment: `颜料`, - code_mature: `[UNTRANSLATED] Mature for combat`, - code_unmature: `[UNTRANSLATED] Regress genetic aging`, + code_mature: `成长至战备`, + code_unmature: `逆转衰老基因`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`, login_emailLabel: `电子邮箱`, login_passwordLabel: `密码`, login_loginButton: `登录`, - login_registerButton: `[UNTRANSLATED] Register`, + login_registerButton: `注册账号`, navbar_logout: `退出登录`, navbar_renameAccount: `重命名账户`, navbar_deleteAccount: `删除账户`, @@ -82,7 +82,7 @@ dict = { inventory_operatorAmps: `增幅器`, inventory_hoverboards: `K式悬浮板`, inventory_moaPets: `恐鸟`, - inventory_kubrowPets: `[UNTRANSLATED] Beasts`, + inventory_kubrowPets: `动物同伴`, inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddWeapons: `添加缺失武器`, @@ -111,15 +111,15 @@ dict = { currency_owned: `当前拥有 |COUNT|。`, powersuit_archonShardsLabel: `执刑官源力石槽位`, powersuit_archonShardsDescription: `您可以使用这些无限插槽应用各种强化效果`, - powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`, + powersuit_archonShardsDescription2: `请注意, 在加载时, 每个执政官源力石都需要一定的时间来生效。`, mods_addRiven: `添加裂罅MOD`, mods_fingerprint: `印记`, mods_fingerprintHelp: `需要印记相关的帮助?`, mods_rivens: `裂罅MOD`, mods_mods: `Mods`, - mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`, - mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`, - mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`, + mods_addMissingUnrankedMods: `添加所有缺失的Mods`, + mods_removeUnranked: `删除所有未升级的Mods`, + mods_addMissingMaxRankMods: `添加所有缺失的满级Mods`, cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 |DISPLAYNAME| 添加到 config.json 的 administratorNames 中。`, cheats_server: `服务器`, cheats_skipTutorial: `跳过教程`, @@ -131,33 +131,33 @@ dict = { cheats_infiniteEndo: `无限内融核心`, cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteHelminthMaterials: `无限Helminth材料`, - cheats_claimingBlueprintRefundsIngredients: `[UNTRANSLATED] Claiming Blueprint Refunds Ingredients`, - cheats_dontSubtractVoidTraces: `[UNTRANSLATED] Don't Subtract Void Traces`, - cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`, + cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`, + cheats_dontSubtractVoidTraces: `虚空光体无消耗`, + cheats_dontSubtractConsumables: `消耗物品使用时损耗`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, cheats_unlockAllFlavourItems: `解锁所有装饰物品`, cheats_unlockAllSkins: `解锁所有外观`, cheats_unlockAllCapturaScenes: `解锁所有Captura场景`, - cheats_unlockAllDecoRecipes: `[UNTRANSLATED] Unlock All Dojo Deco Recipes`, + cheats_unlockAllDecoRecipes: `解锁所有道场配方`, cheats_universalPolarityEverywhere: `全局万用极性`, cheats_unlockDoubleCapacityPotatoesEverywhere: `全物品自带Orokin反应堆`, cheats_unlockExilusEverywhere: `全物品自带适配器`, cheats_unlockArcanesEverywhere: `全物品自带赋能适配器`, cheats_noDailyStandingLimits: `无每日声望限制`, - cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`, - cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`, - cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`, - cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`, - cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`, - cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`, - cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, - cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, + cheats_noDailyFocusLimit: `指挥官专精无每日获取上限`, + cheats_noArgonCrystalDecay: `氩结晶无衰变`, + cheats_noMasteryRankUpCooldown: `段位考核无冷却时间`, + cheats_noVendorPurchaseLimits: `商城或商人无购买限制`, + cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`, + cheats_noKimCooldowns: `无 KIM 冷却时间`, + cheats_syndicateMissionsRepeatable: `集团任务可重复`, + cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, cheats_instantResourceExtractorDrones: `即时资源采集无人机`, - cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`, - cheats_skipClanKeyCrafting: `[UNTRANSLATED] Skip Clan Key Crafting`, + cheats_noResourceExtractorDronesDamage: `资源提取器不会损毁`, + cheats_skipClanKeyCrafting: `跳过氏族钥匙制作, 进入道场无需氏族钥匙`, cheats_noDojoRoomBuildStage: `无视道场房间建造阶段`, - cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`, + cheats_noDojoDecoBuildStage: `道场装饰建造立即完成`, cheats_fastDojoRoomDestruction: `快速拆除道场房间`, cheats_noDojoResearchCosts: `无视道场研究消耗`, cheats_noDojoResearchTime: `无视道场研究时间`, From 4535b193e098a306468e73d1366a04ee21d5e7f3 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 9 Jun 2025 03:37:42 -0700 Subject: [PATCH 02/14] chore: handle nightwaveOverride having an invalid value (#2133) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2133 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/worldStateService.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 1dee4116..5bea6f20 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -19,6 +19,7 @@ import { IWorldState } from "../types/worldStateTypes"; import { version_compare } from "../helpers/inventoryHelpers"; +import { logger } from "../utils/logger"; const sortieBosses = [ "SORTIE_BOSS_HYENA", @@ -1275,7 +1276,13 @@ export const isArchwingMission = (node: IRegion): boolean => { export const getNightwaveSyndicateTag = (buildLabel: string | undefined): string | undefined => { if (config.worldState?.nightwaveOverride) { - return config.worldState.nightwaveOverride; + if (config.worldState.nightwaveOverride in nightwaveTagToSeason) { + return config.worldState.nightwaveOverride; + } + logger.warn(`ignoring invalid config value for worldState.nightwaveOverride`, { + value: config.worldState.nightwaveOverride, + valid_values: Object.keys(nightwaveTagToSeason) + }); } if (!buildLabel || version_compare(buildLabel, "2025.05.20.10.18") >= 0) { return "RadioLegionIntermission13Syndicate"; From 870c96485452aa6eca27b07a1be8d789367dc1c0 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 9 Jun 2025 06:54:58 -0700 Subject: [PATCH 03/14] feat: add eidolonOverride & vallisOverride to replace lockTime (#2135) I think for now it's best to keep the client time somewhat in sync with the server/database time to avoid various issues. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2135 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- README.md | 3 +- config.json.example | 3 +- src/services/configService.ts | 3 +- src/services/worldStateService.ts | 55 ++++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 24d3b2fb..e8d2b419 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi - `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`. - `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`. -- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE. +- `worldState.eidolonOverride` can be set to `day` or `night` to lock the time to day/fass and night/vome on Plains of Eidolon/Cambion Drift. +- `worldState.vallisOverride` can be set to `warm` or `cold` to lock the temperature on Orb Vallis. - `worldState.nightwaveOverride` will lock the nightwave season, assuming the client is new enough for it. Valid values: - `RadioLegionIntermission13Syndicate` for Nora's Mix Vol. 9 - `RadioLegionIntermission12Syndicate` for Nora's Mix Vol. 8 diff --git a/config.json.example b/config.json.example index 4000024f..b9ebaab0 100644 --- a/config.json.example +++ b/config.json.example @@ -55,7 +55,8 @@ "affinityBoost": false, "resourceBoost": false, "starDays": true, - "lockTime": 0, + "eidolonOverride": "", + "vallisOverride": "", "nightwaveOverride": "" } } diff --git a/src/services/configService.ts b/src/services/configService.ts index ba67cf06..dc8a78bc 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -61,7 +61,8 @@ interface IConfig { affinityBoost?: boolean; resourceBoost?: boolean; starDays?: boolean; - lockTime?: number; + eidolonOverride?: string; + vallisOverride?: string; nightwaveOverride?: string; }; } diff --git a/src/services/worldStateService.ts b/src/services/worldStateService.ts index 5bea6f20..e2041907 100644 --- a/src/services/worldStateService.ts +++ b/src/services/worldStateService.ts @@ -939,8 +939,61 @@ const getCalendarSeason = (week: number): ICalendarSeason => { }; }; +const doesTimeSatsifyConstraints = (timeSecs: number): boolean => { + if (config.worldState?.eidolonOverride) { + const eidolonEpoch = 1391992660; + const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000); + const eidolonCycleStart = eidolonEpoch + eidolonCycle * 9000; + const eidolonCycleEnd = eidolonCycleStart + 9000; + const eidolonCycleNightStart = eidolonCycleEnd - 3000; + if (config.worldState.eidolonOverride == "day") { + if ( + //timeSecs < eidolonCycleStart || + isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleNightStart * 1000) + ) { + return false; + } + } else { + if ( + timeSecs < eidolonCycleNightStart || + isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, eidolonCycleEnd * 1000) + ) { + return false; + } + } + } + + if (config.worldState?.vallisOverride) { + const vallisEpoch = 1541837628; + const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600); + const vallisCycleStart = vallisEpoch + vallisCycle * 1600; + const vallisCycleEnd = vallisCycleStart + 1600; + const vallisCycleColdStart = vallisCycleStart + 400; + if (config.worldState.vallisOverride == "cold") { + if ( + timeSecs < vallisCycleColdStart || + isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleEnd * 1000) + ) { + return false; + } + } else { + if ( + //timeSecs < vallisCycleStart || + isBeforeNextExpectedWorldStateRefresh(timeSecs * 1000, vallisCycleColdStart * 1000) + ) { + return false; + } + } + } + + return true; +}; + export const getWorldState = (buildLabel?: string): IWorldState => { - const timeSecs = config.worldState?.lockTime || Math.round(Date.now() / 1000); + let timeSecs = Math.round(Date.now() / 1000); + while (!doesTimeSatsifyConstraints(timeSecs)) { + timeSecs -= 60; + } const timeMs = timeSecs * 1000; const day = Math.trunc((timeMs - EPOCH) / 86400000); const week = Math.trunc(day / 7); From 2b555a64564bd6769c5f3b9193820c48eb234186 Mon Sep 17 00:00:00 2001 From: bishan178 Date: Mon, 9 Jun 2025 08:20:34 -0700 Subject: [PATCH 04/14] chore(webui): update Chinese translation (#2137) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2137 Co-authored-by: bishan178 Co-committed-by: bishan178 --- static/webui/translations/zh.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index 49f39413..d46ce77d 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -18,7 +18,7 @@ dict = { code_kDrive: `K式悬浮板`, code_legendaryCore: `传奇核心`, code_traumaticPeculiar: `创伤怪奇`, - code_starter: `|MOD| (有瑕疵的)`, + code_starter: `|MOD|(有瑕疵的)`, code_badItem: `(Imposter)`, code_maxRank: `满级`, code_rename: `重命名`, @@ -39,7 +39,7 @@ dict = { code_count: `数量`, code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, - code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗?`, + code_addModsConfirm: `确定要向账户添加 |COUNT| 张MOD吗?`, code_succImport: `导入成功。`, code_gild: `镀金`, code_moa: `恐鸟`, @@ -53,7 +53,7 @@ dict = { code_completed: `已完成`, code_active: `正在执行`, code_pigment: `颜料`, - code_mature: `成长至战备`, + code_mature: `成长并战备`, code_unmature: `逆转衰老基因`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`, login_emailLabel: `电子邮箱`, @@ -83,21 +83,21 @@ dict = { inventory_hoverboards: `K式悬浮板`, inventory_moaPets: `恐鸟`, inventory_kubrowPets: `动物同伴`, - inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`, + inventory_evolutionProgress: `灵化之源进度`, inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddSpaceSuits: `添加缺失Archwing`, inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`, inventory_bulkAddSentinels: `添加缺失守护`, inventory_bulkAddSentinelWeapons: `添加缺失守护武器`, - inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`, + inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源`, inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`, inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`, inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, - inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`, + inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`, quests_list: `任务`, quests_completeAll: `完成所有任务`, @@ -133,7 +133,7 @@ dict = { cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_claimingBlueprintRefundsIngredients: `取消蓝图制造时返还材料`, cheats_dontSubtractVoidTraces: `虚空光体无消耗`, - cheats_dontSubtractConsumables: `消耗物品使用时损耗`, + cheats_dontSubtractConsumables: `消耗物品使用时无损耗`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, cheats_unlockAllFlavourItems: `解锁所有装饰物品`, @@ -162,12 +162,12 @@ dict = { cheats_noDojoResearchCosts: `无视道场研究消耗`, cheats_noDojoResearchTime: `无视道场研究时间`, cheats_fastClanAscension: `快速升级氏族`, - cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, + cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, cheats_saveSettings: `保存设置`, cheats_account: `账户`, cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_helminthUnlockAll: `完全升级Helminth`, - cheats_intrinsicsUnlockAll: `[UNTRANSLATED] Max Rank All Intrinsics`, + cheats_intrinsicsUnlockAll: `所有内源之力最大等级`, cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeButton: `更改`, cheats_none: `无`, From c736310ff3f909e2207f5eabf6d56a4253fba544 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:03:56 -0700 Subject: [PATCH 05/14] feat: send clan search message when reaching MR 2 (#2136) Closes #1960 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2136 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/trainingResultController.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/controllers/api/trainingResultController.ts b/src/controllers/api/trainingResultController.ts index f44d7dae..0e436386 100644 --- a/src/controllers/api/trainingResultController.ts +++ b/src/controllers/api/trainingResultController.ts @@ -35,6 +35,17 @@ const trainingResultController: RequestHandler = async (req, res): Promise inventory.PlayerLevel += 1; inventory.TradesRemaining += 1; + if (inventory.PlayerLevel == 2) { + await createMessage(accountId, [ + { + sndr: "/Lotus/Language/Game/Maroo", + msg: "/Lotus/Language/Clan/MarooClanSearchDesc", + sub: "/Lotus/Language/Clan/MarooClanSearchTitle", + icon: "/Lotus/Interface/Icons/Npcs/Maroo.png" + } + ]); + } + await createMessage(accountId, [ { sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender", From 1979b20f8cc00b79f305478f1da3d69f90a3d43e Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 10 Jun 2025 05:49:43 -0700 Subject: [PATCH 06/14] fix: handle multiple syndicate title increases at once (#2139) Only really possible with nightwave afaik. Bug reported via #2138. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2139 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- .../api/syndicateSacrificeController.ts | 125 +++++++++--------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/src/controllers/api/syndicateSacrificeController.ts b/src/controllers/api/syndicateSacrificeController.ts index 8b1a9c7a..021608b3 100644 --- a/src/controllers/api/syndicateSacrificeController.ts +++ b/src/controllers/api/syndicateSacrificeController.ts @@ -3,7 +3,7 @@ import { RequestHandler } from "express"; import { getAccountIdForRequest } from "@/src/services/loginService"; import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus"; import { handleStoreItemAcquisition } from "@/src/services/purchaseService"; -import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService"; +import { addMiscItem, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { toStoreItem } from "@/src/services/itemDataService"; import { logger } from "@/src/utils/logger"; @@ -18,80 +18,83 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: data.AffiliationTag, Standing: 0 }) - 1]; } - const level = data.SacrificeLevel - (syndicate.Title ?? 0); + const oldLevel = syndicate.Title ?? 0; + const levelIncrease = data.SacrificeLevel - oldLevel; + if (levelIncrease < 1) { + throw new Error(`syndicate sacrifice needs an increase of at least 1`); + } + if (levelIncrease > 1 && !data.AllowMultiple) { + throw new Error(`desired syndicate level is an increase of ${levelIncrease}, max. allowed increase is 1`); + } + const res: ISyndicateSacrificeResponse = { AffiliationTag: data.AffiliationTag, InventoryChanges: {}, Level: data.SacrificeLevel, - LevelIncrease: level <= 0 ? 1 : level, + LevelIncrease: levelIncrease, NewEpisodeReward: false }; + // Process sacrifices and rewards for every level we're reaching const manifest = ExportSyndicates[data.AffiliationTag]; - let sacrifice: ISyndicateSacrifice | undefined; - let reward: string | undefined; - if (data.SacrificeLevel == 0) { - sacrifice = manifest.initiationSacrifice; - reward = manifest.initiationReward; - syndicate.Initiated = true; - } else { - sacrifice = manifest.titles?.find(x => x.level == data.SacrificeLevel)?.sacrifice; - } - - if (sacrifice) { - res.InventoryChanges = { ...updateCurrency(inventory, sacrifice.credits, false) }; - - const miscItemChanges = sacrifice.items.map(x => ({ - ItemType: x.ItemType, - ItemCount: x.ItemCount * -1 - })); - addMiscItems(inventory, miscItemChanges); - res.InventoryChanges.MiscItems = miscItemChanges; - } - - syndicate.Title ??= 0; - syndicate.Title += 1; - - if (reward) { - combineInventoryChanges( - res.InventoryChanges, - (await handleStoreItemAcquisition(reward, inventory)).InventoryChanges - ); - } - - // Quacks like a nightwave syndicate? - if (manifest.dailyChallenges) { - const title = manifest.titles!.find(x => x.level == syndicate.Title); - if (title) { - res.NewEpisodeReward = true; - let rewardType: string; - let rewardCount: number; - if (title.storeItemReward) { - rewardType = title.storeItemReward; - rewardCount = 1; - } else { - rewardType = toStoreItem(title.reward!.ItemType); - rewardCount = title.reward!.ItemCount; + for (let level = oldLevel + 1; level <= data.SacrificeLevel; ++level) { + let sacrifice: ISyndicateSacrifice | undefined; + if (level == 0) { + sacrifice = manifest.initiationSacrifice; + if (manifest.initiationReward) { + combineInventoryChanges( + res.InventoryChanges, + (await handleStoreItemAcquisition(manifest.initiationReward, inventory)).InventoryChanges + ); } - const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount)) - .InventoryChanges; - if (Object.keys(rewardInventoryChanges).length == 0) { - logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`); - const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType; - rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }]; - addMiscItems(inventory, rewardInventoryChanges.MiscItems); - } - combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges); + syndicate.Initiated = true; + } else { + sacrifice = manifest.titles?.find(x => x.level == level)?.sacrifice; } - } else { - if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) { - syndicate.FreeFavorsEarned ??= []; - if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) { - syndicate.FreeFavorsEarned.push(syndicate.Title); + + if (sacrifice) { + updateCurrency(inventory, sacrifice.credits, false, res.InventoryChanges); + + for (const item of sacrifice.items) { + addMiscItem(inventory, item.ItemType, item.ItemCount * -1, res.InventoryChanges); + } + } + + // Quacks like a nightwave syndicate? + if (manifest.dailyChallenges) { + const title = manifest.titles!.find(x => x.level == level); + if (title) { + res.NewEpisodeReward = true; + let rewardType: string; + let rewardCount: number; + if (title.storeItemReward) { + rewardType = title.storeItemReward; + rewardCount = 1; + } else { + rewardType = toStoreItem(title.reward!.ItemType); + rewardCount = title.reward!.ItemCount; + } + const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount)) + .InventoryChanges; + if (Object.keys(rewardInventoryChanges).length == 0) { + logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`); + const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType; + addMiscItem(inventory, nightwaveCredsItemType, 50, rewardInventoryChanges); + } + combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges); + } + } else { + if (level > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == level)) { + syndicate.FreeFavorsEarned ??= []; + if (!syndicate.FreeFavorsEarned.includes(level)) { + syndicate.FreeFavorsEarned.push(level); + } } } } + // Commit + syndicate.Title = data.SacrificeLevel; await inventory.save(); response.json(res); From 60236a115475a282d503d28820078e3c3ba914af Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Tue, 10 Jun 2025 09:48:13 -0700 Subject: [PATCH 07/14] chore: update PE+ (#2142) Closes #2141 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2142 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1730599..dc4968db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", - "warframe-public-export-plus": "^0.5.65", + "warframe-public-export-plus": "^0.5.66", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" @@ -3814,9 +3814,9 @@ } }, "node_modules/warframe-public-export-plus": { - "version": "0.5.65", - "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.65.tgz", - "integrity": "sha512-y/HN61lE5g8gx0Giutdl/jzQnQmw1u2uI0BiwKVW341nf42sKWQPsKsCVTL5x9MIDYyRCbFsMU+PazKC7byMdg==" + "version": "0.5.66", + "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.66.tgz", + "integrity": "sha512-AU7XQA96OfYrLm2RioCwDjjdI3IrsmUiqebXyE+bpM0iST+4x/NHu8LTRT4Oygfo/2OBtDYhib7G6re0EeAe5g==" }, "node_modules/warframe-riven-info": { "version": "0.1.2", diff --git a/package.json b/package.json index d540ff13..535dfa30 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "morgan": "^1.10.0", "ncp": "^2.0.0", "typescript": "^5.5", - "warframe-public-export-plus": "^0.5.65", + "warframe-public-export-plus": "^0.5.66", "warframe-riven-info": "^0.1.2", "winston": "^3.17.0", "winston-daily-rotate-file": "^5.0.0" From bdc41de8bbdc1708df645f4a75160d5621c32e21 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 11 Jun 2025 02:31:14 -0700 Subject: [PATCH 08/14] fix: set incubated pet as in stasis when one is already active (#2143) Previously was kept in incubating state which is obviously wrong Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2143 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/claimCompletedRecipeController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/api/claimCompletedRecipeController.ts b/src/controllers/api/claimCompletedRecipeController.ts index 6482c9a3..071a6c9c 100644 --- a/src/controllers/api/claimCompletedRecipeController.ts +++ b/src/controllers/api/claimCompletedRecipeController.ts @@ -118,7 +118,7 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) = break; } } - pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusIncubating; + pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis; } else if (recipe.secretIngredientAction != "SIA_UNBRAND") { InventoryChanges = { ...InventoryChanges, From 48234062297693ce659a527746bb8491fb6b70bd Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 11 Jun 2025 02:32:51 -0700 Subject: [PATCH 09/14] chore: don't fail missionInventoryUpdate on unknown items (#2144) It's possible we started a mission, deleted an item in the webui, and then finished it. The mission completion is still valid, we just can't update that item. Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2144 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/inventoryService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 6060f1e9..5194f771 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -1525,7 +1525,8 @@ export const applyClientEquipmentUpdates = ( gearArray.forEach(({ ItemId, XP, InfestationDate }) => { const item = category.id(fromOid(ItemId)); if (!item) { - throw new Error(`No item with id ${fromOid(ItemId)} in ${categoryName}`); + logger.warn(`Skipping unknown ${categoryName} item: id ${fromOid(ItemId)} not found`); + return; } if (XP) { From 1d813a1b1b04f1a969f8054057f517213752216f Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Wed, 11 Jun 2025 03:10:10 -0700 Subject: [PATCH 10/14] fix: toStoreItem not converting boosters (#2147) Bug reported via #2146 Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2147 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/services/itemDataService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/itemDataService.ts b/src/services/itemDataService.ts index 8af8a756..0e862283 100644 --- a/src/services/itemDataService.ts +++ b/src/services/itemDataService.ts @@ -233,7 +233,7 @@ export const isStoreItem = (type: string): boolean => { }; export const toStoreItem = (type: string): string => { - if (type.startsWith("/Lotus/Types/StoreItems/Boosters/")) { + if (type.startsWith("/Lotus/Types/Boosters/")) { const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == type); if (boosterEntry) { return boosterEntry[0]; From 62d4b9f6cb095040c0b1e1124ebb82b6a612ef08 Mon Sep 17 00:00:00 2001 From: nyaoouo Date: Thu, 12 Jun 2025 04:54:17 -0700 Subject: [PATCH 11/14] feat(webui): boosters (#2140) Co-authored-by: ny <64143453+nyaoouo@users.noreply.github.com> Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com> Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2140 Co-authored-by: nyaoouo Co-committed-by: nyaoouo --- .../custom/getItemListsController.ts | 12 +- .../custom/setBoosterController.ts | 45 ++++++ src/routes/custom.ts | 2 + static/webui/index.html | 15 ++ static/webui/script.js | 131 ++++++++++++++++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 11 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 src/controllers/custom/setBoosterController.ts diff --git a/src/controllers/custom/getItemListsController.ts b/src/controllers/custom/getItemListsController.ts index 4f03ee94..a9f8095f 100644 --- a/src/controllers/custom/getItemListsController.ts +++ b/src/controllers/custom/getItemListsController.ts @@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService" import { ExportArcanes, ExportAvionics, + ExportBoosters, ExportCustoms, ExportDrones, ExportGear, @@ -55,6 +56,7 @@ interface ItemLists { KubrowPets: ListedItem[]; EvolutionProgress: ListedItem[]; mods: ListedItem[]; + Boosters: ListedItem[]; } const relicQualitySuffixes: Record = { @@ -86,7 +88,8 @@ const getItemListsController: RequestHandler = (req, response) => { QuestKeys: [], KubrowPets: [], EvolutionProgress: [], - mods: [] + mods: [], + Boosters: [] }; for (const [uniqueName, item] of Object.entries(ExportWarframes)) { res[item.productCategory].push({ @@ -296,6 +299,13 @@ const getItemListsController: RequestHandler = (req, response) => { }); } + for (const item of Object.values(ExportBoosters)) { + res.Boosters.push({ + uniqueName: item.typeName, + name: getString(item.name, lang) + }); + } + response.json(res); }; diff --git a/src/controllers/custom/setBoosterController.ts b/src/controllers/custom/setBoosterController.ts new file mode 100644 index 00000000..28939614 --- /dev/null +++ b/src/controllers/custom/setBoosterController.ts @@ -0,0 +1,45 @@ +import { getAccountIdForRequest } from "@/src/services/loginService"; +import { getInventory } from "@/src/services/inventoryService"; +import { RequestHandler } from "express"; +import { ExportBoosters } from "warframe-public-export-plus"; + +const I32_MAX = 0x7fffffff; + +export const setBoosterController: RequestHandler = async (req, res) => { + const accountId = await getAccountIdForRequest(req); + const requests = req.body as { ItemType: string; ExpiryDate: number }[]; + const inventory = await getInventory(accountId, "Boosters"); + const boosters = inventory.Boosters; + if ( + requests.some(request => { + if (typeof request.ItemType !== "string") return true; + if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined) + return true; + if (typeof request.ExpiryDate !== "number") return true; + if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true; + return false; + }) + ) { + res.status(400).send("Invalid ItemType provided."); + return; + } + const now = Math.floor(Date.now() / 1000); + for (const { ItemType, ExpiryDate } of requests) { + if (ExpiryDate < now) { + // remove expired boosters + const index = boosters.findIndex(item => item.ItemType === ItemType); + if (index !== -1) { + boosters.splice(index, 1); + } + } else { + const boosterItem = boosters.find(item => item.ItemType === ItemType); + if (boosterItem) { + boosterItem.ExpiryDate = ExpiryDate; + } else { + boosters.push({ ItemType, ExpiryDate }); + } + } + } + await inventory.save(); + res.end(); +}; diff --git a/src/routes/custom.ts b/src/routes/custom.ts index 8411d996..7d8c7c82 100644 --- a/src/routes/custom.ts +++ b/src/routes/custom.ts @@ -23,6 +23,7 @@ import { setEvolutionProgressController } from "@/src/controllers/custom/setEvol import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; +import { setBoosterController } from "../controllers/custom/setBoosterController"; const customRouter = express.Router(); @@ -46,6 +47,7 @@ customRouter.post("/addXp", addXpController); customRouter.post("/import", importController); customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/setEvolutionProgress", setEvolutionProgressController); +customRouter.post("/setBooster", setBoosterController); customRouter.get("/config", getConfigDataController); customRouter.post("/config", updateConfigDataController); diff --git a/static/webui/index.html b/static/webui/index.html index 1b42793e..643270a7 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -416,6 +416,20 @@ +
+
+
+
+
+ + +
+ + +
+
+
+
@@ -804,6 +818,7 @@ + diff --git a/static/webui/script.js b/static/webui/script.js index f4abcb9f..ee16ffdf 100644 --- a/static/webui/script.js +++ b/static/webui/script.js @@ -1011,6 +1011,63 @@ function updateInventory() { } } document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? ""; + + document.getElementById("Boosters-list").innerHTML = ""; + const now = Math.floor(Date.now() / 1000); + data.Boosters.forEach(({ ItemType, ExpiryDate }) => { + if (ExpiryDate < now) { + // Booster has expired, skip it + return; + } + const tr = document.createElement("tr"); + { + const td = document.createElement("td"); + td.textContent = itemMap[ItemType]?.name ?? ItemType; + tr.appendChild(td); + } + { + const td = document.createElement("td"); + td.classList = "text-end text-nowrap"; + const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000); + const inlineForm = document.createElement("form"); + const input = document.createElement("input"); + + inlineForm.style.display = "inline-block"; + inlineForm.onsubmit = function (event) { + event.preventDefault(); + doChangeBoosterExpiry(ItemType, input); + }; + input.type = "datetime-local"; + input.classList.add("form-control"); + input.classList.add("form-control-sm"); + input.value = timeString; + let changed = false; + input.onchange = function () { + changed = true; + }; + input.onblur = function () { + if (changed) { + doChangeBoosterExpiry(ItemType, input); + } + }; + inlineForm.appendChild(input); + + td.appendChild(inlineForm); + + const removeButton = document.createElement("a"); + removeButton.title = loc("code_remove"); + removeButton.innerHTML = ``; + removeButton.href = "#"; + removeButton.onclick = function (event) { + event.preventDefault(); + setBooster(ItemType, 0); + }; + td.appendChild(removeButton); + + tr.appendChild(td); + } + document.getElementById("Boosters-list").appendChild(tr); + }); }); }); } @@ -2027,3 +2084,77 @@ function handleModularSelection(category) { }); }); } + +function setBooster(ItemType, ExpiryDate, callback) { + revalidateAuthz(() => { + $.post({ + url: "/custom/setBooster?" + window.authz, + contentType: "application/json", + data: JSON.stringify([ + { + ItemType, + ExpiryDate + } + ]) + }).done(function () { + updateInventory(); + if (callback) callback(); + }); + }); +} + +function doAcquireBoosters() { + const uniqueName = getKey(document.getElementById("acquire-type-Boosters")); + if (!uniqueName) { + $("#acquire-type-Boosters").addClass("is-invalid").focus(); + return; + } + const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days + setBooster(uniqueName, ExpiryDate, () => { + $("#acquire-type-Boosters").val(""); + updateInventory(); + }); +} + +function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { + console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value); + // cast local datetime string to unix timestamp + const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000; + if (isNaN(ExpiryDate)) { + ExpiryDateInput.addClass("is-invalid").focus(); + return false; + } + setBooster(ItemType, ExpiryDate); + return true; +} + +function formatDatetime(fmt, date) { + if (typeof date === "number") date = new Date(date); + return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => { + switch (match) { + case "%Y": + return date.getFullYear().toString(); + case "%y": + return date.getFullYear().toString().slice(-2); + case "%m": + return (date.getMonth() + 1).toString().padStart(2, "0"); + case "%D": + case "%d": + return date.getDate().toString().padStart(2, "0"); + case "%H": + return date.getHours().toString().padStart(2, "0"); + case "%h": + return (date.getHours() % 12).toString().padStart(2, "0"); + case "%M": + return date.getMinutes().toString().padStart(2, "0"); + case "%S": + case "%s": + return date.getSeconds().toString().padStart(2, "0"); + case "%P": + case "%p": + return date.getHours() < 12 ? "am" : "pm"; + default: + return match; + } + }); +} diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index eb721391..4e1b8a2d 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Quests`, quests_completeAll: `Alle Quests abschließen`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index cd718917..79cec6e7 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -97,6 +97,7 @@ dict = { inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`, + inventory_Boosters: `Boosters`, quests_list: `Quests`, quests_completeAll: `Complete All Quests`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 652a850c..72d8453b 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Misiones`, quests_completeAll: `Completar todas las misiones`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index 5a61111e..bc079ef0 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Toutes les Sentinelles au rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Quêtes`, quests_completeAll: `Compléter toutes les quêtes`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index 445c519a..c99ca154 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`, + inventory_Boosters: `[UNTRANSLATED] Boosters`, quests_list: `Квесты`, quests_completeAll: `Завершить все квесты`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index d46ce77d..d6eb68b3 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -98,6 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`, + inventory_Boosters: `加成器`, quests_list: `任务`, quests_completeAll: `完成所有任务`, From 62eeb313ec1f2c723b4f0ba704764c5a94aae053 Mon Sep 17 00:00:00 2001 From: nyaoouo Date: Thu, 12 Jun 2025 04:54:41 -0700 Subject: [PATCH 12/14] feat: nightwaveStandingMultiplier cheat (#2145) Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com> Co-authored-by: ny <64143453+nyaoouo@users.noreply.github.com> Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2145 Co-authored-by: nyaoouo Co-committed-by: nyaoouo --- src/services/configService.ts | 1 + src/services/inventoryService.ts | 5 +++-- static/webui/index.html | 4 ++++ static/webui/translations/de.js | 1 + static/webui/translations/en.js | 1 + static/webui/translations/es.js | 1 + static/webui/translations/fr.js | 1 + static/webui/translations/ru.js | 1 + static/webui/translations/zh.js | 1 + 9 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/services/configService.ts b/src/services/configService.ts index dc8a78bc..1989330a 100644 --- a/src/services/configService.ts +++ b/src/services/configService.ts @@ -65,6 +65,7 @@ interface IConfig { vallisOverride?: string; nightwaveOverride?: string; }; + nightwaveStandingMultiplier?: number; } export const configPath = path.join(repoDir, "config.json"); diff --git a/src/services/inventoryService.ts b/src/services/inventoryService.ts index 5194f771..ae9158c5 100644 --- a/src/services/inventoryService.ts +++ b/src/services/inventoryService.ts @@ -1773,13 +1773,14 @@ export const addChallenges = ( }) - 1 ]; } - affiliation.Standing += meta.standing!; + const standingToAdd = meta.standing! * (config.nightwaveStandingMultiplier ?? 1); + affiliation.Standing += standingToAdd; if (affiliationMods.length == 0) { affiliationMods.push({ Tag: nightwaveSyndicateTag }); } affiliationMods[0].Standing ??= 0; - affiliationMods[0].Standing += meta.standing!; + affiliationMods[0].Standing += standingToAdd; } } } diff --git a/static/webui/index.html b/static/webui/index.html index 643270a7..398eb647 100644 --- a/static/webui/index.html +++ b/static/webui/index.html @@ -736,6 +736,10 @@
+
+ + +
diff --git a/static/webui/translations/de.js b/static/webui/translations/de.js index 4e1b8a2d..db9492b3 100644 --- a/static/webui/translations/de.js +++ b/static/webui/translations/de.js @@ -164,6 +164,7 @@ dict = { cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`, cheats_fastClanAscension: `Schneller Clan-Aufstieg`, cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`, + cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_saveSettings: `Einstellungen speichern`, cheats_account: `Account`, cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`, diff --git a/static/webui/translations/en.js b/static/webui/translations/en.js index 79cec6e7..1cb2b365 100644 --- a/static/webui/translations/en.js +++ b/static/webui/translations/en.js @@ -163,6 +163,7 @@ dict = { cheats_noDojoResearchTime: `No Dojo Research Time`, cheats_fastClanAscension: `Fast Clan Ascension`, cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, + cheats_nightwaveStandingMultiplier: `Nightwave Standing Multiplier`, cheats_saveSettings: `Save Settings`, cheats_account: `Account`, cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 72d8453b..87d98ca3 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -164,6 +164,7 @@ dict = { cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, + cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_saveSettings: `Guardar configuración`, cheats_account: `Cuenta`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, diff --git a/static/webui/translations/fr.js b/static/webui/translations/fr.js index bc079ef0..4f039319 100644 --- a/static/webui/translations/fr.js +++ b/static/webui/translations/fr.js @@ -164,6 +164,7 @@ dict = { cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_fastClanAscension: `Ascension de clan rapide`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`, + cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_saveSettings: `Sauvegarder les paramètres`, cheats_account: `Compte`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, diff --git a/static/webui/translations/ru.js b/static/webui/translations/ru.js index c99ca154..e159c189 100644 --- a/static/webui/translations/ru.js +++ b/static/webui/translations/ru.js @@ -164,6 +164,7 @@ dict = { cheats_noDojoResearchTime: `Мгновенные Исследование Додзё`, cheats_fastClanAscension: `Мгновенное Вознесение Клана`, cheats_spoofMasteryRank: `Подделанный ранг мастерства (-1 для отключения)`, + cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, cheats_saveSettings: `Сохранить настройки`, cheats_account: `Аккаунт`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, diff --git a/static/webui/translations/zh.js b/static/webui/translations/zh.js index d6eb68b3..813c3d38 100644 --- a/static/webui/translations/zh.js +++ b/static/webui/translations/zh.js @@ -164,6 +164,7 @@ dict = { cheats_noDojoResearchTime: `无视道场研究时间`, cheats_fastClanAscension: `快速升级氏族`, cheats_spoofMasteryRank: `伪造精通段位(-1为禁用)`, + cheats_nightwaveStandingMultiplier: `午夜电波声望倍率`, cheats_saveSettings: `保存设置`, cheats_account: `账户`, cheats_unlockAllFocusSchools: `解锁所有专精学派`, From 54a73ad5d7eab867a1701ccf66d56446db96c226 Mon Sep 17 00:00:00 2001 From: Sainan <63328889+Sainan@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:20:32 -0700 Subject: [PATCH 13/14] fix: syndicate initiation (#2149) Was accidentially broken by 1979b20f8cc00b79f305478f1da3d69f90a3d43e Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2149 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com> --- src/controllers/api/syndicateSacrificeController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/syndicateSacrificeController.ts b/src/controllers/api/syndicateSacrificeController.ts index 021608b3..64b46bbd 100644 --- a/src/controllers/api/syndicateSacrificeController.ts +++ b/src/controllers/api/syndicateSacrificeController.ts @@ -20,8 +20,8 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp const oldLevel = syndicate.Title ?? 0; const levelIncrease = data.SacrificeLevel - oldLevel; - if (levelIncrease < 1) { - throw new Error(`syndicate sacrifice needs an increase of at least 1`); + if (levelIncrease < 0) { + throw new Error(`syndicate sacrifice can not decrease level`); } if (levelIncrease > 1 && !data.AllowMultiple) { throw new Error(`desired syndicate level is an increase of ${levelIncrease}, max. allowed increase is 1`); @@ -37,7 +37,7 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp // Process sacrifices and rewards for every level we're reaching const manifest = ExportSyndicates[data.AffiliationTag]; - for (let level = oldLevel + 1; level <= data.SacrificeLevel; ++level) { + for (let level = oldLevel + levelIncrease; level <= data.SacrificeLevel; ++level) { let sacrifice: ISyndicateSacrifice | undefined; if (level == 0) { sacrifice = manifest.initiationSacrifice; From 3c019e41b9a8b75ca82190cc2f22fd7af614a928 Mon Sep 17 00:00:00 2001 From: hxedcl Date: Thu, 12 Jun 2025 13:23:51 -0700 Subject: [PATCH 14/14] chore(webui): update to Spanish translation (#2151) Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/2151 Co-authored-by: hxedcl Co-committed-by: hxedcl --- static/webui/translations/es.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/webui/translations/es.js b/static/webui/translations/es.js index 87d98ca3..7d57a797 100644 --- a/static/webui/translations/es.js +++ b/static/webui/translations/es.js @@ -98,7 +98,7 @@ dict = { inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, - inventory_Boosters: `[UNTRANSLATED] Boosters`, + inventory_Boosters: `Potenciadores`, quests_list: `Misiones`, quests_completeAll: `Completar todas las misiones`, @@ -164,7 +164,7 @@ dict = { cheats_noDojoResearchTime: `Sin tiempo de investigación del dojo`, cheats_fastClanAscension: `Ascenso rápido del clan`, cheats_spoofMasteryRank: `Rango de maestría simulado (-1 para desactivar)`, - cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`, + cheats_nightwaveStandingMultiplier: `Multiplicador de Reputación de Onda Nocturna`, cheats_saveSettings: `Guardar configuración`, cheats_account: `Cuenta`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,