Fixed text in chaos mode

This commit is contained in:
Magnus Root 2026-07-12 14:06:44 +03:00
parent ceb9bbc37a
commit a85eb152d1
6 changed files with 137 additions and 42 deletions

View file

@ -42,6 +42,17 @@ func (def corpseStarchServitorDef) effectiveNameKey(mode CorpseStarchMode) strin
return def.NameKey return def.NameKey
} }
// modeKey выбирает один из двух ключей перевода в зависимости от
// текущего режима партии — для сообщений и надписей, у которых (в
// отличие от сервиторов/миссий) нет отдельного def-объекта с парой
// имперского/демонического ключа.
func (g *CorpseStarchGameState) modeKey(imperialKey, demonicKey string) string {
if g.Mode == CorpseStarchModeDemonic {
return demonicKey
}
return imperialKey
}
// corpseStarchMissionDef — статическое описание одной доступной // corpseStarchMissionDef — статическое описание одной доступной
// миссии зачистки (или, после перерождения, вторжения на планеты // миссии зачистки (или, после перерождения, вторжения на планеты
// Империума — тот же механизм, другое название). // Империума — тот же механизм, другое название).
@ -374,7 +385,7 @@ func (g *CorpseStarchGameState) triggerWarpBreach(now time.Time) {
g.ChaosIncursionUntil = now.Add(corpseStarchIncursionDuration) g.ChaosIncursionUntil = now.Add(corpseStarchIncursionDuration)
g.ChaosDefendClicks = 0 g.ChaosDefendClicks = 0
g.ChaosMarks++ g.ChaosMarks++
g.addLog("corpsestarch.log.warp_breach") g.addLog(g.modeKey("corpsestarch.log.warp_breach", "corpsestarch.log.warp_breach.demonic"))
} }
// ChaosOfferPending — пора ли показать предложение перейти на // ChaosOfferPending — пора ли показать предложение перейти на
@ -451,9 +462,9 @@ func (g *CorpseStarchGameState) DefendAgainstChaos(now time.Time) bool {
func (g *CorpseStarchGameState) resolveChaosIncursion() { func (g *CorpseStarchGameState) resolveChaosIncursion() {
if g.ChaosDefendClicks >= corpseStarchDefendClicksForBonus { if g.ChaosDefendClicks >= corpseStarchDefendClicksForBonus {
g.earn(corpseStarchDefendBonusReward) 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 { } 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.ChaosIncursionUntil = time.Time{}
g.ChaosDefendClicks = 0 g.ChaosDefendClicks = 0

View file

@ -47,7 +47,8 @@ func NewCorpseStarchModel() CorpseStarchModel {
g.Tick(now) // честно начисляем доход за время, пока партия была закрыта g.Tick(now) // честно начисляем доход за время, пока партия была закрыта
m := CorpseStarchModel{game: g, termWidth: 80} m := CorpseStarchModel{game: g, termWidth: 80}
if earned := g.Starch - before; earned >= 1 { 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 return m
} }
@ -84,10 +85,10 @@ func (m CorpseStarchModel) buildActions(now time.Time) []corpseStarchAction {
var actions []corpseStarchAction var actions []corpseStarchAction
actions = append(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) { Handle: func(m *CorpseStarchModel, now time.Time) {
m.game.Requisition(now) 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) { if m.game.BuyServitor(i, now) {
m.setInfo("corpsestarch.msg.servitor_bought") m.setInfo("corpsestarch.msg.servitor_bought")
} else { } 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() { if m.game.BuyClickUpgrade() {
m.setInfo("corpsestarch.msg.upgrade_bought") m.setInfo("corpsestarch.msg.upgrade_bought")
} else { } 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()) outcome := m.game.DipChainsword(now, rand.Float64())
switch outcome { switch outcome {
case DipBonusStarch: case DipBonusStarch:
m.setInfo("corpsestarch.log.dip_bonus") m.setInfo(m.game.modeKey("corpsestarch.log.dip_bonus", "corpsestarch.log.dip_bonus.demonic"))
case DipMachineSpiritBlessing: case DipMachineSpiritBlessing:
m.setInfo("corpsestarch.log.dip_blessing") m.setInfo("corpsestarch.log.dip_blessing")
case DipNothing: case DipNothing:
@ -403,7 +404,7 @@ func (m CorpseStarchModel) View() string {
fmt.Fprintln(&b) fmt.Fprintln(&b)
if len(g.Log) > 0 { 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 { for _, key := range g.Log {
fmt.Fprintln(&b, dimStyle.Render(" "+wrapText(T(key), m.termWidth))) fmt.Fprintln(&b, dimStyle.Render(" "+wrapText(T(key), m.termWidth)))
} }

View file

@ -437,3 +437,77 @@ func TestCorpseStarchModelMissionFlowViaKeys(t *testing.T) {
t.Errorf("счётчик завершённых миссий должен был увеличиться") 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)
}
}

BIN
ggc (Stored with Git LFS)

Binary file not shown.

BIN
ggc.exe (Stored with Git LFS)

Binary file not shown.

View file

@ -489,6 +489,7 @@ func init() {
"corpsestarch.msg.reset_done": {"Начата новая партия.", "A new game has started."}, "corpsestarch.msg.reset_done": {"Начата новая партия.", "A new game has started."},
"corpsestarch.action.requisition": {"Реквизировать трупный крахмал (+%s)", "Requisition corpse starch (+%s)"}, "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.buy_servitor": {"Приобрести: %s (сейчас: %d, цена: %s)", "Acquire: %s (owned: %d, cost: %s)"},
"corpsestarch.action.upgrade_click": {"Заточить цепной меч (цена: %s)", "Sharpen chainsword (cost: %s)"}, "corpsestarch.action.upgrade_click": {"Заточить цепной меч (цена: %s)", "Sharpen chainsword (cost: %s)"},
"corpsestarch.action.dip": {"Окунуть цепной меч в отстойник", "Dip your chainsword in the sump"}, "corpsestarch.action.dip": {"Окунуть цепной меч в отстойник", "Dip your chainsword in the sump"},
@ -498,9 +499,11 @@ func init() {
"corpsestarch.action.defend.demonic": {"Отбиваться от карателей Империума (заявлено атак: %d)", "Fight off the Imperial punisher fleet (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.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.servitor_bought": {"Новая единица введена в строй.", "A new unit has been brought online."},
"corpsestarch.msg.upgrade_bought": {"Цепной меч заточен острее.", "The chainsword is sharper now."}, "corpsestarch.msg.upgrade_bought": {"Цепной меч заточен острее.", "The chainsword is sharper now."},
"corpsestarch.msg.cannot_afford": {"Недостаточно трупного крахмала.", "Not enough corpse starch."}, "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.mission_started": {"Отряд отправлен на зачистку.", "A squad has been dispatched on the purge."},
"corpsestarch.msg.defended": {"Вы отбили атаку культистов.", "You fought off a cultist attack."}, "corpsestarch.msg.defended": {"Вы отбили атаку культистов.", "You fought off a cultist attack."},
@ -508,15 +511,20 @@ func init() {
"corpsestarch.mission.ready": {"Зачистка завершена: %s — заберите награду!", "Purge complete: %s — collect your reward!"}, "corpsestarch.mission.ready": {"Зачистка завершена: %s — заберите награду!", "Purge complete: %s — collect your reward!"},
"corpsestarch.log.title": {"Журнал Муниторума:", "Munitorum log:"}, "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": {"Отстойник выплюнул горсть трупного крахмала.", "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_blessing": {"Дух машины благословил ваш клинок — временный прирост дохода.", "The machine spirit blessed your blade — temporary income boost."},
"corpsestarch.log.dip_nothing": {"Отстойник ответил тишиной.", "The sump answers only with silence."}, "corpsestarch.log.dip_nothing": {"Отстойник ответил тишиной.", "The sump answers only with silence."},
"corpsestarch.log.dip_corruption": {"Скверна коснулась вашего снаряжения.", "Taint has touched your wargear."}, "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": {"ВТОРЖЕНИЕ ХАОСА! Культисты атакуют станцию — осталось: %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.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": {"Скверна прорвалась варп-выбросом! Половина запасов и оборудования потеряна — из пробоя хлынули культисты Хаоса.", "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": {"Вторжение культистов отражено. Обошлось малой кровью.", "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": {"Вторжение отражено! За стойкость станция получила дополнительный груз трупного крахмала.", "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.title": {"=== ПРЕДЛОЖЕНИЕ ХАОСА ===", "=== THE OFFER OF CHAOS ==="},
"corpsestarch.chaos.offer.text": { "corpsestarch.chaos.offer.text": {
@ -556,6 +564,7 @@ func init() {
"corpsestarch.mission.sector": {"Испепелить целый сектор пламенем Перемен", "Sear a Whole Sector with the Flames of Change"}, "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.name": {"Го (Go)", "Go"},
"game.go.desc": {"Классическая настольная игра на доске 9x9/13x13/19x19, три уровня сложности бота", "Classic board game on a 9x9/13x13/19x19 board, three bot difficulty levels"}, "game.go.desc": {"Классическая настольная игра на доске 9x9/13x13/19x19, три уровня сложности бота", "Classic board game on a 9x9/13x13/19x19 board, three bot difficulty levels"},