Files
bd2/go/internal/server/events/ap_refresh.go
T
Flechazo d8771caef2 feat(all): add hunting and events, commerce and rewards, binary calendars, GameData caching and login diagnostics
Implement hunting AP and progress, event tasks, exchanges, games, stories and actions; connect battle and reward handling.

Add CashShop plugin and installation, server purchases and entitlements, recharge rules and plugin version metadata.

Distribute binary calendars independently; cache GameData in memory and fix single-connection query deadlocks.

Improve automatic login and recovery diagnostics; document configuration and release flows.
2026-10-05 11:50:31 +08:00

165 lines
3.7 KiB
Go

package events
import (
"bd2server/internal/server/gamedata"
"bd2server/internal/server/player"
"bd2server/internal/server/stateio"
"bd2server/internal/server/wire"
"encoding/json"
"fmt"
"math"
"time"
)
type economyAPClock struct {
Next int64 `json:"next"`
}
func (e *Economy) AttachAPRefresh(caps map[uint64]uint64, design gamedata.HuntingAPDesign) error {
e.mu.Lock()
defer e.mu.Unlock()
for typ, max := range caps {
if (typ != 30 && typ != 32) || max == 0 || max > math.MaxInt32 {
return fmt.Errorf("events: invalid free AP refresh")
}
}
e.apCaps = caps
e.resetSeconds = design.ResetSeconds
e.now = time.Now
return nil
}
func (e *Economy) refreshAP(s *economySnapshot) error {
if len(e.apCaps) == 0 {
return nil
}
now := e.now().UnixMilli()
offset := (e.resetSeconds - 9*3600) * 1000
next := ((now-offset)/86400000+1)*86400000 + offset
b, err := e.store.Load("eventapclock")
if err != nil {
return err
}
clock := economyAPClock{Next: next}
reset := b == nil
if b != nil {
if err = stateio.RequireExactJSONObject(b, "next"); err != nil {
return err
}
if err = json.Unmarshal(b, &clock); err != nil {
return err
}
reset = now >= clock.Next
}
if !reset {
return nil
}
for typ, max := range e.apCaps {
s.Balances[typ] = max
}
clock.Next = next
raw, err := json.Marshal(clock)
if err != nil {
return err
}
if err = e.store.Save("eventapclock", raw); err != nil {
return err
}
raw, err = json.Marshal(s)
if err != nil {
return err
}
return e.store.Save("event_economy", raw)
}
func (e *Economy) CanApply(costs []gamedata.Reward) error {
e.mu.Lock()
defer e.mu.Unlock()
s, err := e.load()
if err != nil {
return err
}
if err = e.refreshAP(&s); err != nil {
return err
}
var wallet, ap []gamedata.Reward
var items []player.Item
totals := map[[2]uint64]uint64{}
for _, r := range costs {
k := [2]uint64{r.Type, r.ID}
if r.Count == 0 || r.Count > math.MaxInt32 || totals[k] > math.MaxInt32-r.Count {
return fmt.Errorf("events: invalid cost")
}
totals[k] += r.Count
}
for k, n := range totals {
r := gamedata.Reward{Type: k[0], ID: k[1], Count: n}
switch {
case walletCurrency(r.Type):
wallet = append(wallet, r)
case apCurrency(r.Type):
ap = append(ap, r)
case extraCurrency(r.Type):
if r.ID != 0 || s.Balances[r.Type] < n {
return fmt.Errorf("events: insufficient currency")
}
case inventoryType(r.Type):
selected, err := e.items.SelectMutable(r.Type, r.ID, n)
if err != nil {
return err
}
items = append(items, selected...)
default:
return fmt.Errorf("events: unsupported cost type %d", r.Type)
}
}
if err = e.wallet.CanExchange(wallet, nil); err != nil {
return err
}
if len(ap) > 0 {
if e.hunting == nil {
return fmt.Errorf("events: AP unavailable")
}
if err = e.hunting.CanExchangeAP(ap, nil); err != nil {
return err
}
}
if len(items) > 0 {
return e.items.CanConsume(items)
}
return nil
}
func (e *Economy) ChargeInfo() ([]byte, error) {
e.mu.Lock()
defer e.mu.Unlock()
s, err := e.load()
if err != nil {
return nil, err
}
if err = e.refreshAP(&s); err != nil {
return nil, err
}
raw, err := e.store.Load("eventapclock")
if err != nil {
return nil, err
}
if raw == nil {
return nil, nil
}
var clock economyAPClock
if err = json.Unmarshal(raw, &clock); err != nil {
return nil, err
}
var out []byte
for _, typ := range []uint64{30, 32} {
if _, ok := e.apCaps[typ]; !ok {
continue
}
info := wire.AppendVarint(nil, 1, uint64(clock.Next-86400000))
item := wire.AppendVarint(nil, 3, typ)
item = wire.AppendVarint(item, 4, s.Balances[typ])
info = wire.AppendBytes(info, 2, item)
out = wire.AppendBytes(out, 1, info)
}
return out, nil
}