150 lines
6 KiB
Go
150 lines
6 KiB
Go
package main
|
||
|
||
// WayfarerQuestState — прогресс игрока в активном квесте (какой
|
||
// квест, на каком шаге). Сами тексты и ветвления хранятся отдельно,
|
||
// в WayfarerQuestDefs — так, чтобы состояние партии было маленьким и
|
||
// не дублировало неизменный контент квеста.
|
||
type WayfarerQuestState struct {
|
||
Key string
|
||
Step int
|
||
}
|
||
|
||
// WayfarerQuestChoice — один вариант выбора на шаге квеста.
|
||
type WayfarerQuestChoice struct {
|
||
LabelKey string // ключ перевода текста варианта выбора
|
||
|
||
// NextStep — индекс следующего шага квеста, если выбор не
|
||
// завершает квест. Игнорируется, если Terminal == true.
|
||
NextStep int
|
||
|
||
// Terminal — завершает ли этот выбор квест немедленно.
|
||
Terminal bool
|
||
Success bool // при Terminal: считается ли это успехом или провалом квеста
|
||
|
||
ResultKey string // ключ перевода текста-результата этого выбора
|
||
|
||
CreditsDelta float64 // изменение кредитов (может быть отрицательным)
|
||
HullDelta int // изменение прочности корпуса (может быть отрицательным)
|
||
CargoIdx int // индекс товара в WayfarerCommodities, которого касается CargoDelta (-1, если не используется)
|
||
CargoDelta int // изменение количества этого товара в трюме
|
||
KillsDelta int // изменение числа побед (для пилотского рейтинга)
|
||
}
|
||
|
||
// WayfarerQuestStep — один узел ветвления квеста: текст ситуации и
|
||
// доступные варианты выбора.
|
||
type WayfarerQuestStep struct {
|
||
TextKey string
|
||
Choices []WayfarerQuestChoice
|
||
}
|
||
|
||
// WayfarerQuestDef — полное описание одного квеста: заголовок и все
|
||
// его шаги (шаг 0 — стартовый).
|
||
type WayfarerQuestDef struct {
|
||
Key string
|
||
TitleKey string
|
||
BriefKey string // короткое описание для списка доступных квестов
|
||
Steps []WayfarerQuestStep
|
||
}
|
||
|
||
// FindWayfarerQuest ищет определение квеста по ключу среди
|
||
// WayfarerQuestDefs.
|
||
func FindWayfarerQuest(key string) (WayfarerQuestDef, bool) {
|
||
for _, q := range WayfarerQuestDefs {
|
||
if q.Key == key {
|
||
return q, true
|
||
}
|
||
}
|
||
return WayfarerQuestDef{}, false
|
||
}
|
||
|
||
// StartQuest запускает квест key с самого начала (шаг 0), если он
|
||
// ещё не завершён и не провален ранее и сейчас нет другого
|
||
// активного квеста.
|
||
func (s *WayfarerShip) StartQuest(key string) error {
|
||
if s.ActiveQuest != nil {
|
||
return errWayfarerQuestAlreadyActive
|
||
}
|
||
if s.CompletedQuests[key] || s.FailedQuests[key] {
|
||
return errWayfarerQuestAlreadyDone
|
||
}
|
||
if _, ok := FindWayfarerQuest(key); !ok {
|
||
return errWayfarerQuestNotFound
|
||
}
|
||
s.ActiveQuest = &WayfarerQuestState{Key: key, Step: 0}
|
||
return nil
|
||
}
|
||
|
||
// CurrentQuestStep возвращает текущий шаг активного квеста (или
|
||
// (_, false), если квеста нет).
|
||
func (s *WayfarerShip) CurrentQuestStep() (WayfarerQuestStep, bool) {
|
||
if s.ActiveQuest == nil {
|
||
return WayfarerQuestStep{}, false
|
||
}
|
||
def, ok := FindWayfarerQuest(s.ActiveQuest.Key)
|
||
if !ok || s.ActiveQuest.Step >= len(def.Steps) {
|
||
return WayfarerQuestStep{}, false
|
||
}
|
||
return def.Steps[s.ActiveQuest.Step], true
|
||
}
|
||
|
||
// ChooseQuestOption применяет выбранный вариант choiceIdx текущего
|
||
// шага активного квеста: списывает/начисляет ресурсы и либо
|
||
// переходит на следующий шаг, либо завершает квест (успехом или
|
||
// провалом).
|
||
func (s *WayfarerShip) ChooseQuestOption(choiceIdx int) (WayfarerQuestChoice, error) {
|
||
step, ok := s.CurrentQuestStep()
|
||
if !ok {
|
||
return WayfarerQuestChoice{}, errWayfarerNoActiveQuest
|
||
}
|
||
if choiceIdx < 0 || choiceIdx >= len(step.Choices) {
|
||
return WayfarerQuestChoice{}, errWayfarerInvalidChoice
|
||
}
|
||
choice := step.Choices[choiceIdx]
|
||
|
||
s.Credits += choice.CreditsDelta
|
||
if s.Credits < 0 {
|
||
s.Credits = 0
|
||
}
|
||
s.Hull += choice.HullDelta
|
||
if s.Hull < 0 {
|
||
s.Hull = 0
|
||
}
|
||
if s.Hull > s.MaxHull {
|
||
s.Hull = s.MaxHull
|
||
}
|
||
if choice.CargoIdx >= 0 && choice.CargoIdx < len(s.Cargo) {
|
||
s.Cargo[choice.CargoIdx] += choice.CargoDelta
|
||
if s.Cargo[choice.CargoIdx] < 0 {
|
||
s.Cargo[choice.CargoIdx] = 0
|
||
}
|
||
}
|
||
s.Kills += choice.KillsDelta
|
||
|
||
key := s.ActiveQuest.Key
|
||
if choice.Terminal {
|
||
if choice.Success {
|
||
s.CompletedQuests[key] = true
|
||
} else {
|
||
s.FailedQuests[key] = true
|
||
}
|
||
s.ActiveQuest = nil
|
||
} else {
|
||
s.ActiveQuest.Step = choice.NextStep
|
||
}
|
||
return choice, nil
|
||
}
|
||
|
||
// AbandonQuest прерывает текущий активный квест без последствий,
|
||
// как если бы он никогда не начинался (не считается ни успехом, ни
|
||
// провалом — его можно будет начать заново позже).
|
||
func (s *WayfarerShip) AbandonQuest() {
|
||
s.ActiveQuest = nil
|
||
}
|
||
|
||
var (
|
||
errWayfarerQuestAlreadyActive = wayfarerErr("сейчас уже выполняется другой квест")
|
||
errWayfarerQuestAlreadyDone = wayfarerErr("этот квест уже завершён или провален ранее")
|
||
errWayfarerQuestNotFound = wayfarerErr("квест с таким ключом не найден")
|
||
errWayfarerNoActiveQuest = wayfarerErr("сейчас нет активного квеста")
|
||
errWayfarerInvalidChoice = wayfarerErr("недопустимый номер варианта")
|
||
)
|