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.
This commit is contained in:
2026-10-05 11:50:31 +08:00
parent eb445f88f4
commit d8771caef2
222 changed files with 19000 additions and 4284 deletions
-20
View File
@@ -4,11 +4,8 @@ package schedule
import (
"bd2server/internal/server/versionconfig"
"encoding/json"
"errors"
"fmt"
"math"
"os"
"bd2server/internal/server/wire"
)
@@ -44,23 +41,6 @@ type Service struct {
Regular []RegularSeason `json:"regular"`
}
// Load reads this server's versioned calendar policy. It does not infer live
// official seasons from today's clock or from static reward design tables.
func Load(path string) (*Service, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("schedule: read seed: %w", err)
}
var s Service
if err := json.Unmarshal(raw, &s); err != nil {
return nil, fmt.Errorf("schedule: decode seed: %w", err)
}
if err := s.Validate(); err != nil {
return nil, err
}
return &s, nil
}
func (s *Service) Validate() error {
if s == nil || s.Version != versionconfig.State() || s.CalculateMilliseconds == 0 || s.CalculateMilliseconds > math.MaxInt32 || len(s.Contents) == 0 {
return errors.New("schedule: invalid calendar version/calculation interval")
+5 -41
View File
@@ -1,15 +1,13 @@
package schedule
import (
"os"
"path/filepath"
"encoding/json"
"testing"
"bd2server/internal/server/wire"
)
func TestCalendarReloadsChangedIDsAndTimesFromFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "schedule.json")
func TestCalendarUsesInjectedIDsAndTimes(t *testing.T) {
for _, revision := range []struct {
id, start, end uint64
raw string
@@ -17,11 +15,11 @@ func TestCalendarReloadsChangedIDsAndTimesFromFile(t *testing.T) {
{709, 101, 303, `{"version":"2.35.10","calculate_milliseconds":17,"contents":[{"id":81,"current":{"id":709,"start_milliseconds":101,"end_milliseconds":303},"next":{"id":710,"start_milliseconds":401,"end_milliseconds":501}}],"regular":[{"content_id":81,"season":9}]}`},
{811, 701, 903, `{"version":"2.35.10","calculate_milliseconds":17,"contents":[{"id":81,"current":{"id":811,"start_milliseconds":701,"end_milliseconds":903},"next":{"id":812,"start_milliseconds":1001,"end_milliseconds":1201}}],"regular":[{"content_id":81,"season":11}]}`},
} {
if err := os.WriteFile(path, []byte(revision.raw), 0600); err != nil {
var service Service
if err := json.Unmarshal([]byte(revision.raw), &service); err != nil {
t.Fatal(err)
}
service, err := Load(path)
if err != nil {
if err := service.Validate(); err != nil {
t.Fatal(err)
}
_, response, _, err := service.Handle("/ScheduleInfo", wire.AppendVarint(nil, 1, 1))
@@ -37,40 +35,6 @@ func TestCalendarReloadsChangedIDsAndTimesFromFile(t *testing.T) {
t.Fatalf("season=%d times=%d/%d", id, start, end)
}
}
if err := os.WriteFile(path, []byte(`{"version":"2.35.10","calculate_milliseconds":1,"contents":[{"id":1,"current":{"id":2,"start_milliseconds":4,"end_milliseconds":3}}]}`), 0600); err != nil {
t.Fatal(err)
}
if _, err := Load(path); err == nil {
t.Fatal("reversed date accepted")
}
}
func TestVersion23510ContainsRequiredContentSix(t *testing.T) {
service, err := Load("../../../seed/v2_35_10/schedule.json")
if err != nil {
t.Fatal(err)
}
code, response, handled, err := service.Handle("/ScheduleInfo", wire.AppendVarint(nil, 1, 1))
if err != nil || !handled || code != 117 {
t.Fatalf("schedule code=%d handled=%v err=%v", code, handled, err)
}
found := false
if err := wire.Walk(response, func(field wire.Field) error {
if field.Number == 2 {
id, _, _ := wire.Varint(field.Value, 1)
if id == 6 {
found = true
current, ok, _ := wire.Bytes(field.Value, 2)
season, _, _ := wire.Varint(current, 1)
if !ok || season != 26 {
t.Fatalf("content 6 current season=%d found=%v", season, ok)
}
}
}
return nil
}); err != nil || !found {
t.Fatalf("content 6 missing err=%v", err)
}
}
func TestScheduleRejectsMissingSequence(t *testing.T) {