go_games_collection/wayfarer_ship.go

299 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
// MissileCapacity — сколько ракет (любого типа суммарно)
// помещается на борту; зависит от вида корабля — у стартового 2.
// Пока в игре только один вид корабля, но задел на будущее уже
// сделан отдельным полем, а не константой.
MissileCapacity int
RegularMissiles int // обычные ракеты — слабее лазера по щитам, сильнее по голому корпусу; от них можно увернуться
HomingMissiles int // самонаводящиеся — от них увернуться нельзя, только обмануть тепловой ловушкой
Decoys 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,
MissileCapacity: 2,
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("вы уже в этой системе")
errWayfarerNoMissileSlots = wayfarerErr("все ракетные слоты уже заняты")
errWayfarerNoMissiles = wayfarerErr("на борту нет ракет этого типа")
errWayfarerInvalidWeapon = wayfarerErr("неизвестный тип вооружения")
errWayfarerInvalidShieldDistribution = 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
}
// MissilesLoaded — сколько ракет любого типа сейчас на борту.
func (s *WayfarerShip) MissilesLoaded() int {
return s.RegularMissiles + s.HomingMissiles
}
// BuyMissile покупает одну ракету указанного типа (обычную или
// самонаводящуюся), если хватает кредитов и есть свободный ракетный
// слот (MissileCapacity).
func (s *WayfarerShip) BuyMissile(weapon WayfarerWeaponType, pricePerUnit float64) error {
if weapon != WayfarerWeaponMissileRegular && weapon != WayfarerWeaponMissileHoming {
return errWayfarerInvalidWeapon
}
if s.MissilesLoaded() >= s.MissileCapacity {
return errWayfarerNoMissileSlots
}
if pricePerUnit > s.Credits {
return errWayfarerInsufficientFunds
}
s.Credits -= pricePerUnit
if weapon == WayfarerWeaponMissileRegular {
s.RegularMissiles++
} else {
s.HomingMissiles++
}
return nil
}
// BuyDecoy покупает одну тепловую ловушку — расходуется, чтобы
// гарантированно обмануть самонаводящуюся ракету в бою (см.
// WayfarerCombatState.UseDecoy). Вместимость трюма/ракетных слотов
// на ловушки не распространяется — считаем их мелким расходником.
func (s *WayfarerShip) BuyDecoy(pricePerUnit float64) error {
if pricePerUnit > s.Credits {
return errWayfarerInsufficientFunds
}
s.Credits -= pricePerUnit
s.Decoys++
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)
}