547 lines
24 KiB
Go
547 lines
24 KiB
Go
package main
|
||
|
||
import "time"
|
||
|
||
// ПРИМЕЧАНИЕ: инкрементальная (idle/clicker) игра в духе Candy Box 2
|
||
// — с постепенным открытием механик по мере накопления ресурса.
|
||
// Антураж — пародийная стилизация под сеттинг мрачного далёкого
|
||
// будущего (сервиторы, Муниторум, Скверна, зачистки), без
|
||
// копирования конкретных защищённых текстов, персонажей или
|
||
// фракций — только тематическая лексика.
|
||
//
|
||
// Прогресс существует только в рамках текущего запуска приложения
|
||
// (как и во всех остальных играх коллекции) — между перезапусками
|
||
// ничего не сохраняется.
|
||
|
||
// corpseStarchServitorDef — статическое описание одного тира
|
||
// генератора пассивного дохода. DemonicNameKey — альтернативное имя
|
||
// после перерождения в демоническую сущность (см. AcceptChaosOffer);
|
||
// сам механизм (стоимость, доход) не меняется — меняется только
|
||
// то, как это называется.
|
||
type corpseStarchServitorDef struct {
|
||
NameKey string
|
||
DemonicNameKey string
|
||
UnlockAt float64 // порог по TotalEarned, при котором тир становится виден
|
||
BaseCost float64
|
||
CostScale float64
|
||
YieldPerSec float64
|
||
}
|
||
|
||
var corpseStarchServitorDefs = []corpseStarchServitorDef{
|
||
{NameKey: "corpsestarch.servitor.skull", DemonicNameKey: "corpsestarch.servitor.pinkhorror", UnlockAt: 0, BaseCost: 10, CostScale: 1.15, YieldPerSec: 0.5},
|
||
{NameKey: "corpsestarch.servitor.menial", DemonicNameKey: "corpsestarch.servitor.screamer", UnlockAt: 200, BaseCost: 100, CostScale: 1.15, YieldPerSec: 3},
|
||
{NameKey: "corpsestarch.servitor.cogitator", DemonicNameKey: "corpsestarch.servitor.lordofchange", UnlockAt: 2000, BaseCost: 1000, CostScale: 1.15, YieldPerSec: 20},
|
||
}
|
||
|
||
// effectiveNameKey возвращает ключ имени тира с учётом текущего
|
||
// режима партии (обычный/демонический после перерождения).
|
||
func (def corpseStarchServitorDef) effectiveNameKey(mode CorpseStarchMode) string {
|
||
if mode == CorpseStarchModeDemonic {
|
||
return def.DemonicNameKey
|
||
}
|
||
return def.NameKey
|
||
}
|
||
|
||
// modeKey выбирает один из двух ключей перевода в зависимости от
|
||
// текущего режима партии — для сообщений и надписей, у которых (в
|
||
// отличие от сервиторов/миссий) нет отдельного def-объекта с парой
|
||
// имперского/демонического ключа.
|
||
func (g *CorpseStarchGameState) modeKey(imperialKey, demonicKey string) string {
|
||
if g.Mode == CorpseStarchModeDemonic {
|
||
return demonicKey
|
||
}
|
||
return imperialKey
|
||
}
|
||
|
||
// corpseStarchMissionDef — статическое описание одной доступной
|
||
// миссии зачистки (или, после перерождения, вторжения на планеты
|
||
// Империума — тот же механизм, другое название).
|
||
type corpseStarchMissionDef struct {
|
||
NameKey string
|
||
DemonicNameKey string
|
||
UnlockAt float64
|
||
Duration time.Duration
|
||
RewardMin float64
|
||
RewardMax float64
|
||
}
|
||
|
||
var corpseStarchMissionDefs = []corpseStarchMissionDef{
|
||
{NameKey: "corpsestarch.mission.vermin", DemonicNameKey: "corpsestarch.mission.borderworld", UnlockAt: 800, Duration: 20 * time.Second, RewardMin: 30, RewardMax: 60},
|
||
{NameKey: "corpsestarch.mission.chapel", DemonicNameKey: "corpsestarch.mission.shrineworld", UnlockAt: 800, Duration: 60 * time.Second, RewardMin: 150, RewardMax: 250},
|
||
{NameKey: "corpsestarch.mission.waaagh", DemonicNameKey: "corpsestarch.mission.sector", UnlockAt: 800, Duration: 150 * time.Second, RewardMin: 500, RewardMax: 800},
|
||
}
|
||
|
||
// effectiveNameKey возвращает ключ названия миссии с учётом текущего
|
||
// режима партии.
|
||
func (def corpseStarchMissionDef) effectiveNameKey(mode CorpseStarchMode) string {
|
||
if mode == CorpseStarchModeDemonic {
|
||
return def.DemonicNameKey
|
||
}
|
||
return def.NameKey
|
||
}
|
||
|
||
// Пороги открытия основных разделов интерфейса (по TotalEarned).
|
||
const (
|
||
corpseStarchUnlockServitors = 20
|
||
corpseStarchUnlockShop = 80
|
||
corpseStarchUnlockSump = 300
|
||
corpseStarchUnlockMissions = 800
|
||
)
|
||
|
||
const (
|
||
corpseStarchClickUpgradeBaseCost = 25
|
||
corpseStarchClickUpgradeCostScale = 1.6
|
||
corpseStarchClickUpgradeGain = 1
|
||
)
|
||
|
||
// Параметры варп-выброса: при достижении этого порога Скверна
|
||
// "прорывается" наружу — половина текущего запаса крахмала и
|
||
// половина каждого тира сервиторов теряется, Скверна сбрасывается в
|
||
// 0, и начинается вторжение хаоситов на фиксированное реальное
|
||
// время. Пока оно идёт, сервиторы дают заметно меньше дохода — часть
|
||
// сил станции отвлечена на оборону — а активная защита (повторные
|
||
// нажатия) даёт шанс на бонус по окончании вторжения.
|
||
const (
|
||
corpseStarchCorruptionBreachThreshold = 3
|
||
corpseStarchIncursionDuration = 1 * time.Hour
|
||
corpseStarchIncursionYieldMultiplier = 0.5
|
||
corpseStarchDefendClicksForBonus = 10
|
||
corpseStarchDefendBonusReward = 200.0
|
||
)
|
||
|
||
// Параметры предложения перейти на сторону Хаоса и самого
|
||
// перерождения: после corpseStarchChaosMarksForOffer варп-выбросов
|
||
// подряд (без отказа, который сбрасывает счётчик) появляется выбор
|
||
// — отказаться (награда и сброс счётчика) или принять (переход в
|
||
// демонический режим после суток полной недееспособности).
|
||
const (
|
||
corpseStarchChaosMarksForOffer = 5
|
||
corpseStarchRebirthDuration = 24 * time.Hour
|
||
corpseStarchChaosDeclineReward = 500.0
|
||
)
|
||
|
||
// corpseStarchMission — активная (запущенная) миссия.
|
||
type corpseStarchMission struct {
|
||
DefIdx int
|
||
StartedAt time.Time
|
||
}
|
||
|
||
// CorpseStarchGameState — полное состояние партии.
|
||
// CorpseStarchMode — обычный режим или демоническая сущность после
|
||
// перерождения (см. AcceptChaosOffer). Меняет только НАЗВАНИЯ вещей
|
||
// (ресурс, тиры сервиторов, миссии, счётчик Скверны/отблеска) — сам
|
||
// игровой механизм остаётся тем же самым.
|
||
type CorpseStarchMode int
|
||
|
||
const (
|
||
CorpseStarchModeImperial CorpseStarchMode = iota
|
||
CorpseStarchModeDemonic
|
||
)
|
||
|
||
// CorpseStarchGameState — полное состояние партии.
|
||
type CorpseStarchGameState struct {
|
||
Starch float64 // текущий (тратимый) запас
|
||
TotalEarned float64 // накоплено за всю партию — используется для порогов открытия разделов
|
||
|
||
ClickYield float64
|
||
ClickUpgrades int
|
||
|
||
ServitorCounts []int // по одному счётчику на каждый элемент corpseStarchServitorDefs — уменьшается варп-выбросом
|
||
|
||
// ServitorPurchases — сколько раз куплен каждый тир за всю
|
||
// партию. В отличие от ServitorCounts, варп-выброс её НЕ трогает:
|
||
// цена следующей покупки должна оставаться такой же, какой была
|
||
// до потери инфраструктуры, а не дешеветь только потому, что
|
||
// действующих юнитов вдруг стало меньше.
|
||
ServitorPurchases []int
|
||
|
||
Corruption int
|
||
|
||
// ChaosIncursionUntil — если не нулевое время и ещё не наступило,
|
||
// идёт вторжение хаоситов после варп-выброса (см. triggerWarpBreach).
|
||
// ChaosDefendClicks считает, сколько раз игрок активно отбивался
|
||
// за текущее вторжение — от этого зависит бонус по его окончании.
|
||
ChaosIncursionUntil time.Time
|
||
ChaosDefendClicks int
|
||
|
||
// ChaosMarks растёт на 1 после каждого варп-выброса. При
|
||
// достижении corpseStarchChaosMarksForOffer появляется
|
||
// предложение перейти на сторону Хаоса (см. ChaosOfferPending).
|
||
ChaosMarks int
|
||
|
||
// Mode — обычный режим или уже переродившаяся демоническая
|
||
// сущность. RebirthUntil — если не нулевое и ещё не наступило,
|
||
// идёт само перерождение: все обычные действия недоступны, пока
|
||
// оно не завершится (см. InRebirth).
|
||
Mode CorpseStarchMode
|
||
RebirthUntil time.Time
|
||
|
||
ActiveMission *corpseStarchMission
|
||
MissionsDone int
|
||
|
||
// BonusMultiplier / BonusUntil — временный множитель к клику от
|
||
// удачного окунания цепного меча в отстойник.
|
||
BonusMultiplier float64
|
||
BonusUntil time.Time
|
||
|
||
LastTick time.Time
|
||
|
||
Log []string // последние несколько строк лога событий, новые в конце
|
||
}
|
||
|
||
const corpseStarchLogLimit = 6
|
||
|
||
// NewCorpseStarchGame создаёт новую партию.
|
||
func NewCorpseStarchGame(now time.Time) *CorpseStarchGameState {
|
||
return &CorpseStarchGameState{
|
||
ClickYield: 1,
|
||
BonusMultiplier: 1,
|
||
ServitorCounts: make([]int, len(corpseStarchServitorDefs)),
|
||
ServitorPurchases: make([]int, len(corpseStarchServitorDefs)),
|
||
LastTick: now,
|
||
}
|
||
}
|
||
|
||
func (g *CorpseStarchGameState) addLog(key string) {
|
||
g.Log = append(g.Log, key)
|
||
if len(g.Log) > corpseStarchLogLimit {
|
||
g.Log = g.Log[len(g.Log)-corpseStarchLogLimit:]
|
||
}
|
||
}
|
||
|
||
func (g *CorpseStarchGameState) earn(amount float64) {
|
||
if amount <= 0 {
|
||
return
|
||
}
|
||
g.Starch += amount
|
||
g.TotalEarned += amount
|
||
}
|
||
|
||
// currentClickYield учитывает временный бонусный множитель, если он
|
||
// ещё действует на момент now.
|
||
func (g *CorpseStarchGameState) currentClickYield(now time.Time) float64 {
|
||
if now.Before(g.BonusUntil) {
|
||
return g.ClickYield * g.BonusMultiplier
|
||
}
|
||
return g.ClickYield
|
||
}
|
||
|
||
// Requisition — ручное действие ("нажать кнопку"): начисляет один
|
||
// клик дохода.
|
||
func (g *CorpseStarchGameState) Requisition(now time.Time) {
|
||
g.earn(g.currentClickYield(now))
|
||
}
|
||
|
||
// Tick начисляет пассивный доход от сервиторов за время, прошедшее
|
||
// с последнего тика (now должен монотонно не убывать; для тестов
|
||
// можно подавать произвольные значения). Также сбрасывает истёкший
|
||
// временный бонус.
|
||
func (g *CorpseStarchGameState) Tick(now time.Time) {
|
||
elapsed := now.Sub(g.LastTick).Seconds()
|
||
g.LastTick = now
|
||
g.checkRebirthCompletion(now)
|
||
if elapsed <= 0 || g.InRebirth(now) {
|
||
g.checkIncursionResolution(now)
|
||
return
|
||
}
|
||
var perSec float64
|
||
for i, def := range corpseStarchServitorDefs {
|
||
perSec += float64(g.ServitorCounts[i]) * def.YieldPerSec
|
||
}
|
||
if g.InChaosIncursion(now) {
|
||
perSec *= corpseStarchIncursionYieldMultiplier
|
||
}
|
||
g.earn(perSec * elapsed)
|
||
g.checkIncursionResolution(now)
|
||
}
|
||
|
||
// checkRebirthCompletion переключает партию в демонический режим,
|
||
// как только суточное перерождение подходит к концу. Идемпотентно.
|
||
func (g *CorpseStarchGameState) checkRebirthCompletion(now time.Time) {
|
||
if g.RebirthUntil.IsZero() || now.Before(g.RebirthUntil) {
|
||
return
|
||
}
|
||
g.RebirthUntil = time.Time{}
|
||
g.Mode = CorpseStarchModeDemonic
|
||
g.addLog("corpsestarch.log.rebirth_complete")
|
||
}
|
||
|
||
// checkIncursionResolution проверяет, не истекло ли вторжение
|
||
// хаоситов, и если да — разрешает его (см. resolveChaosIncursion).
|
||
// Идемпотентно: повторные вызовы после разрешения ничего не делают,
|
||
// пока не начнётся новое вторжение.
|
||
func (g *CorpseStarchGameState) checkIncursionResolution(now time.Time) {
|
||
if g.ChaosIncursionUntil.IsZero() || now.Before(g.ChaosIncursionUntil) {
|
||
return
|
||
}
|
||
g.resolveChaosIncursion()
|
||
}
|
||
|
||
// servitorCost возвращает стоимость следующей покупки тира idx.
|
||
func (g *CorpseStarchGameState) servitorCost(idx int) float64 {
|
||
def := corpseStarchServitorDefs[idx]
|
||
cost := def.BaseCost
|
||
for i := 0; i < g.ServitorPurchases[idx]; i++ {
|
||
cost *= def.CostScale
|
||
}
|
||
return cost
|
||
}
|
||
|
||
// BuyServitor покупает одну единицу тира idx, если хватает
|
||
// трупного крахмала, тир уже открыт по порогу, и сейчас не идёт
|
||
// вторжение хаоситов (пока оно не закончится, новую инфраструктуру
|
||
// строить нельзя — часть станции физически недоступна под атакой).
|
||
func (g *CorpseStarchGameState) BuyServitor(idx int, now time.Time) bool {
|
||
if idx < 0 || idx >= len(corpseStarchServitorDefs) {
|
||
return false
|
||
}
|
||
if g.InChaosIncursion(now) {
|
||
return false
|
||
}
|
||
if g.TotalEarned < corpseStarchServitorDefs[idx].UnlockAt {
|
||
return false
|
||
}
|
||
cost := g.servitorCost(idx)
|
||
if g.Starch < cost {
|
||
return false
|
||
}
|
||
g.Starch -= cost
|
||
g.ServitorCounts[idx]++
|
||
g.ServitorPurchases[idx]++
|
||
return true
|
||
}
|
||
|
||
func (g *CorpseStarchGameState) clickUpgradeCost() float64 {
|
||
cost := float64(corpseStarchClickUpgradeBaseCost)
|
||
for i := 0; i < g.ClickUpgrades; i++ {
|
||
cost *= corpseStarchClickUpgradeCostScale
|
||
}
|
||
return cost
|
||
}
|
||
|
||
// BuyClickUpgrade повышает базовый доход от Реквизиции ("заточка
|
||
// цепного меча").
|
||
func (g *CorpseStarchGameState) BuyClickUpgrade() bool {
|
||
cost := g.clickUpgradeCost()
|
||
if g.Starch < cost {
|
||
return false
|
||
}
|
||
g.Starch -= cost
|
||
g.ClickUpgrades++
|
||
g.ClickYield += corpseStarchClickUpgradeGain
|
||
return true
|
||
}
|
||
|
||
// CorpseStarchDipOutcome — исход окунания цепного меча в отстойник.
|
||
type CorpseStarchDipOutcome int
|
||
|
||
const (
|
||
DipBonusStarch CorpseStarchDipOutcome = iota
|
||
DipMachineSpiritBlessing
|
||
DipNothing
|
||
DipCorruption
|
||
)
|
||
|
||
// DipChainsword бросает цепной меч в отстойник; roll — случайное
|
||
// число в [0,1), передаётся вызывающим кодом (реальный TUI передаёт
|
||
// rand.Float64(), тесты — фиксированные значения для детерминизма).
|
||
func (g *CorpseStarchGameState) DipChainsword(now time.Time, roll float64) CorpseStarchDipOutcome {
|
||
switch {
|
||
case roll < 0.5:
|
||
bonus := 5 + roll*70 // немного разнообразия внутри диапазона [5,40)
|
||
g.earn(bonus)
|
||
g.addLog("corpsestarch.log.dip_bonus")
|
||
return DipBonusStarch
|
||
case roll < 0.75:
|
||
g.BonusMultiplier = 2
|
||
g.BonusUntil = now.Add(30 * time.Second)
|
||
g.addLog("corpsestarch.log.dip_blessing")
|
||
return DipMachineSpiritBlessing
|
||
case roll < 0.9:
|
||
g.addLog("corpsestarch.log.dip_nothing")
|
||
return DipNothing
|
||
default:
|
||
g.Corruption++
|
||
penalty := g.Starch * 0.1
|
||
g.Starch -= penalty
|
||
g.addLog("corpsestarch.log.dip_corruption")
|
||
if g.Corruption >= corpseStarchCorruptionBreachThreshold {
|
||
g.triggerWarpBreach(now)
|
||
}
|
||
return DipCorruption
|
||
}
|
||
}
|
||
|
||
// triggerWarpBreach — Скверна прорывается варп-выбросом: половина
|
||
// текущего запаса крахмала и половина каждого тира сервиторов
|
||
// теряется, Скверна сбрасывается в 0 (выброс её "выпустил"), и
|
||
// начинается вторжение хаоситов на фиксированное реальное время.
|
||
func (g *CorpseStarchGameState) triggerWarpBreach(now time.Time) {
|
||
g.Starch *= 0.5
|
||
for i := range g.ServitorCounts {
|
||
g.ServitorCounts[i] /= 2
|
||
}
|
||
g.Corruption = 0
|
||
g.ChaosIncursionUntil = now.Add(corpseStarchIncursionDuration)
|
||
g.ChaosDefendClicks = 0
|
||
g.ChaosMarks++
|
||
g.addLog(g.modeKey("corpsestarch.log.warp_breach", "corpsestarch.log.warp_breach.demonic"))
|
||
}
|
||
|
||
// ChaosOfferPending — пора ли показать предложение перейти на
|
||
// сторону Хаоса (набралось достаточно меток, партия ещё в обычном
|
||
// режиме и перерождение ещё не идёт).
|
||
func (g *CorpseStarchGameState) ChaosOfferPending() bool {
|
||
return g.Mode == CorpseStarchModeImperial &&
|
||
g.ChaosMarks >= corpseStarchChaosMarksForOffer &&
|
||
g.RebirthUntil.IsZero()
|
||
}
|
||
|
||
// DeclineChaosOffer отклоняет предложение: счётчик меток сбрасывается
|
||
// в 0, взамен начисляется разовая награда.
|
||
func (g *CorpseStarchGameState) DeclineChaosOffer() {
|
||
g.earn(corpseStarchChaosDeclineReward)
|
||
g.ChaosMarks = 0
|
||
g.addLog("corpsestarch.log.chaos_declined")
|
||
}
|
||
|
||
// AcceptChaosOffer принимает предложение: начинается перерождение —
|
||
// на corpseStarchRebirthDuration партия становится полностью
|
||
// недееспособной (см. InRebirth), а по его завершении Mode
|
||
// переключается на демонический (см. Tick).
|
||
func (g *CorpseStarchGameState) AcceptChaosOffer(now time.Time) {
|
||
g.RebirthUntil = now.Add(corpseStarchRebirthDuration)
|
||
g.ChaosMarks = 0
|
||
g.addLog("corpsestarch.log.rebirth_begins")
|
||
}
|
||
|
||
// InRebirth — идёт ли прямо сейчас перерождение (после принятия
|
||
// предложения Хаоса, до его завершения).
|
||
func (g *CorpseStarchGameState) InRebirth(now time.Time) bool {
|
||
return !g.RebirthUntil.IsZero() && now.Before(g.RebirthUntil)
|
||
}
|
||
|
||
// RebirthRemaining — сколько реального времени осталось до конца
|
||
// перерождения (0, если оно сейчас не идёт).
|
||
func (g *CorpseStarchGameState) RebirthRemaining(now time.Time) time.Duration {
|
||
if !g.InRebirth(now) {
|
||
return 0
|
||
}
|
||
return g.RebirthUntil.Sub(now)
|
||
}
|
||
|
||
// InChaosIncursion — идёт ли прямо сейчас вторжение хаоситов после
|
||
// варп-выброса.
|
||
func (g *CorpseStarchGameState) InChaosIncursion(now time.Time) bool {
|
||
return !g.ChaosIncursionUntil.IsZero() && now.Before(g.ChaosIncursionUntil)
|
||
}
|
||
|
||
// ChaosIncursionRemaining — сколько реального времени осталось до
|
||
// конца вторжения (0, если вторжения сейчас нет).
|
||
func (g *CorpseStarchGameState) ChaosIncursionRemaining(now time.Time) time.Duration {
|
||
if !g.InChaosIncursion(now) {
|
||
return 0
|
||
}
|
||
return g.ChaosIncursionUntil.Sub(now)
|
||
}
|
||
|
||
// DefendAgainstChaos — активная защита от хаоситов (повторные
|
||
// нажатия во время вторжения). Возвращает false, если вторжения
|
||
// сейчас нет — действие недоступно вне его рамок.
|
||
func (g *CorpseStarchGameState) DefendAgainstChaos(now time.Time) bool {
|
||
if !g.InChaosIncursion(now) {
|
||
return false
|
||
}
|
||
g.ChaosDefendClicks++
|
||
return true
|
||
}
|
||
|
||
// resolveChaosIncursion завершает текущее вторжение: если игрок
|
||
// набрал достаточно активных защит за время вторжения — начисляет
|
||
// бонус в благодарность за стойкость станции.
|
||
func (g *CorpseStarchGameState) resolveChaosIncursion() {
|
||
if g.ChaosDefendClicks >= corpseStarchDefendClicksForBonus {
|
||
g.earn(corpseStarchDefendBonusReward)
|
||
g.addLog(g.modeKey("corpsestarch.log.incursion_repelled_bonus", "corpsestarch.log.incursion_repelled_bonus.demonic"))
|
||
} else {
|
||
g.addLog(g.modeKey("corpsestarch.log.incursion_repelled", "corpsestarch.log.incursion_repelled.demonic"))
|
||
}
|
||
g.ChaosIncursionUntil = time.Time{}
|
||
g.ChaosDefendClicks = 0
|
||
}
|
||
|
||
// StartMission запускает миссию defIdx, если она открыта и сейчас
|
||
// нет другой активной миссии.
|
||
func (g *CorpseStarchGameState) StartMission(now time.Time, defIdx int) bool {
|
||
if g.ActiveMission != nil {
|
||
return false
|
||
}
|
||
if defIdx < 0 || defIdx >= len(corpseStarchMissionDefs) {
|
||
return false
|
||
}
|
||
if g.TotalEarned < corpseStarchMissionDefs[defIdx].UnlockAt {
|
||
return false
|
||
}
|
||
g.ActiveMission = &corpseStarchMission{DefIdx: defIdx, StartedAt: now}
|
||
return true
|
||
}
|
||
|
||
// MissionRemaining возвращает оставшееся время активной миссии (0,
|
||
// если миссии нет или она уже завершена).
|
||
func (g *CorpseStarchGameState) MissionRemaining(now time.Time) time.Duration {
|
||
if g.ActiveMission == nil {
|
||
return 0
|
||
}
|
||
def := corpseStarchMissionDefs[g.ActiveMission.DefIdx]
|
||
remaining := def.Duration - now.Sub(g.ActiveMission.StartedAt)
|
||
if remaining < 0 {
|
||
return 0
|
||
}
|
||
return remaining
|
||
}
|
||
|
||
// CollectMission забирает награду завершённой миссии (если она уже
|
||
// истекла); roll — случайное число в [0,1) для определения награды
|
||
// в диапазоне [RewardMin,RewardMax). Возвращает (награда, true) при
|
||
// успехе.
|
||
func (g *CorpseStarchGameState) CollectMission(now time.Time, roll float64) (float64, bool) {
|
||
if g.ActiveMission == nil {
|
||
return 0, false
|
||
}
|
||
if g.MissionRemaining(now) > 0 {
|
||
return 0, false
|
||
}
|
||
def := corpseStarchMissionDefs[g.ActiveMission.DefIdx]
|
||
reward := def.RewardMin + roll*(def.RewardMax-def.RewardMin)
|
||
g.earn(reward)
|
||
g.MissionsDone++
|
||
g.ActiveMission = nil
|
||
g.addLog("corpsestarch.log.mission_done")
|
||
return reward, true
|
||
}
|
||
|
||
// ServitorsUnlocked, ShopUnlocked, SumpUnlocked, MissionsUnlocked —
|
||
// проверки открытия соответствующих разделов интерфейса.
|
||
func (g *CorpseStarchGameState) ServitorsUnlocked() bool {
|
||
return g.TotalEarned >= corpseStarchUnlockServitors
|
||
}
|
||
func (g *CorpseStarchGameState) ShopUnlocked() bool {
|
||
return g.TotalEarned >= corpseStarchUnlockShop
|
||
}
|
||
func (g *CorpseStarchGameState) SumpUnlocked() bool {
|
||
return g.TotalEarned >= corpseStarchUnlockSump
|
||
}
|
||
func (g *CorpseStarchGameState) MissionsUnlocked() bool {
|
||
return g.TotalEarned >= corpseStarchUnlockMissions
|
||
}
|
||
|
||
// ServitorUnlocked проверяет, виден ли конкретный тир idx (может
|
||
// быть ещё не открыт, даже если раздел "Сервиторы" уже виден).
|
||
func (g *CorpseStarchGameState) ServitorUnlocked(idx int) bool {
|
||
return g.TotalEarned >= corpseStarchServitorDefs[idx].UnlockAt
|
||
}
|
||
|
||
// MissionUnlocked проверяет, видна ли конкретная миссия idx.
|
||
func (g *CorpseStarchGameState) MissionUnlocked(idx int) bool {
|
||
return g.TotalEarned >= corpseStarchMissionDefs[idx].UnlockAt
|
||
}
|