Save endgame progress, team presets, technique support and camera controls
This commit is contained in:
1 parent
a681affdb3
commit
639cef7d19
52 files changed
+11940
-766
No files matched your search
@@ -0,0 +1,107 @@
|
||||
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"]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user