docs & feat: add license & attribution notice (Apache-2.0 / GPL-3.0) and core system enhancements
This commit is contained in:
1 parent
507836d367
commit
ce6aabfc55
47 files changed
+5674
-724
No files matched your search
@@ -0,0 +1,211 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"gac-hkrpg-go/internal/common"
|
||||
"gac-hkrpg-go/internal/testutil"
|
||||
kcplib "gac-hkrpg-go/pkg/kcp"
|
||||
pb "gac-hkrpg-go/protocol/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func isolatedCWTest(t *testing.T) *common.CurrencyWarsCatalog {
|
||||
t.Helper()
|
||||
temporary := t.TempDir()
|
||||
if err := testutil.CopyRuntime(testWorkspaceRoot, temporary); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Chdir(temporary)
|
||||
t.Setenv("GAC_CONFIG", "server.json")
|
||||
if err := os.WriteFile("persistent", []byte(`{}`), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, err := common.LoadCurrencyWarsCatalog()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Use the real typed dispatcher and transport framing, so assertions examine
|
||||
// the Client response rather than merely checking handler registration.
|
||||
func replySession(t *testing.T) (*Session, func(proto.Message)) {
|
||||
t.Helper()
|
||||
client := kcplib.New(1, 1, false, func([]byte) error { return nil })
|
||||
s := NewSession(func(data []byte) error { return client.Input(data) }, 1, 1)
|
||||
s.kcp.SetNodelay(true, 10, 2, true)
|
||||
if err := s.kcp.Update(s.kcp.Now()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return s, func(response proto.Message) {
|
||||
t.Helper()
|
||||
wanted := pb.CmdIDByName[string(response.ProtoReflect().Descriptor().Name())]
|
||||
for {
|
||||
buf := make([]byte, 65536)
|
||||
n, err := client.Recv(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
packet, err := DecodeNetPacket(buf[:n])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if packet.CmdType != wanted {
|
||||
continue
|
||||
}
|
||||
if err := proto.Unmarshal(packet.Body, response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func callReviewRequest(t *testing.T, s *Session, request proto.Message) {
|
||||
t.Helper()
|
||||
raw, err := proto.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handler := typedHandlers[uint16(pb.CmdIDByName[string(request.ProtoReflect().Descriptor().Name())])]
|
||||
if handler == nil {
|
||||
t.Fatal("handler missing")
|
||||
}
|
||||
if err := handler(s, raw); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedCWMutationsReturnFailureAndPreserveSave(t *testing.T) {
|
||||
isolatedCWTest(t)
|
||||
before, _ := os.ReadFile("persistent")
|
||||
cases := []struct{ req, rsp proto.Message }{
|
||||
{&pb.GridFightPermanentTalentEnableCsReq{CKHCLKOALKE: ^uint32(0)}, &pb.GridFightPermanentTalentEnableScRsp{}},
|
||||
{&pb.GridFightSeasonTalentEnableCsReq{CKHCLKOALKE: 1}, &pb.GridFightSeasonTalentEnableScRsp{}},
|
||||
{&pb.GridFightPermanentTalentResetCsReq{}, &pb.GridFightPermanentTalentResetScRsp{}},
|
||||
{&pb.GridFightSeasonTalentResetCsReq{}, &pb.GridFightSeasonTalentResetScRsp{}},
|
||||
{&pb.GridFightTakeWeeklyRewardCsReq{JKCEBHFIENI: []uint32{1, ^uint32(0)}}, &pb.GridFightTakeWeeklyRewardScRsp{}},
|
||||
{&pb.CycleScoreRewardTakeRewardCsReq{JKCEBHFIENI: []uint32{1}}, &pb.CycleScoreRewardTakeRewardScRsp{}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
s, receive := replySession(t)
|
||||
callReviewRequest(t, s, tc.req)
|
||||
receive(tc.rsp)
|
||||
view := tc.rsp.ProtoReflect()
|
||||
fd := view.Descriptor().Fields().ByName("retcode")
|
||||
if view.Get(fd).Uint() == 0 {
|
||||
t.Fatalf("unsupported operation succeeded: %s", view.Descriptor().Name())
|
||||
}
|
||||
after, _ := os.ReadFile("persistent")
|
||||
if !bytes.Equal(before, after) {
|
||||
t.Fatal("unsupported mutation changed save")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrencyWarsPositionValidationBothHandlers(t *testing.T) {
|
||||
c := isolatedCWTest(t)
|
||||
data, err := newCurrencyWarsData(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var roles []*pb.CDEAABODKCP
|
||||
seen := map[uint32]bool{}
|
||||
for _, v := range c.CapturedVariants {
|
||||
if seen[v.Config.AvatarID] {
|
||||
continue
|
||||
}
|
||||
seen[v.Config.AvatarID] = true
|
||||
roles = append(roles, &pb.CDEAABODKCP{Id: v.Config.ID, RoleStar: v.Star, UniqueId: uint32(len(roles) + 1), Pos: uint32(len(roles) + 1)})
|
||||
if len(roles) == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(roles) != 2 {
|
||||
t.Fatal("fixture needs two avatars")
|
||||
}
|
||||
data.DHFOFLFCLAH = &pb.MPIMAEMCEFH{UniqueId: 1, DivisionId: c.CapturedStages[0].ID, LJNGPCKPJLF: c.ActiveSeasonID, NAGDJGMMCBE: []*pb.GridFightGameInfo{
|
||||
{LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{EJIPINIKNCK: &pb.HKFECLCHBFJ{GridGameRoleList: roles}}},
|
||||
{LEOIAOFBBFA: &pb.GridFightGameInfo_FAOCFBOFCJP{FAOCFBOFCJP: &pb.NIHNGPKHEPC{AFFCFLFIFPH: 2, MICEOJBAKGN: 1}}},
|
||||
}}
|
||||
writer := &Session{}
|
||||
if err := writer.saveCurrencyWarsData(data, c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, batch := range []bool{false, true} {
|
||||
for _, kind := range []string{"duplicate", "outside", "unknown", "battle", "stale", "swap"} {
|
||||
s, receive := replySession(t)
|
||||
s.currencyWarsRevision = writer.currencyWarsRevision
|
||||
before, _ := os.ReadFile("persistent")
|
||||
positions := []*pb.MHEGMHKHAAD{{UniqueId: 1, Pos: 2}, {UniqueId: 2, Pos: 1}}
|
||||
switch kind {
|
||||
case "duplicate":
|
||||
positions[1].Pos = 2
|
||||
case "outside":
|
||||
positions[0].Pos = ^uint32(0)
|
||||
case "unknown":
|
||||
positions[0].UniqueId = ^uint32(0)
|
||||
case "battle":
|
||||
CURRENT_BATTLE.Store(s.Token, &pb.SceneBattleInfo{})
|
||||
case "stale":
|
||||
s.currencyWarsRevision = 0
|
||||
}
|
||||
var code uint32
|
||||
if batch {
|
||||
req := &pb.GEBBMDCOLCH{}
|
||||
for _, p := range positions {
|
||||
req.CONJCEMAHGN = append(req.CONJCEMAHGN, &pb.EPKFPIDHMOC{MDNAGANGNKK: p.UniqueId, JFCJNHNBMCB: p.Pos})
|
||||
}
|
||||
callReviewRequest(t, s, req)
|
||||
rsp := &pb.ALNFEBKHCFC{}
|
||||
receive(rsp)
|
||||
code = rsp.Retcode
|
||||
} else {
|
||||
callReviewRequest(t, s, &pb.GridFightUpdatePosCsReq{AAKDHMHCMLP: positions})
|
||||
rsp := &pb.GridFightUpdatePosScRsp{}
|
||||
receive(rsp)
|
||||
code = rsp.Retcode
|
||||
}
|
||||
CURRENT_BATTLE.Delete(s.Token)
|
||||
after, _ := os.ReadFile("persistent")
|
||||
if kind == "swap" {
|
||||
if code != 0 {
|
||||
t.Fatalf("valid swap rejected batch=%t code=%d", batch, code)
|
||||
}
|
||||
writer.currencyWarsRevision = s.currencyWarsRevision
|
||||
} else if code == 0 || !bytes.Equal(before, after) {
|
||||
t.Fatalf("invalid move changed save or succeeded: batch=%t case=%s code=%d", batch, kind, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrencyWarsUnplacedAndBenchRolesGiveNoTraits(t *testing.T) {
|
||||
c := isolatedCWTest(t)
|
||||
var sample common.CurrencyWarsRoleVariant
|
||||
for _, v := range c.CapturedVariants {
|
||||
if len(v.Config.TraitList) > 0 {
|
||||
sample = v
|
||||
break
|
||||
}
|
||||
}
|
||||
if sample.Config.ID == 0 {
|
||||
t.Fatal("trait role fixture missing")
|
||||
}
|
||||
front, _ := c.IntegerRule("GridFight_Front_AvatarMaxNum")
|
||||
back, _ := c.IntegerRule("GridFight_Back_AvatarMaxNum")
|
||||
role := &pb.CDEAABODKCP{Id: sample.Config.ID, RoleStar: sample.Star, UniqueId: 1}
|
||||
run := &pb.MPIMAEMCEFH{NAGDJGMMCBE: []*pb.GridFightGameInfo{{LEOIAOFBBFA: &pb.GridFightGameInfo_EJIPINIKNCK{EJIPINIKNCK: &pb.HKFECLCHBFJ{GridGameRoleList: []*pb.CDEAABODKCP{role}}}}}}
|
||||
for _, pos := range []uint32{0, uint32(front + back + 1)} {
|
||||
role.Pos = pos
|
||||
if len(cwCalculateBoardTraits(c, run)) != 0 || len(cwCalculateBattleTraits(c, run, nil)) != 0 {
|
||||
t.Fatalf("off-board role contributes traits: pos=%d", pos)
|
||||
}
|
||||
}
|
||||
role.Pos = 1
|
||||
if len(cwCalculateBoardTraits(c, run)) == 0 || len(cwCalculateBattleTraits(c, run, nil)) == 0 {
|
||||
t.Fatal("active role traits missing")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user