2025-12-23 10:46:17 +08:00
|
|
|
|
// Package controller 提供游戏逻辑的控制器层,负责处理各种游戏功能的请求分发。
|
|
|
|
|
|
// 包含用户、宠物、战斗、物品、地图、任务等各个模块的控制器方法。
|
|
|
|
|
|
// 通过反射和标签系统自动注册 cmd 处理方法,实现请求到处理函数的映射。
|
2025-12-11 10:32:39 +08:00
|
|
|
|
package controller
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"blazing/cool"
|
|
|
|
|
|
"blazing/logic/service/common"
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
"context"
|
2026-04-04 09:33:31 +08:00
|
|
|
|
"fmt"
|
2025-12-11 10:32:39 +08:00
|
|
|
|
"reflect"
|
2026-04-04 09:33:31 +08:00
|
|
|
|
"strconv"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"sync"
|
2025-12-11 10:32:39 +08:00
|
|
|
|
|
|
|
|
|
|
"github.com/gogf/gf/v2/os/glog"
|
|
|
|
|
|
"github.com/lunixbochs/struc"
|
2026-04-06 07:07:15 +08:00
|
|
|
|
"github.com/panjf2000/gnet/v2"
|
2025-12-11 10:32:39 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-05 23:13:06 +08:00
|
|
|
|
// Maincontroller 是控制器层共享变量。
|
2025-12-11 10:32:39 +08:00
|
|
|
|
var Maincontroller = &Controller{} //注入service
|
|
|
|
|
|
|
2025-12-23 10:46:17 +08:00
|
|
|
|
// Controller 分发cmd逻辑实现
|
2025-12-11 10:32:39 +08:00
|
|
|
|
type Controller struct {
|
2026-03-02 23:59:15 +08:00
|
|
|
|
UID uint32
|
2026-02-07 22:54:44 +08:00
|
|
|
|
RPCClient *struct {
|
2025-12-11 10:32:39 +08:00
|
|
|
|
Kick func(uint32) error
|
|
|
|
|
|
|
2026-03-02 18:34:20 +08:00
|
|
|
|
RegisterLogic func(uint32, uint32) error
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-23 10:46:17 +08:00
|
|
|
|
// ParseCmd 将字节数组数据解析到指定类型的变量中
|
|
|
|
|
|
// 该函数使用struc库进行数据解包操作
|
2025-12-25 12:21:15 +08:00
|
|
|
|
// 参数 data: 需要解析的字节数据
|
|
|
|
|
|
// 返回值: 解析后的指定类型实例
|
|
|
|
|
|
func ParseCmd[T any](data []byte) T {
|
|
|
|
|
|
var result T
|
|
|
|
|
|
// 使用struc.Unpack将字节数据解包到result变量中
|
2026-04-04 09:33:31 +08:00
|
|
|
|
struc.Unpack(bytes.NewReader(data), &result)
|
2025-12-25 12:21:15 +08:00
|
|
|
|
return result
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 12:21:15 +08:00
|
|
|
|
// Init 初始化控制器,注册所有cmd处理方法
|
|
|
|
|
|
// 参数 isGame: 标识是否为游戏服务器(true)或登录服务器(false)
|
|
|
|
|
|
func Init(isGame bool) {
|
|
|
|
|
|
controllerValue := reflect.ValueOf(Maincontroller)
|
|
|
|
|
|
controllerType := controllerValue.Type()
|
|
|
|
|
|
|
|
|
|
|
|
for i := 0; i < controllerType.NumMethod(); i++ {
|
|
|
|
|
|
method := controllerType.Method(i)
|
2026-04-06 07:07:15 +08:00
|
|
|
|
methodValue := controllerValue.Method(i)
|
2026-04-04 09:33:31 +08:00
|
|
|
|
methodType := methodValue.Type()
|
2025-12-25 12:21:15 +08:00
|
|
|
|
|
2026-04-04 09:33:31 +08:00
|
|
|
|
if methodType.NumIn() == 0 {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
reqArgType := methodType.In(0)
|
|
|
|
|
|
if reqArgType.Kind() != reflect.Ptr || reqArgType.Elem().Kind() != reflect.Struct {
|
|
|
|
|
|
glog.Warning(context.Background(), "方法首参必须为结构体指针", method.Name, "跳过注册")
|
2025-12-25 12:21:15 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
2026-04-04 09:33:31 +08:00
|
|
|
|
reqType := reqArgType.Elem()
|
2026-04-06 07:07:15 +08:00
|
|
|
|
binding := getCmdBinding(reqType)
|
2025-12-11 10:32:39 +08:00
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
for _, cmd := range binding.cmds {
|
|
|
|
|
|
if cmd == 0 {
|
2025-12-11 10:32:39 +08:00
|
|
|
|
glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册")
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
if methodType.NumIn() != 2 {
|
|
|
|
|
|
glog.Warning(context.Background(), "方法参数数量必须为2", method.Name, "跳过注册")
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 12:21:15 +08:00
|
|
|
|
if !isGame && cmd > 1000 {
|
2025-12-11 10:32:39 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 12:21:15 +08:00
|
|
|
|
if isGame && cmd < 1000 {
|
2025-12-11 10:32:39 +08:00
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 11:00:37 +08:00
|
|
|
|
if cool.Config.ServerInfo.IsDebug != 0 {
|
|
|
|
|
|
fmt.Println("注册方法", cmd, method.Name)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 09:33:31 +08:00
|
|
|
|
reqTypeForNew := reqType
|
2026-04-06 07:07:15 +08:00
|
|
|
|
cmdInfo := cool.Cmd{
|
|
|
|
|
|
Func: methodValue,
|
|
|
|
|
|
Req: reqType,
|
|
|
|
|
|
HeaderFieldIndex: append([]int(nil), binding.headerFieldIndex...),
|
|
|
|
|
|
UseConn: methodType.In(1) == connType,
|
|
|
|
|
|
NewReqFunc: func() interface{} {
|
|
|
|
|
|
return reflect.New(reqTypeForNew).Interface()
|
|
|
|
|
|
},
|
|
|
|
|
|
NewReqValue: func() reflect.Value {
|
|
|
|
|
|
return reflect.New(reqTypeForNew)
|
|
|
|
|
|
},
|
2026-02-11 11:58:34 +08:00
|
|
|
|
}
|
2026-03-03 19:28:59 +08:00
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
if _, exists := cool.CmdCache[cmd]; exists {
|
2026-03-03 19:28:59 +08:00
|
|
|
|
panic(fmt.Sprintf("命令处理方法已存在,跳过注册 %d %s", cmd, method.Name))
|
|
|
|
|
|
}
|
2026-04-04 09:33:31 +08:00
|
|
|
|
cool.CmdCache[cmd] = cmdInfo
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
var (
|
|
|
|
|
|
targetType = reflect.TypeOf(common.TomeeHeader{})
|
|
|
|
|
|
connType = reflect.TypeOf((*gnet.Conn)(nil)).Elem()
|
|
|
|
|
|
cmdTypeCache sync.Map
|
|
|
|
|
|
)
|
2025-12-11 10:32:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 默认返回值(无匹配字段/解析失败时)
|
|
|
|
|
|
const defaultCmdValue = 0
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
type cmdBinding struct {
|
|
|
|
|
|
cmds []uint32
|
|
|
|
|
|
headerFieldIndex []int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 09:33:31 +08:00
|
|
|
|
func normalizeStructType(typ reflect.Type) reflect.Type {
|
|
|
|
|
|
for typ.Kind() == reflect.Ptr {
|
|
|
|
|
|
typ = typ.Elem()
|
|
|
|
|
|
}
|
|
|
|
|
|
return typ
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
// getCmdBinding 从结构体类型中提取绑定的cmd指令和头字段位置。
|
|
|
|
|
|
func getCmdBinding(typ reflect.Type) cmdBinding {
|
2026-04-04 09:33:31 +08:00
|
|
|
|
typ = normalizeStructType(typ)
|
|
|
|
|
|
if cached, ok := cmdTypeCache.Load(typ); ok {
|
2026-04-06 07:07:15 +08:00
|
|
|
|
return cached.(cmdBinding)
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if typ.Kind() != reflect.Struct {
|
2026-04-06 07:07:15 +08:00
|
|
|
|
binding := cmdBinding{cmds: []uint32{defaultCmdValue}}
|
|
|
|
|
|
cmdTypeCache.Store(typ, binding)
|
|
|
|
|
|
return binding
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
if binding, ok := findCmdBinding(typ, make(map[reflect.Type]struct{})); ok {
|
|
|
|
|
|
cmdTypeCache.Store(typ, binding)
|
|
|
|
|
|
return binding
|
2026-04-04 09:33:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
binding := cmdBinding{cmds: []uint32{defaultCmdValue}}
|
|
|
|
|
|
cmdTypeCache.Store(typ, binding)
|
|
|
|
|
|
return binding
|
2026-04-04 09:33:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
func findCmdBinding(typ reflect.Type, visiting map[reflect.Type]struct{}) (cmdBinding, bool) {
|
2026-04-04 09:33:31 +08:00
|
|
|
|
typ = normalizeStructType(typ)
|
|
|
|
|
|
if typ.Kind() != reflect.Struct {
|
2026-04-06 07:07:15 +08:00
|
|
|
|
return cmdBinding{}, false
|
2026-04-04 09:33:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
if _, seen := visiting[typ]; seen {
|
2026-04-06 07:07:15 +08:00
|
|
|
|
return cmdBinding{}, false
|
2026-04-04 09:33:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
visiting[typ] = struct{}{}
|
|
|
|
|
|
defer delete(visiting, typ)
|
|
|
|
|
|
|
2025-12-11 10:32:39 +08:00
|
|
|
|
for i := 0; i < typ.NumField(); i++ {
|
|
|
|
|
|
field := typ.Field(i)
|
|
|
|
|
|
|
2026-04-04 09:33:31 +08:00
|
|
|
|
cmdSlice, isHeader, err := parseCmdTagWithStructField(field)
|
2026-04-06 07:07:15 +08:00
|
|
|
|
if isHeader && err == nil {
|
|
|
|
|
|
return cmdBinding{
|
|
|
|
|
|
cmds: cmdSlice,
|
|
|
|
|
|
headerFieldIndex: append([]int(nil), field.Index...),
|
|
|
|
|
|
}, true
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 09:33:31 +08:00
|
|
|
|
nestedTyp := normalizeStructType(field.Type)
|
2026-04-06 07:07:15 +08:00
|
|
|
|
if nestedTyp.Kind() != reflect.Struct {
|
|
|
|
|
|
continue
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
2026-04-06 07:07:15 +08:00
|
|
|
|
|
|
|
|
|
|
nestedBinding, ok := findCmdBinding(nestedTyp, visiting)
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fieldIndex := make([]int, 0, len(field.Index)+len(nestedBinding.headerFieldIndex))
|
|
|
|
|
|
fieldIndex = append(fieldIndex, field.Index...)
|
|
|
|
|
|
fieldIndex = append(fieldIndex, nestedBinding.headerFieldIndex...)
|
|
|
|
|
|
nestedBinding.headerFieldIndex = fieldIndex
|
|
|
|
|
|
return nestedBinding, true
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 07:07:15 +08:00
|
|
|
|
return cmdBinding{}, false
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 12:21:15 +08:00
|
|
|
|
// parseCmdTagWithStructField 校验字段是否为TomeeHeader(值/指针)并解析cmd标签
|
|
|
|
|
|
// 参数 field: 结构体字段元信息
|
2026-04-04 09:33:31 +08:00
|
|
|
|
// 返回值: 解析后的cmd切片,是否为目标类型,解析失败错误
|
|
|
|
|
|
func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, bool, error) {
|
|
|
|
|
|
if field.Type != targetType && !(field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType) {
|
|
|
|
|
|
return nil, false, nil
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cmdStr := field.Tag.Get("cmd")
|
|
|
|
|
|
if cmdStr == "" {
|
2026-04-04 09:33:31 +08:00
|
|
|
|
return nil, true, fmt.Errorf("field %s cmd tag is empty", field.Name)
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 09:33:31 +08:00
|
|
|
|
result := make([]uint32, 0, strings.Count(cmdStr, "|")+1)
|
|
|
|
|
|
remain := cmdStr
|
|
|
|
|
|
for idx := 0; ; idx++ {
|
|
|
|
|
|
part, next, found := strings.Cut(remain, "|")
|
|
|
|
|
|
s := strings.TrimSpace(part)
|
2025-12-11 10:32:39 +08:00
|
|
|
|
if s == "" {
|
2026-04-04 09:33:31 +08:00
|
|
|
|
return nil, true, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx)
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
num, err := strconv.ParseUint(s, 10, 32)
|
|
|
|
|
|
if err != nil {
|
2026-04-04 09:33:31 +08:00
|
|
|
|
return nil, true, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)",
|
2025-12-11 10:32:39 +08:00
|
|
|
|
field.Name, idx, err, s)
|
|
|
|
|
|
}
|
|
|
|
|
|
result = append(result, uint32(num))
|
2026-04-04 09:33:31 +08:00
|
|
|
|
|
|
|
|
|
|
if !found {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
remain = next
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 09:33:31 +08:00
|
|
|
|
return result, true, nil
|
2025-12-11 10:32:39 +08:00
|
|
|
|
}
|