package common import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" ) // Commands are separate from the run revision. The live game session applies // them only during Preparation, so a web command cannot invalidate settlement. type CurrencyWarsCommand struct { ID string `json:"id"` UID uint32 `json:"uid"` RunID uint32 `json:"run_id"` Field string `json:"field"` Value uint32 `json:"value"` Quantity uint32 `json:"quantity,omitempty"` Star uint32 `json:"star,omitempty"` } func ReadCurrencyWarsCommands() ([]CurrencyWarsCommand, error) { root, err := readCurrencyWarsPersistentRoot() if err != nil { return nil, err } var commands []CurrencyWarsCommand if raw, ok := root["currency_wars_commands"]; ok { err = json.Unmarshal(raw, &commands) } return commands, err } func QueueCurrencyWarsCommand(uid, runID uint32, field string, value uint32) (string, error) { return QueueCurrencyWarsGrant(uid, runID, field, value, 1, 0) } func QueueCurrencyWarsGrant(uid, runID uint32, field string, value, quantity, star uint32) (string, error) { if uid == 0 || runID == 0 || quantity == 0 || (field != "gold" && field != "hp" && field != "exp" && field != "equip" && field != "role" && field != "orb" && field != "consumable" && field != "forge") { return "", fmt.Errorf("invalid CW command") } var nonce [16]byte if _, err := rand.Read(nonce[:]); err != nil { return "", err } id := hex.EncodeToString(nonce[:]) persistentWriteMu.Lock() defer persistentWriteMu.Unlock() unlock, err := lockPersistentFile() if err != nil { return "", err } defer unlock() root, err := readCurrencyWarsPersistentRoot() if err != nil { return "", err } var commands []CurrencyWarsCommand if raw, ok := root["currency_wars_commands"]; ok { if err = json.Unmarshal(raw, &commands); err != nil { return "", err } } commands = append(commands, CurrencyWarsCommand{ID: id, UID: uid, RunID: runID, Field: field, Value: value, Quantity: quantity, Star: star}) raw, err := json.Marshal(commands) if err != nil { return "", err } root["currency_wars_commands"] = raw return id, writeJSONAtomic("persistent", root) } func AcknowledgeCurrencyWarsCommand(id string) error { persistentWriteMu.Lock() defer persistentWriteMu.Unlock() unlock, err := lockPersistentFile() if err != nil { return err } defer unlock() root, err := readCurrencyWarsPersistentRoot() if err != nil { return err } var commands []CurrencyWarsCommand if raw, ok := root["currency_wars_commands"]; ok { if err = json.Unmarshal(raw, &commands); err != nil { return err } } remaining := commands[:0] for _, c := range commands { if c.ID != id { remaining = append(remaining, c) } } raw, err := json.Marshal(remaining) if err != nil { return err } root["currency_wars_commands"] = raw return writeJSONAtomic("persistent", root) }