package main import "math" // WayfarerEquipment — необязательное снаряжение корабля. type WayfarerEquipment int const ( EquipFuelScoop WayfarerEquipment = iota EquipCargoExpansion EquipShieldBooster EquipEscapePod EquipBetterLaser ) // WayfarerShip — полное состояние корабля и его владельца. type WayfarerShip struct { Credits float64 FuelCapacity float64 Fuel float64 FuelPerLY float64 // расход топлива на 1 условный световой год CargoCapacity int Cargo []int // Cargo[i] — количество товара i в трюме CargoCostBasis []float64 // CargoCostBasis[i] — средняя цена покупки за единицу текущего груза i (0, если груза нет) MaxHull int Hull int MaxShield int Shield int LaserPower int Equipment map[WayfarerEquipment]bool Kills int // сколько вражеских кораблей уничтожено — для рейтинга пилота CurrentSystem WayfarerSystemID Visited map[WayfarerSystemID]bool ActiveQuest *WayfarerQuestState CompletedQuests map[string]bool FailedQuests map[string]bool } // NewWayfarerShip создаёт начальный корабль игрока — скромный, но // работоспособный торговец на старте карьеры. func NewWayfarerShip(start WayfarerSystemID) *WayfarerShip { s := &WayfarerShip{ Credits: 1000, FuelCapacity: 7, Fuel: 7, FuelPerLY: 1.0 / 30.0, // условно: полный бак = ~7 прыжков по системе средней удалённости CargoCapacity: 20, MaxHull: 100, Hull: 100, MaxShield: 50, Shield: 50, LaserPower: 12, Equipment: map[WayfarerEquipment]bool{}, CurrentSystem: start, Visited: map[WayfarerSystemID]bool{start: true}, CompletedQuests: map[string]bool{}, FailedQuests: map[string]bool{}, } s.Cargo = make([]int, len(WayfarerCommodities)) s.CargoCostBasis = make([]float64, len(WayfarerCommodities)) return s } // CargoUsed — сколько единиц трюма сейчас занято. func (s *WayfarerShip) CargoUsed() int { used := 0 for _, n := range s.Cargo { used += n } return used } // CargoFree — сколько единиц трюма ещё свободно. func (s *WayfarerShip) CargoFree() int { return s.CargoCapacity - s.CargoUsed() } // MaxJumpRange — максимальное расстояние прыжка при полном баке. func (s *WayfarerShip) MaxJumpRange() float64 { return s.FuelCapacity / s.FuelPerLY } // FuelRange — максимальное расстояние прыжка при текущем запасе // топлива. func (s *WayfarerShip) FuelRange() float64 { return s.Fuel / s.FuelPerLY } var ( errWayfarerInsufficientFunds = wayfarerErr("недостаточно кредитов") errWayfarerCargoFull = wayfarerErr("трюм заполнен") errWayfarerNoCargo = wayfarerErr("этого товара нет в трюме в таком количестве") errWayfarerOutOfRange = wayfarerErr("не хватит топлива долететь так далеко") errWayfarerSameSystem = wayfarerErr("вы уже в этой системе") ) type wayfarerErr string func (e wayfarerErr) Error() string { return string(e) } // BuyCommodity покупает qty единиц товара commodityIdx по цене // unitPrice за единицу (цена вычисляется вызывающим кодом через // CommodityPrice — так UI и движок используют одно и то же число, // без риска рассинхронизации от повторного вызова генератора // случайности). Заодно обновляет среднюю цену покупки этого товара // в трюме (взвешенное среднее старой и новой партии) — чтобы потом // на рынке можно было показать, по какой цене товар куплен. func (s *WayfarerShip) BuyCommodity(commodityIdx int, qty int, unitPrice float64) error { if qty <= 0 { return nil } if qty > s.CargoFree() { return errWayfarerCargoFull } cost := unitPrice * float64(qty) if cost > s.Credits { return errWayfarerInsufficientFunds } s.Credits -= cost oldQty := s.Cargo[commodityIdx] oldTotal := s.CargoCostBasis[commodityIdx] * float64(oldQty) s.Cargo[commodityIdx] += qty s.CargoCostBasis[commodityIdx] = (oldTotal + cost) / float64(s.Cargo[commodityIdx]) return nil } // SellCommodity продаёт qty единиц товара commodityIdx по цене // unitPrice за единицу. func (s *WayfarerShip) SellCommodity(commodityIdx int, qty int, unitPrice float64) error { if qty <= 0 { return nil } if qty > s.Cargo[commodityIdx] { return errWayfarerNoCargo } s.Cargo[commodityIdx] -= qty s.Credits += unitPrice * float64(qty) if s.Cargo[commodityIdx] == 0 { s.CargoCostBasis[commodityIdx] = 0 // трюм по этому товару опустел — старая цена покупки больше не актуальна } return nil } // Refuel заправляет корабль на amount единиц топлива (не больше // полного бака) по цене pricePerUnit за единицу. func (s *WayfarerShip) Refuel(amount float64, pricePerUnit float64) error { if amount <= 0 { return nil } if s.Fuel+amount > s.FuelCapacity { amount = s.FuelCapacity - s.Fuel } cost := amount * pricePerUnit if cost > s.Credits { return errWayfarerInsufficientFunds } s.Credits -= cost s.Fuel += amount return nil } // Repair чинит корпус на amount очков по цене pricePerPoint. func (s *WayfarerShip) Repair(amount int, pricePerPoint float64) error { if amount <= 0 { return nil } if s.Hull+amount > s.MaxHull { amount = s.MaxHull - s.Hull } cost := float64(amount) * pricePerPoint if cost > s.Credits { return errWayfarerInsufficientFunds } s.Credits -= cost s.Hull += amount return nil } // JumpTo перемещает корабль в целевую систему, если хватает // топлива, списывая расход по фактическому расстоянию. func (s *WayfarerShip) JumpTo(target WayfarerSystemID) error { if target == s.CurrentSystem { return errWayfarerSameSystem } from := GenerateSystem(s.CurrentSystem) to := GenerateSystem(target) dist := from.Distance(to) fuelNeeded := dist * s.FuelPerLY if fuelNeeded > s.Fuel { return errWayfarerOutOfRange } s.Fuel -= fuelNeeded s.CurrentSystem = target s.Visited[target] = true return nil } // wayfarerRatingThresholds — пороги числа побед для присвоения // пилотского рейтинга (намёк на классическую систему рангов Elite — // свой набор названий и порогов, не копия оригинальных чисел). var wayfarerRatingThresholds = []struct { Kills int Key string }{ {0, "rating.harmless"}, {4, "rating.mostly_harmless"}, {12, "rating.poor"}, {28, "rating.average"}, {50, "rating.above_average"}, {100, "rating.competent"}, {200, "rating.dangerous"}, {400, "rating.deadly"}, {800, "rating.wayfarer_elite"}, } // PilotRating возвращает ключ перевода текущего пилотского ранга. func (s *WayfarerShip) PilotRating() string { rating := wayfarerRatingThresholds[0].Key for _, r := range wayfarerRatingThresholds { if s.Kills >= r.Kills { rating = r.Key } } return rating } // NetWorth — кредиты плюс примерная стоимость груза в трюме (по // половине базовой цены каждого товара — грубая, но достаточная для // отображения общего "веса" игрока оценка). func (s *WayfarerShip) NetWorth() float64 { worth := s.Credits for i, n := range s.Cargo { worth += float64(n) * WayfarerCommodities[i].BasePrice * 0.5 } return math.Round(worth) }