diff --git a/.gitignore b/.gitignore index 26b863c4..ac98766f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,6 @@ bin # Game protocol protobuf generate file protocol/proto -protocol/proto_hk4e/proto gate/client_proto/proto gate/client_proto/client_proto_gen.go diff --git a/Makefile b/Makefile index 944c87ee..fce8d5eb 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,65 @@ CUR_DIR=$(shell pwd) +VERSION=1.0.0 + +# 清理 +.PHONY: clean +clean: + rm -rf ./bin + rm -rf ./protocol/proto + rm -rf ./gate/client_proto/client_proto_gen.go + rm -rf ./gdconf/game_data_config/csv/*.csv + +# 构建服务器二进制文件 .PHONY: build build: - mkdir -p bin/ && CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./cmd/... + mkdir -p bin && CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./cmd/... +# 清理镜像 +.PHONY: docker_clean +docker_clean: + rm -rf ./docker/node/bin/* + rm -rf ./docker/dispatch/bin/* + rm -rf ./docker/gate/bin/* + rm -rf ./docker/fight/bin/* + rm -rf ./docker/pathfinding/bin/* + rm -rf ./docker/gs/bin/* + rm -rf ./docker/gm/bin/* + docker rmi flswld/node:$(VERSION) + docker rmi flswld/dispatch:$(VERSION) + docker rmi flswld/gate:$(VERSION) + docker rmi flswld/fight:$(VERSION) + docker rmi flswld/pathfinding:$(VERSION) + docker rmi flswld/gs:$(VERSION) + docker rmi flswld/gm:$(VERSION) + +# 构建镜像 +.PHONY: docker_build +docker_build: + mkdir -p ./docker/node/bin && cp -rf ./bin/node ./cmd/node/* ./docker/node/bin/ + mkdir -p ./docker/dispatch/bin && cp -rf ./bin/dispatch ./cmd/dispatch/* ./docker/dispatch/bin/ + mkdir -p ./docker/gate/bin && cp -rf ./bin/gate ./cmd/gate/* ./docker/gate/bin/ + mkdir -p ./docker/fight/bin && cp -rf ./bin/fight ./cmd/fight/* ./docker/fight/bin/ + mkdir -p ./docker/pathfinding/bin && cp -rf ./bin/pathfinding ./cmd/pathfinding/* ./docker/pathfinding/bin/ + mkdir -p ./docker/gs/bin && cp -rf ./bin/gs ./cmd/gs/* ./docker/gs/bin/ + mkdir -p ./docker/gm/bin && cp -rf ./bin/gm ./cmd/gm/* ./docker/gm/bin/ + docker build -t flswld/node:$(VERSION) ./docker/node + docker build -t flswld/dispatch:$(VERSION) ./docker/dispatch + docker build -t flswld/gate:$(VERSION) ./docker/gate + docker build -t flswld/fight:$(VERSION) ./docker/fight + docker build -t flswld/pathfinding:$(VERSION) ./docker/pathfinding + docker build -t flswld/gs:$(VERSION) ./docker/gs + docker build -t flswld/gm:$(VERSION) ./docker/gm + +# 安装natsrpc生成工具 .PHONY: dev_tool dev_tool: - # 安装natsrpc生成工具 go install github.com/golang/protobuf/protoc-gen-go@v1.5.2 go install github.com/byebyebruce/natsrpc/cmd/protoc-gen-natsrpc@develop -test: - go test ./... - +# 生成natsrpc协议代码 .PHONY: gen_natsrpc gen_natsrpc: - # 生成natsrpc协议代码 protoc \ --proto_path=gs/api \ --go_out=paths=source_relative:gs/api \ @@ -27,9 +71,9 @@ gen_natsrpc: --natsrpc_out=paths=source_relative:node/api \ node/api/*.proto +# 生成客户端协议代码 .PHONY: gen_proto gen_proto: - # 生成客户端协议代码 cd protocol/proto_hk4e && \ rm -rf ./proto && mkdir -p proto && \ protoc --proto_path=./ --go_out=paths=source_relative:./proto ./*.proto && \ @@ -41,3 +85,13 @@ gen_proto: mv ./proto/server_only/* ./proto/ && rm -rf ./proto/server_only && \ rm -rf ../proto && mkdir -p ../proto && mv ./proto/* ../proto/ && rm -rf ./proto && \ cd ../../ + +# 生成服务器配置表 +.PHONY: gen_csv +gen_csv: + cd gdconf && go test -v -run TestGenGdCsv . + +# 生成客户端协议代理功能所需的代码 +.PHONY: gen_client_proto +gen_client_proto: + cd gate/client_proto && go test -v -run TestClientProtoGen . diff --git a/README.md b/README.md index c410ed8d..60d6d03c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ # hk4e -hk4e game server +#### hk4e game server -## 开发快速上手 +## 编译和运行环境 * Go >= 1.18 +* Protoc >= 3.21 +* Protoc Gen Go >= 1.28 > 1. 首次需要安装工具 `make dev_tool` > 2. 生成协议 `make gen_natsrpc && make gen_proto` +> 3. 生成配置表 `make gen_csv` ## 快速运行 @@ -17,15 +20,15 @@ hk4e game server * nats-server * redis -#### 启动顺序 +#### 服务器组件 -> 1. 启动节点服务器(仅单节点 有状态) `cd cmd/node && go run .` -> 2. 启动http登录服务器(可多节点 无状态) `cd cmd/dispatch && go run .` -> 3. 启动网关服务器(可多节点 有状态) `cd cmd/gate && go run .` -> 4. 启动战斗服务器(可多节点 有状态 非必要) `cd cmd/fight && go run .` -> 5. 启动寻路服务器(可多节点 无状态 非必要) `cd cmd/pathfinding && go run .` -> 6. 启动游戏服务器(可多节点 有状态) `cd cmd/gs && go run .` -> 7. 启动游戏管理服务器(仅单节点 无状态) `cd cmd/gm && go run .` +* node 节点服务器 (仅单节点 有状态) +* dispatch 登录服务器 (可多节点 无状态) +* gate 网关服务器 (可多节点 有状态) +* fight 战斗服务器 (可多节点 有状态 非必要) +* pathfinding 寻路服务器 (可多节点 无状态 非必要) +* gs 游戏服务器 (可多节点 有状态) +* gm 游戏管理服务器 (仅单节点 无状态) #### 其它 @@ -34,5 +37,3 @@ hk4e game server ```shell GOLANG_PROTOBUF_REGISTRATION_CONFLICT=ignore ``` - -* 运行gdconf/game_data_config_test.go文件中的TestGenGdCsv方法 生成服务器配置表 diff --git a/cmd/gate/application.toml b/cmd/gate/application.toml index f1c72f6a..6ea90517 100644 --- a/cmd/gate/application.toml +++ b/cmd/gate/application.toml @@ -1,10 +1,10 @@ [hk4e] kcp_addr = "127.0.0.1" # 该地址只用来注册到节点服务器 并非网关本地监听地址 本地监听为0.0.0.0 -kcp_port = 22103 +kcp_port = 22222 client_proto_proxy_enable = false version = "320" gate_tcp_mq_addr = "127.0.0.1" -gate_tcp_mq_port = 9999 +gate_tcp_mq_port = 33333 [logger] level = "DEBUG" diff --git a/cmd/gs/application.toml b/cmd/gs/application.toml index e2543298..b98c3e4f 100644 --- a/cmd/gs/application.toml +++ b/cmd/gs/application.toml @@ -1,6 +1,5 @@ [hk4e] client_proto_proxy_enable = false -resource_path = "./GameDataConfigTable" game_data_config_path = "./game_data_config" gacha_history_server = "https://hk4e.flswld.com/api/v1" diff --git a/common/config/config.go b/common/config/config.go index 414a16c0..325657aa 100644 --- a/common/config/config.go +++ b/common/config/config.go @@ -40,7 +40,6 @@ type Redis struct { type Hk4e struct { KcpPort int32 `toml:"kcp_port"` // 该地址只用来注册到节点服务器 并非网关本地监听地址 本地监听为0.0.0.0 KcpAddr string `toml:"kcp_addr"` - ResourcePath string `toml:"resource_path"` GameDataConfigPath string `toml:"game_data_config_path"` GachaHistoryServer string `toml:"gacha_history_server"` ClientProtoProxyEnable bool `toml:"client_proto_proxy_enable"` diff --git a/docker/dispatch/Dockerfile b/docker/dispatch/Dockerfile new file mode 100644 index 00000000..a1bfe849 --- /dev/null +++ b/docker/dispatch/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:18.04 + +EXPOSE 8080/tcp + +WORKDIR /dispatch +COPY ./bin/dispatch ./dispatch +RUN chmod +x ./dispatch + +ENTRYPOINT ["./dispatch"] diff --git a/docker/fight/Dockerfile b/docker/fight/Dockerfile new file mode 100644 index 00000000..7bf8e351 --- /dev/null +++ b/docker/fight/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:18.04 + +WORKDIR /fight +COPY ./bin/fight ./fight +RUN chmod +x ./fight + +ENTRYPOINT ["./fight"] diff --git a/docker/gate/Dockerfile b/docker/gate/Dockerfile new file mode 100644 index 00000000..f5a13909 --- /dev/null +++ b/docker/gate/Dockerfile @@ -0,0 +1,10 @@ +FROM ubuntu:18.04 + +EXPOSE 22222/udp +EXPOSE 33333/tcp + +WORKDIR /gate +COPY ./bin/gate ./gate +RUN chmod +x ./gate + +ENTRYPOINT ["./gate"] diff --git a/docker/gm/Dockerfile b/docker/gm/Dockerfile new file mode 100644 index 00000000..0a388ff2 --- /dev/null +++ b/docker/gm/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:18.04 + +EXPOSE 9001/tcp + +WORKDIR /gm +COPY ./bin/gm ./gm +RUN chmod +x ./gm + +ENTRYPOINT ["./gm"] diff --git a/docker/gs/Dockerfile b/docker/gs/Dockerfile new file mode 100644 index 00000000..5a8cce87 --- /dev/null +++ b/docker/gs/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:18.04 + +WORKDIR /gs +COPY ./bin/gs ./gs +RUN chmod +x ./gs + +ENTRYPOINT ["./gs"] diff --git a/docker/node/Dockerfile b/docker/node/Dockerfile new file mode 100644 index 00000000..4065cbae --- /dev/null +++ b/docker/node/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:18.04 + +WORKDIR /node +COPY ./bin/node ./node +RUN chmod +x ./node + +ENTRYPOINT ["./node"] diff --git a/docker/pathfinding/Dockerfile b/docker/pathfinding/Dockerfile new file mode 100644 index 00000000..232d91a6 --- /dev/null +++ b/docker/pathfinding/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:18.04 + +WORKDIR /pathfinding +COPY ./bin/pathfinding ./pathfinding +RUN chmod +x ./pathfinding + +ENTRYPOINT ["./pathfinding"] diff --git a/gate/client_proto/README.md b/gate/client_proto/README.md index 4150bca6..d9fc2db1 100644 --- a/gate/client_proto/README.md +++ b/gate/client_proto/README.md @@ -6,10 +6,8 @@ ## 使用方法 -> 1. 在此目录下建立bin目录和proto目录 +> 1. 在此目录下建立proto目录 > 2. 将对应版本的proto协议文件复制到proto目录下并编译成pb.go -> 3. 将client_proto_gen_test.go的TestClientProtoGen方法添加运行配置 -> 4. 将运行配置输出目录和工作目录都设置为bin目录 -> 5. 运行并生成client_proto_gen.go -> 6. 将client_cmd.csv放入gate和gs和fight服务器的运行目录下 -> 7. 将gate和gs和fight服务器的配置文件中开启client_proto_proxy_enable客户端协议代理功能 +> 3. make gen_client_proto +> 4. 将client_cmd.csv放入gate和gs和fight服务器的运行目录下 +> 5. 将gate和gs和fight服务器的配置文件中开启client_proto_proxy_enable客户端协议代理功能 diff --git a/gate/client_proto/client_proto_gen_test.go b/gate/client_proto/client_proto_gen_test.go index cbc0763e..69c2435e 100644 --- a/gate/client_proto/client_proto_gen_test.go +++ b/gate/client_proto/client_proto_gen_test.go @@ -7,7 +7,7 @@ import ( ) func TestClientProtoGen(t *testing.T) { - dir, err := os.ReadDir("../proto") + dir, err := os.ReadDir("./proto") if err != nil { panic(err) } @@ -39,7 +39,7 @@ func TestClientProtoGen(t *testing.T) { fileData += "\t}\n" fileData += "}\n" - err = os.WriteFile("../client_proto_gen.go", []byte(fileData), 0644) + err = os.WriteFile("./client_proto_gen.go", []byte(fileData), 0644) if err != nil { panic(err) } diff --git a/gdconf/game_data_config_test.go b/gdconf/game_data_config_test.go index cde2db0c..c8770727 100644 --- a/gdconf/game_data_config_test.go +++ b/gdconf/game_data_config_test.go @@ -5,6 +5,7 @@ import ( "image" "image/color" "image/jpeg" + "log" "os" "strings" "testing" @@ -16,9 +17,82 @@ import ( "github.com/hjson/hjson-go/v4" ) +type TableField struct { + FieldName string `json:"field_name"` + FieldType string `json:"field_type"` + OriginName string `json:"origin_name"` +} + +type TableStructMapping struct { + TableName string `json:"table_name"` + FieldList []*TableField `json:"field_list"` +} + +// 生成最终服务器读取的配置表 +func TestGenGdCsv(t *testing.T) { + tableStructMappingList := make([]*TableStructMapping, 0) + configFileData, err := os.ReadFile("./table_struct_mapping.json") + if err != nil { + log.Printf("open config file error: %v", err) + return + } + err = json.Unmarshal(configFileData, &tableStructMappingList) + if err != nil { + log.Printf("parse config file error: %v", err) + return + } + for _, tableStructMapping := range tableStructMappingList { + txtFileData, err := os.ReadFile("./game_data_config/txt/" + tableStructMapping.TableName + ".txt") + if err != nil { + log.Printf("read txt file error: %v", err) + continue + } + // 转换txt配置表格式为csv + originCsv := string(txtFileData) + originCsv = strings.ReplaceAll(originCsv, "\r\n", "\n") + originCsv = strings.ReplaceAll(originCsv, "\r", "\n") + originCsv = strings.ReplaceAll(originCsv, ",", "#") + originCsv = strings.ReplaceAll(originCsv, ";", "#") + originCsv = strings.ReplaceAll(originCsv, "\t", ",") + originCsvLineList := strings.Split(originCsv, "\n") + if len(originCsvLineList) == 0 { + log.Printf("origin csv file is empty") + continue + } + originCsvHeadList := strings.Split(originCsvLineList[0], ",") + if len(originCsvHeadList) == 0 { + log.Printf("origin csv file head is empty") + continue + } + fieldNameHead := "" + fieldTypeHead := "" + for index, originCsvHead := range originCsvHeadList { + for _, tableField := range tableStructMapping.FieldList { + if originCsvHead == tableField.OriginName { + // 字段名匹配成功 + fieldNameHead += tableField.FieldName + fieldTypeHead += tableField.FieldType + } + } + if index < len(originCsvHeadList)-1 { + fieldNameHead += "," + fieldTypeHead += "," + } + } + fieldNameHead += "\n" + fieldTypeHead += "\n" + gdCsvFile := fieldNameHead + fieldTypeHead + originCsv + err = os.WriteFile("./game_data_config/csv/"+tableStructMapping.TableName+".csv", []byte(gdCsvFile), 0644) + if err != nil { + log.Printf("write gd csv file error: %v", err) + continue + } + } +} + // 测试初始化加载配置表 func TestInitGameDataConfig(t *testing.T) { - config.InitConfig("./application.toml") + config.InitConfig("./bin/application.toml") logger.InitLogger("InitGameDataConfig") logger.Info("start load conf") InitGameDataConfig() @@ -56,11 +130,11 @@ func CheckJsonLoop(path string, errorJsonFileList *[]string, totalJsonFileCount // 测试加载json配置 func TestCheckJsonValid(t *testing.T) { - config.InitConfig("./application.toml") + config.InitConfig("./bin/application.toml") logger.InitLogger("CheckJsonValid") errorJsonFileList := make([]string, 0) totalJsonFileCount := 0 - CheckJsonLoop("../game_data_config/json", &errorJsonFileList, &totalJsonFileCount) + CheckJsonLoop("./game_data_config/json", &errorJsonFileList, &totalJsonFileCount) for _, v := range errorJsonFileList { logger.Info("%v", v) } @@ -68,86 +142,9 @@ func TestCheckJsonValid(t *testing.T) { time.Sleep(time.Second) } -type TableField struct { - FieldName string `json:"field_name"` - FieldType string `json:"field_type"` - OriginName string `json:"origin_name"` -} - -type TableStructMapping struct { - TableName string `json:"table_name"` - FieldList []*TableField `json:"field_list"` -} - -// 生成最终服务器读取的配置表 -func TestGenGdCsv(t *testing.T) { - config.InitConfig("./application.toml") - logger.InitLogger("GenGdCsv") - tableStructMappingList := make([]*TableStructMapping, 0) - configFileData, err := os.ReadFile("../table_struct_mapping.json") - if err != nil { - logger.Error("open config file error: %v", err) - return - } - err = json.Unmarshal(configFileData, &tableStructMappingList) - if err != nil { - logger.Error("parse config file error: %v", err) - return - } - for _, tableStructMapping := range tableStructMappingList { - txtFileData, err := os.ReadFile("../game_data_config/txt/" + tableStructMapping.TableName + ".txt") - if err != nil { - logger.Error("read txt file error: %v", err) - continue - } - // 转换txt配置表格式为csv - originCsv := string(txtFileData) - originCsv = strings.ReplaceAll(originCsv, "\r\n", "\n") - originCsv = strings.ReplaceAll(originCsv, "\r", "\n") - originCsv = strings.ReplaceAll(originCsv, ",", "#") - originCsv = strings.ReplaceAll(originCsv, ";", "#") - originCsv = strings.ReplaceAll(originCsv, "\t", ",") - originCsvLineList := strings.Split(originCsv, "\n") - if len(originCsvLineList) == 0 { - logger.Error("origin csv file is empty") - continue - } - originCsvHeadList := strings.Split(originCsvLineList[0], ",") - if len(originCsvHeadList) == 0 { - logger.Error("origin csv file head is empty") - continue - } - fieldNameHead := "" - fieldTypeHead := "" - for index, originCsvHead := range originCsvHeadList { - for _, tableField := range tableStructMapping.FieldList { - if originCsvHead == tableField.OriginName { - // 字段名匹配成功 - fieldNameHead += tableField.FieldName - fieldTypeHead += tableField.FieldType - } - } - if index < len(originCsvHeadList)-1 { - fieldNameHead += "," - fieldTypeHead += "," - } - } - fieldNameHead += "\n" - fieldTypeHead += "\n" - gdCsvFile := fieldNameHead + fieldTypeHead + originCsv - err = os.WriteFile("../game_data_config/csv/"+tableStructMapping.TableName+".csv", []byte(gdCsvFile), 0644) - if err != nil { - logger.Error("write gd csv file error: %v", err) - continue - } - } - logger.Info("gen gd csv finish") - time.Sleep(time.Second) -} - // 场景lua区块配置坐标范围可视化 func TestSceneBlock(t *testing.T) { - config.InitConfig("./application.toml") + config.InitConfig("./bin/application.toml") logger.InitLogger("SceneBlock") InitGameDataConfig() scene, exist := CONF.SceneMap[3] @@ -197,7 +194,7 @@ func TestSceneBlock(t *testing.T) { rectColor = 0 } } - file, err := os.Create("./block.jpg") + file, err := os.Create("./bin/block.jpg") if err != nil { return }