243 lines
6.5 KiB
Go
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
|
|
}
|