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, ¶meter) 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 }