Files
bd2/go/internal/server/deck/field_settings.go
T

243 lines
6.5 KiB
Go

package deck
import (
"bd2server/internal/server/gamedata"
"bd2server/internal/server/player"
"bd2server/internal/server/stateio"
"bd2server/internal/server/wire"
"encoding/binary"
"encoding/json"
"fmt"
"time"
)
type fieldSettings struct {
TalentIDs []uint64 `json:"talent_ids"`
AutoRevive bool `json:"auto_revive"`
Caster uint64 `json:"caster"`
}
// A new formal entry owns quick slots and automatic recovery preferences; the
// existing deck core and historical protobuf schema keep their current layout.
func (s *Store) AttachFieldSettings(d *gamedata.FieldSettingsDesign) error {
if d == nil || d.TalentSlots <= 0 {
return fmt.Errorf("deck: invalid field settings design")
}
s.mu.Lock()
defer s.mu.Unlock()
s.fieldSettingsDesign = d
_, e := s.loadFieldSettings()
return e
}
func (s *Store) AttachFieldSettingsPack(source func() (int, error)) error {
if source == nil {
return fmt.Errorf("deck: nil field pack provider")
}
s.mu.Lock()
defer s.mu.Unlock()
s.fieldSettingsPack = source
return nil
}
func (s *Store) temporaryAllowed(c player.Character) bool {
if s.fieldSettingsDesign == nil || s.fieldSettingsPack == nil {
return false
}
p, e := s.fieldSettingsPack()
return e == nil && s.fieldSettingsDesign.CharacterTemporaryPack[c.ID] == p && p != 0
}
func (s *Store) loadFieldSettings() (fieldSettings, error) {
d := s.fieldSettingsDesign
if d == nil {
return fieldSettings{}, fmt.Errorf("deck: field settings unavailable")
}
v := fieldSettings{TalentIDs: make([]uint64, d.TalentSlots)}
raw, ok, e := s.storage.LoadEntry("deck", "field_settings", "state")
if e != nil || !ok {
return v, e
}
if e = stateExactFieldSettings(raw, &v); e != nil {
return v, e
}
if e = s.validateFieldSettings(v, false); e != nil {
return v, e
}
return v, nil
}
func stateExactFieldSettings(raw []byte, v *fieldSettings) error {
var fields map[string]json.RawMessage
if e := json.Unmarshal(raw, &fields); e != nil {
return e
}
if len(fields) != 3 || fields["talent_ids"] == nil || fields["auto_revive"] == nil || fields["caster"] == nil {
return fmt.Errorf("deck: invalid field settings layout")
}
return json.Unmarshal(raw, v)
}
func (s *Store) validateFieldSettings(v fieldSettings, live bool) error {
if len(v.TalentIDs) != s.fieldSettingsDesign.TalentSlots {
return fmt.Errorf("deck: talent slot count")
}
owned := map[uint64]bool{}
if !live {
for id := range s.fieldSettingsDesign.CharacterTalentClass {
owned[id] = true
}
}
if s.characters != nil {
for _, c := range s.characters.RawAll() {
if live && player.CharacterExpired(c, time.Now()) {
continue
}
if !player.IsStoryCharacter(c) || s.temporaryAllowed(c) || !live && s.fieldSettingsDesign.CharacterTemporaryPack[c.ID] != 0 {
owned[c.ID] = true
}
}
}
seen := map[uint64]bool{}
for _, id := range v.TalentIDs {
if id == 0 {
continue
}
if id > 2147483647 || seen[id] || !owned[id] {
return fmt.Errorf("deck: talent slot character unavailable %d", id)
}
if _, ok := s.fieldSettingsDesign.CharacterTalentClass[id]; !ok {
return fmt.Errorf("deck: character has no talent")
}
seen[id] = true
}
if v.Caster != 0 {
if s.characters == nil {
return fmt.Errorf("deck: character provider unavailable")
}
c, ok := s.characters.Find(v.Caster)
if !ok || player.IsStoryCharacter(c) || player.IsCharmCharacter(c) || s.fieldSettingsDesign.CharacterTalentClass[c.ID] != 10 {
return fmt.Errorf("deck: invalid automatic recovery caster")
}
}
if v.AutoRevive && v.Caster == 0 {
return fmt.Errorf("deck: automatic recovery needs caster")
}
return nil
}
func (s *Store) TalentCharacterIDs() ([]uint64, error) {
s.mu.RLock()
defer s.mu.RUnlock()
v, e := s.loadFieldSettings()
return s.projectTalentSlots(v.TalentIDs), e
}
func (s *Store) projectTalentSlots(ids []uint64) []uint64 {
out := append([]uint64(nil), ids...)
owned := map[uint64]bool{}
charm := map[uint64]bool{}
if s.characters != nil {
for _, c := range s.characters.RawAll() {
if player.CharacterExpired(c, time.Now()) {
continue
}
if player.IsCharmCharacter(c) {
owned[c.ID] = true
charm[c.ID] = true
continue
}
if !player.IsStoryCharacter(c) || s.temporaryAllowed(c) {
owned[c.ID] = true
}
}
}
for i, id := range out {
if id != 0 && !owned[id] {
out[i] = 0
continue
}
if s.fieldSettingsDesign.CharacterTemporaryPack[id] != 0 && !charm[id] {
p := 0
if s.fieldSettingsPack != nil {
p, _ = s.fieldSettingsPack()
}
if p != s.fieldSettingsDesign.CharacterTemporaryPack[id] {
out[i] = 0
}
}
}
return out
}
func (s *Store) AutoReviveSettings() (bool, uint64, error) {
s.mu.RLock()
defer s.mu.RUnlock()
v, e := s.loadFieldSettings()
return v.AutoRevive, v.Caster, e
}
func (s *Store) FieldControlType() uint64 {
s.mu.RLock()
defer s.mu.RUnlock()
return s.state.FieldCharControlDeckType
}
func (s *Store) handleFieldSettings(path string, req []byte) (int, []byte, bool, error) {
if e := checkSeq(req); e != nil {
return 0, nil, true, e
}
s.mu.Lock()
defer s.mu.Unlock()
v, e := s.loadFieldSettings()
if e != nil {
return 0, nil, true, e
}
code := 71
var body []byte
if path == "/TalentSlotSave" {
var ids []uint64
e = wire.Walk(req, func(f wire.Field) error {
if f.Number != 2 {
return nil
}
if f.Type != 0 && f.Type != 2 {
return fmt.Errorf("deck: invalid talent slots")
}
b := f.Value
for len(b) > 0 {
n, k := binary.Uvarint(b)
if k <= 0 {
return fmt.Errorf("deck: invalid packed talent slots")
}
ids = append(ids, n)
if len(ids) > s.fieldSettingsDesign.TalentSlots {
return fmt.Errorf("deck: too many talent slots")
}
b = b[k:]
}
return nil
})
if e != nil {
return 0, nil, true, e
}
v.TalentIDs = ids
} else {
code = 372
on, _, e := wire.Varint(req, 2)
if e != nil || on > 1 {
return 0, nil, true, fmt.Errorf("deck: invalid auto-recovery switch")
}
caster, _, e := wire.Varint(req, 3)
if e != nil {
return 0, nil, true, e
}
v.AutoRevive = on != 0
v.Caster = caster
body = wire.AppendVarint(nil, 1, on)
body = wire.AppendVarint(body, 2, caster)
}
if e = s.validateFieldSettings(v, path == "/TalentSlotSave"); e != nil {
return 0, nil, true, e
}
raw, e := json.Marshal(v)
if e == nil {
core, err := json.Marshal(s.state)
if err != nil {
return 0, nil, true, err
}
e = s.storage.SaveWithEntries("deck", core, []stateio.EntryMutation{{Bucket: "field_settings", Key: "state", Payload: raw}})
}
return code, body, true, e
}