244 lines
8.3 KiB
Go
244 lines
8.3 KiB
Go
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)
|
||
}
|