108 lines
3.3 KiB
Go
108 lines
3.3 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
type TechniqueFood struct {
|
|
RecoverMP uint32 `json:"recover_mp"`
|
|
MaxUse uint32 `json:"max_use"`
|
|
PileLimit uint32 `json:"pile_limit"`
|
|
}
|
|
type MazeSkillRule struct {
|
|
ID uint32 `json:"MazeSkillId"`
|
|
MPCost uint32 `json:"MPCost"`
|
|
Trigger string `json:"SkillTriggerKey"`
|
|
}
|
|
type TechniqueRules struct {
|
|
ClientVersion string `json:"client_version"`
|
|
BaseMaxMP uint32 `json:"base_max_mp"`
|
|
InitialMP uint32 `json:"initial_mp"`
|
|
MaxMPByAvatar map[uint32]uint32 `json:"max_mp_by_avatar"`
|
|
NonAttackTechniques []uint32 `json:"non_attack_techniques"`
|
|
Avatars map[uint32][]MazeSkillRule `json:"avatars"`
|
|
Foods map[uint32]TechniqueFood `json:"foods"`
|
|
InitialFoodCount uint32 `json:"initial_food_count"`
|
|
AutoFoodDefault bool `json:"auto_food_default"`
|
|
}
|
|
|
|
var techniqueOnce sync.Once
|
|
var techniqueRules TechniqueRules
|
|
var techniqueErr error
|
|
|
|
func LoadTechniqueRules() (*TechniqueRules, error) {
|
|
techniqueOnce.Do(func() {
|
|
raw, err := ReadResourceFile("resources/TechniqueRules.json")
|
|
if err != nil {
|
|
techniqueErr = err
|
|
return
|
|
}
|
|
if err = json.Unmarshal(raw, &techniqueRules); err != nil {
|
|
techniqueErr = err
|
|
return
|
|
}
|
|
cfg, err := LoadRuntimeConfig()
|
|
if err != nil {
|
|
techniqueErr = err
|
|
return
|
|
}
|
|
if techniqueRules.ClientVersion != cfg.ClientVersion || techniqueRules.BaseMaxMP == 0 || techniqueRules.InitialMP > techniqueRules.BaseMaxMP {
|
|
techniqueErr = fmt.Errorf("invalid or mismatched TechniqueRules client resource")
|
|
return
|
|
}
|
|
for id, f := range techniqueRules.Foods {
|
|
if id == 0 || f.RecoverMP == 0 || f.MaxUse == 0 || techniqueRules.InitialFoodCount > f.PileLimit {
|
|
techniqueErr = fmt.Errorf("invalid technique food %d", id)
|
|
return
|
|
}
|
|
}
|
|
})
|
|
return &techniqueRules, techniqueErr
|
|
}
|
|
func (r *TechniqueRules) MaxMP(avatars map[uint32]uint32) uint32 {
|
|
maximum := r.BaseMaxMP
|
|
// PearlSR buildLineup selects the largest team cap; it does not add both caps.
|
|
for _, id := range avatars {
|
|
if value := r.MaxMPByAvatar[id]; value > maximum {
|
|
maximum = value
|
|
}
|
|
}
|
|
return maximum
|
|
}
|
|
|
|
// Called only by an explicit inventory/cast operation, never by save readers.
|
|
func (f *FreesrData) EnsureTechniqueFoods() error {
|
|
if f.TechniqueFoodStock != nil && f.AutoTechniqueFood != nil {
|
|
return nil
|
|
}
|
|
r, err := LoadTechniqueRules()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if f.TechniqueFoodStock == nil {
|
|
f.TechniqueFoodStock = map[uint32]uint32{}
|
|
for id := range r.Foods {
|
|
f.TechniqueFoodStock[id] = r.InitialFoodCount
|
|
}
|
|
}
|
|
if f.AutoTechniqueFood == nil {
|
|
value := r.AutoFoodDefault
|
|
f.AutoTechniqueFood = &value
|
|
}
|
|
return f.SavePersistentChecked()
|
|
}
|
|
|
|
// Refresh just the setting, and advance its merge baseline so a subsequent
|
|
// session position save cannot undo an external API toggle.
|
|
func (f *FreesrData) RefreshTechniqueSettings(p *Persistent) {
|
|
f.AutoTechniqueFood = p.AutoTechniqueFood
|
|
if snapshot, err := jsonObject(f.persistentSnapshot()); err == nil {
|
|
if f.persistentBaseline == nil {
|
|
f.persistentBaseline = map[string]any{}
|
|
}
|
|
f.persistentBaseline["auto_technique_food"] = snapshot["auto_technique_food"]
|
|
}
|
|
}
|