From a85eb152d154a1fe6fe1a06d1e2c2faaa63eaa60 Mon Sep 17 00:00:00 2001 From: Magnus Root Date: Sun, 12 Jul 2026 14:06:44 +0300 Subject: [PATCH] Fixed text in chaos mode --- corpsestarch_game.go | 19 ++++++++--- corpsestarch_tui.go | 17 ++++----- corpsestarch_tui_test.go | 74 ++++++++++++++++++++++++++++++++++++++++ ggc | 4 +-- ggc.exe | 4 +-- translations.go | 61 +++++++++++++++++++-------------- 6 files changed, 137 insertions(+), 42 deletions(-) diff --git a/corpsestarch_game.go b/corpsestarch_game.go index 2a05a33..0cd73a7 100644 --- a/corpsestarch_game.go +++ b/corpsestarch_game.go @@ -42,6 +42,17 @@ func (def corpseStarchServitorDef) effectiveNameKey(mode CorpseStarchMode) strin return def.NameKey } +// modeKey выбирает один из двух ключей перевода в зависимости от +// текущего режима партии — для сообщений и надписей, у которых (в +// отличие от сервиторов/миссий) нет отдельного def-объекта с парой +// имперского/демонического ключа. +func (g *CorpseStarchGameState) modeKey(imperialKey, demonicKey string) string { + if g.Mode == CorpseStarchModeDemonic { + return demonicKey + } + return imperialKey +} + // corpseStarchMissionDef — статическое описание одной доступной // миссии зачистки (или, после перерождения, вторжения на планеты // Империума — тот же механизм, другое название). @@ -374,7 +385,7 @@ func (g *CorpseStarchGameState) triggerWarpBreach(now time.Time) { g.ChaosIncursionUntil = now.Add(corpseStarchIncursionDuration) g.ChaosDefendClicks = 0 g.ChaosMarks++ - g.addLog("corpsestarch.log.warp_breach") + g.addLog(g.modeKey("corpsestarch.log.warp_breach", "corpsestarch.log.warp_breach.demonic")) } // ChaosOfferPending — пора ли показать предложение перейти на @@ -451,9 +462,9 @@ func (g *CorpseStarchGameState) DefendAgainstChaos(now time.Time) bool { func (g *CorpseStarchGameState) resolveChaosIncursion() { if g.ChaosDefendClicks >= corpseStarchDefendClicksForBonus { g.earn(corpseStarchDefendBonusReward) - g.addLog("corpsestarch.log.incursion_repelled_bonus") + g.addLog(g.modeKey("corpsestarch.log.incursion_repelled_bonus", "corpsestarch.log.incursion_repelled_bonus.demonic")) } else { - g.addLog("corpsestarch.log.incursion_repelled") + g.addLog(g.modeKey("corpsestarch.log.incursion_repelled", "corpsestarch.log.incursion_repelled.demonic")) } g.ChaosIncursionUntil = time.Time{} g.ChaosDefendClicks = 0 @@ -533,4 +544,4 @@ func (g *CorpseStarchGameState) ServitorUnlocked(idx int) bool { // MissionUnlocked проверяет, видна ли конкретная миссия idx. func (g *CorpseStarchGameState) MissionUnlocked(idx int) bool { return g.TotalEarned >= corpseStarchMissionDefs[idx].UnlockAt -} +} \ No newline at end of file diff --git a/corpsestarch_tui.go b/corpsestarch_tui.go index 1e09b04..7922021 100644 --- a/corpsestarch_tui.go +++ b/corpsestarch_tui.go @@ -47,7 +47,8 @@ func NewCorpseStarchModel() CorpseStarchModel { g.Tick(now) // честно начисляем доход за время, пока партия была закрыта m := CorpseStarchModel{game: g, termWidth: 80} if earned := g.Starch - before; earned >= 1 { - m.message = Tf("corpsestarch.msg.welcome_back", formatStarch(earned)) + key := g.modeKey("corpsestarch.msg.welcome_back", "corpsestarch.msg.welcome_back.demonic") + m.message = Tf(key, formatStarch(earned)) } return m } @@ -84,10 +85,10 @@ func (m CorpseStarchModel) buildActions(now time.Time) []corpseStarchAction { var actions []corpseStarchAction actions = append(actions, corpseStarchAction{ - Label: Tf("corpsestarch.action.requisition", formatStarch(g.currentClickYield(now))), + Label: Tf(g.modeKey("corpsestarch.action.requisition", "corpsestarch.action.requisition.demonic"), formatStarch(g.currentClickYield(now))), Handle: func(m *CorpseStarchModel, now time.Time) { m.game.Requisition(now) - m.setInfo("corpsestarch.msg.requisitioned") + m.setInfo(m.game.modeKey("corpsestarch.msg.requisitioned", "corpsestarch.msg.requisitioned.demonic")) }, }) @@ -104,7 +105,7 @@ func (m CorpseStarchModel) buildActions(now time.Time) []corpseStarchAction { if m.game.BuyServitor(i, now) { m.setInfo("corpsestarch.msg.servitor_bought") } else { - m.setInfo("corpsestarch.msg.cannot_afford") + m.setInfo(m.game.modeKey("corpsestarch.msg.cannot_afford", "corpsestarch.msg.cannot_afford.demonic")) } }, }) @@ -119,7 +120,7 @@ func (m CorpseStarchModel) buildActions(now time.Time) []corpseStarchAction { if m.game.BuyClickUpgrade() { m.setInfo("corpsestarch.msg.upgrade_bought") } else { - m.setInfo("corpsestarch.msg.cannot_afford") + m.setInfo(m.game.modeKey("corpsestarch.msg.cannot_afford", "corpsestarch.msg.cannot_afford.demonic")) } }, }) @@ -132,7 +133,7 @@ func (m CorpseStarchModel) buildActions(now time.Time) []corpseStarchAction { outcome := m.game.DipChainsword(now, rand.Float64()) switch outcome { case DipBonusStarch: - m.setInfo("corpsestarch.log.dip_bonus") + m.setInfo(m.game.modeKey("corpsestarch.log.dip_bonus", "corpsestarch.log.dip_bonus.demonic")) case DipMachineSpiritBlessing: m.setInfo("corpsestarch.log.dip_blessing") case DipNothing: @@ -403,7 +404,7 @@ func (m CorpseStarchModel) View() string { fmt.Fprintln(&b) if len(g.Log) > 0 { - fmt.Fprintln(&b, dimStyle.Render(T("corpsestarch.log.title"))) + fmt.Fprintln(&b, dimStyle.Render(T(m.game.modeKey("corpsestarch.log.title", "corpsestarch.log.title.demonic")))) for _, key := range g.Log { fmt.Fprintln(&b, dimStyle.Render(" "+wrapText(T(key), m.termWidth))) } @@ -417,4 +418,4 @@ func (m CorpseStarchModel) View() string { fmt.Fprintln(&b, dimStyle.Render(T("corpsestarch.hint"))) return b.String() -} +} \ No newline at end of file diff --git a/corpsestarch_tui_test.go b/corpsestarch_tui_test.go index fbe84cf..ac4c0fd 100644 --- a/corpsestarch_tui_test.go +++ b/corpsestarch_tui_test.go @@ -437,3 +437,77 @@ func TestCorpseStarchModelMissionFlowViaKeys(t *testing.T) { t.Errorf("счётчик завершённых миссий должен был увеличиться") } } + +// TestCorpseStarchDemonicModeNeverMentionsCorpseStarchOrMunitorum — +// регрессия: после перехода на сторону Хаоса часть сообщений +// (реквизиция, нехватка средств, окунание меча, приветствие после +// возвращения, заголовок журнала) была жёстко зашита на имперскую +// формулировку "трупный крахмал"/"Муниторум" и не переключалась +// вместе с остальной игрой (сервиторы и миссии уже переключались +// корректно). +func TestCorpseStarchDemonicModeNeverMentionsCorpseStarchOrMunitorum(t *testing.T) { + withTempConfigDir(t) + m := NewCorpseStarchModel() + m.game.Mode = CorpseStarchModeDemonic + m.game.TotalEarned = corpseStarchUnlockSump + m.game.Starch = 1000 + now := m.game.LastTick + + // реально прогоняем игровую логику в демоническом режиме, а не + // подставляем старые ключи лога вручную — сами ключи лога + // выбираются один раз, в момент события (см. modeKey), а не + // задним числом при отрисовке + for i := 0; i < 3; i++ { + m.game.DipChainsword(now, 0.95) // >=0.9 — гарантированная Скверна на каждом броске + } + m.game.ChaosDefendClicks = 0 + m.game.ChaosIncursionUntil = now.Add(time.Second) + m.game.resolveChaosIncursion() // без бонуса (мало кликов защиты) + m.game.ChaosDefendClicks = corpseStarchDefendClicksForBonus + m.game.ChaosIncursionUntil = now.Add(time.Second) + m.game.resolveChaosIncursion() // с бонусом + + forbidden := []string{"трупн", "Муниторум", "сервитор"} + checkNoForbidden := func(label string, text string) { + t.Helper() + for _, f := range forbidden { + if strings.Contains(text, f) { + t.Errorf("%s: в демоническом режиме не должно встречаться %q, получено: %q", label, f, text) + } + } + } + + // заголовок игры ("ТРУПНЫЕ БАТОНЧИКИ") — имя самой игры, не + // переключается по режиму и намеренно не проверяется здесь + checkNoForbidden("заголовок журнала", T(m.game.modeKey("corpsestarch.log.title", "corpsestarch.log.title.demonic"))) + for _, key := range m.game.Log { + checkNoForbidden("запись журнала "+key, T(key)) + } + + actions := m.buildActions(now) + for _, a := range actions { + checkNoForbidden("метка действия "+a.Label, a.Label) + } + + m.game.Requisition(now) + m.setInfo(m.game.modeKey("corpsestarch.msg.requisitioned", "corpsestarch.msg.requisitioned.demonic")) + checkNoForbidden("сообщение после реквизиции", m.message) + + m.setInfo(m.game.modeKey("corpsestarch.msg.cannot_afford", "corpsestarch.msg.cannot_afford.demonic")) + checkNoForbidden("сообщение о нехватке средств", m.message) + + m.message = Tf(m.game.modeKey("corpsestarch.msg.welcome_back", "corpsestarch.msg.welcome_back.demonic"), formatStarch(100)) + checkNoForbidden("приветственное сообщение", m.message) +} + +func TestCorpseStarchImperialModeStillUsesOriginalWording(t *testing.T) { + withTempConfigDir(t) + m := NewCorpseStarchModel() + // режим по умолчанию — имперский; проверяем, что оригинальная + // формулировка никуда не делась для тех, кто ещё не перешёл на + // сторону Хаоса + view := m.View() + if !strings.Contains(view, "трупный крахмал") && !strings.Contains(view, "Трупный крахмал") { + t.Errorf("в имперском режиме заголовок должен был по-прежнему упоминать трупный крахмал, получено:\n%s", view) + } +} \ No newline at end of file diff --git a/ggc b/ggc index fef4499..a45c699 100755 --- a/ggc +++ b/ggc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:592f23e82a1d7295b7bcd57996c05efa90251972782f55ac954d9f262556e6ba -size 5038242 +oid sha256:f96bd1922b45b6b68caf900d8d79d3815118b306be9bafa1746c148b18a8ab36 +size 5042338 diff --git a/ggc.exe b/ggc.exe index 7041160..61c8d6a 100755 --- a/ggc.exe +++ b/ggc.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db435cf90757eadc7a719568a0ad545304f776c02ab79055aa781a077a257edd -size 5324800 +oid sha256:b141ba6db6a1ce0b3ccfd8f27d1d946ab50c74f7a77c526230ae8177972c7c1d +size 5329408 diff --git a/translations.go b/translations.go index 3d07fa4..18ef6e4 100644 --- a/translations.go +++ b/translations.go @@ -488,35 +488,43 @@ func init() { "corpsestarch.confirm_reset.hint": {"[y] да, начать заново [n/esc] отмена", "[y] yes, start over [n/esc] cancel"}, "corpsestarch.msg.reset_done": {"Начата новая партия.", "A new game has started."}, - "corpsestarch.action.requisition": {"Реквизировать трупный крахмал (+%s)", "Requisition corpse starch (+%s)"}, - "corpsestarch.action.buy_servitor": {"Приобрести: %s (сейчас: %d, цена: %s)", "Acquire: %s (owned: %d, cost: %s)"}, - "corpsestarch.action.upgrade_click": {"Заточить цепной меч (цена: %s)", "Sharpen chainsword (cost: %s)"}, - "corpsestarch.action.dip": {"Окунуть цепной меч в отстойник", "Dip your chainsword in the sump"}, - "corpsestarch.action.start_mission": {"Начать зачистку: %s (%s)", "Start purge: %s (%s)"}, - "corpsestarch.action.collect_mission": {"Забрать награду за зачистку", "Collect purge reward"}, - "corpsestarch.action.defend": {"Отбиваться от культистов Хаоса (заявлено атак: %d)", "Fight off Chaos cultists (attacks logged: %d)"}, - "corpsestarch.action.defend.demonic": {"Отбиваться от карателей Империума (заявлено атак: %d)", "Fight off the Imperial punisher fleet (attacks logged: %d)"}, + "corpsestarch.action.requisition": {"Реквизировать трупный крахмал (+%s)", "Requisition corpse starch (+%s)"}, + "corpsestarch.action.requisition.demonic": {"Пожать урожай душ (+%s)", "Harvest souls (+%s)"}, + "corpsestarch.action.buy_servitor": {"Приобрести: %s (сейчас: %d, цена: %s)", "Acquire: %s (owned: %d, cost: %s)"}, + "corpsestarch.action.upgrade_click": {"Заточить цепной меч (цена: %s)", "Sharpen chainsword (cost: %s)"}, + "corpsestarch.action.dip": {"Окунуть цепной меч в отстойник", "Dip your chainsword in the sump"}, + "corpsestarch.action.start_mission": {"Начать зачистку: %s (%s)", "Start purge: %s (%s)"}, + "corpsestarch.action.collect_mission": {"Забрать награду за зачистку", "Collect purge reward"}, + "corpsestarch.action.defend": {"Отбиваться от культистов Хаоса (заявлено атак: %d)", "Fight off Chaos cultists (attacks logged: %d)"}, + "corpsestarch.action.defend.demonic": {"Отбиваться от карателей Империума (заявлено атак: %d)", "Fight off the Imperial punisher fleet (attacks logged: %d)"}, - "corpsestarch.msg.requisitioned": {"Реквизиция одобрена Муниторумом.", "Requisition approved by the Munitorum."}, - "corpsestarch.msg.servitor_bought": {"Новая единица введена в строй.", "A new unit has been brought online."}, - "corpsestarch.msg.upgrade_bought": {"Цепной меч заточен острее.", "The chainsword is sharper now."}, - "corpsestarch.msg.cannot_afford": {"Недостаточно трупного крахмала.", "Not enough corpse starch."}, - "corpsestarch.msg.mission_started": {"Отряд отправлен на зачистку.", "A squad has been dispatched on the purge."}, - "corpsestarch.msg.defended": {"Вы отбили атаку культистов.", "You fought off a cultist attack."}, + "corpsestarch.msg.requisitioned": {"Реквизиция одобрена Муниторумом.", "Requisition approved by the Munitorum."}, + "corpsestarch.msg.requisitioned.demonic": {"Урожай душ пожат без остатка.", "The soul harvest is reaped in full."}, + "corpsestarch.msg.servitor_bought": {"Новая единица введена в строй.", "A new unit has been brought online."}, + "corpsestarch.msg.upgrade_bought": {"Цепной меч заточен острее.", "The chainsword is sharper now."}, + "corpsestarch.msg.cannot_afford": {"Недостаточно трупного крахмала.", "Not enough corpse starch."}, + "corpsestarch.msg.cannot_afford.demonic": {"Недостаточно душ.", "Not enough souls."}, + "corpsestarch.msg.mission_started": {"Отряд отправлен на зачистку.", "A squad has been dispatched on the purge."}, + "corpsestarch.msg.defended": {"Вы отбили атаку культистов.", "You fought off a cultist attack."}, "corpsestarch.mission.inprogress": {"Зачистка идёт: %s (осталось %s)", "Purge underway: %s (%s remaining)"}, "corpsestarch.mission.ready": {"Зачистка завершена: %s — заберите награду!", "Purge complete: %s — collect your reward!"}, - "corpsestarch.log.title": {"Журнал Муниторума:", "Munitorum log:"}, - "corpsestarch.log.dip_bonus": {"Отстойник выплюнул горсть трупного крахмала.", "The sump spat out a handful of corpse starch."}, - "corpsestarch.log.dip_blessing": {"Дух машины благословил ваш клинок — временный прирост дохода.", "The machine spirit blessed your blade — temporary income boost."}, - "corpsestarch.log.dip_nothing": {"Отстойник ответил тишиной.", "The sump answers only with silence."}, - "corpsestarch.log.dip_corruption": {"Скверна коснулась вашего снаряжения.", "Taint has touched your wargear."}, - "corpsestarch.incursion.active": {"ВТОРЖЕНИЕ ХАОСА! Культисты атакуют станцию — осталось: %s", "CHAOS INCURSION! Cultists are attacking the station — time left: %s"}, - "corpsestarch.incursion.active.demonic": {"КАРАТЕЛЬНЫЙ ФЛОТ! Силы Империума атакуют вашу цитадель — осталось: %s", "PUNISHER FLEET! Imperial forces are attacking your citadel — time left: %s"}, - "corpsestarch.log.warp_breach": {"Скверна прорвалась варп-выбросом! Половина запасов и оборудования потеряна — из пробоя хлынули культисты Хаоса.", "The taint erupts in a warp breach! Half your stockpile and equipment are lost — Chaos cultists pour through the rupture."}, - "corpsestarch.log.incursion_repelled": {"Вторжение культистов отражено. Обошлось малой кровью.", "The cultist incursion has been repelled. It could have been worse."}, - "corpsestarch.log.incursion_repelled_bonus": {"Вторжение отражено! За стойкость станция получила дополнительный груз трупного крахмала.", "The incursion has been repelled! For your resolve, the station received a bonus shipment of corpse starch."}, + "corpsestarch.log.title": {"Журнал Муниторума:", "Munitorum log:"}, + "corpsestarch.log.title.demonic": {"Летопись Скверны:", "Chronicle of the Taint:"}, + "corpsestarch.log.dip_bonus": {"Отстойник выплюнул горсть трупного крахмала.", "The sump spat out a handful of corpse starch."}, + "corpsestarch.log.dip_bonus.demonic": {"Отстойник выплюнул пригоршню свежих душ.", "The sump spat out a handful of fresh souls."}, + "corpsestarch.log.dip_blessing": {"Дух машины благословил ваш клинок — временный прирост дохода.", "The machine spirit blessed your blade — temporary income boost."}, + "corpsestarch.log.dip_nothing": {"Отстойник ответил тишиной.", "The sump answers only with silence."}, + "corpsestarch.log.dip_corruption": {"Скверна коснулась вашего снаряжения.", "Taint has touched your wargear."}, + "corpsestarch.incursion.active": {"ВТОРЖЕНИЕ ХАОСА! Культисты атакуют станцию — осталось: %s", "CHAOS INCURSION! Cultists are attacking the station — time left: %s"}, + "corpsestarch.incursion.active.demonic": {"КАРАТЕЛЬНЫЙ ФЛОТ! Силы Империума атакуют вашу цитадель — осталось: %s", "PUNISHER FLEET! Imperial forces are attacking your citadel — time left: %s"}, + "corpsestarch.log.warp_breach": {"Скверна прорвалась варп-выбросом! Половина запасов и оборудования потеряна — из пробоя хлынули культисты Хаоса.", "The taint erupts in a warp breach! Half your stockpile and equipment are lost — Chaos cultists pour through the rupture."}, + "corpsestarch.log.warp_breach.demonic": {"Ваше колдовство прорвалось наружу слишком ярко! Половина запасов и оборудования потеряна — Империум заметил цитадель.", "Your sorcery flared too brightly! Half your stockpile and equipment are lost — the Imperium has noticed your citadel."}, + "corpsestarch.log.incursion_repelled": {"Вторжение культистов отражено. Обошлось малой кровью.", "The cultist incursion has been repelled. It could have been worse."}, + "corpsestarch.log.incursion_repelled.demonic": {"Каратели Империума отброшены. Цитадель выстояла.", "The Imperial punisher fleet has been driven off. The citadel held."}, + "corpsestarch.log.incursion_repelled_bonus": {"Вторжение отражено! За стойкость станция получила дополнительный груз трупного крахмала.", "The incursion has been repelled! For your resolve, the station received a bonus shipment of corpse starch."}, + "corpsestarch.log.incursion_repelled_bonus.demonic": {"Каратели отброшены! За стойкость цитадель собрала дополнительный урожай душ.", "The punishers have been driven off! For your resolve, the citadel reaped a bonus harvest of souls."}, "corpsestarch.chaos.offer.title": {"=== ПРЕДЛОЖЕНИЕ ХАОСА ===", "=== THE OFFER OF CHAOS ==="}, "corpsestarch.chaos.offer.text": { @@ -555,7 +563,8 @@ func init() { "corpsestarch.mission.shrineworld": {"Обрушить кару на планету-святилище", "Bring Ruin to a Shrine World"}, "corpsestarch.mission.sector": {"Испепелить целый сектор пламенем Перемен", "Sear a Whole Sector with the Flames of Change"}, - "corpsestarch.msg.welcome_back": {"С возвращением! Пока вас не было, сервиторы намолотили +%s трупного крахмала.", "Welcome back! While you were away, your servitors gathered +%s corpse starch."}, + "corpsestarch.msg.welcome_back": {"С возвращением! Пока вас не было, сервиторы намолотили +%s трупного крахмала.", "Welcome back! While you were away, your servitors gathered +%s corpse starch."}, + "corpsestarch.msg.welcome_back.demonic": {"С возвращением! Пока вас не было, ваши демоны собрали +%s душ.", "Welcome back! While you were away, your daemons harvested +%s souls."}, "game.go.name": {"Го (Go)", "Go"}, "game.go.desc": {"Классическая настольная игра на доске 9x9/13x13/19x19, три уровня сложности бота", "Classic board game on a 9x9/13x13/19x19 board, three bot difficulty levels"}, @@ -616,4 +625,4 @@ func init() { } { translations[k] = v } -} +} \ No newline at end of file