Files
GaCy-GO/internal/common/currency_wars_bonfire.go
T
2026-10-28 18:54:43 +07:00

119 lines
4.0 KiB
Go

package common
import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"strings"
)
type CurrencyWarsBonfireOption struct {
EventID, OptionID, DialogID uint32
JsonPath, OptionShowType string
EffectParamList []struct{ Value float64 }
}
type CurrencyWarsBonfireRecipe struct {
ID, Option uint32
Status, Engine string
Effects []struct {
Trigger string
Actions []struct {
Action, Name string
Value json.RawMessage
}
}
}
type CurrencyWarsBonfire struct {
Events map[uint32]uint32
Options []CurrencyWarsBonfireOption
Configs map[string]CurrencyWarsBonfireRecipe
Heal map[uint32]uint32
}
// This is a labelled FFGO implementation reference, not a Client table capture.
// Its event IDs must additionally occur on the active Client run's node.
func LoadCurrencyWarsBonfire(c *CurrencyWarsCatalog) (*CurrencyWarsBonfire, error) {
raw, err := ReadResourceFile("resources/CurrencyWarsBonfire.json")
if err != nil {
return nil, err
}
var r struct {
Format uint32 `json:"format_version"`
Client string `json:"target_client_version"`
Assembly string `json:"target_assembly_sha256"`
Binary string `json:"source_binary_sha256"`
Tables struct {
Events []struct{ ID, PetPalID, DialogID uint32 } `json:"events"`
Options []CurrencyWarsBonfireOption `json:"options"`
Heal []struct{ ChapterID, HealNum uint32 } `json:"heal"`
} `json:"tables"`
Configs map[string]CurrencyWarsBonfireRecipe `json:"configs"`
}
if err = json.Unmarshal(raw, &r); err != nil {
return nil, err
}
if c == nil || r.Format != 1 || r.Client != c.ClientVersion || r.Assembly != c.Provenance.GameAssemblySHA256 {
return nil, fmt.Errorf("CW Bonfire reference compatibility differs")
}
if h, e := hex.DecodeString(r.Binary); e != nil || len(h) != 32 {
return nil, fmt.Errorf("CW Bonfire source identity missing")
}
out := &CurrencyWarsBonfire{Events: map[uint32]uint32{}, Options: r.Tables.Options, Configs: r.Configs, Heal: map[uint32]uint32{}}
for _, v := range r.Tables.Events {
_, duplicate := out.Events[v.ID]
if v.ID == 0 || duplicate {
return nil, fmt.Errorf("CW Bonfire event identity invalid")
}
out.Events[v.ID] = v.DialogID
}
for _, v := range r.Tables.Heal {
if v.ChapterID == 0 || out.Heal[v.ChapterID] != 0 {
return nil, fmt.Errorf("CW Bonfire heal identity invalid")
}
out.Heal[v.ChapterID] = v.HealNum
}
seen := map[[2]uint32]bool{}
for _, v := range out.Options {
key := [2]uint32{v.EventID, v.OptionID}
_, eventExists := out.Events[v.EventID]
if v.EventID == 0 || v.OptionID == 0 || !eventExists || seen[key] {
return nil, fmt.Errorf("CW Bonfire option identity invalid")
}
seen[key] = true
}
return out, nil
}
func (b *CurrencyWarsBonfire) OptionRules(option CurrencyWarsBonfireOption) (map[string]uint32, error) {
path := strings.TrimPrefix(option.JsonPath, "Config/Level/GridFight/")
recipe, ok := b.Configs[path]
if !ok || recipe.ID != option.EventID || recipe.Option != option.OptionID || recipe.Status != "Implemented" || recipe.Engine != "TeaBreakOption" {
return nil, fmt.Errorf("CW Bonfire option %d/%d has no supported recipe", option.EventID, option.OptionID)
}
rules := map[string]uint32{}
for _, effect := range recipe.Effects {
if effect.Trigger != "Immediately" {
return nil, fmt.Errorf("CW Bonfire trigger %s not implemented", effect.Trigger)
}
for _, action := range effect.Actions {
var parameter string
_ = json.Unmarshal(action.Value, &parameter)
if action.Action != "SetRule" || action.Name != "PetPalStarDown" || parameter != "#1" || len(option.EffectParamList) < 1 {
return nil, fmt.Errorf("CW Bonfire action %s/%s not implemented", action.Action, action.Name)
}
value := option.EffectParamList[0].Value
if math.IsNaN(value) || math.IsInf(value, 0) || value < 0 || value > 100 || value != math.Trunc(value) {
return nil, fmt.Errorf("CW Bonfire probability invalid")
}
rules[action.Name] = uint32(value)
}
}
if len(rules) == 0 {
return nil, fmt.Errorf("CW Bonfire option has no implemented effects")
}
return rules, nil
}