Compare commits
203 Commits
218e23ff81
...
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 | ||
|
|
7916f90992 | ||
|
|
8ac2833ce2 | ||
|
|
fbc845526b | ||
|
|
257a979f93 | ||
|
|
ce7be73e49 | ||
|
|
28f2199142 | ||
|
|
80cfa0a07e | ||
|
|
c89632b409 | ||
|
|
5a5a1db2a3 | ||
|
|
0ac84a9509 | ||
|
|
3a9932e307 | ||
|
|
28d92c1e18 | ||
|
|
b62b4af628 | ||
|
|
31d274dd9d | ||
|
|
9c6f3988de | ||
|
|
6439995434 | ||
|
|
603c1b5ad3 | ||
|
|
4552af99c7 | ||
|
|
8e904e9068 | ||
|
|
ca7222a6c7 | ||
|
|
dabf43aefb | ||
|
|
0f862453cb | ||
|
|
d8fdc956ef | ||
|
|
6eb1a589b4 | ||
| c378d3d5f7 | |||
|
|
1a0e0b405a | ||
|
|
7405aac82d | ||
|
|
5204615c28 | ||
|
|
3c160ef695 | ||
|
|
c19ee7de03 | ||
|
|
43881fd988 | ||
|
|
d86b75408b |
5
.cnb.yml
5
.cnb.yml
@@ -1,7 +1,7 @@
|
||||
$:
|
||||
vscode:
|
||||
- runner:
|
||||
cpus: 4
|
||||
cpus: 6
|
||||
docker:
|
||||
build: .ide/Dockerfile
|
||||
services:
|
||||
@@ -27,5 +27,4 @@ main:
|
||||
username: ${GIT_USERNAME}
|
||||
password: ${GIT_ACCESS_TOKEN}
|
||||
force: true
|
||||
|
||||
#sync_mode: rebase
|
||||
sync_mode: push
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,3 +47,4 @@ public/login-linux-amd64
|
||||
public/login-login-linux-amd64
|
||||
public/logic_linux-amd64_1
|
||||
.cache/**
|
||||
.agents/**
|
||||
|
||||
@@ -18,9 +18,11 @@ ENV GOMODCACHE=/workspace/.cache/gomod
|
||||
# ==========================================
|
||||
# 2. Codex 配置 (更换时修改这里,重新 build)
|
||||
# ==========================================
|
||||
ENV CODEX_BASE_URL="https://www.ananapi.com/"
|
||||
ENV CODEX_MODEL="gpt-5.4"
|
||||
ENV OPENAI_API_KEY="sk-e697de29701289c1a3035f40c3adbd12d6eaa195ef1221c9c22cddde75baf878"
|
||||
ENV CODEX_BASE_URL="http://sub2api.sflaw.store"
|
||||
|
||||
ENV CODEX_MODEL="gpt-5.5"
|
||||
|
||||
ENV OPENAI_API_KEY="sk-e5d137d0e824adabc0cf674f61a558a2cbafdedf9857743116a3d23dcead1f68"
|
||||
|
||||
# ==========================================
|
||||
# 3. 安装系统依赖、Golang、Code-server
|
||||
|
||||
18
.ide/help.md
18
.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,10 +1,11 @@
|
||||
package coolconfig
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// cool config
|
||||
@@ -21,7 +22,7 @@ type sConfig struct {
|
||||
|
||||
}
|
||||
type ServerList struct {
|
||||
OnlineID uint32 `gorm:"column:online_id;comment:'在线ID';uniqueIndex" json:"online_id"`
|
||||
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"`
|
||||
@@ -48,7 +49,11 @@ type ServerList struct {
|
||||
}
|
||||
|
||||
func (s *ServerList) GetID() string {
|
||||
return gconv.String(100000*s.OnlineID + s.Port)
|
||||
return RuntimeIDString(s.OnlineID, s.Port)
|
||||
}
|
||||
|
||||
func (s *ServerList) RuntimeID() uint32 {
|
||||
return ComposeRuntimeID(s.OnlineID, s.Port)
|
||||
}
|
||||
|
||||
// OSS相关配置
|
||||
@@ -72,7 +77,7 @@ type file struct {
|
||||
func newConfig() *sConfig {
|
||||
var ctx g.Ctx
|
||||
config := &sConfig{
|
||||
AutoMigrate: GetCfgWithDefault(ctx, "blazing.autoMigrate", g.NewVar(false)).Bool(),
|
||||
AutoMigrate: hasDebugArg(),
|
||||
Name: GetCfgWithDefault(ctx, "server.name", g.NewVar("")).String(),
|
||||
|
||||
Eps: GetCfgWithDefault(ctx, "blazing.eps", g.NewVar(false)).Bool(),
|
||||
@@ -97,6 +102,19 @@ func newConfig() *sConfig {
|
||||
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"`
|
||||
|
||||
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())
|
||||
}
|
||||
@@ -3,10 +3,14 @@ package rpc
|
||||
import (
|
||||
"blazing/common/data/share"
|
||||
"blazing/cool"
|
||||
"blazing/cool/coolconfig"
|
||||
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
config "blazing/modules/config/service"
|
||||
|
||||
@@ -17,47 +21,101 @@ import (
|
||||
// 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, _ := share.ShareManager.GetUserOnline(userid)
|
||||
if useid1 == 0 {
|
||||
useid1, err := share.ShareManager.GetUserOnline(userid)
|
||||
if err != nil || useid1 == 0 {
|
||||
// 请求到达时用户已离线,直接视为成功
|
||||
return nil
|
||||
}
|
||||
|
||||
cl, ok := cool.GetClientOnly(useid1)
|
||||
if !ok {
|
||||
if !ok || cl == nil {
|
||||
// 目标服务器不在线,清理僵尸在线标记并视为成功
|
||||
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||
cool.DeleteClientOnly(useid1)
|
||||
return nil
|
||||
}
|
||||
cl.KickPerson(userid) //实现指定服务器踢人
|
||||
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)
|
||||
|
||||
//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)
|
||||
func (*ServerHandler) MatchJoinOrUpdate(_ context.Context, payload PVPMatchJoinPayload) error {
|
||||
return DefaultPVPMatchCoordinator().JoinOrUpdate(payload)
|
||||
}
|
||||
|
||||
//Refurh()
|
||||
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.WithReverseClient[cool.ClientHandler](""))
|
||||
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClientSetup[cool.ClientHandler]("", setupLogicReverseClient))
|
||||
|
||||
rpcServer.Register("", &ServerHandler{})
|
||||
|
||||
@@ -68,28 +126,35 @@ func CServer() *jsonrpc.RPCServer {
|
||||
var closer jsonrpc.ClientCloser
|
||||
|
||||
func StartClient(id, port uint32, callback any) *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, PVPMatchJoinPayload) error
|
||||
|
||||
MatchCancel func(context.Context, uint32) error
|
||||
} {
|
||||
//cool.Config.File.Domain = "127.0.0.1"
|
||||
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
|
||||
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),
|
||||
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
|
||||
@@ -97,10 +162,55 @@ func StartClient(id, port uint32, callback any) *struct {
|
||||
|
||||
// Setup RPCClient with reverse call handler
|
||||
var 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, 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`
|
||||
|
||||
@@ -1,627 +0,0 @@
|
||||
# Effect 重构会话总结(2026-03-28)
|
||||
|
||||
## 1. 本次会话完成内容
|
||||
|
||||
### 1.1 注释与说明统一
|
||||
- 已将 `logic/service/fight/effect` 下效果注释统一为:
|
||||
- `// Effect <id>: <desc>`
|
||||
- 说明来源:
|
||||
- `public/config/effectInfo.json`
|
||||
|
||||
### 1.2 结构整理与公共能力抽取
|
||||
- 新增并使用了子效果统一挂载 helper:
|
||||
- `addSubEffect(...)`
|
||||
- 已清理 `effect` 目录中的 `GenSub(...)` 直接调用残留,统一走 helper。
|
||||
|
||||
### 1.3 回合类基类(组合继承)已落地
|
||||
- 当前已引入的 base(位于 `logic/service/fight/effect/sub_effect_helper.go`):
|
||||
- `RoundEffectArg0Base`
|
||||
- `RoundEffectSideArg0Base`
|
||||
- `FixedDuration1Base`
|
||||
- `FixedDurationNeg1Base`
|
||||
- `FixedDuration2Base`
|
||||
- `RoundEffectArg1Base`
|
||||
- `RoundEffectSideArg1Base`
|
||||
- `RoundEffectSideArg0Minus1Base`
|
||||
- `RoundEffectSideArg0Minus1CanStackBase`
|
||||
|
||||
- 已有大量效果结构体改为嵌入上述 base,删除重复 `SetArgs` 模板代码。
|
||||
|
||||
### 1.4 编译状态
|
||||
- 已多轮执行并通过:
|
||||
- `go test ./logic/service/fight/effect`
|
||||
|
||||
### 1.5 本轮新增 effect 实现
|
||||
- 新增缺失效果实现:
|
||||
- `400` 若和对手属性相同,则技能威力翻倍
|
||||
- `480` `{0}`回合内自身所有攻击威力为两倍
|
||||
- `586` `{0}`回合内自己的属性攻击必中
|
||||
- `599` `{0}`回合内受到`{1}`伤害减少`{2}%`
|
||||
- `610` 遇到天敌时先制+`{0}`
|
||||
- `611` 下`{0}`回合自身使用攻击技能则附加`{1}`点固定伤害
|
||||
- `613` `{0}`回合内自身令对手使用的`{1}`系攻击技能无效
|
||||
- `573` `{0}`回合内若自身能力提升状态被消除或吸取则`{1}`%使对手`{2}``{3}`回合
|
||||
- `587` `{0}`回合内若被对手击败则对手损失`{1}`点体力,造成致命伤害时,对手剩余1点体力
|
||||
- `591` 造成伤害大于`{0}`,则下`{1}`回合自己所有直接攻击先制+`{2}`
|
||||
- `592` 下`{0}`回合每回合使用攻击技能`{1}`%令对手`{2}`
|
||||
- `594` 造成的伤害低于`{0}`时`{1}`%令对手`{2}`
|
||||
- `596` 技能使用成功时,`{0}`%给予对手冻伤、中毒、烧伤中任意一种异常状态
|
||||
- `597` `{0}`回合内每回合使用技能吸取对手最大体力的1/`{1}`
|
||||
- `598` `{0}`%恢复自己所有技能PP值`{1}`点
|
||||
- `627` 对手处于能力提升状态时附加其`{0}`值`{1}`%的百分比伤害
|
||||
- `628` 若对手处于能力下降状态则造成伤害的`{0}`%恢复体力
|
||||
- `629` 消除`{0}`状态,消除成功下回合自身先制+`{1}`
|
||||
- `630` `{0}`回合内`{1}`状态被消除,则有`{2}`%概率使对手`{3}`
|
||||
- `631` 消除`{0}`状态,消除成功下回合造成伤害提升`{1}`%
|
||||
- `632` 造成伤害`{0}``{1}`,则下`{2}`回合必定暴击
|
||||
- `633` 造成伤害`{0}``{1}`,则造成伤害的`{2}`%恢复体力
|
||||
- `634` 若当前体力`{0}`对手,则造成伤害的`{1}`%恢复体力
|
||||
- `635` 吸收对手能力上升状态,吸收成功,下回合先制+`{0}`
|
||||
- `636` 消除`{0}`状态,消除成功则令对手`{1}`
|
||||
- `637` 若对手处于异常状态,则对手`{0}``{1}`
|
||||
- `638` 若对手`{0}`,技能威力提升`{1}`%
|
||||
- `639` 造成伤害`{0}``{1}`,则下`{2}`回合所有技能附带`{3}`点固定伤害
|
||||
- `640` 命中后`{0}`%使对手`{1}``{2}`回合,遇到天敌概率翻倍
|
||||
- `641` 命中后`{0}`%使对手进入流血状态
|
||||
- `401` 若和对手属性相同,则技能威力翻倍
|
||||
- `585` 技能使用成功时,`{0}`
|
||||
- `589` 复制对手`{0}`的能力提升状态
|
||||
- `590` 使对手`{0}`,`{6}`%弱化效果翻倍
|
||||
- `593` 附加`{0}`的`{1}`值的`{2}`%的百分比伤害
|
||||
- `595` 技能使用成功时,`{0}`%使对手`{1}`,若没有触发,则对手`{2}`
|
||||
|
||||
- 已同步更新:
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
|
||||
### 1.6 本轮新增文件
|
||||
- `logic/service/fight/effect/400_480_586_599_610_611_613.go`
|
||||
- `logic/service/fight/effect/573_587_591_592_594_596_597_598.go`
|
||||
- `logic/service/fight/effect/627_631.go`
|
||||
- `logic/service/fight/effect/632_636.go`
|
||||
- `logic/service/fight/effect/637_641.go`
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
|
||||
### 1.7 本轮验证
|
||||
- 已执行:
|
||||
- `go test ./service/fight/effect`
|
||||
- 结果:
|
||||
- 通过
|
||||
|
||||
### 1.8 本轮结论
|
||||
- 当前这轮更适合按“低风险、可复用现有模式”的 effect 小批次推进,而不是一次性追求 `effectInfo.json` 全量覆盖。
|
||||
- 现有 `effect` 目录里已经有不少“共享实现 + 批量注册”的写法,后续判断缺失项时不能只靠 grep `InitEffect(...)`。
|
||||
- 文档第 3 节里的“未实现”列表目前仍是历史扫描快照,只能作为候选列表,不能直接当最终事实使用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前仍保留自定义 `SetArgs` 的效果(建议下一轮重点)
|
||||
|
||||
以下属于“非纯模板”或“仍待抽象”的 SetArgs:
|
||||
|
||||
- `Effect570`(`570.go`)
|
||||
- `Effect123`(`effect_119_123.go`)
|
||||
- `Effect41`(`effect_41.go`)
|
||||
- `Effect42`(`effect_42.go`)
|
||||
- `Effect46`(`effect_46.go`)
|
||||
- `Effect47`(`effect_47.go`)
|
||||
- `Effect48`(`effect_48.go`)
|
||||
- `Effect60`(`effect_60.go`)
|
||||
- `EffectPropSyncReverse`(`effect_attr.go`)
|
||||
- `SelfKill`(`selfkill.go`)
|
||||
|
||||
建议分三类继续抽 base:
|
||||
- 随机回合/随机次数类(如 41、42)
|
||||
- 次数型常驻类(如 46、47、48、SelfKill、570)
|
||||
- “SetArgs + 额外上下文初始化”类(如 Effect123、EffectPropSyncReverse)
|
||||
|
||||
---
|
||||
|
||||
## 3. 未实现(或疑似未实现)效果清单
|
||||
|
||||
### 3.0 从 `effectInfo.json` 提取的“未实现”总览(自动扫描)
|
||||
- JSON 配置总效果数:`2112`
|
||||
- 代码已注册效果数(Skill):`338`
|
||||
- JSON 中存在但代码未注册:`1779`
|
||||
- 代码中注册但 JSON 无对应条目:`5`(`21, 31, 41, 42, 174`)
|
||||
|
||||
说明:
|
||||
- 这个口径是“配置覆盖率”,不是“bug 数量”。
|
||||
- 其中大量属于未来版本/未迁移内容,不建议一次性全补;建议按战斗系统实际启用范围分批实现。
|
||||
- 以下列表为上一轮扫描快照,未随本轮新增实现实时回算。
|
||||
- 此外,扫描脚本若未把“共享实现中的批量注册”统计进去,也会把已实现效果误判成缺失。
|
||||
|
||||
JSON 中存在但代码未注册(示例前 60 项):
|
||||
- 2, 10, 11, 12, 14, 15, 16, 17, 22, 30
|
||||
- 38, 40, 45, 51, 55, 56, 61, 64, 66, 67
|
||||
- 70, 78, 84, 86, 92, 94, 96, 97, 99, 102
|
||||
- 103, 104, 106, 108, 109, 114, 118, 132, 133, 139
|
||||
- 141, 158, 162, 167, 168, 185, 401, 421, 431, 529
|
||||
- 543, 554, 569, 573, 581, 582, 583, 584, 585, 586
|
||||
|
||||
### 3.1 明确标记未实装
|
||||
- 文件存在明确标记:
|
||||
- `logic/service/fight/effect/529.go未实装`
|
||||
|
||||
### 3.2 已注册但缺少同名 `Effect{id}` 结构体(需人工确认是否由合并实现覆盖)
|
||||
- `53`
|
||||
- `74`
|
||||
- `75`
|
||||
- `186`
|
||||
- `402`
|
||||
- `433`
|
||||
- `446`
|
||||
- `451`
|
||||
- `463`
|
||||
- `497`
|
||||
- `564`
|
||||
- `588`
|
||||
|
||||
说明:
|
||||
- 这类通常可能是“多个 id 共用一个结构体实现”或“命名与 id 不一致”,需要逐个确认是否真正缺失行为。
|
||||
|
||||
### 3.3 疑似未实现(扫描规则:存在 `Effect{id}` 类型,但没有核心战斗 Hook)
|
||||
- `519`(`effect_519.go`)
|
||||
- `532`(`532.go`)
|
||||
- `552`(`552.go`)
|
||||
- `560`(`560.go`)
|
||||
- `576`(`576.go`)
|
||||
|
||||
说明:
|
||||
- 这批是“高优先级人工复查项”,不一定真的没实现,可能通过组合/间接机制生效。
|
||||
|
||||
### 3.4 当前更可信的“下一批候选”
|
||||
这一组是结合本轮人工核对后,仍然值得优先继续补的缺失 effect 候选:
|
||||
|
||||
- 当前文档 3.4 中这批候选已在本轮补齐。
|
||||
|
||||
说明:
|
||||
- 这批大多是 58x/59x 段的新效果,和当前目录中已有实现重叠较少。
|
||||
- 相比继续深挖旧扫描误差,这批更适合直接新增文件推进。
|
||||
|
||||
---
|
||||
|
||||
## 4. 下次继续的建议顺序
|
||||
|
||||
建议严格按下面顺序继续,不要重新从全量扫描开始:
|
||||
|
||||
1. 先复核文档 3.4 里的候选项是否仍未实现。
|
||||
2. 优先补“单次触发、命中附加、固定伤害、恢复、概率状态”这类低风险逻辑。
|
||||
3. 再处理“复制对手状态 / 多分支条件 / 触发链式子效果”的 58x/59x 复杂效果。
|
||||
4. 最后再回头处理文档 2 中那些仍保留自定义 `SetArgs` 的结构整理。
|
||||
|
||||
本轮更推荐的下一批实现顺序:
|
||||
|
||||
- 第一组:`529 / 552 / 560 / 576`
|
||||
- 第二组:`10 / 11 / 12 / 14 / 15 / 16`
|
||||
- 第三组:`94 / 99 / 103 / 114`
|
||||
|
||||
---
|
||||
|
||||
## 5. 下一次继续让我实现时可直接复制的指令
|
||||
|
||||
可直接用下面这句发起:
|
||||
|
||||
`继续处理 effect,按 docs/effect-refactor-summary-2026-03-28.md 的 3.3 和 4 执行:先复核 529/552/560/576,再补低风险状态附加类 10/11/12/14/15/16,每实现一批就更新同一文档和 effect_info_map.go,并跑 go test ./service/fight/effect。`
|
||||
|
||||
如果你希望按 JSON 覆盖率推进,可用这句:
|
||||
|
||||
`继续处理 effect,按 docs/effect-refactor-summary-2026-03-28.md 的 3.0 从低风险效果开始补实现:先补状态附加类(10/11/12/14/15/16/94/99/103/114),每实现一批就更新文档中的“已实现列表”和“剩余列表”。`
|
||||
|
||||
---
|
||||
|
||||
## 6. 扫描口径说明(供后续排查)
|
||||
|
||||
- 已注册效果 ID 扫描来源:
|
||||
- `InitEffect(input.EffectType.Skill, <id>, ...)`
|
||||
- `initskill(<id>, ...)`
|
||||
- 但这还不够,后续扫描必须额外统计这些“共享实现/批量注册”文件:
|
||||
- `sterStatusEffects.go`
|
||||
- `effect_power_doblue.go`
|
||||
- `EffectAttackMiss.go`
|
||||
- `EffectPhysicalAttackAddStatus.go`
|
||||
- `EffectDefeatTrigger.go`
|
||||
- `effect_attr.go`
|
||||
- `effect_EffectConditionalAddDamage.go`
|
||||
- `effect_74_75.go`
|
||||
- `effect_104_109.go`
|
||||
- `Effect{id}` 结构体与方法扫描来源:
|
||||
- `logic/service/fight/effect/*.go`
|
||||
- “疑似未实现”判断是启发式,不是最终结论,仍需代码级确认。
|
||||
|
||||
---
|
||||
|
||||
## 7. 这次改动涉及的关键文件
|
||||
|
||||
- `public/config/effectInfo.json`
|
||||
- `logic/service/fight/effect/400_480_586_599_610_611_613.go`
|
||||
- `logic/service/fight/effect/573_587_591_592_594_596_597_598.go`
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `logic/service/fight/effect/sub_effect_helper.go`
|
||||
- `docs/effect-refactor-summary-2026-03-28.md`
|
||||
|
||||
---
|
||||
|
||||
## 8. 2026-03-29 增量记录
|
||||
|
||||
### 8.1 本轮补齐的 effect
|
||||
- `663` `{0}回合内若对手使用攻击技能则{1}%使对手{2}`
|
||||
- `664` 若先出手则当回合对手无法造成攻击伤害
|
||||
- `665` 造成的伤害低于`{0}`则`{1}`回合内自身受到的伤害减少`{2}`
|
||||
- `666` 使自身下回合攻击必定先手、必定暴击
|
||||
- `667` 自身为满体力时`{0}{1}`
|
||||
|
||||
### 8.2 实现口径
|
||||
- `663` 复用与 `614` 同类的“对手使用攻击技能时触发”路径,在 `Skill_Use_ex()` 中按概率给对手附加状态。
|
||||
- `664` 复用 `170` 的先手免伤模式,在 `DamageLockEx()` 中将当回合受到的攻击伤害归零。
|
||||
- `665` 按技能实参 `250 3 100` 的实际使用方式,落为“低于阈值则给自己挂 3 回合 100 点固定减伤子效果”,不是百分比减伤。
|
||||
- `666` 落为仅对下回合攻击技能生效的先手与暴击保证:`ComparePre()` 强制先手,`ActionStart()` 强制暴击。
|
||||
- `667` 按配置说明“自身攻击+a、防御+b、特攻+c、特防+d、速度+e、命中+f”处理,仅在满体力时给自己附加前 6 项能力等级。
|
||||
|
||||
### 8.3 本轮新增文件
|
||||
|
||||
---
|
||||
|
||||
## 16. 2026-03-31 增量记录
|
||||
|
||||
### 16.1 本轮补齐的 effect
|
||||
- `764` `{0}回合内若对手使用攻击技能降低对手最大体力的1/{1}`
|
||||
- `765` `{0}回合对手无法使自身能力出现提升状态`
|
||||
- `766` 消除对手能力提升状态,消除成功则`{0}`回合内对手造成的攻击伤害不超过`{1}`点
|
||||
- `767` `{0}`回合内每回合使用技能且出手流程结束后若对手处于能力下降状态则附加给对手`{1}`点固定伤害
|
||||
- `768` 对手每处于一种异常状态则附加`{0}`点固定伤害
|
||||
- `774` 若自身当前体力高于对手则附加对手最大体力1/`{0}`的百分比伤害
|
||||
- `775` `{0}`回合内若受到的伤害大于`{1}`,则恢复自身所有体力
|
||||
- `777` 消除对手能力上升状态,消除成功下`{0}`回合必定先出手
|
||||
- `778` 反转对手的能力提升状态,反转成功则恢复自身所有体力
|
||||
- `779` 若对手处于能力提升状态则先制+2
|
||||
|
||||
### 9.2 已存在并复核通过
|
||||
- `776` 已实现于 `logic/service/fight/effect/effect_776.go`
|
||||
|
||||
### 9.3 本轮新增文件
|
||||
- `logic/service/fight/effect/764_768.go`
|
||||
- `logic/service/fight/effect/774_779.go`
|
||||
|
||||
### 9.4 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
|
||||
### 9.5 本轮顺手修复的同包编译阻塞
|
||||
- `logic/service/fight/effect/2195_2219.go`
|
||||
- 修正 `uint32 * int` 的类型不匹配
|
||||
- `logic/service/fight/effect/2220_2244.go`
|
||||
- 修正将 `info.Category` 误当函数调用的问题,改为 `info.EnumCategory`
|
||||
|
||||
### 9.6 本轮验证
|
||||
- 已执行:
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
- 结果:
|
||||
- 通过
|
||||
|
||||
### 9.7 任务文档状态
|
||||
- `task-031-effects-764-768.md` 本轮已可视为完成
|
||||
- `task-033-effects-774-779.md` 本轮已可视为完成(`776` 为既有实现)
|
||||
- 本轮未删除任务文档;如下一轮继续清理 backlog,可直接移除这两份任务文件
|
||||
|
||||
### 9.8 后续增量
|
||||
- 已继续补齐:
|
||||
- `785` 若自身攻击对手时克制关系为微弱则先制+2
|
||||
- `786` 令对手随机进入`{0}`种异常状态
|
||||
- `787` `{0}`回合内使用技能后若对手处于能力提升状态则附加对手最大体力1/`{1}`的百分比伤害
|
||||
- `788` 消除对手能力提升,消除成功`{0}`回合内免疫异常状态
|
||||
- `789` 消除对手回合类效果,消除成功对手下`{0}`回合受到的伤害翻倍
|
||||
- 新增文件:
|
||||
- `logic/service/fight/effect/785_789.go`
|
||||
- `logic/service/fight/effect/795_799.go`
|
||||
- 已继续补齐:
|
||||
- `795` 每次使用则当回合造成的攻击伤害额外提升`{0}`%,最高额外提升`{1}`%
|
||||
- `796` `{0}`回合内每回合吸取对手当前体力的1/`{1}`
|
||||
- `797` 消除对手回合类效果,消除成功`{0}`回合内对手无法通过自身技能恢复体力
|
||||
- `798` 若对手处于能力提升状态,则对手`{0}`回合内造成的伤害不超过`{1}`
|
||||
- `799` 恢复自身最大体力的1/`{0}`并给对手造成等量百分比伤害,自身体力低于1/`{1}`时效果翻倍
|
||||
- `logic/service/fight/effect/663_667.go`
|
||||
|
||||
### 8.4 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `docs/effect-unimplemented-tasks/task-013-effects-663-667.md` 已完成,可从任务目录移除
|
||||
|
||||
### 8.5 本轮验证
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
|
||||
---
|
||||
|
||||
## 9. 2026-03-29 增量记录(二)
|
||||
|
||||
### 16.1 本轮补齐的 effect
|
||||
- `668` 若对手处于能力提升状态则先制额外+1
|
||||
- `669` 当回合击败对手则下回合自身攻击先制+1
|
||||
- `670` `{0}`回合,每回合附加`{1}`的`{2}`值的`{3}%`的百分比伤害
|
||||
- `671` 若对手处于异常状态则恢复造成伤害的`{0}%`的体力
|
||||
- `672` 当回合击败对手则恢复自身全部体力
|
||||
|
||||
### 16.2 实现口径
|
||||
- `668` 复用 `539` 的条件先制模式,在 `ComparePre()` 中于对手存在能力提升状态时直接给当前技能先制+1。
|
||||
- `669` 按“当回合击败后,为下回合攻击技能生效”处理:在 `SwitchOut()` 中标记击败成立,下一回合 `ComparePre()` 仅对攻击技能追加先制+1。
|
||||
- `670` 参照 `419` 与 `593` 的组合语义,实现为回合类附加伤害:效果持续期间每次使用技能时,附加一次基于指定目标属性值的固定伤害。
|
||||
- `671` 复用 `687` 的伤害回血模式,但条件改为“对手处于任意异常状态”。
|
||||
- `672` 按击败即时触发处理:在对手因本次攻击退场时,立刻将自身体力回复至满值。
|
||||
|
||||
### 9.3 本轮新增文件
|
||||
- `logic/service/fight/effect/668_672.go`
|
||||
|
||||
### 9.4 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `docs/effect-unimplemented-tasks/task-014-effects-668-672.md` 已完成,可从任务目录移除
|
||||
|
||||
### 9.5 本轮验证
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
|
||||
---
|
||||
|
||||
## 10. 2026-03-29 增量记录(三)
|
||||
|
||||
### 10.1 本轮补齐的 effect
|
||||
- `642` `{0}回合内若对手攻击技能命中则己方在场精灵{1}%做出{2}`
|
||||
- `643` `{0}%概率使对手{1}回合内{2}能力每回合变化{3}`
|
||||
- `644` 当回合未击败对手,则减少对手当前体力`1/{0}`
|
||||
- `645` 体力低于`1/{0}`时威力`{1}`倍
|
||||
- `646` 体力高于对手时,此技能命中后 100% 使对手能力下降
|
||||
|
||||
### 10.2 实现口径
|
||||
- `642` 按 `moves.json` 中 `1000642` 的说明实现反击档位映射:`0-5` 分别对应 `50/100/150/200/250/300` 点固定伤害;挂在 defender 侧 `Skill_Use_ex()`,仅对命中的攻击技能生效。
|
||||
- `643` 按真实技能实参 `55 2 1 -1` 这类布局处理为“命中后按概率给对手挂回合子效果”,子效果在 `TurnEnd()` 对指定能力执行一次 `SetProp`。
|
||||
- `644` 复用 `579` 的“未击败对手”判定时机,落在 `Action_end()`,按对手当前体力结算 `1/n` 的百分比伤害。
|
||||
- `645` 复用 `37` 的低血线威力倍率模式,在 `SkillHit()` 中按当前体力是否低于最大体力 `1/n` 改写技能威力。
|
||||
- `646` 未按任务文档里的占位 `param` 硬编码;改按实际技能配置的 6 段能力变化参数通用处理,仅在自身体力高于对手且本次技能命中后生效。
|
||||
|
||||
### 10.3 本轮新增文件
|
||||
- `logic/service/fight/effect/642_646.go`
|
||||
|
||||
### 10.4 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `docs/effect-unimplemented-tasks/task-009-effects-642-646.md` 已完成,可从任务目录移除
|
||||
|
||||
---
|
||||
|
||||
## 11. 2026-03-29 增量记录(四)
|
||||
|
||||
### 11.1 本轮补齐的 effect
|
||||
- `769` 若对手不处于异常状态则造成的攻击伤害额外提升`{0}%`
|
||||
- `770` 若对手处于异常状态,则恢复自身全部体力
|
||||
- `771` `{0}`回合内每次使用攻击技能都有`{1}%`概率使对手进入任意一种异常状态
|
||||
- `772` `{0}`回合内若对手使用攻击技能则有`{1}%`概率随机进入烧伤、冻伤、中毒、麻痹、害怕、睡眠中的一种异常状态
|
||||
- `773` 若自身体力低于对手则与对手互换体力
|
||||
|
||||
### 11.2 实现口径
|
||||
- `769` 复用 `1103` 的条件增伤写法,在 `SkillHit()` 中仅对攻击技能生效,并在对手不存在任意异常状态时追加威力百分比。
|
||||
- `770` 按技能结算后触发处理,落在 `Skill_Use()`,满足“对手处于异常状态”时直接回复自身满体力。
|
||||
- `771` 作为回合类自身增益实现,持续期间在 `OnSkill()` 针对每次攻击技能按概率给对手附加一项随机异常状态。
|
||||
- `772` 参照 `559` 的 defender 侧监听时机,落在 `Skill_Use_ex()`;对手使用攻击技能时按概率附加六选一异常状态。
|
||||
- `773` 复用 `529` 的直接改写当前体力思路,在满足“自身体力低于对手”时交换双方当前体力,并分别按各自最大体力上限截断。
|
||||
|
||||
### 11.3 本轮新增文件
|
||||
- `logic/service/fight/effect/769_773.go`
|
||||
|
||||
### 11.4 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `docs/effect-unimplemented-tasks/task-032-effects-769-773.md` 已完成,可从任务目录移除
|
||||
|
||||
---
|
||||
|
||||
## 12. 2026-03-29 增量记录(五)
|
||||
|
||||
### 12.1 本轮补齐的 effect
|
||||
- `1498` 随机附加烧伤、冻伤、失明、失神中的 `{0}` 种异常状态,未触发则自身下 `{1}` 回合造成的伤害提升 `{2}%`
|
||||
- `1499` 体力低于最大体力的 `1/3` 时先制 `+3`
|
||||
- `1500` 1 回合做 `{0}-{1}` 次攻击,自身处于护盾状态下连击上限为 `{2}`
|
||||
- `1501` 命中后为对手种下一颗黑暗之种
|
||||
- `1502` 对手身上存在黑暗之种时先制 `+1`
|
||||
|
||||
### 12.2 实现口径
|
||||
- `1498` 复用 `1111` 的“未触发则挂自身增伤子效果”模式;随机状态池按任务文案落为烧伤、冻伤、失明、失神,若本次一个状态都未成功挂上,则给自身添加持续 `{1}` 回合的增伤子效果。
|
||||
- `1499` 复用现有条件先制写法,在 `ComparePre()` 中按当前体力是否低于最大体力 `1/3` 直接修改当前技能优先级。
|
||||
- `1500` 参照仓库现有多段技能处理口径,不做逐段攻击,而是在 `Damage_Mul()` 中按随机连击次数折算红伤倍率;若自身当前存在护盾,则用 `{2}` 约束连击上限。
|
||||
- `1501` 作为挂在 defender 身上的持久子效果实现:命中后附加“黑暗之种”,前 4 次 `TurnEnd()` 随机扣 1 个技能 PP,成熟后每回合所有技能 PP `-1`;下场后清除。
|
||||
- `1502` 在 `ComparePre()` 中检查对手是否持有 `1501` 的子效果,存在时当前技能先制 `+1`。
|
||||
|
||||
### 12.3 模型假设
|
||||
- 仓库当前未注册“失神”状态,本轮按状态 ID `29` 追加了一个最小可用实现,行为复用 `StatusCannotAct`。
|
||||
- `1500` 仍受当前战斗模型限制,只能按总连击数折算伤害,不能表现逐段命中、逐段触发的细粒度行为。
|
||||
|
||||
### 12.4 本轮新增文件
|
||||
- `logic/service/fight/effect/1498_1502.go`
|
||||
|
||||
### 12.5 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `docs/effect-unimplemented-tasks/README.md`
|
||||
- `docs/effect-unimplemented-tasks/task-177-effects-1498-1502.md` 已完成,可从任务目录移除
|
||||
|
||||
### 12.6 本轮验证
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
|
||||
---
|
||||
|
||||
## 13. 2026-03-29 增量记录(六)
|
||||
|
||||
### 13.1 本轮补齐的 effect
|
||||
- `1503` 清除对手身上的黑暗之种,清除成功则令对手随机受到 `1-500` 点固定伤害
|
||||
- `1504` `40%` 令对手诅咒,若对手身上存在黑暗之种则概率翻倍
|
||||
- `1505` 黑暗之种成长期时附加 `200` 点固定伤害,黑暗之种长大后固定伤害翻倍
|
||||
- `1506` 若对手不是龙系精灵则恢复自身 `{0}` 点体力
|
||||
- `1507` `{0}` 回合内自身受到攻击则令对手随机进入 `{1}` 种异常状态,未触发则消除对手回合类效果
|
||||
|
||||
### 13.2 实现口径
|
||||
- `1503` 复用本轮新增的黑暗之种清理 helper,在 `Skill_Use()` 中清除对手持有的 `1501` 子效果;仅当成功清除时追加一次 `1-500` 的随机固定伤害。
|
||||
- `1504` 按任务文案直接实现为基础 `40%` 概率,若对手当前仍持有黑暗之种则翻倍到 `80%`;仓库尚无诅咒状态注册,本轮补了一个最小可用的状态壳以承接后续联动。
|
||||
- `1505` 读取 `1501` 子效果当前成长阶段:成长期附加 `200` 固定伤害,成熟后附加 `400` 固定伤害;若对手不存在黑暗之种则不触发。
|
||||
- `1506` 使用宠物当前系别组合判断是否包含龙系;仅在对手主属性、副属性均不为龙系时恢复自身体力。
|
||||
- `1507` 复用 `1228` 的 defender 侧监听模式,落在 `Skill_Use_ex()`:对手使用攻击技能命中本体时,随机附加若干异常状态;若整段持续时间内一次都未成功触发,则在最后一回合结束时清除对手回合类效果。
|
||||
|
||||
### 13.3 模型假设
|
||||
- 仓库当前缺少“诅咒”状态实现,本轮按状态 ID `23` 注册了一个最小 `BaseStatus` 版本,只提供状态存在性与常规下场清理,不额外附带持续结算逻辑。
|
||||
- `1505` 的“黑暗之种成长期/长大后”判断直接复用 `1501` 子效果内部阶段计数:前 4 次回合结束视为成长期,第 5 次起视为成熟。
|
||||
|
||||
### 13.4 本轮新增文件
|
||||
- `logic/service/fight/effect/1503_1507.go`
|
||||
|
||||
### 13.5 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `docs/effect-unimplemented-tasks/README.md`
|
||||
- `docs/effect-unimplemented-tasks/task-178-effects-1503-1507.md` 已完成,可从任务目录移除
|
||||
|
||||
### 13.6 本轮验证
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
|
||||
---
|
||||
|
||||
## 14. 2026-03-30 增量记录(一)
|
||||
|
||||
### 14.1 本轮补齐的 effect
|
||||
- `1508` 先出手时无视攻击免疫效果
|
||||
- `1509` 令对手全属性-`{0}` 且随机 `{1}` 个技能 PP 值归零,技能无效时消耗自身全部体力并令对手全属性-1,然后对手下 3 次使用技能消耗的 PP 值为 3 倍
|
||||
- `1510` `{0}` 回合内对手主动切换精灵则登场精灵 `{1}%` 随机进入 `{2}` 种异常状态
|
||||
- `1511` 先出手时免疫当回合受到的攻击伤害,若对手为自身天敌则免疫并反弹给对手造成伤害值 `{0}%` 的百分比伤害
|
||||
- `1512` 集结天幕四龙之神力,使自身下 2 回合先制+3且攻击必定命中、必定致命
|
||||
|
||||
### 14.2 实现口径
|
||||
- `1508` 采用仓库现有模型下的局部支持方案:若自身本次先出手且使用攻击技能,则在伤害结算前临时屏蔽若干常见“攻击伤害清零类” immunity effect,并在技能结算结束后恢复。
|
||||
- `1509` 正常命中时直接令对手全属性下降并随机清空若干技能 PP;若本次技能实体存在但 `AttackTime == 0`,则按“技能无效”分支处理:自损全部体力、令对手全属性-1,并给对手挂 3 次 PP 三倍消耗子效果。
|
||||
- `1510` 复用 `1562` 的“主动切换后对登场精灵生效”模式:效果挂在对手侧,仅在对手主动切换下场时置 pending,登场后按概率随机附加若干异常状态。
|
||||
- `1511` 复用 `170/1011` 的免疫伤害写法,落在 `DamageLockEx()`;若自身先出手则直接免疫本回合受到的红伤,若对手同时为自身天敌,则按原伤害值 `{0}%` 追加一次百分比伤害反弹。
|
||||
- `1512` 作为持续 2 回合的自身子效果实现,在 `ComparePre()` 中固定追加先制 `+3`,并在 `ActionStart()` 中对攻击技能同时赋予必中与必定致命。
|
||||
|
||||
### 14.3 模型假设
|
||||
- `1508` 当前没有通用“无视攻击免疫”标记位,本轮仅覆盖仓库内已识别的常见攻击免疫 effect:`170/525/570/850/1011/1511`。对其他未来新增或语义不同的 defender 侧免疫实现,不保证自动生效。
|
||||
- `1509` 的“技能无效时”按仓库现有口径解释为“技能实体存在,但本次结算后 `AttackTime == 0`”,不把被控未出手、无 PP 无法释放这类情况算作技能无效。
|
||||
|
||||
### 14.4 本轮新增文件
|
||||
- `logic/service/fight/effect/1508_1512.go`
|
||||
|
||||
### 14.5 本轮同步更新
|
||||
- `logic/service/fight/effect/effect_info_map.go`
|
||||
- `docs/effect-unimplemented-tasks/README.md`
|
||||
- `docs/effect-unimplemented-tasks/task-179-effects-1508-1512.md` 已完成,可从任务目录移除
|
||||
|
||||
### 14.6 本轮验证
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
|
||||
---
|
||||
|
||||
## 15. 2026-03-31 增量记录(二)
|
||||
|
||||
### 15.1 本轮新增实现
|
||||
- `780` `{0}` 回合内受到攻击则 `{1}%` 令对手随机 `{2}` 个技能 PP 值归零
|
||||
- `781` 消除对手回合类效果,消除成功则 `{0}` 回合内令对手使用的属性技能无效
|
||||
- `782` `{0}%` 令对手 `{1}`,每次使用概率增加 `{2}%`,最高概率 `{3}%`
|
||||
- `783` `{0}` 回合内自身能力提升状态被消除或吸取时附加对手最大体力 `1/{1}` 的百分比伤害
|
||||
- `784` 若本回合击败对手则将对手的能力提升效果转移到自己身上
|
||||
- `790` `{0}` 回合内自身所有攻击无视伤害限制效果
|
||||
- `791` `{0}` 回合内每回合使用技能恢复自身最大体力的 `1/{1}`,当前体力低于对手时恢复翻倍
|
||||
- `792` 先出手时对手当回合攻击技能无效
|
||||
- `793` 若造成的伤害低于 `{0}`,则下 `{1}` 回合每回合造成 `{2}` 点固定伤害
|
||||
- `794` 消除对手能力提升,消除成功则抵挡 `{0}` 回合内对手的攻击伤害
|
||||
|
||||
### 15.2 本轮复核后确认已存在
|
||||
- `637-641` 已在 `637_641.go` 落地,相关任务文档可删除
|
||||
- `764-768` 已在 `764_768.go` 落地,相关任务文档可删除
|
||||
- `774-779` 已在 `774_779.go` 落地,相关任务文档可删除
|
||||
- `785-789` 已在 `785_789.go` 落地,相关任务文档可删除
|
||||
|
||||
### 15.3 本轮同步更新
|
||||
- 新增 `logic/service/fight/effect/780_784.go`
|
||||
- 新增 `logic/service/fight/effect/790_794.go`
|
||||
- 删除 `docs/effect-unimplemented-tasks/task-008-effects-637-641.md`
|
||||
- 删除 `docs/effect-unimplemented-tasks/task-031-effects-764-768.md`
|
||||
- 删除 `docs/effect-unimplemented-tasks/task-033-effects-774-779.md`
|
||||
- 删除 `docs/effect-unimplemented-tasks/task-034-effects-780-784.md`
|
||||
- 删除 `docs/effect-unimplemented-tasks/task-035-effects-785-789.md`
|
||||
- 删除 `docs/effect-unimplemented-tasks/task-036-effects-790-794.md`
|
||||
|
||||
### 15.4 本轮验证
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
|
||||
## 16. 2026-03-31 增量记录
|
||||
|
||||
### 16.1 本轮补齐的 effect
|
||||
- `1067` `{0}回合内每回合使用技能恢复自身最大体力的1/{1},恢复体力时若自身体力低于最大体力的1/{2}则恢复效果转变为吸取对手最大体力的1/{3}`
|
||||
- `1068` 下`{0}`回合受到致命伤害时残留`{1}`点体力
|
||||
- `1069` 反转自身能力下降状态,反转成功则`{0}`回合内躲避所有攻击
|
||||
- `1070` 对手处于能力下降状态时自身先制+1
|
||||
- `1071` `{0}`回合内若对手恢复体力(药剂恢复除外),则`{1}`回合内自身攻击附加`{2}`点固定伤害
|
||||
|
||||
### 16.2 实现口径
|
||||
- `1067` 复用 `707` 的“每回合使用技能后回复”模式,在低血线时改为按对手最大体力比例吸取并同步治疗自身。
|
||||
- `1068` 通过回合子效果在致命伤害结算前锁定剩余体力值,满足“下{0}回合保留{1}点体力”。
|
||||
- `1069` 先反转自身全部能力下降,再在成功时挂载受击 MISS 子效果,仅拦截攻击技能。
|
||||
- `1070` 复用现有 `1243` 同类优先级判断,对手存在任一能力下降时自身先制+1。
|
||||
- `1071` 在对手身上挂恢复监听子效果,排除药剂恢复;触发后给自身挂固定伤害增益子效果。
|
||||
|
||||
### 16.3 本轮同步项
|
||||
- 已补 `logic/service/fight/effect/effect_info_map.go` 中 `1067-1071` 的说明映射。
|
||||
- `docs/effect-unimplemented-tasks/task-091-effects-1067-1071.md` 已删除。
|
||||
|
||||
## 17. 2026-03-31 增量记录
|
||||
|
||||
### 17.1 本轮补齐的 effect
|
||||
- `1072` 附加自身当前体力`{0}`%的百分比伤害,连续使用每次增加`{1}`%,最高`{2}`%
|
||||
- `1073` `{0}`回合内受到的伤害大于`{1}`则主动恢复自身全部体力并造成等同于恢复量的固定伤害
|
||||
- `1074` 造成的伤害大于`{0}`则对手`{1}`%`{2}`,未触发则自身下`{3}`回合攻击有`{4}`%的概率使对手`{5}`
|
||||
- `1075` 恢复自身最大体力的1/`{0}`,自身体力低于1/`{1}`时回满
|
||||
- `1076` 对手不处于能力提升状态时先制+2
|
||||
|
||||
### 17.2 实现口径
|
||||
- `1072` 参考 `1280`,按当前体力百分比追加伤害并在连续使用时累计加成,封顶于实参上限。
|
||||
- `1073` 参考 `775`,在受击伤害超过阈值时于行动结束节点回满自身,并按实际恢复量反打固定伤害。
|
||||
- `1074` 参考 `1256/1271`,先按本次造成伤害判定即时概率状态;未触发时再给自身挂后续攻击概率附加状态的回合子效果。
|
||||
- `1075` 参考 `704/900`,常态回复最大体力的分数值,低血线时改为直接补满缺失体力。
|
||||
- `1076` 参考 `779` 的反向条件,对手不存在能力提升状态时令自身先制+2。
|
||||
|
||||
### 17.3 本轮同步项
|
||||
- 已补 `logic/service/fight/effect/effect_info_map.go` 中 `1072-1076` 的说明映射。
|
||||
- `docs/effect-unimplemented-tasks/task-092-effects-1072-1076.md` 已删除。
|
||||
|
||||
## 18. 2026-03-31 增量记录
|
||||
|
||||
### 18.1 本轮补齐的 effect
|
||||
- `1077` `{0}`回合内对手使用攻击技能后使对手`{1}`,未触发则对手下`{2}`回合属性技能命中效果失效
|
||||
- `1078` 使对手随机进入`{0}`种异常状态,未触发则下`{1}`回合自身属性技能先制+`{2}`
|
||||
- `1079` 命中后`{0}`%令对手`{1}`,未触发则下`{2}`回合自身攻击技能先制+`{3}`
|
||||
- `1080` 连续使用时先制+1
|
||||
- `1081` 若对手处于能力提升状态则先制+1
|
||||
|
||||
### 18.2 实现口径
|
||||
- `1077` 参考 `1267/998`,在己方身上挂对手攻击技能监听,若整段持续期未触发则给对手补一段属性技能失效子效果。
|
||||
- `1078` 先尝试随机附加异常,若没有新增异常状态成功落到对手身上,再给自身挂属性技能先制子效果。
|
||||
- `1079` 参考概率异常+后续先制类效果,命中后先判即时异常,未触发则给自身挂攻击技能先制子效果。
|
||||
- `1080` 复用 `AddLvelEffect` 的连续使用计数,在连续使用同一技能时给当前技能先制+1。
|
||||
- `1081` 直接复用 `779` 的同类判断,只是条件改为“对手处于能力提升状态”且先制值为+1。
|
||||
|
||||
### 18.3 本轮同步项
|
||||
- 已补 `logic/service/fight/effect/effect_info_map.go` 中 `1077-1081` 的说明映射。
|
||||
- `docs/effect-unimplemented-tasks/task-093-effects-1077-1081.md` 已删除。
|
||||
|
||||
## 18. 2026-03-31 增量记录
|
||||
|
||||
### 18.1 本轮补齐的 effect
|
||||
- `1077` `{0}`回合内对手使用攻击技能后使对手`{1}`,未触发则对手下`{2}`回合属性技能命中效果失效
|
||||
- `1078` 使对手随机进入`{0}`种异常状态,未触发则下`{1}`回合自身属性技能先制+`{2}`
|
||||
- `1079` 命中后`{0}`%令对手`{1}`,未触发则下`{2}`回合自身攻击技能先制+`{3}`
|
||||
- `1080` 连续使用时先制+1
|
||||
- `1081` 若对手处于能力提升状态则先制+1
|
||||
|
||||
### 18.2 实现口径
|
||||
- `1077` 参考 `1267/1490/738`,在己方身上挂对手攻击技能监听;状态未成功挂上时,再给对手挂属性技能命中效果失效子效果。
|
||||
- `1078` 参考 `786/1100`,先尝试随机异常状态;未触发时给自身挂仅对属性技能生效的先制子效果。
|
||||
- `1079` 参考 `756/1230`,命中后按概率给对手附加状态;未触发时给自身挂仅对攻击技能生效的回合先制子效果。
|
||||
- `1080` 记录上次使用技能 ID,在连续使用同一技能时给予先制+1。
|
||||
- `1081` 复用 `779` 的同类条件判断,对手存在能力提升状态时自身先制+1。
|
||||
|
||||
### 18.3 本轮同步项
|
||||
- 已补 `logic/service/fight/effect/effect_info_map.go` 中 `1077-1081` 的说明映射。
|
||||
- `docs/effect-unimplemented-tasks/task-093-effects-1077-1081.md` 已删除。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 153: Effects 1378-1382
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1378
|
||||
- `argsNum`: `9`
|
||||
- `info`: `全属性+{0},{1}%概率强化效果翻倍,未触发则{2}回合内每回合{3}`
|
||||
- `param`: `0,3,3`
|
||||
|
||||
### Effect 1379
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身不处于能力提升状态时50%的概率打出致命一击`
|
||||
|
||||
### Effect 1380
|
||||
- `argsNum`: `3`
|
||||
- `info`: `牺牲自身全部体力,使对手全属性-{0}且{1}回合内先制-{2}`
|
||||
|
||||
### Effect 1381
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除对手回合类效果,消除成功则自身{0}回合内免疫并反弹异常状态`
|
||||
|
||||
### Effect 1382
|
||||
- `argsNum`: `3`
|
||||
- `info`: `命中后获得{0}层自然祝福,若造成的伤害高于{1}则额外获得{2}层自然祝福`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 154: Effects 1383-1387
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1383
|
||||
- `argsNum`: `1`
|
||||
- `info`: `全属性+{0},自身处于自然祝福状态下则强化效果翻倍`
|
||||
|
||||
### Effect 1384
|
||||
- `argsNum`: `1`
|
||||
- `info`: `自身获得{0}层自然祝福`
|
||||
|
||||
### Effect 1385
|
||||
- `argsNum`: `0`
|
||||
- `info`: `消耗自身所有自然祝福,每消耗1层则随机令对手1个技能PP值归零`
|
||||
|
||||
### Effect 1386
|
||||
- `argsNum`: `1`
|
||||
- `info`: `当回合击败对手则回合结束时损失{0}层自然祝福`
|
||||
|
||||
### Effect 1387
|
||||
- `argsNum`: `2`
|
||||
- `info`: `获得{0}点护盾,护盾消失时附加{1}点固定伤害`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,38 +0,0 @@
|
||||
# Task 155: Effects 1388-1392
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1388
|
||||
- `argsNum`: `2`
|
||||
- `info`: `获得{0}点护罩,护罩消失时使对手{1}回合攻击技能无效`
|
||||
|
||||
### Effect 1389
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内自身能力提升状态被消除或吸取则{1}%使对手{2},未触发则消除对手回合类效果`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
### Effect 1390
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}%令对手{1},未触发则{2}回合内每回合{3}%闪避对手攻击`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1391
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内若对手使用属性技能则造成伤害前令对手{1},未触发则消除对手回合类效果`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1392
|
||||
- `argsNum`: `0`
|
||||
- `info`: `解除自身能力下降状态,解除成功则消除对手回合类效果`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 160: Effects 1413-1417
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1413
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}%的概率打出致命一击,未触发则下{1}回合自身先制+{2}`
|
||||
|
||||
### Effect 1414
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除敌我双方回合类效果,消除任意一项成功则敌我双方同时进入{0}状态`
|
||||
- `param`: `1,0,0`
|
||||
|
||||
### Effect 1415
|
||||
- `argsNum`: `2`
|
||||
- `info`: `造成伤害的{0}%恢复自身体力,若自身体力低于对手则效果提升至{1}%`
|
||||
|
||||
### Effect 1416
|
||||
- `argsNum`: `3`
|
||||
- `info`: `消耗自身全部体力,使对手受到自身最大体力1/{0}的百分比伤害,同时使自身下只出场精灵前{1}回合{2}%闪避对手的攻击技能`
|
||||
|
||||
### Effect 1417
|
||||
- `argsNum`: `2`
|
||||
- `info`: `对手不处于能力下降状态则使对手随机{0}项能力值-{1}`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 161: Effects 1418-1422
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1418
|
||||
- `argsNum`: `1`
|
||||
- `info`: `损失自身{0}点体力`
|
||||
|
||||
### Effect 1419
|
||||
- `argsNum`: `2`
|
||||
- `info`: `消除双方能力提升状态,消除任意一方成功则自身免疫下{0}次异常状态且令对手随机{1}个技能PP值归零`
|
||||
|
||||
### Effect 1420
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身处于能力提升状态时50%打出致命一击`
|
||||
|
||||
### Effect 1421
|
||||
- `argsNum`: `4`
|
||||
- `info`: `当回合打出的攻击伤害在{0}—{1}之间则{2}%令对手{3}`
|
||||
- `param`: `1,3,3`
|
||||
|
||||
### Effect 1422
|
||||
- `argsNum`: `2`
|
||||
- `info`: `获得{0}点护罩,护罩消失时恢复自身{1}点体力`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,39 +0,0 @@
|
||||
# Task 162: Effects 1423-1427
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1423
|
||||
- `argsNum`: `3`
|
||||
- `info`: `消除对手回合类效果,消除成功则{0}%令对手{1},未触发则恢复自身最大体力的1/{2}`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1424
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%令对手{1},自身处于能力提升状态时概率翻倍`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1425
|
||||
- `argsNum`: `7`
|
||||
- `info`: `造成的伤害低于{0}则自身{1}`
|
||||
- `param`: `0,1,1`
|
||||
|
||||
### Effect 1426
|
||||
- `argsNum`: `2`
|
||||
- `info`: `若对手不处于异常状态则{0}%令对手{1}`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1427
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内每回合{1}%闪避对手攻击,未触发则受到的伤害减少{2}%`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 164: Effects 1433-1437
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1433
|
||||
- `argsNum`: `0`
|
||||
- `info`: `损失“辛”所有体力,同时使对手受到损失体力值1/2的固定伤害`
|
||||
|
||||
### Effect 1434
|
||||
- `argsNum`: `0`
|
||||
- `info`: `“辛”死亡后令自身下1次受到的攻击伤害减少50%`
|
||||
|
||||
### Effect 1435
|
||||
- `argsNum`: `0`
|
||||
- `info`: `“辛”存活时反转自身能力下降状态,反转成功则2回合内令对手使用的属性技能无效`
|
||||
|
||||
### Effect 1436
|
||||
- `argsNum`: `2`
|
||||
- `info`: `当回合击败对手则{0}%恢复自身全部体力,未触发则全属性+{1}`
|
||||
|
||||
### Effect 1437
|
||||
- `argsNum`: `0`
|
||||
- `info`: `“辛”存活时造成攻击伤害的50%恢复自身体力,“辛”死亡后造成的攻击伤害额外提升50%`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 165: Effects 1438-1442
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1438
|
||||
- `argsNum`: `1`
|
||||
- `info`: `若对手处于能力下降状态,则{0}回合内令对手使用的属性技能无效`
|
||||
|
||||
### Effect 1439
|
||||
- `argsNum`: `1`
|
||||
- `info`: `对手处于能力下降状态造成的伤害提升{0}%`
|
||||
|
||||
### Effect 1440
|
||||
- `argsNum`: `1`
|
||||
- `info`: `免疫下{0}次自身受到的异常状态`
|
||||
|
||||
### Effect 1441
|
||||
- `argsNum`: `1`
|
||||
- `info`: `解除自身能力下降状态,解除成功则使对手随机{0}个技能PP值归零`
|
||||
|
||||
### Effect 1442
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内每回合{1}%闪避对手攻击,未触发则回合结束时{2}%令对手{3}`
|
||||
- `param`: `1,3,3`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 166: Effects 1443-1447
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1443
|
||||
- `argsNum`: `2`
|
||||
- `info`: `连续使用时威力提升{0}%,最高提升{1}%`
|
||||
|
||||
### Effect 1444
|
||||
- `argsNum`: `0`
|
||||
- `info`: `威力根据自身体力变化,当前剩余体力越少则威力越大`
|
||||
|
||||
### Effect 1445
|
||||
- `argsNum`: `3`
|
||||
- `info`: `吸取对手能力提升状态,吸取成功则{0}回合内对手属性技能无效,若对手不处于能力提升状态则自身下{1}回合先制+{2}`
|
||||
|
||||
### Effect 1446
|
||||
- `argsNum`: `2`
|
||||
- `info`: `获得{0}点护罩,护罩消失时使对手全属性-{1}`
|
||||
|
||||
### Effect 1447
|
||||
- `argsNum`: `3`
|
||||
- `info`: `自身体力高于最大体力的1/{0}时{1}%令对手{2}`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 167: Effects 1448-1452
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1448
|
||||
- `argsNum`: `2`
|
||||
- `info`: `自身体力低于最大体力的1/{0}时造成的伤害提升{1}%`
|
||||
|
||||
### Effect 1449
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内对手无法通过自身技能恢复体力且受到原本恢复量1/{1}的百分比伤害`
|
||||
|
||||
### Effect 1450
|
||||
- `argsNum`: `2`
|
||||
- `info`: `吸取对手{0}点体力,若对手任意1项技能PP值小于{1}点则吸取效果翻倍`
|
||||
|
||||
### Effect 1451
|
||||
- `argsNum`: `2`
|
||||
- `info`: `若自身体力高于对手则获得{0}点护盾,若自身体力低于对手则恢复自身最大体力的1/{1}`
|
||||
|
||||
### Effect 1452
|
||||
- `argsNum`: `1`
|
||||
- `info`: `召唤龙神,使自身下{0}回合获得战之龙魂效果`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 168: Effects 1453-1457
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1453
|
||||
- `argsNum`: `1`
|
||||
- `info`: `召唤龙神,使自身下{0}回合获得御之龙魂效果`
|
||||
|
||||
### Effect 1454
|
||||
- `argsNum`: `4`
|
||||
- `info`: `1回合做{0}-{1}次攻击,每次攻击有{2}%的概率附加{3}点固定伤害`
|
||||
|
||||
### Effect 1455
|
||||
- `argsNum`: `2`
|
||||
- `info`: `1回合做{0}-{1}次攻击,若本回合攻击次数达到最大则八岐大蛇进入暴怒,必定秒杀对手`
|
||||
|
||||
### Effect 1456
|
||||
- `argsNum`: `2`
|
||||
- `info`: `消耗自身全部体力,使对手受到自身最大体力1/{0}的百分比伤害,同时使自身下只出场精灵前{1}回合免疫受到的攻击伤害`
|
||||
|
||||
### Effect 1457
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内攻击技能{1}%令对手{2},未触发则使对手随机{3}项技能PP值归零`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,38 +0,0 @@
|
||||
# Task 169: Effects 1458-1462
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1458
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内每回合{1}%对手属性技能无效,未触发则{2}%令对手{3}`
|
||||
- `param`: `1,3,3`
|
||||
|
||||
### Effect 1459
|
||||
- `argsNum`: `3`
|
||||
- `info`: `先出手时{0}%令对手{1},未触发则对手{2}回合属性技能无效`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1460
|
||||
- `argsNum`: `3`
|
||||
- `info`: `附加{0}{1}值{2}%的百分比伤害,先出手时附加效果翻倍`
|
||||
- `param`: `4,0,0|2,1,1`
|
||||
|
||||
### Effect 1461
|
||||
- `argsNum`: `1`
|
||||
- `info`: `恢复自身最大体力的1/{0},体力低于对手时附加等量百分比伤害`
|
||||
|
||||
### Effect 1462
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内每回合使用技能附加{1}点固定伤害,附加时若自身不处于能力提升状态则固定伤害翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 170: Effects 1463-1467
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1463
|
||||
- `argsNum`: `0`
|
||||
- `info`: `先出手时本回合自身下次死亡时恢复全部体力并解除自身的异常状态和能力下降状态`
|
||||
|
||||
### Effect 1464
|
||||
- `argsNum`: `3`
|
||||
- `info`: `当回合攻击击败对手时,若对手处于{0}状态则{1}%的概率令对手下1只出场精灵进入{2}状态`
|
||||
- `param`: `1,0,0|1,2,2`
|
||||
|
||||
### Effect 1465
|
||||
- `argsNum`: `1`
|
||||
- `info`: `先出手时50%的概率打出致命一击,未触发则令对手全属性-{0}`
|
||||
|
||||
### Effect 1466
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内若对手攻击技能未命中则{1}%令对手{2}`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
### Effect 1467
|
||||
- `argsNum`: `2`
|
||||
- `info`: `随机使自身{0}项能力值+{1},先出手时提升效果翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 171: Effects 1468-1472
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1468
|
||||
- `argsNum`: `2`
|
||||
- `info`: `消除双方能力提升状态、能力下降状态和护盾效果,消除任意一项成功则附加{0}点固定伤害且令对手随机{1}个技能PP值归零`
|
||||
|
||||
### Effect 1469
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内自身造成的固定伤害、百分比伤害提升{1}%`
|
||||
|
||||
### Effect 1470
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身天赋值越高则技能威力越大`
|
||||
|
||||
### Effect 1471
|
||||
- `argsNum`: `3`
|
||||
- `info`: `消除对手能力提升状态,消除成功则{0}%令对手{1},未触发则恢复自身最大体力的1/{2}`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1472
|
||||
- `argsNum`: `1`
|
||||
- `info`: `自身处于能力提升状态时造成的伤害提升{0}%,先出手时效果翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 172: Effects 1473-1477
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1473
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除对手所有护盾效果,消除成功则令对手随机{0}个技能PP值归零`
|
||||
|
||||
### Effect 1474
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内若自身能力提升状态被消除,每消除1项则附加{1}点固定伤害`
|
||||
|
||||
### Effect 1475
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内自身受到的固定伤害和百分比伤害减少{1}%`
|
||||
|
||||
### Effect 1476
|
||||
- `argsNum`: `2`
|
||||
- `info`: `使对手全属性-{0},自身体力低于1/{1}时弱化效果翻倍`
|
||||
|
||||
### Effect 1477
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除敌我双方能力提升状态,消除任意一方成功则吸取对手最大体力的1/{0}`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 173: Effects 1478-1482
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1478
|
||||
- `argsNum`: `1`
|
||||
- `info`: `下{0}回合令对手无法主动切换精灵`
|
||||
|
||||
### Effect 1479
|
||||
- `argsNum`: `1`
|
||||
- `info`: `牺牲自己,消除对手能力提升状态、回合类效果、护盾效果、护罩效果、神耀能量、自然祝福,同时令对手{0}回合内属性技能无效`
|
||||
|
||||
### Effect 1480
|
||||
- `argsNum`: `3`
|
||||
- `info`: `附加{0}点固定伤害,每次使用额外附加{1}点,最高{2}点`
|
||||
|
||||
### Effect 1481
|
||||
- `argsNum`: `1`
|
||||
- `info`: `{0}%的概率造成伤害翻倍,对手处于能力下降状态时概率翻倍`
|
||||
|
||||
### Effect 1482
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%令对手{1},未触发则吸取对手能力提升状态`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 174: Effects 1483-1487
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1483
|
||||
- `argsNum`: `1`
|
||||
- `info`: `本回合打出致命一击则令对手{0}回合属性技能无效`
|
||||
|
||||
### Effect 1484
|
||||
- `argsNum`: `0`
|
||||
- `info`: `体力低于最大体力的1/2时先制+1`
|
||||
|
||||
### Effect 1485
|
||||
- `argsNum`: `4`
|
||||
- `info`: `对手处于能力提升状态则命中后{0}%令对手{1},未触发则{2}%令对手{3}`
|
||||
- `param`: `1,1,1|1,3,3`
|
||||
|
||||
### Effect 1486
|
||||
- `argsNum`: `1`
|
||||
- `info`: `{0}%的概率造成伤害翻倍,自身处于护盾状态时概率翻倍`
|
||||
|
||||
### Effect 1487
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身处于护盾状态下先制+1`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,38 +0,0 @@
|
||||
# Task 175: Effects 1488-1492
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1488
|
||||
- `argsNum`: `2`
|
||||
- `info`: `遇到天敌时{0}%令对手{1}`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1489
|
||||
- `argsNum`: `4`
|
||||
- `info`: `若对手为自身天敌则{0}%令对手{1},未触发则令自身{2}回合内{3}%闪避对手攻击`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1490
|
||||
- `argsNum`: `3`
|
||||
- `info`: `若对手为自身天敌则{0}%令对手{1},未触发则对手{2}回合内属性技能命中效果失效`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1491
|
||||
- `argsNum`: `0`
|
||||
- `info`: `遇到天敌时先制+1`
|
||||
|
||||
### Effect 1492
|
||||
- `argsNum`: `1`
|
||||
- `info`: `对手处于能力下降状态时随机附加{0}种异常状态`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 176: Effects 1493-1497
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1493
|
||||
- `argsNum`: `0`
|
||||
- `info`: `消耗自身所有体力,令自身下只登场精灵3回合内免疫异常状态,若自身下只登场精灵为月照星魂则转变为5回合`
|
||||
|
||||
### Effect 1494
|
||||
- `argsNum`: `2`
|
||||
- `info`: `自身满体力时{0}%令对手随机{1}个技能PP值归零`
|
||||
|
||||
### Effect 1495
|
||||
- `argsNum`: `2`
|
||||
- `info`: `反转对手能力提升状态,反转成功则{0}%令对手随机{1}个技能PP值归零`
|
||||
|
||||
### Effect 1496
|
||||
- `argsNum`: `3`
|
||||
- `info`: `消耗自身全部体力,使自身下只出场精灵获得{0}点的护盾且{1}回合内每回合结束时恢复{2}点体力`
|
||||
|
||||
### Effect 1497
|
||||
- `argsNum`: `0`
|
||||
- `info`: `恢复自身n点体力(n=双方当前护盾值总和)`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 180: Effects 1513-1517
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1513
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内{1}%令对手{2},未触发则削减对手{3}点体力上限`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
### Effect 1514
|
||||
- `argsNum`: `0`
|
||||
- `info`: `消耗自身所有体力,令自身下只登场精灵获得2次八荒之力效果,若自身下只登场精灵为混元天尊则转变为3次`
|
||||
|
||||
### Effect 1515
|
||||
- `argsNum`: `5`
|
||||
- `info`: `对手体力上限高于{0}则造成的攻击伤害提升{1}%,对手体力上限低于{2}则附加自身{3}值{4}%的百分比伤害`
|
||||
- `param`: `2,3,3`
|
||||
|
||||
### Effect 1516
|
||||
- `argsNum`: `0`
|
||||
- `info`: `汇聚无尽的怨气,使用后下3回合自身获得八荒之力效果`
|
||||
|
||||
### Effect 1517
|
||||
- `argsNum`: `1`
|
||||
- `info`: `当回合击败对手则削减对手下只登场精灵{0}点体力上限`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 181: Effects 1518-1522
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1518
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消耗自身所有体力,削减对手本场战斗中{0}点体力上限`
|
||||
|
||||
### Effect 1519
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}%令对手随机{1}项技能PP值归零,若对手处于能力下降状态则{2}%使对手随机{3}项技能PP值归零`
|
||||
|
||||
### Effect 1520
|
||||
- `argsNum`: `2`
|
||||
- `info`: `若对手为雄性精灵则附加自身最大体力1/{0}的百分比伤害且下回合攻击技能先制+{1}`
|
||||
|
||||
### Effect 1521
|
||||
- `argsNum`: `2`
|
||||
- `info`: `若对手雌性精灵则恢复自身最大体力的1/{0}且下回合属性技能先制+{1}`
|
||||
|
||||
### Effect 1522
|
||||
- `argsNum`: `2`
|
||||
- `info`: `附加{0}点固定伤害,若当回合造成的伤害高于{1}则固伤效果翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 182: Effects 1523-1527
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1523
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除对手回合类效果,消除成功则吸取对手{0}点体力`
|
||||
|
||||
### Effect 1524
|
||||
- `argsNum`: `0`
|
||||
- `info`: `将自身能力下降状态反馈给对手,反馈成功则恢复自身所有体力,反馈失败则解除自身能力下降状态`
|
||||
|
||||
### Effect 1525
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%的概率造成的攻击伤害提升{1}%,若出手时自身为满体力则概率翻倍`
|
||||
|
||||
### Effect 1526
|
||||
- `argsNum`: `2`
|
||||
- `info`: `随机附加{0}-{1}点固定伤害,技能无效时减少对手所有技能PP1点`
|
||||
|
||||
### Effect 1527
|
||||
- `argsNum`: `0`
|
||||
- `info`: `后出手则必定打出致命一击`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 183: Effects 1528-1532
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1528
|
||||
- `argsNum`: `1`
|
||||
- `info`: `附加{0}点真实伤害`
|
||||
|
||||
### Effect 1529
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除对手能力提升状态,消除成功则附加{0}点真实伤害`
|
||||
|
||||
### Effect 1530
|
||||
- `argsNum`: `1`
|
||||
- `info`: `遇到天敌时附加{0}点真实伤害`
|
||||
|
||||
### Effect 1531
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内每回合使用技能附加{1}点固定伤害,若对手未受到固定伤害则额外附加等量的真实伤害`
|
||||
|
||||
### Effect 1532
|
||||
- `argsNum`: `0`
|
||||
- `info`: `对手不存在神印则攻击必定打出致命一击,对手每存在1层神印则100%使对手随机1项技能PP值归零`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 184: Effects 1533-1537
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1533
|
||||
- `argsNum`: `1`
|
||||
- `info`: `造成的攻击伤害低于{0}时附加大量真实伤害`
|
||||
|
||||
### Effect 1534
|
||||
- `argsNum`: `0`
|
||||
- `info`: `100%令对手随机1个被放逐的精灵所有技能PP值-1,若对手不存在被放逐的精灵则令对手当前精灵所有技能PP值-1`
|
||||
|
||||
### Effect 1535
|
||||
- `argsNum`: `0`
|
||||
- `info`: `对手存在神印则当回合自身先制+1`
|
||||
|
||||
### Effect 1536
|
||||
- `argsNum`: `1`
|
||||
- `info`: `全属性+{0},对手存在神印时强化效果翻倍`
|
||||
|
||||
### Effect 1537
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消耗自身所有护罩并造成等量固定伤害,若当前护罩值低于{0}则造成的固定伤害翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 185: Effects 1538-1542
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1538
|
||||
- `argsNum`: `0`
|
||||
- `info`: `将对手攻击和特攻中最高的能力值作为自己的能力值进行攻击`
|
||||
|
||||
### Effect 1539
|
||||
- `argsNum`: `3`
|
||||
- `info`: `造成的伤害高于{0}则{1}%令对手{2},未触发则消除对手回合类效果`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
### Effect 1540
|
||||
- `argsNum`: `2`
|
||||
- `info`: `自身体力高于最大体力的1/{0}时吸取对手{1}点体力`
|
||||
|
||||
### Effect 1541
|
||||
- `argsNum`: `1`
|
||||
- `info`: `使自身下{0}回合获得孤雷风暴效果`
|
||||
|
||||
### Effect 1542
|
||||
- `argsNum`: `2`
|
||||
- `info`: `吸取对手能力提升状态,吸取成功则令对手{0},若对手不处于能力提升状态则令对手全属性-{1}`
|
||||
- `param`: `1,0,0`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 186: Effects 1543-1547
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1543
|
||||
- `argsNum`: `0`
|
||||
- `info`: `当回合击败对手则不消耗蓄力层数`
|
||||
|
||||
### Effect 1544
|
||||
- `argsNum`: `0`
|
||||
- `info`: `对手存在护盾时先制+1`
|
||||
|
||||
### Effect 1545
|
||||
- `argsNum`: `0`
|
||||
- `info`: `消除对手所有护盾效果,消除成功则为自身附加等量的护盾值`
|
||||
|
||||
### Effect 1546
|
||||
- `argsNum`: `3`
|
||||
- `info`: `1回合做{0}-{1}次攻击,自身每存在1层蓄力则连击上限次数额外增加{2}次`
|
||||
|
||||
### Effect 1547
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身每存在3层蓄力先制额外+1`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 187: Effects 1548-1552
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1548
|
||||
- `argsNum`: `1`
|
||||
- `info`: `当回合未击败对手则使自身下回合受到的伤害降低{0}%`
|
||||
|
||||
### Effect 1549
|
||||
- `argsNum`: `1`
|
||||
- `info`: `后出手时下回合攻击技能先制+{0}`
|
||||
|
||||
### Effect 1550
|
||||
- `argsNum`: `0`
|
||||
- `info`: `减少自身最大体力的1/2(最多减少至1)并附加减少量2/3的真实伤害,自身的蓄力层数≥8层时不减少自身体力`
|
||||
|
||||
### Effect 1551
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%消耗自身全部体力,{1}%消耗敌方全部体力,若自身触发效果则消除敌方所有PP值,若敌方触发效果则消除自身所有PP值,均未触发则恢复自身全部体力`
|
||||
|
||||
### Effect 1552
|
||||
- `argsNum`: `0`
|
||||
- `info`: `集结天幕四龙之神力,使自身下2回合攻击必定先手、必定命中、必定致命`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 191: Effects 1568-1572
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1568
|
||||
- `argsNum`: `0`
|
||||
- `info`: `无视对手护盾效果`
|
||||
|
||||
### Effect 1569
|
||||
- `argsNum`: `5`
|
||||
- `info`: `{0}%令对手{1},若触发则自身下{2}次攻击技能造成的伤害提升100%,若未触发则令自身{3}且免疫下{4}次受到的攻击`
|
||||
- `param`: `1,1,1|1,3,3`
|
||||
|
||||
### Effect 1570
|
||||
- `argsNum`: `3`
|
||||
- `info`: `出手时若自身体力高于对手则{0}%令对手{1},未触发则附加对手最大体力1/{2}的百分比伤害`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1571
|
||||
- `argsNum`: `0`
|
||||
- `info`: `使自身随机获得圣念状态或邪念状态,若已拥有则切换为另外一种状态`
|
||||
|
||||
### Effect 1572
|
||||
- `argsNum`: `0`
|
||||
- `info`: `3回合内每回合80%闪避对手攻击,自身为圣念状态则回合数延长1回合,自身为邪念状态则闪避率提升至100%`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 192: Effects 1573-1577
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1573
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身为圣念状态时下2回合受到的攻击伤害减少50%,自身为邪念状态时下2回合造成的攻击伤害提升50%`
|
||||
|
||||
### Effect 1574
|
||||
- `argsNum`: `3`
|
||||
- `info`: `附加自身攻击值与速度值总和{0}%的百分比伤害,每次使用增加{1}%,最高{2}%`
|
||||
|
||||
### Effect 1575
|
||||
- `argsNum`: `2`
|
||||
- `info`: `下{0}回合后出手则对手{1}`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1576
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消耗自身全部体力,使对手{0}回合内无法通过自身技能恢复体力`
|
||||
|
||||
### Effect 1577
|
||||
- `argsNum`: `5`
|
||||
- `info`: `{0}回合做{1}-{2}次攻击,当前技能PP值小于{3}时连击上限为{4}`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 193: Effects 1578-1582
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1578
|
||||
- `argsNum`: `2`
|
||||
- `info`: `出手时自身护盾值高于{0}则自身下回合攻击技能先制+{1}`
|
||||
|
||||
### Effect 1579
|
||||
- `argsNum`: `2`
|
||||
- `info`: `出手时自身护盾值低于{0}则下回合属性技能先制+{1}`
|
||||
|
||||
### Effect 1580
|
||||
- `argsNum`: `2`
|
||||
- `info`: `出手时对手体力高于最大体力的1/{0}则造成伤害的{1}%恢复自身体力`
|
||||
|
||||
### Effect 1581
|
||||
- `argsNum`: `2`
|
||||
- `info`: `出手时对手体力低于最大体力的1/{0}则造成的伤害提升{1}%`
|
||||
|
||||
### Effect 1582
|
||||
- `argsNum`: `5`
|
||||
- `info`: `{0}%令对手{1},{2}%令自身{3},均未触发则为自身附加{4}点护盾`
|
||||
- `param`: `1,1,1|1,3,3`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 194: Effects 1583-1587
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1583
|
||||
- `argsNum`: `1`
|
||||
- `info`: `后出手时附加当前战斗回合数×{0}的固定伤害`
|
||||
|
||||
### Effect 1584
|
||||
- `argsNum`: `1`
|
||||
- `info`: `出手时若自身未满体力则下回合自身受到的攻击伤害减少{0}%`
|
||||
|
||||
### Effect 1585
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内若对手使用攻击技能则自身下{1}回合必定暴击`
|
||||
|
||||
### Effect 1586
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}%令对手{1},若对手已处于{2}状态则将此状态刷新至{3}回合`
|
||||
- `param`: `1,1,1|1,2,2`
|
||||
|
||||
### Effect 1587
|
||||
- `argsNum`: `1`
|
||||
- `info`: `当回合击败对手则下回合自身攻击先制+{0}`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 195: Effects 1588-1592
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1588
|
||||
- `argsNum`: `1`
|
||||
- `info`: `自身体力低于对手则为自身附加{0}点护盾且附加等量的固定伤害`
|
||||
|
||||
### Effect 1589
|
||||
- `argsNum`: `1`
|
||||
- `info`: `若对手处于异常状态则令对手随机{0}项技能PP值归零`
|
||||
|
||||
### Effect 1590
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身体力低于对手时100%的概率打出致命一击`
|
||||
|
||||
### Effect 1591
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%秒杀对手,未触发则自身全属性+{1}`
|
||||
|
||||
### Effect 1592
|
||||
- `argsNum`: `1`
|
||||
- `info`: `风之力量觉醒,使自身下{0}次攻击获得蚀骨之风效果`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 196: Effects 1593-1597
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1593
|
||||
- `argsNum`: `3`
|
||||
- `info`: `1回合做{0}-{1}次攻击,自身体力低于对手时连击上限为{2}`
|
||||
|
||||
### Effect 1594
|
||||
- `argsNum`: `5`
|
||||
- `info`: `{0}回合内受到攻击则{1}%使自身全属性+{2},未触发则{3}%使对手全属性-{4}`
|
||||
|
||||
### Effect 1595
|
||||
- `argsNum`: `0`
|
||||
- `info`: `若对手为咸咸粽则100%打出致命一击`
|
||||
|
||||
### Effect 1596
|
||||
- `argsNum`: `0`
|
||||
- `info`: `若对手为甜甜粽则100%打出致命一击`
|
||||
|
||||
### Effect 1597
|
||||
- `argsNum`: `0`
|
||||
- `info`: `命中后50%令对手睡眠,若对手为咸咸粽则概率翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 197: Effects 1598-1602
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1598
|
||||
- `argsNum`: `0`
|
||||
- `info`: `命中后50%令对手害怕,若对手为甜甜粽则概率翻倍`
|
||||
|
||||
### Effect 1599
|
||||
- `argsNum`: `5`
|
||||
- `info`: `{0}回合内受到攻击则{1}%令对手{2},未触发则{3}%令对手{4}`
|
||||
- `param`: `1,2,2|1,4,4`
|
||||
|
||||
### Effect 1600
|
||||
- `argsNum`: `6`
|
||||
- `info`: `命中后{0}%令对手{1},未触发则自身下{2}回合攻击必定致命一击,触发后{3}回合内对手主动切换精灵则登场精灵{4}%进入{5}状态`
|
||||
- `param`: `1,1,1|1,5,5`
|
||||
|
||||
### Effect 1601
|
||||
- `argsNum`: `1`
|
||||
- `info`: `命中后附加自身最大体力{0}%的百分比伤害,若打出的攻击伤害为奇数则额外恢复等量体力值`
|
||||
|
||||
### Effect 1602
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内每回合使用技能恢复自身最大体力的1/{1},恢复体力时若自身为满体力则恢复己方所有不在场精灵{2}点体力`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 198: Effects 1603-1608
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1603
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%降低对手所有PP值{1}点`
|
||||
|
||||
### Effect 1604
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%恢复自身所有PP值{1}点`
|
||||
|
||||
### Effect 1606
|
||||
- `argsNum`: `1`
|
||||
- `info`: `反转自身能力下降状态,反转成功则吸取对手最大体力的1/{0}`
|
||||
|
||||
### Effect 1607
|
||||
- `argsNum`: `1`
|
||||
- `info`: `当回合未击败对手则附加自身最大体力1/{0}的百分比伤害`
|
||||
|
||||
### Effect 1608
|
||||
- `argsNum`: `0`
|
||||
- `info`: `召唤自己的伙伴进行5-10次攻击,布布鸟发起时可额外令自身下回合所有技能先制+3`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 199: Effects 1609-1613
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1609
|
||||
- `argsNum`: `0`
|
||||
- `info`: `召唤自己的伙伴进行5-10次攻击,布布犬发起时可额外令自身下回合攻击造成的伤害翻倍`
|
||||
|
||||
### Effect 1610
|
||||
- `argsNum`: `0`
|
||||
- `info`: `召唤自己的伙伴进行5-10次攻击,布布熊发起时可额外令对手下回合属性技能失效`
|
||||
|
||||
### Effect 1611
|
||||
- `argsNum`: `0`
|
||||
- `info`: `攻击命中后5%的概率汲取泰坦源脉的力量,本次攻击造成5倍伤害且战斗结束后获得5000泰坦之灵(每日上限50000)`
|
||||
|
||||
### Effect 1612
|
||||
- `argsNum`: `5`
|
||||
- `info`: `{0}回合内受到的伤害低于{1}时{2}%令对手{3},未触发则附加{4}点固定伤害`
|
||||
- `param`: `1,3,3`
|
||||
|
||||
### Effect 1613
|
||||
- `argsNum`: `2`
|
||||
- `info`: `自身不处于能力提升状态则吸取对手{0}点体力,若先出手则额外吸取{1}点体力`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 200: Effects 1614-1619
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1614
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内对手使用攻击技能后{1}%令对手{2},未触发则令对手全属性-{3}`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
### Effect 1616
|
||||
- `argsNum`: `0`
|
||||
- `info`: `当回合使用的技能克制对手时获得本系属性加成`
|
||||
|
||||
### Effect 1617
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内受到攻击后{1}%使对手{2},未触发则自身全属性+{3}`
|
||||
- `param`: `1,2,2`
|
||||
|
||||
### Effect 1618
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内每回合结束时令对手随机{1}个技能PP值归零`
|
||||
|
||||
### Effect 1619
|
||||
- `argsNum`: `0`
|
||||
- `info`: `50%复制对手当回合释放的技能,未触发则恢复自身最大体力的1/2且令对手全属性-1`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 201: Effects 1620-1624
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1620
|
||||
- `argsNum`: `1`
|
||||
- `info`: `对手基础速度值高于{0}则下回合先制-1`
|
||||
|
||||
### Effect 1621
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}%令对手所有技能PP值-{1},自身满体力时效果翻倍`
|
||||
|
||||
### Effect 1622
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内每回合{1}%对手属性技能无效,未触发则下{2}次受到的攻击伤害减少{3}%`
|
||||
|
||||
### Effect 1623
|
||||
- `argsNum`: `3`
|
||||
- `info`: `若对手是{0}精灵则下{1}回合对手受到的伤害提高{2}%`
|
||||
- `param`: `5,0,0`
|
||||
|
||||
### Effect 1624
|
||||
- `argsNum`: `1`
|
||||
- `info`: `对手不处于异常状态时随机附加{0}种异常状态`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 202: Effects 1625-1629
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1625
|
||||
- `argsNum`: `3`
|
||||
- `info`: `造成的伤害高于{0}则{1}%令自身全属性+{2}`
|
||||
|
||||
### Effect 1626
|
||||
- `argsNum`: `1`
|
||||
- `info`: `后出手时将当回合护盾所承受的伤害值以百分比伤害的形式{0}%反弹给对手`
|
||||
|
||||
### Effect 1627
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合做{1}-{2}次攻击,若本回合攻击次数达到最大则必定秒杀对手`
|
||||
|
||||
### Effect 1628
|
||||
- `argsNum`: `2`
|
||||
- `info`: `每次使用该技能击败对手则恢复自身全部体力,同时重置该技能使用次数并使该技能攻击威力提升{0}点,未击败对手时令自身下回合攻击技能先制+{1}`
|
||||
|
||||
### Effect 1629
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}基础速度值{1}{2}则自身下回合先制+{3}`
|
||||
- `param`: `4,0,0|7,1,1`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,37 +0,0 @@
|
||||
# Task 203: Effects 1630-1634
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1630
|
||||
- `argsNum`: `2`
|
||||
- `info`: `若对手当回合使用的技能为攻击技能则自身必定先出手且命中后{0}%令对手{1}`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1631
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内每回合若自身未受到攻击伤害则回合结束后附加对手最大体力1/{1}的百分比伤害(自身体力为0时也可触发)`
|
||||
|
||||
### Effect 1632
|
||||
- `argsNum`: `3`
|
||||
- `info`: `吸取对手{0}点体力,若对手任意1项技能PP值小于{1}点则额外吸取{2}点体力`
|
||||
|
||||
### Effect 1633
|
||||
- `argsNum`: `2`
|
||||
- `info`: `使自身体力百分比与对手体力百分比对调,自身体力百分比高于对手时{0}%令对手{1}`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1634
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身体力低于250时必定先手`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 204: Effects 1635-1639
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1635
|
||||
- `argsNum`: `2`
|
||||
- `info`: `立刻恢复自身{0}点体力,{1}回合后恢复自身全部体力`
|
||||
|
||||
### Effect 1636
|
||||
- `argsNum`: `0`
|
||||
- `info`: `涵双1回合释放4-8张玫瑰卡牌进行攻击,每张卡牌额外附加50点固定伤害,自身体力低于最大体力的1/3时效果翻倍`
|
||||
|
||||
### Effect 1637
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内若对手使用属性技能,则使用属性技能后的下{1}回合属性技能命中效果失效`
|
||||
|
||||
### Effect 1638
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内若自身未受到攻击伤害则令对手全属性-{1}`
|
||||
|
||||
### Effect 1639
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身处于能力提升状态时100%打出致命一击`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 205: Effects 1640-1644
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1640
|
||||
- `argsNum`: `0`
|
||||
- `info`: `出手时若自身满体力则100%打出致命一击`
|
||||
|
||||
### Effect 1641
|
||||
- `argsNum`: `2`
|
||||
- `info`: `自身处于能力提升状态时造成伤害的{0}%恢复自身体力值,当前体力低于最大体力的1/{1}时附加等量百分比伤害`
|
||||
|
||||
### Effect 1642
|
||||
- `argsNum`: `2`
|
||||
- `info`: `消除对手能力提升状态,消除成功则{0}%随机为对手任意技能散布{1}枚致命印记`
|
||||
|
||||
### Effect 1643
|
||||
- `argsNum`: `1`
|
||||
- `info`: `对手每存在1层致命裂痕则附加{0}点真实伤害`
|
||||
|
||||
### Effect 1644
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内对手使用攻击技能则随机进入{1}种异常状态,未触发则随机为对手任意技能散布{2}枚致命印记`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 206: Effects 1645-1649
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1645
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内对手使用属性技能则自身下{1}次受到的攻击伤害减少{2}%`
|
||||
|
||||
### Effect 1646
|
||||
- `argsNum`: `1`
|
||||
- `info`: `全属性+{0},对手存在致命裂痕时强化效果翻倍`
|
||||
|
||||
### Effect 1647
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}回合内每回合使用技能吸取对手最大体力的1/{1},吸取体力时若自身体力低于最大体力的1/{2}则吸取效果翻倍,对手免疫百分比伤害时额外附加{3}点真实伤害`
|
||||
|
||||
### Effect 1648
|
||||
- `argsNum`: `1`
|
||||
- `info`: `附加自身最大体力{0}%的百分比伤害并恢复等量体力值,对手存在致命裂痕时转变为等量的真实伤害`
|
||||
|
||||
### Effect 1649
|
||||
- `argsNum`: `4`
|
||||
- `info`: `{0}%概率造成的攻击伤害为{1}倍,对手每存在1层致命裂痕则概率提升{2}%,未触发则{3}回合内令对手使用的属性技能无效`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 207: Effects 1650-1654
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1650
|
||||
- `argsNum`: `4`
|
||||
- `info`: `命中后{0}%随机为对手任意技能散布{1}枚致命印记,若对手当前精灵致命裂痕≥{2}层则额外散布{3}枚致命印记`
|
||||
|
||||
### Effect 1651
|
||||
- `argsNum`: `2`
|
||||
- `info`: `当回合击败对手则令对手下{0}次触发致命印记真实伤害效果转变为1/{1}`
|
||||
|
||||
### Effect 1652
|
||||
- `argsNum`: `2`
|
||||
- `info`: `释放技能时自身每损失{0}%的体力值则此技能威力提升{1}点`
|
||||
|
||||
### Effect 1653
|
||||
- `argsNum`: `2`
|
||||
- `info`: `释放技能时对手每残留{0}%的体力值则此技能附加{1}点固定伤害`
|
||||
|
||||
### Effect 1654
|
||||
- `argsNum`: `1`
|
||||
- `info`: `当回合击败对手则令对手下只登场精灵首次使用的技能所附加的效果失效`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 208: Effects 1655-1659
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1655
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内每回合结束后{1}恢复自身所有技能{2}点PP值`
|
||||
- `param`: `25,1,1`
|
||||
|
||||
### Effect 1656
|
||||
- `argsNum`: `0`
|
||||
- `info`: `100%复制对手当回合释放的技能,若对手当回合使用的技能为攻击技能则令对手随机1个技能PP值归零,若对手当回合使用的技能为属性技能则令对手下回合先制-2`
|
||||
|
||||
### Effect 1657
|
||||
- `argsNum`: `3`
|
||||
- `info`: `己方每有一只精灵死亡则附加{0}点固定伤害,对手体力高于最大体力的1/{1}时转变为{2}点`
|
||||
|
||||
### Effect 1658
|
||||
- `argsNum`: `0`
|
||||
- `info`: `3回合内每回合80%闪避对手攻击,未触发时自身处于圣念状态则使对手随机1项技能PP值归零,自身处于邪念状态则使对手失明`
|
||||
|
||||
### Effect 1659
|
||||
- `argsNum`: `4`
|
||||
- `info`: `随机附加给对手{0}-{1}点固定伤害,若打出致命一击则效果转变为吸取对手{2}-{3}点体力`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 210: Effects 1665-1669
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1665
|
||||
- `argsNum`: `2`
|
||||
- `info`: `全属性+{0},自身背包内每阵亡1只精灵则{1}%的概率强化效果翻倍`
|
||||
|
||||
### Effect 1666
|
||||
- `argsNum`: `3`
|
||||
- `info`: `1回合做{0}-{1}次攻击,自身处于领域效果下连击上限为{2}`
|
||||
|
||||
### Effect 1667
|
||||
- `argsNum`: `0`
|
||||
- `info`: `开启时空漩涡,使用后必定令对手束缚且下2回合令对手无法主动切换精灵`
|
||||
|
||||
### Effect 1668
|
||||
- `argsNum`: `1`
|
||||
- `info`: `附加对手当前已损失技能PP值总和×{0}的固定伤害,若对手未受到固定伤害则额外附加等量的真实伤害`
|
||||
|
||||
### Effect 1669
|
||||
- `argsNum`: `2`
|
||||
- `info`: `全属性+{0},自身背包内每存活1只精灵则{1}%的概率强化效果翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 233: Effects 1780-1784
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1780
|
||||
- `argsNum`: `5`
|
||||
- `info`: `消耗自身全部体力,减少对手当前体力的1/{0},同时使己方下1只出场精灵获得点数等同于对手最大体力值1/{1}的护盾且下{2}次技能先制+{3},护盾最多可获得{4}点`
|
||||
|
||||
### Effect 1781
|
||||
- `argsNum`: `2`
|
||||
- `info`: `若自身拥有的护盾值高于{0}则令自身下{1}回合回合类效果无法被消除`
|
||||
|
||||
### Effect 1782
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身存在护盾则先制+2`
|
||||
|
||||
### Effect 1783
|
||||
- `argsNum`: `2`
|
||||
- `info`: `消除敌我双方回合类效果,消除任意一项成功则敌我双方同时进入{0}状态,若任意一方回合类效果无法被消除则令对手下{1}回合无法主动切换精灵`
|
||||
- `param`: `1,0,0`
|
||||
|
||||
### Effect 1784
|
||||
- `argsNum`: `1`
|
||||
- `info`: `{0}回合内对手使用技能时自身免疫对手下1次非致命一击造成的攻击伤害`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 234: Effects 1785-1789
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 1785
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}回合内每回合使用技能吸取对手最大体力的1/{1},吸取体力时若自身为满体力则恢复己方所有不在场精灵{2}点体力`
|
||||
|
||||
### Effect 1786
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内若自身回合类效果被消除则对手下{1}次使用的攻击技能附加效果失效`
|
||||
|
||||
### Effect 1787
|
||||
- `argsNum`: `1`
|
||||
- `info`: `沙之力量觉醒,使自身下{0}次攻击获得黯天之尘效果`
|
||||
|
||||
### Effect 1788
|
||||
- `argsNum`: `3`
|
||||
- `info`: `{0}%令对手{1},未触发则附加自身最大体力{2}%的百分比伤害,若对手免疫百分比伤害则转变为真实伤害`
|
||||
- `param`: `1,1,1`
|
||||
|
||||
### Effect 1789
|
||||
- `argsNum`: `1`
|
||||
- `info`: `预留,{0}`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 295: Effects 2090-2094
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 2090
|
||||
- `argsNum`: `0`
|
||||
- `info`: `空元之诗·均:附加双方体力上限差值50%的次元·龙系伤害,自身体力上限高于对手时额外吸取对手第五技能剩余的PP值,自身体力上限低于对手时附加伤害翻倍`
|
||||
|
||||
### Effect 2091
|
||||
- `argsNum`: `1`
|
||||
- `info`: ``
|
||||
|
||||
### Effect 2092
|
||||
- `argsNum`: `2`
|
||||
- `info`: `全属性+{0}并将空妄诗章补充至与自身先制等级相等,自身没有空妄诗章时额外书写{1}篇,拥有时强化效果翻倍`
|
||||
|
||||
### Effect 2093
|
||||
- `argsNum`: `5`
|
||||
- `info`: `{0}回合内使用技能吸取对手最大体力的1/{1},自身体力低于1/{2}时效果翻倍,吸取后若对手体力未减少则{3}回合内对手{4}`
|
||||
- `param`: `23,4,4`
|
||||
|
||||
### Effect 2094
|
||||
- `argsNum`: `3`
|
||||
- `info`: `自身每有1篇空妄诗章造成的攻击伤害提升{0}%,若空妄诗章的篇数高于{1}则每有1篇额外附加{2}点真实伤害`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 301: Effects 2120-2124
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 2120
|
||||
- `argsNum`: `2`
|
||||
- `info`: `自身为对手天敌时下{0}次受到攻击的伤害减少{1}%`
|
||||
|
||||
### Effect 2121
|
||||
- `argsNum`: `1`
|
||||
- `info`: `未击败对手则己方在场精灵吸取对手最大体力的{0}%,吸取后若对手体力未减少则延续至下回合`
|
||||
|
||||
### Effect 2122
|
||||
- `argsNum`: `2`
|
||||
- `info`: `获得{0}点护罩,护罩消失时自身所有技能PP值+{1}`
|
||||
|
||||
### Effect 2123
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除对手回合类效果,消除成功令对手感染且自身每有1点灵茉之芯额外附加中毒、睡眠、寄生中的前1种异常状态,未触发则吸取对手最大体力的1/{0}`
|
||||
|
||||
### Effect 2124
|
||||
- `argsNum`: `1`
|
||||
- `info`: `{0}%的概率造成伤害翻倍,自身处于能力提升状态或对手处于能力下降状态时概率翻倍`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,36 +0,0 @@
|
||||
# Task 317: Effects 2200-2204
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 2200
|
||||
- `argsNum`: `2`
|
||||
- `info`: `令双方{0},任意一方未触发则额外令对手{1}`
|
||||
- `param`: `1,0,0|1,1,1`
|
||||
|
||||
### Effect 2201
|
||||
- `argsNum`: `1`
|
||||
- `info`: `自身携带技能中含有的光系多于暗影系时{0}%令对手疲惫,暗影系多于光系时{0}%令对手害怕`
|
||||
|
||||
### Effect 2202
|
||||
- `argsNum`: `1`
|
||||
- `info`: `自身携带技能中含有的光系不少于暗影系时必定打出致命一击,暗影系不少于光系时吸取对手最大体力的1/{0}`
|
||||
|
||||
### Effect 2203
|
||||
- `argsNum`: `0`
|
||||
- `info`: `技能无效时,免疫下次对手的攻击`
|
||||
|
||||
### Effect 2204
|
||||
- `argsNum`: `2`
|
||||
- `info`: `技能威力减少{0}%,对手处于异常状态时改为提升{1}%`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 318: Effects 2205-2209
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 2205
|
||||
- `argsNum`: `0`
|
||||
- `info`: `自身处于异常状态时,克制倍数取自身对对手、对手对自身克制倍数中的最大值`
|
||||
|
||||
### Effect 2206
|
||||
- `argsNum`: `1`
|
||||
- `info`: `{0}回合内自身能力提升状态被消除则解除自身所处的异常状态`
|
||||
|
||||
### Effect 2207
|
||||
- `argsNum`: `1`
|
||||
- `info`: `使自身所处的异常状态剩余回合数-{0}`
|
||||
|
||||
### Effect 2208
|
||||
- `argsNum`: `1`
|
||||
- `info`: `自身星盘每转动1刻技能提升{0}点威力,回返期间提升效果翻倍`
|
||||
|
||||
### Effect 2209
|
||||
- `argsNum`: `1`
|
||||
- `info`: `自身星盘每转动1刻吸取对手{0}点体力,回返期间改为汲取体力`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,35 +0,0 @@
|
||||
# Task 322: Effects 2225-2229
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐以下 5 个(或最后一组不足 5 个)当前判定未实现的 skill effect。
|
||||
- 实现位置优先放在 `logic/service/fight/effect/`。
|
||||
- 如 effect 需要展示说明,同步更新 `logic/service/fight/effect/effect_info_map.go`。
|
||||
- 完成后至少执行:`cd /workspace/logic && go test ./service/fight/effect`。
|
||||
|
||||
## Effect 列表
|
||||
|
||||
### Effect 2225
|
||||
- `argsNum`: `1`
|
||||
- `info`: `减少对手体力上限的1/{0}`
|
||||
|
||||
### Effect 2226
|
||||
- `argsNum`: `1`
|
||||
- `info`: `解除双方所处的异常状态,解除成功则免疫下{0}次对手的攻击`
|
||||
|
||||
### Effect 2227
|
||||
- `argsNum`: `1`
|
||||
- `info`: `消除对手能力提升状态,消除成功对手下{0}回合无法主动切换精灵`
|
||||
|
||||
### Effect 2228
|
||||
- `argsNum`: `2`
|
||||
- `info`: `{0}回合内对手造成的固定伤害和百分比伤害减少{1}%`
|
||||
|
||||
### Effect 2229
|
||||
- `argsNum`: `1`
|
||||
- `info`: `{0}回合内使用技能则全属性+1,自身回合类效果、能力提升效果被消除、吸取时触发自身的登场效果`
|
||||
|
||||
## 备注
|
||||
|
||||
- 该清单按当前仓库静态注册结果生成;如果某个 effect 实际通过其他模块或运行时路径实现,需要先复核后再落代码。
|
||||
- 对 `201`、`445` 这类占位 effect,优先补核心逻辑或补充明确的不可实现说明。
|
||||
@@ -1,218 +0,0 @@
|
||||
---
|
||||
name: fight-effect-impl
|
||||
description: Implement or repair Go fight effects in the Blazing battle system. Use when working in logic/service/fight/effect or nearby boss hooks, especially for missing effect tasks, effect registration, hook selection, delayed/round effects, status application, effect_info_map updates, and package-level validation.
|
||||
---
|
||||
|
||||
# Fight Effect Impl
|
||||
|
||||
Implement effect work in the existing battle framework instead of inventing a parallel pattern.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Read the task source first.
|
||||
If the request comes from `docs/effect-unimplemented-tasks/`, open the task file and extract effect IDs, arg counts, and description text.
|
||||
|
||||
2. Confirm whether each effect is actually missing.
|
||||
Search for both type names and registrations. Do not rely only on direct `InitEffect(...)` grep results.
|
||||
Also inspect shared registration files such as:
|
||||
- `logic/service/fight/effect/sterStatusEffects.go`
|
||||
- `logic/service/fight/effect/effect_power_doblue.go`
|
||||
- `logic/service/fight/effect/EffectAttackMiss.go`
|
||||
- `logic/service/fight/effect/EffectPhysicalAttackAddStatus.go`
|
||||
- `logic/service/fight/effect/EffectDefeatTrigger.go`
|
||||
- `logic/service/fight/effect/effect_attr.go`
|
||||
|
||||
3. Reuse the nearest local pattern.
|
||||
Open effects with similar timing or semantics before writing code. Prefer matching existing hooks, helper bases, registration style, and comments over building a generic abstraction.
|
||||
|
||||
4. Choose the hook from battle flow, not from description text alone.
|
||||
Read `logic/service/fight/input/interface.go`, `logic/service/fight/fightc.go`, and `logic/service/fight/loop.go` when timing is unclear.
|
||||
|
||||
## Effect Hooks
|
||||
|
||||
Use this section when effect timing is unclear.
|
||||
|
||||
### Core call order
|
||||
|
||||
The main references are:
|
||||
- `logic/service/fight/input/interface.go`
|
||||
- `logic/service/fight/fightc.go`
|
||||
- `logic/service/fight/loop.go`
|
||||
|
||||
Typical attack flow inside `processSkillAttack` and `enterturn` is:
|
||||
1. defender `SkillHit_ex()`
|
||||
2. attacker `SkillHit()`
|
||||
3. attacker `CalculatePre()`
|
||||
4. attacker `OnSkill()`
|
||||
5. defender `Damage(...)` settles red/fixed/true damage
|
||||
6. defender `Skill_Use_ex()`
|
||||
7. attacker `Skill_Use()`
|
||||
8. defender `Action_end_ex()`
|
||||
9. attacker `Action_end()`
|
||||
10. both sides `TurnEnd()` at round end
|
||||
11. all live effects `OnBattleEnd()` at fight end
|
||||
|
||||
### Hook selection cheatsheet
|
||||
|
||||
- `SkillHit_ex()`
|
||||
Use for defender-side pre-hit interception, miss forcing, and hit-rate disruption.
|
||||
|
||||
- `SkillHit()`
|
||||
Use for attacker-side power, crit, or skill-property changes before damage is computed.
|
||||
|
||||
- `CalculatePre()`
|
||||
Use for temporary state rewrites that must exist during power calculation and then be restored.
|
||||
|
||||
- `OnSkill()`
|
||||
Use for on-hit side effects, extra fixed damage setup, healing, status attach, or delayed-effect spawning.
|
||||
|
||||
- `ActionStartEx()`
|
||||
Use for defender-side pre-action gates.
|
||||
|
||||
- `ActionStart()`
|
||||
Use for attacker-side action gating, forced no-action behavior, and same-turn priority-sensitive logic.
|
||||
|
||||
- `Skill_Use_ex()`
|
||||
Use for defender-side after-being-targeted behavior.
|
||||
|
||||
- `Skill_Use()`
|
||||
Use for attacker-side after-using-skill behavior.
|
||||
|
||||
- `ComparePre()`
|
||||
Use for priority changes before turn order is finalized.
|
||||
|
||||
- `TurnStart()`
|
||||
Use for per-round setup or replacing the current round's selected action before execution.
|
||||
|
||||
- `TurnEnd()`
|
||||
Use for countdown or expiry. The default node decrements positive durations and clears zero-duration effects.
|
||||
|
||||
- `OnBattleEnd()`
|
||||
Use only when the effect truly settles at battle end. Confirm any reward path can be persisted from this hook.
|
||||
|
||||
### Repo-specific cautions
|
||||
|
||||
- `EffectCache` matters.
|
||||
Parsed skill effects are stored in `EffectCache` before execution. If a first-turn charge effect must suppress the rest of the skill's side effects, explicitly disable sibling cached effects for that turn.
|
||||
|
||||
- `addSubEffect(...)` is lightweight.
|
||||
Read `logic/service/fight/effect/sub_effect_helper.go` before assuming helper behavior. The current helper forwards IDs and args, but does not automatically apply the `duration` argument to the sub-effect instance.
|
||||
|
||||
- Team-wide healing is limited by current model.
|
||||
There is no generic battle-target abstraction for friendly bench targets. If the effect heals all owned pets, iterate `AllPet` and mutate non-active pets carefully.
|
||||
|
||||
- Static task scans can be false positives.
|
||||
Task documents may flag effects as missing even when they already exist in grouped files or shared registration files. Verify before editing.
|
||||
|
||||
## Implementation Rules
|
||||
|
||||
- Prefer existing base structs in `logic/service/fight/effect/sub_effect_helper.go` when they already match duration behavior.
|
||||
- Verify helper semantics before using them. In this repo, some helpers are thinner than their names suggest.
|
||||
- For status effects, create them through `InitEffect(input.EffectType.Status, ...)` and add them through `AddEffect(...)` on the target input.
|
||||
- For healing, use `Input.Heal(...)` for the active battler and mutate non-active owned pets only when the current model already stores them in `AllPet`.
|
||||
- For battle-end rewards or delayed settlement, confirm the hook is actually executed in `loop.go` before coding against it.
|
||||
- Keep comments short and effect-focused.
|
||||
|
||||
## Batch Work Rules
|
||||
|
||||
When continuing `docs/effect-unimplemented-tasks/` in batches:
|
||||
|
||||
- Split work by grouped file and assign disjoint write ranges.
|
||||
- Avoid touching `logic/service/fight/effect/effect_info_map.go` during parallel effect implementation unless the user explicitly asks for description-map updates in the same pass.
|
||||
- Treat task docs as a backlog, not ground truth. A task file may still exist even when some IDs in the slice were already implemented or partially repaired.
|
||||
- Prefer finishing the easiest grounded IDs in a partial slice instead of repeatedly rescanning the entire slice.
|
||||
- Keep a clear list of:
|
||||
- newly implemented IDs,
|
||||
- already-existing IDs,
|
||||
- still-partial IDs,
|
||||
- task docs safe to delete.
|
||||
|
||||
## Frequent Compile Pitfalls
|
||||
|
||||
Before considering a slice done, check these repo-specific issues:
|
||||
|
||||
- `input.InitEffect(...)` always needs all three args: effect type, numeric ID, effect instance.
|
||||
- If a new sub-effect type is added, also register the sub-effect explicitly in `init()`.
|
||||
- `SkillEntity.XML.Power` and `Priority` are `int`-based in current generated structs; avoid mixing with `int8`, `int32`, or `int64` arithmetic.
|
||||
- Skill PP on runtime battle state is commonly `uint32`; cast carefully when subtracting or restoring PP.
|
||||
- `model.SkillInfo` does not expose fields like `MaxPP` or `Category`; look them up through `xmlres.SkillMap[int(skill.ID)]`.
|
||||
- `xmlres.Move` does not expose every runtime field. Use runtime state such as `SkillEntity.AttackTime` when the XML struct lacks a field.
|
||||
- `input.Input` does not always expose nested objects assumed by task text, such as `Opp.SkillEntity`; verify available runtime fields before coding.
|
||||
- Decimal math must use `alpacadecimal` values, not raw integer literals.
|
||||
- Large grouped files can accidentally keep stale duplicate `init()` registration blocks after manual merges or batch patches. Check the file tail before closing out a slice.
|
||||
|
||||
## Partial Slice Strategy
|
||||
|
||||
When a grouped file is only partially grounded:
|
||||
|
||||
- Do not delete the task docs for that slice yet.
|
||||
- Keep the implemented IDs in place and make the remaining gaps explicit.
|
||||
- Prefer conservative, repo-shaped implementations over speculative full feature work for heavy system effects.
|
||||
- Good candidates to finish in partial slices are:
|
||||
- simple priority modifiers,
|
||||
- PP-based power changes,
|
||||
- round-based sub-effects using existing helper bases,
|
||||
- status immunity or status application patterns that already exist nearby.
|
||||
- Leave highly coupled systems partial if they appear to depend on larger mechanics not yet modeled in nearby code, such as custom resource tracks or complex transformation states.
|
||||
|
||||
## Task Doc Deletion
|
||||
|
||||
Delete a task file only when one of these is true:
|
||||
|
||||
- every effect ID in the task range is now implemented in repo code,
|
||||
- the IDs already existed and were verified as not missing,
|
||||
- or the user explicitly accepts documented non-implementation for reserved placeholders.
|
||||
|
||||
Do not delete the task doc when the slice is still mixed between implemented and partial IDs.
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Random power or conditional power effects
|
||||
Use `SkillHit()` when the effect changes `SkillEntity.XML.Power` before damage is calculated.
|
||||
Examples in repo: `139.go`, `effect_power_doblue.go`, `600_605.go`.
|
||||
|
||||
### Hit-time status or side effects
|
||||
Use `OnSkill()` when the effect should fire after hit/damage calculation setup and before final damage application is settled.
|
||||
For direct status application, initialize the status effect from the source input and add it to the opponent.
|
||||
|
||||
### Round or delayed effects
|
||||
For multi-turn logic, confirm whether the effect should:
|
||||
- modify this turn only,
|
||||
- start next turn,
|
||||
- trigger when attacked,
|
||||
- or replace the next selected skill.
|
||||
|
||||
For next-turn logic, inspect nearby effects such as `62`, `407`, `440`, `499`, `551`, `560`, and any adjacent ID patterns.
|
||||
|
||||
### Two-turn charge effects
|
||||
Preserve the repo's existing battle loop assumptions.
|
||||
A practical pattern is:
|
||||
- cache the release skill on the first turn,
|
||||
- suppress first-turn damage/effect output,
|
||||
- inject the cached skill into the next turn's selected action in `TurnStart`,
|
||||
- avoid double PP consumption in `HookPP`.
|
||||
|
||||
### Reward-on-battle-end effects
|
||||
Check `OnBattleEnd()` execution in `logic/service/fight/loop.go`.
|
||||
If a reward has a daily cap, prefer the shared counter utilities under `common/data/share/` over inventing new state.
|
||||
|
||||
## Validation
|
||||
|
||||
Run, at minimum:
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
|
||||
If the user explicitly says tests are not required for the current pass, downgrade validation to:
|
||||
- `gofmt -w` on every edited Go file,
|
||||
- fix current editor/compiler diagnostics for touched files,
|
||||
- and report that package tests/build were intentionally skipped by user request.
|
||||
|
||||
If the task came from `docs/effect-unimplemented-tasks/`, remove the completed task file when the user asked for it.
|
||||
|
||||
## Output
|
||||
|
||||
When finishing a task, report:
|
||||
- which effect IDs were truly implemented,
|
||||
- which IDs already existed and were left untouched,
|
||||
- validation commands actually run,
|
||||
- any remaining model limitations or behavior assumptions.
|
||||
400
docs/fight-group-implementation-checklist-2026-04-04.md
Normal file
400
docs/fight-group-implementation-checklist-2026-04-04.md
Normal file
@@ -0,0 +1,400 @@
|
||||
# 战斗系统对齐 `flash/group` 组队战斗实施清单(执行版)
|
||||
|
||||
日期:2026-04-04
|
||||
适用仓库:`E:\newcode\sun`
|
||||
参考客户端仓库:`E:\newcode\flash`
|
||||
|
||||
---
|
||||
|
||||
## 1. 结论与范围
|
||||
|
||||
### 1.1 结论
|
||||
|
||||
- `sun` 当前战斗系统具备多战位骨架(`ActorIndex/TargetIndex`、`Our/Opp []*input.Input`),但未完成组队战斗全链路。
|
||||
- `flash` 的 `group` 分支当前 HEAD 已回滚组队重构;组队实现主要存在于历史提交 `4c07fa07`。
|
||||
- 因此本次不是“直接搬代码”,而是“按协议与行为对齐实现”。
|
||||
|
||||
### 1.2 本清单目标
|
||||
|
||||
- 在不破坏现有 `1v1` 的前提下,落地组队战斗可运行版本(MVP)。
|
||||
- 对齐 `flash`/社区实现中的关键行为(开战、出招、切宠、道具、结算、战斗结束)。
|
||||
- 协议层采用“一个统一结构体 + phase 字段”方案,单打/双打共用同一序列化模型。
|
||||
- 保留旧 `24xx/25xx` 流程入口,通过服务端适配映射到统一结构体。
|
||||
|
||||
### 1.3 非目标
|
||||
|
||||
- 不要求一次性 100% 复刻客户端所有 UI/演出细节。
|
||||
- 不要求一次性改完全部 effect;先保证核心流程可跑,再分批清理。
|
||||
|
||||
---
|
||||
|
||||
## 2. 基线事实(实施前必须统一认知)
|
||||
|
||||
### 2.1 `flash` 仓库事实
|
||||
|
||||
- `group` 分支相对 `main` 的提交:
|
||||
- `4c07fa07 refactor(group-fight)`(引入组队)
|
||||
- `a410bfca Revert "refactor(group-fight)"`(回滚组队)
|
||||
- `e2382a4f`(地图重构)
|
||||
- `bd84f206`(.gitignore)
|
||||
- 所以 `group` HEAD 不再包含 `GroupFightDLL`、`core/group/*` 组队代码;需参考 `4c07fa07` 的内容。
|
||||
|
||||
### 2.2 `sun` 战斗现状
|
||||
|
||||
- 已有多战位骨架:
|
||||
- `logic/service/fight/input.go`:`Our/Opp []*input.Input`
|
||||
- `logic/service/fight/action/BattleAction.go`:`ActorIndex/TargetIndex`
|
||||
- `logic/service/fight/new_options.go`:`WithFightPlayersOnSide/WithFightInputs`
|
||||
- 仍有关键缺口:
|
||||
- 控制器入站仍是单战位参数(如 `2405/2406/2407` 只传技能/道具/catchTime)
|
||||
- 回合主链仍以双动作兼容流程为中心
|
||||
- 组队相关特性存在 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. 协议对齐清单(按优先级)
|
||||
|
||||
> 说明:本清单改为“统一协议结构体”路线,不再强制先实现 `75xx` 独立命令族。
|
||||
> 推荐做法:保留旧入口命令,服务端内部统一转为 `FightActionEnvelope/FightStateEnvelope`。
|
||||
|
||||
### 3.1 P0 必做(MVP 必须)
|
||||
|
||||
- [ ] 统一入站动作结构 `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 强烈建议(提升一致性)
|
||||
|
||||
- [ ] 完善 `phase=skill_hurt`
|
||||
- 至少带:施法方快照、受击方快照、技能、暴击、伤害、HP 变更
|
||||
- [ ] 完善 `phase=change`
|
||||
- 至少带:切宠发起位、切入目标位、新精灵状态
|
||||
- [ ] 完善 `phase=over`
|
||||
- 至少带:结束原因、胜方、收益主体
|
||||
- [ ] 完善 `phase=load/chat`
|
||||
- 组队加载进度、战斗内聊天统一走同一 envelope
|
||||
|
||||
### 3.3 P2 视时间补齐
|
||||
|
||||
- [ ] `phase=sprite_die/sprite_notice/win_close`
|
||||
- [ ] `phase=skill_wait/skill_wait_notice`
|
||||
- [ ] `phase=overtime/timeout_exit/relation_notice`
|
||||
|
||||
---
|
||||
|
||||
## 4. 代码改造任务清单(可直接分工)
|
||||
|
||||
## 4.1 协议与结构层(Owner A)
|
||||
|
||||
- [ ] 新增统一协议结构文件
|
||||
- 建议新建:`logic/service/fight/cmd_unified.go`
|
||||
- 要求:统一定义 `FightActionEnvelope` 和映射辅助结构
|
||||
|
||||
- [ ] 新增统一出站结构
|
||||
- 建议新建:`logic/service/fight/info/unified_info.go`
|
||||
- 要求:定义 `FightStateEnvelope/FighterState`,支持单打与双打
|
||||
|
||||
- [ ] 统一战位字段命名规范
|
||||
- `actorIndex`:我方执行位
|
||||
- `targetIndex`:敌方目标位
|
||||
- `side+pos` 与 `actorIndex/targetIndex` 转换规则写入注释
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 旧 cmd(`2405/2406/2407/2410`)可无损映射到统一入站结构。
|
||||
- [ ] 统一出站结构在 `start/skill_hurt/change/over` phase 均可序列化。
|
||||
|
||||
---
|
||||
|
||||
## 4.2 控制器与路由层(Owner B)
|
||||
|
||||
- [ ] 新增统一动作入口(可单文件)
|
||||
- 建议新建:`logic/controller/fight_unified.go`
|
||||
- 用途:将旧包和未来扩展包统一落到 `FightActionEnvelope`
|
||||
|
||||
- [ ] 兼容旧协议入口
|
||||
- `2405/2406/2407` 保持可用(默认 `actorIndex=0,targetIndex=0`)
|
||||
- 组队场景由 `actorIndex/targetIndex` 与战斗上下文决定,不再依赖独立 `75xx`
|
||||
|
||||
- [ ] 增加战前校验
|
||||
- 成员是否在同一组队房间
|
||||
- 战斗状态互斥
|
||||
- 战位可操作权限
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 任意技能动作都能转化为 `UseSkillAt(...)`(含 `actorIndex/targetIndex`)。
|
||||
- [ ] 非法战位命令被拒绝,不影响其他战位。
|
||||
|
||||
---
|
||||
|
||||
## 4.3 战斗核心层(Owner C)
|
||||
|
||||
- [ ] 固化“多动作一回合”模型
|
||||
- `collectPlayerActions`:按预期战位数收集,不是按两人收集
|
||||
- `resolveRound`:每回合一次统一排序与执行
|
||||
|
||||
- [ ] 降低对“双动作 enterturn”的耦合
|
||||
- 当前 `enterturn(first, second)` 作为兼容层保留
|
||||
- 新逻辑要确保:
|
||||
- 回合开始钩子只执行一次/回合
|
||||
- 回合结束钩子只执行一次/回合
|
||||
- 不因 pair 分片导致重复触发
|
||||
|
||||
- [ ] 完善动作-战位映射
|
||||
- `GetInputByAction` 在组队模式下严格按 `playerID + actorIndex/targetIndex` 定位
|
||||
- 超时补默认动作按战位补齐
|
||||
|
||||
- [ ] 完善死亡换宠/主动换宠
|
||||
- 按 actorIndex 粒度处理
|
||||
- 切宠广播必须携带 actor 位信息
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 2v2 场景一回合四动作都参与排序,不丢动作。
|
||||
- [ ] 同玩家多战位动作不会互相覆盖。
|
||||
- [ ] 任一战位死亡只影响对应战位换宠链路。
|
||||
|
||||
---
|
||||
|
||||
## 4.4 组队战报与广播层(Owner D)
|
||||
|
||||
- [ ] 统一战报快照结构
|
||||
- 至少包含:
|
||||
- 施法方:`userId/actorIndex/skillId/crit/dmg/hpAfter/status/prop`
|
||||
- 受击方:`userId/actorIndex/hpAfter/status/prop`
|
||||
|
||||
- [ ] 完成关键广播
|
||||
- 开战广播
|
||||
- 技能结果广播
|
||||
- 切宠成功广播
|
||||
- 战斗结束广播
|
||||
|
||||
- [ ] 保留旧包兼容(必要时双发)
|
||||
- 单打/双打统一走同一结构体
|
||||
- 如前端未升级,可按需保留旧 `2503/2505/2506` 过渡映射
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] 观战端/队友端收到的战位与 HP 同步一致。
|
||||
- [ ] 切宠后不会出现“错位显示”。
|
||||
|
||||
---
|
||||
|
||||
## 4.5 Effect 与规则层(Owner E)
|
||||
|
||||
- [ ] 先补明确组队依赖效果
|
||||
- `logic/service/fight/boss/NewSeIdx_501.go`
|
||||
- `logic/service/fight/boss/NewSeIdx_502.go`
|
||||
- `logic/service/fight/boss/NewSeIdx_503.go`
|
||||
|
||||
- [ ] 统一“队友”查询工具函数
|
||||
- 建议在 `input` 或 `fight` 层提供:
|
||||
- 获取同阵营存活战位
|
||||
- 获取队友列表(排除自己)
|
||||
- 群体目标选择上限
|
||||
|
||||
- [ ] 扫描组队敏感 effect
|
||||
- 特别关注含“组队对战时无效”描述项(如 effect 457)
|
||||
- 明确:是直接禁用,还是按组队模式替代逻辑
|
||||
|
||||
验收:
|
||||
|
||||
- [ ] `501/502/503` 在 2v2 场景行为符合设计。
|
||||
- [ ] 组队模式下不再出现空指针或越界。
|
||||
|
||||
---
|
||||
|
||||
## 4.6 测试与回归(Owner F)
|
||||
|
||||
- [ ] 单测补齐
|
||||
- `logic/service/fight/action_test.go`:继续扩充多战位覆盖
|
||||
- 新增建议:
|
||||
- `logic/service/fight/loop_multi_test.go`
|
||||
- `logic/service/fight/fight_group_test.go`
|
||||
|
||||
- [ ] 集成回归用例(最少)
|
||||
- Case 1:1v1 旧流程
|
||||
- Case 2:2v2 双方四动作
|
||||
- Case 3:同一玩家两战位各自出招
|
||||
- Case 4:中途切宠 + 被动死亡切宠
|
||||
- Case 5:超时默认动作补齐
|
||||
- Case 6:逃跑/掉线结束
|
||||
|
||||
- [ ] 构建与测试命令
|
||||
- `cd logic && go test ./service/fight/...`
|
||||
- `cd logic && go test ./controller/...`
|
||||
- `cd logic && go build ./...`
|
||||
|
||||
---
|
||||
|
||||
## 5. 文件级任务地图(便于派工)
|
||||
|
||||
- 协议/结构:
|
||||
- `logic/service/fight/cmd.go`
|
||||
- `logic/service/fight/cmd_unified.go`(新增)
|
||||
- `logic/service/fight/info/info.go`
|
||||
- `logic/service/fight/info/unified_info.go`(新增)
|
||||
|
||||
- 控制器:
|
||||
- `logic/controller/fight_base.go`
|
||||
- `logic/controller/fight_pvp_withplayer.go`
|
||||
- `logic/controller/fight_unified.go`(新增)
|
||||
|
||||
- 核心流程:
|
||||
- `logic/service/fight/new.go`
|
||||
- `logic/service/fight/new_options.go`
|
||||
- `logic/service/fight/input.go`
|
||||
- `logic/service/fight/action.go`
|
||||
- `logic/service/fight/loop.go`
|
||||
- `logic/service/fight/fightc.go`
|
||||
|
||||
- Effect:
|
||||
- `logic/service/fight/boss/NewSeIdx_501.go`
|
||||
- `logic/service/fight/boss/NewSeIdx_502.go`
|
||||
- `logic/service/fight/boss/NewSeIdx_503.go`
|
||||
- 其他含组队语义的 effect 文件
|
||||
|
||||
- 测试:
|
||||
- `logic/service/fight/action_test.go`
|
||||
- `logic/service/fight/*_test.go`(新增)
|
||||
|
||||
---
|
||||
|
||||
## 6. 里程碑与交付标准
|
||||
|
||||
### M1(协议可通)
|
||||
|
||||
- [ ] 统一结构体可完成 `start/skill_hurt/change/over` 四类下发
|
||||
- [ ] 旧命令入口均可映射到 `FightC` indexed 接口
|
||||
|
||||
### M2(核心可跑)
|
||||
|
||||
- [ ] 2v2 全回合可稳定执行
|
||||
- [ ] 切宠/道具/超时可用
|
||||
|
||||
### M3(规则可用)
|
||||
|
||||
- [ ] 501/502/503 完成
|
||||
- [ ] 主要组队战报可用
|
||||
|
||||
### M4(回归上线)
|
||||
|
||||
- [ ] 1v1 不回归
|
||||
- [ ] `go test` 与 `go build` 通过
|
||||
- [ ] 文档补充已完成项与遗留项
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险清单与缓解
|
||||
|
||||
- 风险:旧逻辑大量默认 `CurPet[0]`,多人战位容易错位。
|
||||
缓解:引入统一 `CurrentPetByActor`/`TargetByIndex` 访问函数,禁止新代码直接写死 `[0]`。
|
||||
|
||||
- 风险:`enterturn` 兼容层导致钩子重复触发。
|
||||
缓解:把“回合开始/结束”从 pair 执行中抽离,确保每回合只触发一次。
|
||||
|
||||
- 风险:协议切换导致旧客户端不可用。
|
||||
缓解:服务端保持旧入口不变,先做“旧包 -> 统一结构”映射;前端按版本切流。
|
||||
|
||||
- 风险:effect 批量改动引发回归。
|
||||
缓解:先做组队关键 effect,其他 effect 分批迁移并每批回归。
|
||||
|
||||
---
|
||||
|
||||
## 8. 实施顺序建议(最小阻塞)
|
||||
|
||||
1. 协议结构与控制器入口
|
||||
2. 动作收集与回合统一执行
|
||||
3. 切宠/道具/超时按战位修正
|
||||
4. 关键广播与战报
|
||||
5. 组队 effect(501/502/503)
|
||||
6. 全量测试与回归
|
||||
|
||||
---
|
||||
|
||||
## 9. 交接要求(给执行同学)
|
||||
|
||||
- 每完成一个里程碑,在 `docs/` 新增一段“完成项/未完成项/阻塞项”。
|
||||
- 如改动协议字段,必须附抓包样例或字段注释,不允许只改代码不补说明。
|
||||
- 如发现与本清单冲突的历史逻辑,以“兼容线上行为优先”,并在文档记录偏差原因。
|
||||
|
||||
---
|
||||
|
||||
## 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(...)`,避免调用方歧义。
|
||||
327
docs/fight-multi-battle-refactor-task-2026-04-04.md
Normal file
327
docs/fight-multi-battle-refactor-task-2026-04-04.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# 战斗多单位模式改造交接文档(2026-04-04)
|
||||
|
||||
## 0. 2026-04-04 本轮完成情况
|
||||
|
||||
本轮已完成以下落地项:
|
||||
|
||||
- 动作提交改为按 `playerID + actorIndex` 去重,同一玩家同回合的多个上场位动作不会再互相覆盖
|
||||
- 主循环已从“双动作入口”改为“动作列表入口”,`resolveRound` 现接收并处理 `[]action.BattleActionI`
|
||||
- 回合结算增加了基于优先级和速度的统一排序,并按跨阵营动作对子顺序执行,保留现有 `enterturn(first, second)` 兼容层
|
||||
- 技能和道具的目标选择已接入 `targetIndex`,不再固定打对面 `0` 号位
|
||||
- 切宠同步改为携带 `actorIndex`,同一玩家多上场位的切宠播报不再冲突
|
||||
- 开战同步结构新增当前战斗位数组,同时保留 `Info1/Info2` 兼容旧结构
|
||||
- `FightI` 已补充 `UseSkillAt/ChangePetAt/UseItemAt/GetCurrPETAt`
|
||||
- `NewFight` 已改为包装 `NewFightWithOptions(...)`,创建阶段开始支持 option/builder 扩展
|
||||
- `Ctx` 已拆分为 `LegacySides + EffectBinding`,effect 本体上挂载上下文,并补充 `Source/Carrier/Target`
|
||||
- 核心执行链已开始迁移到真实 source/target 语义:`AddEffect`、`Exec`、`Damage`、`SetProp`、主技能结算流程会注入实际对手上下文
|
||||
- 已迁移一批公共/高复用 effect 到新语义,包括状态基类、击败触发、物攻附加状态、`1097-1101`、`680-690`、部分魂印基础逻辑
|
||||
- 本轮继续完成了 `1263-1287`、`1288-1312`、`1448-1472`、`1473-1497` 四组 effect 的迁移,已不再直接依赖 `Ctx().Our/Opp`,统一改为 `CarrierInput()/OpponentInput()` 访问当前承载侧与对位侧
|
||||
- 增加了动作队列的基础单测,覆盖“同玩家不同槽位保留”和“同槽位动作替换”
|
||||
|
||||
本轮仍保留的限制:
|
||||
|
||||
- `enterturn` 和大量 `effect/node` 逻辑仍是双动作上下文,因此当前实现采用“动作列表排序 + 跨阵营配对兼容执行”的过渡方案,而不是一次性重写所有效果系统
|
||||
- `NewFight` 仍按现有建房流程创建双方 1 个战斗位;本轮打通的是多战斗位结算骨架和接口,不是外部建房入口的全量切换
|
||||
- 大量具体 effect 仍在使用旧的 `Ctx().Our/Opp` 语义;当前已迁移的是上下文承载方式、执行链和部分公共基类,具体 effect 仍需继续分批迁移
|
||||
|
||||
### 0.1 effect 迁移增量记录
|
||||
|
||||
本轮新增完成:
|
||||
|
||||
- `logic/service/fight/effect/1263_1287.go`
|
||||
- `logic/service/fight/effect/1288_1312.go`
|
||||
- `logic/service/fight/effect/1448_1472.go`
|
||||
- `logic/service/fight/effect/1473_1497.go`
|
||||
|
||||
这两组文件当前迁移策略是:
|
||||
|
||||
- 旧语义中的 `Our` 统一视为“当前执行/承载该 effect 的输入侧”,迁移为 `CarrierInput()`
|
||||
- 旧语义中的 `Opp` 统一迁移为当前结算上下文里的对位输入侧,即 `OpponentInput()`
|
||||
- 暂不在这一轮强行把所有 effect 重写成纯 `Source/Target/Carrier` 三元语义;先保证 hostile sub-effect、回合类 effect、挂在对手身上的限制类 effect 都不再依赖 legacy 字段访问
|
||||
|
||||
### 0.2 下一批待迁移队列
|
||||
|
||||
高密度遗留文件已继续向后推进,`1448-1497` 这两个分段本轮已清理完成。
|
||||
|
||||
下一轮继续迁移时,建议直接对 effect 包执行一次全量扫描,按“仍包含 `Ctx().Our/Opp` 的 grouped file”继续往后收口,而不是再只盯固定编号段。
|
||||
|
||||
## 1. 任务目标
|
||||
|
||||
将当前战斗系统从“每回合双方各 1 个动作”的模型,改造成支持多上场位、多操作者的统一回合模型,最终支持以下 3 种战斗模式:
|
||||
|
||||
1. `1玩家:N精灵:1上场 VS 1玩家:N精灵:1上场`
|
||||
2. `N玩家:N精灵:N上场 VS N玩家:N精灵:N上场`
|
||||
3. `1玩家:N精灵:N上场 VS 1玩家:N精灵:N上场`
|
||||
|
||||
当前代码只完整支持模式 1。模式 2 和模式 3 只做了结构铺垫,还没有真正打通。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前已完成的基础改造
|
||||
|
||||
以下结构改造已经落地:
|
||||
|
||||
- `FightC.Our/Opp` 已改成数组,表示战场单位数组,不再是单对象。
|
||||
- `input.Input.CurrentPet` 已改成 `CurPet`,并且是数组。
|
||||
- `FightC.OurPlayers/OppPlayers` 已加入,用于表达操作者数组。
|
||||
- 战斗单位与操作者已解耦:
|
||||
- `Our/Opp` 表示战斗位
|
||||
- `OurPlayers/OppPlayers` 表示操作这些战斗位的玩家
|
||||
- `BattlePetEntity` 已支持绑定控制者:`ControllerUserID`
|
||||
- 动作模型已支持:
|
||||
- `ActorIndex`
|
||||
- `TargetIndex`
|
||||
- 已提供 indexed 入口:
|
||||
- `UseSkillAt(c, skillID, actorIndex, targetIndex)`
|
||||
- `ChangePetAt(c, petID, actorIndex)`
|
||||
- `UseItemAt(c, catchTime, itemID, actorIndex, targetIndex)`
|
||||
|
||||
当前默认行为仍等价于:
|
||||
|
||||
- `actorIndex = 0`
|
||||
- `targetIndex = 0`
|
||||
|
||||
也就是当前模式下仍然是操作和结算 `0` 号单位。
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前未完成的核心问题
|
||||
|
||||
### 3.1 回合模型仍然是“双动作模型”
|
||||
|
||||
目前主流程仍然是每回合只处理双方两个动作,而不是处理一个动作列表。
|
||||
|
||||
关键位置:
|
||||
|
||||
- `logic/service/fight/loop.go`
|
||||
- `collectPlayerActions(...)` 只收 2 个动作
|
||||
- `resolveRound(p1Action, p2Action)` 只结算 2 个动作
|
||||
|
||||
这意味着:
|
||||
|
||||
- 模式 2 无法支持双方多个操作者或多个上场位同时行动
|
||||
- 模式 3 无法支持同一玩家控制多个上场位分别出手
|
||||
|
||||
### 3.2 动作提交仍按 `playerID` 去重
|
||||
|
||||
当前动作队列逻辑仍以 `playerID` 作为主要识别维度。
|
||||
|
||||
关键位置:
|
||||
|
||||
- `logic/service/fight/action.go`
|
||||
- `submitAction(...)`
|
||||
|
||||
这会导致:
|
||||
|
||||
- 同一玩家在同一回合给多个上场位下达动作时,动作会互相覆盖或无法完整保留
|
||||
|
||||
这一点对模式 3 是直接阻塞,对模式 2 也不够健壮。
|
||||
|
||||
### 3.3 切宠和当前上场位逻辑仍大量默认使用 `CurPet[0]`
|
||||
|
||||
虽然 `CurPet` 已经是数组,但主流程中不少逻辑仍固定操作 `0` 号位。
|
||||
|
||||
典型影响:
|
||||
|
||||
- 死亡换宠
|
||||
- 主动换宠
|
||||
- 当前出手单位检查
|
||||
- 当前目标单位检查
|
||||
|
||||
这部分需要按 `actorIndex` 或上场槽位改造。
|
||||
|
||||
### 3.4 开战协议仍然只有两个当前单位
|
||||
|
||||
当前开战下发协议仍然是双单位结构。
|
||||
|
||||
关键位置:
|
||||
|
||||
- `logic/service/fight/info/info.go`
|
||||
- `FightStartOutboundInfo`
|
||||
- 仍只有 `Info1` 和 `Info2`
|
||||
|
||||
这不适合多上场位模式。
|
||||
|
||||
### 3.5 公共接口仍是旧的单单位接口
|
||||
|
||||
关键位置:
|
||||
|
||||
- `logic/service/common/fight.go`
|
||||
- `FightI`
|
||||
|
||||
目前接口仍只有:
|
||||
|
||||
- `UseSkill(c, id)`
|
||||
- `ChangePet(c, id)`
|
||||
- `UseItem(c, cacthid, itemid)`
|
||||
|
||||
而 indexed 版本只存在于具体实现 `FightC` 上,没有进入正式接口层。
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前实现与目标模式的对应关系
|
||||
|
||||
### 4.1 模式 1
|
||||
|
||||
`1玩家:N精灵:1上场 VS 1玩家:N精灵:1上场`
|
||||
|
||||
当前支持。
|
||||
|
||||
原因:
|
||||
|
||||
- 当前默认就是操作 `0` 号单位
|
||||
- 当前默认就是攻击 `0` 号目标
|
||||
- 当前回合系统仍是每边 1 个动作,这与模式 1 一致
|
||||
|
||||
### 4.2 模式 2
|
||||
|
||||
`N玩家:N精灵:N上场 VS N玩家:N精灵:N上场`
|
||||
|
||||
当前不支持。
|
||||
|
||||
直接原因:
|
||||
|
||||
- 一回合只收 2 个动作
|
||||
- 一回合只结算 2 个动作
|
||||
- 协议仍只同步 2 个当前上场位
|
||||
|
||||
### 4.3 模式 3
|
||||
|
||||
`1玩家:N精灵:N上场 VS 1玩家:N精灵:N上场`
|
||||
|
||||
当前不支持。
|
||||
|
||||
直接原因:
|
||||
|
||||
- 同一玩家的多个动作无法作为同回合动作列表完整保留
|
||||
- 主流程仍不是按动作列表统一排序和执行
|
||||
|
||||
---
|
||||
|
||||
## 5. 需要完成的工作
|
||||
|
||||
### 5.1 改造动作收集模型
|
||||
|
||||
将当前“每边 1 个动作”的模型改成“每个可操作上场位 1 个动作”的模型。
|
||||
|
||||
至少需要做到:
|
||||
|
||||
- 同一玩家可以在同一回合提交多个动作
|
||||
- 每个动作能区分是哪个上场位发出的
|
||||
- 每个动作能区分目标上场位
|
||||
|
||||
建议将动作唯一键至少扩为:
|
||||
|
||||
- `playerID`
|
||||
- `actorIndex`
|
||||
|
||||
### 5.2 改造回合结算模型
|
||||
|
||||
将当前:
|
||||
|
||||
- `resolveRound(p1Action, p2Action)`
|
||||
|
||||
改成:
|
||||
|
||||
- `resolveRound(actions []action.BattleActionI)`
|
||||
|
||||
并完成:
|
||||
|
||||
- 动作列表排序
|
||||
- 按优先级、速度等规则统一排序
|
||||
- 排序后逐个结算
|
||||
|
||||
注意:
|
||||
|
||||
- 当前 effect/node 体系里仍有大量“双动作”接口,不适合一次性全部重写
|
||||
- 建议先在主流程做兼容层,逐步过渡
|
||||
|
||||
### 5.3 按槽位处理切宠与死亡换宠
|
||||
|
||||
将当前固定 `CurPet[0]` 的逻辑改成按槽位处理:
|
||||
|
||||
- 主动换宠
|
||||
- 被动死亡换宠
|
||||
- 死亡校验
|
||||
- 出手资格判断
|
||||
|
||||
### 5.4 增加开战与战斗同步结构
|
||||
|
||||
将当前的双单位同步结构扩成可支持多上场位的结构。但是保持协议结构不变。现在是固定两个,可以改成数组来实现
|
||||
|
||||
重点是:
|
||||
|
||||
- 开战协议
|
||||
- 当前上场位同步
|
||||
- 切宠同步
|
||||
- 可能的回合播报结构
|
||||
|
||||
### 5.5 补齐公共接口
|
||||
|
||||
将 indexed 版本能力补进接口层,避免只能通过具体实现类型访问。
|
||||
|
||||
建议新增类似接口:
|
||||
|
||||
- `UseSkillAt(...)`
|
||||
- `ChangePetAt(...)`
|
||||
- `UseItemAt(...)`
|
||||
|
||||
---
|
||||
|
||||
## 6. 推荐实施顺序
|
||||
|
||||
建议按下面顺序推进,避免一次性改动面过大:
|
||||
|
||||
1. 先改动作队列和动作收集逻辑
|
||||
2. 再改回合结算为动作列表
|
||||
3. 再改切宠和死亡换宠按槽位处理
|
||||
4. 最后改协议和正式接口
|
||||
|
||||
不建议一开始就全量重写 effect/node 接口,因为当前大量效果实现仍假设双动作上下文。
|
||||
|
||||
---
|
||||
|
||||
## 7. 建议重点查看文件
|
||||
|
||||
- `logic/service/fight/action.go`
|
||||
- `logic/service/fight/loop.go`
|
||||
- `logic/service/fight/fightc.go`
|
||||
- `logic/service/fight/input.go`
|
||||
- `logic/service/fight/input/input.go`
|
||||
- `logic/service/fight/action/BattleAction.go`
|
||||
- `logic/service/fight/info/info.go`
|
||||
- `logic/service/common/fight.go`
|
||||
|
||||
---
|
||||
|
||||
## 8. 完成标准
|
||||
|
||||
至少满足以下条件,才算这次改造完成:
|
||||
|
||||
1. 同一玩家可以在同一回合给多个上场位分别提交动作,动作不会互相覆盖
|
||||
2. 双方多个上场位可以在同一回合统一排序并依次结算
|
||||
3. 攻击目标位可选,不再默认只能打对面 `0` 号
|
||||
4. 切宠可以按上场槽位处理
|
||||
5. 模式 1 不回归
|
||||
6. 代码编译通过
|
||||
|
||||
---
|
||||
|
||||
## 9. 最低验证要求
|
||||
|
||||
至少执行:
|
||||
|
||||
- `cd /workspace/logic && go build ./...`
|
||||
- `cd /workspace/logic && go test ./service/fight/effect`
|
||||
|
||||
如果本轮改动较大,建议再补一轮:
|
||||
|
||||
- `cd /workspace/logic && go test ./...`
|
||||
|
||||
---
|
||||
|
||||
## 10. 额外提醒
|
||||
|
||||
- 当前仓库工作区可能是脏的,不要回滚无关修改。
|
||||
- 这次改造的真正核心不是结构字段改数组,而是把回合系统从“双动作模型”改成“动作列表模型”。
|
||||
- 已有 `ActorIndex/TargetIndex` 只是入口铺垫,不代表多单位模式已经完成。
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user