Compare commits
171 Commits
7916f90992
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d51372a2d | ||
|
|
188d80d41b | ||
|
|
07b5271532 | ||
|
|
29f70513e8 | ||
|
|
e80e5d526c | ||
|
|
99fd21e2fa | ||
|
|
4f7f1b5072 | ||
|
|
6c76b050b3 | ||
|
|
596d4024cc | ||
|
|
4673c3163e | ||
|
|
52bd4333d9 | ||
|
|
4b0a17e035 | ||
|
|
d2b3130414 | ||
|
|
48c1a7a463 | ||
|
|
7d49aaa212 | ||
|
|
deae6d371e | ||
|
|
45f1485a11 | ||
|
|
20d24428ac | ||
|
|
ab7fe0639a | ||
|
|
ba1a1ffbea | ||
|
|
ec1855dfac | ||
|
|
f97275cb54 | ||
|
|
6781178f6c | ||
|
|
073db875eb | ||
|
|
949a93b2d5 | ||
|
|
9171e53e9c | ||
|
|
1ff4381617 | ||
|
|
8e28e030c1 | ||
|
|
c07e521e4e | ||
|
|
4906197c77 | ||
|
|
415315c288 | ||
|
|
fe9c82fd2d | ||
|
|
cd41b354b9 | ||
|
|
9452e36782 | ||
|
|
1efc8517e1 | ||
|
|
ab2928db1d | ||
|
|
a9999bb93f | ||
|
|
34b65f6399 | ||
|
|
2ce1057566 | ||
|
|
d30028157a | ||
|
|
f0a8f521b6 | ||
|
|
0ae65cee45 | ||
|
|
11bf46c7e4 | ||
|
|
c4b5748e5c | ||
|
|
1f1fbd09d4 | ||
|
|
b1ca3df3ae | ||
|
|
57676e998f | ||
|
|
5500684e29 | ||
|
|
7fd89800fa | ||
|
|
b46a1f442b | ||
|
|
eb76c22c41 | ||
|
|
a6386daad8 | ||
|
|
b59beed45f | ||
|
|
77909a5940 | ||
|
|
c4d3ab725c | ||
|
|
808da76bd0 | ||
|
|
dcbd9950d3 | ||
|
|
4b42a64da0 | ||
|
|
d517c822ef | ||
|
|
04038cd16b | ||
|
|
ec608d69cd | ||
|
|
fd5341da1a | ||
|
|
5967414da4 | ||
|
|
da118dc826 | ||
|
|
823eef00ac | ||
|
|
7844c5b76b | ||
|
|
4abd179a23 | ||
|
|
a3e88c7357 | ||
|
|
4e1a9a815f | ||
|
|
de3ae0bca2 | ||
|
|
b1ff4d3a2a | ||
|
|
24b52e14c3 | ||
|
|
2b92baf530 | ||
|
|
819d5f667b | ||
|
|
de6c700bb3 | ||
|
|
3232efd05a | ||
|
|
0c79fee8af | ||
|
|
3d77e146e9 | ||
|
|
a43a25c610 | ||
|
|
3cfde577eb | ||
|
|
85f9c02ced | ||
|
|
9f7fd83626 | ||
|
|
ee8b0a2182 | ||
|
|
6e95e014fa | ||
|
|
61a135b3a7 | ||
|
|
5a81534e84 | ||
|
|
523d835ac0 | ||
|
|
5a7e20efec | ||
|
|
5f47bf0589 | ||
|
|
a58ef20fab | ||
|
|
3999f34f77 | ||
|
|
6f51a2e349 | ||
|
|
de755f8fd0 | ||
|
|
803aa71771 | ||
|
|
4a77066d08 | ||
|
|
c9b5f8569f | ||
|
|
ddbfe91d8b | ||
|
|
74ac6ce940 | ||
|
|
43b0bc2dec | ||
|
|
b953e7831a | ||
|
|
62d93f65e7 | ||
|
|
7dfa9c297e | ||
|
|
f95fd49efd | ||
|
|
ce1a2a3588 | ||
|
|
3739c2a6f9 | ||
|
|
eca7dd86e1 | ||
|
|
e161e3626f | ||
|
|
e1a994ba11 | ||
|
|
82bb99d141 | ||
|
|
f9543a5156 | ||
|
|
174830731c | ||
|
|
3a7f593105 | ||
|
|
f6aa0c3339 | ||
|
|
ecc483a11a | ||
|
|
97c8231b44 | ||
|
|
5f5634d999 | ||
|
|
5bfdb5c32b | ||
|
|
90f1447d48 | ||
|
|
ee3f25438f | ||
|
|
2d8969bed2 | ||
|
|
fa5d50955d | ||
|
|
6574450489 | ||
|
|
0daeb70900 | ||
|
|
061e4f0c51 | ||
|
|
5c76aa7079 | ||
|
|
b327398448 | ||
|
|
d0abb08d5b | ||
|
|
d2cd601802 | ||
|
|
487ee0e726 | ||
|
|
3b35789b47 | ||
|
|
28b6386963 | ||
|
|
1ca0ff344e | ||
|
|
9825944efc | ||
|
|
ca96be3905 | ||
|
|
4b89588c22 | ||
|
|
0051ac0be8 | ||
|
|
918cdeac0e | ||
|
|
13244313f1 | ||
|
|
4ea9864833 | ||
|
|
77057e01b6 | ||
|
|
f030b61645 | ||
|
|
5a44154d30 | ||
|
|
a905954b5c | ||
|
|
99748ba41e | ||
|
|
40ec827342 | ||
|
|
a16a06e389 | ||
|
|
5b37d9493b | ||
|
|
f433a26a6d | ||
|
|
141ba67014 | ||
|
|
d83cf365ac | ||
|
|
24b463f0aa | ||
|
|
c021b40fbe | ||
|
|
36dd93b076 | ||
|
|
3ee1283a2c | ||
|
|
c3da3162ee | ||
|
|
37cd641942 | ||
|
|
87145579e6 | ||
|
|
7ec6381cf1 | ||
|
|
2ee0cbc094 | ||
|
|
6510e4e09b | ||
|
|
34bc35a6b2 | ||
|
|
8352d23164 | ||
|
|
e71971d0b4 | ||
|
|
bceb7965f7 | ||
|
|
c3f052ef30 | ||
|
|
7d054bbe91 | ||
|
|
102d87da3e | ||
|
|
78a68148ce | ||
|
|
f473c54880 | ||
|
|
2eba4b7915 | ||
|
|
39e1d4c42f |
3
.cnb.yml
3
.cnb.yml
@@ -27,5 +27,4 @@ main:
|
||||
username: ${GIT_USERNAME}
|
||||
password: ${GIT_ACCESS_TOKEN}
|
||||
force: true
|
||||
|
||||
#sync_mode: rebase
|
||||
sync_mode: push
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -46,4 +46,5 @@ public/login-linux-amd64
|
||||
.cache/gomod/**
|
||||
public/login-login-linux-amd64
|
||||
public/logic_linux-amd64_1
|
||||
.cache/**
|
||||
.cache/**
|
||||
.agents/**
|
||||
|
||||
@@ -18,9 +18,11 @@ ENV GOMODCACHE=/workspace/.cache/gomod
|
||||
# ==========================================
|
||||
# 2. Codex 配置 (更换时修改这里,重新 build)
|
||||
# ==========================================
|
||||
ENV CODEX_BASE_URL="https://kuaipao.ai/v1"
|
||||
ENV CODEX_MODEL="gpt-5.3-codex"
|
||||
ENV OPENAI_API_KEY="sk-iv5TV88RjnnKiZtkROKjiR0tN9GTVPyV02nf9bxCqawUjqZG"
|
||||
ENV CODEX_BASE_URL="http://sub2api.sflaw.store"
|
||||
|
||||
ENV CODEX_MODEL="gpt-5.5"
|
||||
|
||||
ENV OPENAI_API_KEY="sk-e5d137d0e824adabc0cf674f61a558a2cbafdedf9857743116a3d23dcead1f68"
|
||||
|
||||
# ==========================================
|
||||
# 3. 安装系统依赖、Golang、Code-server
|
||||
|
||||
20
.ide/help.md
20
.ide/help.md
@@ -1,12 +1,7 @@
|
||||
青氧,十九禁给
|
||||
https://api.aibh.site/console 张晟 2922919493Zs.
|
||||
RUN curl -fsSL https://oss.itbzzb.cn/setup-codex.sh | \
|
||||
YES=1 bash -s -- --base-url https://api.aibh.site \
|
||||
--api-key sk-foAHgsJtmanACECtBlFYZE2z4LkwBboEOYETO3ZdWvCxdmNr \
|
||||
--mirror auto
|
||||
|
||||
|
||||
|
||||
https://api.gemai.cc/console/token 免费给部分额度 ,还有100块
|
||||
https://api.jucode.cn/
|
||||
fastai.fast 使用谷歌邮箱https://linshiguge.com/白嫖
|
||||
https://zread.ai/tawer-blog/lmarena-2api/1-overview GLM web2 pai
|
||||
|
||||
https://crazyrouter.com/console 模型最便宜,看看能不能1:10
|
||||
@@ -14,7 +9,7 @@ https://crazyrouter.com/console 模型最便宜,看看能不能1:10
|
||||
https://agentrouter.org/pricing 签到给,有175
|
||||
|
||||
|
||||
kuaipao.ai 充了十块 cjf19970621 cjf19970621
|
||||
|
||||
|
||||
充了十块
|
||||
使用网址:https://www.jnm.lol
|
||||
@@ -26,3 +21,10 @@ kuaipao.ai 充了十块 cjf19970621 cjf19970621
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fastai.fast 575560454@qq.com 575560454
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -29,7 +29,7 @@
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["-id=2"],
|
||||
"args": ["-id=99"],
|
||||
|
||||
"program": "${workspaceFolder}/logic"
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ skip_clone: true
|
||||
steps:
|
||||
# ========== 1. 替代clone:拉取代码(核心依赖) ==========
|
||||
prepare:
|
||||
image: alpine/git
|
||||
image: docker.1ms.run/alpine/git
|
||||
environment:
|
||||
# WOODPECKER_SSH_KEY:
|
||||
# from_secret: WOODPECKER_SSH_KEY
|
||||
@@ -70,12 +70,14 @@ steps:
|
||||
|
||||
# ========== 4. 编译Logic服务(完全参考GitHub Actions编译配置) ==========
|
||||
build_logic:
|
||||
image: golang:1.25
|
||||
image: docker.m.daocloud.io/golang:1.25
|
||||
depends_on: [prepare]
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
GO111MODULE: on
|
||||
GOSUMDB: off
|
||||
GOMODCACHE: /woodpecker/go/pkg/mod
|
||||
GOCACHE: /woodpecker/.cache/go-build
|
||||
commands:
|
||||
# 2. 清空主源文件(关键:先删空,再写入)
|
||||
- >
|
||||
@@ -135,30 +137,31 @@ steps:
|
||||
- ls -lh ./build/
|
||||
- echo "产物名称:$BIN_NAME"
|
||||
- echo "✅ Logic服务编译完成"
|
||||
# volumes:
|
||||
# - /ext/go/pkg/mod:~/go/pkg/mod
|
||||
# - /ext/.cache/go-build:~/.cache/go-build
|
||||
volumes:
|
||||
# 持久化 Go 模块缓存和编译缓存,避免每次流水线都重新下载/重新编译。
|
||||
- /ext/woodpecker-cache/go/pkg/mod:/woodpecker/go/pkg/mod
|
||||
- /ext/woodpecker-cache/.cache/go-build:/woodpecker/.cache/go-build
|
||||
|
||||
|
||||
# ========== 6. SCP推送产物(依赖编译+配置解析) ==========
|
||||
scp-exe-to-servers: # 与fetch-deploy-config同级,缩进2个空格
|
||||
image: appleboy/drone-scp:1.6.2 # 子元素,缩进4个空格
|
||||
image: docker.1ms.run/appleboy/drone-scp:1.6.2 # 子元素,缩进4个空格
|
||||
settings: # 子元素,缩进4个空格
|
||||
host: &ssh_host 2697v22.mc5173.cn
|
||||
port: &ssh_port 16493
|
||||
host: &ssh_host 43.248.3.21
|
||||
port: &ssh_port 22
|
||||
username: &ssh_user root
|
||||
password: &ssh_pass xIy9PQcBF96C
|
||||
password: &ssh_pass KQv7yzna7BDukK
|
||||
|
||||
source:
|
||||
- blazing/build/**
|
||||
target: /opt/blazing/
|
||||
target: /ext/blazing/
|
||||
strip_components: 1 # 统一缩进6个空格
|
||||
skip_verify: true # 统一缩进6个空格
|
||||
timeout: 30s # 统一缩进6个空格
|
||||
depends_on: # 子元素,缩进4个空格
|
||||
- build_logic # depends_on内的项,缩进6个空格
|
||||
start-login-logic:
|
||||
image: appleboy/drone-ssh:1.6.2
|
||||
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/appleboy/drone-ssh:1.7.7
|
||||
depends_on: [scp-exe-to-servers]
|
||||
settings: # 子元素,缩进4个空格
|
||||
host: *ssh_host
|
||||
@@ -167,7 +170,7 @@ steps:
|
||||
password: *ssh_pass
|
||||
script:
|
||||
- |
|
||||
cd /opt/blazing/build
|
||||
cd /ext/blazing/build
|
||||
ls -t login_* 2>/dev/null | head -1
|
||||
BIN_NAME=$(ls -t login_* 2>/dev/null | head -1)
|
||||
echo "BIN_NAME: $BIN_NAME"
|
||||
@@ -201,9 +204,9 @@ steps:
|
||||
# 移动logic产物到public目录
|
||||
LOGIC_BIN=$(ls -t logic_* 2>/dev/null | head -1)
|
||||
if [ -n "$LOGIC_BIN" ]; then
|
||||
mkdir -p /opt/blazing/build/public
|
||||
mv $LOGIC_BIN /opt/blazing/build/public/
|
||||
echo "✅ Logic产物已移动到 /opt/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
|
||||
mkdir -p /ext/blazing/build/public
|
||||
mv $LOGIC_BIN /ext/blazing/build/public/
|
||||
echo "✅ Logic产物已移动到 /ext/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
|
||||
else
|
||||
echo "⚠️ 未找到Logic产物"
|
||||
fi
|
||||
|
||||
56
common/contrib/drivers/pgsql/cmd/codexcheck/main.go
Normal file
56
common/contrib/drivers/pgsql/cmd/codexcheck/main.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
func main() {
|
||||
const dsn = "user=user_YrK4j7 password=password_jSDm76 host=43.248.3.21 port=5432 dbname=bl sslmode=disable timezone=Asia/Shanghai"
|
||||
|
||||
db, err := sql.Open("postgres", dsn)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
var (
|
||||
id int64
|
||||
cdkCode string
|
||||
cdkType int64
|
||||
exchangeRemainCount int64
|
||||
bindUserID int64
|
||||
validEndTime sql.NullTime
|
||||
remark sql.NullString
|
||||
)
|
||||
|
||||
err = db.QueryRow(`
|
||||
select id, cdk_code, type, exchange_remain_count, bind_user_id, valid_end_time, remark
|
||||
from config_gift_cdk
|
||||
where cdk_code = $1
|
||||
`, "nrTbdXFBhKkaTdDk").Scan(
|
||||
&id,
|
||||
&cdkCode,
|
||||
&cdkType,
|
||||
&exchangeRemainCount,
|
||||
&bindUserID,
|
||||
&validEndTime,
|
||||
&remark,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Printf("id=%d\ncdk_code=%s\ntype=%d\nexchange_remain_count=%d\nbind_user_id=%d\nvalid_end_time=%v\nremark=%q\n",
|
||||
id,
|
||||
cdkCode,
|
||||
cdkType,
|
||||
exchangeRemainCount,
|
||||
bindUserID,
|
||||
validEndTime.Time,
|
||||
remark.String,
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package cool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"blazing/cool/coolconfig"
|
||||
@@ -158,12 +159,33 @@ func (c *Controller) Page(ctx context.Context, req *PageReq) (res *BaseRes, err
|
||||
// 注册控制器到路由
|
||||
func RegisterController(c IController) {
|
||||
var ctx = context.Background()
|
||||
var sController = &Controller{}
|
||||
gconv.Struct(c, &sController)
|
||||
var sController *Controller
|
||||
rv := reflect.ValueOf(c)
|
||||
if rv.IsValid() && rv.Kind() == reflect.Ptr {
|
||||
ev := rv.Elem()
|
||||
if ev.IsValid() {
|
||||
field := ev.FieldByName("Controller")
|
||||
if field.IsValid() && !field.IsNil() {
|
||||
if ctrl, ok := field.Interface().(*Controller); ok && ctrl != nil {
|
||||
sController = ctrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if sController == nil {
|
||||
sController = &Controller{}
|
||||
gconv.Struct(c, &sController)
|
||||
}
|
||||
if coolconfig.Config.Eps {
|
||||
model := sController.Service.GetModel()
|
||||
columns := getModelInfo(ctx, sController.Prefix, model)
|
||||
ModelInfo[sController.Prefix] = columns
|
||||
tableName := ""
|
||||
if model != nil {
|
||||
tableName = strings.TrimSpace(model.TableName())
|
||||
}
|
||||
if tableName != "" && tableName != "this_table_should_not_exist" {
|
||||
columns := getModelInfo(ctx, sController.Prefix, model)
|
||||
ModelInfo[sController.Prefix] = columns
|
||||
}
|
||||
}
|
||||
g.Server().Group(
|
||||
sController.Prefix, func(group *ghttp.RouterGroup) {
|
||||
|
||||
@@ -1,121 +1,139 @@
|
||||
package coolconfig
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// cool config
|
||||
type sConfig struct {
|
||||
AutoMigrate bool `json:"auto_migrate,omitempty"` // 是否自动创建表
|
||||
Eps bool `json:"eps,omitempty"` // 是否开启eps
|
||||
File *file `json:"file,omitempty"` // 文件上传配置
|
||||
Name string `json:"name"` // 项目名称
|
||||
// LoginPort string `json:"port"`
|
||||
GameOnlineID uint32 `json:"port_bl"` //这个是命令行输入的参数
|
||||
ServerInfo ServerList
|
||||
|
||||
Address string //rpc端口
|
||||
|
||||
}
|
||||
type ServerList struct {
|
||||
OnlineID uint32 `gorm:"column:online_id;comment:'在线ID';uniqueIndex" json:"online_id"`
|
||||
//服务器名称Desc
|
||||
Name string `gorm:"comment:'服务器名称'" json:"name"`
|
||||
IP string `gorm:"type:string;comment:'服务器IP'" json:"ip"`
|
||||
Port uint32 `gorm:"comment:'端口号,通常是小整数'" json:"port"`
|
||||
IsOpen uint8 `gorm:"default:0;not null;comment:'是否开启'" json:"is_open"`
|
||||
//登录地址
|
||||
LoginAddr string `gorm:"type:string;comment:'登录地址'" json:"login_addr"`
|
||||
//账号
|
||||
Account string `gorm:"type:string;comment:'账号'" json:"account"`
|
||||
//密码
|
||||
Password string `gorm:"type:string;comment:'密码'" json:"password"`
|
||||
CanPort []uint32 `gorm:"type:jsonb;comment:'可连接端口'" json:"can_port"`
|
||||
//是否测试服
|
||||
IsVip uint32 `gorm:"default:0;not null;comment:'是否为VIP服务器'" json:"is_vip"`
|
||||
//isdebug 是否本地服
|
||||
IsDebug uint8 `gorm:"default:0;comment:'是否为调试模式'" json:"is_debug"`
|
||||
|
||||
//服务器属主Desc
|
||||
Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"`
|
||||
Desc string `gorm:"comment:'服务器描述'" json:"desc"`
|
||||
OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"`
|
||||
//到期时间ServerList
|
||||
ExpireTime time.Time `gorm:"default:0;comment:'到期时间'" json:"expire_time"`
|
||||
}
|
||||
|
||||
func (s *ServerList) GetID() string {
|
||||
return gconv.String(100000*s.OnlineID + s.Port)
|
||||
}
|
||||
|
||||
// OSS相关配置
|
||||
type oss struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
AccessKeyID string `json:"accessKeyID"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
UseSSL bool `json:"useSSL"`
|
||||
BucketName string `json:"bucketName"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
|
||||
// 文件上传配置
|
||||
type file struct {
|
||||
Mode string `json:"mode"` // 模式 local oss
|
||||
Domain string `json:"domain"` // 域名 http://
|
||||
Oss *oss `json:"oss,omitempty"`
|
||||
}
|
||||
|
||||
// NewConfig new config
|
||||
func newConfig() *sConfig {
|
||||
var ctx g.Ctx
|
||||
config := &sConfig{
|
||||
AutoMigrate: GetCfgWithDefault(ctx, "blazing.autoMigrate", g.NewVar(false)).Bool(),
|
||||
Name: GetCfgWithDefault(ctx, "server.name", g.NewVar("")).String(),
|
||||
|
||||
Eps: GetCfgWithDefault(ctx, "blazing.eps", g.NewVar(false)).Bool(),
|
||||
// LoginPort: string(GetCfgWithDefault(ctx, "server.port", g.NewVar("8080")).String()),
|
||||
Address: GetCfgWithDefault(ctx, "server.address", g.NewVar("8080")).String(),
|
||||
//GamePort: GetCfgWithDefault(ctx, "server.game", g.NewVar("8080")).Uint64s(),
|
||||
|
||||
File: &file{
|
||||
Mode: GetCfgWithDefault(ctx, "blazing.file.mode", g.NewVar("none")).String(),
|
||||
Domain: GetCfgWithDefault(ctx, "blazing.file.domain", g.NewVar("http://127.0.0.1:8300")).String(),
|
||||
Oss: &oss{
|
||||
Endpoint: GetCfgWithDefault(ctx, "blazing.file.oss.endpoint", g.NewVar("127.0.0.1:9000")).String(),
|
||||
AccessKeyID: GetCfgWithDefault(ctx, "blazing.file.oss.accessKeyID", g.NewVar("")).String(),
|
||||
SecretAccessKey: GetCfgWithDefault(ctx, "blazing.file.oss.secretAccessKey", g.NewVar("")).String(),
|
||||
UseSSL: GetCfgWithDefault(ctx, "blazing.file.oss.useSSL", g.NewVar(false)).Bool(),
|
||||
BucketName: GetCfgWithDefault(ctx, "blazing.file.oss.bucketName", g.NewVar("blazing")).String(),
|
||||
Location: GetCfgWithDefault(ctx, "blazing.file.oss.location", g.NewVar("us-east-1")).String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// qiniu 七牛云配置
|
||||
type qiniu struct {
|
||||
AccessKey string `json:"ak"`
|
||||
SecretKey string `json:"sk"`
|
||||
Bucket string `json:"bucket"`
|
||||
CDN string `json:"cdn"`
|
||||
}
|
||||
|
||||
// Config config
|
||||
var Config = newConfig()
|
||||
|
||||
// GetCfgWithDefault get config with default value
|
||||
func GetCfgWithDefault(ctx g.Ctx, key string, defaultValue *g.Var) *g.Var {
|
||||
value, err := g.Cfg().GetWithEnv(ctx, key)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
if value.IsEmpty() || value.IsNil() {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
package coolconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// cool config
|
||||
type sConfig struct {
|
||||
AutoMigrate bool `json:"auto_migrate,omitempty"` // 是否自动创建表
|
||||
Eps bool `json:"eps,omitempty"` // 是否开启eps
|
||||
File *file `json:"file,omitempty"` // 文件上传配置
|
||||
Name string `json:"name"` // 项目名称
|
||||
// LoginPort string `json:"port"`
|
||||
GameOnlineID uint32 `json:"port_bl"` //这个是命令行输入的参数
|
||||
ServerInfo ServerList
|
||||
|
||||
Address string //rpc端口
|
||||
|
||||
}
|
||||
type ServerList struct {
|
||||
OnlineID uint32 `gorm:"column:online_id;comment:'在线ID'" json:"online_id"`
|
||||
//服务器名称Desc
|
||||
Name string `gorm:"comment:'服务器名称'" json:"name"`
|
||||
IP string `gorm:"type:string;comment:'服务器IP'" json:"ip"`
|
||||
Port uint32 `gorm:"comment:'端口号,通常是小整数'" json:"port"`
|
||||
IsOpen uint8 `gorm:"default:0;not null;comment:'是否开启'" json:"is_open"`
|
||||
//登录地址
|
||||
LoginAddr string `gorm:"type:string;comment:'登录地址'" json:"login_addr"`
|
||||
//账号
|
||||
Account string `gorm:"type:string;comment:'账号'" json:"account"`
|
||||
//密码
|
||||
Password string `gorm:"type:string;comment:'密码'" json:"password"`
|
||||
CanPort []uint32 `gorm:"type:jsonb;comment:'可连接端口'" json:"can_port"`
|
||||
//是否测试服
|
||||
IsVip uint32 `gorm:"default:0;not null;comment:'是否为VIP服务器'" json:"is_vip"`
|
||||
//isdebug 是否本地服
|
||||
IsDebug uint8 `gorm:"default:0;comment:'是否为调试模式'" json:"is_debug"`
|
||||
|
||||
//服务器属主Desc
|
||||
Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"`
|
||||
Desc string `gorm:"comment:'服务器描述'" json:"desc"`
|
||||
OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"`
|
||||
//到期时间ServerList
|
||||
ExpireTime time.Time `gorm:"default:0;comment:'到期时间'" json:"expire_time"`
|
||||
}
|
||||
|
||||
func (s *ServerList) GetID() string {
|
||||
return RuntimeIDString(s.OnlineID, s.Port)
|
||||
}
|
||||
|
||||
func (s *ServerList) RuntimeID() uint32 {
|
||||
return ComposeRuntimeID(s.OnlineID, s.Port)
|
||||
}
|
||||
|
||||
// OSS相关配置
|
||||
type oss struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
AccessKeyID string `json:"accessKeyID"`
|
||||
SecretAccessKey string `json:"secretAccessKey"`
|
||||
UseSSL bool `json:"useSSL"`
|
||||
BucketName string `json:"bucketName"`
|
||||
Location string `json:"location"`
|
||||
}
|
||||
|
||||
// 文件上传配置
|
||||
type file struct {
|
||||
Mode string `json:"mode"` // 模式 local oss
|
||||
Domain string `json:"domain"` // 域名 http://
|
||||
Oss *oss `json:"oss,omitempty"`
|
||||
}
|
||||
|
||||
// NewConfig new config
|
||||
func newConfig() *sConfig {
|
||||
var ctx g.Ctx
|
||||
config := &sConfig{
|
||||
AutoMigrate: hasDebugArg(),
|
||||
Name: GetCfgWithDefault(ctx, "server.name", g.NewVar("")).String(),
|
||||
|
||||
Eps: GetCfgWithDefault(ctx, "blazing.eps", g.NewVar(false)).Bool(),
|
||||
// LoginPort: string(GetCfgWithDefault(ctx, "server.port", g.NewVar("8080")).String()),
|
||||
Address: GetCfgWithDefault(ctx, "server.address", g.NewVar("8080")).String(),
|
||||
//GamePort: GetCfgWithDefault(ctx, "server.game", g.NewVar("8080")).Uint64s(),
|
||||
|
||||
File: &file{
|
||||
Mode: GetCfgWithDefault(ctx, "blazing.file.mode", g.NewVar("none")).String(),
|
||||
Domain: GetCfgWithDefault(ctx, "blazing.file.domain", g.NewVar("http://127.0.0.1:8300")).String(),
|
||||
Oss: &oss{
|
||||
Endpoint: GetCfgWithDefault(ctx, "blazing.file.oss.endpoint", g.NewVar("127.0.0.1:9000")).String(),
|
||||
AccessKeyID: GetCfgWithDefault(ctx, "blazing.file.oss.accessKeyID", g.NewVar("")).String(),
|
||||
SecretAccessKey: GetCfgWithDefault(ctx, "blazing.file.oss.secretAccessKey", g.NewVar("")).String(),
|
||||
UseSSL: GetCfgWithDefault(ctx, "blazing.file.oss.useSSL", g.NewVar(false)).Bool(),
|
||||
BucketName: GetCfgWithDefault(ctx, "blazing.file.oss.bucketName", g.NewVar("blazing")).String(),
|
||||
Location: GetCfgWithDefault(ctx, "blazing.file.oss.location", g.NewVar("us-east-1")).String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func hasDebugArg() bool {
|
||||
for _, arg := range os.Args[1:] {
|
||||
if arg == "-debug" || arg == "--debug" {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(arg, "-debug=") || strings.HasPrefix(arg, "--debug=") {
|
||||
value := strings.TrimSpace(strings.SplitN(arg, "=", 2)[1])
|
||||
return value != "" && value != "0" && !strings.EqualFold(value, "false")
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// qiniu 七牛云配置
|
||||
type qiniu struct {
|
||||
AccessKey string `json:"ak"`
|
||||
SecretKey string `json:"sk"`
|
||||
Bucket string `json:"bucket"`
|
||||
CDN string `json:"cdn"`
|
||||
}
|
||||
|
||||
// Config config
|
||||
var Config = newConfig()
|
||||
|
||||
// GetCfgWithDefault get config with default value
|
||||
func GetCfgWithDefault(ctx g.Ctx, key string, defaultValue *g.Var) *g.Var {
|
||||
value, err := g.Cfg().GetWithEnv(ctx, key)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
if value.IsEmpty() || value.IsNil() {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
22
common/cool/coolconfig/runtime_id.go
Normal file
22
common/cool/coolconfig/runtime_id.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package coolconfig
|
||||
|
||||
import "github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
const runtimeIDPortBits = 16
|
||||
const runtimeIDPortMask uint32 = 0xFFFF
|
||||
|
||||
// ComposeRuntimeID 将 serverID 和 port 组合成运行时复合 ID。
|
||||
// 高 16 位保存 serverID,低 16 位保存 port。
|
||||
func ComposeRuntimeID(serverID, port uint32) uint32 {
|
||||
return ((serverID & runtimeIDPortMask) << runtimeIDPortBits) | (port & runtimeIDPortMask)
|
||||
}
|
||||
|
||||
// SplitRuntimeID 将运行时复合 ID 拆分为 serverID 和 port。
|
||||
func SplitRuntimeID(runtimeID uint32) (serverID, port uint32) {
|
||||
return runtimeID >> runtimeIDPortBits, runtimeID & runtimeIDPortMask
|
||||
}
|
||||
|
||||
// RuntimeIDString 返回运行时复合 ID 的字符串形式。
|
||||
func RuntimeIDString(serverID, port uint32) string {
|
||||
return gconv.String(ComposeRuntimeID(serverID, port))
|
||||
}
|
||||
@@ -22,8 +22,14 @@ var ctx = context.TODO()
|
||||
type Cmd struct {
|
||||
Func reflect.Value //方法函数
|
||||
Req reflect.Type //请求体
|
||||
// HeaderFieldIndex 是请求结构体中 TomeeHeader 字段的索引路径。
|
||||
HeaderFieldIndex []int
|
||||
// UseConn 标记第二个参数是否为 gnet.Conn。
|
||||
UseConn bool
|
||||
// 新增:预缓存的req创建函数(返回结构体指针)
|
||||
NewReqFunc func() interface{}
|
||||
// NewReqValue 返回请求结构体指针的 reflect.Value,避免重复构造类型信息。
|
||||
NewReqValue func() reflect.Value
|
||||
//Res reflect.Value //返回体
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package cool
|
||||
import (
|
||||
_ "blazing/contrib/drivers/pgsql"
|
||||
"blazing/cool/cooldb"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@@ -10,6 +11,11 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
autoMigrateMu sync.Mutex
|
||||
autoMigrateModels []IModel
|
||||
)
|
||||
|
||||
// 初始化数据库连接供gorm使用
|
||||
func InitDB(group string) (*gorm.DB, error) {
|
||||
// var ctx context.Context
|
||||
@@ -54,9 +60,39 @@ func getDBbyModel(model IModel) *gorm.DB {
|
||||
|
||||
// 根据entity结构体创建表
|
||||
func CreateTable(model IModel) error {
|
||||
if Config.AutoMigrate {
|
||||
autoMigrateMu.Lock()
|
||||
autoMigrateModels = append(autoMigrateModels, model)
|
||||
autoMigrateMu.Unlock()
|
||||
|
||||
if !Config.AutoMigrate {
|
||||
return nil
|
||||
}
|
||||
|
||||
db := getDBbyModel(model)
|
||||
return db.AutoMigrate(model)
|
||||
}
|
||||
|
||||
// RunAutoMigrate 显式执行已注册模型的建表/迁移。
|
||||
func RunAutoMigrate() error {
|
||||
if !Config.AutoMigrate {
|
||||
return nil
|
||||
}
|
||||
autoMigrateMu.Lock()
|
||||
models := append([]IModel(nil), autoMigrateModels...)
|
||||
autoMigrateMu.Unlock()
|
||||
|
||||
seen := make(map[string]struct{}, len(models))
|
||||
for _, model := range models {
|
||||
key := model.GroupName() + ":" + model.TableName()
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
|
||||
db := getDBbyModel(model)
|
||||
return db.AutoMigrate(model)
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
package cool
|
||||
|
||||
import "blazing/cool/coolconfig"
|
||||
|
||||
// 存值示例
|
||||
func AddClient(id uint32, client *ClientHandler) {
|
||||
// 普通map:Clientmap[id] = client
|
||||
Clientmap.Store(id, client) // sync.Map存值
|
||||
}
|
||||
|
||||
// 清理指定 client(高 16 位 serverID,低 16 位 port)
|
||||
func DeleteClientOnly(uid uint32) {
|
||||
Clientmap.Delete(uid)
|
||||
}
|
||||
|
||||
// 清理指定 client(由 serverID 和 port 组合)
|
||||
func DeleteClient(id, port uint32) {
|
||||
Clientmap.Delete(coolconfig.ComposeRuntimeID(id, port))
|
||||
}
|
||||
|
||||
// 取值示例
|
||||
func GetClient(id, port uint32) (*ClientHandler, bool) {
|
||||
// 普通map:client, ok := Clientmap[id]
|
||||
val, ok := Clientmap.Load(100000*id + port) // sync.Map取值
|
||||
val, ok := Clientmap.Load(coolconfig.ComposeRuntimeID(id, port)) // sync.Map取值
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -1,346 +1,128 @@
|
||||
package element
|
||||
|
||||
// 初始化全属性克制矩阵(经双向结果验证)
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed data/skillTypes.json
|
||||
var skillTypesJSON []byte
|
||||
|
||||
//go:embed data/typesRelation.json
|
||||
var typesRelationJSON []byte
|
||||
|
||||
type skillTypesFile struct {
|
||||
Root struct {
|
||||
Item []skillTypeItem `json:"item"`
|
||||
} `json:"root"`
|
||||
}
|
||||
|
||||
type skillTypeItem struct {
|
||||
ID int `json:"id"`
|
||||
CN string `json:"cn"`
|
||||
Att string `json:"att"`
|
||||
IsDou int `json:"is_dou"`
|
||||
EN []string `json:"en"`
|
||||
}
|
||||
|
||||
type typeRelationFile struct {
|
||||
Root struct {
|
||||
Relation []typeRelationItem `json:"relation"`
|
||||
} `json:"root"`
|
||||
}
|
||||
|
||||
type typeRelationItem struct {
|
||||
Type string `json:"type"`
|
||||
Opponent []typeRelationOpponent `json:"opponent"`
|
||||
}
|
||||
|
||||
type typeRelationOpponent struct {
|
||||
Type string `json:"type"`
|
||||
Multiple float64 `json:"multiple"`
|
||||
}
|
||||
|
||||
// 初始化全属性克制矩阵。数据来自 seer-types-relation,默认关系为 1.0。
|
||||
func initFullTableMatrix() {
|
||||
// 初始化17×17矩阵,默认系数1.0
|
||||
// 定义属性克制矩阵(索引对应属性ID,值为克制倍数)
|
||||
// 矩阵维度:227x227(覆盖所有属性ID:1-20、221-226)
|
||||
// 第一步:所有克制关系默认1.0(未特殊配置的都是普通关系)
|
||||
for i := 0; i < maxMatrixSize; i++ {
|
||||
for j := 0; j < maxMatrixSize; j++ {
|
||||
matrix[i][j] = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化矩阵(默认值为1.0,以下仅列出非1.0的特殊值)
|
||||
|
||||
// 1. 草(grass)对其他属性的克制
|
||||
matrix[1][1] = 0.5 // 草->草
|
||||
matrix[1][2] = 2 // 草->水
|
||||
matrix[1][3] = 0.5 // 草->火
|
||||
matrix[1][4] = 0.5 // 草->飞行
|
||||
matrix[1][6] = 0.5 // 草->机械
|
||||
matrix[1][7] = 2 // 草->地面
|
||||
matrix[1][12] = 2 // 草->光
|
||||
matrix[1][16] = 0.5 // 草->圣灵
|
||||
matrix[1][18] = 0.5 // 草->远古
|
||||
matrix[1][222] = 0.5 // 草->混沌
|
||||
matrix[1][223] = 0.5 // 草->神灵
|
||||
|
||||
// 2. 水(water)对其他属性的克制
|
||||
matrix[2][1] = 0.5 // 水->草
|
||||
matrix[2][2] = 0.5 // 水->水
|
||||
matrix[2][3] = 2 // 水->火
|
||||
matrix[2][7] = 2 // 水->地面
|
||||
matrix[2][16] = 0.5 // 水->圣灵
|
||||
matrix[2][20] = 0.5 // 水->自然
|
||||
matrix[2][222] = 0.5 // 水->混沌
|
||||
matrix[2][223] = 0.5 // 水->神灵
|
||||
|
||||
// 3. 火(fire)对其他属性的克制
|
||||
matrix[3][1] = 2 // 火->草
|
||||
matrix[3][2] = 0.5 // 火->水
|
||||
matrix[3][3] = 0.5 // 火->火
|
||||
matrix[3][6] = 2 // 火->机械
|
||||
matrix[3][9] = 2 // 火->冰
|
||||
matrix[3][16] = 0.5 // 火->圣灵
|
||||
matrix[3][20] = 0.5 // 火->自然
|
||||
matrix[3][222] = 0.5 // 火->混沌
|
||||
matrix[3][223] = 0.5 // 火->神灵
|
||||
|
||||
// 4. 飞行(flying)对其他属性的克制
|
||||
matrix[4][1] = 2 // 飞行->草
|
||||
matrix[4][5] = 0.5 // 飞行->电
|
||||
matrix[4][6] = 0.5 // 飞行->机械
|
||||
matrix[4][11] = 2 // 飞行->战斗
|
||||
matrix[4][17] = 0.5 // 飞行->次元
|
||||
matrix[4][19] = 0.5 // 飞行->邪灵
|
||||
matrix[4][20] = 0.5 // 飞行->自然
|
||||
matrix[4][222] = 0.5 // 飞行->混沌
|
||||
matrix[4][225] = 2 // 飞行->虫
|
||||
|
||||
// 5. 电(electric)对其他属性的克制
|
||||
matrix[5][1] = 0.5 // 电->草
|
||||
matrix[5][2] = 2 // 电->水
|
||||
matrix[5][4] = 2 // 电->飞行
|
||||
matrix[5][5] = 0.5 // 电->电
|
||||
matrix[5][7] = 0 // 电->地面(无效)
|
||||
matrix[5][13] = 2 // 电->暗影
|
||||
matrix[5][14] = 0.5 // 电->神秘
|
||||
matrix[5][16] = 0.5 // 电->圣灵
|
||||
matrix[5][17] = 2 // 电->次元
|
||||
matrix[5][20] = 0.5 // 电->自然
|
||||
matrix[5][222] = 2 // 电->混沌
|
||||
matrix[5][223] = 0.5 // 电->神灵
|
||||
matrix[5][226] = 2 // 电->虚空
|
||||
|
||||
// 6. 机械(steel)对其他属性的克制(你提供的示例已包含)
|
||||
matrix[6][2] = 0.5 // 机械->水
|
||||
matrix[6][3] = 0.5 // 机械->火
|
||||
matrix[6][5] = 0.5 // 机械->电
|
||||
matrix[6][6] = 0.5 // 机械->机械
|
||||
matrix[6][9] = 2 // 机械->冰
|
||||
matrix[6][11] = 2 // 机械->战斗
|
||||
matrix[6][17] = 0.5 // 机械->次元
|
||||
matrix[6][18] = 2 // 机械->远古
|
||||
matrix[6][19] = 2 // 机械->邪灵
|
||||
matrix[6][223] = 2 // 机械->神灵
|
||||
|
||||
// 7. 地面(ground)对其他属性的克制
|
||||
matrix[7][1] = 0.5 // 地面->草
|
||||
matrix[7][3] = 2 // 地面->火
|
||||
matrix[7][4] = 0 // 地面->飞行(无效)
|
||||
matrix[7][5] = 2 // 地面->电
|
||||
matrix[7][6] = 2 // 地面->机械
|
||||
matrix[7][10] = 0.5 // 地面->超能
|
||||
matrix[7][13] = 0.5 // 地面->暗影
|
||||
matrix[7][15] = 0.5 // 地面->龙
|
||||
matrix[7][16] = 0.5 // 地面->圣灵
|
||||
matrix[7][20] = 0.5 // 地面->自然
|
||||
matrix[7][221] = 2 // 地面->王
|
||||
matrix[7][223] = 0.5 // 地面->神灵
|
||||
matrix[7][224] = 2 // 地面->轮回
|
||||
matrix[7][225] = 0.5 // 地面->虫
|
||||
|
||||
// 8. 普通(normal)对其他属性的克制(全为1.0,无特殊值)
|
||||
|
||||
// 9. 冰(ice)对其他属性的克制
|
||||
matrix[9][1] = 2 // 冰->草
|
||||
matrix[9][2] = 0.5 // 冰->水
|
||||
matrix[9][3] = 0.5 // 冰->火
|
||||
matrix[9][4] = 2 // 冰->飞行
|
||||
matrix[9][6] = 0.5 // 冰->机械
|
||||
matrix[9][7] = 2 // 冰->地面
|
||||
matrix[9][9] = 0.5 // 冰->冰
|
||||
matrix[9][16] = 0.5 // 冰->圣灵
|
||||
matrix[9][17] = 2 // 冰->次元
|
||||
matrix[9][18] = 2 // 冰->远古
|
||||
matrix[9][222] = 0.5 // 冰->混沌
|
||||
matrix[9][223] = 0.5 // 冰->神灵
|
||||
matrix[9][224] = 2 // 冰->轮回
|
||||
matrix[9][225] = 2 // 冰->虫
|
||||
|
||||
// 10. 超能(psychic)对其他属性的克制
|
||||
matrix[10][6] = 0.5 // 超能->机械
|
||||
matrix[10][10] = 0.5 // 超能->超能
|
||||
matrix[10][11] = 2 // 超能->战斗
|
||||
matrix[10][12] = 0 // 超能->光(无效)
|
||||
matrix[10][14] = 2 // 超能->神秘
|
||||
matrix[10][20] = 2 // 超能->自然
|
||||
matrix[10][225] = 0.5 // 超能->虫
|
||||
|
||||
// 11. 战斗(fight)对其他属性的克制
|
||||
matrix[11][6] = 2 // 战斗->机械
|
||||
matrix[11][9] = 2 // 战斗->冰
|
||||
matrix[11][10] = 0.5 // 战斗->超能
|
||||
matrix[11][11] = 0.5 // 战斗->战斗
|
||||
matrix[11][13] = 0.5 // 战斗->暗影
|
||||
matrix[11][15] = 2 // 战斗->龙
|
||||
matrix[11][16] = 2 // 战斗->圣灵
|
||||
matrix[11][19] = 0.5 // 战斗->邪灵
|
||||
matrix[11][221] = 0.5 // 战斗->王
|
||||
|
||||
// 12. 光(light)对其他属性的克制
|
||||
matrix[12][1] = 0 // 光->草(无效)
|
||||
matrix[12][6] = 0.5 // 光->机械
|
||||
matrix[12][9] = 0.5 // 光->冰
|
||||
matrix[12][10] = 2 // 光->超能
|
||||
matrix[12][12] = 0.5 // 光->光
|
||||
matrix[12][13] = 2 // 光->暗影
|
||||
matrix[12][16] = 0.5 // 光->圣灵
|
||||
matrix[12][19] = 0.5 // 光->邪灵
|
||||
matrix[12][20] = 0.5 // 光->自然
|
||||
matrix[12][223] = 0.5 // 光->神灵
|
||||
matrix[12][224] = 0.5 // 光->轮回
|
||||
matrix[12][225] = 2 // 光->虫
|
||||
matrix[12][226] = 0.5 // 光->虚空
|
||||
|
||||
// 13. 暗影(dark)对其他属性的克制
|
||||
matrix[13][6] = 0.5 // 暗影->机械
|
||||
matrix[13][9] = 0.5 // 暗影->冰
|
||||
matrix[13][10] = 2 // 暗影->超能
|
||||
matrix[13][12] = 0.5 // 暗影->光
|
||||
matrix[13][13] = 2 // 暗影->暗影
|
||||
matrix[13][16] = 0.5 // 暗影->圣灵
|
||||
matrix[13][17] = 2 // 暗影->次元
|
||||
matrix[13][19] = 0.5 // 暗影->邪灵
|
||||
matrix[13][223] = 0.5 // 暗影->神灵
|
||||
|
||||
// 14. 神秘(myth)对其他属性的克制
|
||||
matrix[14][5] = 2 // 神秘->电
|
||||
matrix[14][7] = 0.5 // 神秘->地面
|
||||
matrix[14][11] = 0.5 // 神秘->战斗
|
||||
matrix[14][14] = 2 // 神秘->神秘
|
||||
matrix[14][16] = 2 // 神秘->圣灵
|
||||
matrix[14][19] = 0.5 // 神秘->邪灵
|
||||
matrix[14][20] = 2 // 神秘->自然
|
||||
matrix[14][221] = 2 // 神秘->王
|
||||
matrix[14][222] = 0.5 // 神秘->混沌
|
||||
matrix[14][223] = 2 // 神秘->神灵
|
||||
matrix[14][224] = 2 // 神秘->轮回
|
||||
matrix[14][225] = 0.5 // 神秘->虫
|
||||
|
||||
// 15. 龙(dragon)对其他属性的克制
|
||||
matrix[15][1] = 0.5 // 龙->草
|
||||
matrix[15][2] = 0.5 // 龙->水
|
||||
matrix[15][3] = 0.5 // 龙->火
|
||||
matrix[15][5] = 0.5 // 龙->电
|
||||
matrix[15][9] = 2 // 龙->冰
|
||||
matrix[15][15] = 2 // 龙->龙
|
||||
matrix[15][16] = 2 // 龙->圣灵
|
||||
matrix[15][18] = 0.5 // 龙->远古
|
||||
matrix[15][19] = 2 // 龙->邪灵
|
||||
matrix[15][225] = 0.5 // 龙->虫
|
||||
|
||||
// 16. 圣灵(saint)对其他属性的克制
|
||||
matrix[16][1] = 2 // 圣灵->草
|
||||
matrix[16][2] = 2 // 圣灵->水
|
||||
matrix[16][3] = 2 // 圣灵->火
|
||||
matrix[16][5] = 2 // 圣灵->电
|
||||
matrix[16][9] = 2 // 圣灵->冰
|
||||
matrix[16][11] = 0.5 // 圣灵->战斗
|
||||
matrix[16][14] = 0.5 // 圣灵->神秘
|
||||
matrix[16][15] = 0.5 // 圣灵->龙
|
||||
matrix[16][18] = 2 // 圣灵->远古
|
||||
matrix[16][224] = 0.5 // 圣灵->轮回
|
||||
matrix[16][226] = 2 // 圣灵->虚空
|
||||
|
||||
// 17. 次元(dimension)对其他属性的克制
|
||||
matrix[17][4] = 2 // 次元->飞行
|
||||
matrix[17][6] = 2 // 次元->机械
|
||||
matrix[17][9] = 0.5 // 次元->冰
|
||||
matrix[17][10] = 2 // 次元->超能
|
||||
matrix[17][13] = 0 // 次元->暗影(无效)
|
||||
matrix[17][19] = 2 // 次元->邪灵
|
||||
matrix[17][20] = 2 // 次元->自然
|
||||
matrix[17][221] = 0.5 // 次元->王
|
||||
matrix[17][222] = 0.5 // 次元->混沌
|
||||
matrix[17][223] = 0.5 // 次元->神灵
|
||||
matrix[17][224] = 0.5 // 次元->轮回
|
||||
matrix[17][225] = 2 // 次元->虫
|
||||
matrix[17][226] = 2 // 次元->虚空
|
||||
|
||||
// 18. 远古(ancient)对其他属性的克制
|
||||
matrix[18][1] = 2 // 远古->草
|
||||
matrix[18][4] = 2 // 远古->飞行
|
||||
matrix[18][6] = 0.5 // 远古->机械
|
||||
matrix[18][9] = 0.5 // 远古->冰
|
||||
matrix[18][14] = 2 // 远古->神秘
|
||||
matrix[18][15] = 2 // 远古->龙
|
||||
matrix[18][221] = 0.5 // 远古->王
|
||||
matrix[18][224] = 0.5 // 远古->轮回
|
||||
matrix[18][226] = 2 // 远古->虚空
|
||||
|
||||
// 19. 邪灵(demon)对其他属性的克制
|
||||
matrix[19][6] = 0.5 // 邪灵->机械
|
||||
matrix[19][9] = 0.5 // 邪灵->冰
|
||||
matrix[19][10] = 0.5 // 邪灵->超能
|
||||
matrix[19][12] = 2 // 邪灵->光
|
||||
matrix[19][13] = 2 // 邪灵->暗影
|
||||
matrix[19][14] = 2 // 邪灵->神秘
|
||||
matrix[19][16] = 0.5 // 邪灵->圣灵
|
||||
matrix[19][17] = 2 // 邪灵->次元
|
||||
matrix[19][20] = 2 // 邪灵->自然
|
||||
matrix[19][221] = 0.5 // 邪灵->王
|
||||
matrix[19][222] = 0.5 // 邪灵->混沌
|
||||
matrix[19][223] = 0 // 邪灵->神灵(无效)
|
||||
matrix[19][224] = 0.5 // 邪灵->轮回
|
||||
|
||||
// 20. 自然(nature)对其他属性的克制
|
||||
matrix[20][1] = 2 // 自然->草
|
||||
matrix[20][2] = 2 // 自然->水
|
||||
matrix[20][3] = 2 // 自然->火
|
||||
matrix[20][4] = 2 // 自然->飞行
|
||||
matrix[20][5] = 2 // 自然->电
|
||||
matrix[20][6] = 0.5 // 自然->机械
|
||||
matrix[20][7] = 2 // 自然->地面
|
||||
matrix[20][10] = 0.5 // 自然->超能
|
||||
matrix[20][11] = 0.5 // 自然->战斗
|
||||
matrix[20][12] = 2 // 自然->光
|
||||
matrix[20][13] = 0.5 // 自然->暗影
|
||||
matrix[20][14] = 0.5 // 自然->神秘
|
||||
matrix[20][17] = 0.5 // 自然->次元
|
||||
matrix[20][19] = 0.5 // 自然->邪灵
|
||||
matrix[20][221] = 2 // 自然->王
|
||||
matrix[20][222] = 0.5 // 自然->混沌
|
||||
matrix[20][224] = 2 // 自然->轮回
|
||||
matrix[20][226] = 0.5 // 自然->虚空
|
||||
// 21. 王(king,ID=221)对其他属性的克制
|
||||
matrix[221][10] = 0.5 // 王 -> 超能
|
||||
matrix[221][11] = 2 // 王 -> 战斗
|
||||
matrix[221][13] = 2 // 王 -> 暗影
|
||||
matrix[221][17] = 2 // 王 -> 次元
|
||||
matrix[221][19] = 2 // 王 -> 邪灵
|
||||
matrix[221][20] = 0.5 // 王 -> 自然
|
||||
matrix[221][225] = 0.5 // 王 -> 虫
|
||||
|
||||
// 22. 混沌(chaos,ID=222)对其他属性的克制
|
||||
matrix[222][4] = 2 // 混沌 -> 飞行
|
||||
matrix[222][5] = 0.5 // 混沌 -> 电
|
||||
matrix[222][6] = 0.5 // 混沌 -> 机械
|
||||
matrix[222][9] = 2 // 混沌 -> 冰
|
||||
matrix[222][11] = 0.5 // 混沌 -> 战斗
|
||||
matrix[222][14] = 2 // 混沌 -> 神秘
|
||||
matrix[222][17] = 2 // 混沌 -> 次元
|
||||
matrix[222][19] = 2 // 混沌 -> 邪灵
|
||||
matrix[222][20] = 2 // 混沌 -> 自然
|
||||
matrix[222][222] = 1 // 混沌 -> 混沌(默认1.0,显式标注)
|
||||
matrix[222][223] = 2 // 混沌 -> 神灵
|
||||
matrix[222][224] = 0.5 // 混沌 -> 轮回
|
||||
matrix[222][225] = 2 // 混沌 -> 虫
|
||||
matrix[222][226] = 0 // 混沌 -> 虚空(无效)
|
||||
|
||||
// 23. 神灵(deity,ID=223)对其他属性的克制
|
||||
matrix[223][1] = 2 // 神灵 -> 草
|
||||
matrix[223][2] = 2 // 神灵 -> 水
|
||||
matrix[223][3] = 2 // 神灵 -> 火
|
||||
matrix[223][5] = 2 // 神灵 -> 电
|
||||
matrix[223][6] = 0.5 // 神灵 -> 机械
|
||||
matrix[223][9] = 2 // 神灵 -> 冰
|
||||
matrix[223][11] = 0.5 // 神灵 -> 战斗
|
||||
matrix[223][15] = 0.5 // 神灵 -> 龙
|
||||
matrix[223][18] = 2 // 神灵 -> 远古
|
||||
matrix[223][19] = 2 // 神灵 -> 邪灵
|
||||
matrix[223][222] = 2 // 神灵 -> 混沌
|
||||
matrix[223][223] = 1 // 神灵 -> 神灵(默认1.0,显式标注)
|
||||
|
||||
// 24. 轮回(samsara,ID=224)对其他属性的克制
|
||||
matrix[224][9] = 0.5 // 轮回 -> 冰
|
||||
matrix[224][10] = 0.5 // 轮回 -> 超能
|
||||
matrix[224][12] = 2 // 轮回 -> 光
|
||||
matrix[224][13] = 2 // 轮回 -> 暗影
|
||||
matrix[224][16] = 2 // 轮回 -> 圣灵
|
||||
matrix[224][17] = 2 // 轮回 -> 次元
|
||||
matrix[224][19] = 2 // 轮回 -> 邪灵
|
||||
matrix[224][20] = 0.5 // 轮回 -> 自然
|
||||
matrix[224][222] = 2 // 轮回 -> 混沌
|
||||
matrix[224][224] = 1 // 轮回 -> 轮回(默认1.0,显式标注)
|
||||
matrix[224][226] = 0.5 // 轮回 -> 虚空
|
||||
|
||||
// 25. 虫(insect,ID=225)对其他属性的克制
|
||||
matrix[225][1] = 2 // 虫 -> 草
|
||||
matrix[225][2] = 0.5 // 虫 -> 水
|
||||
matrix[225][3] = 0.5 // 虫 -> 火
|
||||
matrix[225][7] = 2 // 虫 -> 地面
|
||||
matrix[225][9] = 0.5 // 虫 -> 冰
|
||||
matrix[225][11] = 2 // 虫 -> 战斗
|
||||
matrix[225][12] = 0.5 // 虫 -> 光
|
||||
matrix[225][222] = 2 // 虫 -> 混沌
|
||||
matrix[225][224] = 1 // 虫 -> 轮回(默认1.0,显式标注)
|
||||
matrix[225][225] = 2 // 虫 -> 虫
|
||||
|
||||
// 26. 虚空(void,ID=226)对其他属性的克制
|
||||
matrix[226][4] = 0.5 // 虚空 -> 飞行
|
||||
matrix[226][10] = 2 // 虚空 -> 超能
|
||||
matrix[226][11] = 2 // 虚空 -> 战斗
|
||||
matrix[226][12] = 2 // 虚空 -> 光
|
||||
matrix[226][13] = 0.5 // 虚空 -> 暗影
|
||||
matrix[226][14] = 2 // 虚空 -> 神秘
|
||||
matrix[226][16] = 0.5 // 虚空 -> 圣灵
|
||||
matrix[226][17] = 0.5 // 虚空 -> 次元
|
||||
matrix[226][20] = 2 // 虚空 -> 自然
|
||||
matrix[226][222] = 1 // 虚空 -> 混沌(默认1.0,显式标注)
|
||||
matrix[226][224] = 2 // 虚空 -> 轮回
|
||||
matrix[226][226] = 1 // 虚空 -> 虚空(默认1.0,显式标注)
|
||||
|
||||
enToID := initSkillTypes()
|
||||
initTypeRelations(enToID)
|
||||
}
|
||||
|
||||
func initSkillTypes() map[string]int {
|
||||
var cfg skillTypesFile
|
||||
mustUnmarshalJSON(skillTypesJSON, &cfg, "skillTypes.json")
|
||||
|
||||
enToID := make(map[string]int, len(cfg.Root.Item))
|
||||
for _, item := range cfg.Root.Item {
|
||||
mustValidElementID(item.ID, "skill type")
|
||||
if len(item.EN) == 0 {
|
||||
panic(fmt.Sprintf("skill type %d has no en name", item.ID))
|
||||
}
|
||||
|
||||
elementNameMap[item.ID] = strings.ToUpper(strings.Join(item.EN, "_"))
|
||||
if item.IsDou == 0 {
|
||||
validSingleElementIDs[item.ID] = true
|
||||
enToID[item.EN[0]] = item.ID
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range cfg.Root.Item {
|
||||
if item.IsDou == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(item.EN) != 2 {
|
||||
panic(fmt.Sprintf("dual skill type %d must have two en names", item.ID))
|
||||
}
|
||||
primaryID, ok := enToID[item.EN[0]]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("dual skill type %d references unknown primary type %q", item.ID, item.EN[0]))
|
||||
}
|
||||
secondaryID, ok := enToID[item.EN[1]]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("dual skill type %d references unknown secondary type %q", item.ID, item.EN[1]))
|
||||
}
|
||||
dualElementMap[item.ID] = [2]int{primaryID, secondaryID}
|
||||
}
|
||||
|
||||
return enToID
|
||||
}
|
||||
|
||||
func initTypeRelations(enToID map[string]int) {
|
||||
var cfg typeRelationFile
|
||||
mustUnmarshalJSON(typesRelationJSON, &cfg, "typesRelation.json")
|
||||
|
||||
for _, relation := range cfg.Root.Relation {
|
||||
attackerID, ok := enToID[relation.Type]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("type relation references unknown attacker type %q", relation.Type))
|
||||
}
|
||||
|
||||
for _, opponent := range relation.Opponent {
|
||||
defenderID, ok := enToID[opponent.Type]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("type relation references unknown defender type %q", opponent.Type))
|
||||
}
|
||||
matrix[attackerID][defenderID] = opponent.Multiple
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustUnmarshalJSON(data []byte, target any, name string) {
|
||||
if err := json.Unmarshal(data, target); err != nil {
|
||||
panic(fmt.Sprintf("parse %s: %v", name, err))
|
||||
}
|
||||
}
|
||||
|
||||
func mustValidElementID(id int, kind string) {
|
||||
if id <= 0 || id >= maxMatrixSize {
|
||||
panic(fmt.Sprintf("%s id out of range: %d", kind, id))
|
||||
}
|
||||
}
|
||||
|
||||
1340
common/data/Element/data/skillTypes.json
Normal file
1340
common/data/Element/data/skillTypes.json
Normal file
File diff suppressed because it is too large
Load Diff
2840
common/data/Element/data/typesRelation.json
Normal file
2840
common/data/Element/data/typesRelation.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -42,154 +42,14 @@ const (
|
||||
maxMatrixSize = 227 // 矩阵维度(覆盖最大属性ID 226)
|
||||
)
|
||||
|
||||
// 合法单属性ID集合(快速校验)
|
||||
var validSingleElementIDs = map[int]bool{
|
||||
1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true,
|
||||
11: true, 12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true,
|
||||
221: true, 222: true, 223: true, 224: true, 225: true, 226: true,
|
||||
}
|
||||
// 属性配置由 data/skillTypes.json 初始化,数据来自 seer-types-relation。
|
||||
var validSingleElementIDs [maxMatrixSize]bool
|
||||
|
||||
// 元素名称映射(全属性对应,便于日志输出)
|
||||
var elementNameMap = map[ElementType]string{
|
||||
ElementTypeGrass: "GRASS",
|
||||
ElementTypeWater: "WATER",
|
||||
ElementTypeFire: "FIRE",
|
||||
ElementTypeFlying: "FLYING",
|
||||
ElementTypeElectric: "ELECTRIC",
|
||||
ElementTypeSteel: "STEEL",
|
||||
ElementTypeGround: "GROUND",
|
||||
ElementTypeNormal: "NORMAL",
|
||||
ElementTypeIce: "ICE",
|
||||
ElementTypePsychic: "PSYCHIC",
|
||||
ElementTypeFighting: "FIGHTING",
|
||||
ElementTypeLight: "LIGHT",
|
||||
ElementTypeDark: "DARK",
|
||||
ElementTypeMythic: "MYTHIC",
|
||||
ElementTypeDragon: "DRAGON",
|
||||
ElementTypeSaint: "SAINT",
|
||||
ElementTypeDimension: "DIMENSION",
|
||||
ElementTypeAncient: "ANCIENT",
|
||||
ElementTypeDemon: "DEMON",
|
||||
ElementTypeNature: "NATURE",
|
||||
ElementTypeKing: "KING",
|
||||
ElementTypeChaos: "CHAOS",
|
||||
ElementTypeDeity: "DEITY",
|
||||
ElementTypeSamsara: "SAMSARA",
|
||||
ElementTypeInsect: "INSECT",
|
||||
ElementTypeVoid: "VOID",
|
||||
}
|
||||
// 元素名称映射(按ID直接索引,便于日志输出)
|
||||
var elementNameMap [maxMatrixSize]string
|
||||
|
||||
// 双属性映射(key=双属性ID,value=组成的两个单属性ID)
|
||||
var dualElementMap = map[int][2]int{
|
||||
21: {1, 10}, // 草 超能
|
||||
22: {1, 11}, // 草 战斗
|
||||
23: {1, 13}, // 草 暗影
|
||||
24: {2, 10}, // 水 超能
|
||||
25: {2, 13}, // 水 暗影
|
||||
26: {2, 15}, // 水 龙
|
||||
27: {3, 4}, // 火 飞行
|
||||
28: {3, 15}, // 火 龙
|
||||
29: {3, 10}, // 火 超能
|
||||
30: {4, 10}, // 飞行 超能
|
||||
31: {12, 4}, // 光 飞行
|
||||
32: {4, 15}, // 飞行 龙
|
||||
33: {5, 3}, // 电 火
|
||||
34: {5, 9}, // 电 冰
|
||||
35: {5, 11}, // 电 战斗
|
||||
36: {13, 5}, // 暗影 电
|
||||
37: {6, 7}, // 机械 地面
|
||||
38: {6, 10}, // 机械 超能
|
||||
39: {6, 15}, // 机械 龙
|
||||
40: {7, 15}, // 地面 龙
|
||||
41: {11, 7}, // 战斗 地面
|
||||
42: {7, 13}, // 地面 暗影
|
||||
43: {9, 15}, // 冰 龙
|
||||
44: {9, 12}, // 冰 光
|
||||
45: {9, 13}, // 冰 暗影
|
||||
46: {10, 9}, // 超能 冰
|
||||
47: {11, 3}, // 战斗 火
|
||||
48: {11, 13}, // 战斗 暗影
|
||||
49: {12, 14}, // 光 神秘
|
||||
50: {13, 14}, // 暗影 神秘
|
||||
51: {14, 10}, // 神秘 超能
|
||||
52: {16, 12}, // 圣灵 光
|
||||
53: {4, 14}, // 飞行 神秘
|
||||
54: {7, 10}, // 地面 超能
|
||||
55: {13, 15}, // 暗影 龙
|
||||
56: {16, 13}, // 圣灵 暗影
|
||||
57: {18, 11}, // 远古 战斗
|
||||
58: {3, 14}, // 火 神秘
|
||||
59: {12, 11}, // 光 战斗
|
||||
60: {14, 11}, // 神秘 战斗
|
||||
61: {17, 11}, // 次元 战斗
|
||||
62: {19, 14}, // 邪灵 神秘
|
||||
63: {18, 15}, // 远古 龙
|
||||
64: {12, 17}, // 光 次元
|
||||
65: {18, 16}, // 远古 圣灵
|
||||
66: {2, 11}, // 水 战斗
|
||||
67: {5, 15}, // 电 龙
|
||||
68: {12, 3}, // 光 火
|
||||
69: {12, 13}, // 光 暗影
|
||||
70: {19, 15}, // 邪灵 龙
|
||||
71: {18, 14}, // 远古 神秘
|
||||
72: {6, 17}, // 机械 次元
|
||||
73: {11, 15}, // 战斗 龙
|
||||
74: {11, 20}, // 战斗 自然
|
||||
75: {19, 6}, // 邪灵 机械
|
||||
76: {5, 17}, // 电 次元
|
||||
77: {18, 3}, // 远古 火
|
||||
78: {16, 11}, // 圣灵 战斗
|
||||
79: {16, 17}, // 圣灵 次元
|
||||
80: {16, 5}, // 圣灵 电
|
||||
81: {18, 7}, // 远古 地面
|
||||
82: {18, 1}, // 远古 草
|
||||
83: {20, 15}, // 自然 龙
|
||||
84: {9, 14}, // 冰 神秘
|
||||
85: {4, 13}, // 飞行 暗影
|
||||
86: {9, 3}, // 冰 火
|
||||
87: {9, 4}, // 冰 飞行
|
||||
88: {20, 16}, // 自然 圣灵
|
||||
89: {222, 16}, // 混沌 圣灵
|
||||
90: {18, 19}, // 远古 邪灵
|
||||
91: {20, 9}, // 自然 冰
|
||||
92: {222, 13}, // 混沌 暗影
|
||||
93: {222, 11}, // 混沌 战斗
|
||||
94: {222, 10}, // 混沌 超能
|
||||
95: {16, 10}, // 圣灵 超能
|
||||
96: {222, 7}, // 混沌 地面
|
||||
97: {13, 19}, // 暗影 邪灵
|
||||
98: {222, 18}, // 混沌 远古
|
||||
99: {222, 19}, // 混沌 邪灵
|
||||
100: {16, 7}, // 圣灵 地面
|
||||
101: {3, 13}, // 火 暗影
|
||||
102: {12, 10}, // 光 超能
|
||||
103: {6, 11}, // 机械 战斗
|
||||
104: {4, 5}, // 飞行 电
|
||||
105: {222, 4}, // 混沌 飞行
|
||||
106: {222, 15}, // 混沌 龙
|
||||
107: {222, 3}, // 混沌 火
|
||||
108: {16, 3}, // 圣灵 火
|
||||
109: {7, 14}, // 地面 神秘
|
||||
110: {222, 17}, // 混沌 次元
|
||||
111: {222, 9}, // 混沌 冰
|
||||
112: {20, 14}, // 自然 神秘
|
||||
113: {226, 19}, // 虚空 邪灵
|
||||
114: {226, 222}, // 虚空 混沌
|
||||
115: {16, 224}, // 圣灵 轮回
|
||||
116: {2, 17}, // 水 次元
|
||||
117: {16, 14}, // 圣灵 神秘
|
||||
118: {6, 14}, // 机械 神秘
|
||||
119: {2, 14}, // 水 神秘
|
||||
120: {17, 15}, // 次元 龙
|
||||
121: {20, 10}, // 自然 超能
|
||||
122: {5, 6}, // 电 机械
|
||||
123: {14, 224}, // 神秘 轮回
|
||||
124: {2, 6}, // 水 机械
|
||||
125: {3, 6}, // 火 机械
|
||||
126: {1, 6}, // 草 机械
|
||||
127: {18, 5}, // 远古 电
|
||||
128: {16, 4}, // 圣灵 飞行
|
||||
}
|
||||
var dualElementMap = map[int][2]int{}
|
||||
|
||||
// 元素组合结构体
|
||||
type ElementCombination struct {
|
||||
@@ -198,46 +58,55 @@ type ElementCombination struct {
|
||||
ID int // 组合唯一ID
|
||||
}
|
||||
|
||||
// 全局预加载资源(程序启动时init初始化,运行时直接使用)
|
||||
// 全局预加载资源(程序启动时初始化,运行时只读)
|
||||
var (
|
||||
// 元素组合池:key=组合ID,value=组合实例(预加载所有合法组合)
|
||||
elementCombinationPool = make(map[int]*ElementCombination, 150) // 128双+26单=154,预分配足够容量
|
||||
// 单属性克制矩阵(预初始化所有特殊克制关系,默认1.0)
|
||||
matrix [maxMatrixSize][maxMatrixSize]float64
|
||||
validCombinationIDs [maxMatrixSize]bool
|
||||
elementCombinationPool [maxMatrixSize]ElementCombination
|
||||
dualElementSecondaryPool [maxMatrixSize]ElementType
|
||||
matrix [maxMatrixSize][maxMatrixSize]float64
|
||||
Calculator *ElementCalculator
|
||||
)
|
||||
|
||||
// init 预加载所有资源(程序启动时执行一次,无并发问题)
|
||||
func init() {
|
||||
// 1. 初始化单属性克制矩阵
|
||||
initFullTableMatrix()
|
||||
initElementCombinationPool()
|
||||
Calculator = NewElementCalculator()
|
||||
}
|
||||
|
||||
// 2. 预加载所有单属性组合
|
||||
for id := range validSingleElementIDs {
|
||||
combo := &ElementCombination{
|
||||
Primary: ElementType(id),
|
||||
Secondary: nil,
|
||||
ID: id,
|
||||
func initElementCombinationPool() {
|
||||
for id, valid := range validSingleElementIDs {
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
validCombinationIDs[id] = true
|
||||
elementCombinationPool[id] = ElementCombination{
|
||||
Primary: ElementType(id),
|
||||
ID: id,
|
||||
}
|
||||
elementCombinationPool[id] = combo
|
||||
}
|
||||
|
||||
// 3. 预加载所有双属性组合
|
||||
for dualID, atts := range dualElementMap {
|
||||
primaryID, secondaryID := atts[0], atts[1]
|
||||
// 按ID升序排序,保证组合一致性
|
||||
primary, secondary := ElementType(primaryID), ElementType(secondaryID)
|
||||
if primary > secondary {
|
||||
primary, secondary = secondary, primary
|
||||
}
|
||||
combo := &ElementCombination{
|
||||
|
||||
dualElementSecondaryPool[dualID] = secondary
|
||||
validCombinationIDs[dualID] = true
|
||||
elementCombinationPool[dualID] = ElementCombination{
|
||||
Primary: primary,
|
||||
Secondary: &secondary,
|
||||
Secondary: &dualElementSecondaryPool[dualID],
|
||||
ID: dualID,
|
||||
}
|
||||
elementCombinationPool[dualID] = combo
|
||||
}
|
||||
}
|
||||
|
||||
func isValidCombinationID(id int) bool {
|
||||
return id > 0 && id < maxMatrixSize && validCombinationIDs[id]
|
||||
}
|
||||
|
||||
// IsDual 判断是否为双属性
|
||||
func (ec *ElementCombination) IsDual() bool {
|
||||
return ec.Secondary != nil
|
||||
@@ -245,84 +114,82 @@ func (ec *ElementCombination) IsDual() bool {
|
||||
|
||||
// Elements 获取所有属性列表
|
||||
func (ec *ElementCombination) Elements() []ElementType {
|
||||
if ec.IsDual() {
|
||||
return []ElementType{ec.Primary, *ec.Secondary}
|
||||
if secondary := ec.Secondary; secondary != nil {
|
||||
return []ElementType{ec.Primary, *secondary}
|
||||
}
|
||||
return []ElementType{ec.Primary}
|
||||
}
|
||||
|
||||
// String 友好格式化输出
|
||||
func (ec *ElementCombination) String() string {
|
||||
primaryName := elementNameMap[ec.Primary]
|
||||
if !ec.IsDual() {
|
||||
return fmt.Sprintf("(%s)", primaryName)
|
||||
if secondary := ec.Secondary; secondary != nil {
|
||||
return fmt.Sprintf("(%s, %s)", elementNameMap[ec.Primary], elementNameMap[*secondary])
|
||||
}
|
||||
return fmt.Sprintf("(%s, %s)", primaryName, elementNameMap[*ec.Secondary])
|
||||
return fmt.Sprintf("(%s)", elementNameMap[ec.Primary])
|
||||
}
|
||||
|
||||
// ElementCalculator 无锁元素克制计算器(依赖预加载资源)
|
||||
// ElementCalculator 无锁元素克制计算器(所有倍数在初始化阶段预计算)
|
||||
type ElementCalculator struct {
|
||||
offensiveCache map[string]float64 // 攻击克制缓存(运行时填充,无并发写)
|
||||
offensiveTable [maxMatrixSize][maxMatrixSize]float64
|
||||
}
|
||||
|
||||
// NewElementCalculator 创建计算器实例(仅初始化缓存)
|
||||
// NewElementCalculator 创建计算器实例(构建只读查表缓存)
|
||||
func NewElementCalculator() *ElementCalculator {
|
||||
return &ElementCalculator{
|
||||
offensiveCache: make(map[string]float64, 4096), // 预分配大容量缓存
|
||||
c := &ElementCalculator{}
|
||||
c.initOffensiveTable()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ElementCalculator) initOffensiveTable() {
|
||||
for attackerID, valid := range validCombinationIDs {
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
attacker := &elementCombinationPool[attackerID]
|
||||
for defenderID, valid := range validCombinationIDs {
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
defender := &elementCombinationPool[defenderID]
|
||||
c.offensiveTable[attackerID][defenderID] = c.calculateMultiplier(attacker, defender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getMatrixValue 直接返回矩阵值(修复核心问题:不再将0转换为1)
|
||||
func (c *ElementCalculator) getMatrixValue(attacker, defender ElementType) float64 {
|
||||
return matrix[attacker][defender] // 矩阵默认已初始化1.0,特殊值直接返回
|
||||
return matrix[attacker][defender]
|
||||
}
|
||||
|
||||
// GetCombination 获取元素组合(直接从预加载池读取)
|
||||
// GetCombination 获取元素组合(直接按ID索引)
|
||||
func (c *ElementCalculator) GetCombination(id int) (*ElementCombination, error) {
|
||||
combo, exists := elementCombinationPool[id]
|
||||
if !exists {
|
||||
if !isValidCombinationID(id) {
|
||||
return nil, fmt.Errorf("invalid element combination ID: %d", id)
|
||||
}
|
||||
return combo, nil
|
||||
return &elementCombinationPool[id], nil
|
||||
}
|
||||
|
||||
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(缓存优先)
|
||||
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(只读查表)
|
||||
func (c *ElementCalculator) GetOffensiveMultiplier(attackerID, defenderID int) (float64, error) {
|
||||
// 1. 获取预加载的组合实例
|
||||
attacker, err := c.GetCombination(attackerID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("attacker invalid: %w", err)
|
||||
if !isValidCombinationID(attackerID) {
|
||||
return 0, fmt.Errorf("attacker invalid: invalid element combination ID: %d", attackerID)
|
||||
}
|
||||
defender, err := c.GetCombination(defenderID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("defender invalid: %w", err)
|
||||
if !isValidCombinationID(defenderID) {
|
||||
return 0, fmt.Errorf("defender invalid: invalid element combination ID: %d", defenderID)
|
||||
}
|
||||
|
||||
// 2. 缓存键(全局唯一)
|
||||
cacheKey := fmt.Sprintf("a%d_d%d", attackerID, defenderID)
|
||||
if val, exists := c.offensiveCache[cacheKey]; exists {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// 3. 核心计算+缓存
|
||||
val := c.calculateMultiplier(attacker, defender)
|
||||
c.offensiveCache[cacheKey] = val
|
||||
return val, nil
|
||||
return c.offensiveTable[attackerID][defenderID], nil
|
||||
}
|
||||
|
||||
// calculateMultiplier 核心克制计算逻辑
|
||||
func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombination) float64 {
|
||||
// 场景1:单→单
|
||||
if !attacker.IsDual() && !defender.IsDual() {
|
||||
return c.getMatrixValue(attacker.Primary, defender.Primary)
|
||||
}
|
||||
|
||||
// 场景2:单→双
|
||||
if !attacker.IsDual() {
|
||||
y1, y2 := defender.Primary, *defender.Secondary
|
||||
m1 := c.getMatrixValue(attacker.Primary, y1)
|
||||
m2 := c.getMatrixValue(attacker.Primary, y2)
|
||||
|
||||
switch {
|
||||
case m1 == 2 && m2 == 2:
|
||||
return 4.0
|
||||
@@ -333,12 +200,10 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
|
||||
}
|
||||
}
|
||||
|
||||
// 场景3:双→单
|
||||
if !defender.IsDual() {
|
||||
return c.calculateDualToSingle(attacker.Primary, *attacker.Secondary, defender.Primary)
|
||||
}
|
||||
|
||||
// 场景4:双→双
|
||||
x1, x2 := attacker.Primary, *attacker.Secondary
|
||||
y1, y2 := defender.Primary, *defender.Secondary
|
||||
coeffY1 := c.calculateDualToSingle(x1, x2, y1)
|
||||
@@ -350,7 +215,6 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
|
||||
func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender ElementType) float64 {
|
||||
k1 := c.getMatrixValue(attacker1, defender)
|
||||
k2 := c.getMatrixValue(attacker2, defender)
|
||||
|
||||
switch {
|
||||
case k1 == 2 && k2 == 2:
|
||||
return 4.0
|
||||
@@ -361,60 +225,49 @@ func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender
|
||||
}
|
||||
}
|
||||
|
||||
var Calculator = NewElementCalculator()
|
||||
|
||||
// TestAllScenarios 全场景测试(验证预加载和计算逻辑)
|
||||
func TestAllScenarios() {
|
||||
|
||||
// 测试1:单→单(草→水)
|
||||
m1, _ := Calculator.GetOffensiveMultiplier(1, 2)
|
||||
fmt.Println("草→水: %.2f(预期2.0)", m1)
|
||||
if math.Abs(m1-2.0) > 0.001 {
|
||||
fmt.Println("测试1失败:实际%.2f", m1)
|
||||
}
|
||||
|
||||
// 测试2:特殊单→单(混沌→虚空)
|
||||
m2, _ := Calculator.GetOffensiveMultiplier(222, 226)
|
||||
fmt.Println("混沌→虚空: %.2f(预期0.0)", m2)
|
||||
if math.Abs(m2-0.0) > 0.001 {
|
||||
fmt.Println("测试2失败:实际%.2f", m2)
|
||||
}
|
||||
|
||||
// 测试3:单→双(火→冰龙(43))
|
||||
m3, _ := Calculator.GetOffensiveMultiplier(3, 43)
|
||||
fmt.Println("火→冰龙: %.2f(预期1.5)", m3)
|
||||
if math.Abs(m3-1.5) > 0.001 {
|
||||
fmt.Println("测试3失败:实际%.2f", m3)
|
||||
}
|
||||
|
||||
// 测试4:双→特殊单(混沌暗影(92)→神灵(223))
|
||||
m4, _ := Calculator.GetOffensiveMultiplier(92, 223)
|
||||
fmt.Println("混沌暗影→神灵: %.2f(预期1.25)", m4)
|
||||
if math.Abs(m4-1.25) > 0.001 {
|
||||
fmt.Println("测试4失败:实际%.2f", m4)
|
||||
}
|
||||
|
||||
// 测试5:双→双(虚空邪灵(113)→混沌远古(98))
|
||||
m5, _ := Calculator.GetOffensiveMultiplier(113, 98)
|
||||
fmt.Println("虚空邪灵→混沌远古: %.2f(预期0.875", m5)
|
||||
if math.Abs(m5-0.875) > 0.001 {
|
||||
fmt.Println("测试5失败:实际%.2f", m5)
|
||||
}
|
||||
|
||||
// 测试6:缓存命中
|
||||
m6, _ := Calculator.GetOffensiveMultiplier(113, 98)
|
||||
if math.Abs(m6-m5) > 0.001 {
|
||||
fmt.Println("测试6失败:缓存未命中")
|
||||
}
|
||||
|
||||
// 测试7:含无效组合(电→地面)
|
||||
m7, _ := Calculator.GetOffensiveMultiplier(5, 7)
|
||||
fmt.Println("电→地面: %.2f(预期0.0)", m7)
|
||||
if math.Abs(m7-0.0) > 0.001 {
|
||||
fmt.Println("测试7失败:实际%.2f", m7)
|
||||
}
|
||||
|
||||
// 测试8:双属性含无效(电战斗→地面)
|
||||
m8, _ := Calculator.GetOffensiveMultiplier(35, 7)
|
||||
fmt.Println("电战斗→地面: %.2f(预期0.25)", m8)
|
||||
if math.Abs(m8-0.25) > 0.001 {
|
||||
|
||||
56
common/data/Element/element_test.go
Normal file
56
common/data/Element/element_test.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package element
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetOffensiveMultiplierFromSeerTypesRelation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
attackerID int
|
||||
defenderID int
|
||||
want float64
|
||||
}{
|
||||
{name: "single to single", attackerID: 1, defenderID: 2, want: 2},
|
||||
{name: "immunity stays zero", attackerID: 5, defenderID: 7, want: 0},
|
||||
{name: "single to double", attackerID: 3, defenderID: 43, want: 1.5},
|
||||
{name: "double to single", attackerID: 35, defenderID: 7, want: 0.25},
|
||||
{name: "double to double", attackerID: 92, defenderID: 223, want: 1.25},
|
||||
{name: "new dual type ancient steel", attackerID: 129, defenderID: 223, want: 1.5},
|
||||
{name: "new dual type ancient light", attackerID: 130, defenderID: 226, want: 1.25},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Calculator.GetOffensiveMultiplier(tt.attackerID, tt.defenderID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOffensiveMultiplier() error = %v", err)
|
||||
}
|
||||
if math.Abs(got-tt.want) > 0.001 {
|
||||
t.Fatalf("GetOffensiveMultiplier() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCombinationIncludesSeerTypesRelationDualTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
id int
|
||||
primary ElementType
|
||||
secondary ElementType
|
||||
}{
|
||||
{id: 129, primary: ElementTypeSteel, secondary: ElementTypeAncient},
|
||||
{id: 130, primary: ElementTypeLight, secondary: ElementTypeAncient},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, err := Calculator.GetCombination(tt.id)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCombination(%d) error = %v", tt.id, err)
|
||||
}
|
||||
if got.Primary != tt.primary || got.Secondary == nil || *got.Secondary != tt.secondary {
|
||||
t.Fatalf("GetCombination(%d) = %#v, want primary %v secondary %v", tt.id, got, tt.primary, tt.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ type EffectArg struct {
|
||||
SideEffect []struct {
|
||||
ID int `json:"ID"`
|
||||
SideEffectArgcount int `json:"SideEffectArgcount"`
|
||||
SideEffectArg string `json:"SideEffectArg,omitempty"`
|
||||
SideEffectArg rawFlexibleString `json:"SideEffectArg,omitempty"`
|
||||
} `json:"SideEffect"`
|
||||
} `json:"SideEffects"`
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
_ "blazing/common/data/xmlres/packed"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"fmt"
|
||||
|
||||
"github.com/ECUST-XX/xml"
|
||||
"github.com/gogf/gf/v2/os/gres"
|
||||
@@ -14,22 +14,36 @@ import (
|
||||
|
||||
var path string
|
||||
|
||||
func readConfigContent(path string) []byte {
|
||||
return gres.GetContent(path)
|
||||
}
|
||||
|
||||
func getXml[T any](path string) T {
|
||||
|
||||
// 解析XML到结构体
|
||||
var xmls T
|
||||
|
||||
t1 := gres.GetContent(path)
|
||||
t1 := readConfigContent(path)
|
||||
xml.Unmarshal(t1, &xmls)
|
||||
|
||||
return xmls
|
||||
}
|
||||
func getJson[T any](path string) T {
|
||||
|
||||
// 解析XML到结构体
|
||||
// 解析JSON到结构体
|
||||
var xmls T
|
||||
t1 := gres.GetContent(path)
|
||||
json.Unmarshal(t1, &xmls)
|
||||
t1 := readConfigContent(path)
|
||||
if len(t1) == 0 {
|
||||
fmt.Printf("[xmlres] getJson empty content: path=%s\n", path)
|
||||
return xmls
|
||||
}
|
||||
if err := json.Unmarshal(t1, &xmls); err != nil {
|
||||
head := string(t1)
|
||||
if len(head) > 300 {
|
||||
head = head[:300]
|
||||
}
|
||||
fmt.Printf("[xmlres] getJson unmarshal failed: path=%s len=%d err=%v head=%q\n", path, len(t1), err, head)
|
||||
}
|
||||
|
||||
return xmls
|
||||
}
|
||||
@@ -58,8 +72,6 @@ var (
|
||||
|
||||
func Initfile() {
|
||||
//gres.Dump()
|
||||
path1, _ := os.Getwd()
|
||||
path = path1 + "/public/config/"
|
||||
path = "config/"
|
||||
MapConfig = getXml[Maps](path + "210.xml")
|
||||
|
||||
@@ -87,10 +99,10 @@ func Initfile() {
|
||||
return gconv.Int(m.ProductID)
|
||||
|
||||
})
|
||||
Skill := getXml[MovesTbl](path + "227.xml")
|
||||
skillConfig := getJson[MovesJSON](path + "moves_flash.json")
|
||||
|
||||
SkillMap = make(map[int]Move, len(Skill.Moves))
|
||||
for _, v := range Skill.Moves {
|
||||
SkillMap = make(map[int]Move, len(skillConfig.MovesTbl.Moves.Move))
|
||||
for _, v := range skillConfig.MovesTbl.Moves.Move {
|
||||
v.SideEffectS = ParseSideEffectArgs(v.SideEffect)
|
||||
v.SideEffectArgS = ParseSideEffectArgs(v.SideEffectArg)
|
||||
SkillMap[v.ID] = v
|
||||
@@ -101,7 +113,11 @@ func Initfile() {
|
||||
|
||||
})
|
||||
|
||||
PetMAP = utils.ToMap[PetInfo, int](getXml[Monsters](path+"226.xml").Monsters, func(m PetInfo) int {
|
||||
pets := getXml[Monsters](path + "226.xml").Monsters
|
||||
for i := range pets {
|
||||
pets[i].YieldingEVValues = parseYieldingEV(pets[i].YieldingEV)
|
||||
}
|
||||
PetMAP = utils.ToMap[PetInfo, int](pets, func(m PetInfo) int {
|
||||
return m.ID
|
||||
|
||||
})
|
||||
|
||||
26
common/data/xmlres/json_compat_test.go
Normal file
26
common/data/xmlres/json_compat_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package xmlres
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMoveUnmarshalJSONAcceptsNumericName(t *testing.T) {
|
||||
var move Move
|
||||
if err := json.Unmarshal([]byte(`{"ID":10001,"Name":1,"Category":1,"Type":8,"Power":35,"MaxPP":35,"Accuracy":95}`), &move); err != nil {
|
||||
t.Fatalf("unmarshal move failed: %v", err)
|
||||
}
|
||||
if move.Name != "1" {
|
||||
t.Fatalf("expected numeric name to convert to string, got %q", move.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEffectArgUnmarshalJSONAcceptsNumericSideEffectArg(t *testing.T) {
|
||||
var cfg EffectArg
|
||||
if err := json.Unmarshal([]byte(`{"SideEffects":{"SideEffect":[{"ID":1,"SideEffectArgcount":1,"SideEffectArg":3}]}}`), &cfg); err != nil {
|
||||
t.Fatalf("unmarshal effect arg failed: %v", err)
|
||||
}
|
||||
if got := string(cfg.SideEffects.SideEffect[0].SideEffectArg); got != "3" {
|
||||
t.Fatalf("expected numeric side effect arg to convert to string, got %q", got)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,11 @@
|
||||
package xmlres
|
||||
|
||||
import "github.com/ECUST-XX/xml"
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ECUST-XX/xml"
|
||||
)
|
||||
|
||||
// Move 表示怪物可学习的技能
|
||||
type PetMoves struct {
|
||||
@@ -45,6 +50,7 @@ type PetInfo struct {
|
||||
Recycle int `xml:"Recycle,attr"` // 是否可回收
|
||||
LearnableMoves LearnableMoves `xml:"LearnableMoves"` // 可学习的技能
|
||||
NaturalEnemy string `xml:"NaturalEnemy,attr"` //天敌
|
||||
YieldingEVValues []int64 `xml:"-"` // 预解析后的努力值奖励
|
||||
}
|
||||
|
||||
func (basic *PetInfo) GetBasic() uint32 {
|
||||
@@ -61,3 +67,16 @@ type Monsters struct {
|
||||
XMLName xml.Name `xml:"Monsters"`
|
||||
Monsters []PetInfo `xml:"Monster"`
|
||||
}
|
||||
|
||||
func parseYieldingEV(raw string) []int64 {
|
||||
values := make([]int64, 6)
|
||||
parts := strings.Fields(raw)
|
||||
for i := 0; i < len(parts) && i < len(values); i++ {
|
||||
value, err := strconv.ParseInt(parts[i], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
values[i] = value
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package xmlres
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -33,51 +34,156 @@ type MovesTbl struct {
|
||||
Moves []Move `xml:"Moves>Move"`
|
||||
EFF []SideEffect `xml:"SideEffects>SideEffect"`
|
||||
}
|
||||
|
||||
type MovesJSON struct {
|
||||
MovesTbl MovesJSONRoot `json:"MovesTbl"`
|
||||
}
|
||||
|
||||
type MovesJSONRoot struct {
|
||||
Moves struct {
|
||||
Move []Move `json:"Move"`
|
||||
} `json:"Moves"`
|
||||
SideEffects struct {
|
||||
SideEffect []SideEffect `json:"SideEffect"`
|
||||
} `json:"SideEffects"`
|
||||
}
|
||||
|
||||
type MovesMap struct {
|
||||
XMLName xml.Name `xml:"MovesTbl"`
|
||||
Moves map[int]Move
|
||||
EFF []SideEffect `xml:"SideEffects>SideEffect"`
|
||||
}
|
||||
|
||||
type rawFlexibleString string
|
||||
|
||||
func (s *rawFlexibleString) UnmarshalJSON(data []byte) error {
|
||||
text := strings.TrimSpace(string(data))
|
||||
if text == "" || text == "null" {
|
||||
*s = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(text) >= 2 && text[0] == '"' && text[len(text)-1] == '"' {
|
||||
var decoded string
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = rawFlexibleString(decoded)
|
||||
return nil
|
||||
}
|
||||
|
||||
*s = rawFlexibleString(text)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move 定义单个技能的结构
|
||||
type Move struct {
|
||||
ID int `xml:"ID,attr"`
|
||||
Name string `xml:"Name,attr"`
|
||||
ID int `xml:"ID,attr" json:"ID"`
|
||||
Name string `xml:"Name,attr" json:"Name"`
|
||||
|
||||
Category int `xml:"Category,attr"` //属性
|
||||
Type int `xml:"Type,attr"` //类型
|
||||
Power int `xml:"Power,attr"` //威力
|
||||
MaxPP int `xml:"MaxPP,attr"` //最大PP
|
||||
Accuracy int `xml:"Accuracy,attr"` //命中率
|
||||
CritRate int `xml:"CritRate,attr,omitempty"` //暴击率
|
||||
Priority int `xml:"Priority,attr,omitempty"` //优先级
|
||||
MustHit int `xml:"MustHit,attr,omitempty"` //是否必中
|
||||
SwapElemType int `xml:"SwapElemType,attr,omitempty"` //技能交换属性
|
||||
CopyElemType int `xml:"CopyElemType,attr,omitempty"` // 技能复制属性
|
||||
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty"` // 先出手时必定致命一击
|
||||
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty"` //后出手时必定致命一击
|
||||
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty"` //自身体力低于一半时必定致命一击
|
||||
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty"` //对方体力低于一半时必定致命一击
|
||||
DmgBindLv int `xml:"DmgBindLv,attr,omitempty"` //使对方受到的伤害值等于自身的等级
|
||||
PwrBindDv int `xml:"PwrBindDv,attr,omitempty"` //威力(power)取决于自身的潜力(个体值)
|
||||
PwrDouble int `xml:"PwrDouble,attr,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
|
||||
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty"` //使对方受到的伤害值等于自身的体力值
|
||||
SideEffect string `xml:"SideEffect,attr,omitempty"`
|
||||
SideEffectArg string `xml:"SideEffectArg,attr,omitempty"`
|
||||
Category int `xml:"Category,attr" json:"Category"` //属性
|
||||
Type int `xml:"Type,attr" json:"Type"` //类型
|
||||
Power int `xml:"Power,attr" json:"Power"` //威力
|
||||
MaxPP int `xml:"MaxPP,attr" json:"MaxPP"` //最大PP
|
||||
Accuracy int `xml:"Accuracy,attr" json:"Accuracy"` //命中率
|
||||
CritRate int `xml:"CritRate,attr,omitempty" json:"CritRate,omitempty"` //暴击率
|
||||
Priority int `xml:"Priority,attr,omitempty" json:"Priority,omitempty"` //优先级
|
||||
MustHit int `xml:"MustHit,attr,omitempty" json:"MustHit,omitempty"` //是否必中
|
||||
SwapElemType int `xml:"SwapElemType,attr,omitempty" json:"SwapElemType,omitempty"` //技能交换属性
|
||||
CopyElemType int `xml:"CopyElemType,attr,omitempty" json:"CopyElemType,omitempty"` // 技能复制属性
|
||||
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty" json:"CritAtkFirst,omitempty"` // 先出手时必定致命一击
|
||||
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty" json:"CritAtkSecond,omitempty"` //后出手时必定致命一击
|
||||
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty" json:"CritSelfHalfHp,omitempty"` //自身体力低于一半时必定致命一击
|
||||
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty" json:"CritFoeHalfHp,omitempty"` //对方体力低于一半时必定致命一击
|
||||
DmgBindLv int `xml:"DmgBindLv,attr,omitempty" json:"DmgBindLv,omitempty"` //使对方受到的伤害值等于自身的等级
|
||||
PwrBindDv int `xml:"PwrBindDv,attr,omitempty" json:"PwrBindDv,omitempty"` //威力(power)取决于自身的潜力(个体值)
|
||||
PwrDouble int `xml:"PwrDouble,attr,omitempty" json:"PwrDouble,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
|
||||
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty" json:"DmgBindHpDv,omitempty"` //使对方受到的伤害值等于自身的体力值
|
||||
SideEffect string `xml:"SideEffect,attr,omitempty" json:"SideEffect,omitempty"`
|
||||
SideEffectArg string `xml:"SideEffectArg,attr,omitempty" json:"SideEffectArg,omitempty"`
|
||||
SideEffectS []int
|
||||
SideEffectArgS []int
|
||||
AtkNum int `xml:"AtkNum,attr,omitempty"`
|
||||
Url string `xml:"Url,attr,omitempty"`
|
||||
AtkNum int `xml:"AtkNum,attr,omitempty" json:"AtkNum,omitempty"`
|
||||
AtkType int `xml:"AtkType,attr,omitempty" json:"AtkType,omitempty"` // 0:所有人 1:仅己方 2:仅对方 3:仅自己
|
||||
Url string `xml:"Url,attr,omitempty" json:"Url,omitempty"`
|
||||
|
||||
Info string `xml:"info,attr,omitempty"`
|
||||
Info string `xml:"info,attr,omitempty" json:"info,omitempty"`
|
||||
|
||||
CD *int `xml:"CD,attr"`
|
||||
CD *int `xml:"CD,attr" json:"CD"`
|
||||
}
|
||||
|
||||
func (m *Move) UnmarshalJSON(data []byte) error {
|
||||
type moveAlias struct {
|
||||
ID int `json:"ID"`
|
||||
Name rawFlexibleString `json:"Name"`
|
||||
Category int `json:"Category"`
|
||||
Type int `json:"Type"`
|
||||
Power int `json:"Power"`
|
||||
MaxPP int `json:"MaxPP"`
|
||||
Accuracy int `json:"Accuracy"`
|
||||
CritRate int `json:"CritRate,omitempty"`
|
||||
Priority int `json:"Priority,omitempty"`
|
||||
MustHit int `json:"MustHit,omitempty"`
|
||||
SwapElemType int `json:"SwapElemType,omitempty"`
|
||||
CopyElemType int `json:"CopyElemType,omitempty"`
|
||||
CritAtkFirst int `json:"CritAtkFirst,omitempty"`
|
||||
CritAtkSecond int `json:"CritAtkSecond,omitempty"`
|
||||
CritSelfHalfHp int `json:"CritSelfHalfHp,omitempty"`
|
||||
CritFoeHalfHp int `json:"CritFoeHalfHp,omitempty"`
|
||||
DmgBindLv int `json:"DmgBindLv,omitempty"`
|
||||
PwrBindDv int `json:"PwrBindDv,omitempty"`
|
||||
PwrDouble int `json:"PwrDouble,omitempty"`
|
||||
DmgBindHpDv int `json:"DmgBindHpDv,omitempty"`
|
||||
SideEffect rawFlexibleString `json:"SideEffect,omitempty"`
|
||||
SideEffectArg rawFlexibleString `json:"SideEffectArg,omitempty"`
|
||||
AtkNum int `json:"AtkNum,omitempty"`
|
||||
AtkType int `json:"AtkType,omitempty"`
|
||||
Url string `json:"Url,omitempty"`
|
||||
Info string `json:"info,omitempty"`
|
||||
CD *int `json:"CD"`
|
||||
}
|
||||
|
||||
var aux moveAlias
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*m = Move{
|
||||
ID: aux.ID,
|
||||
Name: string(aux.Name),
|
||||
Category: aux.Category,
|
||||
Type: aux.Type,
|
||||
Power: aux.Power,
|
||||
MaxPP: aux.MaxPP,
|
||||
Accuracy: aux.Accuracy,
|
||||
CritRate: aux.CritRate,
|
||||
Priority: aux.Priority,
|
||||
MustHit: aux.MustHit,
|
||||
SwapElemType: aux.SwapElemType,
|
||||
CopyElemType: aux.CopyElemType,
|
||||
CritAtkFirst: aux.CritAtkFirst,
|
||||
CritAtkSecond: aux.CritAtkSecond,
|
||||
CritSelfHalfHp: aux.CritSelfHalfHp,
|
||||
CritFoeHalfHp: aux.CritFoeHalfHp,
|
||||
DmgBindLv: aux.DmgBindLv,
|
||||
PwrBindDv: aux.PwrBindDv,
|
||||
PwrDouble: aux.PwrDouble,
|
||||
DmgBindHpDv: aux.DmgBindHpDv,
|
||||
SideEffect: string(aux.SideEffect),
|
||||
SideEffectArg: string(aux.SideEffectArg),
|
||||
AtkNum: aux.AtkNum,
|
||||
AtkType: aux.AtkType,
|
||||
Url: aux.Url,
|
||||
Info: aux.Info,
|
||||
CD: aux.CD,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SideEffect struct {
|
||||
ID int `xml:"ID,attr"`
|
||||
Help string `xml:"help,attr"`
|
||||
Des string `xml:"des,attr"`
|
||||
ID int `xml:"ID,attr" json:"ID"`
|
||||
Help string `xml:"help,attr" json:"help"`
|
||||
Des string `xml:"des,attr" json:"des"`
|
||||
}
|
||||
|
||||
// ReadHTTPFile 通过HTTP GET请求获取远程文件内容
|
||||
|
||||
@@ -2,17 +2,21 @@ package rpc
|
||||
|
||||
import (
|
||||
"blazing/cool"
|
||||
"context"
|
||||
"blazing/logic/service/fight/pvp"
|
||||
"blazing/logic/service/fight/pvpwire"
|
||||
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// ListenFunc 监听函数
|
||||
// ListenFunc 改造后的 Redis PubSub 监听函数,支持自动重连和心跳保活
|
||||
// ListenFunc 改造后的 Redis PubSub 监听函数,支持自动重连。
|
||||
// 注意:PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
|
||||
func ListenFunc(ctx g.Ctx) {
|
||||
if !cool.IsRedisMode {
|
||||
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
||||
@@ -20,9 +24,8 @@ func ListenFunc(ctx g.Ctx) {
|
||||
|
||||
// 定义常量配置
|
||||
const (
|
||||
subscribeTopic = "cool:func" // 订阅的主题
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
heartbeatInterval = 30 * time.Second // 心跳保活间隔
|
||||
subscribeTopic = "cool:func" // 订阅的主题
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
)
|
||||
|
||||
// 外层循环:负责连接断开后的整体重连
|
||||
@@ -43,47 +46,25 @@ func ListenFunc(ctx g.Ctx) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. 启动心跳保活协程,防止连接因空闲被断开
|
||||
heartbeatCtx, heartbeatCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
ticker := time.NewTicker(heartbeatInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
heartbeatCancel()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-heartbeatCtx.Done():
|
||||
cool.Logger.Info(ctx, "心跳协程退出")
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 发送 PING 心跳,保持连接活跃
|
||||
_, pingErr := conn.Do(ctx, "PING")
|
||||
if pingErr != nil {
|
||||
cool.Logger.Error(ctx, "Redis 心跳失败,触发重连", "error", pingErr)
|
||||
// 心跳失败时主动关闭连接,触发外层重连
|
||||
_ = conn.Close(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 3. 订阅主题
|
||||
// 2. 订阅主题
|
||||
_, err = conn.Do(ctx, "subscribe", subscribeTopic)
|
||||
if err != nil {
|
||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", subscribeTopic, "error", err)
|
||||
heartbeatCancel() // 关闭心跳协程
|
||||
_ = conn.Close(ctx)
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", subscribeTopic)
|
||||
_, err = conn.Do(ctx, "subscribe", "sun:join") //加入队列
|
||||
if err != nil {
|
||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", "sun:join", "error", err)
|
||||
_ = conn.Close(ctx)
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", "sun:join")
|
||||
|
||||
// 4. 循环接收消息
|
||||
// 3. 循环接收消息
|
||||
connError := false
|
||||
for !connError {
|
||||
select {
|
||||
@@ -126,15 +107,15 @@ func ListenFunc(ctx g.Ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 清理资源,准备重连
|
||||
heartbeatCancel() // 关闭心跳协程
|
||||
// 4. 清理资源,准备重连
|
||||
_ = conn.Close(ctx) // 关闭当前连接
|
||||
// Logger.Warn(ctx, "Redis 连接异常,准备重连", "retry_after", retryDelay)
|
||||
cool.Logger.Info(ctx, "Redis 订阅连接异常,准备重连", "retry_after", retryDelay)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenFight 完全对齐 ListenFunc 写法,修复收不到消息问题
|
||||
// ListenFight 完全对齐 ListenFunc 写法,修复收不到消息问题。
|
||||
// 注意:PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
|
||||
func ListenFight(ctx g.Ctx) {
|
||||
if !cool.IsRedisMode {
|
||||
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
||||
@@ -142,14 +123,15 @@ func ListenFight(ctx g.Ctx) {
|
||||
|
||||
// 定义常量配置(对齐 ListenFunc 风格)
|
||||
const (
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
heartbeatInterval = 30 * time.Second // 心跳保活间隔
|
||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||
)
|
||||
|
||||
// 提前拼接订阅主题(避免重复拼接,便于日志打印)
|
||||
serverID := cool.Config.ServerInfo.GetID()
|
||||
startTopic := "sun:start:" + serverID
|
||||
sendPackTopic := "sendpack:" + serverID
|
||||
pvpServerTopic := pvpwire.ServerTopic(gconv.Uint32(serverID))
|
||||
pvpCoordinatorTopic := pvpwire.CoordinatorTopicPrefix
|
||||
|
||||
// 外层循环:负责连接断开后的整体重连
|
||||
for {
|
||||
@@ -170,45 +152,26 @@ func ListenFight(ctx g.Ctx) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 2. 启动心跳保活协程(完全对齐 ListenFunc 逻辑)
|
||||
heartbeatCtx, heartbeatCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
ticker := time.NewTicker(heartbeatInterval)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
heartbeatCancel()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-heartbeatCtx.Done():
|
||||
cool.Logger.Info(ctx, "心跳协程退出")
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 发送 PING 心跳,保持连接活跃
|
||||
_, pingErr := conn.Do(ctx, "PING")
|
||||
if pingErr != nil {
|
||||
cool.Logger.Error(ctx, "Redis 心跳失败,触发重连", "error", pingErr)
|
||||
// 心跳失败时主动关闭连接,触发外层重连
|
||||
_ = conn.Close(ctx)
|
||||
return
|
||||
}
|
||||
cool.Logger.Debug(ctx, "Redis 心跳发送成功,连接正常")
|
||||
}
|
||||
// 2. 订阅主题(对齐 ListenFunc 的错误处理,替换 panic 为优雅重连)
|
||||
subscribeTopics := []string{startTopic, pvpServerTopic}
|
||||
if cool.Config.GameOnlineID == pvp.CoordinatorOnlineID {
|
||||
subscribeTopics = append(subscribeTopics, pvpCoordinatorTopic)
|
||||
}
|
||||
subscribeFailed := false
|
||||
for _, topic := range subscribeTopics {
|
||||
_, err = conn.Do(ctx, "subscribe", topic)
|
||||
if err != nil {
|
||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", topic, "error", err)
|
||||
_ = conn.Close(ctx)
|
||||
time.Sleep(retryDelay)
|
||||
subscribeFailed = true
|
||||
break
|
||||
}
|
||||
}()
|
||||
|
||||
// 3. 订阅主题(对齐 ListenFunc 的错误处理,替换 panic 为优雅重连)
|
||||
// 订阅 sun:start:服务器ID
|
||||
_, err = conn.Do(ctx, "subscribe", startTopic)
|
||||
if err != nil {
|
||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", startTopic, "error", err)
|
||||
heartbeatCancel() // 关闭心跳协程
|
||||
_ = conn.Close(ctx)
|
||||
time.Sleep(retryDelay)
|
||||
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", topic)
|
||||
}
|
||||
if subscribeFailed {
|
||||
continue
|
||||
}
|
||||
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", startTopic)
|
||||
|
||||
// // 订阅 sun:sendpack:服务器ID
|
||||
// _, err = conn.Do(ctx, "subscribe", sendPackTopic)
|
||||
@@ -224,7 +187,7 @@ func ListenFight(ctx g.Ctx) {
|
||||
// 打印监听提示(保留原有日志)
|
||||
fmt.Println("监听战斗", startTopic)
|
||||
|
||||
// 4. 循环接收消息(完全对齐 ListenFunc 逻辑)
|
||||
// 3. 循环接收消息(完全对齐 ListenFunc 逻辑)
|
||||
connError := false
|
||||
for !connError {
|
||||
select {
|
||||
@@ -255,6 +218,10 @@ func ListenFight(ctx g.Ctx) {
|
||||
// universalClient, _ := g.Redis("cool").Client().(goredis.UniversalClient)
|
||||
}
|
||||
|
||||
if dataMap.Channel == pvpServerTopic || dataMap.Channel == pvpCoordinatorTopic {
|
||||
pvp.HandleRedisMessage(dataMap.Channel, dataMap.Payload)
|
||||
}
|
||||
|
||||
// 【可选】处理 sun:sendpack:服务器ID 消息(如果需要)
|
||||
if dataMap.Channel == sendPackTopic {
|
||||
fmt.Println("收到战斗包", dataMap.Payload)
|
||||
@@ -262,9 +229,9 @@ func ListenFight(ctx g.Ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 清理资源,准备重连(完全对齐 ListenFunc)
|
||||
heartbeatCancel() // 关闭心跳协程
|
||||
// 4. 清理资源,准备重连(完全对齐 ListenFunc)
|
||||
_ = conn.Close(ctx) // 关闭当前连接
|
||||
cool.Logger.Info(ctx, "Redis 战斗订阅连接异常,准备重连", "retry_after", retryDelay)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
184
common/rpc/pvp_match.go
Normal file
184
common/rpc/pvp_match.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/fight/pvpwire"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
pvpMatchQueueTTL = 12 * time.Second
|
||||
pvpMatchBanPickSecond = 45
|
||||
)
|
||||
|
||||
type PVPMatchJoinPayload struct {
|
||||
RuntimeServerID uint32 `json:"runtimeServerId"`
|
||||
UserID uint32 `json:"userId"`
|
||||
Nick string `json:"nick"`
|
||||
FightMode uint32 `json:"fightMode"`
|
||||
Status uint32 `json:"status"`
|
||||
IsVip uint32 `json:"isVip"`
|
||||
IsDebug uint8 `json:"isDebug"`
|
||||
CatchTimes []uint32 `json:"catchTimes"`
|
||||
TianxuanPetIDs []uint32 `json:"tianxuanPetIds"`
|
||||
}
|
||||
|
||||
type pvpMatchQueueKey struct {
|
||||
FightMode uint32
|
||||
IsVip uint32
|
||||
IsDebug uint8
|
||||
}
|
||||
|
||||
type pvpMatchCoordinator struct {
|
||||
mu sync.Mutex
|
||||
queues map[pvpMatchQueueKey][]pvpwire.QueuePlayerSnapshot
|
||||
lastSeen map[uint32]time.Time
|
||||
}
|
||||
|
||||
var defaultPVPMatchCoordinator = &pvpMatchCoordinator{
|
||||
queues: make(map[pvpMatchQueueKey][]pvpwire.QueuePlayerSnapshot),
|
||||
lastSeen: make(map[uint32]time.Time),
|
||||
}
|
||||
|
||||
func DefaultPVPMatchCoordinator() *pvpMatchCoordinator {
|
||||
return defaultPVPMatchCoordinator
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) JoinOrUpdate(payload PVPMatchJoinPayload) error {
|
||||
if payload.UserID == 0 || payload.RuntimeServerID == 0 || payload.FightMode == 0 {
|
||||
return fmt.Errorf("invalid pvp match payload: uid=%d server=%d mode=%d", payload.UserID, payload.RuntimeServerID, payload.FightMode)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
player := pvpwire.QueuePlayerSnapshot{
|
||||
RuntimeServerID: payload.RuntimeServerID,
|
||||
UserID: payload.UserID,
|
||||
Nick: payload.Nick,
|
||||
FightMode: payload.FightMode,
|
||||
Status: payload.Status,
|
||||
IsVip: payload.IsVip,
|
||||
IsDebug: payload.IsDebug,
|
||||
JoinedAtUnix: now.Unix(),
|
||||
CatchTimes: append([]uint32(nil), payload.CatchTimes...),
|
||||
TianxuanPetIDs: append([]uint32(nil), payload.TianxuanPetIDs...),
|
||||
}
|
||||
|
||||
var match *pvpwire.MatchFoundPayload
|
||||
|
||||
m.mu.Lock()
|
||||
m.pruneExpiredLocked(now)
|
||||
m.removeUserLocked(payload.UserID)
|
||||
m.lastSeen[payload.UserID] = now
|
||||
|
||||
queueKey := newPVPMatchQueueKey(player)
|
||||
queue := m.queues[queueKey]
|
||||
if len(queue) > 0 {
|
||||
host := queue[0]
|
||||
queue = queue[1:]
|
||||
m.queues[queueKey] = queue
|
||||
delete(m.lastSeen, host.UserID)
|
||||
delete(m.lastSeen, payload.UserID)
|
||||
|
||||
result := pvpwire.MatchFoundPayload{
|
||||
SessionID: buildPVPMatchSessionID(host.UserID, payload.UserID),
|
||||
Stage: pvpwire.StageBanPick,
|
||||
Host: host,
|
||||
Guest: player,
|
||||
BanPickTimeout: pvpMatchBanPickSecond,
|
||||
}
|
||||
match = &result
|
||||
} else {
|
||||
m.queues[queueKey] = append(queue, player)
|
||||
}
|
||||
m.mu.Unlock()
|
||||
|
||||
if match == nil {
|
||||
return nil
|
||||
}
|
||||
if err := publishPVPMatchMessage(pvpwire.ServerTopic(match.Host.RuntimeServerID), pvpwire.MessageTypeMatchFound, *match); err != nil {
|
||||
return err
|
||||
}
|
||||
if match.Guest.RuntimeServerID != match.Host.RuntimeServerID {
|
||||
if err := publishPVPMatchMessage(pvpwire.ServerTopic(match.Guest.RuntimeServerID), pvpwire.MessageTypeMatchFound, *match); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) Cancel(userID uint32) {
|
||||
if userID == 0 {
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
delete(m.lastSeen, userID)
|
||||
m.removeUserLocked(userID)
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) pruneExpiredLocked(now time.Time) {
|
||||
for key, queue := range m.queues {
|
||||
next := make([]pvpwire.QueuePlayerSnapshot, 0, len(queue))
|
||||
for _, queued := range queue {
|
||||
last := m.lastSeen[queued.UserID]
|
||||
if last.IsZero() || now.Sub(last) > pvpMatchQueueTTL {
|
||||
delete(m.lastSeen, queued.UserID)
|
||||
continue
|
||||
}
|
||||
next = append(next, queued)
|
||||
}
|
||||
m.queues[key] = next
|
||||
}
|
||||
}
|
||||
|
||||
func (m *pvpMatchCoordinator) removeUserLocked(userID uint32) {
|
||||
for key, queue := range m.queues {
|
||||
next := make([]pvpwire.QueuePlayerSnapshot, 0, len(queue))
|
||||
for _, queued := range queue {
|
||||
if queued.UserID == userID {
|
||||
continue
|
||||
}
|
||||
next = append(next, queued)
|
||||
}
|
||||
m.queues[key] = next
|
||||
}
|
||||
}
|
||||
|
||||
func newPVPMatchQueueKey(player pvpwire.QueuePlayerSnapshot) pvpMatchQueueKey {
|
||||
return pvpMatchQueueKey{
|
||||
FightMode: player.FightMode,
|
||||
IsVip: player.IsVip,
|
||||
IsDebug: player.IsDebug,
|
||||
}
|
||||
}
|
||||
|
||||
func publishPVPMatchMessage(topic, msgType string, body any) error {
|
||||
payload, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
envelope, err := json.Marshal(pvpwire.Envelope{
|
||||
Type: msgType,
|
||||
Body: payload,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := cool.Redis.Conn(context.Background())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close(context.Background())
|
||||
|
||||
_, err = conn.Do(context.Background(), "publish", topic, envelope)
|
||||
return err
|
||||
}
|
||||
|
||||
func buildPVPMatchSessionID(hostUserID, guestUserID uint32) string {
|
||||
return fmt.Sprintf("xsvr-%d-%d-%d", hostUserID, guestUserID, time.Now().UnixNano())
|
||||
}
|
||||
@@ -1,106 +1,216 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"blazing/common/data/share"
|
||||
"blazing/cool"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
config "blazing/modules/config/service"
|
||||
|
||||
"github.com/filecoin-project/go-jsonrpc"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Define the server handler
|
||||
type ServerHandler struct{}
|
||||
|
||||
// 实现踢人
|
||||
func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
|
||||
|
||||
useid1, _ := share.ShareManager.GetUserOnline(userid)
|
||||
if useid1 == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
cl, ok := cool.GetClientOnly(useid1)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
cl.KickPerson(userid) //实现指定服务器踢人
|
||||
return nil
|
||||
}
|
||||
|
||||
// 注册logic服务器
|
||||
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error {
|
||||
fmt.Println("注册logic服务器", id, port)
|
||||
|
||||
//TODO 待修复滚动更新可能导致的玩家可以同时在旧服务器和新服务器同时在线的bug
|
||||
revClient, ok := jsonrpc.ExtractReverseClient[cool.ClientHandler](ctx)
|
||||
if !ok {
|
||||
return fmt.Errorf("no reverse client")
|
||||
}
|
||||
t := config.NewServerService().GetServerID((id))
|
||||
|
||||
aa, ok := cool.GetClient(t.OnlineID, t.Port)
|
||||
if ok && aa != nil { //如果已经存在且这个端口已经被存过
|
||||
aa.QuitSelf(0)
|
||||
}
|
||||
cool.AddClient(100000*id+port, &revClient)
|
||||
|
||||
//Refurh()
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func CServer() *jsonrpc.RPCServer {
|
||||
// create a new server instance
|
||||
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[cool.ClientHandler](""))
|
||||
|
||||
rpcServer.Register("", &ServerHandler{})
|
||||
|
||||
return rpcServer
|
||||
|
||||
}
|
||||
|
||||
var closer jsonrpc.ClientCloser
|
||||
|
||||
func StartClient(id, port uint32, callback any) *struct {
|
||||
Kick func(uint32) error
|
||||
|
||||
RegisterLogic func(uint32, uint32) error
|
||||
} {
|
||||
//cool.Config.File.Domain = "127.0.0.1"
|
||||
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
|
||||
|
||||
closer1, err := jsonrpc.NewMergeClient(context.Background(),
|
||||
rpcaddr, "", []interface{}{
|
||||
&RPCClient,
|
||||
}, nil, jsonrpc.WithClientHandler("", callback),
|
||||
jsonrpc.WithReconnFun(func() { RPCClient.RegisterLogic(id, port) }),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
//if port != 0 { //注册logic
|
||||
defer RPCClient.RegisterLogic(id, port)
|
||||
|
||||
//}
|
||||
|
||||
closer = closer1
|
||||
|
||||
return &RPCClient
|
||||
}
|
||||
|
||||
// Setup RPCClient with reverse call handler
|
||||
var RPCClient struct {
|
||||
Kick func(uint32) error //踢人
|
||||
|
||||
RegisterLogic func(uint32, uint32) error
|
||||
|
||||
// UserLogin func(int32, int32) error //用户登录事件
|
||||
// UserLogout func(int32, int32) error //用户登出事件
|
||||
}
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"blazing/common/data/share"
|
||||
"blazing/cool"
|
||||
"blazing/cool/coolconfig"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
config "blazing/modules/config/service"
|
||||
|
||||
"github.com/filecoin-project/go-jsonrpc"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// Define the server handler
|
||||
type ServerHandler struct{}
|
||||
|
||||
const kickForwardTimeout = 3 * time.Second
|
||||
const ClientCallTimeout = 5 * time.Second
|
||||
|
||||
// A 服强关,留下僵尸在线状态:B 服可以通过 login 清理后登录。
|
||||
// login 服不可用:B 服不会放行,仍提示系统忙。
|
||||
func isDisconnectedLogicReverseClientError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
errText := err.Error()
|
||||
return strings.Contains(errText, "websocket routine exiting") ||
|
||||
strings.Contains(errText, "sendRequest failed") ||
|
||||
strings.Contains(errText, "closed out channel")
|
||||
}
|
||||
|
||||
// 实现踢人
|
||||
func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
|
||||
useid1, err := share.ShareManager.GetUserOnline(userid)
|
||||
if err != nil || useid1 == 0 {
|
||||
// 请求到达时用户已离线,直接视为成功
|
||||
return nil
|
||||
}
|
||||
|
||||
cl, ok := cool.GetClientOnly(useid1)
|
||||
if !ok || cl == nil {
|
||||
// 目标服务器不在线,清理僵尸在线标记并视为成功
|
||||
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||
cool.DeleteClientOnly(useid1)
|
||||
return nil
|
||||
}
|
||||
|
||||
resultCh := make(chan error, 1)
|
||||
go func() {
|
||||
resultCh <- cl.KickPerson(userid) // 实现指定服务器踢人
|
||||
}()
|
||||
|
||||
select {
|
||||
case callErr := <-resultCh:
|
||||
if callErr == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 调用失败后兜底:用户若已离线/切服/目标服不在线都算成功
|
||||
useid2, err2 := share.ShareManager.GetUserOnline(userid)
|
||||
if err2 != nil || useid2 == 0 || useid2 != useid1 {
|
||||
return nil
|
||||
}
|
||||
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
|
||||
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||
cool.DeleteClientOnly(useid2)
|
||||
return nil
|
||||
}
|
||||
if isDisconnectedLogicReverseClientError(callErr) {
|
||||
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||
cool.DeleteClientOnly(useid2)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 仍在线则返回失败,不按成功处理
|
||||
return callErr
|
||||
case <-time.After(kickForwardTimeout):
|
||||
// 仅防止无限等待;超时不算成功
|
||||
useid2, err2 := share.ShareManager.GetUserOnline(userid)
|
||||
if err2 != nil || useid2 == 0 || useid2 != useid1 {
|
||||
return nil
|
||||
}
|
||||
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
|
||||
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||
cool.DeleteClientOnly(useid2)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("kick timeout, user still online: uid=%d server=%d", userid, useid2)
|
||||
}
|
||||
}
|
||||
|
||||
// 注册logic服务器
|
||||
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error {
|
||||
fmt.Println("注册logic服务器", id, port)
|
||||
return registerReverseLogicClient(ctx, id, port)
|
||||
|
||||
}
|
||||
|
||||
func (*ServerHandler) MatchJoinOrUpdate(_ context.Context, payload PVPMatchJoinPayload) error {
|
||||
return DefaultPVPMatchCoordinator().JoinOrUpdate(payload)
|
||||
}
|
||||
|
||||
func (*ServerHandler) MatchCancel(_ context.Context, userID uint32) error {
|
||||
DefaultPVPMatchCoordinator().Cancel(userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CServer() *jsonrpc.RPCServer {
|
||||
// create a new server instance
|
||||
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClientSetup[cool.ClientHandler]("", setupLogicReverseClient))
|
||||
|
||||
rpcServer.Register("", &ServerHandler{})
|
||||
|
||||
return rpcServer
|
||||
|
||||
}
|
||||
|
||||
var closer jsonrpc.ClientCloser
|
||||
|
||||
func StartClient(id, port uint32, callback any) *struct {
|
||||
Kick func(context.Context, uint32) error
|
||||
|
||||
RegisterLogic func(context.Context, uint32, uint32) error
|
||||
|
||||
MatchJoinOrUpdate func(context.Context, PVPMatchJoinPayload) error
|
||||
|
||||
MatchCancel func(context.Context, uint32) error
|
||||
} {
|
||||
cool.Config.File.Domain = "127.0.0.1"
|
||||
u := url.URL{
|
||||
Scheme: "ws",
|
||||
Host: cool.Config.File.Domain + gconv.String(cool.Config.Address),
|
||||
Path: "/rpc",
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set("logic_id", gconv.String(id))
|
||||
q.Set("logic_port", gconv.String(port))
|
||||
u.RawQuery = q.Encode()
|
||||
rpcaddr := u.String()
|
||||
|
||||
closer1, err := jsonrpc.NewMergeClient(context.Background(),
|
||||
rpcaddr, "", []interface{}{
|
||||
&RPCClient,
|
||||
}, nil, jsonrpc.WithClientHandler("", callback),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
|
||||
closer = closer1
|
||||
|
||||
return &RPCClient
|
||||
}
|
||||
|
||||
// Setup RPCClient with reverse call handler
|
||||
var RPCClient struct {
|
||||
Kick func(context.Context, uint32) error //踢人
|
||||
|
||||
RegisterLogic func(context.Context, uint32, uint32) error
|
||||
|
||||
MatchJoinOrUpdate func(context.Context, PVPMatchJoinPayload) error
|
||||
|
||||
MatchCancel func(context.Context, uint32) error
|
||||
|
||||
// UserLogin func(int32, int32) error //用户登录事件
|
||||
// UserLogout func(int32, int32) error //用户登出事件
|
||||
}
|
||||
|
||||
func setupLogicReverseClient(ctx context.Context, revClient cool.ClientHandler) error {
|
||||
_ = revClient
|
||||
req, ok := jsonrpc.GetHTTPRequest(ctx)
|
||||
if !ok || req == nil {
|
||||
return fmt.Errorf("missing websocket request context")
|
||||
}
|
||||
|
||||
id := gconv.Uint32(req.URL.Query().Get("logic_id"))
|
||||
port := gconv.Uint32(req.URL.Query().Get("logic_port"))
|
||||
if id == 0 || port == 0 {
|
||||
return fmt.Errorf("missing logic identity in websocket query: id=%d port=%d", id, port)
|
||||
}
|
||||
|
||||
if err := registerReverseLogicClient(ctx, id, port); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := coolconfig.ComposeRuntimeID(id, port)
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
cool.DeleteClientOnly(key)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerReverseLogicClient(ctx context.Context, id, port uint32) error {
|
||||
//TODO 待修复滚动更新可能导致的玩家可以同时在旧服务器和新服务器同时在线的bug
|
||||
revClient, ok := jsonrpc.ExtractReverseClient[cool.ClientHandler](ctx)
|
||||
if !ok {
|
||||
return fmt.Errorf("no reverse client")
|
||||
}
|
||||
t := config.NewServerService().GetServerID(id)
|
||||
|
||||
aa, ok := cool.GetClient(t.OnlineID, t.Port)
|
||||
if ok && aa != nil { //如果已经存在且这个端口已经被存过
|
||||
aa.QuitSelf(0)
|
||||
}
|
||||
cool.AddClient(coolconfig.ComposeRuntimeID(id, port), &revClient)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
type RPCfight struct {
|
||||
fightmap *csmap.CsMap[int, common.FightI]
|
||||
fightmap *csmap.CsMap[int, common.FightControllerI]
|
||||
zs *zset.ZSet[uint32, *model.PVP]
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (r *RPCfight) cancel(pvp info.RPCFightinfo) {
|
||||
///定义map,存储用户对战斗容器的映射,便于外部传入时候进行直接操作
|
||||
|
||||
var fightmap = RPCfight{
|
||||
fightmap: csmap.New[int, common.FightI](),
|
||||
fightmap: csmap.New[int, common.FightControllerI](),
|
||||
zs: zset.New[uint32, *model.PVP](func(a, b *model.PVP) bool {
|
||||
return a.Less(b)
|
||||
}),
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
package socket
|
||||
|
||||
import (
|
||||
"blazing/common/socket/codec"
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/modules/config/service"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/modules/config/service"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/panjf2000/gnet/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
minPacketLen = 17
|
||||
maxPacketLen = 10 * 1024
|
||||
)
|
||||
|
||||
func (s *Server) Boot(serverid, port uint32) error {
|
||||
// go s.bootws()
|
||||
s.serverid = serverid
|
||||
@@ -53,62 +58,43 @@ func (s *Server) Stop() error {
|
||||
func (s *Server) OnClose(c gnet.Conn, err error) (action gnet.Action) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值
|
||||
// 1. 打印错误信息
|
||||
if t, ok := c.Context().(*player.ClientData); ok {
|
||||
if t.Player != nil {
|
||||
if t.Player.Info != nil {
|
||||
cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err)
|
||||
t.Player.Service.Info.Save(*t.Player.Info)
|
||||
go t.Player.SaveOnDisconnect()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, err)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
// 识别 RST 导致的连接中断(错误信息含 "connection reset")
|
||||
// if err != nil && (strings.Contains(err.Error(), "connection reset") || strings.Contains(err.Error(), "reset by peer")) {
|
||||
// remoteIP := c.RemoteAddr().(*net.TCPAddr).IP.String()
|
||||
|
||||
// log.Printf("RST 攻击检测: 来源 %s, 累计攻击次数 %d", remoteIP)
|
||||
|
||||
// // 防护逻辑:临时封禁异常 IP(可扩展为 IP 黑名单)
|
||||
// // go s.tempBlockIP(remoteIP, 5*time.Minute)
|
||||
// }
|
||||
//fmt.Println(err, c.RemoteAddr().String(), "断开连接")
|
||||
atomic.AddInt64(&cool.Connected, -1)
|
||||
|
||||
//logging.Infof("conn[%v] disconnected", c.RemoteAddr().String())
|
||||
v, _ := c.Context().(*player.ClientData)
|
||||
|
||||
v.LF.Close()
|
||||
// v.LF.Close()
|
||||
//close(v.MsgChan)
|
||||
if v.Player != nil {
|
||||
v.Player.Save() //保存玩家数据
|
||||
|
||||
if v != nil {
|
||||
v.Close()
|
||||
if v.Player != nil {
|
||||
v.Player.SaveOnDisconnect() //保存玩家数据
|
||||
}
|
||||
}
|
||||
|
||||
//}
|
||||
//关闭连接
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Server) OnTick() (delay time.Duration, action gnet.Action) {
|
||||
g.Log().Async().Info(context.Background(), gtime.Now().ISO8601(), "服务器ID", cool.Config.ServerInfo.OnlineID, "链接数", atomic.LoadInt64(&cool.Connected))
|
||||
if s.quit && atomic.LoadInt64(&cool.Connected) == 0 {
|
||||
//执行正常退出逻辑
|
||||
os.Exit(0)
|
||||
}
|
||||
return 30 * time.Second, gnet.None
|
||||
}
|
||||
|
||||
func (s *Server) OnBoot(eng gnet.Engine) gnet.Action {
|
||||
s.eng = eng
|
||||
|
||||
service.NewServerService().SetServerID(s.serverid, s.port) //设置当前服务器端口
|
||||
service.NewServerService().SetServerID(s.serverid, s.port)
|
||||
return gnet.None
|
||||
}
|
||||
|
||||
@@ -116,59 +102,68 @@ func (s *Server) OnOpen(conn gnet.Conn) (out []byte, action gnet.Action) {
|
||||
if s.network != "tcp" {
|
||||
return nil, gnet.Close
|
||||
}
|
||||
|
||||
if conn.Context() == nil {
|
||||
conn.SetContext(player.NewClientData(conn)) //注入data
|
||||
conn.SetContext(player.NewClientData(conn))
|
||||
}
|
||||
|
||||
atomic.AddInt64(&cool.Connected, 1)
|
||||
|
||||
return nil, gnet.None
|
||||
}
|
||||
|
||||
func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值
|
||||
// 1. 打印错误信息
|
||||
if err := recover(); err != nil {
|
||||
if t, ok := c.Context().(*player.ClientData); ok {
|
||||
if t.Player != nil {
|
||||
if t.Player.Info != nil {
|
||||
cool.Logger.Error(context.TODO(), "OnTraffic 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err)
|
||||
t.Player.Service.Info.Save(*t.Player.Info)
|
||||
|
||||
}
|
||||
|
||||
if t.Player != nil && t.Player.Info != nil {
|
||||
cool.Logger.Error(context.TODO(), "OnTraffic 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err)
|
||||
t.Player.Service.Info.Save(*t.Player.Info)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
|
||||
ws := c.Context().(*player.ClientData).Wsmsg
|
||||
if ws.Tcp { //升级失败时候防止缓冲区溢出
|
||||
return s.handleTCP(c)
|
||||
|
||||
client := c.Context().(*player.ClientData)
|
||||
if s.discorse && !client.IsCrossDomainChecked() {
|
||||
handled, ready, action := handle(c)
|
||||
if action != gnet.None {
|
||||
return action
|
||||
}
|
||||
if handled {
|
||||
client.MarkCrossDomainChecked()
|
||||
return gnet.None
|
||||
}
|
||||
if !ready {
|
||||
return gnet.None
|
||||
}
|
||||
client.MarkCrossDomainChecked()
|
||||
}
|
||||
|
||||
tt, len1 := ws.ReadBufferBytes(c)
|
||||
if tt == gnet.Close {
|
||||
ws := client.Wsmsg
|
||||
if ws.Tcp {
|
||||
return s.handleTCP(c)
|
||||
}
|
||||
|
||||
readAction, inboundLen := ws.ReadBufferBytes(c)
|
||||
if readAction == gnet.Close {
|
||||
return gnet.Close
|
||||
}
|
||||
|
||||
ok, action := ws.Upgrade(c)
|
||||
if action != gnet.None { //连接断开
|
||||
state, action := ws.Upgrade(c)
|
||||
if action != gnet.None {
|
||||
return action
|
||||
}
|
||||
if !ok { //升级失败,说明是tcp连接
|
||||
ws.Tcp = true
|
||||
|
||||
return s.handleTCP(c)
|
||||
|
||||
if state == player.UpgradeNeedMoreData {
|
||||
return gnet.None
|
||||
}
|
||||
if state == player.UpgradeUseTCP {
|
||||
return s.handleTCP(c)
|
||||
}
|
||||
|
||||
if inboundLen > 0 {
|
||||
if _, err := c.Discard(inboundLen); err != nil {
|
||||
return gnet.Close
|
||||
}
|
||||
ws.ResetInboundMirror()
|
||||
}
|
||||
// fmt.Println(ws.Buf.Bytes())
|
||||
c.Discard(len1)
|
||||
|
||||
messages, err := ws.Decode(c)
|
||||
if err != nil {
|
||||
@@ -179,91 +174,93 @@ func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
||||
}
|
||||
|
||||
for _, msg := range messages {
|
||||
|
||||
s.onevent(c, msg.Payload)
|
||||
//t.OnEvent(msg.Payload)
|
||||
if !s.onevent(c, msg.Payload) {
|
||||
return gnet.Close
|
||||
}
|
||||
}
|
||||
|
||||
return gnet.None
|
||||
}
|
||||
|
||||
const maxBodyLen = 10 * 1024 // 业务最大包体长度,按需调整
|
||||
func (s *Server) handleTCP(conn gnet.Conn) (action gnet.Action) {
|
||||
client := conn.Context().(*player.ClientData)
|
||||
if s.discorse && !client.IsCrossDomainChecked() {
|
||||
handled, ready, action := handle(conn)
|
||||
if action != gnet.None {
|
||||
return action
|
||||
}
|
||||
if !ready {
|
||||
return gnet.None
|
||||
}
|
||||
if handled {
|
||||
client.MarkCrossDomainChecked()
|
||||
return gnet.None
|
||||
}
|
||||
client.MarkCrossDomainChecked()
|
||||
}
|
||||
|
||||
conn.Context().(*player.ClientData).IsCrossDomain.Do(func() { //跨域检测
|
||||
handle(conn)
|
||||
})
|
||||
|
||||
// handle(c)
|
||||
// 先读取4字节的包长度
|
||||
lenBuf, err := conn.Peek(4)
|
||||
|
||||
body, err := s.codec.Decode(conn)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.ErrShortBuffer) {
|
||||
return
|
||||
if errors.Is(err, codec.ErrIncompletePacket) {
|
||||
return gnet.None
|
||||
}
|
||||
return gnet.Close
|
||||
}
|
||||
|
||||
bodyLen := binary.BigEndian.Uint32(lenBuf)
|
||||
|
||||
if bodyLen > maxBodyLen {
|
||||
if !s.onevent(conn, body) {
|
||||
return gnet.Close
|
||||
}
|
||||
|
||||
if conn.InboundBuffered() < int(bodyLen) {
|
||||
return
|
||||
}
|
||||
// 提取包体
|
||||
body, err := conn.Next(int(bodyLen))
|
||||
if err != nil {
|
||||
if errors.Is(err, io.ErrShortBuffer) {
|
||||
return
|
||||
}
|
||||
return gnet.Close
|
||||
}
|
||||
|
||||
s.onevent(conn, body)
|
||||
|
||||
if conn.InboundBuffered() > 0 {
|
||||
if err := conn.Wake(nil); err != nil { // wake up the connection manually to avoid missing the leftover data
|
||||
|
||||
if err := conn.Wake(nil); err != nil {
|
||||
return gnet.Close
|
||||
}
|
||||
}
|
||||
return action
|
||||
|
||||
}
|
||||
|
||||
// CROSS_DOMAIN 定义跨域策略文件内容
|
||||
const CROSS_DOMAIN = "<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\x00"
|
||||
|
||||
// TEXT 定义跨域请求的文本格式
|
||||
const TEXT = "<policy-file-request/>\x00"
|
||||
|
||||
func handle(c gnet.Conn) {
|
||||
func handle(c gnet.Conn) (handled bool, ready bool, action gnet.Action) {
|
||||
probeLen := c.InboundBuffered()
|
||||
if probeLen == 0 {
|
||||
return false, false, gnet.None
|
||||
}
|
||||
if probeLen > len(TEXT) {
|
||||
probeLen = len(TEXT)
|
||||
}
|
||||
|
||||
// 读取数据并检查是否为跨域请求
|
||||
data, err := c.Peek(len(TEXT))
|
||||
data, err := c.Peek(probeLen)
|
||||
if err != nil {
|
||||
log.Printf("Error reading cross-domain request: %v", err)
|
||||
return
|
||||
return false, false, gnet.Close
|
||||
}
|
||||
|
||||
if string(data) == TEXT { //判断是否是跨域请求
|
||||
//log.Printf("Received cross-domain request from %s", c.RemoteAddr())
|
||||
// 处理跨域请求
|
||||
c.Write([]byte(CROSS_DOMAIN))
|
||||
c.Discard(len(TEXT))
|
||||
|
||||
return
|
||||
if !bytes.Equal(data, []byte(TEXT[:probeLen])) {
|
||||
return false, true, gnet.None
|
||||
}
|
||||
|
||||
//return
|
||||
if probeLen < len(TEXT) {
|
||||
return false, false, gnet.None
|
||||
}
|
||||
if _, err := c.Write([]byte(CROSS_DOMAIN)); err != nil {
|
||||
return false, true, gnet.Close
|
||||
}
|
||||
if _, err := c.Discard(len(TEXT)); err != nil {
|
||||
return false, true, gnet.Close
|
||||
}
|
||||
return true, true, gnet.None
|
||||
}
|
||||
|
||||
func (s *Server) onevent(c gnet.Conn, v []byte) {
|
||||
func (s *Server) onevent(c gnet.Conn, v []byte) bool {
|
||||
if !isValidPacket(v) {
|
||||
return false
|
||||
}
|
||||
if t, ok := c.Context().(*player.ClientData); ok {
|
||||
t.PushEvent(v, s.workerPool.Submit)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isValidPacket(v []byte) bool {
|
||||
if len(v) < minPacketLen || len(v) > maxPacketLen {
|
||||
return false
|
||||
}
|
||||
return binary.BigEndian.Uint32(v[0:4]) == uint32(len(v))
|
||||
}
|
||||
|
||||
@@ -115,6 +115,10 @@ var ErrorCodes = enum.New[struct {
|
||||
ErrPokemonLevelTooLow ErrorCode `enum:"13007"`
|
||||
// 不能展示背包里的精灵!
|
||||
ErrCannotShowBagPokemon ErrorCode `enum:"13017"`
|
||||
// 基地展示精灵数量已达上限!
|
||||
ErrRoomShowPetLimit ErrorCode `enum:"13019"`
|
||||
// 该精灵不在仓库中,无法设为基地展示!
|
||||
ErrPetNotInWarehouse ErrorCode `enum:"13021"`
|
||||
// 你今天已经被吃掉过一回了,明天再来吧!
|
||||
ErrAlreadyEatenToday ErrorCode `enum:"17018"`
|
||||
// 该道具已经在使用中,无法重复使用。
|
||||
|
||||
@@ -344,7 +344,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
|
||||
}
|
||||
|
||||
func (c *client) setupRequestChan() chan clientRequest {
|
||||
requests := make(chan clientRequest)
|
||||
requests := make(chan clientRequest, 1024)
|
||||
|
||||
c.doRequest = func(ctx context.Context, cr clientRequest) (clientResponse, error) {
|
||||
select {
|
||||
|
||||
@@ -75,33 +75,53 @@ func WithTracer(l Tracer) ServerOption {
|
||||
}
|
||||
}
|
||||
|
||||
func buildReverseClient[RP any](c *ServerConfig, ctx context.Context, conn *wsConn, namespace string, onConnect func(context.Context, RP) error) (context.Context, error) {
|
||||
cl := client{
|
||||
namespace: namespace,
|
||||
paramEncoders: map[reflect.Type]ParamEncoder{},
|
||||
methodNameFormatter: c.methodNameFormatter,
|
||||
}
|
||||
|
||||
// todo test that everything is closing correctly
|
||||
cl.exiting = conn.exiting
|
||||
|
||||
requests := cl.setupRequestChan()
|
||||
conn.requests = requests
|
||||
|
||||
calls := new(RP)
|
||||
|
||||
err := cl.provide([]interface{}{
|
||||
calls,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("provide reverse client calls: %w", err)
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, jsonrpcReverseClient{reflect.TypeOf(calls).Elem()}, calls)
|
||||
if onConnect != nil {
|
||||
if err := onConnect(ctx, *calls); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// WithReverseClient will allow extracting reverse client on **WEBSOCKET** calls.
|
||||
// RP is a proxy-struct type, much like the one passed to NewClient.
|
||||
func WithReverseClient[RP any](namespace string) ServerOption {
|
||||
return func(c *ServerConfig) {
|
||||
c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) {
|
||||
cl := client{
|
||||
namespace: namespace,
|
||||
paramEncoders: map[reflect.Type]ParamEncoder{},
|
||||
methodNameFormatter: c.methodNameFormatter,
|
||||
}
|
||||
return buildReverseClient[RP](c, ctx, conn, namespace, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo test that everything is closing correctly
|
||||
cl.exiting = conn.exiting
|
||||
|
||||
requests := cl.setupRequestChan()
|
||||
conn.requests = requests
|
||||
|
||||
calls := new(RP)
|
||||
|
||||
err := cl.provide([]interface{}{
|
||||
calls,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("provide reverse client calls: %w", err)
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, jsonrpcReverseClient{reflect.TypeOf(calls).Elem()}, calls), nil
|
||||
// WithReverseClientSetup behaves like WithReverseClient, and also runs onConnect
|
||||
// once the reverse client has been created for the websocket connection.
|
||||
func WithReverseClientSetup[RP any](namespace string, onConnect func(context.Context, RP) error) ServerOption {
|
||||
return func(c *ServerConfig) {
|
||||
c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) {
|
||||
return buildReverseClient[RP](c, ctx, conn, namespace, onConnect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,16 @@ func GetConnectionType(ctx context.Context) ConnectionType {
|
||||
return ConnectionTypeUnknown
|
||||
}
|
||||
|
||||
// GetHTTPRequest returns the original HTTP request when the context comes from RPCServer.
|
||||
func GetHTTPRequest(ctx context.Context) (*http.Request, bool) {
|
||||
v := ctx.Value(httpRequestCtxKey{})
|
||||
if v == nil {
|
||||
return nil, false
|
||||
}
|
||||
req, ok := v.(*http.Request)
|
||||
return req, ok
|
||||
}
|
||||
|
||||
// RPCServer provides a jsonrpc 2.0 http server handler
|
||||
type RPCServer struct {
|
||||
*handler
|
||||
@@ -75,6 +85,8 @@ var upgrader = websocket.Upgrader{
|
||||
}
|
||||
|
||||
func (s *RPCServer) handleWS(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
ctx = context.WithValue(ctx, httpRequestCtxKey{}, r)
|
||||
|
||||
// TODO: allow setting
|
||||
// (note that we still are mostly covered by jwt tokens)
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
@@ -181,3 +193,5 @@ func (s *RPCServer) AliasMethod(alias, original string) {
|
||||
}
|
||||
|
||||
var _ error = &JSONRPCError{}
|
||||
|
||||
type httpRequestCtxKey struct{}
|
||||
|
||||
@@ -661,7 +661,9 @@ func (c *wsConn) tryReconnect(ctx context.Context) bool {
|
||||
c.writeLk.Unlock()
|
||||
|
||||
go c.nextMessage()
|
||||
c.reconfun()
|
||||
if c.reconfun != nil {
|
||||
go c.reconfun()
|
||||
}
|
||||
}()
|
||||
|
||||
return true
|
||||
|
||||
@@ -2,33 +2,4 @@ module github.com/zmexing/go-sensitive-word
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/quic-go/quic-go v0.40.1 // indirect
|
||||
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
require github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||
|
||||
@@ -1,55 +1,2 @@
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/imroc/req/v3 v3.42.3 h1:ryPG2AiwouutAopwPxKpWKyxgvO8fB3hts4JXlh3PaE=
|
||||
github.com/imroc/req/v3 v3.42.3/go.mod h1:Axz9Y/a2b++w5/Jht3IhQsdBzrG1ftJd1OJhu21bB2Q=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
|
||||
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
||||
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
183
docs/boss-script-hookaction-guide-2026-04-05.md
Normal file
183
docs/boss-script-hookaction-guide-2026-04-05.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# Boss Script(HookAction)接入说明
|
||||
|
||||
日期:2026-04-05
|
||||
|
||||
## 1. 执行流程
|
||||
|
||||
1. 先执行战斗效果链 `HookAction()`
|
||||
2. 执行脚本 `hookAction(hookaction)`
|
||||
3. 用脚本返回值决定是否继续出手
|
||||
4. 脚本可直接调用 Go 绑定函数:`useSkill()`、`switchPet()`
|
||||
|
||||
## 2. JS 可调用的 Go 函数
|
||||
|
||||
1. `useSkill(skillId: number)`
|
||||
2. `switchPet(catchTime: number)`
|
||||
|
||||
## 3. `hookaction` 参数字段
|
||||
|
||||
基础字段:
|
||||
|
||||
1. `hookaction.hookaction: boolean`
|
||||
2. `hookaction.round: number`
|
||||
3. `hookaction.is_first: boolean`
|
||||
4. `hookaction.our: { pet_id, catch_time, hp, max_hp } | null`
|
||||
5. `hookaction.opp: { pet_id, catch_time, hp, max_hp } | null`
|
||||
6. `hookaction.skills: Array<{ skill_id, pp, can_use }>`
|
||||
|
||||
AttackValue 映射字段(重点):
|
||||
|
||||
1. `hookaction.our_attack`
|
||||
2. `hookaction.opp_attack`
|
||||
|
||||
结构:
|
||||
|
||||
```ts
|
||||
{
|
||||
skill_id: number;
|
||||
attack_time: number;
|
||||
is_critical: number;
|
||||
lost_hp: number;
|
||||
gain_hp: number;
|
||||
remain_hp: number;
|
||||
max_hp: number;
|
||||
state: number;
|
||||
offensive: number;
|
||||
status: number[]; // 对应 AttackValue.Status[20]
|
||||
prop: number[]; // 对应 AttackValue.Prop[6]
|
||||
}
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `prop` 索引:`[攻, 防, 特攻, 特防, 速度, 命中]`
|
||||
- 对应值 `> 0` 代表强化,`< 0` 代表下降,`0` 代表无变化
|
||||
|
||||
返回值:
|
||||
|
||||
- `true`:继续行动
|
||||
- `false`:阻止行动
|
||||
- 不返回:默认回退到 `hookaction.hookaction`
|
||||
|
||||
## 4. 脚本示例
|
||||
|
||||
### 4.1 判断对方是否存在强化(你问的这个)
|
||||
|
||||
```js
|
||||
function hookAction(hookaction) {
|
||||
if (!hookaction.hookaction) return false;
|
||||
|
||||
var oppAtk = hookaction.opp_attack;
|
||||
var oppHasBuff = false;
|
||||
if (oppAtk && oppAtk.prop) {
|
||||
for (var i = 0; i < oppAtk.prop.length; i++) {
|
||||
if (oppAtk.prop[i] > 0) {
|
||||
oppHasBuff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oppHasBuff) {
|
||||
// 对方有强化时,放一个针对技能
|
||||
useSkill(5001);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 判断对方是否有异常状态
|
||||
|
||||
```js
|
||||
function hookAction(hookaction) {
|
||||
if (!hookaction.hookaction) return false;
|
||||
|
||||
var oppAtk = hookaction.opp_attack;
|
||||
var hasStatus = false;
|
||||
if (oppAtk && oppAtk.status) {
|
||||
for (var i = 0; i < oppAtk.status.length; i++) {
|
||||
if (oppAtk.status[i] > 0) {
|
||||
hasStatus = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasStatus) {
|
||||
// 没有异常时尝试上异常
|
||||
useSkill(6002);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Monaco 类型提示
|
||||
|
||||
```ts
|
||||
import * as monaco from "monaco-editor";
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
|
||||
allowNonTsExtensions: true,
|
||||
checkJs: true,
|
||||
target: monaco.languages.typescript.ScriptTarget.ES2020,
|
||||
});
|
||||
|
||||
monaco.languages.typescript.javascriptDefaults.addExtraLib(
|
||||
`
|
||||
interface BossHookPetContext {
|
||||
pet_id: number;
|
||||
catch_time: number;
|
||||
hp: number;
|
||||
max_hp: number;
|
||||
}
|
||||
|
||||
interface BossHookSkillContext {
|
||||
skill_id: number;
|
||||
pp: number;
|
||||
can_use: boolean;
|
||||
}
|
||||
|
||||
interface BossHookAttackContext {
|
||||
skill_id: number;
|
||||
attack_time: number;
|
||||
is_critical: number;
|
||||
lost_hp: number;
|
||||
gain_hp: number;
|
||||
remain_hp: number;
|
||||
max_hp: number;
|
||||
state: number;
|
||||
offensive: number;
|
||||
status: number[];
|
||||
prop: number[];
|
||||
}
|
||||
|
||||
interface BossHookActionContext {
|
||||
hookaction: boolean;
|
||||
round: number;
|
||||
is_first: boolean;
|
||||
our: BossHookPetContext | null;
|
||||
opp: BossHookPetContext | null;
|
||||
skills: BossHookSkillContext[];
|
||||
our_attack: BossHookAttackContext | null;
|
||||
opp_attack: BossHookAttackContext | null;
|
||||
}
|
||||
|
||||
declare function hookAction(hookaction: BossHookActionContext): boolean;
|
||||
declare function HookAction(hookaction: BossHookActionContext): boolean;
|
||||
declare function hookaction(hookaction: BossHookActionContext): boolean;
|
||||
|
||||
declare function useSkill(skillId: number): void;
|
||||
declare function switchPet(catchTime: number): void;
|
||||
`,
|
||||
"ts:boss-script.d.ts"
|
||||
);
|
||||
```
|
||||
|
||||
## 6. 后端代码
|
||||
|
||||
- 脚本执行器与函数绑定:`modules/config/model/boss_pet.go`
|
||||
- AI 出手转发与上下文构建:`logic/service/fight/input/ai.go`
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
### 1.2 本清单目标
|
||||
|
||||
- 在不破坏现有 `1v1` 的前提下,落地组队战斗可运行版本(MVP)。
|
||||
- 对齐 `flash` 组队战斗关键协议与关键行为(开战、出招、切宠、道具、结算、战斗结束)。
|
||||
- 留出兼容层,允许旧 `24xx/25xx` 流程继续使用。
|
||||
- 对齐 `flash`/社区实现中的关键行为(开战、出招、切宠、道具、结算、战斗结束)。
|
||||
- 协议层采用“一个统一结构体 + phase 字段”方案,单打/双打共用同一序列化模型。
|
||||
- 保留旧 `24xx/25xx` 流程入口,通过服务端适配映射到统一结构体。
|
||||
|
||||
### 1.3 非目标
|
||||
|
||||
@@ -49,74 +50,58 @@
|
||||
- 回合主链仍以双动作兼容流程为中心
|
||||
- 组队相关特性存在 TODO(例如 `501/502/503`)
|
||||
|
||||
### 2.3 外部实现参考(本次新增)
|
||||
|
||||
- `arcadia-star/seer2-fight-ui`
|
||||
- 双打核心模型不是独立命令集,而是统一帧模型 + `uiStyle + side + position`。
|
||||
- `uiStyle` 支持 `2v2/2v1`,战位通过 `position(main/sub)` 区分。
|
||||
- `arcadia-star/seer2-next-message/src/entity/fight.rs`
|
||||
- 采用统一战斗实体结构:`team/user/pet` + `side/position`。
|
||||
- 行为包拆分为 `Load/Hurt/Change/Escape/...`,但底层字段模型统一。
|
||||
- `ukuq/seer2-server/src/seer2/fight`
|
||||
- `ArenaResourceLoadCMD -> TeamInfo -> FightUserInfo -> FighterInfo` 为层级化统一结构。
|
||||
- `FighterInfo` 直接包含 `position/hp/maxHp/anger/skills`,适合直接映射为本项目统一结构体。
|
||||
|
||||
---
|
||||
|
||||
## 3. 协议对齐清单(按优先级)
|
||||
|
||||
> 说明:命令号来源于 `flash` 的 `4c07fa07:CommandID.as`。
|
||||
> 实施时可先做“服务端可跑+字段兼容”,逐步补全所有字段。
|
||||
> 说明:本清单改为“统一协议结构体”路线,不再强制先实现 `75xx` 独立命令族。
|
||||
> 推荐做法:保留旧入口命令,服务端内部统一转为 `FightActionEnvelope/FightStateEnvelope`。
|
||||
|
||||
### 3.1 P0 必做(MVP 必须)
|
||||
|
||||
- [ ] `7555 GROUP_READY_TO_FIGHT`
|
||||
- 用途:组队成员准备
|
||||
- 入参建议:`room/groupId + userId + actorSlotsReady`
|
||||
- 出参建议:广播 ready 状态变化
|
||||
|
||||
- [ ] `7557 GROUP_START_FIGHT`
|
||||
- 用途:下发组队开战结构
|
||||
- 参考客户端结构:
|
||||
- `isVsNPC`
|
||||
- `groupOneMembCnt + groupOneList[]`
|
||||
- `groupTwoMembCnt + groupTwoList[]`
|
||||
- `group list` 每项至少要有:`side/pos/userID/petID/catchTime/hp/maxHP/level/catchType`
|
||||
|
||||
- [ ] `7558 GROUP_FT_USE_SKILL`
|
||||
- 用途:组队技能指令
|
||||
- 参考客户端 payload:`skillId(uint32) + side(byte) + pos(byte)`(6字节)
|
||||
- 服务端内部映射:`actorIndex + targetIndex`
|
||||
|
||||
- [ ] `7563 GROUP_FT_CHG_PET`
|
||||
- 用途:组队切宠请求
|
||||
- 至少支持:按操作者位切宠
|
||||
|
||||
- [ ] `7562 GROUP_FT_USE_ITEM`
|
||||
- 用途:组队用道具
|
||||
- 至少支持:按 actor/target 生效
|
||||
|
||||
- [ ] `7560 GROUP_FT_OVER`
|
||||
- 用途:组队战斗结束
|
||||
- 至少下发:结束原因、胜方标识、结算主体
|
||||
- [ ] 统一入站动作结构 `FightActionEnvelope`
|
||||
- 最少字段:`actionType/actorIndex/targetIndex/skillId/itemId/catchTime/escape/chat`
|
||||
- 兼容映射:
|
||||
- `2405 -> actionType=skill`
|
||||
- `2406 -> actionType=item`
|
||||
- `2407 -> actionType=change`
|
||||
- `2410 -> actionType=escape`
|
||||
- [ ] 统一出站状态结构 `FightStateEnvelope`
|
||||
- 最少字段:
|
||||
- `phase`(`start/skill_hurt/change/over/load/chat`)
|
||||
- `left[]/right[]`(元素为统一 `FighterState`)
|
||||
- `meta`(回合号、天气、胜负、结束原因)
|
||||
- [ ] 统一战位子结构 `FighterState`
|
||||
- 每项至少包含:`side/position(userSlot)/userId/petId(catchTime)/hp/maxHp/level/anger/status/prop/skills`
|
||||
|
||||
### 3.2 P1 强烈建议(提升一致性)
|
||||
|
||||
- [ ] `7559 GROUP_FT_SKILL_HURT`
|
||||
- 对齐技能伤害播报(攻击方+受击方快照)
|
||||
- 参考结构包含:状态数组、能力等级、技能、HP 变化、暴击、伤害值
|
||||
|
||||
- [ ] `7567 GROUP_FT_CHG_PET_SUC`
|
||||
- 切宠成功广播,保障前端战位同步
|
||||
|
||||
- [ ] `7568 GROUP_FT_ESCAPE_SUC`
|
||||
- 逃跑成功广播(含退出战位)
|
||||
|
||||
- [ ] `7570 GROUP_CHAT_MSG`
|
||||
- 组队战斗聊天转发
|
||||
|
||||
- [ ] `7571/7572 GROUP_LOAD_PERCENT(_NOTICE)`
|
||||
- 组队加载进度同步
|
||||
- [ ] 完善 `phase=skill_hurt`
|
||||
- 至少带:施法方快照、受击方快照、技能、暴击、伤害、HP 变更
|
||||
- [ ] 完善 `phase=change`
|
||||
- 至少带:切宠发起位、切入目标位、新精灵状态
|
||||
- [ ] 完善 `phase=over`
|
||||
- 至少带:结束原因、胜方、收益主体
|
||||
- [ ] 完善 `phase=load/chat`
|
||||
- 组队加载进度、战斗内聊天统一走同一 envelope
|
||||
|
||||
### 3.3 P2 视时间补齐
|
||||
|
||||
- [ ] `7561 GROUP_FT_SPRITE_DIE`
|
||||
- [ ] `7573 GROUP_FT_SPRITE_NOTICE`
|
||||
- [ ] `7574 GROUP_FT_WIN_CLOSE`
|
||||
- [ ] `7575 GP_SKILL_WAIT_PERT`
|
||||
- [ ] `7576 GP_SKILL_WAIT_NOTICE`
|
||||
- [ ] `7585 GROUP_FT_OVERTIME_NOTICE`
|
||||
- [ ] `7586 GROUP_FT_SKILL_PLAY_OVER`
|
||||
- [ ] `7587 GROUP_FT_TIME_OUT_EXIT`
|
||||
- [ ] `7588 GROUP_FT_RELATION_NOTICE`
|
||||
- [ ] `phase=sprite_die/sprite_notice/win_close`
|
||||
- [ ] `phase=skill_wait/skill_wait_notice`
|
||||
- [ ] `phase=overtime/timeout_exit/relation_notice`
|
||||
|
||||
---
|
||||
|
||||
@@ -124,13 +109,13 @@
|
||||
|
||||
## 4.1 协议与结构层(Owner A)
|
||||
|
||||
- [ ] 新增组队协议结构文件
|
||||
- 建议新建:`logic/service/fight/cmd_group.go`
|
||||
- 要求:所有组队命令入站结构都有 `TomeeHeader cmd:"xxxx"`
|
||||
- [ ] 新增统一协议结构文件
|
||||
- 建议新建:`logic/service/fight/cmd_unified.go`
|
||||
- 要求:统一定义 `FightActionEnvelope` 和映射辅助结构
|
||||
|
||||
- [ ] 新增组队出站结构
|
||||
- 建议新建:`logic/service/fight/info/group_info.go`
|
||||
- 要求:明确 `side/pos/user/pet/hp/maxHp/level` 等核心字段
|
||||
- [ ] 新增统一出站结构
|
||||
- 建议新建:`logic/service/fight/info/unified_info.go`
|
||||
- 要求:定义 `FightStateEnvelope/FighterState`,支持单打与双打
|
||||
|
||||
- [ ] 统一战位字段命名规范
|
||||
- `actorIndex`:我方执行位
|
||||
@@ -139,26 +124,20 @@
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] `controller.Init(true)` 能注册全部新命令,无重复 cmd panic。
|
||||
- [ ] 对每个新 cmd,可反序列化入参并进入控制器方法。
|
||||
- [ ] 旧 cmd(`2405/2406/2407/2410`)可无损映射到统一入站结构。
|
||||
- [ ] 统一出站结构在 `start/skill_hurt/change/over` phase 均可序列化。
|
||||
|
||||
---
|
||||
|
||||
## 4.2 控制器与路由层(Owner B)
|
||||
|
||||
- [ ] 新增组队战斗控制器
|
||||
- 建议新建:`logic/controller/fight_group.go`
|
||||
- 包含:
|
||||
- 组队准备
|
||||
- 组队出招
|
||||
- 组队切宠
|
||||
- 组队道具
|
||||
- 组队逃跑
|
||||
- 组队聊天
|
||||
- [ ] 新增统一动作入口(可单文件)
|
||||
- 建议新建:`logic/controller/fight_unified.go`
|
||||
- 用途:将旧包和未来扩展包统一落到 `FightActionEnvelope`
|
||||
|
||||
- [ ] 兼容旧协议入口
|
||||
- `2405/2406/2407` 保持可用(默认 `actorIndex=0,targetIndex=0`)
|
||||
- 新 `75xx` 走组队专用入口
|
||||
- 组队场景由 `actorIndex/targetIndex` 与战斗上下文决定,不再依赖独立 `75xx`
|
||||
|
||||
- [ ] 增加战前校验
|
||||
- 成员是否在同一组队房间
|
||||
@@ -167,7 +146,7 @@
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 双端同时发送 `7558` 能转化为 `UseSkillAt(...)`。
|
||||
- [ ] 任意技能动作都能转化为 `UseSkillAt(...)`(含 `actorIndex/targetIndex`)。
|
||||
- [ ] 非法战位命令被拒绝,不影响其他战位。
|
||||
|
||||
---
|
||||
@@ -215,8 +194,8 @@
|
||||
- 战斗结束广播
|
||||
|
||||
- [ ] 保留旧包兼容(必要时双发)
|
||||
- 组队战斗对新包
|
||||
- 非组队仍可走旧 `2503/2505/2506`
|
||||
- 单打/双打统一走同一结构体
|
||||
- 如前端未升级,可按需保留旧 `2503/2505/2506` 过渡映射
|
||||
|
||||
验收:
|
||||
|
||||
@@ -276,14 +255,14 @@
|
||||
|
||||
- 协议/结构:
|
||||
- `logic/service/fight/cmd.go`
|
||||
- `logic/service/fight/cmd_group.go`(新增)
|
||||
- `logic/service/fight/cmd_unified.go`(新增)
|
||||
- `logic/service/fight/info/info.go`
|
||||
- `logic/service/fight/info/group_info.go`(新增)
|
||||
- `logic/service/fight/info/unified_info.go`(新增)
|
||||
|
||||
- 控制器:
|
||||
- `logic/controller/fight_base.go`
|
||||
- `logic/controller/fight_pvp_withplayer.go`
|
||||
- `logic/controller/fight_group.go`(新增)
|
||||
- `logic/controller/fight_unified.go`(新增)
|
||||
|
||||
- 核心流程:
|
||||
- `logic/service/fight/new.go`
|
||||
@@ -309,8 +288,8 @@
|
||||
|
||||
### M1(协议可通)
|
||||
|
||||
- [ ] 75xx 关键命令可收发
|
||||
- [ ] 控制器能转发到 `FightC` indexed 接口
|
||||
- [ ] 统一结构体可完成 `start/skill_hurt/change/over` 四类下发
|
||||
- [ ] 旧命令入口均可映射到 `FightC` indexed 接口
|
||||
|
||||
### M2(核心可跑)
|
||||
|
||||
@@ -339,7 +318,7 @@
|
||||
缓解:把“回合开始/结束”从 pair 执行中抽离,确保每回合只触发一次。
|
||||
|
||||
- 风险:协议切换导致旧客户端不可用。
|
||||
缓解:保留旧包并按战斗类型分流;必要时过渡期双发。
|
||||
缓解:服务端保持旧入口不变,先做“旧包 -> 统一结构”映射;前端按版本切流。
|
||||
|
||||
- 风险:effect 批量改动引发回归。
|
||||
缓解:先做组队关键 effect,其他 effect 分批迁移并每批回归。
|
||||
@@ -363,3 +342,59 @@
|
||||
- 如改动协议字段,必须附抓包样例或字段注释,不允许只改代码不补说明。
|
||||
- 如发现与本清单冲突的历史逻辑,以“兼容线上行为优先”,并在文档记录偏差原因。
|
||||
|
||||
---
|
||||
|
||||
## 10. 可实现性结论(统一协议结构体)
|
||||
|
||||
- 结论:可实现,且风险可控。
|
||||
- 依据:
|
||||
- `seer2-fight-ui` 的双打模型本质是统一数据结构 + `uiStyle/side/position`,不是强依赖独立命令族。
|
||||
- `seer2-next-message` 与 `seer2-server` 都采用统一 `team/user/pet` 层级结构,`position` 作为战位核心字段。
|
||||
- 本仓库已具备 `actorIndex/targetIndex` 与 `UseSkillAt/ChangePetAt/UseItemAt` 能力,协议统一后只需补齐映射和广播。
|
||||
- 实施建议:
|
||||
- 先完成“旧入口 -> 统一入站结构”映射。
|
||||
- 再完成“统一出站结构 + phase 广播”。
|
||||
- 最后做前端切换与旧包退场(或长期双通道兼容)。
|
||||
|
||||
---
|
||||
|
||||
## AtkType 目标语义补充(2026-04-05)
|
||||
|
||||
来源:`flash` 端 `SkillXMLInfo.getGpFtSkillType(skillID)`,读取 `movesMap/moveStoneMap` 的 `AtkType`。
|
||||
|
||||
GBTL 规则(已确认):
|
||||
|
||||
1. `AtkNum`:本技能同时攻击数量,默认 `1`(不能为 `0`)
|
||||
2. `AtkType`:目标范围
|
||||
- `0`:所有人
|
||||
- `1`:仅己方
|
||||
- `2`:仅对方
|
||||
- `3`:仅自己
|
||||
- 默认:`2`
|
||||
|
||||
前端目标选择行为(`SkillMouseController.attack(skillID, attackType)`):
|
||||
|
||||
1. `attackType=0` -> `allPetWinList`(全体可选)
|
||||
2. `attackType=1` -> `membPetWinList`(己方可选,含自己与队友)
|
||||
3. `attackType=2` -> `oppPetWinList`(敌方可选)
|
||||
4. `attackType=3` -> `[playerMode.petWin]`(仅自己)
|
||||
|
||||
后端目标关系判定(组队/多战位必须遵循):
|
||||
|
||||
1. 若协议传 `actor + target(side,pos)`:
|
||||
- `target.side != actor.side` => 对方目标
|
||||
- `target.side == actor.side && target.pos == actor.pos` => 自身目标
|
||||
- `target.side == actor.side && target.pos != actor.pos` => 队友目标
|
||||
2. 若协议未显式传目标(旧 `2405`):
|
||||
- 用 `AtkType` 兜底:
|
||||
- `AtkType=3` => 强制自身
|
||||
- `AtkType=1` => 默认自身(无显式队友位时)
|
||||
- 其他 => 维持旧行为(默认对方 `0` 位)
|
||||
|
||||
实施要求(与现有清单并行):
|
||||
|
||||
1. `common/data/xmlres/skill.go` 的 `Move` 需包含 `AtkType` 字段解析。
|
||||
2. 动作目标不再依赖“默认 Opp 绑定”;effect 上下文必须使用“本次动作的实际目标”。
|
||||
3. 需支持区分 `self` 与 `ally`(例如同为 `AtkType=1` 时,不能混用同一默认目标)。
|
||||
4. 保持旧协议兼容:旧入口不报错,但按上述兜底规则执行。
|
||||
|
||||
|
||||
194
docs/fight-input-controller-binding.md
Normal file
194
docs/fight-input-controller-binding.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Fight Input 控制绑定说明
|
||||
|
||||
日期:2026-04-04
|
||||
|
||||
## 1. 背景
|
||||
|
||||
当前战斗模型中,一个 `Input` 对应一个战斗站位(`actorIndex`)。
|
||||
每个 `Input` 通过 `Input.Player` 绑定操作者。
|
||||
|
||||
当前建战主路径已收敛为:`WithFightInputs(ourInputs, oppInputs)`。
|
||||
即:先由调用方创建并组装双方 `Input`,再传给战斗模块。
|
||||
|
||||
为了同时支持以下两种玩法,新增了可配置绑定策略:
|
||||
|
||||
1. 双打:一个玩家控制多个站位(单人多 `Input`)
|
||||
2. 组队:一个玩家控制一个站位(每人一个 `Input`)
|
||||
|
||||
## 2. 绑定策略
|
||||
|
||||
文件:`logic/service/fight/new_options.go`
|
||||
|
||||
- `InputControllerBindingKeep`
|
||||
- 含义:保持输入中已有 `Input.Player` 绑定,不覆盖
|
||||
- 适用:调用方已手动构造 `Input` 绑定
|
||||
|
||||
- `InputControllerBindingSingle`
|
||||
- 含义:单侧全部站位统一绑定为 `players[0]`
|
||||
- 适用:双打中一个人控制多个站位
|
||||
|
||||
- `InputControllerBindingPerSlot`
|
||||
- 含义:按站位顺序绑定为 `players[i]`
|
||||
- 适用:组队中一人一个站位
|
||||
- 说明:当 `players` 数量不足时,回退绑定 `players[0]`
|
||||
|
||||
## 3. 选项接口
|
||||
|
||||
文件:`logic/service/fight/new_options.go`
|
||||
|
||||
新增选项:
|
||||
|
||||
```go
|
||||
WithInputControllerBinding(mode int)
|
||||
```
|
||||
|
||||
## 4. 生效时机
|
||||
|
||||
文件:`logic/service/fight/new.go`
|
||||
|
||||
在 `buildFight` 中,构建完 `Our/Opp` 输入后,先执行控制绑定,再执行上下文绑定:
|
||||
|
||||
1. `bindInputControllers(f.Our, f.OurPlayers, opts.controllerBinding)`
|
||||
2. `bindInputControllers(f.Opp, f.OppPlayers, opts.controllerBinding)`
|
||||
3. `bindInputFightContext(...)`
|
||||
4. `linkTeamViews()`
|
||||
5. `linkOppInputs()`
|
||||
|
||||
## 5. 使用示例
|
||||
|
||||
### 5.1 双打(单人控多站位)
|
||||
|
||||
```go
|
||||
fight.NewFightWithOptions(
|
||||
fight.WithFightPlayersOnSide(
|
||||
[]common.PlayerI{ourPlayer},
|
||||
[]common.PlayerI{oppPlayer},
|
||||
),
|
||||
fight.WithFightInputs(ourInputs, oppInputs),
|
||||
fight.WithInputControllerBinding(fight.InputControllerBindingSingle),
|
||||
)
|
||||
```
|
||||
|
||||
### 5.2 组队(一人一个站位)
|
||||
|
||||
```go
|
||||
fight.NewFightWithOptions(
|
||||
fight.WithFightPlayersOnSide(
|
||||
[]common.PlayerI{ourP1, ourP2},
|
||||
[]common.PlayerI{oppP1, oppP2},
|
||||
),
|
||||
fight.WithFightInputs(ourInputs, oppInputs),
|
||||
fight.WithInputControllerBinding(fight.InputControllerBindingPerSlot),
|
||||
)
|
||||
```
|
||||
|
||||
### 5.3 仅传已绑定 Input(推荐灵活接入)
|
||||
|
||||
```go
|
||||
ourInputs := []*input.Input{
|
||||
input.NewInput(nil, ourP1), // 站位0
|
||||
input.NewInput(nil, ourP2), // 站位1
|
||||
}
|
||||
oppInputs := []*input.Input{
|
||||
input.NewInput(nil, oppP1), // 站位0
|
||||
input.NewInput(nil, oppP2), // 站位1
|
||||
}
|
||||
|
||||
fc, err := fight.NewFightWithOptions(
|
||||
fight.WithFightInputs(ourInputs, oppInputs),
|
||||
// 不传 WithFightPlayersOnSide 也可
|
||||
// owner/opponent 与 side players 会从 inputs 自动提取
|
||||
)
|
||||
_ = fc
|
||||
_ = err
|
||||
```
|
||||
|
||||
说明:`InputControllerBindingSingle/PerSlot` 会覆盖 `ourInputs/oppInputs` 中原有的 `Input.Player` 绑定;`Keep` 不覆盖。
|
||||
|
||||
## 6. 新模式绑定实例(逐模式)
|
||||
|
||||
以下示例假设我方有两个站位:`ourInputs[0]`、`ourInputs[1]`。
|
||||
|
||||
### 6.1 Keep(保持输入原绑定)
|
||||
|
||||
调用:
|
||||
|
||||
```go
|
||||
fight.NewFightWithOptions(
|
||||
fight.WithFightInputs(ourInputs, oppInputs),
|
||||
fight.WithInputControllerBinding(fight.InputControllerBindingKeep),
|
||||
)
|
||||
```
|
||||
|
||||
输入(调用前):
|
||||
|
||||
- `ourInputs[0].Player = ourP1`
|
||||
- `ourInputs[1].Player = ourP2`
|
||||
|
||||
结果(调用后):
|
||||
|
||||
- `ourInputs[0].Player = ourP1`
|
||||
- `ourInputs[1].Player = ourP2`
|
||||
|
||||
适用:调用方已提前把每个站位绑定好,不希望框架覆盖。
|
||||
|
||||
### 6.2 Single(单人控制全部站位)
|
||||
|
||||
调用:
|
||||
|
||||
```go
|
||||
fight.NewFightWithOptions(
|
||||
fight.WithFightPlayersOnSide(
|
||||
[]common.PlayerI{ourCaptain},
|
||||
[]common.PlayerI{oppCaptain},
|
||||
),
|
||||
fight.WithFightInputs(ourInputs, oppInputs),
|
||||
fight.WithInputControllerBinding(fight.InputControllerBindingSingle),
|
||||
)
|
||||
```
|
||||
|
||||
输入(调用前):
|
||||
|
||||
- `ourInputs[0].Player = ourP1`
|
||||
- `ourInputs[1].Player = ourP2`
|
||||
|
||||
结果(调用后):
|
||||
|
||||
- `ourInputs[0].Player = ourCaptain`
|
||||
- `ourInputs[1].Player = ourCaptain`
|
||||
|
||||
适用:双打或多站位由同一玩家操作。
|
||||
|
||||
### 6.3 PerSlot(按站位顺序绑定玩家)
|
||||
|
||||
调用:
|
||||
|
||||
```go
|
||||
fight.NewFightWithOptions(
|
||||
fight.WithFightPlayersOnSide(
|
||||
[]common.PlayerI{ourP1, ourP2},
|
||||
[]common.PlayerI{oppP1, oppP2},
|
||||
),
|
||||
fight.WithFightInputs(ourInputs, oppInputs),
|
||||
fight.WithInputControllerBinding(fight.InputControllerBindingPerSlot),
|
||||
)
|
||||
```
|
||||
|
||||
输入(调用前):
|
||||
|
||||
- `ourInputs[0].Player = anyA`
|
||||
- `ourInputs[1].Player = anyB`
|
||||
|
||||
结果(调用后):
|
||||
|
||||
- `ourInputs[0].Player = ourP1`
|
||||
- `ourInputs[1].Player = ourP2`
|
||||
|
||||
补位规则:若 `players` 数量不足(例如只传一个 `ourP1`),剩余站位回退绑定 `players[0]`。
|
||||
|
||||
## 7. 注意事项
|
||||
|
||||
1. 默认模式是 `InputControllerBindingKeep`,不影响现有调用。
|
||||
2. 若传入 `WithFightInputs(...)` 且每个 `Input.Player` 已预先绑定,可继续用默认模式。
|
||||
3. 仅传 `WithFightInputs(...)` 也可工作:框架会从 `ourInputs/oppInputs` 自动提取 `ourPlayers/oppPlayers`,并以各侧首位玩家作为 owner/opponent。
|
||||
4. 推荐在新组队逻辑中显式传 `WithInputControllerBinding(...)`,避免调用方歧义。
|
||||
81
docs/peak-tianxuan-current-design-2026-04-25.md
Normal file
81
docs/peak-tianxuan-current-design-2026-04-25.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 巅峰之战天选池当前逻辑说明
|
||||
|
||||
更新时间:2026-04-26
|
||||
|
||||
## 1. 入口职责
|
||||
|
||||
- `point_1`:巅峰之战主页面,展示当前玩家已配置的天选精灵。
|
||||
- `point_2`:天选池投票页面,只保存玩家本周投票。
|
||||
- `point_3`:天选精灵配置页面,配置玩家自己的天选精灵属性。
|
||||
|
||||
## 2. 投票数据
|
||||
|
||||
投票表是 `config_peak_tianxuan_vote`。
|
||||
|
||||
字段口径:
|
||||
|
||||
- `week_index`:日期周,格式为 ISO 年周,例如 `202617`。
|
||||
- `player_id`:投票玩家。
|
||||
- `pet_id`:投票精灵。
|
||||
|
||||
约束口径:
|
||||
|
||||
- 每个玩家每个日期周只能投 1 只精灵。
|
||||
- 同周重复投票时更新原记录。
|
||||
- 票数统计按 `week_index + pet_id` 聚合。
|
||||
|
||||
## 3. 投票池
|
||||
|
||||
`point_2` 的候选池来自乱斗精灵池 `config_boss_melee`,按 `mon_id` 去重。
|
||||
|
||||
当前不再因为上周出现过就从候选池移除。上周投票结果只作为展示和后续配置参考。
|
||||
|
||||
页面会展示:
|
||||
|
||||
- 当前日期周投票池。
|
||||
- 上一日期周投票统计。
|
||||
- 当前玩家本周已投的精灵。
|
||||
|
||||
## 4. 天选配置
|
||||
|
||||
天选配置表是 `config_peak_tianxuan`。
|
||||
|
||||
这张表现在表示“玩家额外拥有的天选精灵配置”,参考 `player_pet` 的归属思路,不再按周保存。
|
||||
|
||||
核心字段:
|
||||
|
||||
- `player_id`
|
||||
- `pet_id`
|
||||
- `display_order`
|
||||
- `preset_name`
|
||||
- `level`
|
||||
- `nature`
|
||||
- `hp`
|
||||
- `max_hp`
|
||||
- `attack`
|
||||
- `defence`
|
||||
- `sp_attack`
|
||||
- `sp_defence`
|
||||
- `speed`
|
||||
- `skin_id`
|
||||
- `effect_ids`
|
||||
- `skill_ids`
|
||||
- `remark`
|
||||
- `is_enable`
|
||||
|
||||
约束口径:
|
||||
|
||||
- 同一玩家不能重复配置同一只天选精灵。
|
||||
- 不再校验“本周不能和上周重复”。
|
||||
- 不再要求 `week_index`。
|
||||
|
||||
## 5. BP 使用
|
||||
|
||||
BP 不直接读取投票记录。
|
||||
|
||||
进入 BP 时读取双方各自的 `config_peak_tianxuan` 配置:
|
||||
|
||||
- 我方看到自己的天选配置。
|
||||
- 对方看到对方自己的天选配置。
|
||||
|
||||
投票结果后续可用于决定哪些精灵开放给玩家配置,但配置本身是玩家维度的数据。
|
||||
42
docs/peak-tianxuan-vote-design-2026-04-25.md
Normal file
42
docs/peak-tianxuan-vote-design-2026-04-25.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 巅峰之战天选池投票设计
|
||||
|
||||
## 规则补全
|
||||
|
||||
- 参与规则
|
||||
- 白银及以下:至少 1 只 100 级以上精灵,进行 `1v1`
|
||||
- 黄金、铂金、紫晶:至少 3 只 100 级以上精灵,进行 `3v3`
|
||||
- 钻石及以上:至少 6 只 100 级以上精灵,进行 `6v6`
|
||||
- 段位规则
|
||||
- 初始段位为青铜,初始分 `800`
|
||||
- `1000` 白银,`1200` 黄金,`1500` 铂金,`1800` 紫晶,`2100` 钻石,`3000` 大师,`4000` 王者
|
||||
- 王者榜前 100 自动晋升至尊王者
|
||||
- 天选池规则
|
||||
- 每周开放一组“天选池”候选精灵
|
||||
- 玩家在进入巅峰匹配前,先从本周天选池中投票选择自己的“天选备选”
|
||||
- 白银及以下可选 `1` 只,紫晶及以下可选 `2` 只,钻石及以上可选 `3` 只
|
||||
- 这些投票结果跟随匹配快照一起进入后续 BP 流程
|
||||
- BP 规则
|
||||
- 钻石以下默认无 Ban/Pick 强制要求,但前后端结构统一预留天选池字段
|
||||
- 钻石及以上进入正式 BP
|
||||
- BP 展示区应包含:
|
||||
- 出战备战精灵
|
||||
- 天选精灵
|
||||
- 双方互 Ban / Pick
|
||||
- BP 完成后再进入正式对战
|
||||
- 奖励规则
|
||||
- 本次仅补结构与文档位,结算奖励、赛季奖励、周奖励仍按后续配置表落地
|
||||
|
||||
## 本次后端结构预留
|
||||
|
||||
- `2458` 巅峰加入请求增加 `TianxuanPetIDs`
|
||||
- 匹配快照 `QueuePlayerSnapshot` 增加 `TianxuanPetIDs`
|
||||
- `CrossServerBanPickStartOutboundInfo` 增加:
|
||||
- `TianxuanSelectableCount`
|
||||
- `MyTianxuanPets`
|
||||
- `OpponentTianxuanPets`
|
||||
|
||||
## 当前实现边界
|
||||
|
||||
- 本次先完成“开始界面投票 + 匹配快照透传 + BP 启动数据预留”
|
||||
- 不直接改现有 `banpick.vue`
|
||||
- 天选精灵的正式上场生成、战斗侧实体化和完整 BP 消费,交给后续 BP 页面与战斗链路接入
|
||||
257
docs/pvp-cross-server-battle-cmd-data-design-2026-04-27.md
Normal file
257
docs/pvp-cross-server-battle-cmd-data-design-2026-04-27.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# PVP 跨服战斗消息 `cmd/data` 转发改造说明
|
||||
|
||||
## 背景
|
||||
|
||||
巅峰赛跨服战斗原本已经有 Redis pub/sub 通道,但战斗中的客户端操作转发仍然偏间接:
|
||||
|
||||
1. 本服 controller 先把客户端请求翻译成 `FightI` 调用。
|
||||
2. `RemoteFightProxy` 再把 `FightI` 调用翻译成 `battle_command`。
|
||||
3. 宿主服收到 `battle_command` 后,再翻译回 `FightI` 调用。
|
||||
|
||||
这样一来,跨服战斗链路和本地战斗链路之间多了一层“动作语义映射”,调试时不容易直接看到前端到底发了哪个 cmd、带了什么 data。
|
||||
|
||||
本次改造只处理“战斗中的消息转发”,不改匹配/加入队列逻辑。
|
||||
|
||||
## 保持不变的部分
|
||||
|
||||
- 巅峰赛加入和取消匹配仍然走原有 RPC:
|
||||
- `MatchJoinOrUpdate`
|
||||
- `MatchCancel`
|
||||
- Ban/Pick、MatchFound、SessionClose 等 PVP 服务内消息仍兼容旧外层格式。
|
||||
- `battle_command` 旧语义消息仍保留,作为兼容兜底,不强制一次性切掉。
|
||||
|
||||
对应原因:
|
||||
|
||||
- 匹配加入需要同步返回结果,RPC 更直接。
|
||||
- 现网如果已有旧 envelope 或旧 battle command 发送方,不能直接断。
|
||||
|
||||
## 改造目标
|
||||
|
||||
把跨服战斗中的“客户端操作转发”改成直接发送:
|
||||
|
||||
```json
|
||||
{
|
||||
"cmd": "battle_client_command",
|
||||
"data": {
|
||||
"sessionId": "xsvr-...",
|
||||
"userId": 10001,
|
||||
"cmd": 2405,
|
||||
"data": {
|
||||
"SkillId": 1234
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
核心含义:
|
||||
|
||||
- 外层 `cmd`:PVP 内部 Redis 消息类型。
|
||||
- 内层 `data.cmd`:真实客户端战斗协议 cmd。
|
||||
- 内层 `data.data`:该 cmd 对应的 JSON 业务字段。
|
||||
|
||||
这样宿主服拿到消息后,可以直接按真实战斗 cmd 处理,而不是先理解一层额外的“跨服动作协议”。
|
||||
|
||||
## 本次代码落点
|
||||
|
||||
### 1. 新增 `cmd/data` envelope 能力
|
||||
|
||||
文件:
|
||||
|
||||
- `logic/service/fight/pvpwire/types.go`
|
||||
|
||||
改动:
|
||||
|
||||
- `Envelope` 新增:
|
||||
- `Cmd string`
|
||||
- `Data json.RawMessage`
|
||||
- 保留旧字段:
|
||||
- `Type string`
|
||||
- `Body []byte`
|
||||
- 新增辅助方法:
|
||||
- `NewEnvelope`
|
||||
- `MessageCmd`
|
||||
- `MessageData`
|
||||
|
||||
作用:
|
||||
|
||||
- 新消息可以直接用 `cmd/data`。
|
||||
- 旧消息仍可通过 `type/body` 解析。
|
||||
|
||||
### 2. controller 层遇到远端跨服战斗时,直接转发原始战斗 cmd
|
||||
|
||||
文件:
|
||||
|
||||
- `logic/controller/fight_unified.go`
|
||||
- `logic/controller/fight_base.go`
|
||||
|
||||
改动:
|
||||
|
||||
- 在 `fight_unified.go` 增加 `relayRemoteFightCommand`。
|
||||
- 当 `c.FightC` 是 `*pvp.RemoteFightProxy` 时,不再走本地 `dispatchFightActionEnvelope` 分发,而是直接转发当前请求的:
|
||||
- `data.Head.CMD`
|
||||
- 当前请求结构体 `data`
|
||||
|
||||
已接入的战斗入口包括:
|
||||
|
||||
- `2404` 准备
|
||||
- `7556` 旧组队准备
|
||||
- `2405` 单战位技能
|
||||
- `7505` 多战位技能
|
||||
- `7558` 旧组队技能
|
||||
- `2406` 使用道具
|
||||
- `7562` 旧组队道具
|
||||
- `2407` 切宠
|
||||
- `7563` 旧组队切宠
|
||||
- `2410` 逃跑
|
||||
- `7565` 旧组队逃跑
|
||||
- `2441` 加载进度
|
||||
- `50002` 战斗聊天
|
||||
|
||||
### 3. RemoteFightProxy 新增“直接转发客户端命令”能力
|
||||
|
||||
文件:
|
||||
|
||||
- `logic/service/fight/pvp/proxy.go`
|
||||
|
||||
改动:
|
||||
|
||||
- 新增 `RelayClientCommand(cmd uint32, data any) bool`
|
||||
- 新增 `marshalClientCommandData(data any) ([]byte, error)`
|
||||
|
||||
说明:
|
||||
|
||||
- `marshalClientCommandData` 会把请求结构体转成 JSON。
|
||||
- 会删除 `Head/head` 字段,避免把协议头、二进制字段一起塞进跨服消息。
|
||||
- 这样发出去的 `data.data` 只保留业务字段。
|
||||
|
||||
### 4. PVP 宿主服新增 `battle_client_command` 处理
|
||||
|
||||
文件:
|
||||
|
||||
- `logic/service/fight/pvp/service.go`
|
||||
- `logic/service/fight/pvpwire/types.go`
|
||||
|
||||
改动:
|
||||
|
||||
- 新增消息类型:
|
||||
- `MessageTypeBattleClientCommand`
|
||||
- 新增载荷:
|
||||
- `BattleClientCommandPayload`
|
||||
- `handleRedisMessage` 增加 `battle_client_command` 分支。
|
||||
- 新增 `handleBattleClientCommand`。
|
||||
|
||||
处理方式:
|
||||
|
||||
- 宿主服根据内层真实 `cmd` 解出 `data`。
|
||||
- 再直接调用当前 `FightI` / `FightC`。
|
||||
|
||||
当前支持的映射:
|
||||
|
||||
- `2404` / `7556` -> `ReadyFight`
|
||||
- `2405` / `7505` / `7558` -> `UseSkillAt`
|
||||
- `2406` / `7562` -> `UseItemAt`
|
||||
- `2407` / `7563` -> `ChangePetAt`
|
||||
- `2410` / `7565` -> `Over(PlayerEscape)`
|
||||
- `2441` -> `LoadPercent`
|
||||
- `50002` -> `Chat`
|
||||
|
||||
### 5. 战斗发包转发也统一改成 `cmd/data`
|
||||
|
||||
文件:
|
||||
|
||||
- `logic/service/player/rpc.go`
|
||||
|
||||
改动:
|
||||
|
||||
- `PacketRelayPayload` 外层 envelope 从 `type/body` 改为 `cmd/data`。
|
||||
|
||||
原因:
|
||||
|
||||
- 它本质也是跨服战斗中的消息转发。
|
||||
- 和新的 `battle_client_command` 保持一致,便于抓包和日志排查。
|
||||
|
||||
## 当前实际链路
|
||||
|
||||
### 客户端操作转发
|
||||
|
||||
1. 客户端发战斗包到本服 controller。
|
||||
2. controller 判断当前 `FightC` 是否为 `RemoteFightProxy`。
|
||||
3. 如果是:
|
||||
- 直接发布 `battle_client_command`
|
||||
- 内层携带真实 `cmd` 和 JSON `data`
|
||||
4. 宿主服 `pvp.service` 收到后按 `cmd` 分发回战斗逻辑。
|
||||
|
||||
### 战斗结果/下行发包转发
|
||||
|
||||
1. 宿主服在 `RPC_player.SendPackCmd` 中组包。
|
||||
2. 发布 `packet_relay` 消息,外层使用 `cmd/data`。
|
||||
3. 客服端所在服收到后解包并发回真实客户端。
|
||||
|
||||
## 兼容策略
|
||||
|
||||
为了避免一次性改动过大,这次保留了两层兼容:
|
||||
|
||||
### 1. 旧 envelope 兼容
|
||||
|
||||
`Envelope` 同时支持:
|
||||
|
||||
- 新格式:`cmd/data`
|
||||
- 旧格式:`type/body`
|
||||
|
||||
`handleRedisMessage` 统一通过:
|
||||
|
||||
- `MessageCmd()`
|
||||
- `MessageData()`
|
||||
|
||||
读取消息。
|
||||
|
||||
### 2. 旧 `battle_command` 兼容
|
||||
|
||||
`RemoteFightProxy` 旧的:
|
||||
|
||||
- `UseSkill`
|
||||
- `UseSkillAt`
|
||||
- `UseItem`
|
||||
- `ChangePet`
|
||||
- `Chat`
|
||||
|
||||
这些方法仍然保留,并继续发送原来的 `battle_command`。
|
||||
新接入的 controller 优先走 `battle_client_command`,旧路径仍可兜底。
|
||||
|
||||
## 这次改造的收益
|
||||
|
||||
### 好处
|
||||
|
||||
- 跨服战斗消息更直观,日志里能直接看到真实客户端 cmd。
|
||||
- controller 到宿主服之间不需要再先翻译成一套额外动作协议。
|
||||
- 宿主服调试时更容易复现前端输入。
|
||||
- 新老消息可并存,改造风险相对可控。
|
||||
|
||||
### 仍然保留的复杂度
|
||||
|
||||
- 宿主服仍然需要针对不同战斗 cmd 做一次 decode 和分发。
|
||||
- 这是必要复杂度,因为真实战斗入口本来就有多种协议包。
|
||||
|
||||
## 验证
|
||||
|
||||
本次已执行:
|
||||
|
||||
- `cd logic && go test ./service/fight/pvp ./service/fight/pvpwire`
|
||||
- `cd logic && go test -c ./controller`
|
||||
|
||||
结果:
|
||||
|
||||
- `pvp`、`pvpwire` 包通过。
|
||||
- `controller` 可以完成编译检查。
|
||||
|
||||
补充说明:
|
||||
|
||||
- `go test ./controller ./service/player ...` 在当前环境会因为依赖初始化读取 `/proc/sys/kernel/osrelease` 失败而 panic,这属于运行环境问题,不是这次改动引入的编译错误。
|
||||
|
||||
## 后续建议
|
||||
|
||||
如果后面继续收敛这块逻辑,可以按下面顺序做:
|
||||
|
||||
1. 把 `battle_command` 的旧语义层逐步下线,只保留 `battle_client_command`。
|
||||
2. 把 `handleBattleClientCommand` 里的 cmd 映射抽成独立表,避免 `switch` 继续膨胀。
|
||||
3. 如果后续战斗协议继续统一,可考虑把 controller 入站结构和跨服转发 decode 共享同一套注册表。
|
||||
224
docs/pvp-login-rpc-match-design-2026-04-09.md
Normal file
224
docs/pvp-login-rpc-match-design-2026-04-09.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# PVP Match Via RPC, Battle Via Redis
|
||||
|
||||
## 目标
|
||||
|
||||
本次调整先不解决 `login` 更新期间的排队保活和补偿问题,只收敛到一个更简单、可控的方案:
|
||||
|
||||
- 匹配请求走 `logic -> login` 的同步 RPC
|
||||
- 对战过程仍走 `logic` 本地战斗 + Redis 转发战斗指令
|
||||
- `login` 不可用时,`logic` 直接返回“匹配服务不可用”
|
||||
- 前端通过轮询重新发起 / 更新匹配请求,不在后端保留离线补偿队列
|
||||
|
||||
这个方案的核心是:先把“能否立即判断匹配服务可用”做好,不继续依赖 Redis PubSub 做匹配入口。
|
||||
|
||||
## 当前现状
|
||||
|
||||
### 现有匹配入口
|
||||
|
||||
- 前端 `2458` 进入 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19)
|
||||
- 当前 `JoINtop` 直接调用 [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83) 的 `JoinPeakQueue`
|
||||
- `JoinPeakQueue` 当前实现是本地建 `localQueueTicket`,并通过 Redis `publish` 发 `queue_join`
|
||||
|
||||
### 现有跨服协调
|
||||
|
||||
- `logic` 侧订阅 PVP Redis topic 的入口在 [common/rpc/func.go](/workspace/common/rpc/func.go#L153)
|
||||
- PVP 匹配状态当前存在 `logic/service/fight/pvp/service.go` 的 manager 内存里:
|
||||
- `queues`
|
||||
- `lastSeen`
|
||||
- `localQueues`
|
||||
- `sessions`
|
||||
- `userSession`
|
||||
|
||||
### 现有 RPC 能力
|
||||
|
||||
- `logic` 启动时通过 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L113) 建立到 `login` 的 RPC client
|
||||
- `login` 的 `/rpc/*` 入口绑定在 [modules/base/middleware/middleware.go](/workspace/modules/base/middleware/middleware.go#L152)
|
||||
- `login` 侧 RPC server 由 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L101) 暴露
|
||||
|
||||
### 当前问题
|
||||
|
||||
Redis PubSub 适合“广播消息”,不适合“同步判断服务是否可用”。
|
||||
|
||||
如果继续让匹配入口走 PubSub:
|
||||
|
||||
- `logic` 无法在请求当下知道 `login` 是否真能处理
|
||||
- `login` 更新、重启、未订阅时,匹配请求可能直接丢失
|
||||
- 前端即使轮询,也只是重复投递,不能精确表达“当前匹配服务可用/不可用”
|
||||
|
||||
## 收敛后的职责划分
|
||||
|
||||
### login
|
||||
|
||||
`login` 只负责匹配控制面:
|
||||
|
||||
- 接收 `logic` 发来的同步匹配 RPC
|
||||
- 判断当前匹配服务是否可用
|
||||
- 维护匹配队列
|
||||
- 找到对手后,记录 match 结果
|
||||
- 再通过 Redis 或其他异步方式通知对应 `logic` 开始 Ban/Pick / Battle
|
||||
|
||||
### logic
|
||||
|
||||
`logic` 只负责:
|
||||
|
||||
- 接收前端匹配请求
|
||||
- 同步 RPC 到 `login`
|
||||
- RPC 失败时立即返回“匹配服务不可用”
|
||||
- RPC 成功时返回“排队中”
|
||||
- 收到 match 结果后负责真正 `fight.NewFight(...)`
|
||||
- 对战期间继续使用现有 Redis topic 转发战斗指令
|
||||
|
||||
### Redis
|
||||
|
||||
Redis 只保留在“对战消息面”:
|
||||
|
||||
- `match_found`
|
||||
- `ban_pick_submit`
|
||||
- `battle_command`
|
||||
- `packet_relay`
|
||||
- `session_close`
|
||||
|
||||
也就是说:
|
||||
|
||||
- 匹配入口走 RPC
|
||||
- 对战过程走 Redis
|
||||
|
||||
## 推荐目标链路
|
||||
|
||||
### 1. 前端加入/更新匹配
|
||||
|
||||
前端定期轮询 `logic` 的加入/更新接口。
|
||||
|
||||
`logic` 处理流程:
|
||||
|
||||
1. 校验玩家当前战斗状态
|
||||
2. 同步调用 `login` 的匹配 RPC
|
||||
3. 如果 RPC 成功:返回排队中
|
||||
4. 如果 RPC 失败:清理本地匹配状态,返回匹配服务不可用
|
||||
|
||||
### 2. login 完成匹配
|
||||
|
||||
`login` 维护排队队列和匹配结果,匹配成功后:
|
||||
|
||||
1. 确定 host / guest 所在 `logic`
|
||||
2. 通过 Redis 通知两个 `logic`
|
||||
3. host `logic` 开战
|
||||
4. guest `logic` 设置远端代理并进入 Ban/Pick 或战斗态
|
||||
|
||||
### 3. 对战期间
|
||||
|
||||
继续复用当前 `logic/service/fight/pvp/service.go` 内的 Redis 指令转发模式:
|
||||
|
||||
- 战斗操作通过 Redis topic 转发
|
||||
- host `logic` 维持真实战斗对象
|
||||
- guest `logic` 维持 remote proxy
|
||||
|
||||
## 失败语义
|
||||
|
||||
本阶段不做补偿,不做离线保队列。
|
||||
|
||||
### login 不在线
|
||||
|
||||
如果 `logic -> login` RPC 调用失败:
|
||||
|
||||
- 本次匹配直接失败
|
||||
- `logic` 清理本地匹配状态
|
||||
- 返回前端“匹配服务不可用”
|
||||
|
||||
### 前端轮询停止
|
||||
|
||||
如果前端不再轮询:
|
||||
|
||||
- 视为用户不再持续请求匹配
|
||||
- `logic` 不负责继续保活
|
||||
- 是否从 `login` 队列移除,由 `login` 的超时策略决定
|
||||
|
||||
### login 更新中
|
||||
|
||||
如果 `login` 正在更新:
|
||||
|
||||
- `logic` 的同步 RPC 会失败
|
||||
- 前端当前轮询会收到“匹配服务不可用”
|
||||
- 等 `login` 恢复后,前端下一轮再发起匹配
|
||||
|
||||
这是本阶段明确接受的行为,不在后端做补偿。
|
||||
|
||||
## 最小实现建议
|
||||
|
||||
### 一、先增加 RPC 健康/匹配接口
|
||||
|
||||
在 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go) 增加面向 `logic -> login` 的 RPC 方法。
|
||||
|
||||
建议最小接口:
|
||||
|
||||
- `MatchJoinOrUpdate(PVPMatchJoinPayload) error`
|
||||
- `MatchCancel(userID) error`
|
||||
|
||||
如果需要单独健康检查,也可以加:
|
||||
|
||||
- `MatchPing() error`
|
||||
|
||||
但在最小方案里,`MatchJoinOrUpdate` 自身就可以承担健康检查职责。
|
||||
|
||||
### 二、logic 的匹配入口改为同步 RPC
|
||||
|
||||
改造 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19) 和 [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83):
|
||||
|
||||
- 入口不再直接发布 `queue_join`
|
||||
- 先发 RPC 到 `login`
|
||||
- 成功才更新本地匹配状态
|
||||
- 失败直接返回错误
|
||||
- 取消匹配时通过 `MatchCancel` 做 best-effort 清理
|
||||
|
||||
### 三、保留 Redis 对战链路
|
||||
|
||||
[logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L170) 之后的 Redis 消费、match result 处理、Ban/Pick、战斗 relay 不需要一次性重写,可以继续保留。
|
||||
|
||||
调整重点是:
|
||||
|
||||
- 不再让匹配入口依赖 PubSub
|
||||
- 让对战过程继续走 Redis
|
||||
|
||||
## 对前端的要求
|
||||
|
||||
前端不要无脑重复“新 join”,而是按“轮询更新匹配状态”处理。
|
||||
|
||||
建议行为:
|
||||
|
||||
1. 首次点击匹配时发一次加入
|
||||
2. 匹配中每隔 `3~5s` 轮询一次更新
|
||||
3. 如果返回“匹配服务不可用”,前端退出匹配态并提示
|
||||
4. 如果返回“已匹配/进入 Ban/Pick”,前端切换到对应界面
|
||||
|
||||
## 本阶段不做的事
|
||||
|
||||
以下内容明确不在这次最小改造内:
|
||||
|
||||
- `login` 更新期间的排队保活
|
||||
- 持久化消息补偿
|
||||
- `login` 重启后的队列恢复
|
||||
- Redis Stream 化
|
||||
- 多 `login` 实例协调
|
||||
- 匹配服务自动拉起目标 `logic`
|
||||
|
||||
## 后续可选增强
|
||||
|
||||
如果后面要继续提高可用性,可以再逐步演进为:
|
||||
|
||||
1. 匹配入口仍走 RPC
|
||||
2. `login` 内部把队列落 Redis
|
||||
3. 加入 ticket 和续租机制
|
||||
4. login 更新时支持恢复匹配状态
|
||||
|
||||
但这不是当前阶段的目标。
|
||||
|
||||
## 最终收敛结论
|
||||
|
||||
当前阶段建议明确成一句话:
|
||||
|
||||
`匹配走 RPC,对战走 Redis。`
|
||||
|
||||
对应业务语义:
|
||||
|
||||
- 需要立即判断服务可用性的时候,用 RPC
|
||||
- 需要跨服转发战斗消息的时候,用 Redis
|
||||
276
docs/rpc-blocking-reconnect-review-2026-04-27.md
Normal file
276
docs/rpc-blocking-reconnect-review-2026-04-27.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# RPC 阻塞、并发承载与重连复用检查
|
||||
|
||||
日期:2026-04-27
|
||||
|
||||
## 1. 检查范围
|
||||
|
||||
本次主要检查以下两层:
|
||||
|
||||
- 业务 RPC 封装:`common/rpc/rpc.go`
|
||||
- 底层 websocket JSON-RPC:`common/utils/go-jsonrpc/client.go`
|
||||
- 底层 websocket 重连循环:`common/utils/go-jsonrpc/websocket.go`
|
||||
- 业务调用点:`logic/controller/login_main.go`、`logic/controller/fight_巅峰.go`
|
||||
|
||||
另外同步看了本轮已有的跨服战斗转发改动:
|
||||
|
||||
- `logic/service/fight/pvp/proxy.go`
|
||||
- `logic/service/fight/pvp/service.go`
|
||||
- `logic/service/fight/pvpwire/types.go`
|
||||
- `logic/service/player/rpc.go`
|
||||
|
||||
## 2. 结论
|
||||
|
||||
### 2.1 现在的 RPC 会不会阻塞
|
||||
|
||||
会。
|
||||
|
||||
当前业务侧 `Kick`、`MatchJoinOrUpdate`、`MatchCancel`、`RegisterLogic` 都是同步 RPC 调用。
|
||||
调用 goroutine 会一直等到底层返回响应、连接错误,或者调用方自己的 `context` 超时。
|
||||
|
||||
原先的问题是:
|
||||
|
||||
- 这些业务 RPC 方法没有 `context.Context` 入参
|
||||
- 调用方没法给单次 RPC 设置超时
|
||||
- 一旦对端卡住或网络异常,调用方可能长期挂住
|
||||
|
||||
这会直接影响:
|
||||
|
||||
- 登录踢人流程
|
||||
- 巅峰匹配加入/取消
|
||||
- 重连后的逻辑服重新注册
|
||||
|
||||
### 2.2 并发会不会顶不住
|
||||
|
||||
结论是:底层支持多路并发,但原实现存在明显背压点。
|
||||
|
||||
底层 `go-jsonrpc` 的设计不是“单请求单连接”,而是:
|
||||
|
||||
- 一个 `RPCClient`
|
||||
- 一个 websocket 连接
|
||||
- 多个请求共用一个 `requests` 通道
|
||||
- 通过请求 ID 做响应分发
|
||||
|
||||
所以它本身支持并发复用同一条连接。
|
||||
|
||||
但原实现有两个风险:
|
||||
|
||||
1. `client.setupRequestChan()` 里的 `requests` 是无缓冲通道
|
||||
当 `handleWsConn` 主循环发送不过来时,调用方会在“写入请求通道”这一步被卡住。
|
||||
|
||||
2. 业务调用没有统一超时
|
||||
即使底层连接还能用,某个慢 RPC 也可能把业务 goroutine 长时间挂住。
|
||||
|
||||
这不代表“完全扛不住”,但高并发下会更容易出现请求堆积和业务侧等待放大。
|
||||
|
||||
### 2.3 重连后 URL 会不会复用
|
||||
|
||||
会复用。
|
||||
|
||||
当前 websocket client 在初始化时把地址保存在 `connFactory` 中,重连时走的还是同一个 `addr`:
|
||||
|
||||
- `common/utils/go-jsonrpc/client.go`
|
||||
- `websocketClient(...)` 中构造 `connFactory`
|
||||
- `common/utils/go-jsonrpc/websocket.go`
|
||||
- `tryReconnect(...)` 中再次调用 `c.connFactory()`
|
||||
|
||||
也就是说:
|
||||
|
||||
- 重连不是只发一次注册 RPC 就结束
|
||||
- 重连后不是一次性临时连接
|
||||
- 而是替换 `wsConn.conn` 为新连接
|
||||
- 后续 RPC 仍然继续复用同一个 `RPCClient` 和同一个目标 URL
|
||||
|
||||
### 2.4 重连后是不是必须再发一次注册 RPC
|
||||
|
||||
现在不是了。
|
||||
|
||||
当前实现已经改成:
|
||||
|
||||
- 客户端在 websocket 建连 URL 上直接带 `logic_id` / `logic_port`
|
||||
- 服务端在握手阶段创建 reverse client 后,立刻根据 URL 参数完成 logic 注册
|
||||
- 连接断开时再根据同一身份清理 `cool.Clientmap`
|
||||
|
||||
这样重连时:
|
||||
|
||||
- 仍然走同一个 URL
|
||||
- 新连接在握手阶段就知道 client 身份
|
||||
- 不需要再依赖重连成功后的二次 `RegisterLogic(id, port)` RPC
|
||||
|
||||
保留 `RegisterLogic` 只是兼容已有接口,不再是重连链路的必要步骤。
|
||||
|
||||
## 3. 本轮已做修改
|
||||
|
||||
### 3.1 给业务 RPC 增加显式超时能力
|
||||
|
||||
修改文件:
|
||||
|
||||
- `common/rpc/rpc.go`
|
||||
- `logic/controller/Controller.go`
|
||||
- `logic/controller/login_main.go`
|
||||
- `logic/controller/fight_巅峰.go`
|
||||
|
||||
改动内容:
|
||||
|
||||
- `Kick`
|
||||
- `RegisterLogic`
|
||||
- `MatchJoinOrUpdate`
|
||||
- `MatchCancel`
|
||||
|
||||
统一改成带 `context.Context` 的签名。
|
||||
|
||||
新增:
|
||||
|
||||
- `common/rpc/rpc.go`
|
||||
- `ClientCallTimeout = 5 * time.Second`
|
||||
|
||||
调用侧现在会显式设置超时,避免业务 goroutine 无限等待。
|
||||
|
||||
### 3.2 连接握手阶段直接注册 logic 身份
|
||||
|
||||
修改文件:
|
||||
|
||||
- `common/rpc/rpc.go`
|
||||
- `common/utils/go-jsonrpc/server.go`
|
||||
- `common/utils/go-jsonrpc/options_server.go`
|
||||
|
||||
行为调整:
|
||||
|
||||
- 客户端建连 URL 直接携带 `logic_id` / `logic_port`
|
||||
- 服务端握手时把原始 `*http.Request` 放入 RPC 上下文
|
||||
- reverse client 建好后立即读取 URL 参数并注册 logic client
|
||||
- 连接关闭时按相同 key 自动清理 `cool.Clientmap`
|
||||
|
||||
这样重连后不需要额外补发一次注册 RPC。
|
||||
|
||||
### 3.3 底层请求通道增加缓冲
|
||||
|
||||
修改文件:
|
||||
|
||||
- `common/utils/go-jsonrpc/client.go`
|
||||
|
||||
改动:
|
||||
|
||||
- `requests := make(chan clientRequest, 1024)`
|
||||
|
||||
目的:
|
||||
|
||||
- 把“调用方 goroutine 立刻卡在请求投递”这个点往后挪
|
||||
- 给 `handleWsConn` 主循环留一个有限缓冲区
|
||||
|
||||
这不是彻底消除背压,只是把最硬的无缓冲阻塞改掉。
|
||||
|
||||
### 3.4 重连回调不再承担注册职责
|
||||
|
||||
修改文件:
|
||||
|
||||
- `common/rpc/rpc.go`
|
||||
|
||||
改动:
|
||||
|
||||
- 去掉 `StartClient(...)` 中依赖 `WithReconnFun(...)` 做补注册的逻辑
|
||||
|
||||
目的:
|
||||
|
||||
- 让“谁是这个 logic client”变成握手时就已确定的连接属性
|
||||
- 避免重连后再发一笔注册 RPC
|
||||
|
||||
## 4. 这轮修改后的判断
|
||||
|
||||
### 4.1 RPC 还会不会阻塞
|
||||
|
||||
会,但现在阻塞是“有边界的同步等待”,不是“无上限死等”。
|
||||
|
||||
也就是:
|
||||
|
||||
- 业务仍然是同步 RPC 模式
|
||||
- 但调用方现在有明确超时
|
||||
- 超时后能返回错误,不会无限挂住
|
||||
|
||||
### 4.2 并发有没有改善
|
||||
|
||||
有改善,但不是彻底做成高吞吐 RPC 网关。
|
||||
|
||||
现在比原来更稳的点:
|
||||
|
||||
- 业务调用有超时
|
||||
- 请求投递通道有缓冲
|
||||
- 重连时不再额外补发一次注册 RPC
|
||||
|
||||
仍然保留的现实限制:
|
||||
|
||||
- 单条 websocket 连接仍然只有一个写口
|
||||
- `handleWsConn` 仍是单主循环
|
||||
- 极端并发下仍会出现排队,只是不会像原来那样更早卡死
|
||||
|
||||
## 5. 关于“URL 复用”的最终确认
|
||||
|
||||
最终确认如下:
|
||||
|
||||
1. `RPCClient` 建立时会把目标 `rpcaddr` 固定到 `connFactory`
|
||||
2. 这个 `rpcaddr` 现在已经带上 `logic_id` / `logic_port`
|
||||
3. 连接断开后,`tryReconnect(...)` 继续使用这个 `connFactory`
|
||||
4. 新连接建立后,服务端在握手阶段直接按 URL 参数注册 reverse client
|
||||
5. 原 `RPCClient` 后续 RPC 继续走新连接
|
||||
|
||||
所以这里是:
|
||||
|
||||
- 复用同一个目标 URL
|
||||
- 复用同一个 `RPCClient`
|
||||
- 复用同一个请求分发模型
|
||||
|
||||
不是:
|
||||
|
||||
- 重连后靠额外发一次 `RegisterLogic`
|
||||
- 才让后续 RPC 可用
|
||||
|
||||
现在身份识别和注册已经前置到连接握手本身。
|
||||
|
||||
## 6. 还没解决的风险
|
||||
|
||||
### 6.1 连接级串行写仍然存在
|
||||
|
||||
虽然请求可以并发入队,但真正写 websocket 还是串行的。
|
||||
如果将来 login 侧 RPC 量继续上升,还是可能需要继续做:
|
||||
|
||||
- 更细的调用隔离
|
||||
- 独立连接池
|
||||
- 或把部分强同步调用改为异步消息
|
||||
|
||||
### 6.2 1024 缓冲不是容量上限方案
|
||||
|
||||
当前只是经验值,不是经过压测得出的最终值。
|
||||
如果峰值比预期高,还可能继续积压。
|
||||
|
||||
### 6.3 业务上仍然是同步等待模式
|
||||
|
||||
比如:
|
||||
|
||||
- 登录踢人
|
||||
- 匹配加入
|
||||
|
||||
仍然依赖 RPC 成功/失败来推进。
|
||||
只是现在不会无限挂死,但高峰期延迟仍可能直接体现在业务响应上。
|
||||
|
||||
## 7. 验证情况
|
||||
|
||||
已完成:
|
||||
|
||||
- `gofmt` 已执行
|
||||
- `go test ./rpc` in `common` 通过
|
||||
- `go test -run '^$' .` in `common/utils/go-jsonrpc` 通过
|
||||
|
||||
受环境限制未完整确认:
|
||||
|
||||
- `common/utils/go-jsonrpc` 全量测试在 sandbox 下需要本地监听端口
|
||||
- `logic` 模块测试受当前环境 `/proc/sys/kernel/osrelease` 读取失败影响,无法作为本轮改动的有效回归结论
|
||||
|
||||
另外,`common` 模块全量 `go test ./...` 还会被仓库内已有的 `fmt.Println("%.2f")` 这类历史问题拦住,与本次 RPC 改动无关。
|
||||
|
||||
## 8. 建议的下一步
|
||||
|
||||
如果后面还要继续收敛这块,建议优先级如下:
|
||||
|
||||
1. 给 login 侧关键 RPC 增加更明确的耗时日志和超时日志
|
||||
2. 对 `requests` 队列积压增加指标或告警
|
||||
3. 评估 `Kick` 和匹配 RPC 是否需要拆连接
|
||||
4. 如果 login 压力继续上涨,再考虑把部分同步入口改成异步投递 + 状态查询
|
||||
37
help/base_sys_user_role去重.sql
Normal file
37
help/base_sys_user_role去重.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- base_sys_user_role 角色授权去重
|
||||
-- 只处理未软删除的有效授权;软删除历史记录不参与去重
|
||||
-- 保留每组有效 userId + roleId 中 id 最小的一条,删除其余重复记录
|
||||
|
||||
-- 1. 执行前查看重复数据
|
||||
SELECT
|
||||
"userId",
|
||||
"roleId",
|
||||
COUNT(*) AS cnt,
|
||||
MIN(id) AS keep_id,
|
||||
ARRAY_AGG(id ORDER BY id) AS ids
|
||||
FROM base_sys_user_role
|
||||
WHERE deleted_at IS NULL
|
||||
GROUP BY "userId", "roleId"
|
||||
HAVING COUNT(*) > 1
|
||||
ORDER BY cnt DESC, "userId", "roleId";
|
||||
|
||||
-- 2. 删除重复数据
|
||||
DELETE FROM base_sys_user_role a
|
||||
USING base_sys_user_role b
|
||||
WHERE a."userId" = b."userId"
|
||||
AND a."roleId" = b."roleId"
|
||||
AND a.deleted_at IS NULL
|
||||
AND b.deleted_at IS NULL
|
||||
AND a.id > b.id;
|
||||
|
||||
-- 3. 执行后复查,应返回 0 行
|
||||
SELECT
|
||||
"userId",
|
||||
"roleId",
|
||||
COUNT(*) AS cnt
|
||||
FROM base_sys_user_role
|
||||
WHERE deleted_at IS NULL
|
||||
GROUP BY "userId", "roleId"
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- 唯一约束统一在 约束类.sql 中维护。
|
||||
10
help/ftp.md
Normal file
10
help/ftp.md
Normal file
@@ -0,0 +1,10 @@
|
||||
FTP地址:38.102.84.92 [ 端口:21 ]
|
||||
FTP用户名:m1584920
|
||||
FTP密码:7WKimiwDH5RL2SLs
|
||||
空间容量:10G
|
||||
有效期至:2027-04-21(1年)
|
||||
|
||||
文件上传之后,其URL地址是:
|
||||
https://m1584920.772988.xyz/文件名+文件格式,如果文件名有中文,请注意编码问题,如果你不懂这个就遇到再跟我说。
|
||||
|
||||
PS:如果需要绑定你的域名,手续费¥30
|
||||
@@ -1,99 +0,0 @@
|
||||
# 屎山代码分析报告
|
||||
|
||||
## 总体评估
|
||||
|
||||
- **质量评分**: 31.03/100
|
||||
- **质量等级**: 🌸 偶有异味 - 基本没事,但是有伤风化
|
||||
- **分析文件数**: 203
|
||||
- **代码总行数**: 20972
|
||||
|
||||
## 质量指标
|
||||
|
||||
| 指标 | 得分 | 权重 | 状态 |
|
||||
|------|------|------|------|
|
||||
| 状态管理 | 4.84 | 0.15 | ✓✓ |
|
||||
| 循环复杂度 | 6.28 | 0.25 | ✓✓ |
|
||||
| 命名规范 | 25.00 | 0.10 | ✓ |
|
||||
| 错误处理 | 35.00 | 0.15 | ○ |
|
||||
| 代码结构 | 45.00 | 0.20 | ○ |
|
||||
| 代码重复度 | 55.00 | 0.15 | • |
|
||||
| 注释覆盖率 | 55.94 | 0.15 | • |
|
||||
|
||||
## 问题文件 (Top 5)
|
||||
|
||||
### 1. /workspace/blazing/common/utils/sturc/field.go (得分: 53.85)
|
||||
**问题分类**: 🔄 复杂度问题:10, 📝 注释问题:1, ⚠️ 其他问题:5
|
||||
|
||||
**主要问题**:
|
||||
- 函数 Size 的循环复杂度较高 (12),建议简化
|
||||
- 函数 packVal 的循环复杂度过高 (23),考虑重构
|
||||
- 函数 Pack 的循环复杂度较高 (14),建议简化
|
||||
- 函数 unpackVal 的循环复杂度过高 (21),考虑重构
|
||||
- 函数 Unpack 的循环复杂度较高 (12),建议简化
|
||||
- 函数 'Size' () 较长 (33 行),可考虑重构
|
||||
- 函数 'Size' () 复杂度过高 (12),建议简化
|
||||
- 函数 'packVal' () 过长 (69 行),建议拆分
|
||||
- 函数 'packVal' () 复杂度严重过高 (23),必须简化
|
||||
- 函数 'Pack' () 较长 (48 行),可考虑重构
|
||||
- 函数 'Pack' () 复杂度过高 (14),建议简化
|
||||
- 函数 'unpackVal' () 过长 (57 行),建议拆分
|
||||
- 函数 'unpackVal' () 复杂度严重过高 (21),必须简化
|
||||
- 函数 'Unpack' () 较长 (33 行),可考虑重构
|
||||
- 函数 'Unpack' () 复杂度过高 (12),建议简化
|
||||
- 代码注释率极低 (1.38%),几乎没有注释
|
||||
|
||||
### 2. /workspace/blazing/common/utils/sturc/fields.go (得分: 46.83)
|
||||
**问题分类**: 🔄 复杂度问题:4, 📝 注释问题:1, ⚠️ 其他问题:2
|
||||
|
||||
**主要问题**:
|
||||
- 函数 Pack 的循环复杂度较高 (12),建议简化
|
||||
- 函数 Unpack 的循环复杂度过高 (21),考虑重构
|
||||
- 函数 'Pack' () 较长 (42 行),可考虑重构
|
||||
- 函数 'Pack' () 复杂度过高 (12),建议简化
|
||||
- 函数 'Unpack' () 过长 (73 行),建议拆分
|
||||
- 函数 'Unpack' () 复杂度严重过高 (21),必须简化
|
||||
- 代码注释率极低 (3.91%),几乎没有注释
|
||||
|
||||
### 3. /workspace/blazing/common/utils/sturc/parse.go (得分: 46.68)
|
||||
**问题分类**: 🔄 复杂度问题:4, 📝 注释问题:1, ⚠️ 其他问题:3
|
||||
|
||||
**主要问题**:
|
||||
- 代码注释率较低 (6.93%),建议增加注释
|
||||
- 函数 parseField 的循环复杂度较高 (13),建议简化
|
||||
- 函数 parseFieldsLocked 的循环复杂度过高 (18),考虑重构
|
||||
- 函数 'parseField' () 过长 (64 行),建议拆分
|
||||
- 函数 'parseField' () 复杂度过高 (13),建议简化
|
||||
- 函数 'parseFieldsLocked' () 过长 (64 行),建议拆分
|
||||
- 函数 'parseFieldsLocked' () 复杂度严重过高 (18),必须简化
|
||||
- 函数 'parseFields' () 较长 (31 行),可考虑重构
|
||||
|
||||
### 4. /workspace/blazing/common/utils/xml/typeinfo.go (得分: 46.13)
|
||||
**问题分类**: 🔄 复杂度问题:6, ⚠️ 其他问题:3
|
||||
|
||||
**主要问题**:
|
||||
- 函数 getTypeInfo 的循环复杂度过高 (18),考虑重构
|
||||
- 函数 structFieldInfo 的循环复杂度过高 (33),考虑重构
|
||||
- 函数 addFieldInfo 的循环复杂度过高 (20),考虑重构
|
||||
- 函数 'getTypeInfo' () 过长 (58 行),建议拆分
|
||||
- 函数 'getTypeInfo' () 复杂度严重过高 (18),必须简化
|
||||
- 函数 'structFieldInfo' () 极度过长 (114 行),必须拆分
|
||||
- 函数 'structFieldInfo' () 复杂度严重过高 (33),必须简化
|
||||
- 函数 'addFieldInfo' () 过长 (66 行),建议拆分
|
||||
- 函数 'addFieldInfo' () 复杂度严重过高 (20),必须简化
|
||||
|
||||
### 5. /workspace/blazing/common/utils/go-jsonrpc/auth/handler.go (得分: 45.61)
|
||||
**问题分类**: 📝 注释问题:1, ⚠️ 其他问题:1
|
||||
|
||||
**主要问题**:
|
||||
- 函数 'ServeHTTP' () 较长 (31 行),可考虑重构
|
||||
- 代码注释率极低 (0.00%),几乎没有注释
|
||||
|
||||
## 改进建议
|
||||
|
||||
### 高优先级
|
||||
- 继续保持当前的代码质量标准
|
||||
|
||||
### 中优先级
|
||||
- 可以考虑进一步优化性能和可读性
|
||||
- 完善文档和注释,便于团队协作
|
||||
|
||||
14
help/server_show冠名索引修复.sql
Normal file
14
help/server_show冠名索引修复.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- server_show 冠名索引修复
|
||||
-- 目的:允许同一服务器存在多个不同玩家的冠名记录。
|
||||
-- 同服同属主唯一约束统一在 约束类.sql 中维护。
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 历史上 server_id 被建成了单列唯一约束/唯一索引,会拦截同服多冠名
|
||||
ALTER TABLE server_show DROP CONSTRAINT IF EXISTS idx_server_show_server_id;
|
||||
DROP INDEX IF EXISTS idx_server_show_server_id;
|
||||
|
||||
-- 保留普通查询索引
|
||||
CREATE INDEX IF NOT EXISTS idx_server_show_server_id ON server_show (server_id);
|
||||
|
||||
COMMIT;
|
||||
73
help/初始化SPT配置.sql
Normal file
73
help/初始化SPT配置.sql
Normal file
@@ -0,0 +1,73 @@
|
||||
-- 初始化/修复 SPT 配置表(PostgreSQL)
|
||||
-- 用法:在 sun 数据库执行本文件
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS config_spt (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
"createTime" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updateTime" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
deleted_at TIMESTAMPTZ NULL,
|
||||
is_enable INTEGER NOT NULL DEFAULT 1,
|
||||
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||
task_id INTEGER NOT NULL,
|
||||
title VARCHAR(64) NOT NULL DEFAULT '',
|
||||
pet_id INTEGER NOT NULL DEFAULT 0,
|
||||
online INTEGER NOT NULL DEFAULT 1,
|
||||
level INTEGER NOT NULL DEFAULT 1,
|
||||
enter_id INTEGER NOT NULL DEFAULT 0,
|
||||
description TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
-- task_id 唯一约束统一在 约束类.sql 中维护。
|
||||
|
||||
ALTER TABLE config_spt DROP COLUMN IF EXISTS seat_id;
|
||||
|
||||
INSERT INTO config_spt
|
||||
(task_id, title, pet_id, online, level, enter_id, description, is_enable, remark)
|
||||
VALUES
|
||||
(301,'蘑菇怪',47,1,1,12,'生活在克洛斯星,被艾里逊的液氮冻伤而发狂,使用火焰喷射器可以使它安静下来。制服它可以获得草系精灵小蘑菇。',1,'破除防护罩'),
|
||||
(302,'钢牙鲨',34,1,1,21,'海洋星海底的危险怪兽,据说它躲藏的洞穴中有制作黑武士装的黑晶矿石。记住,到海底一定要穿上耐压的潜水套装。',1,''),
|
||||
(303,'里奥斯',42,1,2,17,'海盗艾里逊在火山被它困住,战胜它有机会获得火系精灵“胡里亚”。在火山,你会用到喷水装的。',1,'扑灭火焰屏障'),
|
||||
(304,'阿克希亚',50,1,4,40,'塞西利亚星的守护者,正义的精灵圣兽,它是不可战胜的,千年来一直等待着宿命的对手。',1,''),
|
||||
(305,'提亚斯',69,1,3,27,'云霄星出现了一只极具攻击性的变异精灵,拥有很多蛋的它虽然想要努力呵护自己的孩子却力不从心,看来需要大家帮帮忙啊。',1,''),
|
||||
(306,'雷伊',70,1,3,32,'赫尔卡星天空中划过一道闪电,映出了一个酷似精灵的黑影,它全身被电流包围,从它的神态中可以看出它正等待来自各方的挑战。',1,'雷雨天'),
|
||||
(307,'纳多雷',88,1,3,106,'在双子阿尔法星上,特派队遇见了一只巨大的精灵,经过多次挑战后,它仍然丝毫无损,赛尔们是否有办法战胜这只精灵呢?',1,''),
|
||||
(308,'雷纳多',113,1,3,49,'彪悍的雷纳多盘踞在双子贝塔星上,和双子阿尔法星的纳多雷遥相对应,守护着星球上所有精灵的。',1,''),
|
||||
(309,'尤纳斯',132,1,4,314,'黑暗之门的制造者,拥有能够抵御一切的暗影屏障和所有能量来源的黑暗之核。',1,''),
|
||||
(310,'魔狮迪露',187,1,4,53,'魔狮迪露具有神秘的力量,能使自己的体力突破界限,但同时也会受到未知的惩罚。',1,''),
|
||||
(311,'哈莫雷特',216,1,5,60,'拥有无比巨大的身躯,集水火草三种原能为一身,龙系的神秘力量使它所向无敌。失忆的它,似乎还有很多谜团……',1,''),
|
||||
(312,'奈尼芬多',264,1,4,325,'奈尼芬多是爱迪星的守护者,凄美的歌声连月亮都为之倾倒,据说只有音乐的力量才能够唤醒它。',1,''),
|
||||
(316,'厄尔塞拉',421,1,5,61,'浑身散发着各色光芒,任何邪恶在她的光芒下消散无形',1,''),
|
||||
(50,'卡特斯',169,1,2,110,'作为暗黑武斗场的试炼精灵,守护着试炼之门,它的气度和风度非同一般,杀气重重很难对付!',1,''),
|
||||
(51,'魔牙鲨',171,1,3,503,'暗黑第一门的魔牙鲨,被赋予了传说中的暗黑斗气,隐藏在暗影中攻击时它的能力可以被放大增强。',1,''),
|
||||
(53,'贝鲁基德',174,1,3,504,'暗黑第二门的贝鲁基德,暗黑火焰环绕周身,凶悍的外表下藏着善战勇敢的心。',1,''),
|
||||
(55,'巴弗洛',177,1,3,505,'勇猛凶横的巴弗洛把守着暗黑武斗场Ⅲ-Ⅰ门,霹雳闪电般的羽翼攻击、震荡心胸的音乐攻击,让人防不胜防。',1,''),
|
||||
(56,'奇拉塔顿',183,1,3,505,'勇敢的奇拉塔顿驻守在暗黑武斗场Ⅲ-Ⅱ门的那一边,身为大地之子的它驾驭着反物质能量,纵横无敌。',1,''),
|
||||
(59,'西萨拉斯',195,1,3,506,'拥有强大反物质电力的暗黑Ⅳ-Ⅰ门守护者,雷霆之刃震撼寰宇天地,电流之剑穿透空间阻隔,威慑四方。',1,''),
|
||||
(60,'克林卡修',192,1,4,506,'暗黑Ⅳ-Ⅱ门守护者冰雪灵兽克林卡修,冰雪之爪具有猛烈的攻击力,果敢不张扬的个性,让它成为忍者般的精灵。',1,''),
|
||||
(76,'卡库',222,1,4,507,'暗黑Ⅴ-Ⅰ门是武学之门,守门精灵不张扬、不蛮横、步步为营,每出一招都会致命。',1,''),
|
||||
(77,'赫德卡',224,1,4,507,'驻守暗黑V-II门的铁血赫德卡,有着铜墙铁壁的防守能力,有着超级强力的电光炮,它的防御之门你能够开启吗?',1,''),
|
||||
(78,'伊兰罗尼',227,1,5,507,'守护暗黑Ⅴ-Ⅲ门的伊兰罗尼是优雅得体的淑女,擅长在裙摆飘飘、光芒闪耀间使出杀手。',1,''),
|
||||
(117,'斯加尔卡',356,1,5,508,'暗黑VI-I门的守护者,擅长使用暗黑电能的家伙,比起折磨对手的身体,斯加尔卡更喜欢震慑对手的心灵。',1,''),
|
||||
(118,'艾尔伊洛',297,1,5,508,'暗黑VI-II门的守护者,历经了炼狱洗礼的艾尔伊洛开始崇尚爽快的攻击方式,喜欢凭借精湛的技巧近距地伤害对手。',1,''),
|
||||
(119,'布林克克',359,1,5,508,'暗黑VI-III门的守护者,拥有海妖之力的庇护,体内充满着混沌的能量企图吞噬整个海洋。',1,''),
|
||||
(502,'魔花使者',438,1,5,509,'暗黑VII-I门的守护者,比恩特的进化形态,浑身散发着反物质世界中的黑暗气息,散发出来的毒粉是它的致命武器。',1,''),
|
||||
(503,'莫尔加斯',441,1,5,509,'暗黑VII-II门的守护者,莫鲁格尔的进化形态,受到了反物质世界的影响,浑身被黑暗所包围,拥有极强的防御能力,所有攻击在它面前都显得非常渺小。',1,''),
|
||||
(504,'萨诺拉斯',435,1,5,509,'暗黑VII-III门的守护者,萨诺的进化形态,经过岩浆洗礼的皮肤拥有独特的降温功能,即使在极其炎热的环境下依然不受影响。',1,''),
|
||||
(606,'帕多尼',656,1,5,510,'暗黑Ⅷ-Ⅰ门的守护者,浑身充斥着暗黑能量,暗黑能量会随着它的歌神散发出来。',1,''),
|
||||
(607,'加洛德',659,1,5,510,'暗黑Ⅷ-Ⅱ门的守护,暗黑能量的注入,使它的脾气变得暴躁,擅长与对手近身搏斗,浑身的尖刺、催生出的植物都是它进攻的利器。',1,''),
|
||||
(608,'萨多拉尼',661,1,5,510,'暗黑Ⅷ-Ⅲ门的守护,将暗黑能量融入自身,肢体变得非常结实有力,虽然体积很小,但是却拥有了堪比巨龙的神力。',1,'')
|
||||
ON CONFLICT (task_id) WHERE deleted_at IS NULL DO UPDATE
|
||||
SET
|
||||
title = EXCLUDED.title,
|
||||
pet_id = EXCLUDED.pet_id,
|
||||
online = EXCLUDED.online,
|
||||
level = EXCLUDED.level,
|
||||
enter_id = EXCLUDED.enter_id,
|
||||
description = EXCLUDED.description,
|
||||
is_enable = EXCLUDED.is_enable,
|
||||
remark = EXCLUDED.remark,
|
||||
"updateTime" = NOW();
|
||||
|
||||
COMMIT;
|
||||
246
help/约束类.sql
246
help/约束类.sql
@@ -1,42 +1,254 @@
|
||||
-- 唯一约束修复
|
||||
-- 规则:所有带 deleted_at 的业务唯一约束,只约束未软删除记录。
|
||||
|
||||
-- 玩家+物品+VIP状态 联合唯一
|
||||
ALTER TABLE player_item
|
||||
ADD CONSTRAINT uk_player_item_player_item_vip
|
||||
UNIQUE (player_id, item_id, is_vip);
|
||||
DROP CONSTRAINT IF EXISTS uk_player_item_player_item_vip;
|
||||
DROP INDEX IF EXISTS uk_player_item_player_item_vip;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_item_player_item_vip
|
||||
ON player_item (player_id, item_id, is_vip)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 玩家+挖矿 联合唯一
|
||||
CREATE UNIQUE INDEX uk_talk_player ON player_talk (talk_id, player_id);
|
||||
ALTER TABLE player_talk
|
||||
DROP CONSTRAINT IF EXISTS uk_talk_player;
|
||||
DROP INDEX IF EXISTS uk_talk_player;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_talk_player
|
||||
ON player_talk (talk_id, player_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 玩家+任务 联合唯一
|
||||
CREATE UNIQUE INDEX uk_player_task ON player_task (player_id, task_id);
|
||||
ALTER TABLE player_task
|
||||
DROP CONSTRAINT IF EXISTS uk_player_task;
|
||||
DROP INDEX IF EXISTS uk_player_task;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_task
|
||||
ON player_task (player_id, task_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 玩家+称号 联合唯一
|
||||
CREATE UNIQUE INDEX uk_player_title ON player_title (player_id, is_vip) WHERE deleted_at IS NULL;
|
||||
ALTER TABLE player_title
|
||||
DROP CONSTRAINT IF EXISTS uk_player_title;
|
||||
DROP INDEX IF EXISTS uk_player_title;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_title
|
||||
ON player_title (player_id, is_vip)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 玩家+精灵 联合唯一
|
||||
CREATE UNIQUE INDEX uk_player_pet ON player_pet (player_id, is_vip, catch_time) WHERE deleted_at IS NULL;
|
||||
ALTER TABLE player_pet
|
||||
DROP CONSTRAINT IF EXISTS uk_player_pet;
|
||||
DROP INDEX IF EXISTS uk_player_pet;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_pet
|
||||
ON player_pet (player_id, is_vip, catch_time)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 玩家+CDK 联合唯一
|
||||
CREATE UNIQUE INDEX uk_player_cdk_log
|
||||
ALTER TABLE player_cdk_log
|
||||
DROP CONSTRAINT IF EXISTS uk_player_cdk_log;
|
||||
DROP INDEX IF EXISTS uk_player_cdk_log;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_cdk_log
|
||||
ON player_cdk_log (player_id, code_id, is_vip)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
|
||||
-- 玩家孵蛋 联合唯一
|
||||
CREATE UNIQUE INDEX uk_player_egg
|
||||
ON player_egg (player_id, is_vip)
|
||||
ALTER TABLE player_egg
|
||||
DROP CONSTRAINT IF EXISTS uk_player_egg;
|
||||
DROP INDEX IF EXISTS uk_player_egg;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_egg
|
||||
ON player_egg (player_id, is_vip)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
---PVP索引
|
||||
CREATE UNIQUE INDEX uk_player_pvp
|
||||
|
||||
-- PVP索引
|
||||
ALTER TABLE player_pvp
|
||||
DROP CONSTRAINT IF EXISTS uk_player_pvp;
|
||||
DROP INDEX IF EXISTS uk_player_pvp;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_pvp
|
||||
ON player_pvp (player_id, season)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
--签到
|
||||
CREATE UNIQUE INDEX uk_player_sign_in_log
|
||||
|
||||
-- 签到
|
||||
ALTER TABLE player_sign_in_log
|
||||
DROP CONSTRAINT IF EXISTS uk_player_sign_in_log;
|
||||
DROP INDEX IF EXISTS uk_player_sign_in_log;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_sign_in_log
|
||||
ON player_sign_in_log (player_id, sign_in_id, is_vip)
|
||||
WHERE deleted_at IS NULL;
|
||||
--房间索引
|
||||
CREATE UNIQUE INDEX uk_player_room_house
|
||||
|
||||
|
||||
-- 房间索引
|
||||
ALTER TABLE player_room_house
|
||||
DROP CONSTRAINT IF EXISTS uk_player_room_house;
|
||||
DROP INDEX IF EXISTS uk_player_room_house;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_room_house
|
||||
ON player_room_house (player_id, is_vip)
|
||||
WHERE deleted_at IS NULL;
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 集市权限角色 联合唯一
|
||||
-- 先清理有效重复授权,保留每组 userId + roleId 中 id 最小的一条。
|
||||
DELETE FROM base_sys_user_role a
|
||||
USING base_sys_user_role b
|
||||
WHERE a."userId" = b."userId"
|
||||
AND a."roleId" = b."roleId"
|
||||
AND a.deleted_at IS NULL
|
||||
AND b.deleted_at IS NULL
|
||||
AND a.id > b.id;
|
||||
|
||||
ALTER TABLE base_sys_user_role
|
||||
DROP CONSTRAINT IF EXISTS uk_base_sys_user_role_user_role;
|
||||
DROP INDEX IF EXISTS uk_base_sys_user_role_user_role;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_base_sys_user_role_user_role
|
||||
ON base_sys_user_role ("userId", "roleId")
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- CDK配置 编号唯一
|
||||
ALTER TABLE config_gift_cdk
|
||||
DROP CONSTRAINT IF EXISTS idx_config_gift_cdk_cdk_code;
|
||||
DROP INDEX IF EXISTS idx_config_gift_cdk_cdk_code;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_gift_cdk_cdk_code
|
||||
ON config_gift_cdk (cdk_code)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 战斗规则 规则索引唯一
|
||||
ALTER TABLE config_fight_rule
|
||||
DROP CONSTRAINT IF EXISTS idx_rule_idx;
|
||||
DROP INDEX IF EXISTS idx_rule_idx;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_rule_idx
|
||||
ON config_fight_rule (rule_idx)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 天选配置 玩家+精灵唯一
|
||||
ALTER TABLE config_peak_tianxuan
|
||||
DROP CONSTRAINT IF EXISTS idx_peak_tianxuan_player_pet;
|
||||
DROP INDEX IF EXISTS idx_peak_tianxuan_player_pet;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_peak_tianxuan_player_pet
|
||||
ON config_peak_tianxuan (player_id, pet_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 天选投票 周期+玩家唯一
|
||||
ALTER TABLE config_peak_tianxuan_vote
|
||||
DROP CONSTRAINT IF EXISTS idx_peak_tianxuan_vote_week_player;
|
||||
DROP INDEX IF EXISTS idx_peak_tianxuan_vote_week_player;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_peak_tianxuan_vote_week_player
|
||||
ON config_peak_tianxuan_vote (week_index, player_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 服务器冠名 同服同属主唯一
|
||||
ALTER TABLE server_show
|
||||
DROP CONSTRAINT IF EXISTS idx_server_show_server_owner;
|
||||
DROP INDEX IF EXISTS idx_server_show_server_owner;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_server_show_server_owner
|
||||
ON server_show (server_id, owner)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 商店 商品ID唯一
|
||||
ALTER TABLE config_shop
|
||||
DROP CONSTRAINT IF EXISTS idx_config_shop_product_id;
|
||||
DROP INDEX IF EXISTS idx_config_shop_product_id;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_shop_product_id
|
||||
ON config_shop (product_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 签到配置 签到类别+阶段唯一
|
||||
ALTER TABLE config_sign_in
|
||||
DROP CONSTRAINT IF EXISTS idx_sign_type_stage;
|
||||
DROP INDEX IF EXISTS idx_sign_type_stage;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_sign_type_stage
|
||||
ON config_sign_in (sign_type, stage_days)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 签到配置 CDK唯一
|
||||
ALTER TABLE config_sign_in
|
||||
DROP CONSTRAINT IF EXISTS idx_config_sign_in_cdk_id;
|
||||
DROP INDEX IF EXISTS idx_config_sign_in_cdk_id;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_sign_in_cdk_id
|
||||
ON config_sign_in (cdk_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- SPT配置 任务ID唯一
|
||||
ALTER TABLE config_spt
|
||||
DROP CONSTRAINT IF EXISTS idx_config_spt_task_id;
|
||||
DROP INDEX IF EXISTS idx_config_spt_task_id;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_spt_task_id
|
||||
ON config_spt (task_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 爬塔配置 层级唯一
|
||||
ALTER TABLE config_tower_1
|
||||
DROP CONSTRAINT IF EXISTS idx_config_tower_1_tower_level;
|
||||
DROP INDEX IF EXISTS idx_config_tower_1_tower_level;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_1_tower_level
|
||||
ON config_tower_1 (tower_level)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
ALTER TABLE config_tower_110
|
||||
DROP CONSTRAINT IF EXISTS idx_config_tower_110_tower_level;
|
||||
DROP INDEX IF EXISTS idx_config_tower_110_tower_level;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_110_tower_level
|
||||
ON config_tower_110 (tower_level)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
ALTER TABLE config_tower_500
|
||||
DROP CONSTRAINT IF EXISTS idx_config_tower_500_tower_level;
|
||||
DROP INDEX IF EXISTS idx_config_tower_500_tower_level;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_500_tower_level
|
||||
ON config_tower_500 (tower_level)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
ALTER TABLE config_tower_600
|
||||
DROP CONSTRAINT IF EXISTS idx_config_tower_600_tower_level;
|
||||
DROP INDEX IF EXISTS idx_config_tower_600_tower_level;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_600_tower_level
|
||||
ON config_tower_600 (tower_level)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
|
||||
-- 玩家信息 角色ID唯一
|
||||
ALTER TABLE player_info
|
||||
DROP CONSTRAINT IF EXISTS idx_player_info_player_id;
|
||||
DROP INDEX IF EXISTS idx_player_info_player_id;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_player_info_player_id
|
||||
ON player_info (player_id)
|
||||
WHERE deleted_at IS NULL;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/rpc"
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/common"
|
||||
"bytes"
|
||||
@@ -16,17 +17,23 @@ import (
|
||||
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/panjf2000/gnet/v2"
|
||||
)
|
||||
|
||||
// Maincontroller 是控制器层共享变量。
|
||||
var Maincontroller = &Controller{} //注入service
|
||||
|
||||
// Controller 分发cmd逻辑实现
|
||||
type Controller struct {
|
||||
UID uint32
|
||||
RPCClient *struct {
|
||||
Kick func(uint32) error
|
||||
Kick func(context.Context, uint32) error
|
||||
|
||||
RegisterLogic func(uint32, uint32) error
|
||||
RegisterLogic func(context.Context, uint32, uint32) error
|
||||
|
||||
MatchJoinOrUpdate func(context.Context, rpc.PVPMatchJoinPayload) error
|
||||
|
||||
MatchCancel func(context.Context, uint32) error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,19 +51,14 @@ func ParseCmd[T any](data []byte) T {
|
||||
// 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)
|
||||
methodValue := controllerValue.MethodByName(method.Name)
|
||||
methodValue := controllerValue.Method(i)
|
||||
methodType := methodValue.Type()
|
||||
|
||||
// 获取方法第一个参数的类型(请求结构体)
|
||||
if methodType.NumIn() == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -67,43 +69,46 @@ func Init(isGame bool) {
|
||||
continue
|
||||
}
|
||||
reqType := reqArgType.Elem()
|
||||
binding := getCmdBinding(reqType)
|
||||
|
||||
// 解析请求结构体中的cmd标签
|
||||
for _, cmd := range getCmd(reqType) {
|
||||
if cmd == 0 { // 说明不是有效的注册方法
|
||||
for _, cmd := range binding.cmds {
|
||||
if cmd == 0 {
|
||||
glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册")
|
||||
continue
|
||||
}
|
||||
|
||||
// 根据服务器类型过滤cmd
|
||||
// 登录服务器只处理小于1000的cmd
|
||||
if methodType.NumIn() != 2 {
|
||||
glog.Warning(context.Background(), "方法参数数量必须为2", method.Name, "跳过注册")
|
||||
continue
|
||||
}
|
||||
|
||||
if !isGame && cmd > 1000 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 游戏服务器只处理大于等于1000的cmd
|
||||
if isGame && cmd < 1000 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 注册命令处理函数
|
||||
if cool.Config.ServerInfo.IsDebug != 0 {
|
||||
fmt.Println("注册方法", cmd, method.Name)
|
||||
}
|
||||
|
||||
cmdInfo := cool.Cmd{
|
||||
Func: methodValue,
|
||||
Req: reqType,
|
||||
|
||||
// Res: , // TODO 待实现对不同用户初始化方法以取消全局cmdcache
|
||||
}
|
||||
// 预编译创建req实例的函数:返回结构体指针
|
||||
reqTypeForNew := reqType
|
||||
cmdInfo.NewReqFunc = func() interface{} {
|
||||
return reflect.New(reqTypeForNew).Interface()
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
if _, exists := cool.CmdCache[cmd]; exists { // 方法已存在
|
||||
if _, exists := cool.CmdCache[cmd]; exists {
|
||||
panic(fmt.Sprintf("命令处理方法已存在,跳过注册 %d %s", cmd, method.Name))
|
||||
}
|
||||
cool.CmdCache[cmd] = cmdInfo
|
||||
@@ -111,12 +116,20 @@ func Init(isGame bool) {
|
||||
}
|
||||
}
|
||||
|
||||
var targetType = reflect.TypeOf(common.TomeeHeader{})
|
||||
var cmdTypeCache sync.Map
|
||||
var (
|
||||
targetType = reflect.TypeOf(common.TomeeHeader{})
|
||||
connType = reflect.TypeOf((*gnet.Conn)(nil)).Elem()
|
||||
cmdTypeCache sync.Map
|
||||
)
|
||||
|
||||
// 默认返回值(无匹配字段/解析失败时)
|
||||
const defaultCmdValue = 0
|
||||
|
||||
type cmdBinding struct {
|
||||
cmds []uint32
|
||||
headerFieldIndex []int
|
||||
}
|
||||
|
||||
func normalizeStructType(typ reflect.Type) reflect.Type {
|
||||
for typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
@@ -124,92 +137,93 @@ func normalizeStructType(typ reflect.Type) reflect.Type {
|
||||
return typ
|
||||
}
|
||||
|
||||
// getCmd 从结构体类型中提取绑定的cmd指令(递归查找嵌套结构体,支持值/指针类型的TomeeHeader)
|
||||
// 参数 typ: 待解析的结构体类型(支持多层指针)
|
||||
// 返回值: 解析到的cmd切片,无匹配/解析失败时返回[defaultCmdValue]
|
||||
func getCmd(typ reflect.Type) []uint32 {
|
||||
// getCmdBinding 从结构体类型中提取绑定的cmd指令和头字段位置。
|
||||
func getCmdBinding(typ reflect.Type) cmdBinding {
|
||||
typ = normalizeStructType(typ)
|
||||
if cached, ok := cmdTypeCache.Load(typ); ok {
|
||||
return cached.([]uint32)
|
||||
return cached.(cmdBinding)
|
||||
}
|
||||
|
||||
// 非结构体类型直接返回默认值
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return []uint32{defaultCmdValue}
|
||||
binding := cmdBinding{cmds: []uint32{defaultCmdValue}}
|
||||
cmdTypeCache.Store(typ, binding)
|
||||
return binding
|
||||
}
|
||||
|
||||
if cmd, ok := findCmd(typ, make(map[reflect.Type]struct{})); ok {
|
||||
cmdTypeCache.Store(typ, cmd)
|
||||
return cmd
|
||||
if binding, ok := findCmdBinding(typ, make(map[reflect.Type]struct{})); ok {
|
||||
cmdTypeCache.Store(typ, binding)
|
||||
return binding
|
||||
}
|
||||
|
||||
// 未找到目标字段/所有解析失败,返回默认值
|
||||
defaultCmd := []uint32{defaultCmdValue}
|
||||
cmdTypeCache.Store(typ, defaultCmd)
|
||||
return defaultCmd
|
||||
binding := cmdBinding{cmds: []uint32{defaultCmdValue}}
|
||||
cmdTypeCache.Store(typ, binding)
|
||||
return binding
|
||||
}
|
||||
|
||||
func findCmd(typ reflect.Type, visiting map[reflect.Type]struct{}) ([]uint32, bool) {
|
||||
func findCmdBinding(typ reflect.Type, visiting map[reflect.Type]struct{}) (cmdBinding, bool) {
|
||||
typ = normalizeStructType(typ)
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return nil, false
|
||||
return cmdBinding{}, false
|
||||
}
|
||||
if _, seen := visiting[typ]; seen {
|
||||
return nil, false
|
||||
return cmdBinding{}, false
|
||||
}
|
||||
visiting[typ] = struct{}{}
|
||||
defer delete(visiting, typ)
|
||||
|
||||
// 遍历结构体字段,查找TomeeHeader字段并解析cmd
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
// 尝试解析当前字段的cmd标签
|
||||
cmdSlice, isHeader, err := parseCmdTagWithStructField(field)
|
||||
if isHeader && err == nil { // 解析成功,直接返回结果
|
||||
return cmdSlice, true
|
||||
if isHeader && err == nil {
|
||||
return cmdBinding{
|
||||
cmds: cmdSlice,
|
||||
headerFieldIndex: append([]int(nil), field.Index...),
|
||||
}, true
|
||||
}
|
||||
|
||||
// 递归处理嵌套结构体(值/指针类型)
|
||||
nestedTyp := normalizeStructType(field.Type)
|
||||
if nestedTyp.Kind() == reflect.Struct {
|
||||
// 递归查找,找到有效cmd则立即返回
|
||||
if nestedCmd, ok := findCmd(nestedTyp, visiting); ok {
|
||||
return nestedCmd, true
|
||||
}
|
||||
if nestedTyp.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return nil, false
|
||||
return cmdBinding{}, false
|
||||
}
|
||||
|
||||
// parseCmdTagWithStructField 校验字段是否为TomeeHeader(值/指针)并解析cmd标签
|
||||
// 参数 field: 结构体字段元信息
|
||||
// 返回值: 解析后的cmd切片,是否为目标类型,解析失败错误
|
||||
func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, bool, error) {
|
||||
// 判断字段类型是否为 TomeeHeader 或 *TomeeHeader
|
||||
if field.Type != targetType && !(field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// 提取cmd标签
|
||||
cmdStr := field.Tag.Get("cmd")
|
||||
if cmdStr == "" {
|
||||
return nil, true, fmt.Errorf("field %s cmd tag is empty", field.Name)
|
||||
}
|
||||
|
||||
// 高性能解析标签为uint32切片(替代gconv,减少第三方依赖且可控)
|
||||
result := make([]uint32, 0, strings.Count(cmdStr, "|")+1)
|
||||
remain := cmdStr
|
||||
for idx := 0; ; idx++ {
|
||||
part, next, found := strings.Cut(remain, "|")
|
||||
// 去除空白字符(兼容标签中意外的空格)
|
||||
s := strings.TrimSpace(part)
|
||||
if s == "" {
|
||||
return nil, true, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx)
|
||||
}
|
||||
|
||||
// 手动解析uint32,比gconv更可控,避免隐式转换问题
|
||||
num, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return nil, true, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)",
|
||||
|
||||
@@ -43,7 +43,8 @@ var masterCupRequiredItems = map[uint32][]ItemS{
|
||||
},
|
||||
}
|
||||
|
||||
func (h Controller) DASHIbei(req *C2s_MASTER_REWARDS, c *player.Player) (result *S2C_MASTER_REWARDS, err errorcode.ErrorCode) {
|
||||
// GetMasterCupRewards 处理控制器请求。
|
||||
func (h Controller) GetMasterCupRewards(req *C2s_MASTER_REWARDS, c *player.Player) (result *S2C_MASTER_REWARDS, err errorcode.ErrorCode) {
|
||||
_ = req
|
||||
result = &S2C_MASTER_REWARDS{}
|
||||
items := c.Service.Item.Get(masterCupRewardItemMin, masterCupRewardItemMax)
|
||||
@@ -52,7 +53,8 @@ func (h Controller) DASHIbei(req *C2s_MASTER_REWARDS, c *player.Player) (result
|
||||
return
|
||||
}
|
||||
|
||||
func (h Controller) DASHIbeiR(req *C2s_MASTER_REWARDSR, c *player.Player) (result *S2C_MASTER_REWARDSR, err errorcode.ErrorCode) {
|
||||
// ClaimMasterCupReward 处理控制器请求。
|
||||
func (h Controller) ClaimMasterCupReward(req *C2s_MASTER_REWARDSR, c *player.Player) (result *S2C_MASTER_REWARDSR, err errorcode.ErrorCode) {
|
||||
result = &S2C_MASTER_REWARDSR{}
|
||||
|
||||
requiredItems, ok := masterCupRequiredItems[req.ElementType]
|
||||
@@ -70,30 +72,37 @@ func (h Controller) DASHIbeiR(req *C2s_MASTER_REWARDSR, c *player.Player) (resul
|
||||
}
|
||||
|
||||
result.ItemList = make([]data.ItemInfo, 0, len(taskInfo.ItemList))
|
||||
c.Service.Task.Exec(masterCupTaskID, func(te *model.Task) bool {
|
||||
progress := bitset32.From(te.Data)
|
||||
if progress.Test(uint(req.ElementType)) {
|
||||
err = errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||
return false
|
||||
}
|
||||
taskData, taskErr := c.Service.Task.GetTask(masterCupTaskID)
|
||||
if taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
consumeMasterCupItems(c, requiredItems)
|
||||
progress.Set(uint(req.ElementType))
|
||||
te.Data = progress.Bytes()
|
||||
progress := bitset32.From(taskData.Data)
|
||||
if progress.Test(uint(req.ElementType)) {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||
}
|
||||
|
||||
if taskInfo.Pet != nil {
|
||||
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
||||
result.CaptureTime = taskInfo.Pet.CatchTime
|
||||
result.PetTypeId = taskInfo.Pet.ID
|
||||
}
|
||||
if err := consumeMasterCupItems(c, requiredItems); err != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrInsufficientItems)
|
||||
}
|
||||
progress.Set(uint(req.ElementType))
|
||||
taskData.Data = progress.Bytes()
|
||||
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
appendMasterCupRewardItems(c, result, taskInfo.ItemList)
|
||||
return true
|
||||
})
|
||||
if taskInfo.Pet != nil {
|
||||
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
||||
result.CaptureTime = taskInfo.Pet.CatchTime
|
||||
result.PetTypeId = taskInfo.Pet.ID
|
||||
}
|
||||
|
||||
appendMasterCupRewardItems(c, result, taskInfo.ItemList)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ItemS 定义请求或响应数据结构。
|
||||
type ItemS struct {
|
||||
ItemId uint32
|
||||
ItemCnt uint32
|
||||
@@ -123,10 +132,13 @@ func hasEnoughMasterCupItems(c *player.Player, requiredItems []ItemS) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func consumeMasterCupItems(c *player.Player, requiredItems []ItemS) {
|
||||
func consumeMasterCupItems(c *player.Player, requiredItems []ItemS) error {
|
||||
for _, item := range requiredItems {
|
||||
c.Service.Item.UPDATE(item.ItemId, -int(item.ItemCnt))
|
||||
if err := c.Service.Item.UPDATE(item.ItemId, -int(item.ItemCnt)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendMasterCupRewardItems(c *player.Player, result *S2C_MASTER_REWARDSR, itemList []data.ItemInfo) {
|
||||
@@ -137,6 +149,7 @@ func appendMasterCupRewardItems(c *player.Player, result *S2C_MASTER_REWARDSR, i
|
||||
}
|
||||
}
|
||||
|
||||
// C2s_MASTER_REWARDS 定义请求或响应数据结构。
|
||||
type C2s_MASTER_REWARDS struct {
|
||||
Head common.TomeeHeader `cmd:"2611" struc:"skip"` //玩家登录
|
||||
}
|
||||
@@ -147,6 +160,7 @@ type S2C_MASTER_REWARDS struct {
|
||||
Reward []uint32 `json:"Reward"`
|
||||
}
|
||||
|
||||
// C2s_MASTER_REWARDSR 定义请求或响应数据结构。
|
||||
type C2s_MASTER_REWARDSR struct {
|
||||
Head common.TomeeHeader `cmd:"2612" struc:"skip"` //玩家登录
|
||||
ElementType uint32
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
// EggGamePlay 处理控制器请求。
|
||||
func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (result *S2C_EGG_GAME_PLAY, err errorcode.ErrorCode) {
|
||||
|
||||
switch data1.EggNum {
|
||||
@@ -25,12 +26,11 @@ func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (res
|
||||
if data1.EggNum > 10 || data1.EggNum <= 0 {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
if r < 0 {
|
||||
if r <= 0 || data1.EggNum > r {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
||||
}
|
||||
if data1.EggNum > r {
|
||||
if err := c.Service.Item.UPDATE(400501, int(-data1.EggNum)); err != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
||||
|
||||
}
|
||||
result = &S2C_EGG_GAME_PLAY{ListInfo: []data.ItemInfo{}}
|
||||
if grand.Meet(int(data1.EggNum), 100) {
|
||||
@@ -51,8 +51,6 @@ func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (res
|
||||
for _, item := range addedItems {
|
||||
result.ListInfo = append(result.ListInfo, data.ItemInfo{ItemId: item.ItemId, ItemCnt: item.ItemCnt})
|
||||
}
|
||||
|
||||
c.Service.Item.UPDATE(400501, int(-data1.EggNum))
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
// Draw15To10WithBitSet 15抽10,返回标记抽取结果的uint32(位1表示选中)
|
||||
// drawTenOfFifteenBitset 15抽10,返回标记抽取结果的uint32(位1表示选中)
|
||||
// 规则:uint32的第n位(0≤n≤14)=1 → 选中第n+1号元素
|
||||
func Draw15To10WithBitSet() uint32 {
|
||||
func drawTenOfFifteenBitset() uint32 {
|
||||
// 初始化随机数生成器
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
@@ -37,7 +37,8 @@ func Draw15To10WithBitSet() uint32 {
|
||||
return resultBits
|
||||
}
|
||||
|
||||
func (h Controller) GET_XUANCAI(data *C2s_GET_XUANCAI, c *player.Player) (result *S2C_GET_XUANCAI, err errorcode.ErrorCode) {
|
||||
// ClaimXuanCaiShards 处理控制器请求。
|
||||
func (h Controller) ClaimXuanCaiShards(data *C2s_GET_XUANCAI, c *player.Player) (result *S2C_GET_XUANCAI, err errorcode.ErrorCode) {
|
||||
result = &S2C_GET_XUANCAI{}
|
||||
selectedCount := 0 // 已选中的数量
|
||||
res := c.Info.GetTask(13) //第一期
|
||||
@@ -56,6 +57,7 @@ func (h Controller) GET_XUANCAI(data *C2s_GET_XUANCAI, c *player.Player) (result
|
||||
|
||||
// 检查该位是否未被选中(避免重复)
|
||||
if (result.Status & mask) == 0 {
|
||||
result.Status |= mask
|
||||
itemID := uint32(400686 + randBitIdx + 1)
|
||||
selectedItems = append(selectedItems, itemID)
|
||||
itemMask[itemID] = mask
|
||||
@@ -74,6 +76,7 @@ func (h Controller) GET_XUANCAI(data *C2s_GET_XUANCAI, c *player.Player) (result
|
||||
|
||||
}
|
||||
|
||||
// C2s_GET_XUANCAI 定义请求或响应数据结构。
|
||||
type C2s_GET_XUANCAI struct {
|
||||
Head common.TomeeHeader `cmd:"60001" struc:"skip"` //玩家登录
|
||||
}
|
||||
|
||||
@@ -10,19 +10,17 @@ import (
|
||||
)
|
||||
|
||||
// 进入超时空隧道
|
||||
func (h Controller) TimeMap(data *C2s_SP, c *player.Player) (result *S2C_SP, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetTimeTunnelMaps(data *C2s_SP, c *player.Player) (result *S2C_SP, err errorcode.ErrorCode) {
|
||||
result = &S2C_SP{}
|
||||
mapPitService := service.NewMapPitService()
|
||||
maps := service.NewMapService().GetTimeMap()
|
||||
result.MapList = make([]ServerInfo, len(maps))
|
||||
for i, v := range maps {
|
||||
result.MapList[i].ID = v.MapID
|
||||
result.MapList[i].DropItemIds = v.DropItemIds
|
||||
pits := service.NewMapPitService().GetDataALL(v.MapID)
|
||||
|
||||
for _, v := range pits {
|
||||
|
||||
result.MapList[i].Pet = append(result.MapList[i].Pet, v.RefreshID...)
|
||||
|
||||
for i, mapInfo := range maps {
|
||||
result.MapList[i].ID = mapInfo.MapID
|
||||
result.MapList[i].DropItemIds = mapInfo.DropItemIds
|
||||
pits := mapPitService.GetDataALL(mapInfo.MapID)
|
||||
for _, pit := range pits {
|
||||
result.MapList[i].Pet = append(result.MapList[i].Pet, pit.RefreshID...)
|
||||
}
|
||||
result.MapList[i].Pet = lo.Union(result.MapList[i].Pet)
|
||||
}
|
||||
@@ -30,6 +28,7 @@ func (h Controller) TimeMap(data *C2s_SP, c *player.Player) (result *S2C_SP, err
|
||||
|
||||
}
|
||||
|
||||
// C2s_SP 定义请求或响应数据结构。
|
||||
type C2s_SP struct {
|
||||
Head common.TomeeHeader `cmd:"60002" struc:"skip"` //超时空地图
|
||||
}
|
||||
@@ -40,6 +39,7 @@ type S2C_SP struct {
|
||||
MapList []ServerInfo
|
||||
}
|
||||
|
||||
// ServerInfo 定义请求或响应数据结构。
|
||||
type ServerInfo struct {
|
||||
ID uint32 //地图ID
|
||||
PetLen uint32 `struc:"sizeof=Pet"`
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"blazing/logic/service/player"
|
||||
)
|
||||
|
||||
// GetLeiyiTrainStatus 处理控制器请求。
|
||||
func (h Controller) GetLeiyiTrainStatus(data *C2s_LEIYI_TRAIN_GET_STATUS, c *player.Player) (result *S2C_LEIYI_TRAIN_GET_STATUS, err errorcode.ErrorCode) {
|
||||
result = &S2C_LEIYI_TRAIN_GET_STATUS{}
|
||||
|
||||
@@ -19,6 +20,7 @@ func (h Controller) GetLeiyiTrainStatus(data *C2s_LEIYI_TRAIN_GET_STATUS, c *pla
|
||||
|
||||
}
|
||||
|
||||
// C2s_LEIYI_TRAIN_GET_STATUS 定义请求或响应数据结构。
|
||||
type C2s_LEIYI_TRAIN_GET_STATUS struct {
|
||||
Head common.TomeeHeader `cmd:"2393" struc:"skip"` //玩家登录
|
||||
}
|
||||
@@ -28,6 +30,7 @@ type S2C_LEIYI_TRAIN_GET_STATUS struct {
|
||||
Status [10]S2C_LEIYI_TRAIN_GET_STATUS_info `json:"status"`
|
||||
}
|
||||
|
||||
// S2C_LEIYI_TRAIN_GET_STATUS_info 定义请求或响应数据结构。
|
||||
type S2C_LEIYI_TRAIN_GET_STATUS_info struct {
|
||||
// Today uint32 // 今日训练HP次数
|
||||
Current uint32 // 当前训练HP次数
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// data: 空输入结构
|
||||
// c: 当前玩家对象
|
||||
// 返回: 捕捉结果(消耗的EV值)和错误码
|
||||
func (h Controller) HanLiuQiang(data *C2S_2608, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ClaimColdFlowGunReward(data *C2S_2608, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
if c.ItemAdd(100245, 1) {
|
||||
return
|
||||
@@ -35,6 +35,7 @@ func (h Controller) HanLiuQiang(data *C2S_2608, c *player.Player) (result *fight
|
||||
return result, -1
|
||||
}
|
||||
|
||||
// C2S_2608 定义请求或响应数据结构。
|
||||
type C2S_2608 struct {
|
||||
Head common.TomeeHeader `cmd:"2608" struc:"skip"`
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/fight/info"
|
||||
@@ -18,86 +17,197 @@ func (h Controller) checkFightStatus(c *player.Player) errorcode.ErrorCode {
|
||||
}
|
||||
|
||||
// OnReadyToFight 准备战斗
|
||||
func (h Controller) OnReadyToFight(data *fight.ReadyToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) OnReadyToFight(data *ReadyToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
go c.FightC.ReadyFight(c)
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
go c.ReadyFight()
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// GroupReadyFightFinish 旧组队协议准备完成。
|
||||
func (h Controller) GroupReadyFightFinish(data *GroupReadyFightFinishInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
go c.ReadyFight()
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupUseSkill(data *GroupUseSkillInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
targetRelation := fight.SkillTargetOpponent
|
||||
if data.TargetSide == 1 {
|
||||
targetRelation = fight.SkillTargetAlly
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewSkillActionEnvelope(data.SkillId, int(data.ActorIndex), int(data.TargetPos), targetRelation, 0))
|
||||
c.SendPackCmd(7558, nil)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupUseItem(data *GroupUseItemInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewItemActionEnvelope(0, data.ItemId, int(data.ActorIndex), int(data.ActorIndex), fight.SkillTargetSelf))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupChangePet(data *GroupChangePetInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewChangeActionEnvelope(data.CatchTime, int(data.ActorIndex)))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupEscape(data *GroupEscapeInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
if fightC, ok := c.GetFightController().(*fight.FightC); ok && fightC != nil && fightC.LegacyGroupProtocol {
|
||||
fightC.SendLegacyEscapeSuccess(c, int(data.ActorIndex))
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, fight.NewEscapeActionEnvelope())
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (h Controller) GroupFightWinClose(data *GroupFightWinCloseInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if c != nil {
|
||||
c.QuitFight()
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GroupFightTimeoutExit(data *GroupFightTimeoutExitInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if c != nil {
|
||||
c.QuitFight()
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// UseSkill 使用技能包
|
||||
func (h Controller) UseSkill(data *fight.UseSkillInInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) UseSkill(data *UseSkillInInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
go c.FightC.UseSkill(c, data.SkillId)
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, 0
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, buildLegacyUseSkillEnvelope(data))
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// UseSkillAt 组队/多战位技能包(cmd=7505)。
|
||||
// 目标关系:0=对方 1=自己 2=队友。
|
||||
func (h Controller) UseSkillAt(data *UseSkillAtInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, 0
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, buildIndexedUseSkillEnvelope(data))
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// Escape 战斗逃跑
|
||||
func (h Controller) Escape(data *fight.EscapeFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) Escape(data *EscapeFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go c.FightC.Over(c, model.BattleOverReason.PlayerEscape)
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, 0
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, buildLegacyEscapeEnvelope())
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// ChangePet 切换精灵
|
||||
func (h Controller) ChangePet(data *fight.ChangePetInboundInfo, c *player.Player) (result *info.ChangePetInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ChangePet(data *ChangePetInboundInfo, c *player.Player) (result *info.ChangePetInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
go c.FightC.ChangePet(c, data.CatchTime)
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, buildLegacyChangeEnvelope(data))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// Capture 捕捉精灵
|
||||
func (h Controller) Capture(data *fight.CatchMonsterInboundInfo, c *player.Player) (result *info.CatchMonsterOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) Capture(data *CatchMonsterInboundInfo, c *player.Player) (result *info.CatchMonsterOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if c.GetSpace().IsTime {
|
||||
if data.CapsuleId < 300009 {
|
||||
go c.FightC.UseSkill(c, 0)
|
||||
go c.UseSkill(0)
|
||||
return nil, -1
|
||||
}
|
||||
}
|
||||
go c.FightC.Capture(c, data.CapsuleId)
|
||||
go c.Capture(data.CapsuleId)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// LoadPercent 加载进度
|
||||
func (h Controller) LoadPercent(data *fight.LoadPercentInboundInfo, c *player.Player) (result *info.LoadPercentOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) LoadPercent(data *LoadPercentInboundInfo, c *player.Player) (result *info.LoadPercentOutboundInfo, err errorcode.ErrorCode) {
|
||||
if c.FightC == nil {
|
||||
return nil, -1
|
||||
}
|
||||
go c.FightC.LoadPercent(c, int32(data.Percent))
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
go c.LoadPercent(int32(data.Percent))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// UsePetItemInboundInfo 使用宠物道具
|
||||
func (h Controller) UsePetItemInboundInfo(data *fight.UsePetItemInboundInfo, c *player.Player) (result *info.UsePetIteminfo, err errorcode.ErrorCode) {
|
||||
// UsePetItemInFight 使用宠物道具
|
||||
func (h Controller) UsePetItemInFight(data *UsePetItemInboundInfo, c *player.Player) (result *info.UsePetIteminfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
if c.GetSpace().IsTime {
|
||||
if data.ItemId < 300009 {
|
||||
go c.FightC.UseSkill(c, 0)
|
||||
go c.UseSkill(0)
|
||||
}
|
||||
}
|
||||
|
||||
go c.FightC.UseItem(c, data.CatchTime, data.ItemId)
|
||||
h.dispatchFightActionEnvelope(c, buildLegacyUseItemEnvelope(data))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// FightChat 战斗聊天
|
||||
func (h Controller) FightChat(data *fight.ChatInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) FightChat(data *ChatInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err := h.checkFightStatus(c); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
go c.FightC.Chat(c, data.Message)
|
||||
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||
return nil, -1
|
||||
}
|
||||
h.dispatchFightActionEnvelope(c, buildChatEnvelope(data))
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"blazing/common/data"
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/common/socket/errorcode"
|
||||
"strings"
|
||||
|
||||
"blazing/logic/service/fight"
|
||||
fightinfo "blazing/logic/service/fight/info"
|
||||
@@ -18,13 +17,21 @@ import (
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
const (
|
||||
rewardItemExpPool = 3
|
||||
groupBossSlotLimit = 3
|
||||
)
|
||||
|
||||
// PlayerFightBoss 挑战地图boss
|
||||
func (Controller) PlayerFightBoss(req *fight.ChallengeBossInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err = p.CanFight(); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mapNode := service.NewMapNodeService().GetDataNode(p.Info.MapID, req.BossId)
|
||||
mapNode := p.GetSpace().GetMatchedMapNode(req.BossId)
|
||||
if mapNode == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
bossConfigs, err := loadMapBossConfigs(mapNode)
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
@@ -36,14 +43,15 @@ func (Controller) PlayerFightBoss(req *fight.ChallengeBossInboundInfo, p *player
|
||||
}
|
||||
|
||||
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
|
||||
p.Fightinfo.Mode = resolveMapNodeFightMode(mapNode)
|
||||
|
||||
ai := player.NewAI_player(monsterInfo)
|
||||
ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID)
|
||||
ai.Prop[0] = 2
|
||||
ai.BossScript = bossConfigs[0].Script
|
||||
ai.AddBattleProp(0, 2)
|
||||
|
||||
var fightC *fight.FightC
|
||||
fightC, err = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||
fightC, err = startMapBossFight(mapNode, p, ai, func(foi model.FightOverInfo) {
|
||||
if mapNode.WinBonusID == 0 {
|
||||
return
|
||||
}
|
||||
@@ -58,13 +66,40 @@ func (Controller) PlayerFightBoss(req *fight.ChallengeBossInboundInfo, p *player
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func startMapBossFight(
|
||||
mapNode *configmodel.MapNode,
|
||||
p *player.Player,
|
||||
ai *player.AI_player,
|
||||
fn func(model.FightOverInfo),
|
||||
) (*fight.FightC, errorcode.ErrorCode) {
|
||||
ourPets := p.GetPetInfo(p.CurrentMapPetLevelLimit())
|
||||
oppPets := ai.GetPetInfo(0)
|
||||
if mapNode != nil && mapNode.IsGroupBoss != 0 {
|
||||
if len(ourPets) > 0 && len(oppPets) > 0 {
|
||||
slotLimit := groupBossSlotLimit
|
||||
if mapNode.PkFlag != 0 {
|
||||
slotLimit = 1
|
||||
}
|
||||
return fight.NewLegacyGroupFightSingleController(p, ai, ourPets, oppPets, slotLimit, fn)
|
||||
}
|
||||
}
|
||||
return fight.NewFight(p, ai, ourPets, oppPets, fn)
|
||||
}
|
||||
|
||||
func resolveMapNodeFightMode(mapNode *configmodel.MapNode) uint32 {
|
||||
if mapNode != nil && mapNode.PkFlag != 0 {
|
||||
return fightinfo.BattleMode.SINGLE_MODE
|
||||
}
|
||||
return fightinfo.BattleMode.MULTI_MODE
|
||||
}
|
||||
|
||||
// OnPlayerFightNpcMonster 战斗野怪
|
||||
func (Controller) OnPlayerFightNpcMonster(req *fight.FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if err = p.CanFight(); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if req.Number > 9 {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
if int(req.Number) >= len(p.Data) {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotHere
|
||||
}
|
||||
|
||||
refPet := p.Data[req.Number]
|
||||
@@ -79,7 +114,7 @@ func (Controller) OnPlayerFightNpcMonster(req *fight.FightNpcMonsterInboundInfo,
|
||||
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
|
||||
|
||||
_, err = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||
_, err = fight.NewFight(p, ai, p.GetPetInfo(p.CurrentMapPetLevelLimit()), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||
handleNpcFightRewards(p, foi, monster)
|
||||
})
|
||||
if err != 0 {
|
||||
@@ -201,7 +236,7 @@ func shouldGrantBossWinBonus(fightC *fight.FightC, playerID uint32, bossConfig c
|
||||
|
||||
func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) {
|
||||
if refPet.ID == 0 {
|
||||
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotHere
|
||||
}
|
||||
|
||||
monster := model.GenPetInfo(
|
||||
@@ -250,7 +285,7 @@ func handleNpcFightRewards(p *player.Player, foi model.FightOverInfo, monster *m
|
||||
rewards := &fightinfo.S2C_GET_BOSS_MONSTER{}
|
||||
|
||||
p.ItemAdd(3, int64(poolexp+addexp))
|
||||
rewards.ADDitem(3, uint32(poolexp))
|
||||
rewards.AddItem(rewardItemExpPool, uint32(poolexp))
|
||||
p.AddPetExp(foi.Winpet, int64(addexp))
|
||||
|
||||
if p.CanGetItem() {
|
||||
@@ -258,7 +293,7 @@ func handleNpcFightRewards(p *player.Player, foi model.FightOverInfo, monster *m
|
||||
if itemID != 0 {
|
||||
count := uint32(grand.N(1, 2))
|
||||
if p.ItemAdd(itemID, int64(count)) {
|
||||
rewards.ADDitem(uint32(itemID), count)
|
||||
rewards.AddItem(uint32(itemID), count)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,10 +303,12 @@ func handleNpcFightRewards(p *player.Player, foi model.FightOverInfo, monster *m
|
||||
xuanID := uint32(400686 + petType)
|
||||
count := uint32(grand.N(1, 2))
|
||||
if p.ItemAdd(int64(xuanID), int64(count)) {
|
||||
rewards.ADDitem(xuanID, count)
|
||||
rewards.AddItem(xuanID, count)
|
||||
}
|
||||
}
|
||||
|
||||
p.SendPackCmd(8004, rewards)
|
||||
foi.Winpet.AddEV(gconv.Int64s(strings.Fields(petCfg.YieldingEV)))
|
||||
if rewards.HasReward() {
|
||||
p.SendPackCmd(8004, rewards)
|
||||
}
|
||||
foi.Winpet.AddEV(petCfg.YieldingEVValues)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
//大乱斗
|
||||
|
||||
func (h Controller) PetMelee(data *fight.StartPetWarInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) JoinPetMelee(data *StartPetWarInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
c.Fightinfo.Mode = info.BattleMode.PET_MELEE
|
||||
c.Fightinfo.Status = info.BattleMode.PET_MELEE
|
||||
@@ -70,9 +70,12 @@ func (h Controller) PetMelee(data *fight.StartPetWarInboundInfo, c *player.Playe
|
||||
|
||||
return
|
||||
}
|
||||
func (h Controller) PetKing(data *fight.PetKingJoinInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
// JoinPetKing 处理控制器请求。
|
||||
func (h Controller) JoinPetKing(data *PetKingJoinInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
c.Fightinfo.Status = info.BattleMode.PET_TOPLEVEL
|
||||
// ElementTypeNumbers 是控制器层共享变量。
|
||||
var ElementTypeNumbers = []int{1, 2, 3, 5, 11, 4, 6, 7, 9}
|
||||
|
||||
switch data.Type {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// 接收战斗或者取消战斗的包
|
||||
func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) OnPlayerHandleFightInvite(data *HandleFightInviteInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if c.IsArenaPVPLocked() {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
|
||||
return
|
||||
}
|
||||
|
||||
_, err = fight.NewFight(v, c, v.GetInfo().PetList, c.GetInfo().PetList, func(foi model.FightOverInfo) {
|
||||
_, err = fight.NewFight(v, c, v.GetPetInfo(100), c.GetPetInfo(100), func(foi model.FightOverInfo) {
|
||||
|
||||
//println("好友对战测试", foi.Reason)
|
||||
|
||||
@@ -78,7 +78,7 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
|
||||
}
|
||||
|
||||
// 邀请其他人进行战斗
|
||||
func (h Controller) OnPlayerInviteOtherFight(data *fight.InviteToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) OnPlayerInviteOtherFight(data *InviteToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if c.IsArenaPVPLocked() {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func (h Controller) OnPlayerInviteOtherFight(data *fight.InviteToFightInboundInf
|
||||
}
|
||||
|
||||
// 取消队列
|
||||
func (h Controller) OnPlayerCanceledOtherInviteFight(data *fight.InviteFightCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) OnPlayerCanceledOtherInviteFight(data *InviteFightCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
atomic.StoreUint32(&c.Fightinfo.Mode, 0) //设置状态为0
|
||||
return
|
||||
}
|
||||
|
||||
91
logic/controller/fight_unified.go
Normal file
91
logic/controller/fight_unified.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/fight/pvp"
|
||||
"blazing/logic/service/player"
|
||||
)
|
||||
|
||||
func (h Controller) relayRemoteFightCommand(c *player.Player, cmd uint32, data any) bool {
|
||||
if c == nil || c.FightC == nil || cmd == 0 {
|
||||
return false
|
||||
}
|
||||
remote, ok := c.GetFightController().(*pvp.RemoteFightProxy)
|
||||
if !ok || remote == nil {
|
||||
return false
|
||||
}
|
||||
return remote.RelayClientCommand(cmd, data)
|
||||
}
|
||||
|
||||
// dispatchFightActionEnvelope 把控制器层收到的统一动作结构分发回现有 FightI 接口。
|
||||
func (h Controller) dispatchFightActionEnvelope(c *player.Player, envelope fight.FightActionEnvelope) {
|
||||
if c == nil || c.FightC == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch envelope.ActionType {
|
||||
case fight.FightActionTypeSkill:
|
||||
go c.UseSkillAt(envelope.SkillID, envelope.ActorIndex, envelope.EncodedTargetIndex())
|
||||
case fight.FightActionTypeItem:
|
||||
go c.UseItemAt(envelope.CatchTime, envelope.ItemID, envelope.ActorIndex, envelope.EncodedTargetIndex())
|
||||
case fight.FightActionTypeChange:
|
||||
go c.ChangePetAt(envelope.CatchTime, envelope.ActorIndex)
|
||||
case fight.FightActionTypeEscape:
|
||||
go c.Over(model.BattleOverReason.PlayerEscape)
|
||||
case fight.FightActionTypeChat:
|
||||
go c.Chat(envelope.Chat)
|
||||
}
|
||||
}
|
||||
|
||||
// buildLegacyUseSkillEnvelope 把旧 2405 技能包映射成统一动作结构。
|
||||
func buildLegacyUseSkillEnvelope(data *UseSkillInInfo) fight.FightActionEnvelope {
|
||||
if data == nil {
|
||||
return fight.NewSkillActionEnvelope(0, 0, 0, fight.SkillTargetOpponent, 0)
|
||||
}
|
||||
return fight.NewSkillActionEnvelope(data.SkillId, 0, 0, fight.SkillTargetOpponent, 0)
|
||||
}
|
||||
|
||||
// buildIndexedUseSkillEnvelope 把 7505 多战位技能包映射成统一动作结构。
|
||||
func buildIndexedUseSkillEnvelope(data *UseSkillAtInboundInfo) fight.FightActionEnvelope {
|
||||
if data == nil {
|
||||
return fight.NewSkillActionEnvelope(0, 0, 0, fight.SkillTargetOpponent, 0)
|
||||
}
|
||||
return fight.NewSkillActionEnvelope(
|
||||
data.SkillId,
|
||||
int(data.ActorIndex),
|
||||
int(data.TargetIndex),
|
||||
data.TargetRelation,
|
||||
data.AtkType,
|
||||
)
|
||||
}
|
||||
|
||||
// buildLegacyUseItemEnvelope 把旧 2406 道具包映射成统一动作结构。
|
||||
func buildLegacyUseItemEnvelope(data *UsePetItemInboundInfo) fight.FightActionEnvelope {
|
||||
if data == nil {
|
||||
return fight.NewItemActionEnvelope(0, 0, 0, 0, fight.SkillTargetOpponent)
|
||||
}
|
||||
return fight.NewItemActionEnvelope(data.CatchTime, data.ItemId, 0, 0, fight.SkillTargetOpponent)
|
||||
}
|
||||
|
||||
// buildLegacyChangeEnvelope 把旧 2407 切宠包映射成统一动作结构。
|
||||
func buildLegacyChangeEnvelope(data *ChangePetInboundInfo) fight.FightActionEnvelope {
|
||||
if data == nil {
|
||||
return fight.NewChangeActionEnvelope(0, 0)
|
||||
}
|
||||
return fight.NewChangeActionEnvelope(data.CatchTime, 0)
|
||||
}
|
||||
|
||||
// buildLegacyEscapeEnvelope 构造旧 2410 逃跑包对应的统一动作结构。
|
||||
func buildLegacyEscapeEnvelope() fight.FightActionEnvelope {
|
||||
return fight.NewEscapeActionEnvelope()
|
||||
}
|
||||
|
||||
// buildChatEnvelope 把战斗聊天包映射成统一动作结构。
|
||||
func buildChatEnvelope(data *ChatInfo) fight.FightActionEnvelope {
|
||||
if data == nil {
|
||||
return fight.NewChatActionEnvelope("")
|
||||
}
|
||||
return fight.NewChatActionEnvelope(data.Message)
|
||||
}
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
@@ -36,7 +35,7 @@ type towerChoiceState struct {
|
||||
}
|
||||
|
||||
// 暗黑门进入boss
|
||||
func (h Controller) FreshOpen(data *fight.C2S_OPEN_DARKPORTAL, c *player.Player) (result *fight.S2C_OPEN_DARKPORTAL, err errorcode.ErrorCode) {
|
||||
func (h Controller) OpenDarkPortal(data *C2S_OPEN_DARKPORTAL, c *player.Player) (result *fight.S2C_OPEN_DARKPORTAL, err errorcode.ErrorCode) {
|
||||
result = &fight.S2C_OPEN_DARKPORTAL{}
|
||||
|
||||
towerBosses := service.NewTower110Service().Boss(uint32(data.Level))
|
||||
@@ -56,8 +55,8 @@ func (h Controller) FreshOpen(data *fight.C2S_OPEN_DARKPORTAL, c *player.Player)
|
||||
return result, 0
|
||||
}
|
||||
|
||||
// FreshChoiceFightLevel 处理玩家选择挑战模式(试炼之塔或勇者之塔)
|
||||
func (h Controller) FreshChoiceFightLevel(data *fight.C2S_FRESH_CHOICE_FIGHT_LEVEL, c *player.Player) (result *fight.S2C_FreshChoiceLevelRequestInfo, err errorcode.ErrorCode) {
|
||||
// ChooseTowerFightLevel 处理玩家选择挑战模式(试炼之塔或勇者之塔)
|
||||
func (h Controller) ChooseTowerFightLevel(data *C2S_FRESH_CHOICE_FIGHT_LEVEL, c *player.Player) (result *fight.S2C_FreshChoiceLevelRequestInfo, err errorcode.ErrorCode) {
|
||||
result = &fight.S2C_FreshChoiceLevelRequestInfo{}
|
||||
c.Info.CurrentFreshStage = utils.Max(c.Info.CurrentFreshStage, 1)
|
||||
c.Info.CurrentStage = utils.Max(c.Info.CurrentStage, 1)
|
||||
@@ -82,7 +81,8 @@ func (h Controller) FreshChoiceFightLevel(data *fight.C2S_FRESH_CHOICE_FIGHT_LEV
|
||||
return result, 0
|
||||
}
|
||||
|
||||
func (h Controller) FreshLeaveFightLevel(data *fight.FRESH_LEAVE_FIGHT_LEVEL, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
// LeaveTowerFightLevel 处理控制器请求。
|
||||
func (h Controller) LeaveTowerFightLevel(data *FRESH_LEAVE_FIGHT_LEVEL, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
_ = data
|
||||
defer c.GetSpace().EnterMap(c)
|
||||
|
||||
@@ -92,7 +92,8 @@ func (h Controller) FreshLeaveFightLevel(data *fight.FRESH_LEAVE_FIGHT_LEVEL, c
|
||||
return result, 0
|
||||
}
|
||||
|
||||
func (h Controller) PetTawor(data *fight.StartTwarInboundInfo, c *player.Player) (result *fight.S2C_ChoiceLevelRequestInfo, err errorcode.ErrorCode) {
|
||||
// StartTowerFight 处理控制器请求。
|
||||
func (h Controller) StartTowerFight(data *StartTwarInboundInfo, c *player.Player) (result *fight.S2C_ChoiceLevelRequestInfo, err errorcode.ErrorCode) {
|
||||
if err = c.CanFight(); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,7 +111,7 @@ func (h Controller) PetTawor(data *fight.StartTwarInboundInfo, c *player.Player)
|
||||
result = &fight.S2C_ChoiceLevelRequestInfo{CurFightLevel: currentLevel}
|
||||
appendTowerNextBossPreview(&result.BossID, bossList)
|
||||
|
||||
monsterInfo, ok := buildTowerMonsterInfo(currentBoss)
|
||||
monsterInfo, bossScript, ok := buildTowerMonsterInfo(currentBoss)
|
||||
if !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
@@ -119,6 +120,7 @@ func (h Controller) PetTawor(data *fight.StartTwarInboundInfo, c *player.Player)
|
||||
c.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||
|
||||
ai := player.NewAI_player(monsterInfo)
|
||||
ai.BossScript = bossScript
|
||||
_, err = fight.NewFight(c, ai, c.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||
if foi.Reason != 0 || foi.WinnerId != c.Info.UserID {
|
||||
return
|
||||
@@ -195,46 +197,22 @@ func appendTowerNextBossPreview(dst *[]uint32, bossList []configmodel.BaseTowerC
|
||||
}
|
||||
}
|
||||
|
||||
func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.PlayerInfo, bool) {
|
||||
func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.PlayerInfo, string, bool) {
|
||||
bosses := service.NewBossService().Get(towerBoss.BossIds[0])
|
||||
if len(bosses) == 0 {
|
||||
return nil, false
|
||||
return nil, "", false
|
||||
}
|
||||
|
||||
monsterInfo := &model.PlayerInfo{Nick: towerBoss.Name}
|
||||
for i, boss := range bosses {
|
||||
monster := model.GenPetInfo(int(boss.MonID), 24, int(boss.Nature), 0, int(boss.Lv), nil, 0)
|
||||
if boss.Hp != 0 {
|
||||
monster.Hp = uint32(boss.Hp)
|
||||
monster.MaxHp = uint32(boss.Hp)
|
||||
}
|
||||
|
||||
for statIdx, prop := range boss.Prop {
|
||||
if prop != 0 {
|
||||
monster.Prop[statIdx] = prop
|
||||
}
|
||||
}
|
||||
|
||||
for skillIdx := 0; skillIdx < len(monster.SkillList) && skillIdx < len(boss.SKill); skillIdx++ {
|
||||
if boss.SKill[skillIdx] != 0 {
|
||||
monster.SkillList[skillIdx].ID = boss.SKill[skillIdx]
|
||||
}
|
||||
}
|
||||
|
||||
effects := service.NewEffectService().Args(boss.Effect)
|
||||
for _, effect := range effects {
|
||||
monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{
|
||||
Idx: uint16(effect.ID),
|
||||
EID: gconv.Uint16(effect.Eid),
|
||||
Args: gconv.Ints(effect.Args),
|
||||
})
|
||||
}
|
||||
|
||||
monster.ConfigBoss(boss.PetBaseConfig)
|
||||
appendPetEffects(monster, boss.Effect)
|
||||
monster.CatchTime = uint32(i)
|
||||
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
|
||||
}
|
||||
|
||||
return monsterInfo, true
|
||||
return monsterInfo, bosses[0].Script, true
|
||||
}
|
||||
|
||||
func handleTowerFightWin(c *player.Player, cmd uint32, taskID int, currentLevel uint32) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/rpc"
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/common"
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/pvp"
|
||||
"blazing/logic/service/player"
|
||||
"context"
|
||||
)
|
||||
@@ -15,17 +16,60 @@ type PetTOPLEVELnboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2458" struc:"skip"`
|
||||
Mode uint32 //巅峰赛对战模式 19 = 普通模式单精灵 20 = 普通模式多精灵
|
||||
|
||||
TianxuanPetIDsLen uint32 `struc:"sizeof=TianxuanPetIDs"`
|
||||
TianxuanPetIDs []uint32 `json:"tianxuanPetIds"`
|
||||
}
|
||||
|
||||
func (h Controller) JoINtop(data *PetTOPLEVELnboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
cool.RedisDo(context.TODO(), "sun:join", info.RPCFightinfo{
|
||||
PlayerID: c.Info.UserID,
|
||||
Mode: data.Mode,
|
||||
Type: 1,
|
||||
})
|
||||
|
||||
// // 类型断言为 UniversalClient
|
||||
// universalClient, _ := client.(goredis.UniversalClient)
|
||||
// repo.NewPlayerRepository(universalClient).AddPlayerToPool(context.TODO(), data.Head.UserID, 1)
|
||||
// JoinPeakQueue 处理控制器请求。
|
||||
func (h Controller) JoinPeakQueue(data *PetTOPLEVELnboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
err = pvp.JoinPeakQueue(c, data.Mode)
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
if Maincontroller.RPCClient == nil || Maincontroller.RPCClient.MatchJoinOrUpdate == nil {
|
||||
pvp.CancelPeakQueue(c)
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||
}
|
||||
fightMode, status, err := pvp.NormalizePeakMode(data.Mode)
|
||||
if err != 0 {
|
||||
pvp.CancelPeakQueue(c)
|
||||
return nil, err
|
||||
}
|
||||
joinPayload := rpc.PVPMatchJoinPayload{
|
||||
RuntimeServerID: h.UID,
|
||||
UserID: c.Info.UserID,
|
||||
Nick: c.Info.Nick,
|
||||
FightMode: fightMode,
|
||||
Status: status,
|
||||
IsVip: cool.Config.ServerInfo.IsVip,
|
||||
IsDebug: cool.Config.ServerInfo.IsDebug,
|
||||
CatchTimes: pvp.AvailableCatchTimes(c.GetPetInfo(0)),
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||
defer cancel()
|
||||
if callErr := Maincontroller.RPCClient.MatchJoinOrUpdate(ctx, joinPayload); callErr != nil {
|
||||
pvp.CancelPeakQueue(c)
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// CancelPeakQueue 处理控制器请求。
|
||||
func (h Controller) CancelPeakQueue(data *PeakQueueCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if Maincontroller.RPCClient != nil && Maincontroller.RPCClient.MatchCancel != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||
_ = Maincontroller.RPCClient.MatchCancel(ctx, c.Info.UserID)
|
||||
cancel()
|
||||
}
|
||||
pvp.CancelPeakQueue(c)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// SubmitPeakBanPick 处理控制器请求。
|
||||
func (h Controller) SubmitPeakBanPick(data *PeakBanPickSubmitInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
err = pvp.SubmitBanPick(c, data.SelectedCatchTimes, data.BanCatchTimes)
|
||||
if err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/data"
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/modules/player/model"
|
||||
"sync/atomic"
|
||||
|
||||
"blazing/logic/service/common"
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/logic/service/space"
|
||||
)
|
||||
|
||||
// ARENA_SET_OWENR 定义请求或响应数据结构。
|
||||
type ARENA_SET_OWENR struct {
|
||||
Head common.TomeeHeader `cmd:"2417" struc:"skip"`
|
||||
}
|
||||
|
||||
// ArenaSetOwner 处理玩家占据擂台的请求
|
||||
// public static const ARENA_SET_OWENR:uint = 2417;
|
||||
// 如果星际擂台上无人,站到星际擂台的包
|
||||
// 前端到后端无数据内容 空包
|
||||
// 后端到前端无数据内容 空包
|
||||
// ArenaSetOwner 都需要通过2419包广播更新擂台状态
|
||||
func (h Controller) ArenaSetOwner(data *fight.ARENA_SET_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ArenaSetOwner(data *ARENA_SET_OWENR, c *player.Player) (result *struct{}, err errorcode.ErrorCode) {
|
||||
r := c.CanFight()
|
||||
if r != 0 {
|
||||
return nil, r
|
||||
@@ -35,12 +36,15 @@ func (h Controller) ArenaSetOwner(data *fight.ARENA_SET_OWENR, c *player.Player)
|
||||
return nil, errorcode.ErrorCodes.ErrChampionExists
|
||||
}
|
||||
|
||||
// ARENA_FIGHT_OWENR 定义请求或响应数据结构。
|
||||
type ARENA_FIGHT_OWENR struct {
|
||||
Head common.TomeeHeader `cmd:"2418" struc:"skip"`
|
||||
}
|
||||
|
||||
// ArenaFightOwner 挑战擂台的包
|
||||
// 前端到后端无数据内容 空包
|
||||
// 后端到前端无数据内容 空包
|
||||
// 还是后端主动发送2503的包给双方前端后 等待前端加载完毕 主动发送2404包通知后端开始战斗
|
||||
// ArenaFightOwner 并不会通知对方是否接受挑战。只要有人挑战就直接进入对战
|
||||
func (h Controller) ArenaFightOwner(data1 *fight.ARENA_FIGHT_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ArenaFightOwner(data1 *ARENA_FIGHT_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
r := c.CanFight()
|
||||
if r != 0 {
|
||||
@@ -86,12 +90,9 @@ func (h Controller) ArenaFightOwner(data1 *fight.ARENA_FIGHT_OWENR, c *player.Pl
|
||||
if addev != 0 {
|
||||
c.Info.EVPool += addev
|
||||
|
||||
c.SendPackCmd(8004, &info.S2C_GET_BOSS_MONSTER{ //发送EV
|
||||
ItemList: []data.ItemInfo{{
|
||||
ItemId: 9,
|
||||
ItemCnt: int64(addev),
|
||||
}},
|
||||
})
|
||||
rewards := &info.S2C_GET_BOSS_MONSTER{}
|
||||
rewards.AddItem(9, uint32(addev))
|
||||
c.SendPackCmd(8004, rewards) //发送EV
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -102,12 +103,9 @@ func (h Controller) ArenaFightOwner(data1 *fight.ARENA_FIGHT_OWENR, c *player.Pl
|
||||
if addev != 0 {
|
||||
c.GetSpace().Owner.ARENA_Player.GetInfo().EVPool += addev
|
||||
|
||||
c.GetSpace().Owner.ARENA_Player.SendPackCmd(8004, &info.S2C_GET_BOSS_MONSTER{ //发送EV
|
||||
ItemList: []data.ItemInfo{{
|
||||
ItemId: 9,
|
||||
ItemCnt: int64(addev),
|
||||
}},
|
||||
})
|
||||
rewards := &info.S2C_GET_BOSS_MONSTER{}
|
||||
rewards.AddItem(9, uint32(addev))
|
||||
c.GetSpace().Owner.ARENA_Player.SendPackCmd(8004, rewards) //发送EV
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,17 +139,15 @@ func (h Controller) ArenaFightOwner(data1 *fight.ARENA_FIGHT_OWENR, c *player.Pl
|
||||
// ArenaGetInfo 获取星际擂台信息的包 进入空间站地图前端会发送请求包 或者 有人站到星际擂台上后 广播回包
|
||||
// 前端到后端无数据内容
|
||||
// ArenaGetInfo 后端到前端
|
||||
func (h Controller) ArenaGetInfo(data *fight.ARENA_GET_INFO, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) {
|
||||
func (h Controller) ArenaGetInfo(data *ARENA_GET_INFO, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) {
|
||||
|
||||
result = &c.GetSpace().Owner
|
||||
return
|
||||
}
|
||||
|
||||
// ArenaUpfight 放弃擂台挑战的包
|
||||
// 前端到后端无数据内容
|
||||
// 后端到前端无数据内容
|
||||
// ArenaUpfight 都需要通过2419包广播更新擂台状态
|
||||
func (h Controller) ArenaUpfight(data *fight.ARENA_UPFIGHT, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
// LeaveArena 放弃擂台挑战的包
|
||||
// LeaveArena 都需要通过2419包广播更新擂台状态
|
||||
func (h Controller) LeaveArena(data *ARENA_UPFIGHT, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
//原子操作,修改擂台状态
|
||||
if atomic.LoadUint32(&c.GetSpace().Owner.UserID) != c.GetInfo().UserID { //说明已经有人了
|
||||
return nil, errorcode.ErrorCodes.ErrChampionCannotCancel
|
||||
@@ -168,12 +164,12 @@ func (h Controller) ArenaUpfight(data *fight.ARENA_UPFIGHT, c *player.Player) (r
|
||||
|
||||
}
|
||||
|
||||
// ArenaOwnerAcce 此包为擂台战对战结束后 胜方前端会发送给后端 具体作用为通知后端发送2419包更新擂台信息。
|
||||
// ConfirmArenaFightResult 此包为擂台战对战结束后 胜方前端会发送给后端 具体作用为通知后端发送2419包更新擂台信息。
|
||||
// 前端到后端无数据内容
|
||||
// 后端到前端无数据内容
|
||||
// public static const ARENA_OWENR_OUT:uint = 2423;
|
||||
// ArenaOwnerAcce 此包不清楚具体怎么触发 但已知此包为后端主动发送。不清楚什么情况下回用到
|
||||
func (h Controller) ArenaOwnerAcce(data *fight.ARENA_OWENR_ACCE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
// ConfirmArenaFightResult 此包不清楚具体怎么触发 但已知此包为后端主动发送。不清楚什么情况下回用到
|
||||
func (h Controller) ConfirmArenaFightResult(data *ARENA_OWENR_ACCE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
s := c.GetSpace()
|
||||
|
||||
|
||||
198
logic/controller/inbound_fight.go
Normal file
198
logic/controller/inbound_fight.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package controller
|
||||
|
||||
import "blazing/logic/service/common"
|
||||
|
||||
// FightNpcMonsterInboundInfo 定义请求或响应数据结构。
|
||||
type FightNpcMonsterInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2408" struc:"skip"`
|
||||
Number uint32 `fieldDesc:"地图刷新怪物结构体对应的序号 1 - 9 的位置序号" `
|
||||
}
|
||||
|
||||
// ChallengeBossInboundInfo 定义请求或响应数据结构。
|
||||
type ChallengeBossInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2411" struc:"skip"`
|
||||
BossId uint32 `json:"bossId"`
|
||||
}
|
||||
|
||||
// ReadyToFightInboundInfo 定义请求或响应数据结构。
|
||||
type ReadyToFightInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2404" struc:"skip"`
|
||||
}
|
||||
|
||||
// GroupReadyFightFinishInboundInfo 旧组队协议准备完成。
|
||||
type GroupReadyFightFinishInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7556" struc:"skip"`
|
||||
}
|
||||
|
||||
type GroupUseSkillInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7558" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
TargetSide uint8
|
||||
TargetPos uint8
|
||||
SkillId uint32
|
||||
}
|
||||
|
||||
type GroupUseItemInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7562" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
ItemId uint32
|
||||
}
|
||||
|
||||
type GroupChangePetInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7563" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
CatchTime uint32
|
||||
}
|
||||
|
||||
type GroupEscapeInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7565" struc:"skip"`
|
||||
ActorIndex uint8
|
||||
}
|
||||
|
||||
type GroupFightWinCloseInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7574" struc:"skip"`
|
||||
}
|
||||
|
||||
type GroupFightTimeoutExitInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7587" struc:"skip"`
|
||||
}
|
||||
|
||||
// EscapeFightInboundInfo 定义请求或响应数据结构。
|
||||
type EscapeFightInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2410" struc:"skip"`
|
||||
}
|
||||
|
||||
// StartPetWarInboundInfo 定义请求或响应数据结构。
|
||||
type StartPetWarInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2431" struc:"skip"`
|
||||
}
|
||||
|
||||
// StartTwarInboundInfo 定义请求或响应数据结构。
|
||||
type StartTwarInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2429|2415|2425" struc:"skip"`
|
||||
}
|
||||
|
||||
// ARENA_GET_INFO 定义请求或响应数据结构。
|
||||
type ARENA_GET_INFO struct {
|
||||
Head common.TomeeHeader `cmd:"2419" struc:"skip"`
|
||||
}
|
||||
|
||||
// ARENA_UPFIGHT 定义请求或响应数据结构。
|
||||
type ARENA_UPFIGHT struct {
|
||||
Head common.TomeeHeader `cmd:"2420" struc:"skip"`
|
||||
}
|
||||
|
||||
// ARENA_OWENR_ACCE 定义请求或响应数据结构。
|
||||
type ARENA_OWENR_ACCE struct {
|
||||
Head common.TomeeHeader `cmd:"2422" struc:"skip"`
|
||||
}
|
||||
|
||||
// PetKingJoinInboundInfo 定义请求或响应数据结构。
|
||||
type PetKingJoinInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2413" struc:"skip"`
|
||||
Type uint32
|
||||
FightType uint32
|
||||
}
|
||||
|
||||
// PeakQueueCancelInboundInfo 定义请求或响应数据结构。
|
||||
type PeakQueueCancelInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2459" struc:"skip"`
|
||||
}
|
||||
|
||||
// PeakBanPickSubmitInboundInfo 定义请求或响应数据结构。
|
||||
type PeakBanPickSubmitInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2460" struc:"skip"`
|
||||
|
||||
SelectedCatchTimesLen uint32 `struc:"sizeof=SelectedCatchTimes"`
|
||||
SelectedCatchTimes []uint32 `json:"selectedCatchTimes"`
|
||||
|
||||
BanCatchTimesLen uint32 `struc:"sizeof=BanCatchTimes"`
|
||||
BanCatchTimes []uint32 `json:"banCatchTimes"`
|
||||
}
|
||||
|
||||
// HandleFightInviteInboundInfo 定义请求或响应数据结构。
|
||||
type HandleFightInviteInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2403" struc:"skip"`
|
||||
UserID uint32 `json:"userId" codec:"userId,uint"`
|
||||
Flag uint32 `json:"flag" codec:"flag,uint"`
|
||||
Mode uint32 `json:"mode" codec:"mode,uint"`
|
||||
}
|
||||
|
||||
// InviteToFightInboundInfo 定义请求或响应数据结构。
|
||||
type InviteToFightInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2401" struc:"skip"`
|
||||
UserID uint32
|
||||
Mode uint32
|
||||
}
|
||||
|
||||
// InviteFightCancelInboundInfo 定义请求或响应数据结构。
|
||||
type InviteFightCancelInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2402" struc:"skip"`
|
||||
}
|
||||
|
||||
// UseSkillInInfo 定义请求或响应数据结构。
|
||||
type UseSkillInInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2405" struc:"skip"`
|
||||
SkillId uint32
|
||||
}
|
||||
|
||||
// UseSkillAtInboundInfo 定义请求或响应数据结构。
|
||||
type UseSkillAtInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"7505" struc:"skip"`
|
||||
SkillId uint32 `json:"skillId"`
|
||||
ActorIndex uint8 `json:"actorIndex"`
|
||||
TargetIndex uint8 `json:"targetIndex"`
|
||||
TargetRelation uint8 `json:"targetRelation"`
|
||||
AtkType uint8 `json:"atkType"`
|
||||
}
|
||||
|
||||
// ChangePetInboundInfo 定义请求或响应数据结构。
|
||||
type ChangePetInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2407" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
}
|
||||
|
||||
// CatchMonsterInboundInfo 定义请求或响应数据结构。
|
||||
type CatchMonsterInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2409" struc:"skip"`
|
||||
CapsuleId uint32 `json:"capsuleId" fieldDescription:"胶囊id" uint:"true"`
|
||||
}
|
||||
|
||||
// LoadPercentInboundInfo 定义请求或响应数据结构。
|
||||
type LoadPercentInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2441" struc:"skip"`
|
||||
Percent uint32 `fieldDescription:"加载百分比"`
|
||||
}
|
||||
|
||||
// UsePetItemInboundInfo 定义请求或响应数据结构。
|
||||
type UsePetItemInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2406" struc:"skip"`
|
||||
CatchTime uint32 `description:"精灵捕获时间" codec:"catchTime"`
|
||||
ItemId uint32 `description:"使用的物品ID" codec:"itemId"`
|
||||
Reversed1 uint32 `description:"填充字段 0" codec:"reversed1"`
|
||||
}
|
||||
|
||||
// ChatInfo 定义请求或响应数据结构。
|
||||
type ChatInfo struct {
|
||||
Head common.TomeeHeader `cmd:"50002" struc:"skip"`
|
||||
Reserve uint32 `json:"reserve" fieldDescription:"填充 默认值为0" uint:"true"`
|
||||
MessageLen uint32 `struc:"sizeof=Message"`
|
||||
Message string `json:"message" fieldDescription:"消息内容, 结束符为utf-8的数字0"`
|
||||
}
|
||||
|
||||
// C2S_FRESH_CHOICE_FIGHT_LEVEL 定义请求或响应数据结构。
|
||||
type C2S_FRESH_CHOICE_FIGHT_LEVEL struct {
|
||||
Head common.TomeeHeader `cmd:"2428|2414" struc:"skip"`
|
||||
Level uint `json:"level"`
|
||||
}
|
||||
|
||||
// C2S_OPEN_DARKPORTAL 定义请求或响应数据结构。
|
||||
type C2S_OPEN_DARKPORTAL struct {
|
||||
Head common.TomeeHeader `cmd:"2424" struc:"skip"`
|
||||
Level uint32 `json:"level"`
|
||||
}
|
||||
|
||||
// FRESH_LEAVE_FIGHT_LEVEL 定义请求或响应数据结构。
|
||||
type FRESH_LEAVE_FIGHT_LEVEL struct {
|
||||
Head common.TomeeHeader `cmd:"2430|2416|2426" struc:"skip"`
|
||||
}
|
||||
61
logic/controller/inbound_friend_task.go
Normal file
61
logic/controller/inbound_friend_task.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package controller
|
||||
|
||||
import "blazing/logic/service/common"
|
||||
|
||||
// SeeOnlineInboundInfo 定义请求或响应数据结构。
|
||||
type SeeOnlineInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2157" struc:"skip"`
|
||||
UserIdsLen uint32 `json:"userIdsLen" struc:"sizeof=UserIds"`
|
||||
UserIds []uint32 `json:"userIds" `
|
||||
}
|
||||
|
||||
// FriendAddInboundInfo 定义请求或响应数据结构。
|
||||
type FriendAddInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2151" struc:"skip"`
|
||||
UserID uint32 `json:"userID"`
|
||||
}
|
||||
|
||||
// FriendAnswerInboundInfo 定义请求或响应数据结构。
|
||||
type FriendAnswerInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2152" struc:"skip"`
|
||||
UserID uint32 `json:"userID"`
|
||||
Flag uint32 `json:"flag"`
|
||||
}
|
||||
|
||||
// FriendRemoveInboundInfo 定义请求或响应数据结构。
|
||||
type FriendRemoveInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2153" struc:"skip"`
|
||||
UserID uint32 `json:"userID"`
|
||||
}
|
||||
|
||||
// AcceptTaskInboundInfo 定义请求或响应数据结构。
|
||||
type AcceptTaskInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2201|2231" struc:"skip"`
|
||||
TaskId uint32 `json:"taskId" description:"任务ID"`
|
||||
}
|
||||
|
||||
// AddTaskBufInboundInfo 定义请求或响应数据结构。
|
||||
type AddTaskBufInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2204|2235" struc:"skip"`
|
||||
TaskId uint32 `json:"taskId" description:"任务ID"`
|
||||
TaskList []uint32 `struc:"[20]byte"`
|
||||
}
|
||||
|
||||
// CompleteTaskInboundInfo 定义请求或响应数据结构。
|
||||
type CompleteTaskInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2202|2233" struc:"skip"`
|
||||
TaskId uint32 `json:"taskId" description:"任务ID"`
|
||||
OutState uint32 `json:"outState" `
|
||||
}
|
||||
|
||||
// GetTaskBufInboundInfo 定义请求或响应数据结构。
|
||||
type GetTaskBufInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2203|2234" struc:"skip"`
|
||||
TaskId uint32 `json:"taskId" description:"任务ID"`
|
||||
}
|
||||
|
||||
// DeleteTaskInboundInfo 定义请求或响应数据结构。
|
||||
type DeleteTaskInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2205|2232" struc:"skip"`
|
||||
TaskId uint32 `json:"taskId" description:"任务ID"`
|
||||
}
|
||||
102
logic/controller/inbound_item.go
Normal file
102
logic/controller/inbound_item.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package controller
|
||||
|
||||
import "blazing/logic/service/common"
|
||||
|
||||
// BuyInboundInfo 定义请求或响应数据结构。
|
||||
type BuyInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2601" struc:"skip"`
|
||||
ItemId int64 `struc:"uint32"`
|
||||
Count int64 `struc:"uint32"`
|
||||
}
|
||||
|
||||
// BuyMultiInboundInfo 定义请求或响应数据结构。
|
||||
type BuyMultiInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2606" struc:"skip"`
|
||||
ItemListLen uint32 `struc:"sizeof=ItemIds"`
|
||||
ItemIds []uint32 `json:"itemIds" description:"购买的物品ID列表"`
|
||||
}
|
||||
|
||||
// C2S_GOLD_BUY_PRODUCT 定义请求或响应数据结构。
|
||||
type C2S_GOLD_BUY_PRODUCT struct {
|
||||
Head common.TomeeHeader `cmd:"1104" struc:"skip"`
|
||||
Type uint32 `json:"type"`
|
||||
ProductID uint32 `json:"product_id"`
|
||||
Count int64 `struc:"uint32"`
|
||||
}
|
||||
|
||||
// ItemListInboundInfo 定义请求或响应数据结构。
|
||||
type ItemListInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2605|4475" struc:"skip"`
|
||||
Param1 uint32
|
||||
Param2 uint32
|
||||
Param3 uint32
|
||||
}
|
||||
|
||||
// GoldOnlineRemainInboundInfo 定义请求或响应数据结构。
|
||||
type GoldOnlineRemainInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"1105|1106" struc:"skip"`
|
||||
}
|
||||
|
||||
// ExpTotalRemainInboundInfo 定义请求或响应数据结构。
|
||||
type ExpTotalRemainInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2319" struc:"skip"`
|
||||
}
|
||||
|
||||
// ChangePlayerClothInboundInfo 定义请求或响应数据结构。
|
||||
type ChangePlayerClothInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2604" struc:"skip"`
|
||||
ClothesLen uint32 `struc:"sizeof=ClothList" fieldDesc:"穿戴装备的信息" json:"clothes_len"`
|
||||
ClothList []uint32 `description:"玩家装备列表" codec:"list"`
|
||||
}
|
||||
|
||||
// TalkCountInboundInfo 定义请求或响应数据结构。
|
||||
type TalkCountInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2701" struc:"skip"`
|
||||
ID uint32 `description:"奖品的Type, 即ID" codec:"uint"`
|
||||
}
|
||||
|
||||
// TalkCateInboundInfo 定义请求或响应数据结构。
|
||||
type TalkCateInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2702" struc:"skip"`
|
||||
ID uint32 `description:"奖品的Type, 即ID" codec:"uint"`
|
||||
}
|
||||
|
||||
// C2S_USE_PET_ITEM_OUT_OF_FIGHT 定义请求或响应数据结构。
|
||||
type C2S_USE_PET_ITEM_OUT_OF_FIGHT struct {
|
||||
Head common.TomeeHeader `cmd:"2326" struc:"skip"`
|
||||
CatchTime uint32 `json:"catch_time"`
|
||||
ItemID int32 `struc:"uint32"`
|
||||
}
|
||||
|
||||
// C2S_PET_RESET_NATURE 定义请求或响应数据结构。
|
||||
type C2S_PET_RESET_NATURE struct {
|
||||
Head common.TomeeHeader `cmd:"2343" struc:"skip"`
|
||||
CatchTime uint32
|
||||
Nature uint32
|
||||
ItemId uint32
|
||||
}
|
||||
|
||||
// C2S_ITEM_SALE 定义请求或响应数据结构。
|
||||
type C2S_ITEM_SALE struct {
|
||||
Head common.TomeeHeader `cmd:"2602" struc:"skip"`
|
||||
ItemId uint32
|
||||
Amount uint32
|
||||
}
|
||||
|
||||
// C2S_USE_SPEEDUP_ITEM 定义请求或响应数据结构。
|
||||
type C2S_USE_SPEEDUP_ITEM struct {
|
||||
Head common.TomeeHeader `cmd:"2327" struc:"skip"`
|
||||
ItemID uint32
|
||||
}
|
||||
|
||||
// C2S_USE_ENERGY_XISHOU 定义请求或响应数据结构。
|
||||
type C2S_USE_ENERGY_XISHOU struct {
|
||||
Head common.TomeeHeader `cmd:"2331" struc:"skip"`
|
||||
ItemID uint32
|
||||
}
|
||||
|
||||
// C2S_USE_AUTO_FIGHT_ITEM 定义请求或响应数据结构。
|
||||
type C2S_USE_AUTO_FIGHT_ITEM struct {
|
||||
Head common.TomeeHeader `cmd:"2329" struc:"skip"`
|
||||
ItemID uint32
|
||||
}
|
||||
126
logic/controller/inbound_map_room_nono.go
Normal file
126
logic/controller/inbound_map_room_nono.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/logic/service/common"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// EnterMapInboundInfo 定义请求或响应数据结构。
|
||||
type EnterMapInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2001" struc:"skip"`
|
||||
MapType uint32
|
||||
MapId uint32
|
||||
Point model.Pos `fieldDesc:"直接给坐标x,y"`
|
||||
}
|
||||
|
||||
// GetMapHotInboundInfo 定义请求或响应数据结构。
|
||||
type GetMapHotInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"1004" struc:"skip"`
|
||||
}
|
||||
|
||||
// LeaveMapInboundInfo 定义请求或响应数据结构。
|
||||
type LeaveMapInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2002" struc:"skip"`
|
||||
}
|
||||
|
||||
// ListMapPlayerInboundInfo 定义请求或响应数据结构。
|
||||
type ListMapPlayerInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2003" struc:"skip"`
|
||||
}
|
||||
|
||||
// AttackBossInboundInfo 定义请求或响应数据结构。
|
||||
type AttackBossInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2412" struc:"skip"`
|
||||
}
|
||||
|
||||
// WalkInInfo 定义请求或响应数据结构。
|
||||
type WalkInInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2101" struc:"skip"`
|
||||
Flag uint32
|
||||
Point model.Pos `fieldDesc:"直接给坐标x,y"`
|
||||
PathLen uint32 `struc:"sizeof=Path"`
|
||||
Path string
|
||||
}
|
||||
|
||||
// FitmentUseringInboundInfo 定义请求或响应数据结构。
|
||||
type FitmentUseringInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"10006" struc:"skip"`
|
||||
TargetUserID uint32 `json:"targetUserId"`
|
||||
}
|
||||
|
||||
// PetRoomListInboundInfo 定义请求或响应数据结构。
|
||||
type PetRoomListInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2324" struc:"skip"`
|
||||
TargetUserID uint32 `json:"targetUserId"`
|
||||
}
|
||||
|
||||
// FitmentAllInboundEmpty 定义请求或响应数据结构。
|
||||
type FitmentAllInboundEmpty struct {
|
||||
Head common.TomeeHeader `cmd:"10007" struc:"skip"`
|
||||
}
|
||||
|
||||
// SET_FITMENT 定义请求或响应数据结构。
|
||||
type SET_FITMENT struct {
|
||||
Head common.TomeeHeader `cmd:"10008" struc:"skip"`
|
||||
RoomID uint32 `json:"roomID"`
|
||||
FitmentsLen uint32 `json:"fitmentsLen" struc:"sizeof=Fitments"`
|
||||
Fitments []model.FitmentShowInfo `json:"usedList"`
|
||||
}
|
||||
|
||||
// C2S_PetShowList 定义请求或响应数据结构。
|
||||
type C2S_PetShowList struct {
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
PetID uint32 `json:"petID"`
|
||||
}
|
||||
|
||||
// C2S_PET_ROOM_SHOW 定义请求或响应数据结构。
|
||||
type C2S_PET_ROOM_SHOW struct {
|
||||
Head common.TomeeHeader `cmd:"2323" struc:"skip"`
|
||||
PetShowInfoLen uint32 `json:"PetShowInfoLen" struc:"sizeof=PetShowList"`
|
||||
PetShowList []C2S_PetShowList `json:"PetShowList"`
|
||||
}
|
||||
|
||||
// C2S_RoomPetInfo 定义请求或响应数据结构。
|
||||
type C2S_RoomPetInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2325" struc:"skip"`
|
||||
UserID uint32 `json:"userID"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
}
|
||||
|
||||
// C2S_RoomPetShowToggle 基地展示精灵添加/移除请求
|
||||
type C2S_RoomPetShowToggle struct {
|
||||
Head common.TomeeHeader `cmd:"2326" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
PetID uint32 `json:"petID"`
|
||||
Flag uint32 `json:"flag"` // 1=添加展示, 0=移除展示
|
||||
}
|
||||
|
||||
// C2S_BUY_FITMENT 定义请求或响应数据结构。
|
||||
type C2S_BUY_FITMENT struct {
|
||||
Head common.TomeeHeader `cmd:"10004" struc:"skip"`
|
||||
ID uint32 `json:"id"`
|
||||
Count uint32 `json:"count"`
|
||||
}
|
||||
|
||||
// NonoInboundInfo 定义请求或响应数据结构。
|
||||
type NonoInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"9003" struc:"skip"`
|
||||
UserID uint32
|
||||
}
|
||||
|
||||
// NonoFollowOrHomeInInfo 定义请求或响应数据结构。
|
||||
type NonoFollowOrHomeInInfo struct {
|
||||
Head common.TomeeHeader `cmd:"9019" struc:"skip"`
|
||||
Flag uint32 `fieldDescription:"1为跟随 0为收回 且如果为收回 那么后续结构不需要发送" uint:"true"`
|
||||
}
|
||||
|
||||
// SwitchFlyingInboundInfo 定义请求或响应数据结构。
|
||||
type SwitchFlyingInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2112" struc:"skip"`
|
||||
Type uint32 `description:"开关, 0为取消飞行模式, 大于0为开启飞行模式" codec:"auto" uint:"true"`
|
||||
}
|
||||
|
||||
// PetCureInboundInfo 定义请求或响应数据结构。
|
||||
type PetCureInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2306" struc:"skip"`
|
||||
}
|
||||
114
logic/controller/inbound_pet.go
Normal file
114
logic/controller/inbound_pet.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package controller
|
||||
|
||||
import "blazing/logic/service/common"
|
||||
|
||||
// GetPetInfoInboundInfo 定义请求或响应数据结构。
|
||||
type GetPetInfoInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2301" struc:"skip"`
|
||||
CatchTime uint32
|
||||
}
|
||||
|
||||
// GetUserBagPetInfoInboundEmpty 定义请求或响应数据结构。
|
||||
type GetUserBagPetInfoInboundEmpty struct {
|
||||
Head common.TomeeHeader `cmd:"4483" struc:"skip"`
|
||||
}
|
||||
|
||||
// SavePetBagOrderInboundInfo 定义请求或响应数据结构。
|
||||
type SavePetBagOrderInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"4484" struc:"skip"`
|
||||
|
||||
PetListLen uint32 `struc:"int32,sizeof=PetList"`
|
||||
PetList []uint32
|
||||
BackupPetListLen uint32 `struc:"int32,sizeof=BackupPetList"`
|
||||
BackupPetList []uint32
|
||||
}
|
||||
|
||||
// PetReleaseInboundInfo 定义请求或响应数据结构。
|
||||
type PetReleaseInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2304" struc:"skip"`
|
||||
CatchTime uint32
|
||||
Flag uint32 `json:"flag" fieldDescription:"0为放入仓库,1为放入背包" autoCodec:"true" uint:"true"`
|
||||
}
|
||||
|
||||
// PetShowInboundInfo 定义请求或响应数据结构。
|
||||
type PetShowInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2305" struc:"skip"`
|
||||
CatchTime uint32 `codec:"catchTime" inboundMessageType:"Pet_Show"`
|
||||
Flag uint32 `codec:"flag"`
|
||||
}
|
||||
|
||||
// PetOneCureInboundInfo 定义请求或响应数据结构。
|
||||
type PetOneCureInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2310" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true"`
|
||||
}
|
||||
|
||||
// PET_ROWEI 定义请求或响应数据结构。
|
||||
type PET_ROWEI struct {
|
||||
Head common.TomeeHeader `cmd:"2321" struc:"skip"`
|
||||
ID uint32
|
||||
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true"`
|
||||
}
|
||||
|
||||
// PET_RETRIEVE 定义请求或响应数据结构。
|
||||
type PET_RETRIEVE struct {
|
||||
Head common.TomeeHeader `cmd:"2322" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true"`
|
||||
}
|
||||
|
||||
// PetDefaultInboundInfo 定义请求或响应数据结构。
|
||||
type PetDefaultInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2308" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true" autoCodec:"true" inboundMessageType:"Pet_Default"`
|
||||
}
|
||||
|
||||
// PetSetExpInboundInfo 定义请求或响应数据结构。
|
||||
type PetSetExpInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2318" struc:"skip"`
|
||||
CatchTime uint32 `fieldDescription:"精灵获取时间" uint:"true" autoCodec:"true"`
|
||||
Exp int64 `struc:"uint32"`
|
||||
}
|
||||
|
||||
// PetBargeListInboundInfo 定义请求或响应数据结构。
|
||||
type PetBargeListInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2309" struc:"skip"`
|
||||
StartPetId uint32 `description:"开始精灵id" codec:"startPetId"`
|
||||
EndPetId uint32 `description:"结束精灵id" codec:"endPetId"`
|
||||
}
|
||||
|
||||
// ChangeSkillInfo 定义请求或响应数据结构。
|
||||
type ChangeSkillInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2312" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
Reserved uint32 `json:"reserved"`
|
||||
Reserved1 uint32 `json:"reserved1"`
|
||||
HasSkill uint32 `json:"hasSkill"`
|
||||
ReplaceSkill uint32 `json:"replaceSkill"`
|
||||
}
|
||||
|
||||
// C2S_Skill_Sort 定义请求或响应数据结构。
|
||||
type C2S_Skill_Sort struct {
|
||||
Head common.TomeeHeader `cmd:"2328" struc:"skip"`
|
||||
CapTm uint32 `json:"capTm"`
|
||||
Skill [4]uint32 `json:"skill_1"`
|
||||
}
|
||||
|
||||
// GetPetLearnableSkillsInboundInfo 查询当前精灵可学习技能(含额外技能ExtSKill)
|
||||
type GetPetLearnableSkillsInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"52312" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
}
|
||||
|
||||
type CommitPetSkillsInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"52313" struc:"skip"`
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
Skill [4]uint32 `json:"skill"`
|
||||
}
|
||||
|
||||
type C2S_PetFusion struct {
|
||||
Head common.TomeeHeader `cmd:"2351" struc:"skip"`
|
||||
Mcatchtime uint32 `json:"mcatchtime" msgpack:"mcatchtime"`
|
||||
Auxcatchtime uint32 `json:"auxcatchtime" msgpack:"auxcatchtime"`
|
||||
Item1 [4]uint32 `json:"item1" msgpack:"item1"`
|
||||
GoldItem1 [2]uint32 `json:"gold_item1" msgpack:"gold_item1"`
|
||||
}
|
||||
111
logic/controller/inbound_user.go
Normal file
111
logic/controller/inbound_user.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/common"
|
||||
"blazing/modules/player/model"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
)
|
||||
|
||||
// MAIN_LOGIN_IN 定义请求或响应数据结构。
|
||||
type MAIN_LOGIN_IN struct {
|
||||
Head common.TomeeHeader `cmd:"1001" struc:"skip"`
|
||||
Sid []byte `struc:"[16]byte"`
|
||||
}
|
||||
|
||||
// CheckSession 校验登录session。
|
||||
func (l *MAIN_LOGIN_IN) CheckSession() (bool, uint32) {
|
||||
t1 := hex.EncodeToString(l.Sid)
|
||||
r, err := cool.CacheManager.Get(context.Background(), fmt.Sprintf("session:%d", l.Head.UserID))
|
||||
if err != nil {
|
||||
return false, 0
|
||||
}
|
||||
if r.String() != t1 {
|
||||
return false, 0
|
||||
}
|
||||
crc32Table := crc32.MakeTable(crc32.IEEE)
|
||||
crcValue := crc32.Checksum([]byte(l.Sid), crc32Table)
|
||||
cool.CacheManager.Remove(context.Background(), fmt.Sprintf("session:%d", l.Head.UserID))
|
||||
return true, crcValue
|
||||
}
|
||||
|
||||
// SimUserInfoInboundInfo 定义请求或响应数据结构。
|
||||
type SimUserInfoInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2051" struc:"skip"`
|
||||
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
|
||||
}
|
||||
|
||||
// MoreUserInfoInboundInfo 定义请求或响应数据结构。
|
||||
type MoreUserInfoInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2052" struc:"skip"`
|
||||
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
|
||||
}
|
||||
|
||||
// AimatInboundInfo 定义请求或响应数据结构。
|
||||
type AimatInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2104" struc:"skip"`
|
||||
ItemId uint32 `description:"物品id 射击激光 物品id为0" codec:"auto" uint:"true"`
|
||||
ShootType uint32 `description:"射击类型 未知 给0" codec:"auto" uint:"true"`
|
||||
Point model.Pos `description:"射击的坐标 x y" codec:"auto"`
|
||||
}
|
||||
|
||||
// ChatInboundInfo 定义请求或响应数据结构。
|
||||
type ChatInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2102" struc:"skip"`
|
||||
Reserve uint32 `json:"reserve" fieldDescription:"填充 默认值为0" uint:"true"`
|
||||
MessageLen uint32 `struc:"sizeof=Message"`
|
||||
Message string `json:"message" fieldDescription:"消息内容, 结束符为utf-8的数字0"`
|
||||
}
|
||||
|
||||
// ChangeColorInboundInfo 定义请求或响应数据结构。
|
||||
type ChangeColorInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2063" struc:"skip"`
|
||||
Color uint32 `codec:"color"`
|
||||
}
|
||||
|
||||
// ChangeDoodleInboundInfo 定义请求或响应数据结构。
|
||||
type ChangeDoodleInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2062" struc:"skip"`
|
||||
Id uint32 `codec:"id"`
|
||||
Color uint32 `codec:"color"`
|
||||
}
|
||||
|
||||
// ChangeNONOColorInboundInfo 定义请求或响应数据结构。
|
||||
type ChangeNONOColorInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"9012" struc:"skip"`
|
||||
Color uint32 `codec:"color"`
|
||||
}
|
||||
|
||||
// C2SDanceAction 定义请求或响应数据结构。
|
||||
type C2SDanceAction struct {
|
||||
Head common.TomeeHeader `cmd:"2103" struc:"skip"`
|
||||
Reserve uint32 `struc:"uint32,big"`
|
||||
Type uint32 `struc:"uint32,big"`
|
||||
}
|
||||
|
||||
// C2SPEOPLE_TRANSFROM 定义请求或响应数据结构。
|
||||
type C2SPEOPLE_TRANSFROM struct {
|
||||
Head common.TomeeHeader `cmd:"2111" struc:"skip"`
|
||||
SuitID uint32 `struc:"uint32,big"`
|
||||
}
|
||||
|
||||
// ChangePlayerNameInboundInfo 定义请求或响应数据结构。
|
||||
type ChangePlayerNameInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"2061" struc:"skip"`
|
||||
Nickname string `struc:"[16]byte"`
|
||||
}
|
||||
|
||||
// ChangeTitleInboundInfo 定义请求或响应数据结构。
|
||||
type ChangeTitleInboundInfo struct {
|
||||
Head common.TomeeHeader `cmd:"3404" struc:"skip"`
|
||||
TileID uint32
|
||||
}
|
||||
|
||||
// C2S_GET_GIFT_COMPLETE 定义请求或响应数据结构。
|
||||
type C2S_GET_GIFT_COMPLETE struct {
|
||||
Head common.TomeeHeader `cmd:"2801" struc:"skip"`
|
||||
PassText string `struc:"[16]byte"`
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// 防止封包通过领取来获取道具
|
||||
|
||||
// BuyItem 购买单个道具
|
||||
func (h Controller) BuyItem(data *item.BuyInboundInfo, player *player.Player) (result *item.BuyOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) BuyItem(data *BuyInboundInfo, player *player.Player) (result *item.BuyOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &item.BuyOutboundInfo{Coins: player.Info.Coins}
|
||||
|
||||
bought, err := buySeerdouBackpackItem(player, data.ItemId, data.Count)
|
||||
@@ -31,7 +31,7 @@ func (h Controller) BuyItem(data *item.BuyInboundInfo, player *player.Player) (r
|
||||
}
|
||||
|
||||
// BuyMultipleItems 批量购买道具
|
||||
func (h Controller) BuyMultipleItems(data *item.BuyMultiInboundInfo, player *player.Player) (result *item.BuyMultiOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) BuyMultipleItems(data *BuyMultiInboundInfo, player *player.Player) (result *item.BuyMultiOutboundInfo, err errorcode.ErrorCode) {
|
||||
for _, itemID := range data.ItemIds {
|
||||
bought, buyErr := buySeerdouBackpackItem(player, int64(itemID), 1)
|
||||
if buyErr == errorcode.ErrorCodes.ErrSunDouInsufficient10016 {
|
||||
@@ -48,7 +48,7 @@ func (h Controller) BuyMultipleItems(data *item.BuyMultiInboundInfo, player *pla
|
||||
}
|
||||
|
||||
// BuyGoldItem 使用金豆购买商品
|
||||
func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, player *player.Player) (result *item.S2C_GoldBuyProductInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) BuyGoldItem(data *C2S_GOLD_BUY_PRODUCT, player *player.Player) (result *item.S2C_GoldBuyProductInfo, err errorcode.ErrorCode) {
|
||||
pro := service.NewShopService().Get(data.ProductID)
|
||||
if pro == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrTooManyProducts
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/item"
|
||||
"blazing/logic/service/player"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@@ -14,15 +13,18 @@ import (
|
||||
// data: 包含道具ID和数量的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 空结果和错误码
|
||||
func (h Controller) ItemSale(data *item.C2S_ITEM_SALE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ItemSale(data *C2S_ITEM_SALE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if c.Service.Item.CheakItem(data.ItemId) < int64(data.Amount) {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
if err := c.Service.Item.UPDATE(data.ItemId, -gconv.Int(data.Amount)); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
|
||||
itemConfig := xmlres.ItemsMAP[int(data.ItemId)]
|
||||
if itemConfig.SellPrice != 0 {
|
||||
c.Info.Coins += int64(int64(data.Amount) * int64(itemConfig.SellPrice))
|
||||
}
|
||||
c.Service.Item.UPDATE(data.ItemId, -gconv.Int(data.Amount))
|
||||
return result, 0
|
||||
}
|
||||
|
||||
@@ -15,13 +15,15 @@ import (
|
||||
const (
|
||||
// ItemDefaultLeftTime 道具默认剩余时间(毫秒)
|
||||
ItemDefaultLeftTime = 360000
|
||||
// UniversalNatureItemID 全能性格转化剂Ω
|
||||
UniversalNatureItemID uint32 = 300136
|
||||
)
|
||||
|
||||
// GetUserItemList 获取用户道具列表
|
||||
// data: 包含分页参数的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 道具列表和错误码
|
||||
func (h Controller) GetUserItemList(data *item.ItemListInboundInfo, c *player.Player) (result *item.ItemListOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetUserItemList(data *ItemListInboundInfo, c *player.Player) (result *item.ItemListOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &item.ItemListOutboundInfo{
|
||||
ItemList: c.Service.Item.GetUserItemList(data.Param1, data.Param2, ItemDefaultLeftTime),
|
||||
}
|
||||
@@ -32,23 +34,33 @@ func (h Controller) GetUserItemList(data *item.ItemListInboundInfo, c *player.Pl
|
||||
// data: 包含道具ID和宠物捕获时间的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 使用后的宠物信息和错误码
|
||||
func (h Controller) UsePetItemOutOfFight(data *item.C2S_USE_PET_ITEM_OUT_OF_FIGHT, c *player.Player) (result *item.S2C_USE_PET_ITEM_OUT_OF_FIGHT, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := c.FindPet(data.CatchTime)
|
||||
func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c *player.Player) (result *item.S2C_USE_PET_ITEM_OUT_OF_FIGHT, err errorcode.ErrorCode) {
|
||||
slot, found := c.FindPetBagSlot(data.CatchTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
itemID := uint32(data.ItemID)
|
||||
if c.Service.Item.CheakItem(itemID) == 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
|
||||
oldHP := currentPet.Hp
|
||||
itemCfg, ok := xmlres.ItemsMAP[int(itemID)]
|
||||
if !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
errcode := h.handleRegularPetItem(itemID, currentPet)
|
||||
if errcode != 0 {
|
||||
return nil, errcode
|
||||
}
|
||||
refreshPetPaneKeepHP(currentPet, oldHP)
|
||||
return finishUsePetItemOutOfFight(c, itemID, currentPet)
|
||||
}
|
||||
|
||||
oldHP := currentPet.Hp
|
||||
var errcode errorcode.ErrorCode
|
||||
switch {
|
||||
case itemID == 300036:
|
||||
@@ -57,7 +69,7 @@ func (h Controller) UsePetItemOutOfFight(data *item.C2S_USE_PET_ITEM_OUT_OF_FIGH
|
||||
refreshPetPaneKeepHP(currentPet, oldHP)
|
||||
}
|
||||
case itemID == 300212:
|
||||
errcode = h.handlexuancaiItem(currentPet, c)
|
||||
errcode = h.handleXuanCaiItem(currentPet, c)
|
||||
case itemCfg.Bonus != 0:
|
||||
errcode = errorcode.ErrorCodes.ErrItemUnusable
|
||||
case itemCfg.HP != 0:
|
||||
@@ -75,8 +87,15 @@ func (h Controller) UsePetItemOutOfFight(data *item.C2S_USE_PET_ITEM_OUT_OF_FIGH
|
||||
return nil, errcode
|
||||
}
|
||||
|
||||
c.Service.Item.UPDATE(itemID, -1)
|
||||
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
||||
return finishUsePetItemOutOfFight(c, itemID, currentPet)
|
||||
}
|
||||
|
||||
func finishUsePetItemOutOfFight(c *player.Player, itemID uint32, currentPet *model.PetInfo) (*item.S2C_USE_PET_ITEM_OUT_OF_FIGHT, errorcode.ErrorCode) {
|
||||
if err := c.Service.Item.UPDATE(itemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
c.Service.Info.Save(*c.Info)
|
||||
result := &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
||||
copier.Copy(&result, currentPet)
|
||||
return result, 0
|
||||
}
|
||||
@@ -102,23 +121,30 @@ func (h Controller) handleNeuronItem(currentPet *model.PetInfo, c *player.Player
|
||||
}
|
||||
|
||||
// 炫彩碎片 处理神300212
|
||||
func (h Controller) handlexuancaiItem(currentPet *model.PetInfo, c *player.Player) errorcode.ErrorCode {
|
||||
r, _ := element.Calculator.GetCombination(int(xmlres.PetMAP[int(currentPet.ID)].Type))
|
||||
func (h Controller) handleXuanCaiItem(currentPet *model.PetInfo, c *player.Player) errorcode.ErrorCode {
|
||||
petCfg, ok := xmlres.PetMAP[int(currentPet.ID)]
|
||||
if !ok {
|
||||
return errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
r, _ := element.Calculator.GetCombination(int(petCfg.Type))
|
||||
if r.Secondary != nil {
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
itemid := uint32(currentPet.Type()) + 400686
|
||||
items := c.Service.Item.CheakItem(itemid)
|
||||
shardItemID := uint32(currentPet.Type()) + 400686
|
||||
items := c.Service.Item.CheakItem(shardItemID)
|
||||
if items < 100 {
|
||||
return errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
|
||||
ok := currentPet.FixShiny()
|
||||
ok = currentPet.FixShiny()
|
||||
if !ok {
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
|
||||
c.Service.Item.UPDATE(itemid, -100)
|
||||
if err := c.Service.Item.UPDATE(shardItemID, -100); err != nil {
|
||||
return errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -166,26 +192,32 @@ func refreshPetPaneKeepHP(currentPet *model.PetInfo, hp uint32) {
|
||||
|
||||
// handleRegularPetItem 处理普通宠物道具
|
||||
func (h Controller) handleRegularPetItem(itemID uint32, currentPet *model.PetInfo) errorcode.ErrorCode {
|
||||
handler := item.PetItemRegistry.GetHandler(itemID)
|
||||
if handler == nil {
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
if !handler(itemID, currentPet) {
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
return 0
|
||||
return item.PetItemRegistry.Handle(itemID, currentPet)
|
||||
}
|
||||
|
||||
// ResetNature 重置宠物性格
|
||||
// data: 包含道具ID和宠物捕获时间的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据和错误码
|
||||
func (h Controller) ResetNature(data *item.C2S_PET_RESET_NATURE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := c.FindPet(data.CatchTime)
|
||||
func (h Controller) ResetNature(data *C2S_PET_RESET_NATURE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
slot, found := c.FindPetBagSlot(data.CatchTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
if data.ItemId != UniversalNatureItemID {
|
||||
return nil, errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
|
||||
if _, ok := xmlres.NatureRootMap[int(data.Nature)]; !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
|
||||
if c.Service.Item.CheakItem(data.ItemId) <= 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
@@ -193,7 +225,10 @@ func (h Controller) ResetNature(data *item.C2S_PET_RESET_NATURE, c *player.Playe
|
||||
currentHP := currentPet.Hp
|
||||
currentPet.Nature = data.Nature
|
||||
refreshPetPaneKeepHP(currentPet, currentHP)
|
||||
c.Service.Item.UPDATE(data.ItemId, -1)
|
||||
if err := c.Service.Item.UPDATE(data.ItemId, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
c.Service.Info.Save(*c.Info)
|
||||
return result, 0
|
||||
}
|
||||
|
||||
@@ -206,7 +241,7 @@ func (h Controller) ResetNature(data *item.C2S_PET_RESET_NATURE, c *player.Playe
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据(响应包单独组装)和错误码
|
||||
// 说明:根据物品ID区分双倍/三倍经验加速器,使用后扣减道具并更新玩家剩余次数
|
||||
func (h Controller) UseSpeedupItem(data *item.C2S_USE_SPEEDUP_ITEM, c *player.Player) (result *item.S2C_USE_SPEEDUP_ITEM, err errorcode.ErrorCode) {
|
||||
func (h Controller) UseSpeedupItem(data *C2S_USE_SPEEDUP_ITEM, c *player.Player) (result *item.S2C_USE_SPEEDUP_ITEM, err errorcode.ErrorCode) {
|
||||
// 1. 校验道具是否存在且数量充足
|
||||
itemCount := c.Service.Item.CheakItem(data.ItemID)
|
||||
if itemCount <= 0 {
|
||||
@@ -221,29 +256,38 @@ func (h Controller) UseSpeedupItem(data *item.C2S_USE_SPEEDUP_ITEM, c *player.Pl
|
||||
if c.Info.TwoTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.TwoTimes += 50 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300067:
|
||||
if c.Info.TwoTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.TwoTimes += 25 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300051: // 假设1002是三倍经验加速器道具ID
|
||||
if c.Info.ThreeTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.ThreeTimes += 50 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
case 300115:
|
||||
if c.Info.ThreeTimes != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
c.Info.ThreeTimes += 30 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
|
||||
default:
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError // 未知道具ID
|
||||
}
|
||||
|
||||
// 3. 扣减道具(数量-1)
|
||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
||||
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
|
||||
switch data.ItemID {
|
||||
case 300027: // 假设1001是双倍经验加速器道具ID
|
||||
c.Info.TwoTimes += 50 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300067:
|
||||
c.Info.TwoTimes += 25 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||
case 300051: // 假设1002是三倍经验加速器道具ID
|
||||
c.Info.ThreeTimes += 50 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
case 300115:
|
||||
c.Info.ThreeTimes += 30 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||
}
|
||||
result.ThreeTimes = uint32(c.Info.ThreeTimes) // 返回三倍经验剩余次数
|
||||
result.TwoTimes = uint32(c.Info.TwoTimes) // 返回双倍经验剩余次数
|
||||
|
||||
@@ -258,12 +302,12 @@ func (h Controller) UseSpeedupItem(data *item.C2S_USE_SPEEDUP_ITEM, c *player.Pl
|
||||
// 能量吸收器相关方法
|
||||
// ==============================
|
||||
|
||||
// UseEnergyXishou 使用能量吸收器
|
||||
// UseEnergyAbsorber 使用能量吸收器
|
||||
// data: 包含使用的能量吸收器物品ID的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据(响应包单独组装)和错误码
|
||||
// 说明:使用后扣减道具并更新玩家能量吸收器剩余次数
|
||||
func (h Controller) UseEnergyXishou(data *item.C2S_USE_ENERGY_XISHOU, c *player.Player) (result *item.S2C_USE_ENERGY_XISHOU, err errorcode.ErrorCode) {
|
||||
func (h Controller) UseEnergyAbsorber(data *C2S_USE_ENERGY_XISHOU, c *player.Player) (result *item.S2C_USE_ENERGY_XISHOU, err errorcode.ErrorCode) {
|
||||
// 1. 校验道具是否存在且数量充足
|
||||
itemCount := c.Service.Item.CheakItem(data.ItemID)
|
||||
if itemCount <= 0 {
|
||||
@@ -274,10 +318,11 @@ func (h Controller) UseEnergyXishou(data *item.C2S_USE_ENERGY_XISHOU, c *player.
|
||||
}
|
||||
// 2. 核心业务逻辑:更新能量吸收器剩余次数
|
||||
// (注:可根据道具ID配置不同的次数加成,此处默认+1)
|
||||
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
c.Info.EnergyTime += 40 // 玩家对象新增 EnergyTimes 字段存储能量吸收剩余次数
|
||||
|
||||
// 3. 扣减道具(数量-1)
|
||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
||||
result = &item.S2C_USE_ENERGY_XISHOU{
|
||||
EnergyTimes: uint32(c.Info.EnergyTime),
|
||||
}
|
||||
@@ -298,7 +343,7 @@ func (h Controller) UseEnergyXishou(data *item.C2S_USE_ENERGY_XISHOU, c *player.
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据(响应包单独组装)和错误码
|
||||
// 说明:使用后扣减道具,开启自动战斗(flag设为3)并更新剩余次数
|
||||
func (h Controller) UseAutoFightItem(data *item.C2S_USE_AUTO_FIGHT_ITEM, c *player.Player) (result *item.S2C_USE_AUTO_FIGHT_ITEM, err errorcode.ErrorCode) {
|
||||
func (h Controller) UseAutoFightItem(data *C2S_USE_AUTO_FIGHT_ITEM, c *player.Player) (result *item.S2C_USE_AUTO_FIGHT_ITEM, err errorcode.ErrorCode) {
|
||||
// 1. 校验道具是否存在且数量充足
|
||||
itemCount := c.Service.Item.CheakItem(data.ItemID)
|
||||
|
||||
@@ -308,6 +353,9 @@ func (h Controller) UseAutoFightItem(data *item.C2S_USE_AUTO_FIGHT_ITEM, c *play
|
||||
if c.Info.AutoFightTime != 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||
}
|
||||
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||
}
|
||||
result = &item.S2C_USE_AUTO_FIGHT_ITEM{}
|
||||
// 2. 核心业务逻辑:开启自动战斗 + 更新剩余次数
|
||||
c.Info.AutoFight = 3 // 按需求设置自动战斗flag为3(需测试)
|
||||
@@ -323,8 +371,6 @@ func (h Controller) UseAutoFightItem(data *item.C2S_USE_AUTO_FIGHT_ITEM, c *play
|
||||
c.Info.AutoFightTime += 50
|
||||
}
|
||||
result.AutoFightTimes = c.Info.AutoFightTime
|
||||
// 3. 扣减道具(数量-1)
|
||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
||||
|
||||
return result, 0
|
||||
}
|
||||
|
||||
137
logic/controller/item_use_test.go
Normal file
137
logic/controller/item_use_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
blservice "blazing/modules/player/service"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUsePetItemOutOfFightAppliesToBackupPetInMemory(t *testing.T) {
|
||||
petID := firstPetIDForControllerTest(t)
|
||||
backupPet := playermodel.GenPetInfo(petID, 31, 0, 0, 50, nil, 0)
|
||||
if backupPet == nil {
|
||||
t.Fatal("failed to generate backup pet")
|
||||
}
|
||||
if backupPet.MaxHp <= 1 {
|
||||
t.Fatalf("expected generated pet to have max hp > 1, got %d", backupPet.MaxHp)
|
||||
}
|
||||
backupPet.Hp = 1
|
||||
|
||||
testPlayer := player.NewPlayer(nil)
|
||||
testPlayer.Info = &playermodel.PlayerInfo{
|
||||
UserID: 1,
|
||||
PetList: []playermodel.PetInfo{},
|
||||
BackupPetList: []playermodel.PetInfo{*backupPet},
|
||||
}
|
||||
testPlayer.Service = blservice.NewUserService(testPlayer.Info.UserID)
|
||||
|
||||
itemID, recoverHP := firstRecoverHPItemForControllerTest(t)
|
||||
if recoverHP <= 0 {
|
||||
t.Fatalf("expected positive recover hp for item %d, got %d", itemID, recoverHP)
|
||||
}
|
||||
|
||||
_, err := (Controller{}).UsePetItemOutOfFight(&C2S_USE_PET_ITEM_OUT_OF_FIGHT{
|
||||
CatchTime: backupPet.CatchTime,
|
||||
ItemID: int32(itemID),
|
||||
}, testPlayer)
|
||||
if err != 0 {
|
||||
t.Fatalf("expected backup pet item use to succeed in-memory, got err=%d", err)
|
||||
}
|
||||
|
||||
updatedPet := testPlayer.Info.BackupPetList[0]
|
||||
if updatedPet.Hp <= 1 {
|
||||
t.Fatalf("expected backup pet hp to increase in memory, got hp=%d", updatedPet.Hp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsePetItemOutOfFightPersistsEnergyOrbEffectInfo(t *testing.T) {
|
||||
petID := firstPetIDForControllerTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 50, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatal("failed to generate test pet")
|
||||
}
|
||||
|
||||
testPlayer := player.NewPlayer(nil)
|
||||
testPlayer.Info = &playermodel.PlayerInfo{
|
||||
UserID: 10001,
|
||||
PetList: []playermodel.PetInfo{*petInfo},
|
||||
}
|
||||
testPlayer.Service = blservice.NewUserService(testPlayer.Info.UserID)
|
||||
|
||||
itemID, effectCfg := firstEnergyOrbItemForControllerTest(t)
|
||||
if err := testPlayer.Service.Item.UPDATE(itemID, 1); err != nil {
|
||||
t.Fatalf("failed to seed energy orb item %d: %v", itemID, err)
|
||||
}
|
||||
|
||||
_, err := (Controller{}).UsePetItemOutOfFight(&C2S_USE_PET_ITEM_OUT_OF_FIGHT{
|
||||
CatchTime: petInfo.CatchTime,
|
||||
ItemID: int32(itemID),
|
||||
}, testPlayer)
|
||||
if err != 0 {
|
||||
t.Fatalf("expected energy orb use to succeed, got err=%d", err)
|
||||
}
|
||||
|
||||
storedPet := testPlayer.Info.PetList[0]
|
||||
if len(storedPet.EffectInfo) == 0 {
|
||||
t.Fatalf("expected pet effect info to persist after using item %d", itemID)
|
||||
}
|
||||
|
||||
last := storedPet.EffectInfo[len(storedPet.EffectInfo)-1]
|
||||
if last.ItemID != itemID {
|
||||
t.Fatalf("expected stored item id %d, got %d", itemID, last.ItemID)
|
||||
}
|
||||
if last.Status != 2 {
|
||||
t.Fatalf("expected energy orb status 2, got %d", last.Status)
|
||||
}
|
||||
if last.EID != uint16(atoiOrZero(effectCfg.Eid)) {
|
||||
t.Fatalf("expected effect id %s, got %d", effectCfg.Eid, last.EID)
|
||||
}
|
||||
}
|
||||
|
||||
func firstRecoverHPItemForControllerTest(t *testing.T) (uint32, int) {
|
||||
t.Helper()
|
||||
|
||||
for id, cfg := range xmlres.ItemsMAP {
|
||||
if cfg.HP > 0 {
|
||||
return uint32(id), cfg.HP
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatal("xmlres.ItemsMAP has no HP recovery item")
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func firstEnergyOrbItemForControllerTest(t *testing.T) (uint32, xmlres.NewSeIdx) {
|
||||
t.Helper()
|
||||
|
||||
for id, itemCfg := range xmlres.ItemsMAP {
|
||||
if itemCfg.NewSeIdx == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
effectCfg, ok := xmlres.EffectMAP[itemCfg.NewSeIdx]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if effectCfg.Stat == "2" && effectCfg.ItemId != nil {
|
||||
return uint32(id), effectCfg
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatal("xmlres.ItemsMAP has no energy orb item")
|
||||
return 0, xmlres.NewSeIdx{}
|
||||
}
|
||||
|
||||
func atoiOrZero(value string) int {
|
||||
result := 0
|
||||
for _, ch := range value {
|
||||
if ch < '0' || ch > '9' {
|
||||
return result
|
||||
}
|
||||
result = result*10 + int(ch-'0')
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -2,14 +2,12 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/data/share"
|
||||
"blazing/cool"
|
||||
|
||||
"blazing/common/rpc"
|
||||
"blazing/common/socket/errorcode"
|
||||
|
||||
"blazing/logic/service/user"
|
||||
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/logic/service/space"
|
||||
"blazing/logic/service/user"
|
||||
"blazing/modules/player/service"
|
||||
"context"
|
||||
"time"
|
||||
@@ -17,25 +15,62 @@ import (
|
||||
"github.com/panjf2000/gnet/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
waitUserOfflineTimeout = 30 * time.Second
|
||||
waitUserOfflineInterval = 200 * time.Millisecond
|
||||
waitUserOfflineKickGap = 5 * time.Second
|
||||
)
|
||||
|
||||
func waitUserOffline(userID uint32, timeout time.Duration) bool {
|
||||
deadline := time.Now().Add(timeout)
|
||||
lastKickAt := time.Now()
|
||||
for {
|
||||
if _, onlineErr := share.ShareManager.GetUserOnline(userID); onlineErr != nil {
|
||||
return true
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
return false
|
||||
}
|
||||
if time.Since(lastKickAt) >= waitUserOfflineKickGap {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||
kickErr := Maincontroller.RPCClient.Kick(ctx, userID)
|
||||
cancel()
|
||||
if kickErr != nil {
|
||||
cool.Logger.Error(context.Background(), "补踢失败", userID, kickErr)
|
||||
}
|
||||
lastKickAt = time.Now()
|
||||
}
|
||||
time.Sleep(waitUserOfflineInterval)
|
||||
}
|
||||
}
|
||||
|
||||
// Login 处理命令: 1001
|
||||
func (h Controller) Login(data *user.MAIN_LOGIN_IN, c gnet.Conn) (result *user.LoginMSInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
func (h Controller) Login(data *MAIN_LOGIN_IN, c gnet.Conn) (result *user.LoginMSInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
|
||||
if data.Head.UserID == 0 {
|
||||
defer c.Close()
|
||||
return
|
||||
}
|
||||
isSessionValid, hashcode := data.CheakSession()
|
||||
isSessionValid, hashcode := data.CheckSession()
|
||||
if !isSessionValid {
|
||||
|
||||
defer c.Close()
|
||||
return
|
||||
}
|
||||
_, erre := share.ShareManager.GetUserOnline(data.Head.UserID)
|
||||
if erre == nil {
|
||||
error := Maincontroller.RPCClient.Kick(data.Head.UserID) //通知其他服务器踢人
|
||||
if error != nil {
|
||||
cool.Logger.Error(context.Background(), "踢人失败", err)
|
||||
|
||||
if onlineServerID, onlineErr := share.ShareManager.GetUserOnline(data.Head.UserID); onlineErr == nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||
kickErr := Maincontroller.RPCClient.Kick(ctx, data.Head.UserID) //通知其他服务器踢人
|
||||
cancel()
|
||||
if kickErr != nil {
|
||||
cool.Logger.Error(context.Background(), "踢人失败", data.Head.UserID, onlineServerID, kickErr)
|
||||
err = errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||
defer c.Close()
|
||||
return
|
||||
}
|
||||
if ok := waitUserOffline(data.Head.UserID, waitUserOfflineTimeout); !ok {
|
||||
cool.Logger.Error(context.Background(), "等待旧会话下线超时", data.Head.UserID, onlineServerID, waitUserOfflineTimeout)
|
||||
err = errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||
defer c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// EnterMap 处理玩家进入地图。
|
||||
func (h Controller) EnterMap(data *space.InInfo, c *player.Player) (result *info.SimpleInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) EnterMap(data *EnterMapInboundInfo, c *player.Player) (result *info.SimpleInfo, err errorcode.ErrorCode) {
|
||||
if c.Info.MapID != data.MapId {
|
||||
atomic.StoreUint32(&c.Canmon, 2)
|
||||
c.MapNPC.Reset(6 * time.Second)
|
||||
@@ -39,7 +39,8 @@ func (h Controller) EnterMap(data *space.InInfo, c *player.Player) (result *info
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func (h Controller) GetMapHot(data *maphot.InInfo, c *player.Player) (result *maphot.OutInfo, err errorcode.ErrorCode) {
|
||||
// GetMapHot 处理控制器请求。
|
||||
func (h Controller) GetMapHot(data *GetMapHotInboundInfo, c *player.Player) (result *maphot.OutInfo, err errorcode.ErrorCode) {
|
||||
result = &maphot.OutInfo{
|
||||
HotInfos: space.GetMapHot(),
|
||||
}
|
||||
@@ -47,7 +48,7 @@ func (h Controller) GetMapHot(data *maphot.InInfo, c *player.Player) (result *ma
|
||||
}
|
||||
|
||||
// LeaveMap 处理玩家离开地图。
|
||||
func (h Controller) LeaveMap(data *space.LeaveMapInboundInfo, c *player.Player) (result *info.LeaveMapOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) LeaveMap(data *LeaveMapInboundInfo, c *player.Player) (result *info.LeaveMapOutboundInfo, err errorcode.ErrorCode) {
|
||||
atomic.StoreUint32(&c.Canmon, 0)
|
||||
|
||||
c.GetSpace().LeaveMap(c) // 从当前空间移除玩家。
|
||||
@@ -57,7 +58,7 @@ func (h Controller) LeaveMap(data *space.LeaveMapInboundInfo, c *player.Player)
|
||||
}
|
||||
|
||||
// GetMapPlayerList 获取当前地图内的玩家列表与地图广播信息。
|
||||
func (h Controller) GetMapPlayerList(data *space.ListMapPlayerInboundInfo, c *player.Player) (result *info.ListMapPlayerOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetMapPlayerList(data *ListMapPlayerInboundInfo, c *player.Player) (result *info.ListMapPlayerOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &info.ListMapPlayerOutboundInfo{
|
||||
Player: c.GetSpace().GetInfo(c),
|
||||
}
|
||||
@@ -71,7 +72,7 @@ func (h Controller) GetMapPlayerList(data *space.ListMapPlayerInboundInfo, c *pl
|
||||
}
|
||||
|
||||
// AttackBoss 调试扣减当前地图广播BOSS血量。
|
||||
func (h Controller) AttackBoss(data *space.AttackBossInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) AttackBoss(data *AttackBossInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
for i := 0; i < len(c.GetSpace().MapBossSInfo.INFO); i++ {
|
||||
if atomic.LoadInt32(&c.GetSpace().MapBossSInfo.INFO[i].Hp) > 0 {
|
||||
atomic.AddInt32(&c.GetSpace().MapBossSInfo.INFO[i].Hp, -1)
|
||||
|
||||
@@ -14,7 +14,8 @@ const (
|
||||
nonoPetCureCost int64 = 50
|
||||
)
|
||||
|
||||
func (h Controller) NonoFollowOrHome(data *nono.NonoFollowOrHomeInInfo, c *player.Player) (result *nono.NonoFollowOutInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
// NonoFollowOrHome 处理控制器请求。
|
||||
func (h Controller) NonoFollowOrHome(data *NonoFollowOrHomeInInfo, c *player.Player) (result *nono.NonoFollowOutInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
c.Info.NONO.Flag = data.Flag
|
||||
result = &nono.NonoFollowOutInfo{
|
||||
UserID: data.Head.UserID,
|
||||
@@ -29,7 +30,7 @@ func (h Controller) NonoFollowOrHome(data *nono.NonoFollowOrHomeInInfo, c *playe
|
||||
}
|
||||
|
||||
// GetNonoInfo 获取nono信息
|
||||
func (h *Controller) GetNonoInfo(data *nono.NonoInboundInfo, c *player.Player) (result *nono.NonoOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
func (h *Controller) GetNonoInfo(data *NonoInboundInfo, c *player.Player) (result *nono.NonoOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
_ = data
|
||||
|
||||
result = &nono.NonoOutboundInfo{
|
||||
@@ -49,7 +50,8 @@ func (h *Controller) GetNonoInfo(data *nono.NonoInboundInfo, c *player.Player) (
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Controller) SwitchFlying(data *nono.SwitchFlyingInboundInfo, c *player.Player) (result *nono.SwitchFlyingOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
// SwitchFlying 处理控制器请求。
|
||||
func (h *Controller) SwitchFlying(data *SwitchFlyingInboundInfo, c *player.Player) (result *nono.SwitchFlyingOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
result = &nono.SwitchFlyingOutboundInfo{
|
||||
UserId: data.Head.UserID,
|
||||
Type: data.Type,
|
||||
@@ -59,8 +61,10 @@ func (h *Controller) SwitchFlying(data *nono.SwitchFlyingInboundInfo, c *player.
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Controller) PlayerPetCure(data *nono.PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
// PlayerPetCure 处理控制器请求。
|
||||
func (h *Controller) PlayerPetCure(data *PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
_ = data
|
||||
result = &nono.PetCureOutboundEmpty{}
|
||||
if c.IsArenaHealLocked() {
|
||||
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
|
||||
}
|
||||
@@ -70,6 +74,9 @@ func (h *Controller) PlayerPetCure(data *nono.PetCureInboundInfo, c *player.Play
|
||||
for i := range c.Info.PetList {
|
||||
c.Info.PetList[i].Cure()
|
||||
}
|
||||
for i := range c.Info.BackupPetList {
|
||||
c.Info.BackupPetList[i].Cure()
|
||||
}
|
||||
c.Info.Coins -= nonoPetCureCost
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,73 +9,29 @@ import (
|
||||
|
||||
// SavePetBagOrder 保存当前主背包和备用背包顺序
|
||||
func (h Controller) SavePetBagOrder(
|
||||
data *pet.SavePetBagOrderInboundInfo,
|
||||
data *SavePetBagOrderInboundInfo,
|
||||
player *player.Player) (result *fight.NullOutboundInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
syncBackupPetList(player)
|
||||
|
||||
if len(data.PetList) > 6 || len(data.BackupPetList) > 6 {
|
||||
if !player.SavePetBagOrder(data.PetList, data.BackupPetList) {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
totalPetCount := len(player.Info.PetList) + len(player.Info.BackupPetList)
|
||||
if len(data.PetList)+len(data.BackupPetList) != totalPetCount {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
petMap := buildPetInfoMap(player.Info.PetList, player.Info.BackupPetList)
|
||||
used := make(map[uint32]struct{}, totalPetCount)
|
||||
|
||||
battleList, ok := buildOrderedPetList(data.PetList, petMap, used)
|
||||
if !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
backupList, ok := buildOrderedPetList(data.BackupPetList, petMap, used)
|
||||
if !ok {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
if len(used) != totalPetCount {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
player.Info.PetList = battleList
|
||||
player.Info.BackupPetList = backupList
|
||||
player.Service.Info.Save(*player.Info)
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// PetRetrieveFromWarehouse 领回仓库精灵
|
||||
// PetRetrieveFromWarehouse 从放生仓库领回精灵
|
||||
func (h Controller) PetRetrieveFromWarehouse(
|
||||
data *pet.PET_RETRIEVE, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if _, ok := findPetListSlot(player, data.CatchTime); ok {
|
||||
return nil, 0
|
||||
data *PET_RETRIEVE, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
if !player.Service.Pet.UpdateFree(data.CatchTime, 1, 0) {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
petInfo := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
||||
if petInfo == nil {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
syncBackupPetList(player)
|
||||
changed := false
|
||||
if len(player.Info.PetList) < 6 {
|
||||
player.Info.PetList = append(player.Info.PetList, petInfo.Data)
|
||||
changed = true
|
||||
} else if len(player.Info.BackupPetList) < 6 {
|
||||
player.Info.BackupPetList = append(player.Info.BackupPetList, petInfo.Data)
|
||||
changed = true
|
||||
}
|
||||
if changed {
|
||||
player.Service.Info.Save(*player.Info)
|
||||
}
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// TogglePetBagWarehouse 精灵背包仓库切换
|
||||
func (h Controller) TogglePetBagWarehouse(
|
||||
data *pet.PetReleaseInboundInfo,
|
||||
data *PetReleaseInboundInfo,
|
||||
player *player.Player) (result *pet.PetReleaseOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &pet.PetReleaseOutboundInfo{
|
||||
Flag: uint32(data.Flag),
|
||||
@@ -85,31 +41,26 @@ func (h Controller) TogglePetBagWarehouse(
|
||||
return result, errorcode.ErrorCodes.ErrChampionCannotSwitch
|
||||
}
|
||||
|
||||
syncBackupPetList(player)
|
||||
|
||||
switch data.Flag {
|
||||
case 0:
|
||||
slot, ok := findPetListSlot(player, data.CatchTime)
|
||||
slot, ok := player.FindPetBagSlot(data.CatchTime)
|
||||
if !ok {
|
||||
return result, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
if !slot.isValid() {
|
||||
if !slot.IsValid() {
|
||||
return result, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
if !player.Service.Pet.Update(slot.info) {
|
||||
if !player.Service.Pet.Update(slot.PetInfo()) {
|
||||
return result, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
slot.remove()
|
||||
if slot.kind == petListKindMain {
|
||||
player.Service.Info.Save(*player.Info)
|
||||
}
|
||||
slot.Remove()
|
||||
|
||||
case 1:
|
||||
if len(player.Info.PetList) >= 6 && len(player.Info.BackupPetList) >= 6 {
|
||||
return result, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
if _, ok := findPetListSlot(player, data.CatchTime); ok {
|
||||
if _, ok := player.FindPetBagSlot(data.CatchTime); ok {
|
||||
return result, 0
|
||||
}
|
||||
|
||||
@@ -117,77 +68,7 @@ func (h Controller) TogglePetBagWarehouse(
|
||||
if petInfo == nil {
|
||||
return result, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
if len(player.Info.PetList) < 6 {
|
||||
player.Info.PetList = append(player.Info.PetList, petInfo.Data)
|
||||
} else {
|
||||
player.Info.BackupPetList = append(player.Info.BackupPetList, petInfo.Data)
|
||||
}
|
||||
result.PetInfo = petInfo.Data
|
||||
}
|
||||
|
||||
if len(player.Info.PetList) > 0 {
|
||||
result.FirstPetTime = player.Info.PetList[0].CatchTime
|
||||
}
|
||||
|
||||
return result, 0
|
||||
}
|
||||
|
||||
// TogglePetBagWarehouseLegacy 旧版精灵背包仓库切换
|
||||
func (h Controller) TogglePetBagWarehouseLegacy(
|
||||
data *pet.PetReleaseLegacyInboundInfo,
|
||||
player *player.Player) (result *pet.PetReleaseOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &pet.PetReleaseOutboundInfo{
|
||||
Flag: uint32(data.Flag),
|
||||
}
|
||||
|
||||
if player.IsArenaSwitchLocked() {
|
||||
return result, errorcode.ErrorCodes.ErrChampionCannotSwitch
|
||||
}
|
||||
|
||||
switch data.Flag {
|
||||
case 0:
|
||||
index, currentPet, ok := player.FindPet(data.CatchTime)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if index < 0 || index >= len(player.Info.PetList) {
|
||||
return result, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
if !player.Service.Pet.Update(*currentPet) {
|
||||
return result, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
player.Info.PetList = append(player.Info.PetList[:index], player.Info.PetList[index+1:]...)
|
||||
player.Info.BackupPetList = removePetByCatchTime(player.Info.BackupPetList, data.CatchTime)
|
||||
player.Info.BackupPetList = append(player.Info.BackupPetList, *currentPet)
|
||||
|
||||
case 1:
|
||||
if len(player.Info.PetList) >= 6 {
|
||||
break
|
||||
}
|
||||
|
||||
if _, _, ok := player.FindPet(data.CatchTime); ok {
|
||||
player.Info.BackupPetList = removePetByCatchTime(player.Info.BackupPetList, data.CatchTime)
|
||||
break
|
||||
}
|
||||
|
||||
if index, backupPet, ok := findBackupPet(player, data.CatchTime); ok {
|
||||
if index < 0 || index >= len(player.Info.BackupPetList) {
|
||||
return result, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||
}
|
||||
|
||||
result.PetInfo = *backupPet
|
||||
player.Info.PetList = append(player.Info.PetList, *backupPet)
|
||||
player.Info.BackupPetList = append(player.Info.BackupPetList[:index], player.Info.BackupPetList[index+1:]...)
|
||||
break
|
||||
}
|
||||
|
||||
petInfo := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
||||
if petInfo == nil {
|
||||
return result, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
player.Info.PetList = append(player.Info.PetList, petInfo.Data)
|
||||
player.AddPetToAvailableBag(petInfo.Data)
|
||||
result.PetInfo = petInfo.Data
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// GetPetBargeList 精灵图鉴
|
||||
func (h Controller) GetPetBargeList(data *pet.PetBargeListInboundInfo, player *player.Player) (result *pet.PetBargeListOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetPetBargeList(data *PetBargeListInboundInfo, player *player.Player) (result *pet.PetBargeListOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
ret := &pet.PetBargeListOutboundInfo{
|
||||
PetBargeList: make([]pet.PetBargeListInfo, 0),
|
||||
@@ -18,8 +18,8 @@ func (h Controller) GetPetBargeList(data *pet.PetBargeListInboundInfo, player *p
|
||||
ret.PetBargeList = append(ret.PetBargeList, pet.PetBargeListInfo{
|
||||
PetId: uint32(v.Args[0]),
|
||||
EnCntCnt: 1,
|
||||
IsCatched: uint32(v.Results[0]),
|
||||
IsKilled: uint32(v.Results[1]),
|
||||
IsCatched: uint32(v.Results[1]),
|
||||
IsKilled: uint32(v.Results[0]),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
func (h Controller) PetELV(data *C2S_PET_EVOLVTION, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
// EvolvePet 处理控制器请求。
|
||||
func (h Controller) EvolvePet(data *C2S_PET_EVOLVTION, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := c.FindPet(data.CacthTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
@@ -36,7 +37,9 @@ func (h Controller) PetELV(data *C2S_PET_EVOLVTION, c *player.Player) (result *f
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti
|
||||
}
|
||||
if branch.EvolvItem != 0 {
|
||||
c.Service.Item.UPDATE(uint32(branch.EvolvItem), -branch.EvolvItemCount)
|
||||
if err := c.Service.Item.UPDATE(uint32(branch.EvolvItem), -branch.EvolvItemCount); err != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti
|
||||
}
|
||||
}
|
||||
|
||||
currentPet.ID = uint32(branch.MonTo)
|
||||
|
||||
@@ -12,16 +12,21 @@ const (
|
||||
petEVMaxSingle uint32 = 255
|
||||
)
|
||||
|
||||
// PetEVDiy 自定义分配宠物努力值(EV)
|
||||
// CustomizePetEV 自定义分配宠物努力值(EV)
|
||||
// data: 包含宠物捕获时间和EV分配数据的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 分配结果和错误码
|
||||
func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := c.FindPet(data.CacthTime)
|
||||
func (h Controller) CustomizePetEV(data *PetEV, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
slot, found := c.FindPetBagSlot(data.CacthTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.Err10401
|
||||
}
|
||||
|
||||
var targetTotal uint32
|
||||
var currentTotal uint32
|
||||
for i, evValue := range data.EVs {
|
||||
@@ -63,6 +68,7 @@ func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullO
|
||||
return result, 0
|
||||
}
|
||||
|
||||
// PetEV 定义请求或响应数据结构。
|
||||
type PetEV struct {
|
||||
Head common.TomeeHeader `cmd:"50001" struc:"skip"`
|
||||
CacthTime uint32 `description:"捕捉时间" codec:"cacthTime"`
|
||||
|
||||
45
logic/controller/pet_ev_test.go
Normal file
45
logic/controller/pet_ev_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
)
|
||||
|
||||
func TestCustomizePetEV_AppliesToBackupPet(t *testing.T) {
|
||||
p := player.NewPlayer(nil)
|
||||
p.Info = &playermodel.PlayerInfo{
|
||||
EVPool: 20,
|
||||
PetList: []playermodel.PetInfo{
|
||||
{CatchTime: 1},
|
||||
},
|
||||
BackupPetList: []playermodel.PetInfo{
|
||||
{
|
||||
CatchTime: 2,
|
||||
Level: 100,
|
||||
Ev: [6]uint32{0, 4, 0, 0, 0, 0},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data := &PetEV{
|
||||
CacthTime: 2,
|
||||
EVs: [6]uint32{0, 8, 4, 0, 0, 0},
|
||||
}
|
||||
|
||||
_, err := (Controller{}).CustomizePetEV(data, p)
|
||||
if err != 0 {
|
||||
t.Fatalf("CustomizePetEV returned error: %v", err)
|
||||
}
|
||||
|
||||
got := p.Info.BackupPetList[0].Ev
|
||||
want := [6]uint32{0, 8, 4, 0, 0, 0}
|
||||
if got != want {
|
||||
t.Fatalf("backup pet EV mismatch, got %v want %v", got, want)
|
||||
}
|
||||
|
||||
if gotPool, wantPool := p.Info.EVPool, int64(12); gotPool != wantPool {
|
||||
t.Fatalf("EVPool mismatch, got %d want %d", gotPool, wantPool)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ func (h Controller) PetExt(
|
||||
|
||||
}
|
||||
|
||||
// C2S_NONO_EXE_LIST 定义请求或响应数据结构。
|
||||
type C2S_NONO_EXE_LIST struct {
|
||||
Head common.TomeeHeader `cmd:"9015" struc:"skip"`
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ const (
|
||||
petFusionSoulID = 1000017
|
||||
)
|
||||
|
||||
func (h Controller) PetFusion(data *pet.C2S_PetFusion, c *player.Player) (result *pet.PetFusionInfo, err errorcode.ErrorCode) {
|
||||
// PetFusion 处理控制器请求。
|
||||
func (h Controller) PetFusion(data *C2S_PetFusion, c *player.Player) (result *pet.PetFusionInfo, err errorcode.ErrorCode) {
|
||||
result = &pet.PetFusionInfo{
|
||||
SoulID: petFusionSoulID,
|
||||
}
|
||||
@@ -64,16 +65,33 @@ func (h Controller) PetFusion(data *pet.C2S_PetFusion, c *player.Player) (result
|
||||
return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
consumeItems(c, materialCounts)
|
||||
c.Info.Coins -= petFusionCost
|
||||
|
||||
if resultPetID == 0 {
|
||||
if useOptionalItem(c, data.GoldItem1[:], petFusionFailureItemID) {
|
||||
result.CostItemFlag = 1
|
||||
} else if auxPet.Level > 5 {
|
||||
auxPet.Downgrade(auxPet.Level - 5)
|
||||
failedAux := *auxPet
|
||||
if auxPet.Level > 5 {
|
||||
failedAux.Downgrade(auxPet.Level - 5)
|
||||
} else {
|
||||
auxPet.Downgrade(1)
|
||||
failedAux.Downgrade(1)
|
||||
}
|
||||
txResult, errCode := c.Service.PetFusionTx(
|
||||
*c.Info,
|
||||
data.Mcatchtime,
|
||||
data.Auxcatchtime,
|
||||
materialCounts,
|
||||
data.GoldItem1[:],
|
||||
petFusionKeepAuxItemID,
|
||||
petFusionFailureItemID,
|
||||
petFusionCost,
|
||||
nil,
|
||||
&failedAux,
|
||||
)
|
||||
if errCode != 0 {
|
||||
return result, errCode
|
||||
}
|
||||
c.Info.Coins -= petFusionCost
|
||||
if txResult.CostItemUsed {
|
||||
result.CostItemFlag = 1
|
||||
} else if txResult.UpdatedAux != nil {
|
||||
*auxPet = *txResult.UpdatedAux
|
||||
}
|
||||
return &pet.PetFusionInfo{}, 0
|
||||
}
|
||||
@@ -100,18 +118,37 @@ func (h Controller) PetFusion(data *pet.C2S_PetFusion, c *player.Player) (result
|
||||
newPet.RandomByWeightShiny()
|
||||
}
|
||||
|
||||
c.Service.Pet.PetAdd(newPet, 0)
|
||||
println(c.Info.UserID, "进行融合", len(c.Info.PetList), masterPet.ID, auxPet.ID, newPet.ID)
|
||||
|
||||
c.PetDel(data.Mcatchtime)
|
||||
if useOptionalItem(c, data.GoldItem1[:], petFusionKeepAuxItemID) {
|
||||
result.CostItemFlag = 1
|
||||
} else {
|
||||
c.PetDel(data.Auxcatchtime)
|
||||
txResult, errCode := c.Service.PetFusionTx(
|
||||
*c.Info,
|
||||
data.Mcatchtime,
|
||||
data.Auxcatchtime,
|
||||
materialCounts,
|
||||
data.GoldItem1[:],
|
||||
petFusionKeepAuxItemID,
|
||||
petFusionFailureItemID,
|
||||
petFusionCost,
|
||||
newPet,
|
||||
nil,
|
||||
)
|
||||
if errCode != 0 {
|
||||
return result, errCode
|
||||
}
|
||||
|
||||
result.ObtainTime = newPet.CatchTime
|
||||
result.StarterCpTm = newPet.ID
|
||||
c.Info.Coins -= petFusionCost
|
||||
if txResult.CostItemUsed {
|
||||
result.CostItemFlag = 1
|
||||
} else {
|
||||
removePetFromPlayerInfo(c, data.Auxcatchtime)
|
||||
}
|
||||
removePetFromPlayerInfo(c, data.Mcatchtime)
|
||||
|
||||
if txResult.NewPet == nil {
|
||||
return result, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
c.Info.PetList = append(c.Info.PetList, *txResult.NewPet)
|
||||
|
||||
result.ObtainTime = txResult.NewPet.CatchTime
|
||||
result.StarterCpTm = txResult.NewPet.ID
|
||||
return result, 0
|
||||
}
|
||||
|
||||
@@ -148,21 +185,10 @@ func hasEnoughItems(c *player.Player, itemCounts map[uint32]int) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func consumeItems(c *player.Player, itemCounts map[uint32]int) {
|
||||
for itemID, count := range itemCounts {
|
||||
_ = c.Service.Item.UPDATE(itemID, -count)
|
||||
func removePetFromPlayerInfo(c *player.Player, catchTime uint32) {
|
||||
index, _, ok := c.FindPet(catchTime)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func useOptionalItem(c *player.Player, itemIDs []uint32, target uint32) bool {
|
||||
if c.Service.Item.CheakItem(target) <= 0 {
|
||||
return false
|
||||
}
|
||||
for _, itemID := range itemIDs {
|
||||
if itemID == target {
|
||||
_ = c.Service.Item.UPDATE(target, -1)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
c.Info.PetList = append(c.Info.PetList[:index], c.Info.PetList[index+1:]...)
|
||||
}
|
||||
|
||||
@@ -2,20 +2,24 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/common"
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
playersvc "blazing/logic/service/player"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// GetPetInfo 获取精灵信息
|
||||
func (h Controller) GetPetInfo(
|
||||
data *pet.InInfo,
|
||||
player *player.Player) (result *model.PetInfo,
|
||||
data *GetPetInfoInboundInfo,
|
||||
player *playersvc.Player) (result *model.PetInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
_, petInfo, found := player.FindPet(data.CatchTime)
|
||||
if found {
|
||||
result = petInfo
|
||||
return result, 0
|
||||
levelLimit := player.CurrentMapPetLevelLimit()
|
||||
if slot, found := player.FindPetBagSlot(data.CatchTime); found {
|
||||
if petInfo := slot.PetInfoPtr(); petInfo != nil {
|
||||
petCopy := playersvc.ApplyPetLevelLimit(*petInfo, levelLimit)
|
||||
result = &petCopy
|
||||
return result, 0
|
||||
}
|
||||
}
|
||||
|
||||
ret := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
||||
@@ -23,46 +27,57 @@ func (h Controller) GetPetInfo(
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
result = &ret.Data
|
||||
petData := ret.Data
|
||||
petData = playersvc.ApplyPetLevelLimit(petData, levelLimit)
|
||||
result = &petData
|
||||
return result, 0
|
||||
}
|
||||
|
||||
// GetUserBagPetInfo 获取主背包和并列备用精灵列表
|
||||
func (h Controller) GetUserBagPetInfo(
|
||||
data *pet.GetUserBagPetInfoInboundEmpty,
|
||||
player *player.Player) (result *pet.GetUserBagPetInfoOutboundInfo,
|
||||
data *GetUserBagPetInfoInboundEmpty,
|
||||
player *playersvc.Player) (result *pet.GetUserBagPetInfoOutboundInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
return buildUserBagPetInfo(player), 0
|
||||
return player.GetUserBagPetInfo(player.CurrentMapPetLevelLimit()), 0
|
||||
}
|
||||
|
||||
// GetPetListInboundEmpty 定义请求或响应数据结构。
|
||||
type GetPetListInboundEmpty struct {
|
||||
Head common.TomeeHeader `cmd:"2303" struc:"skip"`
|
||||
}
|
||||
|
||||
// GetPetList 获取当前主背包列表
|
||||
func (h Controller) GetPetList(
|
||||
data *pet.GetPetListInboundEmpty,
|
||||
player *player.Player) (result *pet.GetPetListOutboundInfo,
|
||||
data *GetPetListInboundEmpty,
|
||||
player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
return buildPetListOutboundInfo(player.Info.PetList), 0
|
||||
}
|
||||
|
||||
// GetPetListFreeInboundEmpty 定义请求或响应数据结构。
|
||||
type GetPetListFreeInboundEmpty struct {
|
||||
Head common.TomeeHeader `cmd:"2320" struc:"skip"`
|
||||
}
|
||||
|
||||
// GetPetReleaseList 获取仓库可放生列表
|
||||
func (h Controller) GetPetReleaseList(
|
||||
data *pet.GetPetListFreeInboundEmpty,
|
||||
player *player.Player) (result *pet.GetPetListOutboundInfo,
|
||||
data *GetPetListFreeInboundEmpty,
|
||||
player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
|
||||
err errorcode.ErrorCode) {
|
||||
syncBackupPetList(player)
|
||||
return buildPetListOutboundInfo(buildWarehousePetList(player)), 0
|
||||
|
||||
return buildPetListOutboundInfo(player.WarehousePetList()), 0
|
||||
}
|
||||
|
||||
// PlayerShowPet 精灵展示
|
||||
func (h Controller) PlayerShowPet(
|
||||
data *pet.PetShowInboundInfo,
|
||||
player *player.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) {
|
||||
data *PetShowInboundInfo,
|
||||
player *playersvc.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &pet.PetShowOutboundInfo{
|
||||
UserID: data.Head.UserID,
|
||||
CatchTime: data.CatchTime,
|
||||
Flag: data.Flag,
|
||||
}
|
||||
|
||||
_, currentPet, ok := player.FindPet(data.CatchTime)
|
||||
if data.Flag == 0 {
|
||||
player.SetPetDisplay(0, nil)
|
||||
player.GetSpace().RefreshUserInfo(player)
|
||||
@@ -70,7 +85,14 @@ func (h Controller) PlayerShowPet(
|
||||
return
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// 仅允许背包精灵跟随:仓库中的精灵不允许跟随
|
||||
slot, found := player.FindPetBagSlot(data.CatchTime)
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.ErrCannotShowBagPokemon
|
||||
}
|
||||
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
|
||||
@@ -2,32 +2,9 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
type petListKind uint8
|
||||
|
||||
const (
|
||||
petListKindMain petListKind = iota
|
||||
petListKindBackup
|
||||
)
|
||||
|
||||
type petListSlot struct {
|
||||
list *[]model.PetInfo
|
||||
index int
|
||||
info model.PetInfo
|
||||
kind petListKind
|
||||
}
|
||||
|
||||
func (slot petListSlot) isValid() bool {
|
||||
return slot.list != nil && slot.index >= 0 && slot.index < len(*slot.list)
|
||||
}
|
||||
|
||||
func (slot petListSlot) remove() {
|
||||
*slot.list = append((*slot.list)[:slot.index], (*slot.list)[slot.index+1:]...)
|
||||
}
|
||||
|
||||
func buildPetShortInfo(info model.PetInfo) pet.PetShortInfo {
|
||||
return pet.PetShortInfo{
|
||||
ID: info.ID,
|
||||
@@ -49,163 +26,6 @@ func buildPetListOutboundInfo(petList []model.PetInfo) *pet.GetPetListOutboundIn
|
||||
return result
|
||||
}
|
||||
|
||||
func removePetByCatchTime(petList []model.PetInfo, catchTime uint32) []model.PetInfo {
|
||||
for i := range petList {
|
||||
if petList[i].CatchTime == catchTime {
|
||||
return append(petList[:i], petList[i+1:]...)
|
||||
}
|
||||
}
|
||||
return petList
|
||||
}
|
||||
|
||||
func buildCatchTimeSet(petLists ...[]model.PetInfo) map[uint32]struct{} {
|
||||
total := 0
|
||||
for _, petList := range petLists {
|
||||
total += len(petList)
|
||||
}
|
||||
|
||||
catchTimes := make(map[uint32]struct{}, total)
|
||||
for _, petList := range petLists {
|
||||
for _, petInfo := range petList {
|
||||
catchTimes[petInfo.CatchTime] = struct{}{}
|
||||
}
|
||||
}
|
||||
return catchTimes
|
||||
}
|
||||
|
||||
func buildPetInfoMap(petLists ...[]model.PetInfo) map[uint32]model.PetInfo {
|
||||
total := 0
|
||||
for _, petList := range petLists {
|
||||
total += len(petList)
|
||||
}
|
||||
|
||||
petMap := make(map[uint32]model.PetInfo, total)
|
||||
for _, petList := range petLists {
|
||||
for _, petInfo := range petList {
|
||||
petMap[petInfo.CatchTime] = petInfo
|
||||
}
|
||||
}
|
||||
return petMap
|
||||
}
|
||||
|
||||
func buildWarehousePetList(player *player.Player) []model.PetInfo {
|
||||
allPets := player.Service.Pet.PetInfo(0)
|
||||
if len(allPets) == 0 {
|
||||
return make([]model.PetInfo, 0)
|
||||
}
|
||||
|
||||
usedCatchTimes := buildCatchTimeSet(player.Info.PetList, player.Info.BackupPetList)
|
||||
result := make([]model.PetInfo, 0, len(allPets))
|
||||
for i := range allPets {
|
||||
catchTime := allPets[i].Data.CatchTime
|
||||
if _, exists := usedCatchTimes[catchTime]; exists {
|
||||
continue
|
||||
}
|
||||
result = append(result, allPets[i].Data)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func findBackupPet(player *player.Player, catchTime uint32) (int, *model.PetInfo, bool) {
|
||||
for i := range player.Info.BackupPetList {
|
||||
if player.Info.BackupPetList[i].CatchTime == catchTime {
|
||||
return i, &player.Info.BackupPetList[i], true
|
||||
}
|
||||
}
|
||||
return -1, nil, false
|
||||
}
|
||||
|
||||
func findPetListSlot(player *player.Player, catchTime uint32) (petListSlot, bool) {
|
||||
if index, petInfo, ok := player.FindPet(catchTime); ok {
|
||||
return petListSlot{
|
||||
list: &player.Info.PetList,
|
||||
index: index,
|
||||
info: *petInfo,
|
||||
kind: petListKindMain,
|
||||
}, true
|
||||
}
|
||||
|
||||
if index, petInfo, ok := findBackupPet(player, catchTime); ok {
|
||||
return petListSlot{
|
||||
list: &player.Info.BackupPetList,
|
||||
index: index,
|
||||
info: *petInfo,
|
||||
kind: petListKindBackup,
|
||||
}, true
|
||||
}
|
||||
|
||||
return petListSlot{}, false
|
||||
}
|
||||
|
||||
func syncBackupPetList(player *player.Player) {
|
||||
if player.Info.BackupPetList == nil {
|
||||
player.Info.BackupPetList = make([]model.PetInfo, 0)
|
||||
return
|
||||
}
|
||||
|
||||
bagPets := player.Service.Pet.PetInfo(0)
|
||||
if len(bagPets) == 0 {
|
||||
player.Info.BackupPetList = make([]model.PetInfo, 0)
|
||||
return
|
||||
}
|
||||
|
||||
bagCatchTimes := make(map[uint32]struct{}, len(bagPets))
|
||||
for i := range bagPets {
|
||||
bagCatchTimes[bagPets[i].Data.CatchTime] = struct{}{}
|
||||
}
|
||||
|
||||
mainPetCatchTimes := buildCatchTimeSet(player.Info.PetList)
|
||||
nextBackupList := make([]model.PetInfo, 0, len(player.Info.BackupPetList))
|
||||
for _, petInfo := range player.Info.BackupPetList {
|
||||
if _, inBag := bagCatchTimes[petInfo.CatchTime]; !inBag {
|
||||
continue
|
||||
}
|
||||
if _, inMain := mainPetCatchTimes[petInfo.CatchTime]; inMain {
|
||||
continue
|
||||
}
|
||||
nextBackupList = append(nextBackupList, petInfo)
|
||||
}
|
||||
|
||||
player.Info.BackupPetList = nextBackupList
|
||||
}
|
||||
|
||||
func buildUserBagPetInfo(player *player.Player) *pet.GetUserBagPetInfoOutboundInfo {
|
||||
syncBackupPetList(player)
|
||||
|
||||
result := &pet.GetUserBagPetInfoOutboundInfo{
|
||||
PetList: make([]model.PetInfo, len(player.Info.PetList)),
|
||||
BackupPetList: make([]model.PetInfo, len(player.Info.BackupPetList)),
|
||||
}
|
||||
copy(result.PetList, player.Info.PetList)
|
||||
copy(result.BackupPetList, player.Info.BackupPetList)
|
||||
return result
|
||||
}
|
||||
|
||||
func buildOrderedPetList(
|
||||
catchTimes []uint32,
|
||||
petMap map[uint32]model.PetInfo,
|
||||
used map[uint32]struct{},
|
||||
) ([]model.PetInfo, bool) {
|
||||
result := make([]model.PetInfo, 0, len(catchTimes))
|
||||
for _, catchTime := range catchTimes {
|
||||
if catchTime == 0 {
|
||||
return nil, false
|
||||
}
|
||||
if _, exists := used[catchTime]; exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
petInfo, exists := petMap[catchTime]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
used[catchTime] = struct{}{}
|
||||
result = append(result, petInfo)
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
|
||||
func buildPetShowOutboundInfo(userID, flag uint32, info *model.PetInfo) *pet.PetShowOutboundInfo {
|
||||
return &pet.PetShowOutboundInfo{
|
||||
UserID: userID,
|
||||
|
||||
@@ -6,20 +6,50 @@ import (
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
)
|
||||
|
||||
func petSetExpLimit(currentPet *playermodel.PetInfo) int64 {
|
||||
if currentPet == nil || currentPet.Level >= 100 {
|
||||
return 0
|
||||
}
|
||||
|
||||
simulatedPet := *currentPet
|
||||
allowedExp := simulatedPet.NextLvExp - simulatedPet.Exp
|
||||
if allowedExp < 0 {
|
||||
allowedExp = 0
|
||||
}
|
||||
|
||||
for simulatedPet.Level < 100 && simulatedPet.NextLvExp > 0 {
|
||||
simulatedPet.Level++
|
||||
simulatedPet.Update(true)
|
||||
if simulatedPet.Level >= 100 {
|
||||
break
|
||||
}
|
||||
allowedExp += simulatedPet.NextLvExp
|
||||
}
|
||||
|
||||
return allowedExp
|
||||
}
|
||||
|
||||
func minInt64(a, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// PetReleaseToWarehouse 将精灵从仓库包中放生
|
||||
func (h Controller) PetReleaseToWarehouse(
|
||||
data *pet.PET_ROWEI, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
data *PET_ROWEI, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, _, inBag := player.FindPet(data.CatchTime)
|
||||
_, _, inBackup := findBackupPet(player, data.CatchTime)
|
||||
_, _, inBackup := player.FindBackupPet(data.CatchTime)
|
||||
freeForbidden := xmlres.PetMAP[int(data.ID)].FreeForbidden
|
||||
if inBag || inBackup || freeForbidden == 1 {
|
||||
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
|
||||
}
|
||||
|
||||
if !player.Service.Pet.UpdateFree(data.CatchTime, 1) {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
if !player.Service.Pet.UpdateFree(data.CatchTime, 0, 1) {
|
||||
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
@@ -27,14 +57,16 @@ func (h Controller) PetReleaseToWarehouse(
|
||||
|
||||
// PetOneCure 单体治疗
|
||||
func (h Controller) PetOneCure(
|
||||
data *pet.PetOneCureInboundInfo, player *player.Player) (result *pet.PetOneCureOutboundInfo, err errorcode.ErrorCode) {
|
||||
data *PetOneCureInboundInfo, player *player.Player) (result *pet.PetOneCureOutboundInfo, err errorcode.ErrorCode) {
|
||||
if player.IsArenaHealLocked() {
|
||||
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
|
||||
}
|
||||
|
||||
_, currentPet, ok := player.FindPet(data.CatchTime)
|
||||
if ok {
|
||||
defer currentPet.Cure()
|
||||
if slot, ok := player.FindPetBagSlot(data.CatchTime); ok {
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if currentPet != nil {
|
||||
defer currentPet.Cure()
|
||||
}
|
||||
}
|
||||
|
||||
return &pet.PetOneCureOutboundInfo{
|
||||
@@ -44,7 +76,7 @@ func (h Controller) PetOneCure(
|
||||
|
||||
// PetFirst 精灵首发
|
||||
func (h Controller) PetFirst(
|
||||
data *pet.PetDefaultInboundInfo, player *player.Player) (result *pet.PetDefaultOutboundInfo, err errorcode.ErrorCode) {
|
||||
data *PetDefaultInboundInfo, player *player.Player) (result *pet.PetDefaultOutboundInfo, err errorcode.ErrorCode) {
|
||||
if player.IsArenaSwitchLocked() {
|
||||
return result, errorcode.ErrorCodes.ErrChampionCannotSwitch
|
||||
}
|
||||
@@ -61,13 +93,19 @@ func (h Controller) PetFirst(
|
||||
|
||||
// SetPetExp 设置宠物经验
|
||||
func (h Controller) SetPetExp(
|
||||
data *pet.PetSetExpInboundInfo,
|
||||
data *PetSetExpInboundInfo,
|
||||
player *player.Player) (result *pet.PetSetExpOutboundInfo, err errorcode.ErrorCode) {
|
||||
_, currentPet, found := player.FindPet(data.CatchTime)
|
||||
if !found || currentPet.Level >= 100 {
|
||||
slot, found := player.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !found || currentPet == nil || currentPet.Level >= 100 {
|
||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
player.AddPetExp(currentPet, data.Exp)
|
||||
allowedExp := petSetExpLimit(currentPet)
|
||||
if allowedExp <= 0 {
|
||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
player.AddPetExp(currentPet, minInt64(data.Exp, allowedExp))
|
||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, 0
|
||||
}
|
||||
|
||||
75
logic/controller/pet_manage_test.go
Normal file
75
logic/controller/pet_manage_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/player"
|
||||
playermodel "blazing/modules/player/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func firstPetIDForControllerTest(t *testing.T) int {
|
||||
t.Helper()
|
||||
|
||||
for id := range xmlres.PetMAP {
|
||||
return id
|
||||
}
|
||||
|
||||
t.Fatal("xmlres.PetMAP is empty")
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestSetPetExpCapsLevelAt100(t *testing.T) {
|
||||
petID := firstPetIDForControllerTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 99, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatal("failed to generate test pet")
|
||||
}
|
||||
|
||||
expPool := petInfo.NextLvExp + 10_000
|
||||
testPlayer := player.NewPlayer(nil)
|
||||
testPlayer.Info = &playermodel.PlayerInfo{
|
||||
ExpPool: expPool,
|
||||
PetList: []playermodel.PetInfo{*petInfo},
|
||||
}
|
||||
|
||||
currentPet := &testPlayer.Info.PetList[0]
|
||||
result, err := (Controller{}).SetPetExp(&PetSetExpInboundInfo{
|
||||
CatchTime: currentPet.CatchTime,
|
||||
Exp: expPool,
|
||||
}, testPlayer)
|
||||
if err != 0 {
|
||||
t.Fatalf("expected SetPetExp to succeed, got err=%d", err)
|
||||
}
|
||||
if currentPet.Level != 100 {
|
||||
t.Fatalf("expected pet level to stop at 100, got %d", currentPet.Level)
|
||||
}
|
||||
if result.Exp != 10_000 {
|
||||
t.Fatalf("expected overflow exp to remain in pool, got %d", result.Exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPetExpRejectsPetAtLevel100(t *testing.T) {
|
||||
petID := firstPetIDForControllerTest(t)
|
||||
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
|
||||
if petInfo == nil {
|
||||
t.Fatal("failed to generate test pet")
|
||||
}
|
||||
|
||||
testPlayer := player.NewPlayer(nil)
|
||||
testPlayer.Info = &playermodel.PlayerInfo{
|
||||
ExpPool: 50_000,
|
||||
PetList: []playermodel.PetInfo{*petInfo},
|
||||
}
|
||||
|
||||
result, err := (Controller{}).SetPetExp(&PetSetExpInboundInfo{
|
||||
CatchTime: petInfo.CatchTime,
|
||||
Exp: 12_345,
|
||||
}, testPlayer)
|
||||
if err != errorcode.ErrorCodes.ErrSystemError {
|
||||
t.Fatalf("expected level-100 pet to be rejected, got err=%d", err)
|
||||
}
|
||||
if result.Exp != 50_000 {
|
||||
t.Fatalf("expected exp pool to remain unchanged, got %d", result.Exp)
|
||||
}
|
||||
}
|
||||
@@ -8,47 +8,136 @@ import (
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type GetPetLearnableSkillsOutboundInfo struct {
|
||||
SkillListLen uint32 `struc:"sizeof=SkillList"`
|
||||
SkillList []uint32 `json:"skillList"`
|
||||
}
|
||||
|
||||
func isSameUint32Slice(a []uint32, b []uint32) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for index := range a {
|
||||
if a[index] != b[index] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func collectPetLearnableSkillList(currentPet *model.PetInfo) []uint32 {
|
||||
skillSet := make(map[uint32]struct{})
|
||||
skills := make([]uint32, 0)
|
||||
|
||||
appendSkill := func(skillID uint32) {
|
||||
if skillID == 0 {
|
||||
return
|
||||
}
|
||||
if _, exists := skillSet[skillID]; exists {
|
||||
return
|
||||
}
|
||||
skillSet[skillID] = struct{}{}
|
||||
skills = append(skills, skillID)
|
||||
}
|
||||
|
||||
for _, skillID := range currentPet.GetLevelRangeCanLearningSkills(1, currentPet.Level) {
|
||||
appendSkill(skillID)
|
||||
}
|
||||
for _, skillID := range currentPet.ExtSKill {
|
||||
appendSkill(skillID)
|
||||
}
|
||||
|
||||
for _, skill := range currentPet.SkillList {
|
||||
delete(skillSet, skill.ID)
|
||||
}
|
||||
|
||||
result := make([]uint32, 0, len(skillSet))
|
||||
for _, skillID := range skills {
|
||||
if _, exists := skillSet[skillID]; exists {
|
||||
result = append(result, skillID)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetPetLearnableSkills 查询当前精灵可学习技能(等级技能 + 额外技能ExtSKill)
|
||||
func (h Controller) GetPetLearnableSkills(
|
||||
data *GetPetLearnableSkillsInboundInfo,
|
||||
c *player.Player,
|
||||
) (result *GetPetLearnableSkillsOutboundInfo, err errorcode.ErrorCode) {
|
||||
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
return &GetPetLearnableSkillsOutboundInfo{
|
||||
SkillList: collectPetLearnableSkillList(currentPet),
|
||||
}, 0
|
||||
}
|
||||
|
||||
// SetPetSkill 设置宠物技能,消耗50赛尔豆
|
||||
func (h Controller) SetPetSkill(data *pet.ChangeSkillInfo, c *player.Player) (result *pet.ChangeSkillOutInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result *pet.ChangeSkillOutInfo, err errorcode.ErrorCode) {
|
||||
const setSkillCost = 50
|
||||
|
||||
if !c.GetCoins(setSkillCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
c.Info.Coins -= setSkillCost
|
||||
|
||||
_, currentPet, ok := c.FindPet(data.CatchTime)
|
||||
if !ok {
|
||||
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
canleaernskill := currentPet.GetLevelRangeCanLearningSkills(1, currentPet.Level)
|
||||
|
||||
_, ok = lo.Find(canleaernskill, func(item uint32) bool {
|
||||
return item == data.ReplaceSkill
|
||||
})
|
||||
|
||||
if !ok {
|
||||
return result, errorcode.ErrorCodes.ErrSystemBusy
|
||||
canLearnSkillSet := make(map[uint32]struct{})
|
||||
for _, skillID := range collectPetLearnableSkillList(currentPet) {
|
||||
canLearnSkillSet[skillID] = struct{}{}
|
||||
}
|
||||
_, _, ok = utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool { //已经存在技能
|
||||
if _, exists := canLearnSkillSet[data.ReplaceSkill]; !exists {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
skillInfo, exists := xmlres.SkillMap[int(data.ReplaceSkill)]
|
||||
if !exists {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
_, _, ok = utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == data.ReplaceSkill
|
||||
})
|
||||
if ok {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
// 查找要学习的技能并替换
|
||||
_, targetSkill, ok := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == data.HasSkill
|
||||
})
|
||||
if ok {
|
||||
if data.HasSkill == 0 && len(currentPet.SkillList) >= 4 {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
|
||||
if data.HasSkill != 0 {
|
||||
_, _, found := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == data.HasSkill
|
||||
})
|
||||
if !found {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
}
|
||||
|
||||
if !c.GetCoins(setSkillCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
c.Info.Coins -= setSkillCost
|
||||
maxPP := uint32(skillInfo.MaxPP)
|
||||
if data.HasSkill != 0 {
|
||||
_, targetSkill, _ := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == data.HasSkill
|
||||
})
|
||||
targetSkill.ID = data.ReplaceSkill
|
||||
targetSkill.PP = uint32(xmlres.SkillMap[int(targetSkill.ID)].MaxPP)
|
||||
targetSkill.PP = maxPP
|
||||
} else {
|
||||
currentPet.SkillList = append(currentPet.SkillList, model.SkillInfo{
|
||||
ID: data.ReplaceSkill,
|
||||
PP: maxPP,
|
||||
})
|
||||
}
|
||||
|
||||
return &pet.ChangeSkillOutInfo{
|
||||
@@ -57,28 +146,142 @@ func (h Controller) SetPetSkill(data *pet.ChangeSkillInfo, c *player.Player) (re
|
||||
}
|
||||
|
||||
// SortPetSkills 排序宠物技能,消耗50赛尔豆
|
||||
func (h Controller) SortPetSkills(data *pet.C2S_Skill_Sort, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
const skillSortCost = 50
|
||||
|
||||
slot, ok := c.FindPetBagSlot(data.CapTm)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
usedSkillSet := make(map[uint32]struct{})
|
||||
newSkillList := make([]model.SkillInfo, 0, 4)
|
||||
|
||||
for _, skillID := range data.Skill {
|
||||
if skillID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, used := usedSkillSet[skillID]; used {
|
||||
continue
|
||||
}
|
||||
_, skill, found := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == skillID
|
||||
})
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
newSkillList = append(newSkillList, *skill)
|
||||
usedSkillSet[skillID] = struct{}{}
|
||||
}
|
||||
|
||||
for _, skill := range currentPet.SkillList {
|
||||
if skill.ID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, used := usedSkillSet[skill.ID]; used {
|
||||
continue
|
||||
}
|
||||
newSkillList = append(newSkillList, skill)
|
||||
usedSkillSet[skill.ID] = struct{}{}
|
||||
}
|
||||
|
||||
if len(newSkillList) > 4 {
|
||||
newSkillList = newSkillList[:4]
|
||||
}
|
||||
|
||||
if !c.GetCoins(skillSortCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
|
||||
c.Info.Coins -= skillSortCost
|
||||
|
||||
_, currentPet, ok := c.FindPet(data.CapTm)
|
||||
if ok {
|
||||
var newSkillList []model.SkillInfo
|
||||
for _, skillID := range data.Skill {
|
||||
_, skill, found := utils.FindWithIndex(currentPet.SkillList, func(item model.SkillInfo) bool {
|
||||
return item.ID == skillID
|
||||
})
|
||||
if found {
|
||||
newSkillList = append(newSkillList, *skill)
|
||||
}
|
||||
}
|
||||
currentPet.SkillList = newSkillList
|
||||
}
|
||||
currentPet.SkillList = newSkillList
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// CommitPetSkills 按最终技能列表一次性提交学习/替换/排序结果。
|
||||
func (h Controller) CommitPetSkills(
|
||||
data *CommitPetSkillsInboundInfo,
|
||||
c *player.Player,
|
||||
) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
const setSkillCost = 50
|
||||
const skillSortCost = 50
|
||||
|
||||
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||
currentPet := slot.PetInfoPtr()
|
||||
if !ok || currentPet == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
|
||||
currentSkillSet := make(map[uint32]model.SkillInfo, len(currentPet.SkillList))
|
||||
currentSkillOrder := make([]uint32, 0, len(currentPet.SkillList))
|
||||
for _, skill := range currentPet.SkillList {
|
||||
if skill.ID == 0 {
|
||||
continue
|
||||
}
|
||||
currentSkillSet[skill.ID] = skill
|
||||
currentSkillOrder = append(currentSkillOrder, skill.ID)
|
||||
}
|
||||
|
||||
finalSkillIDs := make([]uint32, 0, 4)
|
||||
usedSkillSet := make(map[uint32]struct{}, 4)
|
||||
for _, skillID := range data.Skill {
|
||||
if skillID == 0 {
|
||||
continue
|
||||
}
|
||||
if _, exists := usedSkillSet[skillID]; exists {
|
||||
continue
|
||||
}
|
||||
usedSkillSet[skillID] = struct{}{}
|
||||
finalSkillIDs = append(finalSkillIDs, skillID)
|
||||
}
|
||||
|
||||
if len(finalSkillIDs) == 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
if len(finalSkillIDs) > 4 {
|
||||
finalSkillIDs = finalSkillIDs[:4]
|
||||
}
|
||||
if isSameUint32Slice(currentSkillOrder, finalSkillIDs) {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
learnableSkillSet := make(map[uint32]struct{})
|
||||
for _, skillID := range collectPetLearnableSkillList(currentPet) {
|
||||
learnableSkillSet[skillID] = struct{}{}
|
||||
}
|
||||
|
||||
newSkillCount := 0
|
||||
finalSkillList := make([]model.SkillInfo, 0, len(finalSkillIDs))
|
||||
for _, skillID := range finalSkillIDs {
|
||||
if skill, exists := currentSkillSet[skillID]; exists {
|
||||
finalSkillList = append(finalSkillList, skill)
|
||||
continue
|
||||
}
|
||||
if _, exists := learnableSkillSet[skillID]; !exists {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
skillInfo, exists := xmlres.SkillMap[int(skillID)]
|
||||
if !exists {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||
}
|
||||
newSkillCount++
|
||||
finalSkillList = append(finalSkillList, model.SkillInfo{
|
||||
ID: skillID,
|
||||
PP: uint32(skillInfo.MaxPP),
|
||||
})
|
||||
}
|
||||
|
||||
totalCost := int64(newSkillCount * setSkillCost)
|
||||
if newSkillCount == 0 {
|
||||
totalCost += int64(skillSortCost)
|
||||
}
|
||||
if totalCost > 0 && !c.GetCoins(totalCost) {
|
||||
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||
}
|
||||
c.Info.Coins -= totalCost
|
||||
currentPet.SkillList = finalSkillList
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
@@ -10,21 +10,20 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// IsCollect 处理控制器请求。
|
||||
func (h Controller) IsCollect(
|
||||
data *pet.C2S_IS_COLLECT, c *player.Player) (result *pet.S2C_IS_COLLECT, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
result = &pet.S2C_IS_COLLECT{
|
||||
ID: data.Type,
|
||||
}
|
||||
|
||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
||||
|
||||
r := bitset32.From(te.Data)
|
||||
// 分支未完成时,标记完成并发放奖励
|
||||
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||
if taskErr == nil {
|
||||
r := bitset32.From(taskData.Data)
|
||||
if r.Test(uint(data.Type)) {
|
||||
result.IsCom = 1
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
_, ok := lo.Find([]uint32{1, 2, 3, 4, 301}, func(item uint32) bool {
|
||||
return data.Type == item
|
||||
@@ -49,6 +48,7 @@ var validTypeIDMap = map[int][]uint32{
|
||||
100: {856, 857, 858}, //测试
|
||||
}
|
||||
|
||||
// Collect 处理控制器请求。
|
||||
func (h Controller) Collect(
|
||||
data *pet.C2S_PET_COLLECT, c *player.Player) (result *pet.S2C_PET_COLLECT, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
result = &pet.S2C_PET_COLLECT{ID: data.ID}
|
||||
@@ -57,14 +57,17 @@ func (h Controller) Collect(
|
||||
return data.Type == item
|
||||
})
|
||||
if res == model.Completed && ok { //这块是为了兼容旧版本
|
||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
||||
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||
if taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
r := bitset32.From(te.Data)
|
||||
|
||||
r.Set(uint(data.Type))
|
||||
te.Data = r.Bytes()
|
||||
return true
|
||||
})
|
||||
r := bitset32.From(taskData.Data)
|
||||
r.Set(uint(data.Type))
|
||||
taskData.Data = r.Bytes()
|
||||
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
@@ -78,21 +81,22 @@ func (h Controller) Collect(
|
||||
if !lo.Contains(validIDs, data.ID) {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
||||
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||
if taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
|
||||
r := bitset32.From(te.Data)
|
||||
// 分支未完成时,标记完成并发放奖励
|
||||
if !r.Test(uint(data.Type)) {
|
||||
r.Set(uint(data.Type))
|
||||
te.Data = r.Bytes()
|
||||
r := model.GenPetInfo(int(data.ID), -1, -1, 0, 1, nil, 0)
|
||||
c.Service.Pet.PetAdd(r, 0)
|
||||
result.CatchTime = r.CatchTime
|
||||
|
||||
return true
|
||||
r := bitset32.From(taskData.Data)
|
||||
if !r.Test(uint(data.Type)) {
|
||||
r.Set(uint(data.Type))
|
||||
taskData.Data = r.Bytes()
|
||||
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||
}
|
||||
return false
|
||||
})
|
||||
petInfo := model.GenPetInfo(int(data.ID), -1, -1, 0, 1, nil, 0)
|
||||
c.Service.Pet.PetAdd(petInfo, 0)
|
||||
result.CatchTime = petInfo.CatchTime
|
||||
}
|
||||
|
||||
if result.CatchTime == 0 {
|
||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||
|
||||
@@ -40,7 +40,7 @@ func (ctl Controller) GetBreedPet(
|
||||
}
|
||||
|
||||
result = &pet.S2C_GET_BREED_PET{}
|
||||
compatibleFemaleIDs := buildBreedPetIDSet(service.NewEggService().GetData(malePet.ID))
|
||||
compatibleFemaleIDs := buildBreedPetIDSet(breedConfigService.GetData(malePet.ID))
|
||||
if len(compatibleFemaleIDs) == 0 {
|
||||
return result, 0
|
||||
}
|
||||
@@ -116,10 +116,15 @@ func buildBreedPetIDSet(ids []int32) map[uint32]struct{} {
|
||||
}
|
||||
|
||||
func canBreedPair(maleID, femaleID uint32) bool {
|
||||
_, ok := buildBreedPetIDSet(service.NewEggService().GetData(maleID))[femaleID]
|
||||
return ok
|
||||
for _, id := range breedConfigService.GetData(maleID) {
|
||||
if uint32(id) == femaleID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetEggList 处理控制器请求。
|
||||
func (ctl Controller) GetEggList(
|
||||
data *pet.C2S_GET_EGG_LIST, player *player.Player) (result *pet.S2C_GET_EGG_LIST, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||
|
||||
@@ -143,7 +148,10 @@ const (
|
||||
breedCost = 1000
|
||||
)
|
||||
|
||||
var limiter *ratelimit.Rule = ratelimit.NewRule()
|
||||
var (
|
||||
breedConfigService = service.NewEggService()
|
||||
limiter = ratelimit.NewRule()
|
||||
)
|
||||
|
||||
// 简单规则案例
|
||||
func init() {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// BuyFitment 购买基地家具
|
||||
func (h Controller) BuyFitment(data *room.C2S_BUY_FITMENT, c *player.Player) (result *room.S2C_BUY_FITMENT, err errorcode.ErrorCode) {
|
||||
func (h Controller) BuyFitment(data *C2S_BUY_FITMENT, c *player.Player) (result *room.S2C_BUY_FITMENT, err errorcode.ErrorCode) {
|
||||
result = &room.S2C_BUY_FITMENT{Coins: c.Info.Coins}
|
||||
|
||||
bought, err := buySeerdouBackpackItem(c, int64(data.ID), int64(data.Count))
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/modules/player/model"
|
||||
"blazing/modules/player/service"
|
||||
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
@@ -15,12 +16,11 @@ import (
|
||||
// data: 包含目标用户ID的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 基地家具信息和错误码
|
||||
func (h Controller) GetFitmentUsing(data *room.FitmentUseringInboundInfo, c *player.Player) (result *room.FitmentUseringOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetFitmentUsing(data *FitmentUseringInboundInfo, c *player.Player) (result *room.FitmentUseringOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &room.FitmentUseringOutboundInfo{UserId: c.Info.UserID, RoomId: data.TargetUserID}
|
||||
result.Fitments = make([]model.FitmentShowInfo, 0)
|
||||
result.Fitments = append(result.Fitments, model.FitmentShowInfo{Id: 500001, Status: 1, X: 1, Y: 1, Dir: 1})
|
||||
|
||||
roomInfo := c.Service.Room.Get(data.TargetUserID)
|
||||
result.Fitments = make([]model.FitmentShowInfo, 0, len(roomInfo.PlacedItems)+1)
|
||||
result.Fitments = append(result.Fitments, model.FitmentShowInfo{Id: 500001, Status: 1, X: 1, Y: 1, Dir: 1})
|
||||
result.Fitments = append(result.Fitments, roomInfo.PlacedItems...)
|
||||
return
|
||||
}
|
||||
@@ -29,20 +29,48 @@ func (h Controller) GetFitmentUsing(data *room.FitmentUseringInboundInfo, c *pla
|
||||
// data: 包含目标用户ID的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 精灵展示列表和错误码
|
||||
func (h Controller) GetRoomPetShowInfo(data *room.PetRoomListInboundInfo, c *player.Player) (result *room.PetRoomListOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetRoomPetShowInfo(data *PetRoomListInboundInfo, c *player.Player) (result *room.PetRoomListOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &room.PetRoomListOutboundInfo{}
|
||||
result.Pets = make([]pet.PetShortInfo, 0)
|
||||
roomInfo := c.Service.Room.Get(data.TargetUserID)
|
||||
for _, catchTime := range roomInfo.ShowPokemon {
|
||||
petInfo := c.Service.Pet.PetInfoOneOther(data.TargetUserID, catchTime)
|
||||
if petInfo.Data.ID == 0 {
|
||||
showPets := service.NewPetService(data.TargetUserID).GetShowPets()
|
||||
result.Pets = make([]pet.PetShortInfo, 0, len(showPets))
|
||||
for i := range showPets {
|
||||
var petShortInfo pet.PetShortInfo
|
||||
copier.Copy(&petShortInfo, &showPets[i].Data)
|
||||
result.Pets = append(result.Pets, petShortInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetRoomPetShowInfo 设置基地展示精灵并返回最新展示列表(cmd:2323)
|
||||
func (h Controller) SetRoomPetShowInfo(data *C2S_PET_ROOM_SHOW, c *player.Player) (result *room.S2C_PET_ROOM_SHOW, err errorcode.ErrorCode) {
|
||||
result = &room.S2C_PET_ROOM_SHOW{}
|
||||
|
||||
catchTimes := make([]uint32, 0, len(data.PetShowList))
|
||||
seen := make(map[uint32]struct{}, len(data.PetShowList))
|
||||
for _, item := range data.PetShowList {
|
||||
ct := uint32(item.CatchTime)
|
||||
if ct == 0 {
|
||||
continue
|
||||
}
|
||||
var petShortInfo pet.PetShortInfo
|
||||
copier.Copy(&petShortInfo, &petInfo.Data)
|
||||
if petInfo.ID != 0 {
|
||||
result.Pets = append(result.Pets, petShortInfo)
|
||||
if _, ok := seen[ct]; ok {
|
||||
continue
|
||||
}
|
||||
seen[ct] = struct{}{}
|
||||
catchTimes = append(catchTimes, ct)
|
||||
}
|
||||
|
||||
petSvc := service.NewPetService(c.Info.UserID)
|
||||
if !petSvc.SetShowCatchTimes(catchTimes) {
|
||||
err = errorcode.ErrorCodes.ErrSystemError
|
||||
return
|
||||
}
|
||||
|
||||
showPets := petSvc.GetShowPets()
|
||||
result.PetShowList = make([]pet.PetShortInfo, 0, len(showPets))
|
||||
for i := range showPets {
|
||||
var petShortInfo pet.PetShortInfo
|
||||
copier.Copy(&petShortInfo, &showPets[i].Data)
|
||||
result.PetShowList = append(result.PetShowList, petShortInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -51,12 +79,12 @@ func (h Controller) GetRoomPetShowInfo(data *room.PetRoomListInboundInfo, c *pla
|
||||
// data: 空输入结构
|
||||
// c: 当前玩家对象
|
||||
// 返回: 玩家所有家具列表和错误码
|
||||
func (h Controller) GetAllFurniture(data *room.FitmentAllInboundEmpty, c *player.Player) (result *room.FitmentAllOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetAllFurniture(data *FitmentAllInboundEmpty, c *player.Player) (result *room.FitmentAllOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &room.FitmentAllOutboundInfo{}
|
||||
result.Fitments = make([]room.FitmentItemInfo, 0)
|
||||
|
||||
items := c.Service.Item.Get(500000, 600000)
|
||||
roomData := c.Service.Room.Get(c.Info.UserID)
|
||||
result.Fitments = make([]room.FitmentItemInfo, 0, len(items))
|
||||
for _, item := range items {
|
||||
var itemInfo room.FitmentItemInfo
|
||||
itemInfo.Id = item.ItemId
|
||||
@@ -75,8 +103,11 @@ func (h Controller) GetAllFurniture(data *room.FitmentAllInboundEmpty, c *player
|
||||
// data: 包含用户ID和精灵捕获时间的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 精灵详细信息和错误码
|
||||
func (h Controller) GetRoomPetInfo(data *room.C2S_RoomPetInfo, c *player.Player) (result *pet.RoomPetInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetRoomPetInfo(data *C2S_RoomPetInfo, c *player.Player) (result *pet.RoomPetInfo, err errorcode.ErrorCode) {
|
||||
petInfo := c.Service.Pet.PetInfoOneOther(data.UserID, data.CatchTime)
|
||||
if petInfo == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||
}
|
||||
result = &pet.RoomPetInfo{}
|
||||
copier.CopyWithOption(result, &petInfo.Data, copier.Option{DeepCopy: true})
|
||||
result.OwnerId = data.UserID
|
||||
|
||||
@@ -2,45 +2,16 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/pet"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/logic/service/room"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
// SetFitment 设置基地家具摆放
|
||||
// data: 包含家具列表的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 空结果和错误码
|
||||
func (h Controller) SetFitment(data *room.SET_FITMENT, c *player.Player) (result *room.NullInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) SetFitment(data *SET_FITMENT, c *player.Player) (result *room.NullInfo, err errorcode.ErrorCode) {
|
||||
|
||||
c.Service.Room.Set(data.Fitments)
|
||||
return
|
||||
}
|
||||
|
||||
// SetPet 设置基地展示的精灵
|
||||
// data: 包含精灵展示列表的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 精灵展示列表和错误码
|
||||
func (h Controller) SetPet(data *room.C2S_PET_ROOM_SHOW, c *player.Player) (result *room.S2C_PET_ROOM_SHOW, err errorcode.ErrorCode) {
|
||||
var showPetCatchTimes []uint32
|
||||
for _, petShowInfo := range data.PetShowList {
|
||||
if petShowInfo.CatchTime != 0 {
|
||||
showPetCatchTimes = append(showPetCatchTimes, petShowInfo.CatchTime)
|
||||
}
|
||||
}
|
||||
c.Service.Room.Show(showPetCatchTimes)
|
||||
result = &room.S2C_PET_ROOM_SHOW{}
|
||||
result.PetShowList = make([]pet.PetShortInfo, len(showPetCatchTimes))
|
||||
for _, catchTime := range showPetCatchTimes {
|
||||
petInfo := c.Service.Pet.PetInfoOneByCatchTime(catchTime)
|
||||
if petInfo == nil {
|
||||
continue
|
||||
}
|
||||
var petShortInfo pet.PetShortInfo
|
||||
copier.Copy(&petShortInfo, &petInfo.Data)
|
||||
result.PetShowList = append(result.PetShowList, petShortInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"blazing/logic/service/player"
|
||||
)
|
||||
|
||||
// SystemTimeInfo 处理控制器请求。
|
||||
func (h Controller) SystemTimeInfo(data *InInfo, c *player.Player) (result *OutInfo, err errorcode.ErrorCode) {
|
||||
|
||||
return &OutInfo{
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// data: 包含射击信息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: 射击结果和错误码
|
||||
func (h Controller) PlayerAim(data *user.AimatInboundInfo, player *player.Player) (result *user.AimatOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) PlayerAim(data *AimatInboundInfo, player *player.Player) (result *user.AimatOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &user.AimatOutboundInfo{
|
||||
|
||||
ItemId: data.ItemId,
|
||||
@@ -33,7 +33,7 @@ func (h Controller) PlayerAim(data *user.AimatInboundInfo, player *player.Player
|
||||
// data: 包含聊天消息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: 聊天结果和错误码
|
||||
func (h Controller) PlayerChat(data *user.ChatInboundInfo, player *player.Player) (result *user.ChatOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) PlayerChat(data *ChatInboundInfo, player *player.Player) (result *user.ChatOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
result = &user.ChatOutboundInfo{
|
||||
|
||||
@@ -50,7 +50,7 @@ func (h Controller) PlayerChat(data *user.ChatInboundInfo, player *player.Player
|
||||
// data: 包含颜色信息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: 颜色更改结果和错误码
|
||||
func (h Controller) ChangePlayerColor(data *user.ChangeColorInboundInfo, player *player.Player) (result *user.ChangeColorOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ChangePlayerColor(data *ChangeColorInboundInfo, player *player.Player) (result *user.ChangeColorOutboundInfo, err errorcode.ErrorCode) {
|
||||
const changeColorCost = 50
|
||||
|
||||
if !player.GetCoins(changeColorCost) {
|
||||
@@ -76,7 +76,7 @@ func (h Controller) ChangePlayerColor(data *user.ChangeColorInboundInfo, player
|
||||
// data: 包含涂鸦信息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: 涂鸦更改结果和错误码
|
||||
func (h Controller) ChangePlayerDoodle(data *user.ChangeDoodleInboundInfo, player *player.Player) (result *user.ChangeDoodleOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ChangePlayerDoodle(data *ChangeDoodleInboundInfo, player *player.Player) (result *user.ChangeDoodleOutboundInfo, err errorcode.ErrorCode) {
|
||||
const changeDoodleCost = 50
|
||||
|
||||
if !player.GetCoins(changeDoodleCost) {
|
||||
@@ -102,11 +102,11 @@ func (h Controller) ChangePlayerDoodle(data *user.ChangeDoodleInboundInfo, playe
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeNONOColor 修改NONO颜色
|
||||
// ChangeNonoColor 修改NONO颜色
|
||||
// data: 包含NONO颜色信息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: NONO颜色更改结果和错误码
|
||||
func (h Controller) ChangeNONOColor(data *user.ChangeNONOColorInboundInfo, player *player.Player) (result *user.ChangeNONOColorOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ChangeNonoColor(data *ChangeNONOColorInboundInfo, player *player.Player) (result *user.ChangeNONOColorOutboundInfo, err errorcode.ErrorCode) {
|
||||
//player.Info.Coins -= 200
|
||||
player.Info.NONO.NonoColor = data.Color
|
||||
|
||||
@@ -122,7 +122,7 @@ func (h Controller) ChangeNONOColor(data *user.ChangeNONOColorInboundInfo, playe
|
||||
// data: 包含跳舞类型信息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: 跳舞动作结果和错误码
|
||||
func (h Controller) DanceAction(data *user.C2SDanceAction, player *player.Player) (result *user.S2CDanceAction, err errorcode.ErrorCode) {
|
||||
func (h Controller) DanceAction(data *C2SDanceAction, player *player.Player) (result *user.S2CDanceAction, err errorcode.ErrorCode) {
|
||||
|
||||
result = &user.S2CDanceAction{
|
||||
Type: data.Type,
|
||||
@@ -136,7 +136,7 @@ func (h Controller) DanceAction(data *user.C2SDanceAction, player *player.Player
|
||||
// data: 包含变形信息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: 变形结果和错误码
|
||||
func (h Controller) PeopleTransform(data *user.C2SPEOPLE_TRANSFROM, player *player.Player) (result *user.S2CPEOPLE_TRANSFROM, err errorcode.ErrorCode) {
|
||||
func (h Controller) PeopleTransform(data *C2SPEOPLE_TRANSFROM, player *player.Player) (result *user.S2CPEOPLE_TRANSFROM, err errorcode.ErrorCode) {
|
||||
|
||||
result = &user.S2CPEOPLE_TRANSFROM{
|
||||
SuitID: data.SuitID,
|
||||
@@ -150,7 +150,7 @@ func (h Controller) PeopleTransform(data *user.C2SPEOPLE_TRANSFROM, player *play
|
||||
// data: 包含服装信息的输入数据
|
||||
// player: 当前玩家对象
|
||||
// 返回: 服装更改结果和错误码
|
||||
func (h Controller) ChangePlayerCloth(data *item.ChangePlayerClothInboundInfo, player *player.Player) (result *item.ChangePlayerClothOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) ChangePlayerCloth(data *ChangePlayerClothInboundInfo, player *player.Player) (result *item.ChangePlayerClothOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
result = &item.ChangePlayerClothOutboundInfo{
|
||||
UserID: player.Info.UserID,
|
||||
@@ -171,7 +171,8 @@ func (h Controller) ChangePlayerCloth(data *item.ChangePlayerClothInboundInfo, p
|
||||
return
|
||||
}
|
||||
|
||||
func (h Controller) ChangePlayerName(data *user.ChangePlayerNameInboundInfo, c *player.Player) (result *user.ChangePlayerNameOutboundInfo, err errorcode.ErrorCode) {
|
||||
// ChangePlayerName 处理控制器请求。
|
||||
func (h Controller) ChangePlayerName(data *ChangePlayerNameInboundInfo, c *player.Player) (result *user.ChangePlayerNameOutboundInfo, err errorcode.ErrorCode) {
|
||||
newNickname := cool.Filter.Replace(strings.Trim(data.Nickname, "\x00"), '*')
|
||||
|
||||
c.Info.Nick = newNickname
|
||||
@@ -183,7 +184,9 @@ func (h Controller) ChangePlayerName(data *user.ChangePlayerNameInboundInfo, c *
|
||||
|
||||
return result, 0
|
||||
}
|
||||
func (h Controller) ChangeTile(data *user.ChangeTitleInboundInfo, c *player.Player) (result *user.ChangeTitleOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
// ChangeTitle 处理控制器请求。
|
||||
func (h Controller) ChangeTitle(data *ChangeTitleInboundInfo, c *player.Player) (result *user.ChangeTitleOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &user.ChangeTitleOutboundInfo{
|
||||
|
||||
UserID: c.Info.UserID,
|
||||
|
||||
@@ -2,61 +2,80 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/player"
|
||||
logicplayer "blazing/logic/service/player"
|
||||
"blazing/logic/service/user"
|
||||
"blazing/modules/config/service"
|
||||
"blazing/modules/player/model"
|
||||
baseservice "blazing/modules/base/service"
|
||||
configservice "blazing/modules/config/service"
|
||||
playerservice "blazing/modules/player/service"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (h Controller) CDK(data *user.C2S_GET_GIFT_COMPLETE, player *player.Player) (result *user.S2C_GET_GIFT_COMPLETE, err errorcode.ErrorCode) {
|
||||
// RedeemGiftCode 处理控制器请求。
|
||||
func (h Controller) RedeemGiftCode(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player) (result *user.S2C_GET_GIFT_COMPLETE, err errorcode.ErrorCode) {
|
||||
result = &user.S2C_GET_GIFT_COMPLETE{}
|
||||
|
||||
cdkService := service.NewCdkService()
|
||||
rewardPetService := service.NewPetRewardService()
|
||||
itemRewardService := service.NewItemService()
|
||||
userInfo := baseservice.NewBaseSysUserService().GetPerson(data.Head.UserID)
|
||||
if userInfo == nil || userInfo.QQ == 0 {
|
||||
return nil, errorcode.ErrorCodes.ErrCannotPerformAction
|
||||
}
|
||||
|
||||
cdkCode := strings.Trim(data.PassText, "\x00")
|
||||
cdkService := configservice.NewCdkService()
|
||||
now := time.Now()
|
||||
|
||||
r := cdkService.Get(data.PassText)
|
||||
r := cdkService.Get(cdkCode)
|
||||
if r == nil {
|
||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeNotExists
|
||||
}
|
||||
if r.Type != configservice.CDKTypeReward {
|
||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeNotExists
|
||||
}
|
||||
if r.BindUserId != 0 && r.BindUserId != data.Head.UserID {
|
||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeFrozen
|
||||
}
|
||||
|
||||
if r.ValidEndTime.Compare(now) == -1 {
|
||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeExpired
|
||||
}
|
||||
if !player.Service.Cdk.CanGet(uint32(r.ID)) {
|
||||
return
|
||||
}
|
||||
if !cdkService.Set(data.PassText) {
|
||||
if !cdkService.Set(cdkCode) {
|
||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeGiftsGone
|
||||
}
|
||||
|
||||
reward, grantErr := playerservice.NewCdkService(data.Head.UserID).GrantConfigReward(
|
||||
uint32(r.ID),
|
||||
func(itemID uint32, count int64) bool {
|
||||
return player.ItemAdd(int64(itemID), count)
|
||||
},
|
||||
)
|
||||
if grantErr != nil {
|
||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
result.Flag = 1
|
||||
for _, rewardID := range r.ElfRewardIds {
|
||||
pet := rewardPetService.Get(rewardID)
|
||||
if pet == nil {
|
||||
continue
|
||||
appendGift := func(giftID, count int64) {
|
||||
if giftID == 0 || count <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
petInfo := model.GenPetInfo(int(pet.MonID), int(pet.DV), int(pet.Nature), int(pet.Effect), int(pet.Lv), nil, 0)
|
||||
player.Service.Pet.PetAdd(petInfo, 0)
|
||||
result.PetGift = append(result.PetGift, user.PetGiftInfo{PetID: petInfo.ID, CacthTime: petInfo.CatchTime})
|
||||
result.GiftList = append(result.GiftList, user.GiftInfo{GiftID: giftID, Count: count})
|
||||
}
|
||||
|
||||
for _, rewardID := range r.ItemRewardIds {
|
||||
itemInfo := itemRewardService.GetItemCount(rewardID)
|
||||
player.ItemAdd(itemInfo.ItemId, itemInfo.ItemCnt)
|
||||
result.GiftList = append(result.GiftList, user.GiftInfo{GiftID: itemInfo.ItemId, Count: itemInfo.ItemCnt})
|
||||
appendGift(1, reward.Coins)
|
||||
appendGift(3, reward.ExpPool)
|
||||
appendGift(5, reward.Gold)
|
||||
appendGift(9, reward.EVPool)
|
||||
for _, item := range reward.Items {
|
||||
appendGift(item.ItemId, item.ItemCnt)
|
||||
}
|
||||
if r.TitleRewardIds != 0 {
|
||||
player.Service.Title.Give(r.TitleRewardIds)
|
||||
result.Tile = r.TitleRewardIds
|
||||
for _, pet := range reward.Pets {
|
||||
result.PetGift = append(result.PetGift, user.PetGiftInfo{PetID: pet.PetID, CacthTime: pet.CatchTime})
|
||||
}
|
||||
if len(reward.TitleIDs) > 0 {
|
||||
result.Tile = reward.TitleIDs[0]
|
||||
}
|
||||
|
||||
player.Service.Cdk.Log(uint32(r.ID))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
// data: 包含用户ID列表的输入信息
|
||||
// c: 当前玩家对象
|
||||
// 返回: 好友在线信息和错误码
|
||||
func (h Controller) GetOnlineFriends(data *friend.SeeOnlineInboundInfo, c *player.Player) (result *friend.SeeOnlineOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetOnlineFriends(data *SeeOnlineInboundInfo, c *player.Player) (result *friend.SeeOnlineOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &friend.SeeOnlineOutboundInfo{}
|
||||
result.Friends = make([]friend.OnlineInfo, 0)
|
||||
return
|
||||
@@ -21,7 +21,7 @@ func (h Controller) GetOnlineFriends(data *friend.SeeOnlineInboundInfo, c *playe
|
||||
// data: 包含要添加好友的用户ID
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据内容的响应和错误码
|
||||
func (h Controller) FriendAdd(data *friend.FriendAddInboundInfo, c *player.Player) (result fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) FriendAdd(data *FriendAddInboundInfo, c *player.Player) (result fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
v, ok := c.GetSpace().User.Load(data.UserID)
|
||||
|
||||
@@ -40,7 +40,7 @@ func (h Controller) FriendAdd(data *friend.FriendAddInboundInfo, c *player.Playe
|
||||
// data: 包含发起好友请求的用户ID和回复标志(1为同意,0为拒绝)
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据内容的响应和错误码
|
||||
func (h Controller) FriendAnswer(data *friend.FriendAnswerInboundInfo, c *player.Player) (result fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) FriendAnswer(data *FriendAnswerInboundInfo, c *player.Player) (result fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
v, ok := c.GetSpace().User.Load(data.UserID)
|
||||
|
||||
if ok {
|
||||
@@ -62,7 +62,7 @@ func (h Controller) FriendAnswer(data *friend.FriendAnswerInboundInfo, c *player
|
||||
// data: 包含要删除的好友ID
|
||||
// c: 当前玩家对象
|
||||
// 返回: 无数据内容的响应和错误码
|
||||
func (h Controller) FriendRemove(data *friend.FriendRemoveInboundInfo, c *player.Player) (result fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) FriendRemove(data *FriendRemoveInboundInfo, c *player.Player) (result fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
c.Service.Friend.Del(data.UserID)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// data: 包含用户ID的输入信息
|
||||
// player: 玩家对象
|
||||
// 返回: 模拟用户信息及错误码
|
||||
func (h Controller) GetUserSimInfo(data *user.SimUserInfoInboundInfo, player *player.Player) (result *user.SimUserInfoOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetUserSimInfo(data *SimUserInfoInboundInfo, player *player.Player) (result *user.SimUserInfoOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &user.SimUserInfoOutboundInfo{}
|
||||
t, ok := player.GetSpace().UserInfo.Load(data.UserId)
|
||||
if ok {
|
||||
@@ -33,7 +33,7 @@ func (h Controller) GetUserSimInfo(data *user.SimUserInfoInboundInfo, player *pl
|
||||
// data: 包含用户ID的输入信息
|
||||
// player: 当前玩家对象
|
||||
// 返回: 包含用户更多信息的输出结果和错误码
|
||||
func (h Controller) GetUserMoreInfo(data *user.MoreUserInfoInboundInfo, player *player.Player) (result *user.MoreUserInfoOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetUserMoreInfo(data *MoreUserInfoInboundInfo, player *player.Player) (result *user.MoreUserInfoOutboundInfo, err errorcode.ErrorCode) {
|
||||
result = &user.MoreUserInfoOutboundInfo{}
|
||||
info := player.Service.Info.Person(data.UserId)
|
||||
if info == nil {
|
||||
@@ -49,7 +49,7 @@ func (h Controller) GetUserMoreInfo(data *user.MoreUserInfoInboundInfo, player *
|
||||
// data: 输入信息(无实际内容)
|
||||
// player: 当前玩家对象
|
||||
// 返回: 玩家金币和代币数量及错误码
|
||||
func (h Controller) GetPlayerGoldCount(data *item.GoldOnlineRemainInboundInfo, player *player.Player) (result *item.GoldOnlineRemainOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetPlayerGoldCount(data *GoldOnlineRemainInboundInfo, player *player.Player) (result *item.GoldOnlineRemainOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
return &item.GoldOnlineRemainOutboundInfo{
|
||||
|
||||
@@ -62,7 +62,7 @@ func (h Controller) GetPlayerGoldCount(data *item.GoldOnlineRemainInboundInfo, p
|
||||
// data: 输入信息(无实际内容)
|
||||
// player: 当前玩家对象
|
||||
// 返回: 玩家总经验值及错误码
|
||||
func (h Controller) GetPlayerExp(data *item.ExpTotalRemainInboundInfo, player *player.Player) (result *item.ExpTotalRemainOutboundInfo, err errorcode.ErrorCode) {
|
||||
func (h Controller) GetPlayerExp(data *ExpTotalRemainInboundInfo, player *player.Player) (result *item.ExpTotalRemainOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
return &item.ExpTotalRemainOutboundInfo{
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user