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.
165 lines
3.7 KiB
Go
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
|
|
}
|