Compare commits
115 Commits
1ca0ff344e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
3
.cnb.yml
3
.cnb.yml
@@ -27,5 +27,4 @@ main:
|
|||||||
username: ${GIT_USERNAME}
|
username: ${GIT_USERNAME}
|
||||||
password: ${GIT_ACCESS_TOKEN}
|
password: ${GIT_ACCESS_TOKEN}
|
||||||
force: true
|
force: true
|
||||||
|
sync_mode: push
|
||||||
#sync_mode: rebase
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -46,4 +46,5 @@ public/login-linux-amd64
|
|||||||
.cache/gomod/**
|
.cache/gomod/**
|
||||||
public/login-login-linux-amd64
|
public/login-login-linux-amd64
|
||||||
public/logic_linux-amd64_1
|
public/logic_linux-amd64_1
|
||||||
.cache/**
|
.cache/**
|
||||||
|
.agents/**
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ ENV GOMODCACHE=/workspace/.cache/gomod
|
|||||||
# ==========================================
|
# ==========================================
|
||||||
# 2. Codex 配置 (更换时修改这里,重新 build)
|
# 2. Codex 配置 (更换时修改这里,重新 build)
|
||||||
# ==========================================
|
# ==========================================
|
||||||
ENV CODEX_BASE_URL="http://fast.jnm.lol/v1"
|
ENV CODEX_BASE_URL="https://api.jucode.cn/v1"
|
||||||
|
|
||||||
ENV CODEX_MODEL="gpt-5.4"
|
ENV CODEX_MODEL="gpt-5.4"
|
||||||
|
|
||||||
ENV OPENAI_API_KEY="pk_live_d15iVqaSMxD_XLHh0nrFPJ_fzFZy8IfR4Cd62bERl8g"
|
ENV OPENAI_API_KEY="sk-E0ZZIFNnD0RkhMC9pT2AGMutz9vNy2VLNrgyyobT5voa81pQ"
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# 3. 安装系统依赖、Golang、Code-server
|
# 3. 安装系统依赖、Golang、Code-server
|
||||||
|
|||||||
13
.ide/help.md
13
.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.jucode.cn/
|
||||||
https://api.gemai.cc/console/token 免费给部分额度 ,还有100块
|
fastai.fast 使用谷歌邮箱https://linshiguge.com/白嫖
|
||||||
https://zread.ai/tawer-blog/lmarena-2api/1-overview GLM web2 pai
|
https://zread.ai/tawer-blog/lmarena-2api/1-overview GLM web2 pai
|
||||||
|
|
||||||
https://crazyrouter.com/console 模型最便宜,看看能不能1:10
|
https://crazyrouter.com/console 模型最便宜,看看能不能1:10
|
||||||
@@ -14,7 +9,7 @@ https://crazyrouter.com/console 模型最便宜,看看能不能1:10
|
|||||||
https://agentrouter.org/pricing 签到给,有175
|
https://agentrouter.org/pricing 签到给,有175
|
||||||
|
|
||||||
|
|
||||||
kuaipao.ai 充了十块 cjf19970621 cjf19970621
|
|
||||||
|
|
||||||
充了十块
|
充了十块
|
||||||
使用网址:https://www.jnm.lol
|
使用网址:https://www.jnm.lol
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ skip_clone: true
|
|||||||
steps:
|
steps:
|
||||||
# ========== 1. 替代clone:拉取代码(核心依赖) ==========
|
# ========== 1. 替代clone:拉取代码(核心依赖) ==========
|
||||||
prepare:
|
prepare:
|
||||||
image: alpine/git
|
image: docker.1ms.run/alpine/git
|
||||||
environment:
|
environment:
|
||||||
# WOODPECKER_SSH_KEY:
|
# WOODPECKER_SSH_KEY:
|
||||||
# from_secret: WOODPECKER_SSH_KEY
|
# from_secret: WOODPECKER_SSH_KEY
|
||||||
@@ -70,7 +70,7 @@ steps:
|
|||||||
|
|
||||||
# ========== 4. 编译Logic服务(完全参考GitHub Actions编译配置) ==========
|
# ========== 4. 编译Logic服务(完全参考GitHub Actions编译配置) ==========
|
||||||
build_logic:
|
build_logic:
|
||||||
image: golang:1.25
|
image: docker.m.daocloud.io/golang:1.25
|
||||||
depends_on: [prepare]
|
depends_on: [prepare]
|
||||||
environment:
|
environment:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
@@ -142,23 +142,23 @@ steps:
|
|||||||
|
|
||||||
# ========== 6. SCP推送产物(依赖编译+配置解析) ==========
|
# ========== 6. SCP推送产物(依赖编译+配置解析) ==========
|
||||||
scp-exe-to-servers: # 与fetch-deploy-config同级,缩进2个空格
|
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个空格
|
settings: # 子元素,缩进4个空格
|
||||||
host: &ssh_host 2697v22.mc5173.cn
|
host: &ssh_host 43.248.3.21
|
||||||
port: &ssh_port 16493
|
port: &ssh_port 22
|
||||||
username: &ssh_user root
|
username: &ssh_user root
|
||||||
password: &ssh_pass xIy9PQcBF96C
|
password: &ssh_pass KQv7yzna7BDukK
|
||||||
|
|
||||||
source:
|
source:
|
||||||
- blazing/build/**
|
- blazing/build/**
|
||||||
target: /opt/blazing/
|
target: /ext/blazing/
|
||||||
strip_components: 1 # 统一缩进6个空格
|
strip_components: 1 # 统一缩进6个空格
|
||||||
skip_verify: true # 统一缩进6个空格
|
skip_verify: true # 统一缩进6个空格
|
||||||
timeout: 30s # 统一缩进6个空格
|
timeout: 30s # 统一缩进6个空格
|
||||||
depends_on: # 子元素,缩进4个空格
|
depends_on: # 子元素,缩进4个空格
|
||||||
- build_logic # depends_on内的项,缩进6个空格
|
- build_logic # depends_on内的项,缩进6个空格
|
||||||
start-login-logic:
|
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]
|
depends_on: [scp-exe-to-servers]
|
||||||
settings: # 子元素,缩进4个空格
|
settings: # 子元素,缩进4个空格
|
||||||
host: *ssh_host
|
host: *ssh_host
|
||||||
@@ -167,7 +167,7 @@ steps:
|
|||||||
password: *ssh_pass
|
password: *ssh_pass
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
cd /opt/blazing/build
|
cd /ext/blazing/build
|
||||||
ls -t login_* 2>/dev/null | head -1
|
ls -t login_* 2>/dev/null | head -1
|
||||||
BIN_NAME=$(ls -t login_* 2>/dev/null | head -1)
|
BIN_NAME=$(ls -t login_* 2>/dev/null | head -1)
|
||||||
echo "BIN_NAME: $BIN_NAME"
|
echo "BIN_NAME: $BIN_NAME"
|
||||||
@@ -201,9 +201,9 @@ steps:
|
|||||||
# 移动logic产物到public目录
|
# 移动logic产物到public目录
|
||||||
LOGIC_BIN=$(ls -t logic_* 2>/dev/null | head -1)
|
LOGIC_BIN=$(ls -t logic_* 2>/dev/null | head -1)
|
||||||
if [ -n "$LOGIC_BIN" ]; then
|
if [ -n "$LOGIC_BIN" ]; then
|
||||||
mkdir -p /opt/blazing/build/public
|
mkdir -p /ext/blazing/build/public
|
||||||
mv $LOGIC_BIN /opt/blazing/build/public/
|
mv $LOGIC_BIN /ext/blazing/build/public/
|
||||||
echo "✅ Logic产物已移动到 /opt/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
|
echo "✅ Logic产物已移动到 /ext/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
|
||||||
else
|
else
|
||||||
echo "⚠️ 未找到Logic产物"
|
echo "⚠️ 未找到Logic产物"
|
||||||
fi
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"blazing/cool/coolconfig"
|
"blazing/cool/coolconfig"
|
||||||
@@ -158,12 +159,33 @@ func (c *Controller) Page(ctx context.Context, req *PageReq) (res *BaseRes, err
|
|||||||
// 注册控制器到路由
|
// 注册控制器到路由
|
||||||
func RegisterController(c IController) {
|
func RegisterController(c IController) {
|
||||||
var ctx = context.Background()
|
var ctx = context.Background()
|
||||||
var sController = &Controller{}
|
var sController *Controller
|
||||||
gconv.Struct(c, &sController)
|
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 {
|
if coolconfig.Config.Eps {
|
||||||
model := sController.Service.GetModel()
|
model := sController.Service.GetModel()
|
||||||
columns := getModelInfo(ctx, sController.Prefix, model)
|
tableName := ""
|
||||||
ModelInfo[sController.Prefix] = columns
|
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(
|
g.Server().Group(
|
||||||
sController.Prefix, func(group *ghttp.RouterGroup) {
|
sController.Prefix, func(group *ghttp.RouterGroup) {
|
||||||
|
|||||||
@@ -1,121 +1,136 @@
|
|||||||
package coolconfig
|
package coolconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"os"
|
||||||
|
"strings"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"time"
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
|
||||||
)
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
// cool config
|
)
|
||||||
type sConfig struct {
|
|
||||||
AutoMigrate bool `json:"auto_migrate,omitempty"` // 是否自动创建表
|
// cool config
|
||||||
Eps bool `json:"eps,omitempty"` // 是否开启eps
|
type sConfig struct {
|
||||||
File *file `json:"file,omitempty"` // 文件上传配置
|
AutoMigrate bool `json:"auto_migrate,omitempty"` // 是否自动创建表
|
||||||
Name string `json:"name"` // 项目名称
|
Eps bool `json:"eps,omitempty"` // 是否开启eps
|
||||||
// LoginPort string `json:"port"`
|
File *file `json:"file,omitempty"` // 文件上传配置
|
||||||
GameOnlineID uint32 `json:"port_bl"` //这个是命令行输入的参数
|
Name string `json:"name"` // 项目名称
|
||||||
ServerInfo ServerList
|
// LoginPort string `json:"port"`
|
||||||
|
GameOnlineID uint32 `json:"port_bl"` //这个是命令行输入的参数
|
||||||
Address string //rpc端口
|
ServerInfo ServerList
|
||||||
|
|
||||||
}
|
Address string //rpc端口
|
||||||
type ServerList struct {
|
|
||||||
OnlineID uint32 `gorm:"column:online_id;comment:'在线ID';uniqueIndex" json:"online_id"`
|
}
|
||||||
//服务器名称Desc
|
type ServerList struct {
|
||||||
Name string `gorm:"comment:'服务器名称'" json:"name"`
|
OnlineID uint32 `gorm:"column:online_id;comment:'在线ID'" json:"online_id"`
|
||||||
IP string `gorm:"type:string;comment:'服务器IP'" json:"ip"`
|
//服务器名称Desc
|
||||||
Port uint32 `gorm:"comment:'端口号,通常是小整数'" json:"port"`
|
Name string `gorm:"comment:'服务器名称'" json:"name"`
|
||||||
IsOpen uint8 `gorm:"default:0;not null;comment:'是否开启'" json:"is_open"`
|
IP string `gorm:"type:string;comment:'服务器IP'" json:"ip"`
|
||||||
//登录地址
|
Port uint32 `gorm:"comment:'端口号,通常是小整数'" json:"port"`
|
||||||
LoginAddr string `gorm:"type:string;comment:'登录地址'" json:"login_addr"`
|
IsOpen uint8 `gorm:"default:0;not null;comment:'是否开启'" json:"is_open"`
|
||||||
//账号
|
//登录地址
|
||||||
Account string `gorm:"type:string;comment:'账号'" json:"account"`
|
LoginAddr string `gorm:"type:string;comment:'登录地址'" json:"login_addr"`
|
||||||
//密码
|
//账号
|
||||||
Password string `gorm:"type:string;comment:'密码'" json:"password"`
|
Account string `gorm:"type:string;comment:'账号'" json:"account"`
|
||||||
CanPort []uint32 `gorm:"type:jsonb;comment:'可连接端口'" json:"can_port"`
|
//密码
|
||||||
//是否测试服
|
Password string `gorm:"type:string;comment:'密码'" json:"password"`
|
||||||
IsVip uint32 `gorm:"default:0;not null;comment:'是否为VIP服务器'" json:"is_vip"`
|
CanPort []uint32 `gorm:"type:jsonb;comment:'可连接端口'" json:"can_port"`
|
||||||
//isdebug 是否本地服
|
//是否测试服
|
||||||
IsDebug uint8 `gorm:"default:0;comment:'是否为调试模式'" json:"is_debug"`
|
IsVip uint32 `gorm:"default:0;not null;comment:'是否为VIP服务器'" json:"is_vip"`
|
||||||
|
//isdebug 是否本地服
|
||||||
//服务器属主Desc
|
IsDebug uint8 `gorm:"default:0;comment:'是否为调试模式'" json:"is_debug"`
|
||||||
Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"`
|
|
||||||
Desc string `gorm:"comment:'服务器描述'" json:"desc"`
|
//服务器属主Desc
|
||||||
OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"`
|
Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"`
|
||||||
//到期时间ServerList
|
Desc string `gorm:"comment:'服务器描述'" json:"desc"`
|
||||||
ExpireTime time.Time `gorm:"default:0;comment:'到期时间'" json:"expire_time"`
|
OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"`
|
||||||
}
|
//到期时间ServerList
|
||||||
|
ExpireTime time.Time `gorm:"default:0;comment:'到期时间'" json:"expire_time"`
|
||||||
func (s *ServerList) GetID() string {
|
}
|
||||||
return gconv.String(100000*s.OnlineID + s.Port)
|
|
||||||
}
|
func (s *ServerList) GetID() string {
|
||||||
|
return gconv.String(100000*s.OnlineID + s.Port)
|
||||||
// OSS相关配置
|
}
|
||||||
type oss struct {
|
|
||||||
Endpoint string `json:"endpoint"`
|
// OSS相关配置
|
||||||
AccessKeyID string `json:"accessKeyID"`
|
type oss struct {
|
||||||
SecretAccessKey string `json:"secretAccessKey"`
|
Endpoint string `json:"endpoint"`
|
||||||
UseSSL bool `json:"useSSL"`
|
AccessKeyID string `json:"accessKeyID"`
|
||||||
BucketName string `json:"bucketName"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
Location string `json:"location"`
|
UseSSL bool `json:"useSSL"`
|
||||||
}
|
BucketName string `json:"bucketName"`
|
||||||
|
Location string `json:"location"`
|
||||||
// 文件上传配置
|
}
|
||||||
type file struct {
|
|
||||||
Mode string `json:"mode"` // 模式 local oss
|
// 文件上传配置
|
||||||
Domain string `json:"domain"` // 域名 http://
|
type file struct {
|
||||||
Oss *oss `json:"oss,omitempty"`
|
Mode string `json:"mode"` // 模式 local oss
|
||||||
}
|
Domain string `json:"domain"` // 域名 http://
|
||||||
|
Oss *oss `json:"oss,omitempty"`
|
||||||
// NewConfig new config
|
}
|
||||||
func newConfig() *sConfig {
|
|
||||||
var ctx g.Ctx
|
// NewConfig new config
|
||||||
config := &sConfig{
|
func newConfig() *sConfig {
|
||||||
AutoMigrate: GetCfgWithDefault(ctx, "blazing.autoMigrate", g.NewVar(false)).Bool(),
|
var ctx g.Ctx
|
||||||
Name: GetCfgWithDefault(ctx, "server.name", g.NewVar("")).String(),
|
config := &sConfig{
|
||||||
|
AutoMigrate: hasDebugArg(),
|
||||||
Eps: GetCfgWithDefault(ctx, "blazing.eps", g.NewVar(false)).Bool(),
|
Name: GetCfgWithDefault(ctx, "server.name", g.NewVar("")).String(),
|
||||||
// LoginPort: string(GetCfgWithDefault(ctx, "server.port", g.NewVar("8080")).String()),
|
|
||||||
Address: GetCfgWithDefault(ctx, "server.address", g.NewVar("8080")).String(),
|
Eps: GetCfgWithDefault(ctx, "blazing.eps", g.NewVar(false)).Bool(),
|
||||||
//GamePort: GetCfgWithDefault(ctx, "server.game", g.NewVar("8080")).Uint64s(),
|
// LoginPort: string(GetCfgWithDefault(ctx, "server.port", g.NewVar("8080")).String()),
|
||||||
|
Address: GetCfgWithDefault(ctx, "server.address", g.NewVar("8080")).String(),
|
||||||
File: &file{
|
//GamePort: GetCfgWithDefault(ctx, "server.game", g.NewVar("8080")).Uint64s(),
|
||||||
Mode: GetCfgWithDefault(ctx, "blazing.file.mode", g.NewVar("none")).String(),
|
|
||||||
Domain: GetCfgWithDefault(ctx, "blazing.file.domain", g.NewVar("http://127.0.0.1:8300")).String(),
|
File: &file{
|
||||||
Oss: &oss{
|
Mode: GetCfgWithDefault(ctx, "blazing.file.mode", g.NewVar("none")).String(),
|
||||||
Endpoint: GetCfgWithDefault(ctx, "blazing.file.oss.endpoint", g.NewVar("127.0.0.1:9000")).String(),
|
Domain: GetCfgWithDefault(ctx, "blazing.file.domain", g.NewVar("http://127.0.0.1:8300")).String(),
|
||||||
AccessKeyID: GetCfgWithDefault(ctx, "blazing.file.oss.accessKeyID", g.NewVar("")).String(),
|
Oss: &oss{
|
||||||
SecretAccessKey: GetCfgWithDefault(ctx, "blazing.file.oss.secretAccessKey", g.NewVar("")).String(),
|
Endpoint: GetCfgWithDefault(ctx, "blazing.file.oss.endpoint", g.NewVar("127.0.0.1:9000")).String(),
|
||||||
UseSSL: GetCfgWithDefault(ctx, "blazing.file.oss.useSSL", g.NewVar(false)).Bool(),
|
AccessKeyID: GetCfgWithDefault(ctx, "blazing.file.oss.accessKeyID", g.NewVar("")).String(),
|
||||||
BucketName: GetCfgWithDefault(ctx, "blazing.file.oss.bucketName", g.NewVar("blazing")).String(),
|
SecretAccessKey: GetCfgWithDefault(ctx, "blazing.file.oss.secretAccessKey", g.NewVar("")).String(),
|
||||||
Location: GetCfgWithDefault(ctx, "blazing.file.oss.location", g.NewVar("us-east-1")).String(),
|
UseSSL: GetCfgWithDefault(ctx, "blazing.file.oss.useSSL", g.NewVar(false)).Bool(),
|
||||||
},
|
BucketName: GetCfgWithDefault(ctx, "blazing.file.oss.bucketName", g.NewVar("blazing")).String(),
|
||||||
},
|
Location: GetCfgWithDefault(ctx, "blazing.file.oss.location", g.NewVar("us-east-1")).String(),
|
||||||
}
|
},
|
||||||
|
},
|
||||||
return config
|
}
|
||||||
}
|
|
||||||
|
return config
|
||||||
// qiniu 七牛云配置
|
}
|
||||||
type qiniu struct {
|
|
||||||
AccessKey string `json:"ak"`
|
func hasDebugArg() bool {
|
||||||
SecretKey string `json:"sk"`
|
for _, arg := range os.Args[1:] {
|
||||||
Bucket string `json:"bucket"`
|
if arg == "-debug" || arg == "--debug" {
|
||||||
CDN string `json:"cdn"`
|
return true
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(arg, "-debug=") || strings.HasPrefix(arg, "--debug=") {
|
||||||
// Config config
|
value := strings.TrimSpace(strings.SplitN(arg, "=", 2)[1])
|
||||||
var Config = newConfig()
|
return value != "" && value != "0" && !strings.EqualFold(value, "false")
|
||||||
|
}
|
||||||
// GetCfgWithDefault get config with default value
|
}
|
||||||
func GetCfgWithDefault(ctx g.Ctx, key string, defaultValue *g.Var) *g.Var {
|
return false
|
||||||
value, err := g.Cfg().GetWithEnv(ctx, key)
|
}
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
// qiniu 七牛云配置
|
||||||
}
|
type qiniu struct {
|
||||||
if value.IsEmpty() || value.IsNil() {
|
AccessKey string `json:"ak"`
|
||||||
return defaultValue
|
SecretKey string `json:"sk"`
|
||||||
}
|
Bucket string `json:"bucket"`
|
||||||
return value
|
CDN string `json:"cdn"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Config config
|
||||||
|
var Config = newConfig()
|
||||||
|
|
||||||
|
// GetCfgWithDefault get config with default value
|
||||||
|
func GetCfgWithDefault(ctx g.Ctx, key string, defaultValue *g.Var) *g.Var {
|
||||||
|
value, err := g.Cfg().GetWithEnv(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
if value.IsEmpty() || value.IsNil() {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cool
|
|||||||
import (
|
import (
|
||||||
_ "blazing/contrib/drivers/pgsql"
|
_ "blazing/contrib/drivers/pgsql"
|
||||||
"blazing/cool/cooldb"
|
"blazing/cool/cooldb"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/encoding/gjson"
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
@@ -10,6 +11,11 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
autoMigrateMu sync.Mutex
|
||||||
|
autoMigrateModels []IModel
|
||||||
|
)
|
||||||
|
|
||||||
// 初始化数据库连接供gorm使用
|
// 初始化数据库连接供gorm使用
|
||||||
func InitDB(group string) (*gorm.DB, error) {
|
func InitDB(group string) (*gorm.DB, error) {
|
||||||
// var ctx context.Context
|
// var ctx context.Context
|
||||||
@@ -54,9 +60,39 @@ func getDBbyModel(model IModel) *gorm.DB {
|
|||||||
|
|
||||||
// 根据entity结构体创建表
|
// 根据entity结构体创建表
|
||||||
func CreateTable(model IModel) error {
|
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)
|
db := getDBbyModel(model)
|
||||||
return db.AutoMigrate(model)
|
if err := db.AutoMigrate(model); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type EffectArg struct {
|
|||||||
SideEffect []struct {
|
SideEffect []struct {
|
||||||
ID int `json:"ID"`
|
ID int `json:"ID"`
|
||||||
SideEffectArgcount int `json:"SideEffectArgcount"`
|
SideEffectArgcount int `json:"SideEffectArgcount"`
|
||||||
SideEffectArg string `json:"SideEffectArg,omitempty"`
|
SideEffectArg rawFlexibleString `json:"SideEffectArg,omitempty"`
|
||||||
} `json:"SideEffect"`
|
} `json:"SideEffect"`
|
||||||
} `json:"SideEffects"`
|
} `json:"SideEffects"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
_ "blazing/common/data/xmlres/packed"
|
_ "blazing/common/data/xmlres/packed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ECUST-XX/xml"
|
"github.com/ECUST-XX/xml"
|
||||||
"github.com/gogf/gf/v2/os/gres"
|
"github.com/gogf/gf/v2/os/gres"
|
||||||
@@ -14,22 +14,36 @@ import (
|
|||||||
|
|
||||||
var path string
|
var path string
|
||||||
|
|
||||||
|
func readConfigContent(path string) []byte {
|
||||||
|
return gres.GetContent(path)
|
||||||
|
}
|
||||||
|
|
||||||
func getXml[T any](path string) T {
|
func getXml[T any](path string) T {
|
||||||
|
|
||||||
// 解析XML到结构体
|
// 解析XML到结构体
|
||||||
var xmls T
|
var xmls T
|
||||||
|
|
||||||
t1 := gres.GetContent(path)
|
t1 := readConfigContent(path)
|
||||||
xml.Unmarshal(t1, &xmls)
|
xml.Unmarshal(t1, &xmls)
|
||||||
|
|
||||||
return xmls
|
return xmls
|
||||||
}
|
}
|
||||||
func getJson[T any](path string) T {
|
func getJson[T any](path string) T {
|
||||||
|
|
||||||
// 解析XML到结构体
|
// 解析JSON到结构体
|
||||||
var xmls T
|
var xmls T
|
||||||
t1 := gres.GetContent(path)
|
t1 := readConfigContent(path)
|
||||||
json.Unmarshal(t1, &xmls)
|
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
|
return xmls
|
||||||
}
|
}
|
||||||
@@ -58,8 +72,6 @@ var (
|
|||||||
|
|
||||||
func Initfile() {
|
func Initfile() {
|
||||||
//gres.Dump()
|
//gres.Dump()
|
||||||
path1, _ := os.Getwd()
|
|
||||||
path = path1 + "/public/config/"
|
|
||||||
path = "config/"
|
path = "config/"
|
||||||
MapConfig = getXml[Maps](path + "210.xml")
|
MapConfig = getXml[Maps](path + "210.xml")
|
||||||
|
|
||||||
@@ -87,10 +99,10 @@ func Initfile() {
|
|||||||
return gconv.Int(m.ProductID)
|
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))
|
SkillMap = make(map[int]Move, len(skillConfig.MovesTbl.Moves.Move))
|
||||||
for _, v := range Skill.Moves {
|
for _, v := range skillConfig.MovesTbl.Moves.Move {
|
||||||
v.SideEffectS = ParseSideEffectArgs(v.SideEffect)
|
v.SideEffectS = ParseSideEffectArgs(v.SideEffect)
|
||||||
v.SideEffectArgS = ParseSideEffectArgs(v.SideEffectArg)
|
v.SideEffectArgS = ParseSideEffectArgs(v.SideEffectArg)
|
||||||
SkillMap[v.ID] = v
|
SkillMap[v.ID] = v
|
||||||
|
|||||||
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,7 @@
|
|||||||
package xmlres
|
package xmlres
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -33,52 +34,156 @@ type MovesTbl struct {
|
|||||||
Moves []Move `xml:"Moves>Move"`
|
Moves []Move `xml:"Moves>Move"`
|
||||||
EFF []SideEffect `xml:"SideEffects>SideEffect"`
|
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 {
|
type MovesMap struct {
|
||||||
XMLName xml.Name `xml:"MovesTbl"`
|
XMLName xml.Name `xml:"MovesTbl"`
|
||||||
Moves map[int]Move
|
Moves map[int]Move
|
||||||
EFF []SideEffect `xml:"SideEffects>SideEffect"`
|
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 定义单个技能的结构
|
// Move 定义单个技能的结构
|
||||||
type Move struct {
|
type Move struct {
|
||||||
ID int `xml:"ID,attr"`
|
ID int `xml:"ID,attr" json:"ID"`
|
||||||
Name string `xml:"Name,attr"`
|
Name string `xml:"Name,attr" json:"Name"`
|
||||||
|
|
||||||
Category int `xml:"Category,attr"` //属性
|
Category int `xml:"Category,attr" json:"Category"` //属性
|
||||||
Type int `xml:"Type,attr"` //类型
|
Type int `xml:"Type,attr" json:"Type"` //类型
|
||||||
Power int `xml:"Power,attr"` //威力
|
Power int `xml:"Power,attr" json:"Power"` //威力
|
||||||
MaxPP int `xml:"MaxPP,attr"` //最大PP
|
MaxPP int `xml:"MaxPP,attr" json:"MaxPP"` //最大PP
|
||||||
Accuracy int `xml:"Accuracy,attr"` //命中率
|
Accuracy int `xml:"Accuracy,attr" json:"Accuracy"` //命中率
|
||||||
CritRate int `xml:"CritRate,attr,omitempty"` //暴击率
|
CritRate int `xml:"CritRate,attr,omitempty" json:"CritRate,omitempty"` //暴击率
|
||||||
Priority int `xml:"Priority,attr,omitempty"` //优先级
|
Priority int `xml:"Priority,attr,omitempty" json:"Priority,omitempty"` //优先级
|
||||||
MustHit int `xml:"MustHit,attr,omitempty"` //是否必中
|
MustHit int `xml:"MustHit,attr,omitempty" json:"MustHit,omitempty"` //是否必中
|
||||||
SwapElemType int `xml:"SwapElemType,attr,omitempty"` //技能交换属性
|
SwapElemType int `xml:"SwapElemType,attr,omitempty" json:"SwapElemType,omitempty"` //技能交换属性
|
||||||
CopyElemType int `xml:"CopyElemType,attr,omitempty"` // 技能复制属性
|
CopyElemType int `xml:"CopyElemType,attr,omitempty" json:"CopyElemType,omitempty"` // 技能复制属性
|
||||||
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty"` // 先出手时必定致命一击
|
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty" json:"CritAtkFirst,omitempty"` // 先出手时必定致命一击
|
||||||
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty"` //后出手时必定致命一击
|
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty" json:"CritAtkSecond,omitempty"` //后出手时必定致命一击
|
||||||
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty"` //自身体力低于一半时必定致命一击
|
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty" json:"CritSelfHalfHp,omitempty"` //自身体力低于一半时必定致命一击
|
||||||
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty"` //对方体力低于一半时必定致命一击
|
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty" json:"CritFoeHalfHp,omitempty"` //对方体力低于一半时必定致命一击
|
||||||
DmgBindLv int `xml:"DmgBindLv,attr,omitempty"` //使对方受到的伤害值等于自身的等级
|
DmgBindLv int `xml:"DmgBindLv,attr,omitempty" json:"DmgBindLv,omitempty"` //使对方受到的伤害值等于自身的等级
|
||||||
PwrBindDv int `xml:"PwrBindDv,attr,omitempty"` //威力(power)取决于自身的潜力(个体值)
|
PwrBindDv int `xml:"PwrBindDv,attr,omitempty" json:"PwrBindDv,omitempty"` //威力(power)取决于自身的潜力(个体值)
|
||||||
PwrDouble int `xml:"PwrDouble,attr,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
|
PwrDouble int `xml:"PwrDouble,attr,omitempty" json:"PwrDouble,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
|
||||||
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty"` //使对方受到的伤害值等于自身的体力值
|
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty" json:"DmgBindHpDv,omitempty"` //使对方受到的伤害值等于自身的体力值
|
||||||
SideEffect string `xml:"SideEffect,attr,omitempty"`
|
SideEffect string `xml:"SideEffect,attr,omitempty" json:"SideEffect,omitempty"`
|
||||||
SideEffectArg string `xml:"SideEffectArg,attr,omitempty"`
|
SideEffectArg string `xml:"SideEffectArg,attr,omitempty" json:"SideEffectArg,omitempty"`
|
||||||
SideEffectS []int
|
SideEffectS []int
|
||||||
SideEffectArgS []int
|
SideEffectArgS []int
|
||||||
AtkNum int `xml:"AtkNum,attr,omitempty"`
|
AtkNum int `xml:"AtkNum,attr,omitempty" json:"AtkNum,omitempty"`
|
||||||
AtkType int `xml:"AtkType,attr,omitempty"` // 0:所有人 1:仅己方 2:仅对方 3:仅自己
|
AtkType int `xml:"AtkType,attr,omitempty" json:"AtkType,omitempty"` // 0:所有人 1:仅己方 2:仅对方 3:仅自己
|
||||||
Url string `xml:"Url,attr,omitempty"`
|
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 {
|
type SideEffect struct {
|
||||||
ID int `xml:"ID,attr"`
|
ID int `xml:"ID,attr" json:"ID"`
|
||||||
Help string `xml:"help,attr"`
|
Help string `xml:"help,attr" json:"help"`
|
||||||
Des string `xml:"des,attr"`
|
Des string `xml:"des,attr" json:"des"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadHTTPFile 通过HTTP GET请求获取远程文件内容
|
// ReadHTTPFile 通过HTTP GET请求获取远程文件内容
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"blazing/logic/service/fight/pvp"
|
"blazing/logic/service/fight/pvp"
|
||||||
"blazing/logic/service/fight/pvpwire"
|
"blazing/logic/service/fight/pvpwire"
|
||||||
|
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,7 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ListenFunc 监听函数
|
// ListenFunc 监听函数
|
||||||
// ListenFunc 改造后的 Redis PubSub 监听函数,支持自动重连和心跳保活
|
// ListenFunc 改造后的 Redis PubSub 监听函数,支持自动重连。
|
||||||
|
// 注意:PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
|
||||||
func ListenFunc(ctx g.Ctx) {
|
func ListenFunc(ctx g.Ctx) {
|
||||||
if !cool.IsRedisMode {
|
if !cool.IsRedisMode {
|
||||||
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
||||||
@@ -24,9 +24,8 @@ func ListenFunc(ctx g.Ctx) {
|
|||||||
|
|
||||||
// 定义常量配置
|
// 定义常量配置
|
||||||
const (
|
const (
|
||||||
subscribeTopic = "cool:func" // 订阅的主题
|
subscribeTopic = "cool:func" // 订阅的主题
|
||||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||||
heartbeatInterval = 30 * time.Second // 心跳保活间隔
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 外层循环:负责连接断开后的整体重连
|
// 外层循环:负责连接断开后的整体重连
|
||||||
@@ -47,47 +46,25 @@ func ListenFunc(ctx g.Ctx) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 启动心跳保活协程,防止连接因空闲被断开
|
// 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. 订阅主题
|
|
||||||
_, err = conn.Do(ctx, "subscribe", subscribeTopic)
|
_, err = conn.Do(ctx, "subscribe", subscribeTopic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", subscribeTopic, "error", err)
|
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", subscribeTopic, "error", err)
|
||||||
heartbeatCancel() // 关闭心跳协程
|
|
||||||
_ = conn.Close(ctx)
|
_ = conn.Close(ctx)
|
||||||
time.Sleep(retryDelay)
|
time.Sleep(retryDelay)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", subscribeTopic)
|
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", subscribeTopic)
|
||||||
_, err = conn.Do(ctx, "subscribe", "sun:join") //加入队列
|
_, 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
|
connError := false
|
||||||
for !connError {
|
for !connError {
|
||||||
select {
|
select {
|
||||||
@@ -130,15 +107,15 @@ func ListenFunc(ctx g.Ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 清理资源,准备重连
|
// 4. 清理资源,准备重连
|
||||||
heartbeatCancel() // 关闭心跳协程
|
|
||||||
_ = conn.Close(ctx) // 关闭当前连接
|
_ = conn.Close(ctx) // 关闭当前连接
|
||||||
// Logger.Warn(ctx, "Redis 连接异常,准备重连", "retry_after", retryDelay)
|
cool.Logger.Info(ctx, "Redis 订阅连接异常,准备重连", "retry_after", retryDelay)
|
||||||
time.Sleep(retryDelay)
|
time.Sleep(retryDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenFight 完全对齐 ListenFunc 写法,修复收不到消息问题
|
// ListenFight 完全对齐 ListenFunc 写法,修复收不到消息问题。
|
||||||
|
// 注意:PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
|
||||||
func ListenFight(ctx g.Ctx) {
|
func ListenFight(ctx g.Ctx) {
|
||||||
if !cool.IsRedisMode {
|
if !cool.IsRedisMode {
|
||||||
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
|
||||||
@@ -146,8 +123,7 @@ func ListenFight(ctx g.Ctx) {
|
|||||||
|
|
||||||
// 定义常量配置(对齐 ListenFunc 风格)
|
// 定义常量配置(对齐 ListenFunc 风格)
|
||||||
const (
|
const (
|
||||||
retryDelay = 10 * time.Second // 连接失败重试间隔
|
retryDelay = 10 * time.Second // 连接失败重试间隔
|
||||||
heartbeatInterval = 30 * time.Second // 心跳保活间隔
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 提前拼接订阅主题(避免重复拼接,便于日志打印)
|
// 提前拼接订阅主题(避免重复拼接,便于日志打印)
|
||||||
@@ -176,35 +152,7 @@ func ListenFight(ctx g.Ctx) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 启动心跳保活协程(完全对齐 ListenFunc 逻辑)
|
// 2. 订阅主题(对齐 ListenFunc 的错误处理,替换 panic 为优雅重连)
|
||||||
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 心跳发送成功,连接正常")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 3. 订阅主题(对齐 ListenFunc 的错误处理,替换 panic 为优雅重连)
|
|
||||||
subscribeTopics := []string{startTopic, pvpServerTopic}
|
subscribeTopics := []string{startTopic, pvpServerTopic}
|
||||||
if cool.Config.GameOnlineID == pvp.CoordinatorOnlineID {
|
if cool.Config.GameOnlineID == pvp.CoordinatorOnlineID {
|
||||||
subscribeTopics = append(subscribeTopics, pvpCoordinatorTopic)
|
subscribeTopics = append(subscribeTopics, pvpCoordinatorTopic)
|
||||||
@@ -214,7 +162,6 @@ func ListenFight(ctx g.Ctx) {
|
|||||||
_, err = conn.Do(ctx, "subscribe", topic)
|
_, err = conn.Do(ctx, "subscribe", topic)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", topic, "error", err)
|
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", topic, "error", err)
|
||||||
heartbeatCancel()
|
|
||||||
_ = conn.Close(ctx)
|
_ = conn.Close(ctx)
|
||||||
time.Sleep(retryDelay)
|
time.Sleep(retryDelay)
|
||||||
subscribeFailed = true
|
subscribeFailed = true
|
||||||
@@ -240,7 +187,7 @@ func ListenFight(ctx g.Ctx) {
|
|||||||
// 打印监听提示(保留原有日志)
|
// 打印监听提示(保留原有日志)
|
||||||
fmt.Println("监听战斗", startTopic)
|
fmt.Println("监听战斗", startTopic)
|
||||||
|
|
||||||
// 4. 循环接收消息(完全对齐 ListenFunc 逻辑)
|
// 3. 循环接收消息(完全对齐 ListenFunc 逻辑)
|
||||||
connError := false
|
connError := false
|
||||||
for !connError {
|
for !connError {
|
||||||
select {
|
select {
|
||||||
@@ -282,9 +229,9 @@ func ListenFight(ctx g.Ctx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 清理资源,准备重连(完全对齐 ListenFunc)
|
// 4. 清理资源,准备重连(完全对齐 ListenFunc)
|
||||||
heartbeatCancel() // 关闭心跳协程
|
|
||||||
_ = conn.Close(ctx) // 关闭当前连接
|
_ = conn.Close(ctx) // 关闭当前连接
|
||||||
|
cool.Logger.Info(ctx, "Redis 战斗订阅连接异常,准备重连", "retry_after", retryDelay)
|
||||||
time.Sleep(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())
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
config "blazing/modules/config/service"
|
config "blazing/modules/config/service"
|
||||||
@@ -19,6 +21,19 @@ import (
|
|||||||
type ServerHandler struct{}
|
type ServerHandler struct{}
|
||||||
|
|
||||||
const kickForwardTimeout = 3 * time.Second
|
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 {
|
func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
|
||||||
@@ -57,6 +72,11 @@ func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
|
|||||||
cool.DeleteClientOnly(useid2)
|
cool.DeleteClientOnly(useid2)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if isDisconnectedLogicReverseClientError(callErr) {
|
||||||
|
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||||
|
cool.DeleteClientOnly(useid2)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 仍在线则返回失败,不按成功处理
|
// 仍在线则返回失败,不按成功处理
|
||||||
return callErr
|
return callErr
|
||||||
@@ -79,28 +99,22 @@ func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
|
|||||||
// 注册logic服务器
|
// 注册logic服务器
|
||||||
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error {
|
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error {
|
||||||
fmt.Println("注册logic服务器", id, port)
|
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)
|
func (*ServerHandler) MatchJoinOrUpdate(_ context.Context, payload PVPMatchJoinPayload) error {
|
||||||
if ok && aa != nil { //如果已经存在且这个端口已经被存过
|
return DefaultPVPMatchCoordinator().JoinOrUpdate(payload)
|
||||||
aa.QuitSelf(0)
|
}
|
||||||
}
|
|
||||||
cool.AddClient(100000*id+port, &revClient)
|
|
||||||
|
|
||||||
//Refurh()
|
func (*ServerHandler) MatchCancel(_ context.Context, userID uint32) error {
|
||||||
|
DefaultPVPMatchCoordinator().Cancel(userID)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CServer() *jsonrpc.RPCServer {
|
func CServer() *jsonrpc.RPCServer {
|
||||||
// create a new server instance
|
// create a new server instance
|
||||||
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[cool.ClientHandler](""))
|
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClientSetup[cool.ClientHandler]("", setupLogicReverseClient))
|
||||||
|
|
||||||
rpcServer.Register("", &ServerHandler{})
|
rpcServer.Register("", &ServerHandler{})
|
||||||
|
|
||||||
@@ -111,28 +125,35 @@ func CServer() *jsonrpc.RPCServer {
|
|||||||
var closer jsonrpc.ClientCloser
|
var closer jsonrpc.ClientCloser
|
||||||
|
|
||||||
func StartClient(id, port uint32, callback any) *struct {
|
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"
|
//cool.Config.File.Domain = "127.0.0.1"
|
||||||
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
|
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(),
|
closer1, err := jsonrpc.NewMergeClient(context.Background(),
|
||||||
rpcaddr, "", []interface{}{
|
rpcaddr, "", []interface{}{
|
||||||
&RPCClient,
|
&RPCClient,
|
||||||
}, nil, jsonrpc.WithClientHandler("", callback),
|
}, nil, jsonrpc.WithClientHandler("", callback),
|
||||||
jsonrpc.WithReconnFun(func() { RPCClient.RegisterLogic(id, port) }),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create client: %v", err)
|
log.Fatalf("Failed to create client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//if port != 0 { //注册logic
|
|
||||||
defer RPCClient.RegisterLogic(id, port)
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
closer = closer1
|
closer = closer1
|
||||||
|
|
||||||
return &RPCClient
|
return &RPCClient
|
||||||
@@ -140,10 +161,55 @@ func StartClient(id, port uint32, callback any) *struct {
|
|||||||
|
|
||||||
// Setup RPCClient with reverse call handler
|
// Setup RPCClient with reverse call handler
|
||||||
var RPCClient struct {
|
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 //用户登录事件
|
// UserLogin func(int32, int32) error //用户登录事件
|
||||||
// UserLogout 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 := 100000*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(100000*id+port, &revClient)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ func (s *Server) OnClose(c gnet.Conn, err error) (action gnet.Action) {
|
|||||||
if v != nil {
|
if v != nil {
|
||||||
v.Close()
|
v.Close()
|
||||||
if v.Player != nil {
|
if v.Player != nil {
|
||||||
v.Player.Save() //保存玩家数据
|
v.Player.SaveOnDisconnect() //保存玩家数据
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -121,7 +121,23 @@ func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ws := c.Context().(*player.ClientData).Wsmsg
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
ws := client.Wsmsg
|
||||||
if ws.Tcp {
|
if ws.Tcp {
|
||||||
return s.handleTCP(c)
|
return s.handleTCP(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ var ErrorCodes = enum.New[struct {
|
|||||||
ErrPokemonLevelTooLow ErrorCode `enum:"13007"`
|
ErrPokemonLevelTooLow ErrorCode `enum:"13007"`
|
||||||
// 不能展示背包里的精灵!
|
// 不能展示背包里的精灵!
|
||||||
ErrCannotShowBagPokemon ErrorCode `enum:"13017"`
|
ErrCannotShowBagPokemon ErrorCode `enum:"13017"`
|
||||||
|
// 基地展示精灵数量已达上限!
|
||||||
|
ErrRoomShowPetLimit ErrorCode `enum:"13019"`
|
||||||
|
// 该精灵不在仓库中,无法设为基地展示!
|
||||||
|
ErrPetNotInWarehouse ErrorCode `enum:"13021"`
|
||||||
// 你今天已经被吃掉过一回了,明天再来吧!
|
// 你今天已经被吃掉过一回了,明天再来吧!
|
||||||
ErrAlreadyEatenToday ErrorCode `enum:"17018"`
|
ErrAlreadyEatenToday ErrorCode `enum:"17018"`
|
||||||
// 该道具已经在使用中,无法重复使用。
|
// 该道具已经在使用中,无法重复使用。
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ func websocketClient(ctx context.Context, addr string, namespace string, outs []
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) setupRequestChan() chan clientRequest {
|
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) {
|
c.doRequest = func(ctx context.Context, cr clientRequest) (clientResponse, error) {
|
||||||
select {
|
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.
|
// WithReverseClient will allow extracting reverse client on **WEBSOCKET** calls.
|
||||||
// RP is a proxy-struct type, much like the one passed to NewClient.
|
// RP is a proxy-struct type, much like the one passed to NewClient.
|
||||||
func WithReverseClient[RP any](namespace string) ServerOption {
|
func WithReverseClient[RP any](namespace string) ServerOption {
|
||||||
return func(c *ServerConfig) {
|
return func(c *ServerConfig) {
|
||||||
c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) {
|
c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) {
|
||||||
cl := client{
|
return buildReverseClient[RP](c, ctx, conn, namespace, nil)
|
||||||
namespace: namespace,
|
}
|
||||||
paramEncoders: map[reflect.Type]ParamEncoder{},
|
}
|
||||||
methodNameFormatter: c.methodNameFormatter,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// todo test that everything is closing correctly
|
// WithReverseClientSetup behaves like WithReverseClient, and also runs onConnect
|
||||||
cl.exiting = conn.exiting
|
// once the reverse client has been created for the websocket connection.
|
||||||
|
func WithReverseClientSetup[RP any](namespace string, onConnect func(context.Context, RP) error) ServerOption {
|
||||||
requests := cl.setupRequestChan()
|
return func(c *ServerConfig) {
|
||||||
conn.requests = requests
|
c.reverseClientBuilder = func(ctx context.Context, conn *wsConn) (context.Context, error) {
|
||||||
|
return buildReverseClient[RP](c, ctx, conn, namespace, onConnect)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,16 @@ func GetConnectionType(ctx context.Context) ConnectionType {
|
|||||||
return ConnectionTypeUnknown
|
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
|
// RPCServer provides a jsonrpc 2.0 http server handler
|
||||||
type RPCServer struct {
|
type RPCServer struct {
|
||||||
*handler
|
*handler
|
||||||
@@ -75,6 +85,8 @@ var upgrader = websocket.Upgrader{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RPCServer) handleWS(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
func (s *RPCServer) handleWS(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx = context.WithValue(ctx, httpRequestCtxKey{}, r)
|
||||||
|
|
||||||
// TODO: allow setting
|
// TODO: allow setting
|
||||||
// (note that we still are mostly covered by jwt tokens)
|
// (note that we still are mostly covered by jwt tokens)
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
@@ -181,3 +193,5 @@ func (s *RPCServer) AliasMethod(alias, original string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ error = &JSONRPCError{}
|
var _ error = &JSONRPCError{}
|
||||||
|
|
||||||
|
type httpRequestCtxKey struct{}
|
||||||
|
|||||||
@@ -661,7 +661,9 @@ func (c *wsConn) tryReconnect(ctx context.Context) bool {
|
|||||||
c.writeLk.Unlock()
|
c.writeLk.Unlock()
|
||||||
|
|
||||||
go c.nextMessage()
|
go c.nextMessage()
|
||||||
c.reconfun()
|
if c.reconfun != nil {
|
||||||
|
go c.reconfun()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -2,33 +2,4 @@ module github.com/zmexing/go-sensitive-word
|
|||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require github.com/orcaman/concurrent-map/v2 v2.0.1
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
|
||||||
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
|
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=
|
|
||||||
|
|||||||
81
docs/peak-tianxuan-current-design-2026-04-25.md
Normal file
81
docs/peak-tianxuan-current-design-2026-04-25.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# 巅峰之战天选池当前逻辑说明
|
||||||
|
|
||||||
|
更新时间:2026-04-26
|
||||||
|
|
||||||
|
## 1. 入口职责
|
||||||
|
|
||||||
|
- `point_1`:巅峰之战主页面,展示当前玩家已配置的天选精灵。
|
||||||
|
- `point_2`:天选池投票页面,只保存玩家本周投票。
|
||||||
|
- `point_3`:天选精灵配置页面,配置玩家自己的天选精灵属性。
|
||||||
|
|
||||||
|
## 2. 投票数据
|
||||||
|
|
||||||
|
投票表是 `config_peak_tianxuan_vote`。
|
||||||
|
|
||||||
|
字段口径:
|
||||||
|
|
||||||
|
- `week_index`:日期周,格式为 ISO 年周,例如 `202617`。
|
||||||
|
- `player_id`:投票玩家。
|
||||||
|
- `pet_id`:投票精灵。
|
||||||
|
|
||||||
|
约束口径:
|
||||||
|
|
||||||
|
- 每个玩家每个日期周只能投 1 只精灵。
|
||||||
|
- 同周重复投票时更新原记录。
|
||||||
|
- 票数统计按 `week_index + pet_id` 聚合。
|
||||||
|
|
||||||
|
## 3. 投票池
|
||||||
|
|
||||||
|
`point_2` 的候选池来自乱斗精灵池 `config_boss_melee`,按 `mon_id` 去重。
|
||||||
|
|
||||||
|
当前不再因为上周出现过就从候选池移除。上周投票结果只作为展示和后续配置参考。
|
||||||
|
|
||||||
|
页面会展示:
|
||||||
|
|
||||||
|
- 当前日期周投票池。
|
||||||
|
- 上一日期周投票统计。
|
||||||
|
- 当前玩家本周已投的精灵。
|
||||||
|
|
||||||
|
## 4. 天选配置
|
||||||
|
|
||||||
|
天选配置表是 `config_peak_tianxuan`。
|
||||||
|
|
||||||
|
这张表现在表示“玩家额外拥有的天选精灵配置”,参考 `player_pet` 的归属思路,不再按周保存。
|
||||||
|
|
||||||
|
核心字段:
|
||||||
|
|
||||||
|
- `player_id`
|
||||||
|
- `pet_id`
|
||||||
|
- `display_order`
|
||||||
|
- `preset_name`
|
||||||
|
- `level`
|
||||||
|
- `nature`
|
||||||
|
- `hp`
|
||||||
|
- `max_hp`
|
||||||
|
- `attack`
|
||||||
|
- `defence`
|
||||||
|
- `sp_attack`
|
||||||
|
- `sp_defence`
|
||||||
|
- `speed`
|
||||||
|
- `skin_id`
|
||||||
|
- `effect_ids`
|
||||||
|
- `skill_ids`
|
||||||
|
- `remark`
|
||||||
|
- `is_enable`
|
||||||
|
|
||||||
|
约束口径:
|
||||||
|
|
||||||
|
- 同一玩家不能重复配置同一只天选精灵。
|
||||||
|
- 不再校验“本周不能和上周重复”。
|
||||||
|
- 不再要求 `week_index`。
|
||||||
|
|
||||||
|
## 5. BP 使用
|
||||||
|
|
||||||
|
BP 不直接读取投票记录。
|
||||||
|
|
||||||
|
进入 BP 时读取双方各自的 `config_peak_tianxuan` 配置:
|
||||||
|
|
||||||
|
- 我方看到自己的天选配置。
|
||||||
|
- 对方看到对方自己的天选配置。
|
||||||
|
|
||||||
|
投票结果后续可用于决定哪些精灵开放给玩家配置,但配置本身是玩家维度的数据。
|
||||||
42
docs/peak-tianxuan-vote-design-2026-04-25.md
Normal file
42
docs/peak-tianxuan-vote-design-2026-04-25.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 巅峰之战天选池投票设计
|
||||||
|
|
||||||
|
## 规则补全
|
||||||
|
|
||||||
|
- 参与规则
|
||||||
|
- 白银及以下:至少 1 只 100 级以上精灵,进行 `1v1`
|
||||||
|
- 黄金、铂金、紫晶:至少 3 只 100 级以上精灵,进行 `3v3`
|
||||||
|
- 钻石及以上:至少 6 只 100 级以上精灵,进行 `6v6`
|
||||||
|
- 段位规则
|
||||||
|
- 初始段位为青铜,初始分 `800`
|
||||||
|
- `1000` 白银,`1200` 黄金,`1500` 铂金,`1800` 紫晶,`2100` 钻石,`3000` 大师,`4000` 王者
|
||||||
|
- 王者榜前 100 自动晋升至尊王者
|
||||||
|
- 天选池规则
|
||||||
|
- 每周开放一组“天选池”候选精灵
|
||||||
|
- 玩家在进入巅峰匹配前,先从本周天选池中投票选择自己的“天选备选”
|
||||||
|
- 白银及以下可选 `1` 只,紫晶及以下可选 `2` 只,钻石及以上可选 `3` 只
|
||||||
|
- 这些投票结果跟随匹配快照一起进入后续 BP 流程
|
||||||
|
- BP 规则
|
||||||
|
- 钻石以下默认无 Ban/Pick 强制要求,但前后端结构统一预留天选池字段
|
||||||
|
- 钻石及以上进入正式 BP
|
||||||
|
- BP 展示区应包含:
|
||||||
|
- 出战备战精灵
|
||||||
|
- 天选精灵
|
||||||
|
- 双方互 Ban / Pick
|
||||||
|
- BP 完成后再进入正式对战
|
||||||
|
- 奖励规则
|
||||||
|
- 本次仅补结构与文档位,结算奖励、赛季奖励、周奖励仍按后续配置表落地
|
||||||
|
|
||||||
|
## 本次后端结构预留
|
||||||
|
|
||||||
|
- `2458` 巅峰加入请求增加 `TianxuanPetIDs`
|
||||||
|
- 匹配快照 `QueuePlayerSnapshot` 增加 `TianxuanPetIDs`
|
||||||
|
- `CrossServerBanPickStartOutboundInfo` 增加:
|
||||||
|
- `TianxuanSelectableCount`
|
||||||
|
- `MyTianxuanPets`
|
||||||
|
- `OpponentTianxuanPets`
|
||||||
|
|
||||||
|
## 当前实现边界
|
||||||
|
|
||||||
|
- 本次先完成“开始界面投票 + 匹配快照透传 + BP 启动数据预留”
|
||||||
|
- 不直接改现有 `banpick.vue`
|
||||||
|
- 天选精灵的正式上场生成、战斗侧实体化和完整 BP 消费,交给后续 BP 页面与战斗链路接入
|
||||||
257
docs/pvp-cross-server-battle-cmd-data-design-2026-04-27.md
Normal file
257
docs/pvp-cross-server-battle-cmd-data-design-2026-04-27.md
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
# PVP 跨服战斗消息 `cmd/data` 转发改造说明
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
巅峰赛跨服战斗原本已经有 Redis pub/sub 通道,但战斗中的客户端操作转发仍然偏间接:
|
||||||
|
|
||||||
|
1. 本服 controller 先把客户端请求翻译成 `FightI` 调用。
|
||||||
|
2. `RemoteFightProxy` 再把 `FightI` 调用翻译成 `battle_command`。
|
||||||
|
3. 宿主服收到 `battle_command` 后,再翻译回 `FightI` 调用。
|
||||||
|
|
||||||
|
这样一来,跨服战斗链路和本地战斗链路之间多了一层“动作语义映射”,调试时不容易直接看到前端到底发了哪个 cmd、带了什么 data。
|
||||||
|
|
||||||
|
本次改造只处理“战斗中的消息转发”,不改匹配/加入队列逻辑。
|
||||||
|
|
||||||
|
## 保持不变的部分
|
||||||
|
|
||||||
|
- 巅峰赛加入和取消匹配仍然走原有 RPC:
|
||||||
|
- `MatchJoinOrUpdate`
|
||||||
|
- `MatchCancel`
|
||||||
|
- Ban/Pick、MatchFound、SessionClose 等 PVP 服务内消息仍兼容旧外层格式。
|
||||||
|
- `battle_command` 旧语义消息仍保留,作为兼容兜底,不强制一次性切掉。
|
||||||
|
|
||||||
|
对应原因:
|
||||||
|
|
||||||
|
- 匹配加入需要同步返回结果,RPC 更直接。
|
||||||
|
- 现网如果已有旧 envelope 或旧 battle command 发送方,不能直接断。
|
||||||
|
|
||||||
|
## 改造目标
|
||||||
|
|
||||||
|
把跨服战斗中的“客户端操作转发”改成直接发送:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"cmd": "battle_client_command",
|
||||||
|
"data": {
|
||||||
|
"sessionId": "xsvr-...",
|
||||||
|
"userId": 10001,
|
||||||
|
"cmd": 2405,
|
||||||
|
"data": {
|
||||||
|
"SkillId": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
核心含义:
|
||||||
|
|
||||||
|
- 外层 `cmd`:PVP 内部 Redis 消息类型。
|
||||||
|
- 内层 `data.cmd`:真实客户端战斗协议 cmd。
|
||||||
|
- 内层 `data.data`:该 cmd 对应的 JSON 业务字段。
|
||||||
|
|
||||||
|
这样宿主服拿到消息后,可以直接按真实战斗 cmd 处理,而不是先理解一层额外的“跨服动作协议”。
|
||||||
|
|
||||||
|
## 本次代码落点
|
||||||
|
|
||||||
|
### 1. 新增 `cmd/data` envelope 能力
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
- `logic/service/fight/pvpwire/types.go`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
|
||||||
|
- `Envelope` 新增:
|
||||||
|
- `Cmd string`
|
||||||
|
- `Data json.RawMessage`
|
||||||
|
- 保留旧字段:
|
||||||
|
- `Type string`
|
||||||
|
- `Body []byte`
|
||||||
|
- 新增辅助方法:
|
||||||
|
- `NewEnvelope`
|
||||||
|
- `MessageCmd`
|
||||||
|
- `MessageData`
|
||||||
|
|
||||||
|
作用:
|
||||||
|
|
||||||
|
- 新消息可以直接用 `cmd/data`。
|
||||||
|
- 旧消息仍可通过 `type/body` 解析。
|
||||||
|
|
||||||
|
### 2. controller 层遇到远端跨服战斗时,直接转发原始战斗 cmd
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
- `logic/controller/fight_unified.go`
|
||||||
|
- `logic/controller/fight_base.go`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
|
||||||
|
- 在 `fight_unified.go` 增加 `relayRemoteFightCommand`。
|
||||||
|
- 当 `c.FightC` 是 `*pvp.RemoteFightProxy` 时,不再走本地 `dispatchFightActionEnvelope` 分发,而是直接转发当前请求的:
|
||||||
|
- `data.Head.CMD`
|
||||||
|
- 当前请求结构体 `data`
|
||||||
|
|
||||||
|
已接入的战斗入口包括:
|
||||||
|
|
||||||
|
- `2404` 准备
|
||||||
|
- `7556` 旧组队准备
|
||||||
|
- `2405` 单战位技能
|
||||||
|
- `7505` 多战位技能
|
||||||
|
- `7558` 旧组队技能
|
||||||
|
- `2406` 使用道具
|
||||||
|
- `7562` 旧组队道具
|
||||||
|
- `2407` 切宠
|
||||||
|
- `7563` 旧组队切宠
|
||||||
|
- `2410` 逃跑
|
||||||
|
- `7565` 旧组队逃跑
|
||||||
|
- `2441` 加载进度
|
||||||
|
- `50002` 战斗聊天
|
||||||
|
|
||||||
|
### 3. RemoteFightProxy 新增“直接转发客户端命令”能力
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
- `logic/service/fight/pvp/proxy.go`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
|
||||||
|
- 新增 `RelayClientCommand(cmd uint32, data any) bool`
|
||||||
|
- 新增 `marshalClientCommandData(data any) ([]byte, error)`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `marshalClientCommandData` 会把请求结构体转成 JSON。
|
||||||
|
- 会删除 `Head/head` 字段,避免把协议头、二进制字段一起塞进跨服消息。
|
||||||
|
- 这样发出去的 `data.data` 只保留业务字段。
|
||||||
|
|
||||||
|
### 4. PVP 宿主服新增 `battle_client_command` 处理
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
- `logic/service/fight/pvp/service.go`
|
||||||
|
- `logic/service/fight/pvpwire/types.go`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
|
||||||
|
- 新增消息类型:
|
||||||
|
- `MessageTypeBattleClientCommand`
|
||||||
|
- 新增载荷:
|
||||||
|
- `BattleClientCommandPayload`
|
||||||
|
- `handleRedisMessage` 增加 `battle_client_command` 分支。
|
||||||
|
- 新增 `handleBattleClientCommand`。
|
||||||
|
|
||||||
|
处理方式:
|
||||||
|
|
||||||
|
- 宿主服根据内层真实 `cmd` 解出 `data`。
|
||||||
|
- 再直接调用当前 `FightI` / `FightC`。
|
||||||
|
|
||||||
|
当前支持的映射:
|
||||||
|
|
||||||
|
- `2404` / `7556` -> `ReadyFight`
|
||||||
|
- `2405` / `7505` / `7558` -> `UseSkillAt`
|
||||||
|
- `2406` / `7562` -> `UseItemAt`
|
||||||
|
- `2407` / `7563` -> `ChangePetAt`
|
||||||
|
- `2410` / `7565` -> `Over(PlayerEscape)`
|
||||||
|
- `2441` -> `LoadPercent`
|
||||||
|
- `50002` -> `Chat`
|
||||||
|
|
||||||
|
### 5. 战斗发包转发也统一改成 `cmd/data`
|
||||||
|
|
||||||
|
文件:
|
||||||
|
|
||||||
|
- `logic/service/player/rpc.go`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
|
||||||
|
- `PacketRelayPayload` 外层 envelope 从 `type/body` 改为 `cmd/data`。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 它本质也是跨服战斗中的消息转发。
|
||||||
|
- 和新的 `battle_client_command` 保持一致,便于抓包和日志排查。
|
||||||
|
|
||||||
|
## 当前实际链路
|
||||||
|
|
||||||
|
### 客户端操作转发
|
||||||
|
|
||||||
|
1. 客户端发战斗包到本服 controller。
|
||||||
|
2. controller 判断当前 `FightC` 是否为 `RemoteFightProxy`。
|
||||||
|
3. 如果是:
|
||||||
|
- 直接发布 `battle_client_command`
|
||||||
|
- 内层携带真实 `cmd` 和 JSON `data`
|
||||||
|
4. 宿主服 `pvp.service` 收到后按 `cmd` 分发回战斗逻辑。
|
||||||
|
|
||||||
|
### 战斗结果/下行发包转发
|
||||||
|
|
||||||
|
1. 宿主服在 `RPC_player.SendPackCmd` 中组包。
|
||||||
|
2. 发布 `packet_relay` 消息,外层使用 `cmd/data`。
|
||||||
|
3. 客服端所在服收到后解包并发回真实客户端。
|
||||||
|
|
||||||
|
## 兼容策略
|
||||||
|
|
||||||
|
为了避免一次性改动过大,这次保留了两层兼容:
|
||||||
|
|
||||||
|
### 1. 旧 envelope 兼容
|
||||||
|
|
||||||
|
`Envelope` 同时支持:
|
||||||
|
|
||||||
|
- 新格式:`cmd/data`
|
||||||
|
- 旧格式:`type/body`
|
||||||
|
|
||||||
|
`handleRedisMessage` 统一通过:
|
||||||
|
|
||||||
|
- `MessageCmd()`
|
||||||
|
- `MessageData()`
|
||||||
|
|
||||||
|
读取消息。
|
||||||
|
|
||||||
|
### 2. 旧 `battle_command` 兼容
|
||||||
|
|
||||||
|
`RemoteFightProxy` 旧的:
|
||||||
|
|
||||||
|
- `UseSkill`
|
||||||
|
- `UseSkillAt`
|
||||||
|
- `UseItem`
|
||||||
|
- `ChangePet`
|
||||||
|
- `Chat`
|
||||||
|
|
||||||
|
这些方法仍然保留,并继续发送原来的 `battle_command`。
|
||||||
|
新接入的 controller 优先走 `battle_client_command`,旧路径仍可兜底。
|
||||||
|
|
||||||
|
## 这次改造的收益
|
||||||
|
|
||||||
|
### 好处
|
||||||
|
|
||||||
|
- 跨服战斗消息更直观,日志里能直接看到真实客户端 cmd。
|
||||||
|
- controller 到宿主服之间不需要再先翻译成一套额外动作协议。
|
||||||
|
- 宿主服调试时更容易复现前端输入。
|
||||||
|
- 新老消息可并存,改造风险相对可控。
|
||||||
|
|
||||||
|
### 仍然保留的复杂度
|
||||||
|
|
||||||
|
- 宿主服仍然需要针对不同战斗 cmd 做一次 decode 和分发。
|
||||||
|
- 这是必要复杂度,因为真实战斗入口本来就有多种协议包。
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
本次已执行:
|
||||||
|
|
||||||
|
- `cd logic && go test ./service/fight/pvp ./service/fight/pvpwire`
|
||||||
|
- `cd logic && go test -c ./controller`
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
- `pvp`、`pvpwire` 包通过。
|
||||||
|
- `controller` 可以完成编译检查。
|
||||||
|
|
||||||
|
补充说明:
|
||||||
|
|
||||||
|
- `go test ./controller ./service/player ...` 在当前环境会因为依赖初始化读取 `/proc/sys/kernel/osrelease` 失败而 panic,这属于运行环境问题,不是这次改动引入的编译错误。
|
||||||
|
|
||||||
|
## 后续建议
|
||||||
|
|
||||||
|
如果后面继续收敛这块逻辑,可以按下面顺序做:
|
||||||
|
|
||||||
|
1. 把 `battle_command` 的旧语义层逐步下线,只保留 `battle_client_command`。
|
||||||
|
2. 把 `handleBattleClientCommand` 里的 cmd 映射抽成独立表,避免 `switch` 继续膨胀。
|
||||||
|
3. 如果后续战斗协议继续统一,可考虑把 controller 入站结构和跨服转发 decode 共享同一套注册表。
|
||||||
224
docs/pvp-login-rpc-match-design-2026-04-09.md
Normal file
224
docs/pvp-login-rpc-match-design-2026-04-09.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# PVP Match Via RPC, Battle Via Redis
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
本次调整先不解决 `login` 更新期间的排队保活和补偿问题,只收敛到一个更简单、可控的方案:
|
||||||
|
|
||||||
|
- 匹配请求走 `logic -> login` 的同步 RPC
|
||||||
|
- 对战过程仍走 `logic` 本地战斗 + Redis 转发战斗指令
|
||||||
|
- `login` 不可用时,`logic` 直接返回“匹配服务不可用”
|
||||||
|
- 前端通过轮询重新发起 / 更新匹配请求,不在后端保留离线补偿队列
|
||||||
|
|
||||||
|
这个方案的核心是:先把“能否立即判断匹配服务可用”做好,不继续依赖 Redis PubSub 做匹配入口。
|
||||||
|
|
||||||
|
## 当前现状
|
||||||
|
|
||||||
|
### 现有匹配入口
|
||||||
|
|
||||||
|
- 前端 `2458` 进入 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19)
|
||||||
|
- 当前 `JoINtop` 直接调用 [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83) 的 `JoinPeakQueue`
|
||||||
|
- `JoinPeakQueue` 当前实现是本地建 `localQueueTicket`,并通过 Redis `publish` 发 `queue_join`
|
||||||
|
|
||||||
|
### 现有跨服协调
|
||||||
|
|
||||||
|
- `logic` 侧订阅 PVP Redis topic 的入口在 [common/rpc/func.go](/workspace/common/rpc/func.go#L153)
|
||||||
|
- PVP 匹配状态当前存在 `logic/service/fight/pvp/service.go` 的 manager 内存里:
|
||||||
|
- `queues`
|
||||||
|
- `lastSeen`
|
||||||
|
- `localQueues`
|
||||||
|
- `sessions`
|
||||||
|
- `userSession`
|
||||||
|
|
||||||
|
### 现有 RPC 能力
|
||||||
|
|
||||||
|
- `logic` 启动时通过 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L113) 建立到 `login` 的 RPC client
|
||||||
|
- `login` 的 `/rpc/*` 入口绑定在 [modules/base/middleware/middleware.go](/workspace/modules/base/middleware/middleware.go#L152)
|
||||||
|
- `login` 侧 RPC server 由 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L101) 暴露
|
||||||
|
|
||||||
|
### 当前问题
|
||||||
|
|
||||||
|
Redis PubSub 适合“广播消息”,不适合“同步判断服务是否可用”。
|
||||||
|
|
||||||
|
如果继续让匹配入口走 PubSub:
|
||||||
|
|
||||||
|
- `logic` 无法在请求当下知道 `login` 是否真能处理
|
||||||
|
- `login` 更新、重启、未订阅时,匹配请求可能直接丢失
|
||||||
|
- 前端即使轮询,也只是重复投递,不能精确表达“当前匹配服务可用/不可用”
|
||||||
|
|
||||||
|
## 收敛后的职责划分
|
||||||
|
|
||||||
|
### login
|
||||||
|
|
||||||
|
`login` 只负责匹配控制面:
|
||||||
|
|
||||||
|
- 接收 `logic` 发来的同步匹配 RPC
|
||||||
|
- 判断当前匹配服务是否可用
|
||||||
|
- 维护匹配队列
|
||||||
|
- 找到对手后,记录 match 结果
|
||||||
|
- 再通过 Redis 或其他异步方式通知对应 `logic` 开始 Ban/Pick / Battle
|
||||||
|
|
||||||
|
### logic
|
||||||
|
|
||||||
|
`logic` 只负责:
|
||||||
|
|
||||||
|
- 接收前端匹配请求
|
||||||
|
- 同步 RPC 到 `login`
|
||||||
|
- RPC 失败时立即返回“匹配服务不可用”
|
||||||
|
- RPC 成功时返回“排队中”
|
||||||
|
- 收到 match 结果后负责真正 `fight.NewFight(...)`
|
||||||
|
- 对战期间继续使用现有 Redis topic 转发战斗指令
|
||||||
|
|
||||||
|
### Redis
|
||||||
|
|
||||||
|
Redis 只保留在“对战消息面”:
|
||||||
|
|
||||||
|
- `match_found`
|
||||||
|
- `ban_pick_submit`
|
||||||
|
- `battle_command`
|
||||||
|
- `packet_relay`
|
||||||
|
- `session_close`
|
||||||
|
|
||||||
|
也就是说:
|
||||||
|
|
||||||
|
- 匹配入口走 RPC
|
||||||
|
- 对战过程走 Redis
|
||||||
|
|
||||||
|
## 推荐目标链路
|
||||||
|
|
||||||
|
### 1. 前端加入/更新匹配
|
||||||
|
|
||||||
|
前端定期轮询 `logic` 的加入/更新接口。
|
||||||
|
|
||||||
|
`logic` 处理流程:
|
||||||
|
|
||||||
|
1. 校验玩家当前战斗状态
|
||||||
|
2. 同步调用 `login` 的匹配 RPC
|
||||||
|
3. 如果 RPC 成功:返回排队中
|
||||||
|
4. 如果 RPC 失败:清理本地匹配状态,返回匹配服务不可用
|
||||||
|
|
||||||
|
### 2. login 完成匹配
|
||||||
|
|
||||||
|
`login` 维护排队队列和匹配结果,匹配成功后:
|
||||||
|
|
||||||
|
1. 确定 host / guest 所在 `logic`
|
||||||
|
2. 通过 Redis 通知两个 `logic`
|
||||||
|
3. host `logic` 开战
|
||||||
|
4. guest `logic` 设置远端代理并进入 Ban/Pick 或战斗态
|
||||||
|
|
||||||
|
### 3. 对战期间
|
||||||
|
|
||||||
|
继续复用当前 `logic/service/fight/pvp/service.go` 内的 Redis 指令转发模式:
|
||||||
|
|
||||||
|
- 战斗操作通过 Redis topic 转发
|
||||||
|
- host `logic` 维持真实战斗对象
|
||||||
|
- guest `logic` 维持 remote proxy
|
||||||
|
|
||||||
|
## 失败语义
|
||||||
|
|
||||||
|
本阶段不做补偿,不做离线保队列。
|
||||||
|
|
||||||
|
### login 不在线
|
||||||
|
|
||||||
|
如果 `logic -> login` RPC 调用失败:
|
||||||
|
|
||||||
|
- 本次匹配直接失败
|
||||||
|
- `logic` 清理本地匹配状态
|
||||||
|
- 返回前端“匹配服务不可用”
|
||||||
|
|
||||||
|
### 前端轮询停止
|
||||||
|
|
||||||
|
如果前端不再轮询:
|
||||||
|
|
||||||
|
- 视为用户不再持续请求匹配
|
||||||
|
- `logic` 不负责继续保活
|
||||||
|
- 是否从 `login` 队列移除,由 `login` 的超时策略决定
|
||||||
|
|
||||||
|
### login 更新中
|
||||||
|
|
||||||
|
如果 `login` 正在更新:
|
||||||
|
|
||||||
|
- `logic` 的同步 RPC 会失败
|
||||||
|
- 前端当前轮询会收到“匹配服务不可用”
|
||||||
|
- 等 `login` 恢复后,前端下一轮再发起匹配
|
||||||
|
|
||||||
|
这是本阶段明确接受的行为,不在后端做补偿。
|
||||||
|
|
||||||
|
## 最小实现建议
|
||||||
|
|
||||||
|
### 一、先增加 RPC 健康/匹配接口
|
||||||
|
|
||||||
|
在 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go) 增加面向 `logic -> login` 的 RPC 方法。
|
||||||
|
|
||||||
|
建议最小接口:
|
||||||
|
|
||||||
|
- `MatchJoinOrUpdate(PVPMatchJoinPayload) error`
|
||||||
|
- `MatchCancel(userID) error`
|
||||||
|
|
||||||
|
如果需要单独健康检查,也可以加:
|
||||||
|
|
||||||
|
- `MatchPing() error`
|
||||||
|
|
||||||
|
但在最小方案里,`MatchJoinOrUpdate` 自身就可以承担健康检查职责。
|
||||||
|
|
||||||
|
### 二、logic 的匹配入口改为同步 RPC
|
||||||
|
|
||||||
|
改造 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19) 和 [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83):
|
||||||
|
|
||||||
|
- 入口不再直接发布 `queue_join`
|
||||||
|
- 先发 RPC 到 `login`
|
||||||
|
- 成功才更新本地匹配状态
|
||||||
|
- 失败直接返回错误
|
||||||
|
- 取消匹配时通过 `MatchCancel` 做 best-effort 清理
|
||||||
|
|
||||||
|
### 三、保留 Redis 对战链路
|
||||||
|
|
||||||
|
[logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L170) 之后的 Redis 消费、match result 处理、Ban/Pick、战斗 relay 不需要一次性重写,可以继续保留。
|
||||||
|
|
||||||
|
调整重点是:
|
||||||
|
|
||||||
|
- 不再让匹配入口依赖 PubSub
|
||||||
|
- 让对战过程继续走 Redis
|
||||||
|
|
||||||
|
## 对前端的要求
|
||||||
|
|
||||||
|
前端不要无脑重复“新 join”,而是按“轮询更新匹配状态”处理。
|
||||||
|
|
||||||
|
建议行为:
|
||||||
|
|
||||||
|
1. 首次点击匹配时发一次加入
|
||||||
|
2. 匹配中每隔 `3~5s` 轮询一次更新
|
||||||
|
3. 如果返回“匹配服务不可用”,前端退出匹配态并提示
|
||||||
|
4. 如果返回“已匹配/进入 Ban/Pick”,前端切换到对应界面
|
||||||
|
|
||||||
|
## 本阶段不做的事
|
||||||
|
|
||||||
|
以下内容明确不在这次最小改造内:
|
||||||
|
|
||||||
|
- `login` 更新期间的排队保活
|
||||||
|
- 持久化消息补偿
|
||||||
|
- `login` 重启后的队列恢复
|
||||||
|
- Redis Stream 化
|
||||||
|
- 多 `login` 实例协调
|
||||||
|
- 匹配服务自动拉起目标 `logic`
|
||||||
|
|
||||||
|
## 后续可选增强
|
||||||
|
|
||||||
|
如果后面要继续提高可用性,可以再逐步演进为:
|
||||||
|
|
||||||
|
1. 匹配入口仍走 RPC
|
||||||
|
2. `login` 内部把队列落 Redis
|
||||||
|
3. 加入 ticket 和续租机制
|
||||||
|
4. login 更新时支持恢复匹配状态
|
||||||
|
|
||||||
|
但这不是当前阶段的目标。
|
||||||
|
|
||||||
|
## 最终收敛结论
|
||||||
|
|
||||||
|
当前阶段建议明确成一句话:
|
||||||
|
|
||||||
|
`匹配走 RPC,对战走 Redis。`
|
||||||
|
|
||||||
|
对应业务语义:
|
||||||
|
|
||||||
|
- 需要立即判断服务可用性的时候,用 RPC
|
||||||
|
- 需要跨服转发战斗消息的时候,用 Redis
|
||||||
276
docs/rpc-blocking-reconnect-review-2026-04-27.md
Normal file
276
docs/rpc-blocking-reconnect-review-2026-04-27.md
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
# RPC 阻塞、并发承载与重连复用检查
|
||||||
|
|
||||||
|
日期:2026-04-27
|
||||||
|
|
||||||
|
## 1. 检查范围
|
||||||
|
|
||||||
|
本次主要检查以下两层:
|
||||||
|
|
||||||
|
- 业务 RPC 封装:`common/rpc/rpc.go`
|
||||||
|
- 底层 websocket JSON-RPC:`common/utils/go-jsonrpc/client.go`
|
||||||
|
- 底层 websocket 重连循环:`common/utils/go-jsonrpc/websocket.go`
|
||||||
|
- 业务调用点:`logic/controller/login_main.go`、`logic/controller/fight_巅峰.go`
|
||||||
|
|
||||||
|
另外同步看了本轮已有的跨服战斗转发改动:
|
||||||
|
|
||||||
|
- `logic/service/fight/pvp/proxy.go`
|
||||||
|
- `logic/service/fight/pvp/service.go`
|
||||||
|
- `logic/service/fight/pvpwire/types.go`
|
||||||
|
- `logic/service/player/rpc.go`
|
||||||
|
|
||||||
|
## 2. 结论
|
||||||
|
|
||||||
|
### 2.1 现在的 RPC 会不会阻塞
|
||||||
|
|
||||||
|
会。
|
||||||
|
|
||||||
|
当前业务侧 `Kick`、`MatchJoinOrUpdate`、`MatchCancel`、`RegisterLogic` 都是同步 RPC 调用。
|
||||||
|
调用 goroutine 会一直等到底层返回响应、连接错误,或者调用方自己的 `context` 超时。
|
||||||
|
|
||||||
|
原先的问题是:
|
||||||
|
|
||||||
|
- 这些业务 RPC 方法没有 `context.Context` 入参
|
||||||
|
- 调用方没法给单次 RPC 设置超时
|
||||||
|
- 一旦对端卡住或网络异常,调用方可能长期挂住
|
||||||
|
|
||||||
|
这会直接影响:
|
||||||
|
|
||||||
|
- 登录踢人流程
|
||||||
|
- 巅峰匹配加入/取消
|
||||||
|
- 重连后的逻辑服重新注册
|
||||||
|
|
||||||
|
### 2.2 并发会不会顶不住
|
||||||
|
|
||||||
|
结论是:底层支持多路并发,但原实现存在明显背压点。
|
||||||
|
|
||||||
|
底层 `go-jsonrpc` 的设计不是“单请求单连接”,而是:
|
||||||
|
|
||||||
|
- 一个 `RPCClient`
|
||||||
|
- 一个 websocket 连接
|
||||||
|
- 多个请求共用一个 `requests` 通道
|
||||||
|
- 通过请求 ID 做响应分发
|
||||||
|
|
||||||
|
所以它本身支持并发复用同一条连接。
|
||||||
|
|
||||||
|
但原实现有两个风险:
|
||||||
|
|
||||||
|
1. `client.setupRequestChan()` 里的 `requests` 是无缓冲通道
|
||||||
|
当 `handleWsConn` 主循环发送不过来时,调用方会在“写入请求通道”这一步被卡住。
|
||||||
|
|
||||||
|
2. 业务调用没有统一超时
|
||||||
|
即使底层连接还能用,某个慢 RPC 也可能把业务 goroutine 长时间挂住。
|
||||||
|
|
||||||
|
这不代表“完全扛不住”,但高并发下会更容易出现请求堆积和业务侧等待放大。
|
||||||
|
|
||||||
|
### 2.3 重连后 URL 会不会复用
|
||||||
|
|
||||||
|
会复用。
|
||||||
|
|
||||||
|
当前 websocket client 在初始化时把地址保存在 `connFactory` 中,重连时走的还是同一个 `addr`:
|
||||||
|
|
||||||
|
- `common/utils/go-jsonrpc/client.go`
|
||||||
|
- `websocketClient(...)` 中构造 `connFactory`
|
||||||
|
- `common/utils/go-jsonrpc/websocket.go`
|
||||||
|
- `tryReconnect(...)` 中再次调用 `c.connFactory()`
|
||||||
|
|
||||||
|
也就是说:
|
||||||
|
|
||||||
|
- 重连不是只发一次注册 RPC 就结束
|
||||||
|
- 重连后不是一次性临时连接
|
||||||
|
- 而是替换 `wsConn.conn` 为新连接
|
||||||
|
- 后续 RPC 仍然继续复用同一个 `RPCClient` 和同一个目标 URL
|
||||||
|
|
||||||
|
### 2.4 重连后是不是必须再发一次注册 RPC
|
||||||
|
|
||||||
|
现在不是了。
|
||||||
|
|
||||||
|
当前实现已经改成:
|
||||||
|
|
||||||
|
- 客户端在 websocket 建连 URL 上直接带 `logic_id` / `logic_port`
|
||||||
|
- 服务端在握手阶段创建 reverse client 后,立刻根据 URL 参数完成 logic 注册
|
||||||
|
- 连接断开时再根据同一身份清理 `cool.Clientmap`
|
||||||
|
|
||||||
|
这样重连时:
|
||||||
|
|
||||||
|
- 仍然走同一个 URL
|
||||||
|
- 新连接在握手阶段就知道 client 身份
|
||||||
|
- 不需要再依赖重连成功后的二次 `RegisterLogic(id, port)` RPC
|
||||||
|
|
||||||
|
保留 `RegisterLogic` 只是兼容已有接口,不再是重连链路的必要步骤。
|
||||||
|
|
||||||
|
## 3. 本轮已做修改
|
||||||
|
|
||||||
|
### 3.1 给业务 RPC 增加显式超时能力
|
||||||
|
|
||||||
|
修改文件:
|
||||||
|
|
||||||
|
- `common/rpc/rpc.go`
|
||||||
|
- `logic/controller/Controller.go`
|
||||||
|
- `logic/controller/login_main.go`
|
||||||
|
- `logic/controller/fight_巅峰.go`
|
||||||
|
|
||||||
|
改动内容:
|
||||||
|
|
||||||
|
- `Kick`
|
||||||
|
- `RegisterLogic`
|
||||||
|
- `MatchJoinOrUpdate`
|
||||||
|
- `MatchCancel`
|
||||||
|
|
||||||
|
统一改成带 `context.Context` 的签名。
|
||||||
|
|
||||||
|
新增:
|
||||||
|
|
||||||
|
- `common/rpc/rpc.go`
|
||||||
|
- `ClientCallTimeout = 5 * time.Second`
|
||||||
|
|
||||||
|
调用侧现在会显式设置超时,避免业务 goroutine 无限等待。
|
||||||
|
|
||||||
|
### 3.2 连接握手阶段直接注册 logic 身份
|
||||||
|
|
||||||
|
修改文件:
|
||||||
|
|
||||||
|
- `common/rpc/rpc.go`
|
||||||
|
- `common/utils/go-jsonrpc/server.go`
|
||||||
|
- `common/utils/go-jsonrpc/options_server.go`
|
||||||
|
|
||||||
|
行为调整:
|
||||||
|
|
||||||
|
- 客户端建连 URL 直接携带 `logic_id` / `logic_port`
|
||||||
|
- 服务端握手时把原始 `*http.Request` 放入 RPC 上下文
|
||||||
|
- reverse client 建好后立即读取 URL 参数并注册 logic client
|
||||||
|
- 连接关闭时按相同 key 自动清理 `cool.Clientmap`
|
||||||
|
|
||||||
|
这样重连后不需要额外补发一次注册 RPC。
|
||||||
|
|
||||||
|
### 3.3 底层请求通道增加缓冲
|
||||||
|
|
||||||
|
修改文件:
|
||||||
|
|
||||||
|
- `common/utils/go-jsonrpc/client.go`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
|
||||||
|
- `requests := make(chan clientRequest, 1024)`
|
||||||
|
|
||||||
|
目的:
|
||||||
|
|
||||||
|
- 把“调用方 goroutine 立刻卡在请求投递”这个点往后挪
|
||||||
|
- 给 `handleWsConn` 主循环留一个有限缓冲区
|
||||||
|
|
||||||
|
这不是彻底消除背压,只是把最硬的无缓冲阻塞改掉。
|
||||||
|
|
||||||
|
### 3.4 重连回调不再承担注册职责
|
||||||
|
|
||||||
|
修改文件:
|
||||||
|
|
||||||
|
- `common/rpc/rpc.go`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
|
||||||
|
- 去掉 `StartClient(...)` 中依赖 `WithReconnFun(...)` 做补注册的逻辑
|
||||||
|
|
||||||
|
目的:
|
||||||
|
|
||||||
|
- 让“谁是这个 logic client”变成握手时就已确定的连接属性
|
||||||
|
- 避免重连后再发一笔注册 RPC
|
||||||
|
|
||||||
|
## 4. 这轮修改后的判断
|
||||||
|
|
||||||
|
### 4.1 RPC 还会不会阻塞
|
||||||
|
|
||||||
|
会,但现在阻塞是“有边界的同步等待”,不是“无上限死等”。
|
||||||
|
|
||||||
|
也就是:
|
||||||
|
|
||||||
|
- 业务仍然是同步 RPC 模式
|
||||||
|
- 但调用方现在有明确超时
|
||||||
|
- 超时后能返回错误,不会无限挂住
|
||||||
|
|
||||||
|
### 4.2 并发有没有改善
|
||||||
|
|
||||||
|
有改善,但不是彻底做成高吞吐 RPC 网关。
|
||||||
|
|
||||||
|
现在比原来更稳的点:
|
||||||
|
|
||||||
|
- 业务调用有超时
|
||||||
|
- 请求投递通道有缓冲
|
||||||
|
- 重连时不再额外补发一次注册 RPC
|
||||||
|
|
||||||
|
仍然保留的现实限制:
|
||||||
|
|
||||||
|
- 单条 websocket 连接仍然只有一个写口
|
||||||
|
- `handleWsConn` 仍是单主循环
|
||||||
|
- 极端并发下仍会出现排队,只是不会像原来那样更早卡死
|
||||||
|
|
||||||
|
## 5. 关于“URL 复用”的最终确认
|
||||||
|
|
||||||
|
最终确认如下:
|
||||||
|
|
||||||
|
1. `RPCClient` 建立时会把目标 `rpcaddr` 固定到 `connFactory`
|
||||||
|
2. 这个 `rpcaddr` 现在已经带上 `logic_id` / `logic_port`
|
||||||
|
3. 连接断开后,`tryReconnect(...)` 继续使用这个 `connFactory`
|
||||||
|
4. 新连接建立后,服务端在握手阶段直接按 URL 参数注册 reverse client
|
||||||
|
5. 原 `RPCClient` 后续 RPC 继续走新连接
|
||||||
|
|
||||||
|
所以这里是:
|
||||||
|
|
||||||
|
- 复用同一个目标 URL
|
||||||
|
- 复用同一个 `RPCClient`
|
||||||
|
- 复用同一个请求分发模型
|
||||||
|
|
||||||
|
不是:
|
||||||
|
|
||||||
|
- 重连后靠额外发一次 `RegisterLogic`
|
||||||
|
- 才让后续 RPC 可用
|
||||||
|
|
||||||
|
现在身份识别和注册已经前置到连接握手本身。
|
||||||
|
|
||||||
|
## 6. 还没解决的风险
|
||||||
|
|
||||||
|
### 6.1 连接级串行写仍然存在
|
||||||
|
|
||||||
|
虽然请求可以并发入队,但真正写 websocket 还是串行的。
|
||||||
|
如果将来 login 侧 RPC 量继续上升,还是可能需要继续做:
|
||||||
|
|
||||||
|
- 更细的调用隔离
|
||||||
|
- 独立连接池
|
||||||
|
- 或把部分强同步调用改为异步消息
|
||||||
|
|
||||||
|
### 6.2 1024 缓冲不是容量上限方案
|
||||||
|
|
||||||
|
当前只是经验值,不是经过压测得出的最终值。
|
||||||
|
如果峰值比预期高,还可能继续积压。
|
||||||
|
|
||||||
|
### 6.3 业务上仍然是同步等待模式
|
||||||
|
|
||||||
|
比如:
|
||||||
|
|
||||||
|
- 登录踢人
|
||||||
|
- 匹配加入
|
||||||
|
|
||||||
|
仍然依赖 RPC 成功/失败来推进。
|
||||||
|
只是现在不会无限挂死,但高峰期延迟仍可能直接体现在业务响应上。
|
||||||
|
|
||||||
|
## 7. 验证情况
|
||||||
|
|
||||||
|
已完成:
|
||||||
|
|
||||||
|
- `gofmt` 已执行
|
||||||
|
- `go test ./rpc` in `common` 通过
|
||||||
|
- `go test -run '^$' .` in `common/utils/go-jsonrpc` 通过
|
||||||
|
|
||||||
|
受环境限制未完整确认:
|
||||||
|
|
||||||
|
- `common/utils/go-jsonrpc` 全量测试在 sandbox 下需要本地监听端口
|
||||||
|
- `logic` 模块测试受当前环境 `/proc/sys/kernel/osrelease` 读取失败影响,无法作为本轮改动的有效回归结论
|
||||||
|
|
||||||
|
另外,`common` 模块全量 `go test ./...` 还会被仓库内已有的 `fmt.Println("%.2f")` 这类历史问题拦住,与本次 RPC 改动无关。
|
||||||
|
|
||||||
|
## 8. 建议的下一步
|
||||||
|
|
||||||
|
如果后面还要继续收敛这块,建议优先级如下:
|
||||||
|
|
||||||
|
1. 给 login 侧关键 RPC 增加更明确的耗时日志和超时日志
|
||||||
|
2. 对 `requests` 队列积压增加指标或告警
|
||||||
|
3. 评估 `Kick` 和匹配 RPC 是否需要拆连接
|
||||||
|
4. 如果 login 压力继续上涨,再考虑把部分同步入口改成异步投递 + 状态查询
|
||||||
37
help/base_sys_user_role去重.sql
Normal file
37
help/base_sys_user_role去重.sql
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
-- base_sys_user_role 角色授权去重
|
||||||
|
-- 只处理未软删除的有效授权;软删除历史记录不参与去重
|
||||||
|
-- 保留每组有效 userId + roleId 中 id 最小的一条,删除其余重复记录
|
||||||
|
|
||||||
|
-- 1. 执行前查看重复数据
|
||||||
|
SELECT
|
||||||
|
"userId",
|
||||||
|
"roleId",
|
||||||
|
COUNT(*) AS cnt,
|
||||||
|
MIN(id) AS keep_id,
|
||||||
|
ARRAY_AGG(id ORDER BY id) AS ids
|
||||||
|
FROM base_sys_user_role
|
||||||
|
WHERE deleted_at IS NULL
|
||||||
|
GROUP BY "userId", "roleId"
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY cnt DESC, "userId", "roleId";
|
||||||
|
|
||||||
|
-- 2. 删除重复数据
|
||||||
|
DELETE FROM base_sys_user_role a
|
||||||
|
USING base_sys_user_role b
|
||||||
|
WHERE a."userId" = b."userId"
|
||||||
|
AND a."roleId" = b."roleId"
|
||||||
|
AND a.deleted_at IS NULL
|
||||||
|
AND b.deleted_at IS NULL
|
||||||
|
AND a.id > b.id;
|
||||||
|
|
||||||
|
-- 3. 执行后复查,应返回 0 行
|
||||||
|
SELECT
|
||||||
|
"userId",
|
||||||
|
"roleId",
|
||||||
|
COUNT(*) AS cnt
|
||||||
|
FROM base_sys_user_role
|
||||||
|
WHERE deleted_at IS NULL
|
||||||
|
GROUP BY "userId", "roleId"
|
||||||
|
HAVING COUNT(*) > 1;
|
||||||
|
|
||||||
|
-- 唯一约束统一在 约束类.sql 中维护。
|
||||||
10
help/ftp.md
Normal file
10
help/ftp.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FTP地址:38.102.84.92 [ 端口:21 ]
|
||||||
|
FTP用户名:m1584920
|
||||||
|
FTP密码:7WKimiwDH5RL2SLs
|
||||||
|
空间容量:10G
|
||||||
|
有效期至:2027-04-21(1年)
|
||||||
|
|
||||||
|
文件上传之后,其URL地址是:
|
||||||
|
https://m1584920.772988.xyz/文件名+文件格式,如果文件名有中文,请注意编码问题,如果你不懂这个就遇到再跟我说。
|
||||||
|
|
||||||
|
PS:如果需要绑定你的域名,手续费¥30
|
||||||
14
help/server_show冠名索引修复.sql
Normal file
14
help/server_show冠名索引修复.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- server_show 冠名索引修复
|
||||||
|
-- 目的:允许同一服务器存在多个不同玩家的冠名记录。
|
||||||
|
-- 同服同属主唯一约束统一在 约束类.sql 中维护。
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- 历史上 server_id 被建成了单列唯一约束/唯一索引,会拦截同服多冠名
|
||||||
|
ALTER TABLE server_show DROP CONSTRAINT IF EXISTS idx_server_show_server_id;
|
||||||
|
DROP INDEX IF EXISTS idx_server_show_server_id;
|
||||||
|
|
||||||
|
-- 保留普通查询索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_server_show_server_id ON server_show (server_id);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
73
help/初始化SPT配置.sql
Normal file
73
help/初始化SPT配置.sql
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
-- 初始化/修复 SPT 配置表(PostgreSQL)
|
||||||
|
-- 用法:在 sun 数据库执行本文件
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS config_spt (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
"createTime" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
"updateTime" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMPTZ NULL,
|
||||||
|
is_enable INTEGER NOT NULL DEFAULT 1,
|
||||||
|
remark VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
task_id INTEGER NOT NULL,
|
||||||
|
title VARCHAR(64) NOT NULL DEFAULT '',
|
||||||
|
pet_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
online INTEGER NOT NULL DEFAULT 1,
|
||||||
|
level INTEGER NOT NULL DEFAULT 1,
|
||||||
|
enter_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
description TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
-- task_id 唯一约束统一在 约束类.sql 中维护。
|
||||||
|
|
||||||
|
ALTER TABLE config_spt DROP COLUMN IF EXISTS seat_id;
|
||||||
|
|
||||||
|
INSERT INTO config_spt
|
||||||
|
(task_id, title, pet_id, online, level, enter_id, description, is_enable, remark)
|
||||||
|
VALUES
|
||||||
|
(301,'蘑菇怪',47,1,1,12,'生活在克洛斯星,被艾里逊的液氮冻伤而发狂,使用火焰喷射器可以使它安静下来。制服它可以获得草系精灵小蘑菇。',1,'破除防护罩'),
|
||||||
|
(302,'钢牙鲨',34,1,1,21,'海洋星海底的危险怪兽,据说它躲藏的洞穴中有制作黑武士装的黑晶矿石。记住,到海底一定要穿上耐压的潜水套装。',1,''),
|
||||||
|
(303,'里奥斯',42,1,2,17,'海盗艾里逊在火山被它困住,战胜它有机会获得火系精灵“胡里亚”。在火山,你会用到喷水装的。',1,'扑灭火焰屏障'),
|
||||||
|
(304,'阿克希亚',50,1,4,40,'塞西利亚星的守护者,正义的精灵圣兽,它是不可战胜的,千年来一直等待着宿命的对手。',1,''),
|
||||||
|
(305,'提亚斯',69,1,3,27,'云霄星出现了一只极具攻击性的变异精灵,拥有很多蛋的它虽然想要努力呵护自己的孩子却力不从心,看来需要大家帮帮忙啊。',1,''),
|
||||||
|
(306,'雷伊',70,1,3,32,'赫尔卡星天空中划过一道闪电,映出了一个酷似精灵的黑影,它全身被电流包围,从它的神态中可以看出它正等待来自各方的挑战。',1,'雷雨天'),
|
||||||
|
(307,'纳多雷',88,1,3,106,'在双子阿尔法星上,特派队遇见了一只巨大的精灵,经过多次挑战后,它仍然丝毫无损,赛尔们是否有办法战胜这只精灵呢?',1,''),
|
||||||
|
(308,'雷纳多',113,1,3,49,'彪悍的雷纳多盘踞在双子贝塔星上,和双子阿尔法星的纳多雷遥相对应,守护着星球上所有精灵的。',1,''),
|
||||||
|
(309,'尤纳斯',132,1,4,314,'黑暗之门的制造者,拥有能够抵御一切的暗影屏障和所有能量来源的黑暗之核。',1,''),
|
||||||
|
(310,'魔狮迪露',187,1,4,53,'魔狮迪露具有神秘的力量,能使自己的体力突破界限,但同时也会受到未知的惩罚。',1,''),
|
||||||
|
(311,'哈莫雷特',216,1,5,60,'拥有无比巨大的身躯,集水火草三种原能为一身,龙系的神秘力量使它所向无敌。失忆的它,似乎还有很多谜团……',1,''),
|
||||||
|
(312,'奈尼芬多',264,1,4,325,'奈尼芬多是爱迪星的守护者,凄美的歌声连月亮都为之倾倒,据说只有音乐的力量才能够唤醒它。',1,''),
|
||||||
|
(316,'厄尔塞拉',421,1,5,61,'浑身散发着各色光芒,任何邪恶在她的光芒下消散无形',1,''),
|
||||||
|
(50,'卡特斯',169,1,2,110,'作为暗黑武斗场的试炼精灵,守护着试炼之门,它的气度和风度非同一般,杀气重重很难对付!',1,''),
|
||||||
|
(51,'魔牙鲨',171,1,3,503,'暗黑第一门的魔牙鲨,被赋予了传说中的暗黑斗气,隐藏在暗影中攻击时它的能力可以被放大增强。',1,''),
|
||||||
|
(53,'贝鲁基德',174,1,3,504,'暗黑第二门的贝鲁基德,暗黑火焰环绕周身,凶悍的外表下藏着善战勇敢的心。',1,''),
|
||||||
|
(55,'巴弗洛',177,1,3,505,'勇猛凶横的巴弗洛把守着暗黑武斗场Ⅲ-Ⅰ门,霹雳闪电般的羽翼攻击、震荡心胸的音乐攻击,让人防不胜防。',1,''),
|
||||||
|
(56,'奇拉塔顿',183,1,3,505,'勇敢的奇拉塔顿驻守在暗黑武斗场Ⅲ-Ⅱ门的那一边,身为大地之子的它驾驭着反物质能量,纵横无敌。',1,''),
|
||||||
|
(59,'西萨拉斯',195,1,3,506,'拥有强大反物质电力的暗黑Ⅳ-Ⅰ门守护者,雷霆之刃震撼寰宇天地,电流之剑穿透空间阻隔,威慑四方。',1,''),
|
||||||
|
(60,'克林卡修',192,1,4,506,'暗黑Ⅳ-Ⅱ门守护者冰雪灵兽克林卡修,冰雪之爪具有猛烈的攻击力,果敢不张扬的个性,让它成为忍者般的精灵。',1,''),
|
||||||
|
(76,'卡库',222,1,4,507,'暗黑Ⅴ-Ⅰ门是武学之门,守门精灵不张扬、不蛮横、步步为营,每出一招都会致命。',1,''),
|
||||||
|
(77,'赫德卡',224,1,4,507,'驻守暗黑V-II门的铁血赫德卡,有着铜墙铁壁的防守能力,有着超级强力的电光炮,它的防御之门你能够开启吗?',1,''),
|
||||||
|
(78,'伊兰罗尼',227,1,5,507,'守护暗黑Ⅴ-Ⅲ门的伊兰罗尼是优雅得体的淑女,擅长在裙摆飘飘、光芒闪耀间使出杀手。',1,''),
|
||||||
|
(117,'斯加尔卡',356,1,5,508,'暗黑VI-I门的守护者,擅长使用暗黑电能的家伙,比起折磨对手的身体,斯加尔卡更喜欢震慑对手的心灵。',1,''),
|
||||||
|
(118,'艾尔伊洛',297,1,5,508,'暗黑VI-II门的守护者,历经了炼狱洗礼的艾尔伊洛开始崇尚爽快的攻击方式,喜欢凭借精湛的技巧近距地伤害对手。',1,''),
|
||||||
|
(119,'布林克克',359,1,5,508,'暗黑VI-III门的守护者,拥有海妖之力的庇护,体内充满着混沌的能量企图吞噬整个海洋。',1,''),
|
||||||
|
(502,'魔花使者',438,1,5,509,'暗黑VII-I门的守护者,比恩特的进化形态,浑身散发着反物质世界中的黑暗气息,散发出来的毒粉是它的致命武器。',1,''),
|
||||||
|
(503,'莫尔加斯',441,1,5,509,'暗黑VII-II门的守护者,莫鲁格尔的进化形态,受到了反物质世界的影响,浑身被黑暗所包围,拥有极强的防御能力,所有攻击在它面前都显得非常渺小。',1,''),
|
||||||
|
(504,'萨诺拉斯',435,1,5,509,'暗黑VII-III门的守护者,萨诺的进化形态,经过岩浆洗礼的皮肤拥有独特的降温功能,即使在极其炎热的环境下依然不受影响。',1,''),
|
||||||
|
(606,'帕多尼',656,1,5,510,'暗黑Ⅷ-Ⅰ门的守护者,浑身充斥着暗黑能量,暗黑能量会随着它的歌神散发出来。',1,''),
|
||||||
|
(607,'加洛德',659,1,5,510,'暗黑Ⅷ-Ⅱ门的守护,暗黑能量的注入,使它的脾气变得暴躁,擅长与对手近身搏斗,浑身的尖刺、催生出的植物都是它进攻的利器。',1,''),
|
||||||
|
(608,'萨多拉尼',661,1,5,510,'暗黑Ⅷ-Ⅲ门的守护,将暗黑能量融入自身,肢体变得非常结实有力,虽然体积很小,但是却拥有了堪比巨龙的神力。',1,'')
|
||||||
|
ON CONFLICT (task_id) WHERE deleted_at IS NULL DO UPDATE
|
||||||
|
SET
|
||||||
|
title = EXCLUDED.title,
|
||||||
|
pet_id = EXCLUDED.pet_id,
|
||||||
|
online = EXCLUDED.online,
|
||||||
|
level = EXCLUDED.level,
|
||||||
|
enter_id = EXCLUDED.enter_id,
|
||||||
|
description = EXCLUDED.description,
|
||||||
|
is_enable = EXCLUDED.is_enable,
|
||||||
|
remark = EXCLUDED.remark,
|
||||||
|
"updateTime" = NOW();
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
246
help/约束类.sql
246
help/约束类.sql
@@ -1,42 +1,254 @@
|
|||||||
|
-- 唯一约束修复
|
||||||
|
-- 规则:所有带 deleted_at 的业务唯一约束,只约束未软删除记录。
|
||||||
|
|
||||||
-- 玩家+物品+VIP状态 联合唯一
|
-- 玩家+物品+VIP状态 联合唯一
|
||||||
ALTER TABLE player_item
|
ALTER TABLE player_item
|
||||||
ADD CONSTRAINT uk_player_item_player_item_vip
|
DROP CONSTRAINT IF EXISTS uk_player_item_player_item_vip;
|
||||||
UNIQUE (player_id, item_id, is_vip);
|
DROP INDEX IF EXISTS uk_player_item_player_item_vip;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_item_player_item_vip
|
||||||
|
ON player_item (player_id, item_id, is_vip)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
-- 玩家+挖矿 联合唯一
|
-- 玩家+挖矿 联合唯一
|
||||||
CREATE UNIQUE INDEX uk_talk_player ON player_talk (talk_id, player_id);
|
ALTER TABLE player_talk
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_talk_player;
|
||||||
|
DROP INDEX IF EXISTS uk_talk_player;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_talk_player
|
||||||
|
ON player_talk (talk_id, player_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
-- 玩家+任务 联合唯一
|
-- 玩家+任务 联合唯一
|
||||||
CREATE UNIQUE INDEX uk_player_task ON player_task (player_id, task_id);
|
ALTER TABLE player_task
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_player_task;
|
||||||
|
DROP INDEX IF EXISTS uk_player_task;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_task
|
||||||
|
ON player_task (player_id, task_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
-- 玩家+称号 联合唯一
|
-- 玩家+称号 联合唯一
|
||||||
CREATE UNIQUE INDEX uk_player_title ON player_title (player_id, is_vip) WHERE deleted_at IS NULL;
|
ALTER TABLE player_title
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_player_title;
|
||||||
|
DROP INDEX IF EXISTS uk_player_title;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_title
|
||||||
|
ON player_title (player_id, is_vip)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
-- 玩家+精灵 联合唯一
|
-- 玩家+精灵 联合唯一
|
||||||
CREATE UNIQUE INDEX uk_player_pet ON player_pet (player_id, is_vip, catch_time) WHERE deleted_at IS NULL;
|
ALTER TABLE player_pet
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_player_pet;
|
||||||
|
DROP INDEX IF EXISTS uk_player_pet;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_pet
|
||||||
|
ON player_pet (player_id, is_vip, catch_time)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
-- 玩家+CDK 联合唯一
|
-- 玩家+CDK 联合唯一
|
||||||
CREATE UNIQUE INDEX uk_player_cdk_log
|
ALTER TABLE player_cdk_log
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_player_cdk_log;
|
||||||
|
DROP INDEX IF EXISTS uk_player_cdk_log;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_cdk_log
|
||||||
ON player_cdk_log (player_id, code_id, is_vip)
|
ON player_cdk_log (player_id, code_id, is_vip)
|
||||||
WHERE deleted_at IS NULL;
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
-- 玩家孵蛋 联合唯一
|
-- 玩家孵蛋 联合唯一
|
||||||
CREATE UNIQUE INDEX uk_player_egg
|
ALTER TABLE player_egg
|
||||||
ON player_egg (player_id, is_vip)
|
DROP CONSTRAINT IF EXISTS uk_player_egg;
|
||||||
|
DROP INDEX IF EXISTS uk_player_egg;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_egg
|
||||||
|
ON player_egg (player_id, is_vip)
|
||||||
WHERE deleted_at IS NULL;
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
---PVP索引
|
|
||||||
CREATE UNIQUE INDEX uk_player_pvp
|
-- PVP索引
|
||||||
|
ALTER TABLE player_pvp
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_player_pvp;
|
||||||
|
DROP INDEX IF EXISTS uk_player_pvp;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_pvp
|
||||||
ON player_pvp (player_id, season)
|
ON player_pvp (player_id, season)
|
||||||
WHERE deleted_at IS NULL;
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
--签到
|
|
||||||
CREATE UNIQUE INDEX uk_player_sign_in_log
|
-- 签到
|
||||||
|
ALTER TABLE player_sign_in_log
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_player_sign_in_log;
|
||||||
|
DROP INDEX IF EXISTS uk_player_sign_in_log;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_sign_in_log
|
||||||
ON player_sign_in_log (player_id, sign_in_id, is_vip)
|
ON player_sign_in_log (player_id, sign_in_id, is_vip)
|
||||||
WHERE deleted_at IS NULL;
|
WHERE deleted_at IS NULL;
|
||||||
--房间索引
|
|
||||||
CREATE UNIQUE INDEX uk_player_room_house
|
|
||||||
|
-- 房间索引
|
||||||
|
ALTER TABLE player_room_house
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_player_room_house;
|
||||||
|
DROP INDEX IF EXISTS uk_player_room_house;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_room_house
|
||||||
ON player_room_house (player_id, is_vip)
|
ON player_room_house (player_id, is_vip)
|
||||||
WHERE deleted_at IS NULL;
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 集市权限角色 联合唯一
|
||||||
|
-- 先清理有效重复授权,保留每组 userId + roleId 中 id 最小的一条。
|
||||||
|
DELETE FROM base_sys_user_role a
|
||||||
|
USING base_sys_user_role b
|
||||||
|
WHERE a."userId" = b."userId"
|
||||||
|
AND a."roleId" = b."roleId"
|
||||||
|
AND a.deleted_at IS NULL
|
||||||
|
AND b.deleted_at IS NULL
|
||||||
|
AND a.id > b.id;
|
||||||
|
|
||||||
|
ALTER TABLE base_sys_user_role
|
||||||
|
DROP CONSTRAINT IF EXISTS uk_base_sys_user_role_user_role;
|
||||||
|
DROP INDEX IF EXISTS uk_base_sys_user_role_user_role;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS uk_base_sys_user_role_user_role
|
||||||
|
ON base_sys_user_role ("userId", "roleId")
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- CDK配置 编号唯一
|
||||||
|
ALTER TABLE config_gift_cdk
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_gift_cdk_cdk_code;
|
||||||
|
DROP INDEX IF EXISTS idx_config_gift_cdk_cdk_code;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_gift_cdk_cdk_code
|
||||||
|
ON config_gift_cdk (cdk_code)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 战斗规则 规则索引唯一
|
||||||
|
ALTER TABLE config_fight_rule
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_rule_idx;
|
||||||
|
DROP INDEX IF EXISTS idx_rule_idx;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_rule_idx
|
||||||
|
ON config_fight_rule (rule_idx)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 天选配置 玩家+精灵唯一
|
||||||
|
ALTER TABLE config_peak_tianxuan
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_peak_tianxuan_player_pet;
|
||||||
|
DROP INDEX IF EXISTS idx_peak_tianxuan_player_pet;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_peak_tianxuan_player_pet
|
||||||
|
ON config_peak_tianxuan (player_id, pet_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 天选投票 周期+玩家唯一
|
||||||
|
ALTER TABLE config_peak_tianxuan_vote
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_peak_tianxuan_vote_week_player;
|
||||||
|
DROP INDEX IF EXISTS idx_peak_tianxuan_vote_week_player;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_peak_tianxuan_vote_week_player
|
||||||
|
ON config_peak_tianxuan_vote (week_index, player_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 服务器冠名 同服同属主唯一
|
||||||
|
ALTER TABLE server_show
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_server_show_server_owner;
|
||||||
|
DROP INDEX IF EXISTS idx_server_show_server_owner;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_server_show_server_owner
|
||||||
|
ON server_show (server_id, owner)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 商店 商品ID唯一
|
||||||
|
ALTER TABLE config_shop
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_shop_product_id;
|
||||||
|
DROP INDEX IF EXISTS idx_config_shop_product_id;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_shop_product_id
|
||||||
|
ON config_shop (product_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 签到配置 签到类别+阶段唯一
|
||||||
|
ALTER TABLE config_sign_in
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_sign_type_stage;
|
||||||
|
DROP INDEX IF EXISTS idx_sign_type_stage;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_sign_type_stage
|
||||||
|
ON config_sign_in (sign_type, stage_days)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 签到配置 CDK唯一
|
||||||
|
ALTER TABLE config_sign_in
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_sign_in_cdk_id;
|
||||||
|
DROP INDEX IF EXISTS idx_config_sign_in_cdk_id;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_sign_in_cdk_id
|
||||||
|
ON config_sign_in (cdk_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- SPT配置 任务ID唯一
|
||||||
|
ALTER TABLE config_spt
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_spt_task_id;
|
||||||
|
DROP INDEX IF EXISTS idx_config_spt_task_id;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_spt_task_id
|
||||||
|
ON config_spt (task_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 爬塔配置 层级唯一
|
||||||
|
ALTER TABLE config_tower_1
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_tower_1_tower_level;
|
||||||
|
DROP INDEX IF EXISTS idx_config_tower_1_tower_level;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_1_tower_level
|
||||||
|
ON config_tower_1 (tower_level)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE config_tower_110
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_tower_110_tower_level;
|
||||||
|
DROP INDEX IF EXISTS idx_config_tower_110_tower_level;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_110_tower_level
|
||||||
|
ON config_tower_110 (tower_level)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE config_tower_500
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_tower_500_tower_level;
|
||||||
|
DROP INDEX IF EXISTS idx_config_tower_500_tower_level;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_500_tower_level
|
||||||
|
ON config_tower_500 (tower_level)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
ALTER TABLE config_tower_600
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_config_tower_600_tower_level;
|
||||||
|
DROP INDEX IF EXISTS idx_config_tower_600_tower_level;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_600_tower_level
|
||||||
|
ON config_tower_600 (tower_level)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|
||||||
|
|
||||||
|
-- 玩家信息 角色ID唯一
|
||||||
|
ALTER TABLE player_info
|
||||||
|
DROP CONSTRAINT IF EXISTS idx_player_info_player_id;
|
||||||
|
DROP INDEX IF EXISTS idx_player_info_player_id;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_player_info_player_id
|
||||||
|
ON player_info (player_id)
|
||||||
|
WHERE deleted_at IS NULL;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"blazing/common/rpc"
|
||||||
"blazing/cool"
|
"blazing/cool"
|
||||||
"blazing/logic/service/common"
|
"blazing/logic/service/common"
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -26,9 +27,13 @@ var Maincontroller = &Controller{} //注入service
|
|||||||
type Controller struct {
|
type Controller struct {
|
||||||
UID uint32
|
UID uint32
|
||||||
RPCClient *struct {
|
RPCClient *struct {
|
||||||
Kick func(uint32) error
|
Kick func(context.Context, uint32) error
|
||||||
|
|
||||||
RegisterLogic func(uint32, uint32) error
|
RegisterLogic func(context.Context, uint32, uint32) error
|
||||||
|
|
||||||
|
MatchJoinOrUpdate func(context.Context, rpc.PVPMatchJoinPayload) error
|
||||||
|
|
||||||
|
MatchCancel func(context.Context, uint32) error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,26 +72,32 @@ func (h Controller) DASHIbeiR(req *C2s_MASTER_REWARDSR, c *player.Player) (resul
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.ItemList = make([]data.ItemInfo, 0, len(taskInfo.ItemList))
|
result.ItemList = make([]data.ItemInfo, 0, len(taskInfo.ItemList))
|
||||||
c.Service.Task.Exec(masterCupTaskID, func(te *model.Task) bool {
|
taskData, taskErr := c.Service.Task.GetTask(masterCupTaskID)
|
||||||
progress := bitset32.From(te.Data)
|
if taskErr != nil {
|
||||||
if progress.Test(uint(req.ElementType)) {
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
err = errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
consumeMasterCupItems(c, requiredItems)
|
progress := bitset32.From(taskData.Data)
|
||||||
progress.Set(uint(req.ElementType))
|
if progress.Test(uint(req.ElementType)) {
|
||||||
te.Data = progress.Bytes()
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||||
|
}
|
||||||
|
|
||||||
if taskInfo.Pet != nil {
|
if err := consumeMasterCupItems(c, requiredItems); err != nil {
|
||||||
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrInsufficientItems)
|
||||||
result.CaptureTime = taskInfo.Pet.CatchTime
|
}
|
||||||
result.PetTypeId = taskInfo.Pet.ID
|
progress.Set(uint(req.ElementType))
|
||||||
}
|
taskData.Data = progress.Bytes()
|
||||||
|
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||||
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
|
}
|
||||||
|
|
||||||
appendMasterCupRewardItems(c, result, taskInfo.ItemList)
|
if taskInfo.Pet != nil {
|
||||||
return true
|
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
||||||
})
|
result.CaptureTime = taskInfo.Pet.CatchTime
|
||||||
|
result.PetTypeId = taskInfo.Pet.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
appendMasterCupRewardItems(c, result, taskInfo.ItemList)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -126,10 +132,13 @@ func hasEnoughMasterCupItems(c *player.Player, requiredItems []ItemS) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func consumeMasterCupItems(c *player.Player, requiredItems []ItemS) {
|
func consumeMasterCupItems(c *player.Player, requiredItems []ItemS) error {
|
||||||
for _, item := range requiredItems {
|
for _, item := range requiredItems {
|
||||||
c.Service.Item.UPDATE(item.ItemId, -int(item.ItemCnt))
|
if err := c.Service.Item.UPDATE(item.ItemId, -int(item.ItemCnt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendMasterCupRewardItems(c *player.Player, result *S2C_MASTER_REWARDSR, itemList []data.ItemInfo) {
|
func appendMasterCupRewardItems(c *player.Player, result *S2C_MASTER_REWARDSR, itemList []data.ItemInfo) {
|
||||||
|
|||||||
@@ -26,12 +26,11 @@ func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (res
|
|||||||
if data1.EggNum > 10 || data1.EggNum <= 0 {
|
if data1.EggNum > 10 || data1.EggNum <= 0 {
|
||||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
}
|
}
|
||||||
if r < 0 {
|
if r <= 0 || data1.EggNum > r {
|
||||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
||||||
}
|
}
|
||||||
if data1.EggNum > r {
|
if err := c.Service.Item.UPDATE(400501, int(-data1.EggNum)); err != nil {
|
||||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
|
||||||
|
|
||||||
}
|
}
|
||||||
result = &S2C_EGG_GAME_PLAY{ListInfo: []data.ItemInfo{}}
|
result = &S2C_EGG_GAME_PLAY{ListInfo: []data.ItemInfo{}}
|
||||||
if grand.Meet(int(data1.EggNum), 100) {
|
if grand.Meet(int(data1.EggNum), 100) {
|
||||||
@@ -52,8 +51,6 @@ func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (res
|
|||||||
for _, item := range addedItems {
|
for _, item := range addedItems {
|
||||||
result.ListInfo = append(result.ListInfo, data.ItemInfo{ItemId: item.ItemId, ItemCnt: item.ItemCnt})
|
result.ListInfo = append(result.ListInfo, data.ItemInfo{ItemId: item.ItemId, ItemCnt: item.ItemCnt})
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Service.Item.UPDATE(400501, int(-data1.EggNum))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func (h Controller) GET_XUANCAI(data *C2s_GET_XUANCAI, c *player.Player) (result
|
|||||||
|
|
||||||
// 检查该位是否未被选中(避免重复)
|
// 检查该位是否未被选中(避免重复)
|
||||||
if (result.Status & mask) == 0 {
|
if (result.Status & mask) == 0 {
|
||||||
|
result.Status |= mask
|
||||||
itemID := uint32(400686 + randBitIdx + 1)
|
itemID := uint32(400686 + randBitIdx + 1)
|
||||||
selectedItems = append(selectedItems, itemID)
|
selectedItems = append(selectedItems, itemID)
|
||||||
itemMask[itemID] = mask
|
itemMask[itemID] = mask
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ func (h Controller) OnReadyToFight(data *ReadyToFightInboundInfo, c *player.Play
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
go c.FightC.ReadyFight(c)
|
go c.FightC.ReadyFight(c)
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
@@ -30,6 +33,9 @@ func (h Controller) GroupReadyFightFinish(data *GroupReadyFightFinishInboundInfo
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
go c.FightC.ReadyFight(c)
|
go c.FightC.ReadyFight(c)
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
@@ -38,18 +44,25 @@ func (h Controller) GroupUseSkill(data *GroupUseSkillInboundInfo, c *player.Play
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
targetRelation := fight.SkillTargetOpponent
|
targetRelation := fight.SkillTargetOpponent
|
||||||
if data.TargetSide == 1 {
|
if data.TargetSide == 1 {
|
||||||
targetRelation = fight.SkillTargetAlly
|
targetRelation = fight.SkillTargetAlly
|
||||||
}
|
}
|
||||||
h.dispatchFightActionEnvelope(c, fight.NewSkillActionEnvelope(data.SkillId, int(data.ActorIndex), int(data.TargetPos), targetRelation, 0))
|
h.dispatchFightActionEnvelope(c, fight.NewSkillActionEnvelope(data.SkillId, int(data.ActorIndex), int(data.TargetPos), targetRelation, 0))
|
||||||
return nil, 0
|
c.SendPackCmd(7558, nil)
|
||||||
|
return nil, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Controller) GroupUseItem(data *GroupUseItemInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
func (h Controller) GroupUseItem(data *GroupUseItemInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
h.dispatchFightActionEnvelope(c, fight.NewItemActionEnvelope(0, data.ItemId, int(data.ActorIndex), int(data.ActorIndex), fight.SkillTargetSelf))
|
h.dispatchFightActionEnvelope(c, fight.NewItemActionEnvelope(0, data.ItemId, int(data.ActorIndex), int(data.ActorIndex), fight.SkillTargetSelf))
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
@@ -58,6 +71,9 @@ func (h Controller) GroupChangePet(data *GroupChangePetInboundInfo, c *player.Pl
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
h.dispatchFightActionEnvelope(c, fight.NewChangeActionEnvelope(data.CatchTime, int(data.ActorIndex)))
|
h.dispatchFightActionEnvelope(c, fight.NewChangeActionEnvelope(data.CatchTime, int(data.ActorIndex)))
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
@@ -66,6 +82,9 @@ func (h Controller) GroupEscape(data *GroupEscapeInboundInfo, c *player.Player)
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
if fightC, ok := c.FightC.(*fight.FightC); ok && fightC != nil && fightC.LegacyGroupProtocol {
|
if fightC, ok := c.FightC.(*fight.FightC); ok && fightC != nil && fightC.LegacyGroupProtocol {
|
||||||
fightC.SendLegacyEscapeSuccess(c, int(data.ActorIndex))
|
fightC.SendLegacyEscapeSuccess(c, int(data.ActorIndex))
|
||||||
}
|
}
|
||||||
@@ -73,11 +92,28 @@ func (h Controller) GroupEscape(data *GroupEscapeInboundInfo, c *player.Player)
|
|||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h Controller) GroupFightWinClose(data *GroupFightWinCloseInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
|
if c != nil {
|
||||||
|
c.QuitFight()
|
||||||
|
}
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Controller) GroupFightTimeoutExit(data *GroupFightTimeoutExitInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
|
if c != nil {
|
||||||
|
c.QuitFight()
|
||||||
|
}
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
|
|
||||||
// UseSkill 使用技能包
|
// UseSkill 使用技能包
|
||||||
func (h Controller) UseSkill(data *UseSkillInInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
func (h Controller) UseSkill(data *UseSkillInInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
h.dispatchFightActionEnvelope(c, buildLegacyUseSkillEnvelope(data))
|
h.dispatchFightActionEnvelope(c, buildLegacyUseSkillEnvelope(data))
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
@@ -88,6 +124,9 @@ func (h Controller) UseSkillAt(data *UseSkillAtInboundInfo, c *player.Player) (r
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
h.dispatchFightActionEnvelope(c, buildIndexedUseSkillEnvelope(data))
|
h.dispatchFightActionEnvelope(c, buildIndexedUseSkillEnvelope(data))
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
@@ -97,6 +136,9 @@ func (h Controller) Escape(data *EscapeFightInboundInfo, c *player.Player) (resu
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
h.dispatchFightActionEnvelope(c, buildLegacyEscapeEnvelope())
|
h.dispatchFightActionEnvelope(c, buildLegacyEscapeEnvelope())
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
@@ -106,6 +148,9 @@ func (h Controller) ChangePet(data *ChangePetInboundInfo, c *player.Player) (res
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
h.dispatchFightActionEnvelope(c, buildLegacyChangeEnvelope(data))
|
h.dispatchFightActionEnvelope(c, buildLegacyChangeEnvelope(data))
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
@@ -130,6 +175,9 @@ func (h Controller) LoadPercent(data *LoadPercentInboundInfo, c *player.Player)
|
|||||||
if c.FightC == nil {
|
if c.FightC == nil {
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
go c.FightC.LoadPercent(c, int32(data.Percent))
|
go c.FightC.LoadPercent(c, int32(data.Percent))
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
@@ -139,6 +187,9 @@ func (h Controller) UsePetItemInboundInfo(data *UsePetItemInboundInfo, c *player
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
if c.GetSpace().IsTime {
|
if c.GetSpace().IsTime {
|
||||||
if data.ItemId < 300009 {
|
if data.ItemId < 300009 {
|
||||||
go c.FightC.UseSkill(c, 0)
|
go c.FightC.UseSkill(c, 0)
|
||||||
@@ -154,6 +205,9 @@ func (h Controller) FightChat(data *ChatInfo, c *player.Player) (result *fight.N
|
|||||||
if err := h.checkFightStatus(c); err != 0 {
|
if err := h.checkFightStatus(c); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if h.relayRemoteFightCommand(c, data.Head.CMD, data) {
|
||||||
|
return nil, -1
|
||||||
|
}
|
||||||
h.dispatchFightActionEnvelope(c, buildChatEnvelope(data))
|
h.dispatchFightActionEnvelope(c, buildChatEnvelope(data))
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Playe
|
|||||||
}
|
}
|
||||||
|
|
||||||
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||||
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
|
p.Fightinfo.Mode = resolveMapNodeFightMode(mapNode)
|
||||||
|
|
||||||
ai := player.NewAI_player(monsterInfo)
|
ai := player.NewAI_player(monsterInfo)
|
||||||
ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID)
|
ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID)
|
||||||
@@ -72,55 +72,25 @@ func startMapBossFight(
|
|||||||
ai *player.AI_player,
|
ai *player.AI_player,
|
||||||
fn func(model.FightOverInfo),
|
fn func(model.FightOverInfo),
|
||||||
) (*fight.FightC, errorcode.ErrorCode) {
|
) (*fight.FightC, errorcode.ErrorCode) {
|
||||||
ourPets := p.GetPetInfo(100)
|
ourPets := p.GetPetInfo(p.CurrentMapPetLevelLimit())
|
||||||
oppPets := ai.GetPetInfo(0)
|
oppPets := ai.GetPetInfo(0)
|
||||||
if mapNode != nil && mapNode.IsGroupBoss != 0 {
|
if mapNode != nil && mapNode.IsGroupBoss != 0 {
|
||||||
ourSlots := buildGroupBossPetSlots(ourPets, groupBossSlotLimit)
|
if len(ourPets) > 0 && len(oppPets) > 0 {
|
||||||
oppSlots := buildGroupBossPetSlots(oppPets, groupBossSlotLimit)
|
slotLimit := groupBossSlotLimit
|
||||||
if len(ourSlots) > 0 && len(oppSlots) > 0 {
|
if mapNode.PkFlag != 0 {
|
||||||
return fight.NewLegacyGroupFightSingleControllerN(p, ai, ourSlots, oppSlots, fn)
|
slotLimit = 1
|
||||||
|
}
|
||||||
|
return fight.NewLegacyGroupFightSingleController(p, ai, ourPets, oppPets, slotLimit, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fight.NewFight(p, ai, ourPets, oppPets, fn)
|
return fight.NewFight(p, ai, ourPets, oppPets, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildGroupBossPetSlots(pets []model.PetInfo, slotLimit int) [][]model.PetInfo {
|
func resolveMapNodeFightMode(mapNode *configmodel.MapNode) uint32 {
|
||||||
if len(pets) == 0 {
|
if mapNode != nil && mapNode.PkFlag != 0 {
|
||||||
return nil
|
return fightinfo.BattleMode.SINGLE_MODE
|
||||||
}
|
}
|
||||||
|
return fightinfo.BattleMode.MULTI_MODE
|
||||||
slots := make([][]model.PetInfo, 0, slotLimit)
|
|
||||||
for _, pet := range pets {
|
|
||||||
if pet.Hp == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if slotLimit <= 0 {
|
|
||||||
slotLimit = 3
|
|
||||||
}
|
|
||||||
if len(slots) < slotLimit {
|
|
||||||
slots = append(slots, []model.PetInfo{pet})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(slots) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var idx int = 0
|
|
||||||
for _, pet := range pets[len(slots):] {
|
|
||||||
if pet.Hp == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for step := 0; step < len(slots); step++ {
|
|
||||||
slotIdx := (idx + step) % len(slots)
|
|
||||||
if len(slots[slotIdx]) < 6 {
|
|
||||||
slots[slotIdx] = append(slots[slotIdx], pet)
|
|
||||||
idx = (slotIdx + 1) % len(slots)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return slots
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnPlayerFightNpcMonster 战斗野怪
|
// OnPlayerFightNpcMonster 战斗野怪
|
||||||
@@ -128,8 +98,8 @@ func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *pl
|
|||||||
if err = p.CanFight(); err != 0 {
|
if err = p.CanFight(); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if req.Number > 9 {
|
if int(req.Number) >= len(p.Data) {
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
return nil, errorcode.ErrorCodes.ErrPokemonNotHere
|
||||||
}
|
}
|
||||||
|
|
||||||
refPet := p.Data[req.Number]
|
refPet := p.Data[req.Number]
|
||||||
@@ -144,7 +114,7 @@ func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *pl
|
|||||||
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
|
||||||
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
|
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
|
||||||
|
|
||||||
_, err = fight.NewFight(p, ai, p.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
_, err = fight.NewFight(p, ai, p.GetPetInfo(p.CurrentMapPetLevelLimit()), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
|
||||||
handleNpcFightRewards(p, foi, monster)
|
handleNpcFightRewards(p, foi, monster)
|
||||||
})
|
})
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
@@ -266,7 +236,7 @@ func shouldGrantBossWinBonus(fightC *fight.FightC, playerID uint32, bossConfig c
|
|||||||
|
|
||||||
func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) {
|
func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) {
|
||||||
if refPet.ID == 0 {
|
if refPet.ID == 0 {
|
||||||
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotHere
|
||||||
}
|
}
|
||||||
|
|
||||||
monster := model.GenPetInfo(
|
monster := model.GenPetInfo(
|
||||||
|
|||||||
@@ -4,9 +4,21 @@ import (
|
|||||||
"blazing/modules/player/model"
|
"blazing/modules/player/model"
|
||||||
|
|
||||||
"blazing/logic/service/fight"
|
"blazing/logic/service/fight"
|
||||||
|
"blazing/logic/service/fight/pvp"
|
||||||
"blazing/logic/service/player"
|
"blazing/logic/service/player"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (h Controller) relayRemoteFightCommand(c *player.Player, cmd uint32, data any) bool {
|
||||||
|
if c == nil || c.FightC == nil || cmd == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
remote, ok := c.FightC.(*pvp.RemoteFightProxy)
|
||||||
|
if !ok || remote == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return remote.RelayClientCommand(cmd, data)
|
||||||
|
}
|
||||||
|
|
||||||
// dispatchFightActionEnvelope 把控制器层收到的统一动作结构分发回现有 FightI 接口。
|
// dispatchFightActionEnvelope 把控制器层收到的统一动作结构分发回现有 FightI 接口。
|
||||||
func (h Controller) dispatchFightActionEnvelope(c *player.Player, envelope fight.FightActionEnvelope) {
|
func (h Controller) dispatchFightActionEnvelope(c *player.Player, envelope fight.FightActionEnvelope) {
|
||||||
if c == nil || c.FightC == nil {
|
if c == nil || c.FightC == nil {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -207,32 +206,8 @@ func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.Player
|
|||||||
monsterInfo := &model.PlayerInfo{Nick: towerBoss.Name}
|
monsterInfo := &model.PlayerInfo{Nick: towerBoss.Name}
|
||||||
for i, boss := range bosses {
|
for i, boss := range bosses {
|
||||||
monster := model.GenPetInfo(int(boss.MonID), 24, int(boss.Nature), 0, int(boss.Lv), nil, 0)
|
monster := model.GenPetInfo(int(boss.MonID), 24, int(boss.Nature), 0, int(boss.Lv), nil, 0)
|
||||||
if boss.Hp != 0 {
|
monster.ConfigBoss(boss.PetBaseConfig)
|
||||||
monster.Hp = uint32(boss.Hp)
|
appendPetEffects(monster, boss.Effect)
|
||||||
monster.MaxHp = uint32(boss.Hp)
|
|
||||||
}
|
|
||||||
|
|
||||||
for statIdx, prop := range boss.Prop {
|
|
||||||
if prop != 0 {
|
|
||||||
monster.Prop[statIdx] = prop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for skillIdx := 0; skillIdx < len(monster.SkillList) && skillIdx < len(boss.SKill); skillIdx++ {
|
|
||||||
if boss.SKill[skillIdx] != 0 {
|
|
||||||
monster.SkillList[skillIdx].ID = boss.SKill[skillIdx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
effects := service.NewEffectService().Args(boss.Effect)
|
|
||||||
for _, effect := range effects {
|
|
||||||
monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{
|
|
||||||
Idx: uint16(effect.ID),
|
|
||||||
EID: gconv.Uint16(effect.Eid),
|
|
||||||
Args: gconv.Ints(effect.Args),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
monster.CatchTime = uint32(i)
|
monster.CatchTime = uint32(i)
|
||||||
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
|
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"blazing/common/rpc"
|
||||||
"blazing/common/socket/errorcode"
|
"blazing/common/socket/errorcode"
|
||||||
|
"blazing/cool"
|
||||||
"blazing/logic/service/common"
|
"blazing/logic/service/common"
|
||||||
"blazing/logic/service/fight"
|
"blazing/logic/service/fight"
|
||||||
"blazing/logic/service/fight/pvp"
|
"blazing/logic/service/fight/pvp"
|
||||||
"blazing/logic/service/player"
|
"blazing/logic/service/player"
|
||||||
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 表示"宠物王加入"的入站消息数据
|
// 表示"宠物王加入"的入站消息数据
|
||||||
@@ -13,6 +16,8 @@ type PetTOPLEVELnboundInfo struct {
|
|||||||
Head common.TomeeHeader `cmd:"2458" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2458" struc:"skip"`
|
||||||
Mode uint32 //巅峰赛对战模式 19 = 普通模式单精灵 20 = 普通模式多精灵
|
Mode uint32 //巅峰赛对战模式 19 = 普通模式单精灵 20 = 普通模式多精灵
|
||||||
|
|
||||||
|
TianxuanPetIDsLen uint32 `struc:"sizeof=TianxuanPetIDs"`
|
||||||
|
TianxuanPetIDs []uint32 `json:"tianxuanPetIds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoINtop 处理控制器请求。
|
// JoINtop 处理控制器请求。
|
||||||
@@ -21,11 +26,41 @@ func (h Controller) JoINtop(data *PetTOPLEVELnboundInfo, c *player.Player) (resu
|
|||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if Maincontroller.RPCClient == nil || Maincontroller.RPCClient.MatchJoinOrUpdate == nil {
|
||||||
|
pvp.CancelPeakQueue(c)
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||||
|
}
|
||||||
|
fightMode, status, err := pvp.NormalizePeakMode(data.Mode)
|
||||||
|
if err != 0 {
|
||||||
|
pvp.CancelPeakQueue(c)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
joinPayload := rpc.PVPMatchJoinPayload{
|
||||||
|
RuntimeServerID: h.UID,
|
||||||
|
UserID: c.Info.UserID,
|
||||||
|
Nick: c.Info.Nick,
|
||||||
|
FightMode: fightMode,
|
||||||
|
Status: status,
|
||||||
|
IsVip: cool.Config.ServerInfo.IsVip,
|
||||||
|
IsDebug: cool.Config.ServerInfo.IsDebug,
|
||||||
|
CatchTimes: pvp.AvailableCatchTimes(c.GetPetInfo(0)),
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if callErr := Maincontroller.RPCClient.MatchJoinOrUpdate(ctx, joinPayload); callErr != nil {
|
||||||
|
pvp.CancelPeakQueue(c)
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||||
|
}
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelPeakQueue 处理控制器请求。
|
// CancelPeakQueue 处理控制器请求。
|
||||||
func (h Controller) CancelPeakQueue(data *PeakQueueCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
func (h Controller) CancelPeakQueue(data *PeakQueueCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
|
if Maincontroller.RPCClient != nil && Maincontroller.RPCClient.MatchCancel != nil {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||||
|
_ = Maincontroller.RPCClient.MatchCancel(ctx, c.Info.UserID)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
pvp.CancelPeakQueue(c)
|
pvp.CancelPeakQueue(c)
|
||||||
return nil, -1
|
return nil, -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ type GroupEscapeInboundInfo struct {
|
|||||||
ActorIndex uint8
|
ActorIndex uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GroupFightWinCloseInboundInfo struct {
|
||||||
|
Head common.TomeeHeader `cmd:"7574" struc:"skip"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupFightTimeoutExitInboundInfo struct {
|
||||||
|
Head common.TomeeHeader `cmd:"7587" struc:"skip"`
|
||||||
|
}
|
||||||
|
|
||||||
// EscapeFightInboundInfo 定义请求或响应数据结构。
|
// EscapeFightInboundInfo 定义请求或响应数据结构。
|
||||||
type EscapeFightInboundInfo struct {
|
type EscapeFightInboundInfo struct {
|
||||||
Head common.TomeeHeader `cmd:"2410" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2410" struc:"skip"`
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ type C2S_RoomPetInfo struct {
|
|||||||
CatchTime uint32 `json:"catchTime"`
|
CatchTime uint32 `json:"catchTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// C2S_RoomPetShowToggle 基地展示精灵添加/移除请求
|
||||||
|
type C2S_RoomPetShowToggle struct {
|
||||||
|
Head common.TomeeHeader `cmd:"2326" struc:"skip"`
|
||||||
|
CatchTime uint32 `json:"catchTime"`
|
||||||
|
PetID uint32 `json:"petID"`
|
||||||
|
Flag uint32 `json:"flag"` // 1=添加展示, 0=移除展示
|
||||||
|
}
|
||||||
|
|
||||||
// C2S_BUY_FITMENT 定义请求或响应数据结构。
|
// C2S_BUY_FITMENT 定义请求或响应数据结构。
|
||||||
type C2S_BUY_FITMENT struct {
|
type C2S_BUY_FITMENT struct {
|
||||||
Head common.TomeeHeader `cmd:"10004" struc:"skip"`
|
Head common.TomeeHeader `cmd:"10004" struc:"skip"`
|
||||||
|
|||||||
@@ -99,6 +99,12 @@ type GetPetLearnableSkillsInboundInfo struct {
|
|||||||
CatchTime uint32 `json:"catchTime"`
|
CatchTime uint32 `json:"catchTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommitPetSkillsInboundInfo struct {
|
||||||
|
Head common.TomeeHeader `cmd:"52313" struc:"skip"`
|
||||||
|
CatchTime uint32 `json:"catchTime"`
|
||||||
|
Skill [4]uint32 `json:"skill"`
|
||||||
|
}
|
||||||
|
|
||||||
type C2S_PetFusion struct {
|
type C2S_PetFusion struct {
|
||||||
Head common.TomeeHeader `cmd:"2351" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2351" struc:"skip"`
|
||||||
Mcatchtime uint32 `json:"mcatchtime" msgpack:"mcatchtime"`
|
Mcatchtime uint32 `json:"mcatchtime" msgpack:"mcatchtime"`
|
||||||
|
|||||||
@@ -1,56 +1,92 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import "blazing/logic/service/common"
|
import (
|
||||||
|
"blazing/cool"
|
||||||
|
"blazing/logic/service/common"
|
||||||
|
"blazing/modules/player/model"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
|
)
|
||||||
|
|
||||||
type Login struct {
|
// MAIN_LOGIN_IN 定义请求或响应数据结构。
|
||||||
|
type MAIN_LOGIN_IN struct {
|
||||||
Head common.TomeeHeader `cmd:"1001" struc:"skip"`
|
Head common.TomeeHeader `cmd:"1001" struc:"skip"`
|
||||||
Sid []byte `struc:"[16]byte"`
|
Sid []byte `struc:"[16]byte"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeartBeat struct {
|
// CheakSession 校验登录session。
|
||||||
Head common.TomeeHeader `cmd:"2051" struc:"skip"`
|
func (l *MAIN_LOGIN_IN) CheakSession() (bool, uint32) {
|
||||||
Time uint32
|
t1 := hex.EncodeToString(l.Sid)
|
||||||
|
r, err := cool.CacheManager.Get(context.Background(), fmt.Sprintf("session:%d", l.Head.UserID))
|
||||||
|
if err != nil {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
if r.String() != t1 {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
crc32Table := crc32.MakeTable(crc32.IEEE)
|
||||||
|
crcValue := crc32.Checksum([]byte(l.Sid), crc32Table)
|
||||||
|
cool.CacheManager.Remove(context.Background(), fmt.Sprintf("session:%d", l.Head.UserID))
|
||||||
|
return true, crcValue
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeepAlive struct {
|
// SimUserInfoInboundInfo 定义请求或响应数据结构。
|
||||||
Head common.TomeeHeader `cmd:"2052" struc:"skip"`
|
type SimUserInfoInboundInfo struct {
|
||||||
Time uint32
|
Head common.TomeeHeader `cmd:"2051" struc:"skip"`
|
||||||
|
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
// MoreUserInfoInboundInfo 定义请求或响应数据结构。
|
||||||
|
type MoreUserInfoInboundInfo struct {
|
||||||
|
Head common.TomeeHeader `cmd:"2052" struc:"skip"`
|
||||||
|
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AimatInboundInfo 定义请求或响应数据结构。
|
||||||
|
type AimatInboundInfo struct {
|
||||||
Head common.TomeeHeader `cmd:"2104" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2104" struc:"skip"`
|
||||||
UserID uint32
|
ItemId uint32 `description:"物品id 射击激光 物品id为0" codec:"auto" uint:"true"`
|
||||||
ShowNono uint8
|
ShootType uint32 `description:"射击类型 未知 给0" codec:"auto" uint:"true"`
|
||||||
RoomStyle uint32
|
Point model.Pos `description:"射击的坐标 x y" codec:"auto"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserTalk struct {
|
// ChatInboundInfo 定义请求或响应数据结构。
|
||||||
|
type ChatInboundInfo struct {
|
||||||
Head common.TomeeHeader `cmd:"2102" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2102" struc:"skip"`
|
||||||
ReceiverID uint32
|
Reserve uint32 `json:"reserve" fieldDescription:"填充 默认值为0" uint:"true"`
|
||||||
MessageLen uint32 `struc:"sizeof=Message"`
|
MessageLen uint32 `struc:"sizeof=Message"`
|
||||||
Message []byte
|
Message string `json:"message" fieldDescription:"消息内容, 结束符为utf-8的数字0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangeTitle struct {
|
// ChangeColorInboundInfo 定义请求或响应数据结构。
|
||||||
|
type ChangeColorInboundInfo struct {
|
||||||
Head common.TomeeHeader `cmd:"2063" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2063" struc:"skip"`
|
||||||
Title uint32
|
Color uint32 `codec:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangePlayerIcon struct {
|
// ChangeDoodleInboundInfo 定义请求或响应数据结构。
|
||||||
Head common.TomeeHeader `cmd:"2062" struc:"skip"`
|
type ChangeDoodleInboundInfo struct {
|
||||||
Icon uint32
|
Head common.TomeeHeader `cmd:"2062" struc:"skip"`
|
||||||
|
Id uint32 `codec:"id"`
|
||||||
|
Color uint32 `codec:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetMotto struct {
|
// ChangeNONOColorInboundInfo 定义请求或响应数据结构。
|
||||||
Head common.TomeeHeader `cmd:"9012" struc:"skip"`
|
type ChangeNONOColorInboundInfo struct {
|
||||||
|
Head common.TomeeHeader `cmd:"9012" struc:"skip"`
|
||||||
|
Color uint32 `codec:"color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetTask struct {
|
// C2SDanceAction 定义请求或响应数据结构。
|
||||||
Head common.TomeeHeader `cmd:"2103" struc:"skip"`
|
type C2SDanceAction struct {
|
||||||
TaskLen uint32 `struc:"sizeof=TaskList"`
|
Head common.TomeeHeader `cmd:"2103" struc:"skip"`
|
||||||
TaskList []uint32
|
Reserve uint32 `struc:"uint32,big"`
|
||||||
|
Type uint32 `struc:"uint32,big"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// C2SPEOPLE_TRANSFROM 定义请求或响应数据结构。
|
||||||
type C2SPEOPLE_TRANSFROM struct {
|
type C2SPEOPLE_TRANSFROM struct {
|
||||||
Head common.TomeeHeader `cmd:"2111" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2111" struc:"skip"`
|
||||||
SuitID uint32 `struc:"uint32,big"`
|
SuitID uint32 `struc:"uint32,big"`
|
||||||
@@ -70,13 +106,6 @@ type ChangeTitleInboundInfo struct {
|
|||||||
|
|
||||||
// C2S_GET_GIFT_COMPLETE 定义请求或响应数据结构。
|
// C2S_GET_GIFT_COMPLETE 定义请求或响应数据结构。
|
||||||
type C2S_GET_GIFT_COMPLETE struct {
|
type C2S_GET_GIFT_COMPLETE struct {
|
||||||
Head common.TomeeHeader `cmd:"2801" struc:"skip"`
|
Head common.TomeeHeader `cmd:"2801" struc:"skip"`
|
||||||
PassText string `struc:"[16]byte"`
|
PassText string `struc:"[16]byte"`
|
||||||
Type uint32
|
|
||||||
ServerID uint32
|
|
||||||
ServerName string `struc:"[16]byte"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type C2S_GET_DONATION_SERVER_IDS struct {
|
|
||||||
Head common.TomeeHeader `cmd:"2802" struc:"skip"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ func (h Controller) ItemSale(data *C2S_ITEM_SALE, c *player.Player) (result *fig
|
|||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.Service.Item.UPDATE(data.ItemId, -gconv.Int(data.Amount)); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
|
|
||||||
itemConfig := xmlres.ItemsMAP[int(data.ItemId)]
|
itemConfig := xmlres.ItemsMAP[int(data.ItemId)]
|
||||||
if itemConfig.SellPrice != 0 {
|
if itemConfig.SellPrice != 0 {
|
||||||
c.Info.Coins += int64(int64(data.Amount) * int64(itemConfig.SellPrice))
|
c.Info.Coins += int64(int64(data.Amount) * int64(itemConfig.SellPrice))
|
||||||
}
|
}
|
||||||
c.Service.Item.UPDATE(data.ItemId, -gconv.Int(data.Amount))
|
|
||||||
return result, 0
|
return result, 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
// ItemDefaultLeftTime 道具默认剩余时间(毫秒)
|
// ItemDefaultLeftTime 道具默认剩余时间(毫秒)
|
||||||
ItemDefaultLeftTime = 360000
|
ItemDefaultLeftTime = 360000
|
||||||
|
// UniversalNatureItemID 全能性格转化剂Ω
|
||||||
|
UniversalNatureItemID uint32 = 300136
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetUserItemList 获取用户道具列表
|
// GetUserItemList 获取用户道具列表
|
||||||
@@ -33,11 +35,16 @@ func (h Controller) GetUserItemList(data *ItemListInboundInfo, c *player.Player)
|
|||||||
// c: 当前玩家对象
|
// c: 当前玩家对象
|
||||||
// 返回: 使用后的宠物信息和错误码
|
// 返回: 使用后的宠物信息和错误码
|
||||||
func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c *player.Player) (result *item.S2C_USE_PET_ITEM_OUT_OF_FIGHT, err errorcode.ErrorCode) {
|
func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c *player.Player) (result *item.S2C_USE_PET_ITEM_OUT_OF_FIGHT, err errorcode.ErrorCode) {
|
||||||
_, currentPet, found := c.FindPet(data.CatchTime)
|
slot, found := c.FindPetBagSlot(data.CatchTime)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, errorcode.ErrorCodes.Err10401
|
return nil, errorcode.ErrorCodes.Err10401
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if currentPet == nil {
|
||||||
|
return nil, errorcode.ErrorCodes.Err10401
|
||||||
|
}
|
||||||
|
|
||||||
itemID := uint32(data.ItemID)
|
itemID := uint32(data.ItemID)
|
||||||
if c.Service.Item.CheakItem(itemID) == 0 {
|
if c.Service.Item.CheakItem(itemID) == 0 {
|
||||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
@@ -51,7 +58,10 @@ func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c
|
|||||||
return nil, errcode
|
return nil, errcode
|
||||||
}
|
}
|
||||||
refreshPetPaneKeepHP(currentPet, oldHP)
|
refreshPetPaneKeepHP(currentPet, oldHP)
|
||||||
c.Service.Item.UPDATE(itemID, -1)
|
if err := c.Service.Item.UPDATE(itemID, -1); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
|
c.Service.Info.Save(*c.Info)
|
||||||
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
||||||
copier.Copy(&result, currentPet)
|
copier.Copy(&result, currentPet)
|
||||||
return result, 0
|
return result, 0
|
||||||
@@ -83,7 +93,10 @@ func (h Controller) UsePetItemOutOfFight(data *C2S_USE_PET_ITEM_OUT_OF_FIGHT, c
|
|||||||
return nil, errcode
|
return nil, errcode
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Service.Item.UPDATE(itemID, -1)
|
if err := c.Service.Item.UPDATE(itemID, -1); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
|
c.Service.Info.Save(*c.Info)
|
||||||
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
result = &item.S2C_USE_PET_ITEM_OUT_OF_FIGHT{}
|
||||||
copier.Copy(&result, currentPet)
|
copier.Copy(&result, currentPet)
|
||||||
return result, 0
|
return result, 0
|
||||||
@@ -126,7 +139,9 @@ func (h Controller) handlexuancaiItem(currentPet *model.PetInfo, c *player.Playe
|
|||||||
return errorcode.ErrorCodes.ErrItemUnusable
|
return errorcode.ErrorCodes.ErrItemUnusable
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Service.Item.UPDATE(itemid, -100)
|
if err := c.Service.Item.UPDATE(itemid, -100); err != nil {
|
||||||
|
return errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,11 +197,24 @@ func (h Controller) handleRegularPetItem(itemID uint32, currentPet *model.PetInf
|
|||||||
// c: 当前玩家对象
|
// c: 当前玩家对象
|
||||||
// 返回: 无数据和错误码
|
// 返回: 无数据和错误码
|
||||||
func (h Controller) ResetNature(data *C2S_PET_RESET_NATURE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
func (h Controller) ResetNature(data *C2S_PET_RESET_NATURE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
_, currentPet, found := c.FindPet(data.CatchTime)
|
slot, found := c.FindPetBagSlot(data.CatchTime)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, errorcode.ErrorCodes.Err10401
|
return nil, errorcode.ErrorCodes.Err10401
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if currentPet == nil {
|
||||||
|
return nil, errorcode.ErrorCodes.Err10401
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.ItemId != UniversalNatureItemID {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrItemUnusable
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := xmlres.NatureRootMap[int(data.Nature)]; !ok {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrItemUnusable
|
||||||
|
}
|
||||||
|
|
||||||
if c.Service.Item.CheakItem(data.ItemId) <= 0 {
|
if c.Service.Item.CheakItem(data.ItemId) <= 0 {
|
||||||
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
}
|
}
|
||||||
@@ -194,7 +222,10 @@ func (h Controller) ResetNature(data *C2S_PET_RESET_NATURE, c *player.Player) (r
|
|||||||
currentHP := currentPet.Hp
|
currentHP := currentPet.Hp
|
||||||
currentPet.Nature = data.Nature
|
currentPet.Nature = data.Nature
|
||||||
refreshPetPaneKeepHP(currentPet, currentHP)
|
refreshPetPaneKeepHP(currentPet, currentHP)
|
||||||
c.Service.Item.UPDATE(data.ItemId, -1)
|
if err := c.Service.Item.UPDATE(data.ItemId, -1); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
|
c.Service.Info.Save(*c.Info)
|
||||||
return result, 0
|
return result, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,29 +253,38 @@ func (h Controller) UseSpeedupItem(data *C2S_USE_SPEEDUP_ITEM, c *player.Player)
|
|||||||
if c.Info.TwoTimes != 0 {
|
if c.Info.TwoTimes != 0 {
|
||||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||||
}
|
}
|
||||||
c.Info.TwoTimes += 50 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
|
||||||
case 300067:
|
case 300067:
|
||||||
if c.Info.TwoTimes != 0 {
|
if c.Info.TwoTimes != 0 {
|
||||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||||
}
|
}
|
||||||
c.Info.TwoTimes += 25 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
|
||||||
case 300051: // 假设1002是三倍经验加速器道具ID
|
case 300051: // 假设1002是三倍经验加速器道具ID
|
||||||
if c.Info.ThreeTimes != 0 {
|
if c.Info.ThreeTimes != 0 {
|
||||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||||
}
|
}
|
||||||
c.Info.ThreeTimes += 50 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
|
||||||
case 300115:
|
case 300115:
|
||||||
if c.Info.ThreeTimes != 0 {
|
if c.Info.ThreeTimes != 0 {
|
||||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||||
}
|
}
|
||||||
c.Info.ThreeTimes += 30 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError // 未知道具ID
|
return nil, errorcode.ErrorCodes.ErrSystemError // 未知道具ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 扣减道具(数量-1)
|
// 3. 扣减道具(数量-1)
|
||||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data.ItemID {
|
||||||
|
case 300027: // 假设1001是双倍经验加速器道具ID
|
||||||
|
c.Info.TwoTimes += 50 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||||
|
case 300067:
|
||||||
|
c.Info.TwoTimes += 25 // 玩家对象新增 TwoTimesExp 字段存储双倍剩余次数
|
||||||
|
case 300051: // 假设1002是三倍经验加速器道具ID
|
||||||
|
c.Info.ThreeTimes += 50 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||||
|
case 300115:
|
||||||
|
c.Info.ThreeTimes += 30 // 玩家对象新增 ThreeTimesExp 字段存储三倍剩余次数
|
||||||
|
}
|
||||||
result.ThreeTimes = uint32(c.Info.ThreeTimes) // 返回三倍经验剩余次数
|
result.ThreeTimes = uint32(c.Info.ThreeTimes) // 返回三倍经验剩余次数
|
||||||
result.TwoTimes = uint32(c.Info.TwoTimes) // 返回双倍经验剩余次数
|
result.TwoTimes = uint32(c.Info.TwoTimes) // 返回双倍经验剩余次数
|
||||||
|
|
||||||
@@ -275,10 +315,11 @@ func (h Controller) UseEnergyXishou(data *C2S_USE_ENERGY_XISHOU, c *player.Playe
|
|||||||
}
|
}
|
||||||
// 2. 核心业务逻辑:更新能量吸收器剩余次数
|
// 2. 核心业务逻辑:更新能量吸收器剩余次数
|
||||||
// (注:可根据道具ID配置不同的次数加成,此处默认+1)
|
// (注:可根据道具ID配置不同的次数加成,此处默认+1)
|
||||||
|
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
c.Info.EnergyTime += 40 // 玩家对象新增 EnergyTimes 字段存储能量吸收剩余次数
|
c.Info.EnergyTime += 40 // 玩家对象新增 EnergyTimes 字段存储能量吸收剩余次数
|
||||||
|
|
||||||
// 3. 扣减道具(数量-1)
|
|
||||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
|
||||||
result = &item.S2C_USE_ENERGY_XISHOU{
|
result = &item.S2C_USE_ENERGY_XISHOU{
|
||||||
EnergyTimes: uint32(c.Info.EnergyTime),
|
EnergyTimes: uint32(c.Info.EnergyTime),
|
||||||
}
|
}
|
||||||
@@ -309,6 +350,9 @@ func (h Controller) UseAutoFightItem(data *C2S_USE_AUTO_FIGHT_ITEM, c *player.Pl
|
|||||||
if c.Info.AutoFightTime != 0 {
|
if c.Info.AutoFightTime != 0 {
|
||||||
return nil, errorcode.ErrorCodes.ErrItemInUse
|
return nil, errorcode.ErrorCodes.ErrItemInUse
|
||||||
}
|
}
|
||||||
|
if err := c.Service.Item.UPDATE(data.ItemID, -1); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItems
|
||||||
|
}
|
||||||
result = &item.S2C_USE_AUTO_FIGHT_ITEM{}
|
result = &item.S2C_USE_AUTO_FIGHT_ITEM{}
|
||||||
// 2. 核心业务逻辑:开启自动战斗 + 更新剩余次数
|
// 2. 核心业务逻辑:开启自动战斗 + 更新剩余次数
|
||||||
c.Info.AutoFight = 3 // 按需求设置自动战斗flag为3(需测试)
|
c.Info.AutoFight = 3 // 按需求设置自动战斗flag为3(需测试)
|
||||||
@@ -324,8 +368,6 @@ func (h Controller) UseAutoFightItem(data *C2S_USE_AUTO_FIGHT_ITEM, c *player.Pl
|
|||||||
c.Info.AutoFightTime += 50
|
c.Info.AutoFightTime += 50
|
||||||
}
|
}
|
||||||
result.AutoFightTimes = c.Info.AutoFightTime
|
result.AutoFightTimes = c.Info.AutoFightTime
|
||||||
// 3. 扣减道具(数量-1)
|
|
||||||
c.Service.Item.UPDATE(data.ItemID, -1)
|
|
||||||
|
|
||||||
return result, 0
|
return result, 0
|
||||||
}
|
}
|
||||||
|
|||||||
60
logic/controller/item_use_test.go
Normal file
60
logic/controller/item_use_test.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blazing/common/data/xmlres"
|
||||||
|
"blazing/logic/service/player"
|
||||||
|
playermodel "blazing/modules/player/model"
|
||||||
|
blservice "blazing/modules/player/service"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUsePetItemOutOfFightAppliesToBackupPetInMemory(t *testing.T) {
|
||||||
|
petID := firstPetIDForControllerTest(t)
|
||||||
|
backupPet := playermodel.GenPetInfo(petID, 31, 0, 0, 50, nil, 0)
|
||||||
|
if backupPet == nil {
|
||||||
|
t.Fatal("failed to generate backup pet")
|
||||||
|
}
|
||||||
|
if backupPet.MaxHp <= 1 {
|
||||||
|
t.Fatalf("expected generated pet to have max hp > 1, got %d", backupPet.MaxHp)
|
||||||
|
}
|
||||||
|
backupPet.Hp = 1
|
||||||
|
|
||||||
|
testPlayer := player.NewPlayer(nil)
|
||||||
|
testPlayer.Info = &playermodel.PlayerInfo{
|
||||||
|
UserID: 1,
|
||||||
|
PetList: []playermodel.PetInfo{},
|
||||||
|
BackupPetList: []playermodel.PetInfo{*backupPet},
|
||||||
|
}
|
||||||
|
testPlayer.Service = blservice.NewUserService(testPlayer.Info.UserID)
|
||||||
|
|
||||||
|
itemID, recoverHP := firstRecoverHPItemForControllerTest(t)
|
||||||
|
if recoverHP <= 0 {
|
||||||
|
t.Fatalf("expected positive recover hp for item %d, got %d", itemID, recoverHP)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := (Controller{}).UsePetItemOutOfFight(&C2S_USE_PET_ITEM_OUT_OF_FIGHT{
|
||||||
|
CatchTime: backupPet.CatchTime,
|
||||||
|
ItemID: int32(itemID),
|
||||||
|
}, testPlayer)
|
||||||
|
if err != 0 {
|
||||||
|
t.Fatalf("expected backup pet item use to succeed in-memory, got err=%d", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedPet := testPlayer.Info.BackupPetList[0]
|
||||||
|
if updatedPet.Hp <= 1 {
|
||||||
|
t.Fatalf("expected backup pet hp to increase in memory, got hp=%d", updatedPet.Hp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstRecoverHPItemForControllerTest(t *testing.T) (uint32, int) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
for id, cfg := range xmlres.ItemsMAP {
|
||||||
|
if cfg.HP > 0 {
|
||||||
|
return uint32(id), cfg.HP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatal("xmlres.ItemsMAP has no HP recovery item")
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/common/data/share"
|
"blazing/common/data/share"
|
||||||
|
"blazing/common/rpc"
|
||||||
"blazing/common/socket/errorcode"
|
"blazing/common/socket/errorcode"
|
||||||
"blazing/cool"
|
"blazing/cool"
|
||||||
"blazing/logic/service/player"
|
"blazing/logic/service/player"
|
||||||
@@ -31,7 +32,10 @@ func waitUserOffline(userID uint32, timeout time.Duration) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if time.Since(lastKickAt) >= waitUserOfflineKickGap {
|
if time.Since(lastKickAt) >= waitUserOfflineKickGap {
|
||||||
if kickErr := Maincontroller.RPCClient.Kick(userID); kickErr != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||||
|
kickErr := Maincontroller.RPCClient.Kick(ctx, userID)
|
||||||
|
cancel()
|
||||||
|
if kickErr != nil {
|
||||||
cool.Logger.Error(context.Background(), "补踢失败", userID, kickErr)
|
cool.Logger.Error(context.Background(), "补踢失败", userID, kickErr)
|
||||||
}
|
}
|
||||||
lastKickAt = time.Now()
|
lastKickAt = time.Now()
|
||||||
@@ -55,7 +59,9 @@ func (h Controller) Login(data *MAIN_LOGIN_IN, c gnet.Conn) (result *user.LoginM
|
|||||||
}
|
}
|
||||||
|
|
||||||
if onlineServerID, onlineErr := share.ShareManager.GetUserOnline(data.Head.UserID); onlineErr == nil {
|
if onlineServerID, onlineErr := share.ShareManager.GetUserOnline(data.Head.UserID); onlineErr == nil {
|
||||||
kickErr := Maincontroller.RPCClient.Kick(data.Head.UserID) //通知其他服务器踢人
|
ctx, cancel := context.WithTimeout(context.Background(), rpc.ClientCallTimeout)
|
||||||
|
kickErr := Maincontroller.RPCClient.Kick(ctx, data.Head.UserID) //通知其他服务器踢人
|
||||||
|
cancel()
|
||||||
if kickErr != nil {
|
if kickErr != nil {
|
||||||
cool.Logger.Error(context.Background(), "踢人失败", data.Head.UserID, onlineServerID, kickErr)
|
cool.Logger.Error(context.Background(), "踢人失败", data.Head.UserID, onlineServerID, kickErr)
|
||||||
err = errorcode.ErrorCodes.ErrSystemBusyTryLater
|
err = errorcode.ErrorCodes.ErrSystemBusyTryLater
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func (h *Controller) SwitchFlying(data *SwitchFlyingInboundInfo, c *player.Playe
|
|||||||
// PlayerPetCure 处理控制器请求。
|
// PlayerPetCure 处理控制器请求。
|
||||||
func (h *Controller) PlayerPetCure(data *PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的
|
func (h *Controller) PlayerPetCure(data *PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的
|
||||||
_ = data
|
_ = data
|
||||||
|
result = &nono.PetCureOutboundEmpty{}
|
||||||
if c.IsArenaHealLocked() {
|
if c.IsArenaHealLocked() {
|
||||||
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
|
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,9 @@ func (h *Controller) PlayerPetCure(data *PetCureInboundInfo, c *player.Player) (
|
|||||||
for i := range c.Info.PetList {
|
for i := range c.Info.PetList {
|
||||||
c.Info.PetList[i].Cure()
|
c.Info.PetList[i].Cure()
|
||||||
}
|
}
|
||||||
|
for i := range c.Info.BackupPetList {
|
||||||
|
c.Info.BackupPetList[i].Cure()
|
||||||
|
}
|
||||||
c.Info.Coins -= nonoPetCureCost
|
c.Info.Coins -= nonoPetCureCost
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,20 +19,13 @@ func (h Controller) SavePetBagOrder(
|
|||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// PetRetrieveFromWarehouse 领回仓库精灵
|
// PetRetrieveFromWarehouse 从放生仓库领回精灵
|
||||||
func (h Controller) PetRetrieveFromWarehouse(
|
func (h Controller) PetRetrieveFromWarehouse(
|
||||||
data *PET_RETRIEVE, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
data *PET_RETRIEVE, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
if _, ok := player.FindPetBagSlot(data.CatchTime); ok {
|
if !player.Service.Pet.UpdateFree(data.CatchTime, 1, 0) {
|
||||||
return nil, 0
|
return nil, errorcode.ErrorCodes.ErrPokemonIDMismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
petInfo := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
|
||||||
if petInfo == nil {
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
player.AddPetToAvailableBag(petInfo.Data)
|
|
||||||
|
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ func (h Controller) GetPetBargeList(data *PetBargeListInboundInfo, player *playe
|
|||||||
ret.PetBargeList = append(ret.PetBargeList, pet.PetBargeListInfo{
|
ret.PetBargeList = append(ret.PetBargeList, pet.PetBargeListInfo{
|
||||||
PetId: uint32(v.Args[0]),
|
PetId: uint32(v.Args[0]),
|
||||||
EnCntCnt: 1,
|
EnCntCnt: 1,
|
||||||
IsCatched: uint32(v.Results[0]),
|
IsCatched: uint32(v.Results[1]),
|
||||||
IsKilled: uint32(v.Results[1]),
|
IsKilled: uint32(v.Results[0]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ func (h Controller) PetELV(data *C2S_PET_EVOLVTION, c *player.Player) (result *f
|
|||||||
return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti
|
return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti
|
||||||
}
|
}
|
||||||
if branch.EvolvItem != 0 {
|
if branch.EvolvItem != 0 {
|
||||||
c.Service.Item.UPDATE(uint32(branch.EvolvItem), -branch.EvolvItemCount)
|
if err := c.Service.Item.UPDATE(uint32(branch.EvolvItem), -branch.EvolvItemCount); err != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrInsufficientItemsMulti
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPet.ID = uint32(branch.MonTo)
|
currentPet.ID = uint32(branch.MonTo)
|
||||||
|
|||||||
@@ -17,11 +17,16 @@ const (
|
|||||||
// c: 当前玩家对象
|
// c: 当前玩家对象
|
||||||
// 返回: 分配结果和错误码
|
// 返回: 分配结果和错误码
|
||||||
func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
func (h Controller) PetEVDiy(data *PetEV, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
_, currentPet, found := c.FindPet(data.CacthTime)
|
slot, found := c.FindPetBagSlot(data.CacthTime)
|
||||||
if !found {
|
if !found {
|
||||||
return nil, errorcode.ErrorCodes.Err10401
|
return nil, errorcode.ErrorCodes.Err10401
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if currentPet == nil {
|
||||||
|
return nil, errorcode.ErrorCodes.Err10401
|
||||||
|
}
|
||||||
|
|
||||||
var targetTotal uint32
|
var targetTotal uint32
|
||||||
var currentTotal uint32
|
var currentTotal uint32
|
||||||
for i, evValue := range data.EVs {
|
for i, evValue := range data.EVs {
|
||||||
|
|||||||
45
logic/controller/pet_ev_test.go
Normal file
45
logic/controller/pet_ev_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"blazing/logic/service/player"
|
||||||
|
playermodel "blazing/modules/player/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPetEVDiy_AppliesToBackupPet(t *testing.T) {
|
||||||
|
p := player.NewPlayer(nil)
|
||||||
|
p.Info = &playermodel.PlayerInfo{
|
||||||
|
EVPool: 20,
|
||||||
|
PetList: []playermodel.PetInfo{
|
||||||
|
{CatchTime: 1},
|
||||||
|
},
|
||||||
|
BackupPetList: []playermodel.PetInfo{
|
||||||
|
{
|
||||||
|
CatchTime: 2,
|
||||||
|
Level: 100,
|
||||||
|
Ev: [6]uint32{0, 4, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &PetEV{
|
||||||
|
CacthTime: 2,
|
||||||
|
EVs: [6]uint32{0, 8, 4, 0, 0, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := (Controller{}).PetEVDiy(data, p)
|
||||||
|
if err != 0 {
|
||||||
|
t.Fatalf("PetEVDiy returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := p.Info.BackupPetList[0].Ev
|
||||||
|
want := [6]uint32{0, 8, 4, 0, 0, 0}
|
||||||
|
if got != want {
|
||||||
|
t.Fatalf("backup pet EV mismatch, got %v want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotPool, wantPool := p.Info.EVPool, int64(12); gotPool != wantPool {
|
||||||
|
t.Fatalf("EVPool mismatch, got %d want %d", gotPool, wantPool)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,16 +65,33 @@ func (h Controller) PetFusion(data *C2S_PetFusion, c *player.Player) (result *pe
|
|||||||
return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
return result, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||||
}
|
}
|
||||||
|
|
||||||
consumeItems(c, materialCounts)
|
|
||||||
c.Info.Coins -= petFusionCost
|
|
||||||
|
|
||||||
if resultPetID == 0 {
|
if resultPetID == 0 {
|
||||||
if useOptionalItem(c, data.GoldItem1[:], petFusionFailureItemID) {
|
failedAux := *auxPet
|
||||||
result.CostItemFlag = 1
|
if auxPet.Level > 5 {
|
||||||
} else if auxPet.Level > 5 {
|
failedAux.Downgrade(auxPet.Level - 5)
|
||||||
auxPet.Downgrade(auxPet.Level - 5)
|
|
||||||
} else {
|
} else {
|
||||||
auxPet.Downgrade(1)
|
failedAux.Downgrade(1)
|
||||||
|
}
|
||||||
|
txResult, errCode := c.Service.PetFusionTx(
|
||||||
|
*c.Info,
|
||||||
|
data.Mcatchtime,
|
||||||
|
data.Auxcatchtime,
|
||||||
|
materialCounts,
|
||||||
|
data.GoldItem1[:],
|
||||||
|
petFusionKeepAuxItemID,
|
||||||
|
petFusionFailureItemID,
|
||||||
|
petFusionCost,
|
||||||
|
nil,
|
||||||
|
&failedAux,
|
||||||
|
)
|
||||||
|
if errCode != 0 {
|
||||||
|
return result, errCode
|
||||||
|
}
|
||||||
|
c.Info.Coins -= petFusionCost
|
||||||
|
if txResult.CostItemUsed {
|
||||||
|
result.CostItemFlag = 1
|
||||||
|
} else if txResult.UpdatedAux != nil {
|
||||||
|
*auxPet = *txResult.UpdatedAux
|
||||||
}
|
}
|
||||||
return &pet.PetFusionInfo{}, 0
|
return &pet.PetFusionInfo{}, 0
|
||||||
}
|
}
|
||||||
@@ -101,18 +118,37 @@ func (h Controller) PetFusion(data *C2S_PetFusion, c *player.Player) (result *pe
|
|||||||
newPet.RandomByWeightShiny()
|
newPet.RandomByWeightShiny()
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Service.Pet.PetAdd(newPet, 0)
|
txResult, errCode := c.Service.PetFusionTx(
|
||||||
//println(c.Info.UserID, "进行融合", len(c.Info.PetList), masterPet.ID, auxPet.ID, newPet.ID)
|
*c.Info,
|
||||||
|
data.Mcatchtime,
|
||||||
c.PetDel(data.Mcatchtime)
|
data.Auxcatchtime,
|
||||||
if useOptionalItem(c, data.GoldItem1[:], petFusionKeepAuxItemID) {
|
materialCounts,
|
||||||
result.CostItemFlag = 1
|
data.GoldItem1[:],
|
||||||
} else {
|
petFusionKeepAuxItemID,
|
||||||
c.PetDel(data.Auxcatchtime)
|
petFusionFailureItemID,
|
||||||
|
petFusionCost,
|
||||||
|
newPet,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if errCode != 0 {
|
||||||
|
return result, errCode
|
||||||
}
|
}
|
||||||
|
|
||||||
result.ObtainTime = newPet.CatchTime
|
c.Info.Coins -= petFusionCost
|
||||||
result.StarterCpTm = newPet.ID
|
if txResult.CostItemUsed {
|
||||||
|
result.CostItemFlag = 1
|
||||||
|
} else {
|
||||||
|
removePetFromPlayerInfo(c, data.Auxcatchtime)
|
||||||
|
}
|
||||||
|
removePetFromPlayerInfo(c, data.Mcatchtime)
|
||||||
|
|
||||||
|
if txResult.NewPet == nil {
|
||||||
|
return result, errorcode.ErrorCodes.ErrSystemError
|
||||||
|
}
|
||||||
|
c.Info.PetList = append(c.Info.PetList, *txResult.NewPet)
|
||||||
|
|
||||||
|
result.ObtainTime = txResult.NewPet.CatchTime
|
||||||
|
result.StarterCpTm = txResult.NewPet.ID
|
||||||
return result, 0
|
return result, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,21 +185,10 @@ func hasEnoughItems(c *player.Player, itemCounts map[uint32]int) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func consumeItems(c *player.Player, itemCounts map[uint32]int) {
|
func removePetFromPlayerInfo(c *player.Player, catchTime uint32) {
|
||||||
for itemID, count := range itemCounts {
|
index, _, ok := c.FindPet(catchTime)
|
||||||
_ = c.Service.Item.UPDATE(itemID, -count)
|
if !ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
c.Info.PetList = append(c.Info.PetList[:index], c.Info.PetList[index+1:]...)
|
||||||
|
|
||||||
func useOptionalItem(c *player.Player, itemIDs []uint32, target uint32) bool {
|
|
||||||
if c.Service.Item.CheakItem(target) <= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, itemID := range itemIDs {
|
|
||||||
if itemID == target {
|
|
||||||
_ = c.Service.Item.UPDATE(target, -1)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,22 @@ import (
|
|||||||
"blazing/common/socket/errorcode"
|
"blazing/common/socket/errorcode"
|
||||||
"blazing/logic/service/common"
|
"blazing/logic/service/common"
|
||||||
"blazing/logic/service/pet"
|
"blazing/logic/service/pet"
|
||||||
"blazing/logic/service/player"
|
playersvc "blazing/logic/service/player"
|
||||||
"blazing/modules/player/model"
|
"blazing/modules/player/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPetInfo 获取精灵信息
|
// GetPetInfo 获取精灵信息
|
||||||
func (h Controller) GetPetInfo(
|
func (h Controller) GetPetInfo(
|
||||||
data *GetPetInfoInboundInfo,
|
data *GetPetInfoInboundInfo,
|
||||||
player *player.Player) (result *model.PetInfo,
|
player *playersvc.Player) (result *model.PetInfo,
|
||||||
err errorcode.ErrorCode) {
|
err errorcode.ErrorCode) {
|
||||||
_, petInfo, found := player.FindPet(data.CatchTime)
|
levelLimit := player.CurrentMapPetLevelLimit()
|
||||||
if found {
|
if slot, found := player.FindPetBagSlot(data.CatchTime); found {
|
||||||
result = petInfo
|
if petInfo := slot.PetInfoPtr(); petInfo != nil {
|
||||||
return result, 0
|
petCopy := playersvc.ApplyPetLevelLimit(*petInfo, levelLimit)
|
||||||
|
result = &petCopy
|
||||||
|
return result, 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
ret := player.Service.Pet.PetInfoOneByCatchTime(data.CatchTime)
|
||||||
@@ -24,16 +27,18 @@ func (h Controller) GetPetInfo(
|
|||||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||||
}
|
}
|
||||||
|
|
||||||
result = &ret.Data
|
petData := ret.Data
|
||||||
|
petData = playersvc.ApplyPetLevelLimit(petData, levelLimit)
|
||||||
|
result = &petData
|
||||||
return result, 0
|
return result, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserBagPetInfo 获取主背包和并列备用精灵列表
|
// GetUserBagPetInfo 获取主背包和并列备用精灵列表
|
||||||
func (h Controller) GetUserBagPetInfo(
|
func (h Controller) GetUserBagPetInfo(
|
||||||
data *GetUserBagPetInfoInboundEmpty,
|
data *GetUserBagPetInfoInboundEmpty,
|
||||||
player *player.Player) (result *pet.GetUserBagPetInfoOutboundInfo,
|
player *playersvc.Player) (result *pet.GetUserBagPetInfoOutboundInfo,
|
||||||
err errorcode.ErrorCode) {
|
err errorcode.ErrorCode) {
|
||||||
return player.GetUserBagPetInfo(), 0
|
return player.GetUserBagPetInfo(player.CurrentMapPetLevelLimit()), 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPetListInboundEmpty 定义请求或响应数据结构。
|
// GetPetListInboundEmpty 定义请求或响应数据结构。
|
||||||
@@ -44,7 +49,7 @@ type GetPetListInboundEmpty struct {
|
|||||||
// GetPetList 获取当前主背包列表
|
// GetPetList 获取当前主背包列表
|
||||||
func (h Controller) GetPetList(
|
func (h Controller) GetPetList(
|
||||||
data *GetPetListInboundEmpty,
|
data *GetPetListInboundEmpty,
|
||||||
player *player.Player) (result *pet.GetPetListOutboundInfo,
|
player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
|
||||||
err errorcode.ErrorCode) {
|
err errorcode.ErrorCode) {
|
||||||
return buildPetListOutboundInfo(player.Info.PetList), 0
|
return buildPetListOutboundInfo(player.Info.PetList), 0
|
||||||
}
|
}
|
||||||
@@ -57,7 +62,7 @@ type GetPetListFreeInboundEmpty struct {
|
|||||||
// GetPetReleaseList 获取仓库可放生列表
|
// GetPetReleaseList 获取仓库可放生列表
|
||||||
func (h Controller) GetPetReleaseList(
|
func (h Controller) GetPetReleaseList(
|
||||||
data *GetPetListFreeInboundEmpty,
|
data *GetPetListFreeInboundEmpty,
|
||||||
player *player.Player) (result *pet.GetPetListOutboundInfo,
|
player *playersvc.Player) (result *pet.GetPetListOutboundInfo,
|
||||||
err errorcode.ErrorCode) {
|
err errorcode.ErrorCode) {
|
||||||
|
|
||||||
return buildPetListOutboundInfo(player.WarehousePetList()), 0
|
return buildPetListOutboundInfo(player.WarehousePetList()), 0
|
||||||
@@ -66,14 +71,13 @@ func (h Controller) GetPetReleaseList(
|
|||||||
// PlayerShowPet 精灵展示
|
// PlayerShowPet 精灵展示
|
||||||
func (h Controller) PlayerShowPet(
|
func (h Controller) PlayerShowPet(
|
||||||
data *PetShowInboundInfo,
|
data *PetShowInboundInfo,
|
||||||
player *player.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) {
|
player *playersvc.Player) (result *pet.PetShowOutboundInfo, err errorcode.ErrorCode) {
|
||||||
result = &pet.PetShowOutboundInfo{
|
result = &pet.PetShowOutboundInfo{
|
||||||
UserID: data.Head.UserID,
|
UserID: data.Head.UserID,
|
||||||
CatchTime: data.CatchTime,
|
CatchTime: data.CatchTime,
|
||||||
Flag: data.Flag,
|
Flag: data.Flag,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, currentPet, ok := player.FindPet(data.CatchTime)
|
|
||||||
if data.Flag == 0 {
|
if data.Flag == 0 {
|
||||||
player.SetPetDisplay(0, nil)
|
player.SetPetDisplay(0, nil)
|
||||||
player.GetSpace().RefreshUserInfo(player)
|
player.GetSpace().RefreshUserInfo(player)
|
||||||
@@ -81,7 +85,14 @@ func (h Controller) PlayerShowPet(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
// 仅允许背包精灵跟随:仓库中的精灵不允许跟随
|
||||||
|
slot, found := player.FindPetBagSlot(data.CatchTime)
|
||||||
|
if !found {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrCannotShowBagPokemon
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if currentPet == nil {
|
||||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,39 @@ import (
|
|||||||
"blazing/logic/service/fight"
|
"blazing/logic/service/fight"
|
||||||
"blazing/logic/service/pet"
|
"blazing/logic/service/pet"
|
||||||
"blazing/logic/service/player"
|
"blazing/logic/service/player"
|
||||||
|
playermodel "blazing/modules/player/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func petSetExpLimit(currentPet *playermodel.PetInfo) int64 {
|
||||||
|
if currentPet == nil || currentPet.Level >= 100 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
simulatedPet := *currentPet
|
||||||
|
allowedExp := simulatedPet.NextLvExp - simulatedPet.Exp
|
||||||
|
if allowedExp < 0 {
|
||||||
|
allowedExp = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for simulatedPet.Level < 100 && simulatedPet.NextLvExp > 0 {
|
||||||
|
simulatedPet.Level++
|
||||||
|
simulatedPet.Update(true)
|
||||||
|
if simulatedPet.Level >= 100 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
allowedExp += simulatedPet.NextLvExp
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowedExp
|
||||||
|
}
|
||||||
|
|
||||||
|
func minInt64(a, b int64) int64 {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
// PetReleaseToWarehouse 将精灵从仓库包中放生
|
// PetReleaseToWarehouse 将精灵从仓库包中放生
|
||||||
func (h Controller) PetReleaseToWarehouse(
|
func (h Controller) PetReleaseToWarehouse(
|
||||||
data *PET_ROWEI, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
data *PET_ROWEI, player *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
@@ -17,9 +48,8 @@ func (h Controller) PetReleaseToWarehouse(
|
|||||||
if inBag || inBackup || freeForbidden == 1 {
|
if inBag || inBackup || freeForbidden == 1 {
|
||||||
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
|
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
|
||||||
}
|
}
|
||||||
|
if !player.Service.Pet.UpdateFree(data.CatchTime, 0, 1) {
|
||||||
if !player.Service.Pet.UpdateFree(data.CatchTime, 1) {
|
return nil, errorcode.ErrorCodes.ErrCannotReleaseNonWarehouse
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, 0
|
return nil, 0
|
||||||
@@ -32,9 +62,11 @@ func (h Controller) PetOneCure(
|
|||||||
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
|
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
|
||||||
}
|
}
|
||||||
|
|
||||||
_, currentPet, ok := player.FindPet(data.CatchTime)
|
if slot, ok := player.FindPetBagSlot(data.CatchTime); ok {
|
||||||
if ok {
|
currentPet := slot.PetInfoPtr()
|
||||||
defer currentPet.Cure()
|
if currentPet != nil {
|
||||||
|
defer currentPet.Cure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pet.PetOneCureOutboundInfo{
|
return &pet.PetOneCureOutboundInfo{
|
||||||
@@ -63,11 +95,17 @@ func (h Controller) PetFirst(
|
|||||||
func (h Controller) SetPetExp(
|
func (h Controller) SetPetExp(
|
||||||
data *PetSetExpInboundInfo,
|
data *PetSetExpInboundInfo,
|
||||||
player *player.Player) (result *pet.PetSetExpOutboundInfo, err errorcode.ErrorCode) {
|
player *player.Player) (result *pet.PetSetExpOutboundInfo, err errorcode.ErrorCode) {
|
||||||
_, currentPet, found := player.FindPet(data.CatchTime)
|
slot, found := player.FindPetBagSlot(data.CatchTime)
|
||||||
if !found || currentPet.Level >= 100 {
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if !found || currentPet == nil || currentPet.Level >= 100 {
|
||||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, errorcode.ErrorCodes.ErrSystemError
|
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, errorcode.ErrorCodes.ErrSystemError
|
||||||
}
|
}
|
||||||
|
|
||||||
player.AddPetExp(currentPet, data.Exp)
|
allowedExp := petSetExpLimit(currentPet)
|
||||||
|
if allowedExp <= 0 {
|
||||||
|
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, errorcode.ErrorCodes.ErrSystemError
|
||||||
|
}
|
||||||
|
|
||||||
|
player.AddPetExp(currentPet, minInt64(data.Exp, allowedExp))
|
||||||
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, 0
|
return &pet.PetSetExpOutboundInfo{Exp: player.Info.ExpPool}, 0
|
||||||
}
|
}
|
||||||
|
|||||||
75
logic/controller/pet_manage_test.go
Normal file
75
logic/controller/pet_manage_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blazing/common/data/xmlres"
|
||||||
|
"blazing/common/socket/errorcode"
|
||||||
|
"blazing/logic/service/player"
|
||||||
|
playermodel "blazing/modules/player/model"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func firstPetIDForControllerTest(t *testing.T) int {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
for id := range xmlres.PetMAP {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Fatal("xmlres.PetMAP is empty")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetPetExpCapsLevelAt100(t *testing.T) {
|
||||||
|
petID := firstPetIDForControllerTest(t)
|
||||||
|
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 99, nil, 0)
|
||||||
|
if petInfo == nil {
|
||||||
|
t.Fatal("failed to generate test pet")
|
||||||
|
}
|
||||||
|
|
||||||
|
expPool := petInfo.NextLvExp + 10_000
|
||||||
|
testPlayer := player.NewPlayer(nil)
|
||||||
|
testPlayer.Info = &playermodel.PlayerInfo{
|
||||||
|
ExpPool: expPool,
|
||||||
|
PetList: []playermodel.PetInfo{*petInfo},
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPet := &testPlayer.Info.PetList[0]
|
||||||
|
result, err := (Controller{}).SetPetExp(&PetSetExpInboundInfo{
|
||||||
|
CatchTime: currentPet.CatchTime,
|
||||||
|
Exp: expPool,
|
||||||
|
}, testPlayer)
|
||||||
|
if err != 0 {
|
||||||
|
t.Fatalf("expected SetPetExp to succeed, got err=%d", err)
|
||||||
|
}
|
||||||
|
if currentPet.Level != 100 {
|
||||||
|
t.Fatalf("expected pet level to stop at 100, got %d", currentPet.Level)
|
||||||
|
}
|
||||||
|
if result.Exp != 10_000 {
|
||||||
|
t.Fatalf("expected overflow exp to remain in pool, got %d", result.Exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetPetExpRejectsPetAtLevel100(t *testing.T) {
|
||||||
|
petID := firstPetIDForControllerTest(t)
|
||||||
|
petInfo := playermodel.GenPetInfo(petID, 31, 0, 0, 100, nil, 0)
|
||||||
|
if petInfo == nil {
|
||||||
|
t.Fatal("failed to generate test pet")
|
||||||
|
}
|
||||||
|
|
||||||
|
testPlayer := player.NewPlayer(nil)
|
||||||
|
testPlayer.Info = &playermodel.PlayerInfo{
|
||||||
|
ExpPool: 50_000,
|
||||||
|
PetList: []playermodel.PetInfo{*petInfo},
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := (Controller{}).SetPetExp(&PetSetExpInboundInfo{
|
||||||
|
CatchTime: petInfo.CatchTime,
|
||||||
|
Exp: 12_345,
|
||||||
|
}, testPlayer)
|
||||||
|
if err != errorcode.ErrorCodes.ErrSystemError {
|
||||||
|
t.Fatalf("expected level-100 pet to be rejected, got err=%d", err)
|
||||||
|
}
|
||||||
|
if result.Exp != 50_000 {
|
||||||
|
t.Fatalf("expected exp pool to remain unchanged, got %d", result.Exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,18 @@ type GetPetLearnableSkillsOutboundInfo struct {
|
|||||||
SkillList []uint32 `json:"skillList"`
|
SkillList []uint32 `json:"skillList"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSameUint32Slice(a []uint32, b []uint32) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for index := range a {
|
||||||
|
if a[index] != b[index] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func collectPetLearnableSkillList(currentPet *model.PetInfo) []uint32 {
|
func collectPetLearnableSkillList(currentPet *model.PetInfo) []uint32 {
|
||||||
skillSet := make(map[uint32]struct{})
|
skillSet := make(map[uint32]struct{})
|
||||||
skills := make([]uint32, 0)
|
skills := make([]uint32, 0)
|
||||||
@@ -55,8 +67,9 @@ func (h Controller) GetPetLearnableSkills(
|
|||||||
data *GetPetLearnableSkillsInboundInfo,
|
data *GetPetLearnableSkillsInboundInfo,
|
||||||
c *player.Player,
|
c *player.Player,
|
||||||
) (result *GetPetLearnableSkillsOutboundInfo, err errorcode.ErrorCode) {
|
) (result *GetPetLearnableSkillsOutboundInfo, err errorcode.ErrorCode) {
|
||||||
_, currentPet, ok := c.FindPet(data.CatchTime)
|
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||||
if !ok {
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if !ok || currentPet == nil {
|
||||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +82,9 @@ func (h Controller) GetPetLearnableSkills(
|
|||||||
func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result *pet.ChangeSkillOutInfo, err errorcode.ErrorCode) {
|
func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result *pet.ChangeSkillOutInfo, err errorcode.ErrorCode) {
|
||||||
const setSkillCost = 50
|
const setSkillCost = 50
|
||||||
|
|
||||||
_, currentPet, ok := c.FindPet(data.CatchTime)
|
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||||
if !ok {
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if !ok || currentPet == nil {
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +149,9 @@ func (h Controller) SetPetSkill(data *ChangeSkillInfo, c *player.Player) (result
|
|||||||
func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
const skillSortCost = 50
|
const skillSortCost = 50
|
||||||
|
|
||||||
_, currentPet, ok := c.FindPet(data.CapTm)
|
slot, ok := c.FindPetBagSlot(data.CapTm)
|
||||||
if !ok {
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if !ok || currentPet == nil {
|
||||||
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,3 +199,89 @@ func (h Controller) SortPetSkills(data *C2S_Skill_Sort, c *player.Player) (resul
|
|||||||
|
|
||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitPetSkills 按最终技能列表一次性提交学习/替换/排序结果。
|
||||||
|
func (h Controller) CommitPetSkills(
|
||||||
|
data *CommitPetSkillsInboundInfo,
|
||||||
|
c *player.Player,
|
||||||
|
) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||||
|
const setSkillCost = 50
|
||||||
|
const skillSortCost = 50
|
||||||
|
|
||||||
|
slot, ok := c.FindPetBagSlot(data.CatchTime)
|
||||||
|
currentPet := slot.PetInfoPtr()
|
||||||
|
if !ok || currentPet == nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSkillSet := make(map[uint32]model.SkillInfo, len(currentPet.SkillList))
|
||||||
|
currentSkillOrder := make([]uint32, 0, len(currentPet.SkillList))
|
||||||
|
for _, skill := range currentPet.SkillList {
|
||||||
|
if skill.ID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentSkillSet[skill.ID] = skill
|
||||||
|
currentSkillOrder = append(currentSkillOrder, skill.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalSkillIDs := make([]uint32, 0, 4)
|
||||||
|
usedSkillSet := make(map[uint32]struct{}, 4)
|
||||||
|
for _, skillID := range data.Skill {
|
||||||
|
if skillID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := usedSkillSet[skillID]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
usedSkillSet[skillID] = struct{}{}
|
||||||
|
finalSkillIDs = append(finalSkillIDs, skillID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(finalSkillIDs) == 0 {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||||
|
}
|
||||||
|
if len(finalSkillIDs) > 4 {
|
||||||
|
finalSkillIDs = finalSkillIDs[:4]
|
||||||
|
}
|
||||||
|
if isSameUint32Slice(currentSkillOrder, finalSkillIDs) {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
learnableSkillSet := make(map[uint32]struct{})
|
||||||
|
for _, skillID := range collectPetLearnableSkillList(currentPet) {
|
||||||
|
learnableSkillSet[skillID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
newSkillCount := 0
|
||||||
|
finalSkillList := make([]model.SkillInfo, 0, len(finalSkillIDs))
|
||||||
|
for _, skillID := range finalSkillIDs {
|
||||||
|
if skill, exists := currentSkillSet[skillID]; exists {
|
||||||
|
finalSkillList = append(finalSkillList, skill)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := learnableSkillSet[skillID]; !exists {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||||
|
}
|
||||||
|
skillInfo, exists := xmlres.SkillMap[int(skillID)]
|
||||||
|
if !exists {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemBusy
|
||||||
|
}
|
||||||
|
newSkillCount++
|
||||||
|
finalSkillList = append(finalSkillList, model.SkillInfo{
|
||||||
|
ID: skillID,
|
||||||
|
PP: uint32(skillInfo.MaxPP),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCost := int64(newSkillCount * setSkillCost)
|
||||||
|
if newSkillCount == 0 {
|
||||||
|
totalCost += int64(skillSortCost)
|
||||||
|
}
|
||||||
|
if totalCost > 0 && !c.GetCoins(totalCost) {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSunDouInsufficient10016
|
||||||
|
}
|
||||||
|
c.Info.Coins -= totalCost
|
||||||
|
currentPet.SkillList = finalSkillList
|
||||||
|
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,15 +17,13 @@ func (h Controller) IsCollect(
|
|||||||
ID: data.Type,
|
ID: data.Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||||
|
if taskErr == nil {
|
||||||
r := bitset32.From(te.Data)
|
r := bitset32.From(taskData.Data)
|
||||||
// 分支未完成时,标记完成并发放奖励
|
|
||||||
if r.Test(uint(data.Type)) {
|
if r.Test(uint(data.Type)) {
|
||||||
result.IsCom = 1
|
result.IsCom = 1
|
||||||
}
|
}
|
||||||
return false
|
}
|
||||||
})
|
|
||||||
|
|
||||||
_, ok := lo.Find([]uint32{1, 2, 3, 4, 301}, func(item uint32) bool {
|
_, ok := lo.Find([]uint32{1, 2, 3, 4, 301}, func(item uint32) bool {
|
||||||
return data.Type == item
|
return data.Type == item
|
||||||
@@ -59,14 +57,17 @@ func (h Controller) Collect(
|
|||||||
return data.Type == item
|
return data.Type == item
|
||||||
})
|
})
|
||||||
if res == model.Completed && ok { //这块是为了兼容旧版本
|
if res == model.Completed && ok { //这块是为了兼容旧版本
|
||||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||||
|
if taskErr != nil {
|
||||||
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
|
}
|
||||||
|
|
||||||
r := bitset32.From(te.Data)
|
r := bitset32.From(taskData.Data)
|
||||||
|
r.Set(uint(data.Type))
|
||||||
r.Set(uint(data.Type))
|
taskData.Data = r.Bytes()
|
||||||
te.Data = r.Bytes()
|
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||||
return true
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
})
|
}
|
||||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,21 +81,22 @@ func (h Controller) Collect(
|
|||||||
if !lo.Contains(validIDs, data.ID) {
|
if !lo.Contains(validIDs, data.ID) {
|
||||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
}
|
}
|
||||||
c.Service.Task.Exec(uint32(1335), func(te *model.Task) bool {
|
taskData, taskErr := c.Service.Task.GetTask(uint32(1335))
|
||||||
|
if taskErr != nil {
|
||||||
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
|
}
|
||||||
|
|
||||||
r := bitset32.From(te.Data)
|
r := bitset32.From(taskData.Data)
|
||||||
// 分支未完成时,标记完成并发放奖励
|
if !r.Test(uint(data.Type)) {
|
||||||
if !r.Test(uint(data.Type)) {
|
r.Set(uint(data.Type))
|
||||||
r.Set(uint(data.Type))
|
taskData.Data = r.Bytes()
|
||||||
te.Data = r.Bytes()
|
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||||
r := model.GenPetInfo(int(data.ID), -1, -1, 0, 1, nil, 0)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
|
||||||
c.Service.Pet.PetAdd(r, 0)
|
|
||||||
result.CatchTime = r.CatchTime
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
petInfo := model.GenPetInfo(int(data.ID), -1, -1, 0, 1, nil, 0)
|
||||||
})
|
c.Service.Pet.PetAdd(petInfo, 0)
|
||||||
|
result.CatchTime = petInfo.CatchTime
|
||||||
|
}
|
||||||
|
|
||||||
if result.CatchTime == 0 {
|
if result.CatchTime == 0 {
|
||||||
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"blazing/common/socket/errorcode"
|
"blazing/common/socket/errorcode"
|
||||||
"blazing/modules/player/model"
|
"blazing/modules/player/model"
|
||||||
|
"blazing/modules/player/service"
|
||||||
|
|
||||||
"blazing/logic/service/pet"
|
"blazing/logic/service/pet"
|
||||||
"blazing/logic/service/player"
|
"blazing/logic/service/player"
|
||||||
@@ -32,17 +33,45 @@ func (h Controller) GetFitmentUsing(data *FitmentUseringInboundInfo, c *player.P
|
|||||||
func (h Controller) GetRoomPetShowInfo(data *PetRoomListInboundInfo, c *player.Player) (result *room.PetRoomListOutboundInfo, err errorcode.ErrorCode) {
|
func (h Controller) GetRoomPetShowInfo(data *PetRoomListInboundInfo, c *player.Player) (result *room.PetRoomListOutboundInfo, err errorcode.ErrorCode) {
|
||||||
result = &room.PetRoomListOutboundInfo{}
|
result = &room.PetRoomListOutboundInfo{}
|
||||||
result.Pets = make([]pet.PetShortInfo, 0)
|
result.Pets = make([]pet.PetShortInfo, 0)
|
||||||
roomInfo := c.Service.Room.Get(data.TargetUserID)
|
showPets := service.NewPetService(data.TargetUserID).GetShowPets()
|
||||||
for _, catchTime := range roomInfo.ShowPokemon {
|
for i := range showPets {
|
||||||
petInfo := c.Service.Pet.PetInfoOneOther(data.TargetUserID, catchTime)
|
var petShortInfo pet.PetShortInfo
|
||||||
if petInfo.Data.ID == 0 {
|
copier.Copy(&petShortInfo, &showPets[i].Data)
|
||||||
|
result.Pets = append(result.Pets, petShortInfo)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRoomPetShowInfo 设置基地展示精灵并返回最新展示列表(cmd:2323)
|
||||||
|
func (h Controller) SetRoomPetShowInfo(data *C2S_PET_ROOM_SHOW, c *player.Player) (result *room.S2C_PET_ROOM_SHOW, err errorcode.ErrorCode) {
|
||||||
|
result = &room.S2C_PET_ROOM_SHOW{}
|
||||||
|
result.PetShowList = make([]pet.PetShortInfo, 0)
|
||||||
|
|
||||||
|
catchTimes := make([]uint32, 0, len(data.PetShowList))
|
||||||
|
seen := make(map[uint32]struct{}, len(data.PetShowList))
|
||||||
|
for _, item := range data.PetShowList {
|
||||||
|
ct := uint32(item.CatchTime)
|
||||||
|
if ct == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var petShortInfo pet.PetShortInfo
|
if _, ok := seen[ct]; ok {
|
||||||
copier.Copy(&petShortInfo, &petInfo.Data)
|
continue
|
||||||
if petInfo.ID != 0 {
|
|
||||||
result.Pets = append(result.Pets, petShortInfo)
|
|
||||||
}
|
}
|
||||||
|
seen[ct] = struct{}{}
|
||||||
|
catchTimes = append(catchTimes, ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
petSvc := service.NewPetService(c.Info.UserID)
|
||||||
|
if !petSvc.SetShowCatchTimes(catchTimes) {
|
||||||
|
err = errorcode.ErrorCodes.ErrSystemError
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
showPets := petSvc.GetShowPets()
|
||||||
|
for i := range showPets {
|
||||||
|
var petShortInfo pet.PetShortInfo
|
||||||
|
copier.Copy(&petShortInfo, &showPets[i].Data)
|
||||||
|
result.PetShowList = append(result.PetShowList, petShortInfo)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -77,6 +106,9 @@ func (h Controller) GetAllFurniture(data *FitmentAllInboundEmpty, c *player.Play
|
|||||||
// 返回: 精灵详细信息和错误码
|
// 返回: 精灵详细信息和错误码
|
||||||
func (h Controller) GetRoomPetInfo(data *C2S_RoomPetInfo, c *player.Player) (result *pet.RoomPetInfo, err errorcode.ErrorCode) {
|
func (h Controller) GetRoomPetInfo(data *C2S_RoomPetInfo, c *player.Player) (result *pet.RoomPetInfo, err errorcode.ErrorCode) {
|
||||||
petInfo := c.Service.Pet.PetInfoOneOther(data.UserID, data.CatchTime)
|
petInfo := c.Service.Pet.PetInfoOneOther(data.UserID, data.CatchTime)
|
||||||
|
if petInfo == nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
|
||||||
|
}
|
||||||
result = &pet.RoomPetInfo{}
|
result = &pet.RoomPetInfo{}
|
||||||
copier.CopyWithOption(result, &petInfo.Data, copier.Option{DeepCopy: true})
|
copier.CopyWithOption(result, &petInfo.Data, copier.Option{DeepCopy: true})
|
||||||
result.OwnerId = data.UserID
|
result.OwnerId = data.UserID
|
||||||
|
|||||||
@@ -2,11 +2,8 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/common/socket/errorcode"
|
"blazing/common/socket/errorcode"
|
||||||
"blazing/logic/service/pet"
|
|
||||||
"blazing/logic/service/player"
|
"blazing/logic/service/player"
|
||||||
"blazing/logic/service/room"
|
"blazing/logic/service/room"
|
||||||
|
|
||||||
"github.com/jinzhu/copier"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetFitment 设置基地家具摆放
|
// SetFitment 设置基地家具摆放
|
||||||
@@ -18,29 +15,3 @@ func (h Controller) SetFitment(data *SET_FITMENT, c *player.Player) (result *roo
|
|||||||
c.Service.Room.Set(data.Fitments)
|
c.Service.Room.Set(data.Fitments)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPet 设置基地展示的精灵
|
|
||||||
// data: 包含精灵展示列表的输入信息
|
|
||||||
// c: 当前玩家对象
|
|
||||||
// 返回: 精灵展示列表和错误码
|
|
||||||
func (h Controller) SetPet(data *C2S_PET_ROOM_SHOW, c *player.Player) (result *room.S2C_PET_ROOM_SHOW, err errorcode.ErrorCode) {
|
|
||||||
var showPetCatchTimes []uint32
|
|
||||||
for _, petShowInfo := range data.PetShowList {
|
|
||||||
if petShowInfo.CatchTime != 0 {
|
|
||||||
showPetCatchTimes = append(showPetCatchTimes, petShowInfo.CatchTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.Service.Room.Show(showPetCatchTimes)
|
|
||||||
result = &room.S2C_PET_ROOM_SHOW{}
|
|
||||||
result.PetShowList = make([]pet.PetShortInfo, len(showPetCatchTimes))
|
|
||||||
for _, catchTime := range showPetCatchTimes {
|
|
||||||
petInfo := c.Service.Pet.PetInfoOneByCatchTime(catchTime)
|
|
||||||
if petInfo == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var petShortInfo pet.PetShortInfo
|
|
||||||
copier.Copy(&petShortInfo, &petInfo.Data)
|
|
||||||
result.PetShowList = append(result.PetShowList, petShortInfo)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,25 +2,23 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/common/socket/errorcode"
|
"blazing/common/socket/errorcode"
|
||||||
"blazing/cool"
|
|
||||||
logicplayer "blazing/logic/service/player"
|
logicplayer "blazing/logic/service/player"
|
||||||
"blazing/logic/service/user"
|
"blazing/logic/service/user"
|
||||||
|
baseservice "blazing/modules/base/service"
|
||||||
configservice "blazing/modules/config/service"
|
configservice "blazing/modules/config/service"
|
||||||
playerservice "blazing/modules/player/service"
|
playerservice "blazing/modules/player/service"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DonationServerIDs 返回当前可用于捐赠冠名的服务器ID列表。
|
|
||||||
func (h Controller) DonationServerIDs(data *C2S_GET_DONATION_SERVER_IDS, player *logicplayer.Player) (result *user.S2C_GET_DONATION_SERVER_IDS, err errorcode.ErrorCode) {
|
|
||||||
return &user.S2C_GET_DONATION_SERVER_IDS{
|
|
||||||
ServerIDs: configservice.NewServerService().GetDonationAvailableServerIDs(),
|
|
||||||
}, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CDK 处理控制器请求。
|
// CDK 处理控制器请求。
|
||||||
func (h Controller) CDK(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player) (result *user.S2C_GET_GIFT_COMPLETE, err errorcode.ErrorCode) {
|
func (h Controller) CDK(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player) (result *user.S2C_GET_GIFT_COMPLETE, err errorcode.ErrorCode) {
|
||||||
result = &user.S2C_GET_GIFT_COMPLETE{Type: data.Type}
|
result = &user.S2C_GET_GIFT_COMPLETE{}
|
||||||
|
|
||||||
|
userInfo := baseservice.NewBaseSysUserService().GetPerson(data.Head.UserID)
|
||||||
|
if userInfo == nil || userInfo.QQ == 0 {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrCannotPerformAction
|
||||||
|
}
|
||||||
|
|
||||||
cdkCode := strings.Trim(data.PassText, "\x00")
|
cdkCode := strings.Trim(data.PassText, "\x00")
|
||||||
cdkService := configservice.NewCdkService()
|
cdkService := configservice.NewCdkService()
|
||||||
@@ -30,6 +28,9 @@ func (h Controller) CDK(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player)
|
|||||||
if r == nil {
|
if r == nil {
|
||||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeNotExists
|
return nil, errorcode.ErrorCodes.ErrMolecularCodeNotExists
|
||||||
}
|
}
|
||||||
|
if r.Type != configservice.CDKTypeReward {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrMolecularCodeNotExists
|
||||||
|
}
|
||||||
if r.BindUserId != 0 && r.BindUserId != data.Head.UserID {
|
if r.BindUserId != 0 && r.BindUserId != data.Head.UserID {
|
||||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeFrozen
|
return nil, errorcode.ErrorCodes.ErrMolecularCodeFrozen
|
||||||
}
|
}
|
||||||
@@ -39,37 +40,16 @@ func (h Controller) CDK(data *C2S_GET_GIFT_COMPLETE, player *logicplayer.Player)
|
|||||||
if !player.Service.Cdk.CanGet(uint32(r.ID)) {
|
if !player.Service.Cdk.CanGet(uint32(r.ID)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.CDKType == configservice.CDKTypeServerNaming {
|
|
||||||
if data.Type != configservice.CDKTypeServerNaming {
|
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
|
||||||
}
|
|
||||||
|
|
||||||
serverName := cool.Filter.Replace(strings.Trim(data.ServerName, "\x00"), '*')
|
|
||||||
if data.ServerID == 0 || serverName == "" {
|
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
|
||||||
}
|
|
||||||
|
|
||||||
serverInfo, useErr := cdkService.UseServerNamingCDK(nil, cdkCode, data.Head.UserID, data.ServerID, serverName)
|
|
||||||
if useErr != nil {
|
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Flag = 1
|
|
||||||
result.ServerID = serverInfo.OnlineID
|
|
||||||
result.ServerName = serverInfo.Name
|
|
||||||
player.Service.Cdk.Log(uint32(r.ID))
|
|
||||||
return result, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Type == configservice.CDKTypeServerNaming {
|
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
|
||||||
}
|
|
||||||
if !cdkService.Set(cdkCode) {
|
if !cdkService.Set(cdkCode) {
|
||||||
return nil, errorcode.ErrorCodes.ErrMolecularCodeGiftsGone
|
return nil, errorcode.ErrorCodes.ErrMolecularCodeGiftsGone
|
||||||
}
|
}
|
||||||
|
|
||||||
reward, grantErr := playerservice.NewCdkService(data.Head.UserID).GrantConfigReward(uint32(r.ID))
|
reward, grantErr := playerservice.NewCdkService(data.Head.UserID).GrantConfigReward(
|
||||||
|
uint32(r.ID),
|
||||||
|
func(itemID uint32, count int64) bool {
|
||||||
|
return player.ItemAdd(int64(itemID), count)
|
||||||
|
},
|
||||||
|
)
|
||||||
if grantErr != nil {
|
if grantErr != nil {
|
||||||
return nil, errorcode.ErrorCodes.ErrSystemError
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,15 @@ func (h Controller) AcceptTask(data *AcceptTaskInboundInfo, c *player.Player) (r
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Info.SetTask(int(data.TaskId), model.Accepted)
|
c.Info.SetTask(int(data.TaskId), model.Accepted)
|
||||||
c.Service.Task.Exec(uint32(data.TaskId), func(t *model.Task) bool {
|
taskData, taskErr := c.Service.Task.GetTask(uint32(data.TaskId))
|
||||||
t.Data = []uint32{}
|
if taskErr != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
|
}
|
||||||
|
taskData.Data = []uint32{}
|
||||||
|
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
})
|
|
||||||
result = &task.AcceptTaskOutboundInfo{}
|
result = &task.AcceptTaskOutboundInfo{}
|
||||||
result.TaskId = data.TaskId
|
result.TaskId = data.TaskId
|
||||||
return result, 0
|
return result, 0
|
||||||
@@ -48,10 +51,14 @@ func (h Controller) AddTaskBuf(data *AddTaskBufInboundInfo, c *player.Player) (r
|
|||||||
if c.Info.GetTask(int(data.TaskId)) != model.Accepted {
|
if c.Info.GetTask(int(data.TaskId)) != model.Accepted {
|
||||||
return result, errorcode.ErrorCodes.ErrAwardAlreadyClaimed
|
return result, errorcode.ErrorCodes.ErrAwardAlreadyClaimed
|
||||||
}
|
}
|
||||||
c.Service.Task.Exec(data.TaskId, func(taskEx *model.Task) bool {
|
taskData, taskErr := c.Service.Task.GetTask(data.TaskId)
|
||||||
taskEx.Data = data.TaskList
|
if taskErr != nil {
|
||||||
return true
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
})
|
}
|
||||||
|
taskData.Data = data.TaskList
|
||||||
|
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
|
||||||
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
|
}
|
||||||
return result, 0
|
return result, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,31 +77,16 @@ func (h Controller) CompleteTask(data1 *CompleteTaskInboundInfo, c *player.Playe
|
|||||||
// if service.NewTaskService().IsAcceptable(data1.TaskId) == nil {
|
// if service.NewTaskService().IsAcceptable(data1.TaskId) == nil {
|
||||||
// return nil, errorcode.ErrorCodes.ErrSystemError
|
// return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
// }
|
// }
|
||||||
c.Info.SetTask(int(data1.TaskId), model.Completed)
|
|
||||||
|
|
||||||
result = &task.CompleteTaskOutboundInfo{
|
result = &task.CompleteTaskOutboundInfo{
|
||||||
TaskId: data1.TaskId,
|
TaskId: data1.TaskId,
|
||||||
ItemList: make([]data.ItemInfo, 0),
|
ItemList: make([]data.ItemInfo, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
taskInfo := task.GetTaskInfo(int(data1.TaskId), int(data1.OutState))
|
if _, err = c.ApplyTaskCompletion(data1.TaskId, int(data1.OutState), result); err != 0 {
|
||||||
if taskInfo == nil {
|
return nil, err
|
||||||
return nil, errorcode.ErrorCodes.ErrNeedCompleteTaskForPrize
|
|
||||||
}
|
}
|
||||||
|
if taskErr := c.Info.SetTask(int(data1.TaskId), model.Completed); taskErr != nil {
|
||||||
if taskInfo.Pet != nil {
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
|
|
||||||
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
|
|
||||||
result.CaptureTime = taskInfo.Pet.CatchTime
|
|
||||||
result.PetTypeId = taskInfo.Pet.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range taskInfo.ItemList {
|
|
||||||
success := c.ItemAdd(item.ItemId, item.ItemCnt)
|
|
||||||
if success {
|
|
||||||
result.ItemList = append(result.ItemList, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, 0 //通过PUB/SUB回包
|
return result, 0 //通过PUB/SUB回包
|
||||||
@@ -105,11 +97,12 @@ func (h Controller) GetTaskBuf(data *GetTaskBufInboundInfo, c *player.Player) (r
|
|||||||
result = &task.GetTaskBufOutboundInfo{
|
result = &task.GetTaskBufOutboundInfo{
|
||||||
TaskId: data.TaskId,
|
TaskId: data.TaskId,
|
||||||
}
|
}
|
||||||
c.Service.Task.Exec(data.TaskId, func(te *model.Task) bool {
|
|
||||||
|
|
||||||
result.TaskList = te.Data
|
taskData, taskErr := c.Service.Task.GetTask(data.TaskId)
|
||||||
return false
|
if taskErr != nil {
|
||||||
})
|
return nil, errorcode.ErrorCodes.ErrSystemError
|
||||||
|
}
|
||||||
|
result.TaskList = taskData.Data
|
||||||
|
|
||||||
return result, 0
|
return result, 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func PprofWeb() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 所有端口都失败时的兜底
|
// 所有端口都失败时的兜底
|
||||||
errMsg := fmt.Sprintf("[FATAL] 端口9909/9910均监听失败,pprof服务启动失败")
|
errMsg := "[FATAL] 端口9909/9910均监听失败,pprof服务启动失败"
|
||||||
fmt.Println(errMsg)
|
fmt.Println(errMsg)
|
||||||
// 可选:根据业务需求决定是否panic
|
// 可选:根据业务需求决定是否panic
|
||||||
// panic(errMsg)
|
// panic(errMsg)
|
||||||
@@ -148,7 +148,7 @@ func monitorMemAndQuit() {
|
|||||||
|
|
||||||
// 4. 超70%阈值,执行优雅退出
|
// 4. 超70%阈值,执行优雅退出
|
||||||
if usedRatio >= memThresholdRatio {
|
if usedRatio >= memThresholdRatio {
|
||||||
log.Fatalf("内存占比达%.1f%%,超过90%阈值,程序开始退出", usedRatio*100)
|
log.Fatalf("内存占比达%.1f%%,超过90%%阈值,程序开始退出", usedRatio*100)
|
||||||
// ########## 可选:这里添加你的优雅清理逻辑 ##########
|
// ########## 可选:这里添加你的优雅清理逻辑 ##########
|
||||||
// 如:关闭数据库连接、释放文件句柄、保存业务状态、推送退出告警等
|
// 如:关闭数据库连接、释放文件句柄、保存业务状态、推送退出告警等
|
||||||
cleanup()
|
cleanup()
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"blazing/cool"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"reflect"
|
||||||
|
|
||||||
"github.com/lunixbochs/struc"
|
"github.com/lunixbochs/struc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TomeeHeader 定义协议包头。
|
// TomeeHeader 定义协议包头。
|
||||||
type TomeeHeader struct {
|
type TomeeHeader struct {
|
||||||
Len uint32 `json:"len"` // 包总长度(包头 + 数据体)。
|
Len uint32 `json:"len"` // 包总长度(包头 + 数据体)。
|
||||||
Version byte `json:"version" struc:"[1]byte"` // 协议版本。
|
Version byte `json:"version" struc:"[1]byte"` // 协议版本。
|
||||||
CMD uint32 `json:"cmdId" struc:"uint32"` // 命令 ID。
|
CMD uint32 `json:"cmdId" struc:"uint32"` // 命令 ID。
|
||||||
UserID uint32 `json:"userId"` // 玩家 ID。
|
UserID uint32 `json:"userId"` // 玩家 ID。
|
||||||
Result uint32 `json:"result"` // 结果码。
|
Result uint32 `json:"result"` // 结果码。
|
||||||
Data []byte `json:"data" struc:"skip"` // 数据体,序列化时跳过。
|
Data []byte `json:"data" struc:"skip"` // 数据体,序列化时跳过。
|
||||||
Res []byte `struc:"skip"` // 预留返回数据,序列化时跳过。
|
Res []byte `struc:"skip"` // 预留返回数据,序列化时跳过。
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTomeeHeader 创建用于下行封包的默认 TomeeHeader。
|
// NewTomeeHeader 创建用于下行封包的默认 TomeeHeader。
|
||||||
@@ -39,7 +41,14 @@ func (h *TomeeHeader) Pack(data any) []byte {
|
|||||||
var data1 bytes.Buffer
|
var data1 bytes.Buffer
|
||||||
err := struc.Pack(&data1, data)
|
err := struc.Pack(&data1, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
cool.Logger.Error(context.Background(),
|
||||||
|
"struc pack failed",
|
||||||
|
"cmd", h.CMD,
|
||||||
|
"userID", h.UserID,
|
||||||
|
"result", h.Result,
|
||||||
|
"payloadType", packetPayloadType(data),
|
||||||
|
"err", err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data1.Bytes()) == 0 {
|
if len(data1.Bytes()) == 0 {
|
||||||
@@ -89,3 +98,10 @@ func (h *TomeeHeader) packHeaderWithData(data []byte) []byte {
|
|||||||
|
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func packetPayloadType(data any) string {
|
||||||
|
if data == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return reflect.TypeOf(data).String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func (f *FightC) openActionWindow() {
|
|||||||
f.actionMu.Lock()
|
f.actionMu.Lock()
|
||||||
f.acceptActions = true
|
f.acceptActions = true
|
||||||
f.pendingActions = f.pendingActions[:0]
|
f.pendingActions = f.pendingActions[:0]
|
||||||
|
f.pendingHead = 0
|
||||||
f.actionRound.Store(uint32(f.Round))
|
f.actionRound.Store(uint32(f.Round))
|
||||||
f.actionMu.Unlock()
|
f.actionMu.Unlock()
|
||||||
}
|
}
|
||||||
@@ -52,6 +53,7 @@ func (f *FightC) closeActionWindow() {
|
|||||||
f.actionMu.Lock()
|
f.actionMu.Lock()
|
||||||
f.acceptActions = false
|
f.acceptActions = false
|
||||||
f.pendingActions = f.pendingActions[:0]
|
f.pendingActions = f.pendingActions[:0]
|
||||||
|
f.pendingHead = 0
|
||||||
f.actionRound.Store(0)
|
f.actionRound.Store(0)
|
||||||
f.actionMu.Unlock()
|
f.actionMu.Unlock()
|
||||||
|
|
||||||
@@ -73,8 +75,10 @@ func (f *FightC) submitAction(act action.BattleActionI) {
|
|||||||
f.actionMu.Unlock()
|
f.actionMu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
f.compactPendingActionsLocked()
|
||||||
replaceIndex := -1
|
replaceIndex := -1
|
||||||
for i, pending := range f.pendingActions {
|
for i := f.pendingHead; i < len(f.pendingActions); i++ {
|
||||||
|
pending := f.pendingActions[i]
|
||||||
if pending == nil || actionSlotKeyFromAction(pending) != actionSlotKeyFromAction(act) {
|
if pending == nil || actionSlotKeyFromAction(pending) != actionSlotKeyFromAction(act) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -82,6 +86,10 @@ func (f *FightC) submitAction(act action.BattleActionI) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if replaceIndex >= 0 {
|
if replaceIndex >= 0 {
|
||||||
|
if f.LegacyGroupProtocol {
|
||||||
|
f.actionMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
f.pendingActions[replaceIndex] = act
|
f.pendingActions[replaceIndex] = act
|
||||||
} else {
|
} else {
|
||||||
f.pendingActions = append(f.pendingActions, act)
|
f.pendingActions = append(f.pendingActions, act)
|
||||||
@@ -101,15 +109,23 @@ func (f *FightC) submitAction(act action.BattleActionI) {
|
|||||||
|
|
||||||
func (f *FightC) nextAction() action.BattleActionI {
|
func (f *FightC) nextAction() action.BattleActionI {
|
||||||
f.actionMu.Lock()
|
f.actionMu.Lock()
|
||||||
if len(f.pendingActions) == 0 {
|
if f.pendingHead >= len(f.pendingActions) {
|
||||||
|
f.pendingActions = f.pendingActions[:0]
|
||||||
|
f.pendingHead = 0
|
||||||
f.actionMu.Unlock()
|
f.actionMu.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
act := f.pendingActions[0]
|
act := f.pendingActions[f.pendingHead]
|
||||||
copy(f.pendingActions, f.pendingActions[1:])
|
f.pendingActions[f.pendingHead] = nil
|
||||||
f.pendingActions = f.pendingActions[:len(f.pendingActions)-1]
|
f.pendingHead++
|
||||||
hasMore := len(f.pendingActions) > 0
|
hasMore := f.pendingHead < len(f.pendingActions)
|
||||||
|
if !hasMore {
|
||||||
|
f.pendingActions = f.pendingActions[:0]
|
||||||
|
f.pendingHead = 0
|
||||||
|
} else {
|
||||||
|
f.compactPendingActionsLocked()
|
||||||
|
}
|
||||||
notify := f.actionNotify
|
notify := f.actionNotify
|
||||||
f.actionMu.Unlock()
|
f.actionMu.Unlock()
|
||||||
|
|
||||||
@@ -123,6 +139,22 @@ func (f *FightC) nextAction() action.BattleActionI {
|
|||||||
return act
|
return act
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FightC) compactPendingActionsLocked() {
|
||||||
|
if f.pendingHead == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if f.pendingHead < len(f.pendingActions)/2 && len(f.pendingActions) < cap(f.pendingActions) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remaining := len(f.pendingActions) - f.pendingHead
|
||||||
|
copy(f.pendingActions, f.pendingActions[f.pendingHead:])
|
||||||
|
for i := remaining; i < len(f.pendingActions); i++ {
|
||||||
|
f.pendingActions[i] = nil
|
||||||
|
}
|
||||||
|
f.pendingActions = f.pendingActions[:remaining]
|
||||||
|
f.pendingHead = 0
|
||||||
|
}
|
||||||
|
|
||||||
// 玩家逃跑/无响应/掉线
|
// 玩家逃跑/无响应/掉线
|
||||||
func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
|
func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
|
||||||
if f.closefight {
|
if f.closefight {
|
||||||
@@ -143,7 +175,7 @@ func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
f.overl.Do(func() {
|
f.overl.Do(func() {
|
||||||
f.Reason = res
|
f.Reason = normalizeFightOverReason(res)
|
||||||
if f.GetInputByPlayer(c, true) != nil {
|
if f.GetInputByPlayer(c, true) != nil {
|
||||||
f.WinnerId = f.GetInputByPlayer(c, true).UserID
|
f.WinnerId = f.GetInputByPlayer(c, true).UserID
|
||||||
}
|
}
|
||||||
@@ -295,9 +327,8 @@ func (f *FightC) ReadyFight(c common.PlayerI) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.Broadcast(func(ff *input.Input) {
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
|
p.SendPackCmd(2404, &info.S2C_2404{UserID: c.GetInfo().UserID})
|
||||||
ff.Player.SendPackCmd(2404, &info.S2C_2404{UserID: c.GetInfo().UserID})
|
|
||||||
})
|
})
|
||||||
// 2. 标记当前玩家已准备完成
|
// 2. 标记当前玩家已准备完成
|
||||||
input := f.GetInputByPlayer(c, false)
|
input := f.GetInputByPlayer(c, false)
|
||||||
@@ -339,7 +370,7 @@ func (f *FightC) collectFightPetInfos(inputs []*input.Input) []info.FightPetInfo
|
|||||||
Hp: currentPet.Info.Hp,
|
Hp: currentPet.Info.Hp,
|
||||||
MaxHp: currentPet.Info.MaxHp,
|
MaxHp: currentPet.Info.MaxHp,
|
||||||
Level: currentPet.Info.Level,
|
Level: currentPet.Info.Level,
|
||||||
Catchable: uint32(fighter.CanCapture),
|
Catchable: fightPetCatchableFlag(fighter.CanCapture),
|
||||||
}
|
}
|
||||||
if fighter.AttackValue != nil {
|
if fighter.AttackValue != nil {
|
||||||
fightInfo.Prop = fighter.AttackValue.Prop
|
fightInfo.Prop = fighter.AttackValue.Prop
|
||||||
@@ -349,6 +380,13 @@ func (f *FightC) collectFightPetInfos(inputs []*input.Input) []info.FightPetInfo
|
|||||||
return infos
|
return infos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fightPetCatchableFlag(catchRate int) uint32 {
|
||||||
|
if catchRate > 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// checkBothPlayersReady 检查PVP战斗中双方是否都已准备完成
|
// checkBothPlayersReady 检查PVP战斗中双方是否都已准备完成
|
||||||
// 参数c为当前准备的玩家,返回true表示双方均准备完成
|
// 参数c为当前准备的玩家,返回true表示双方均准备完成
|
||||||
func (f *FightC) checkBothPlayersReady(currentPlayer common.PlayerI) bool {
|
func (f *FightC) checkBothPlayersReady(currentPlayer common.PlayerI) bool {
|
||||||
@@ -369,8 +407,12 @@ func (f *FightC) startBattle(startInfo info.FightStartOutboundInfo) {
|
|||||||
go f.battleLoop()
|
go f.battleLoop()
|
||||||
|
|
||||||
// 向双方广播战斗开始信息
|
// 向双方广播战斗开始信息
|
||||||
f.Broadcast(func(ff *input.Input) {
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
f.sendFightPacket(ff.Player, fightPacketStart, &startInfo)
|
if f.LegacyGroupProtocol {
|
||||||
|
f.sendLegacyGroupStart(p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.sendFightPacket(p, fightPacketStart, &startInfo)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,13 @@ func (e *NewSel0) IsOwner() bool {
|
|||||||
return e.ID().GetCatchTime() == source.CurPet[0].Info.CatchTime
|
return e.ID().GetCatchTime() == source.CurPet[0].Info.CatchTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *NewSel0) CurrentSkillHit() bool {
|
||||||
|
if e.Ctx().SkillEntity == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return e.Ctx().SkillEntity.AttackTime != 0
|
||||||
|
}
|
||||||
|
|
||||||
// 免疫"能力(battle_lv)下降"
|
// 免疫"能力(battle_lv)下降"
|
||||||
type NewSel1 struct {
|
type NewSel1 struct {
|
||||||
NewSel0
|
NewSel0
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ type NewSel38 struct {
|
|||||||
NewSel0
|
NewSel0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *NewSel39) DamageAdd(t *info.DamageZone) bool {
|
func (e *NewSel38) DamageAdd(t *info.DamageZone) bool {
|
||||||
|
|
||||||
if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime {
|
if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Damage = t.Damage.Add(t.Damage.Mul(alpacadecimal.NewFromInt(int64(e.Args()[0].IntPart()))))
|
t.Damage = t.Damage.Add(t.Damage.Mul(e.Args()[0].Div(alpacadecimal.NewFromInt(100))))
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ func (e *NewSel49) Action_end_ex() bool {
|
|||||||
if e.Ctx().SkillEntity == nil {
|
if e.Ctx().SkillEntity == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if !e.CurrentSkillHit() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if e.Ctx().SkillEntity.Category() == info.Category.PHYSICAL {
|
if e.Ctx().SkillEntity.Category() == info.Category.PHYSICAL {
|
||||||
e.attackType = 1
|
e.attackType = 1
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ func (e *NewSel6) Action_end_ex() bool {
|
|||||||
if e.Ctx().SkillEntity.Category() != info.Category.PHYSICAL {
|
if e.Ctx().SkillEntity.Category() != info.Category.PHYSICAL {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if !e.CurrentSkillHit() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 概率判定(Args()[1]为触发概率)
|
// 3. 概率判定(Args()[1]为触发概率)
|
||||||
success, _, _ := e.Input.Player.Roll(int(e.Args()[1].IntPart()), 100)
|
success, _, _ := e.Input.Player.Roll(int(e.Args()[1].IntPart()), 100)
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ func (e *NewSel74) Action_end_ex() bool {
|
|||||||
if e.Ctx().SkillEntity.Category() == info.Category.STATUS {
|
if e.Ctx().SkillEntity.Category() == info.Category.STATUS {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if !e.CurrentSkillHit() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// 检查概率是否触发
|
// 检查概率是否触发
|
||||||
success, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100)
|
success, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100)
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ func (e *NewSel78) Action_end_ex() bool {
|
|||||||
if e.Ctx().SkillEntity.Category() != info.Category.SPECIAL {
|
if e.Ctx().SkillEntity.Category() != info.Category.SPECIAL {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if !e.CurrentSkillHit() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// 检查概率是否触发
|
// 检查概率是否触发
|
||||||
success, _, _ := e.Input.Player.Roll(int(e.Args()[1].IntPart()), 100)
|
success, _, _ := e.Input.Player.Roll(int(e.Args()[1].IntPart()), 100)
|
||||||
|
|||||||
@@ -94,6 +94,11 @@ type CrossServerBanPickPetInfo struct {
|
|||||||
MaxHp uint32 `json:"maxHp"`
|
MaxHp uint32 `json:"maxHp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CrossServerBanPickTianxuanPetInfo struct {
|
||||||
|
PetID uint32 `json:"petId"`
|
||||||
|
Name string `struc:"[16]byte" json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
type CrossServerBanPickStartOutboundInfo struct {
|
type CrossServerBanPickStartOutboundInfo struct {
|
||||||
SessionIDLen uint32 `struc:"sizeof=SessionID"`
|
SessionIDLen uint32 `struc:"sizeof=SessionID"`
|
||||||
SessionID string `json:"sessionId"`
|
SessionID string `json:"sessionId"`
|
||||||
@@ -106,12 +111,19 @@ type CrossServerBanPickStartOutboundInfo struct {
|
|||||||
|
|
||||||
TimeoutSeconds uint32 `json:"timeoutSeconds"`
|
TimeoutSeconds uint32 `json:"timeoutSeconds"`
|
||||||
SelectableCount uint32 `json:"selectableCount"`
|
SelectableCount uint32 `json:"selectableCount"`
|
||||||
|
TianxuanSelectableCount uint32 `json:"tianxuanSelectableCount"`
|
||||||
|
|
||||||
MyPetsLen uint32 `struc:"sizeof=MyPets"`
|
MyPetsLen uint32 `struc:"sizeof=MyPets"`
|
||||||
MyPets []CrossServerBanPickPetInfo `json:"myPets"`
|
MyPets []CrossServerBanPickPetInfo `json:"myPets"`
|
||||||
|
|
||||||
|
MyTianxuanPetsLen uint32 `struc:"sizeof=MyTianxuanPets"`
|
||||||
|
MyTianxuanPets []CrossServerBanPickTianxuanPetInfo `json:"myTianxuanPets"`
|
||||||
|
|
||||||
OpponentPetsLen uint32 `struc:"sizeof=OpponentPets"`
|
OpponentPetsLen uint32 `struc:"sizeof=OpponentPets"`
|
||||||
OpponentPets []CrossServerBanPickPetInfo `json:"opponentPets"`
|
OpponentPets []CrossServerBanPickPetInfo `json:"opponentPets"`
|
||||||
|
|
||||||
|
OpponentTianxuanPetsLen uint32 `struc:"sizeof=OpponentTianxuanPets"`
|
||||||
|
OpponentTianxuanPets []CrossServerBanPickTianxuanPetInfo `json:"opponentTianxuanPets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleFightInviteInboundInfo 处理战斗邀请的入站消息
|
// HandleFightInviteInboundInfo 处理战斗邀请的入站消息
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func (e *Effect1181) OnSkill() bool {
|
|||||||
type Effect1182 struct{ node.EffectNode }
|
type Effect1182 struct{ node.EffectNode }
|
||||||
|
|
||||||
func (e *Effect1182) Skill_Use() bool {
|
func (e *Effect1182) Skill_Use() bool {
|
||||||
if len(e.Args()) < 2 || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil || e.Ctx().Opp == nil || e.Ctx().Opp.CurPet[0] == nil {
|
if len(e.Args()) < 2 || e.Ctx().Our == nil || e.Ctx().Our.CurPet[0] == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,9 +153,15 @@ func (e *Effect1182) Skill_Use() bool {
|
|||||||
if targetHP.Cmp(alpacadecimal.Zero) < 0 {
|
if targetHP.Cmp(alpacadecimal.Zero) < 0 {
|
||||||
targetHP = alpacadecimal.Zero
|
targetHP = alpacadecimal.Zero
|
||||||
}
|
}
|
||||||
if e.Ctx().Opp.CurPet[0].GetHP().Cmp(targetHP) > 0 {
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
e.Ctx().Opp.CurPet[0].Info.Hp = uint32(targetHP.IntPart())
|
if target == nil || target.CurrentPet() == nil {
|
||||||
}
|
return true
|
||||||
|
}
|
||||||
|
if target.CurrentPet().GetHP().Cmp(targetHP) > 0 {
|
||||||
|
target.CurrentPet().Info.Hp = uint32(targetHP.IntPart())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1182, int(e.Args()[1].IntPart()))
|
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1182, int(e.Args()[1].IntPart()))
|
||||||
if sub != nil {
|
if sub != nil {
|
||||||
|
|||||||
@@ -10,20 +10,23 @@ type Effect169 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Effect169) OnSkill() bool {
|
func (e *Effect169) OnSkill() bool {
|
||||||
|
|
||||||
chance := e.Args()[1].IntPart()
|
chance := e.Args()[1].IntPart()
|
||||||
success, _, _ := e.Input.Player.Roll(int(chance), 100)
|
success, _, _ := e.Input.Player.Roll(int(chance), 100)
|
||||||
if success {
|
if success {
|
||||||
// 添加异常状态
|
e.ForEachOpponentSlot(func(target *input.Input) bool {
|
||||||
statusEffect := e.CarrierInput().InitEffect(input.EffectType.Status, int(e.Args()[2].IntPart())) // 以麻痹为例
|
if target == nil {
|
||||||
if statusEffect != nil {
|
return true
|
||||||
e.TargetInput().AddEffect(e.CarrierInput(), statusEffect)
|
}
|
||||||
}
|
statusEffect := e.CarrierInput().InitEffect(input.EffectType.Status, int(e.Args()[2].IntPart()))
|
||||||
|
if statusEffect != nil {
|
||||||
|
target.AddEffect(e.CarrierInput(), statusEffect)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
input.InitEffect(input.EffectType.Skill, 169, &Effect169{})
|
input.InitEffect(input.EffectType.Skill, 169, &Effect169{})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ func (e *Effect2194) OnSkill() bool {
|
|||||||
if e.Ctx().Opp.CurPet[0] == nil {
|
if e.Ctx().Opp.CurPet[0] == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
addStatusByID(e.Ctx().Our, e.Ctx().Opp, int(info.PetStatus.DrainedHP))
|
addTimedStatus(e.Ctx().Our, e.Ctx().Opp, int(info.PetStatus.DrainedHP), 4)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -444,7 +444,7 @@ func (e *Effect2278) Skill_Use() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
damage := marked.GetHP().Mul(alpacadecimal.NewFromInt(percent)).Div(hundred)
|
damage := marked.GetMaxHP().Mul(alpacadecimal.NewFromInt(percent)).Div(hundred)
|
||||||
if damage.Cmp(alpacadecimal.Zero) > 0 {
|
if damage.Cmp(alpacadecimal.Zero) > 0 {
|
||||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
||||||
Type: info.DamageType.Percent,
|
Type: info.DamageType.Percent,
|
||||||
|
|||||||
@@ -14,18 +14,25 @@ type Effect85 struct {
|
|||||||
// 执行时逻辑
|
// 执行时逻辑
|
||||||
// ----------------------
|
// ----------------------
|
||||||
func (e *Effect85) OnSkill() bool {
|
func (e *Effect85) OnSkill() bool {
|
||||||
|
carrier := e.CarrierInput()
|
||||||
for i, v := range e.Ctx().Opp.Prop[:] {
|
opp := e.OpponentInput()
|
||||||
if v > 0 {
|
if carrier == nil || opp == nil {
|
||||||
e.Ctx().Our.SetProp(e.Ctx().Our, int8(i), v)
|
return true
|
||||||
e.Ctx().Opp.SetProp(e.Ctx().Our, int8(i), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
e.transferPositiveProps(carrier, opp)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transferPositiveProps 使用显式入参执行业务逻辑,避免嵌套结算时再从 Ctx 取到漂移后的对象。
|
||||||
|
func (e *Effect85) transferPositiveProps(carrier, opp *input.Input) {
|
||||||
|
for i, v := range opp.Prop[:] {
|
||||||
|
if v > 0 {
|
||||||
|
carrier.SetProp(carrier, int8(i), v)
|
||||||
|
opp.SetProp(carrier, int8(i), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------
|
// ----------------------
|
||||||
// 注册所有效果
|
// 注册所有效果
|
||||||
// ----------------------
|
// ----------------------
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (e *Effect13) OnSkill() bool {
|
|||||||
if eff == nil {
|
if eff == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
eff.Duration(e.EffectNode.SideEffectArgs[0] - 1)
|
eff.Duration(e.EffectNode.SideEffectArgs[0])
|
||||||
|
|
||||||
e.Ctx().Opp.AddEffect(e.Ctx().Our, eff)
|
e.Ctx().Opp.AddEffect(e.Ctx().Our, eff)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package effect
|
package effect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"blazing/logic/service/fight/input"
|
||||||
"blazing/logic/service/fight/node"
|
"blazing/logic/service/fight/node"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -41,14 +42,16 @@ type Effect5 struct {
|
|||||||
// 技能触发时调用
|
// 技能触发时调用
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
func (e *Effect5) Skill_Use() bool {
|
func (e *Effect5) Skill_Use() bool {
|
||||||
|
|
||||||
// 概率判定
|
// 概率判定
|
||||||
ok, _, _ := e.Input.Player.Roll(e.SideEffectArgs[1], 100)
|
ok, _, _ := e.Input.Player.Roll(e.SideEffectArgs[1], 100)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Ctx().Opp.SetProp(e.Ctx().Our, int8(e.SideEffectArgs[0]), int8(e.SideEffectArgs[2]))
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
|
target.SetProp(e.Ctx().Our, int8(e.SideEffectArgs[0]), int8(e.SideEffectArgs[2]))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"blazing/logic/service/fight/info"
|
"blazing/logic/service/fight/info"
|
||||||
"blazing/logic/service/fight/input"
|
"blazing/logic/service/fight/input"
|
||||||
"blazing/logic/service/fight/node"
|
"blazing/logic/service/fight/node"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Effect 724: {0}回合内受到攻击{1}%恢复1/{2}体力
|
// Effect 724: {0}回合内受到攻击{1}%恢复1/{2}体力
|
||||||
@@ -85,6 +86,7 @@ func (e *Effect727) Action_end() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[Effect727] Action_end RESET: Our.Prop %v -> LastTurnEndProp %v\n", e.Ctx().Our.Prop, e.Ctx().Our.LastTurnEndProp)
|
||||||
e.Ctx().Our.AttackValue.Prop = e.Ctx().Our.LastTurnEndProp
|
e.Ctx().Our.AttackValue.Prop = e.Ctx().Our.LastTurnEndProp
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,15 +22,19 @@ type Effect76 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Effect76) OnSkill() bool {
|
func (e *Effect76) OnSkill() bool {
|
||||||
|
|
||||||
// 概率判定
|
// 概率判定
|
||||||
ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100)
|
ok, _, _ := e.Input.Player.Roll(int(e.Args()[0].IntPart()), 100)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
|
||||||
Type: info.DamageType.Fixed,
|
damage := alpacadecimal.NewFromInt(int64(e.SideEffectArgs[2]))
|
||||||
Damage: alpacadecimal.NewFromInt(int64(e.SideEffectArgs[2])),
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
|
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||||
|
Type: info.DamageType.Fixed,
|
||||||
|
Damage: damage,
|
||||||
|
})
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package effect
|
package effect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"blazing/logic/service/fight/action"
|
|
||||||
"blazing/logic/service/fight/input"
|
"blazing/logic/service/fight/input"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,23 +12,72 @@ func init() {
|
|||||||
t := &Effect91{}
|
t := &Effect91{}
|
||||||
|
|
||||||
input.InitEffect(input.EffectType.Skill, 91, t)
|
input.InitEffect(input.EffectType.Skill, 91, t)
|
||||||
|
input.InitEffect(input.EffectType.Sub, 91, &Effect91Sub{})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effect 91: {0}回合内对手的状态变化会同时作用在自己身上
|
// Effect 91: {0}回合内对手的状态变化会同时作用在自己身上
|
||||||
type Effect91 struct {
|
type Effect91 struct {
|
||||||
RoundEffectSideArg0Base
|
RoundEffectSideArg0Base
|
||||||
can bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认添加回合
|
func (e *Effect91) Skill_Use() bool {
|
||||||
|
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 91, e.SideEffectArgs...)
|
||||||
|
if sub != nil {
|
||||||
|
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Effect91) TurnStart(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) {
|
type Effect91Sub struct {
|
||||||
|
RoundEffectSideArg0Base
|
||||||
for i, v := range e.Ctx().Opp.Prop[:] {
|
}
|
||||||
|
|
||||||
e.Ctx().Our.SetProp(e.Ctx().Our, int8(i), v)
|
|
||||||
|
|
||||||
|
func (e *Effect91Sub) PropBefer(_ *input.Input, prop int8, level int8) bool {
|
||||||
|
if prop < 0 || int(prop) >= len(e.Ctx().Our.Prop) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当前效果挂在“对手”身上:当对手能力变化时,将实际变化量同步给来源方。
|
||||||
|
target := e.Ctx().Our
|
||||||
|
current := target.Prop[prop]
|
||||||
|
actualDelta := calcPropActualDelta(current, level)
|
||||||
|
if actualDelta == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
owner := e.SourceInput()
|
||||||
|
if owner == nil || owner == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
owner.SetProp(owner, prop, actualDelta)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcPropActualDelta(current, change int8) int8 {
|
||||||
|
switch {
|
||||||
|
case change < 0:
|
||||||
|
if current <= -6 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
next := current + change
|
||||||
|
if next < -6 {
|
||||||
|
next = -6
|
||||||
|
}
|
||||||
|
return next - current
|
||||||
|
case change > 0:
|
||||||
|
if current >= 6 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
next := current + change
|
||||||
|
if next > 6 {
|
||||||
|
next = 6
|
||||||
|
}
|
||||||
|
return next - current
|
||||||
|
default:
|
||||||
|
if current == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return -current
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ var effectInfoByID = map[int]string{
|
|||||||
29: "额外附加{0}点固定伤害",
|
29: "额外附加{0}点固定伤害",
|
||||||
31: "",
|
31: "",
|
||||||
32: "使用后{0}回合攻击击中对象要害概率增加1/16",
|
32: "使用后{0}回合攻击击中对象要害概率增加1/16",
|
||||||
33: "消除对手能力提升状态",
|
33: "消除敌方阵营所有强化",
|
||||||
34: "将所受的伤害{0}倍反馈给对手",
|
34: "将所受的伤害{0}倍反馈给对手",
|
||||||
35: "惩罚,对方能力等级越高,此技能威力越大",
|
35: "惩罚,对方能力等级越高,此技能威力越大",
|
||||||
36: "命中时{0}%的概率秒杀对方",
|
36: "命中时{0}%的概率秒杀对方",
|
||||||
@@ -120,7 +120,7 @@ var effectInfoByID = map[int]string{
|
|||||||
164: "{0}回合内若受到攻击则有{1}%概率令对手{2}",
|
164: "{0}回合内若受到攻击则有{1}%概率令对手{2}",
|
||||||
165: "{0}回合内每回合防御和特防等级+{1}",
|
165: "{0}回合内每回合防御和特防等级+{1}",
|
||||||
166: "{0}回合内若对手使用属性攻击则{2}%对手{1}等级{3}",
|
166: "{0}回合内若对手使用属性攻击则{2}%对手{1}等级{3}",
|
||||||
169: "{0}回合内每回合额外附加{1}%概率令对手{2}",
|
169: "{0}回合内每回合额外附加{1}%概率令对方阵营全体{2}",
|
||||||
170: "若先出手,则免疫当回合伤害并回复1/{0}的最大体力值",
|
170: "若先出手,则免疫当回合伤害并回复1/{0}的最大体力值",
|
||||||
171: "{0}回合内自身使用属性技能时能较快出手",
|
171: "{0}回合内自身使用属性技能时能较快出手",
|
||||||
172: "若后出手,则给予对方损伤的1/{0}会回复自己的体力",
|
172: "若后出手,则给予对方损伤的1/{0}会回复自己的体力",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (e *Effect3) Skill_Use() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effect 33: 消除对手能力提升状态
|
// Effect 33: 消除敌方阵营所有强化
|
||||||
type Effect33 struct {
|
type Effect33 struct {
|
||||||
node.EffectNode
|
node.EffectNode
|
||||||
Reverse bool
|
Reverse bool
|
||||||
@@ -38,13 +38,17 @@ type Effect33 struct {
|
|||||||
// 执行时逻辑
|
// 执行时逻辑
|
||||||
// ----------------------
|
// ----------------------
|
||||||
func (e *Effect33) Skill_Use() bool {
|
func (e *Effect33) Skill_Use() bool {
|
||||||
|
e.ForEachOpponentSlot(func(target *input.Input) bool {
|
||||||
for i, v := range e.Ctx().Opp.Prop[:] {
|
if target == nil {
|
||||||
if v > 0 {
|
return true
|
||||||
e.Ctx().Opp.SetProp(e.Ctx().Our, int8(i), 0)
|
|
||||||
}
|
}
|
||||||
|
for i, v := range target.Prop[:] {
|
||||||
}
|
if v > 0 {
|
||||||
|
target.SetProp(e.Ctx().Our, int8(i), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -54,8 +58,8 @@ func (e *Effect33) Skill_Use() bool {
|
|||||||
// ----------------------
|
// ----------------------
|
||||||
func init() {
|
func init() {
|
||||||
// {3, false, 0}, // 解除自身能力下降状态
|
// {3, false, 0}, // 解除自身能力下降状态
|
||||||
// {33, true, 0}, // 消除对手能力提升状态{3, false, 0}, // 解除自身能力下降状态
|
// {33, true, 0}, // 消除敌方阵营所有强化{3, false, 0}, // 解除自身能力下降状态
|
||||||
// {33, true, 0}, // 消除对手能力提升状态
|
// {33, true, 0}, // 消除敌方阵营所有强化
|
||||||
input.InitEffect(input.EffectType.Skill, 3, &Effect3{})
|
input.InitEffect(input.EffectType.Skill, 3, &Effect3{})
|
||||||
input.InitEffect(input.EffectType.Skill, 33, &Effect33{})
|
input.InitEffect(input.EffectType.Skill, 33, &Effect33{})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,24 +39,17 @@ func (e *StatusCannotAct) ActionStart(attacker, defender *action.SelectSkillActi
|
|||||||
// 睡眠状态:受击后解除
|
// 睡眠状态:受击后解除
|
||||||
type StatusSleep struct {
|
type StatusSleep struct {
|
||||||
StatusCannotAct
|
StatusCannotAct
|
||||||
hasTriedAct bool // 标记是否尝试过行动
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试出手时标记状态
|
|
||||||
func (e *StatusSleep) ActionStart(attacker, defender *action.SelectSkillAction) bool {
|
|
||||||
e.hasTriedAct = true
|
|
||||||
return e.StatusCannotAct.ActionStart(attacker, defender)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 技能使用后处理:非状态类技能触发后解除睡眠
|
|
||||||
func (e *StatusSleep) Skill_Use_ex() bool {
|
func (e *StatusSleep) Skill_Use_ex() bool {
|
||||||
if !e.hasTriedAct {
|
|
||||||
return true
|
if e.Ctx().SkillEntity != nil {
|
||||||
}
|
if e.Ctx().SkillEntity.AttackTime != 0 && e.Ctx().Category() != info.Category.STATUS {
|
||||||
// 技能实体存在且非状态类型技能,解除睡眠
|
e.Alive(false)
|
||||||
if e.Ctx().SkillEntity != nil && e.Ctx().Category() != info.Category.STATUS {
|
}
|
||||||
e.Alive(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +59,7 @@ type ContinuousDamage struct {
|
|||||||
isheal bool //是否回血
|
isheal bool //是否回血
|
||||||
}
|
}
|
||||||
|
|
||||||
// 技能命中前触发伤害(1/8最大生命值真实伤害)
|
// 行动开始触发持续伤害:当中招方轮到自己行动时结算。
|
||||||
func (e *ContinuousDamage) ActionStart(attacker, defender *action.SelectSkillAction) bool {
|
func (e *ContinuousDamage) ActionStart(attacker, defender *action.SelectSkillAction) bool {
|
||||||
carrier := e.CarrierInput()
|
carrier := e.CarrierInput()
|
||||||
source := e.SourceInput()
|
source := e.SourceInput()
|
||||||
@@ -131,15 +124,13 @@ func (e *ParasiticSeed) SwitchOut(in *input.Input) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 技能命中前触发寄生效果
|
// 回合开始触发寄生效果。寄生属于完整回合流程的一部分,不依赖本回合是否成功出手。
|
||||||
func (e *ParasiticSeed) ActionStartEx(attacker, defender *action.SelectSkillAction) bool {
|
func (e *ParasiticSeed) TurnStart(attacker, defender *action.SelectSkillAction) {
|
||||||
carrier := e.CarrierInput()
|
carrier := e.CarrierInput()
|
||||||
source := e.SourceInput()
|
source := e.SourceInput()
|
||||||
opp := e.TargetInput()
|
|
||||||
if carrier == nil {
|
if carrier == nil {
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
// 过滤特定类型单位(假设1是植物类型,使用枚举替代魔法数字)
|
|
||||||
|
|
||||||
damage := alpacadecimal.NewFromInt(int64(carrier.CurPet[0].Info.MaxHp)).
|
damage := alpacadecimal.NewFromInt(int64(carrier.CurPet[0].Info.MaxHp)).
|
||||||
Div(alpacadecimal.NewFromInt(8))
|
Div(alpacadecimal.NewFromInt(8))
|
||||||
@@ -149,13 +140,12 @@ func (e *ParasiticSeed) ActionStartEx(attacker, defender *action.SelectSkillActi
|
|||||||
Type: info.DamageType.True,
|
Type: info.DamageType.True,
|
||||||
Damage: damage,
|
Damage: damage,
|
||||||
})
|
})
|
||||||
if opp == nil || opp.CurPet[0].GetHP().IntPart() == 0 {
|
if source == nil || source.CurPet[0] == nil || source.CurPet[0].GetHP().IntPart() == 0 {
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 给对方回血(不受回血限制影响)
|
// 给寄生种子的施放者回血(不受回血限制影响)
|
||||||
opp.Heal(carrier, nil, damage)
|
source.Heal(carrier, nil, damage)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Flammable struct {
|
type Flammable struct {
|
||||||
@@ -271,9 +261,9 @@ func init() {
|
|||||||
// 批量注册不能行动的状态
|
// 批量注册不能行动的状态
|
||||||
nonActingStatuses := []info.EnumPetStatus{
|
nonActingStatuses := []info.EnumPetStatus{
|
||||||
info.PetStatus.Paralysis, // 麻痹
|
info.PetStatus.Paralysis, // 麻痹
|
||||||
info.PetStatus.Tired, // 疲惫
|
|
||||||
info.PetStatus.Fear, // 害怕
|
info.PetStatus.Fear, // 害怕
|
||||||
info.PetStatus.Petrified, // 石化
|
info.PetStatus.Petrified, // 石化
|
||||||
|
info.PetStatus.Tired, // 疲惫
|
||||||
}
|
}
|
||||||
for _, status := range nonActingStatuses {
|
for _, status := range nonActingStatuses {
|
||||||
effect := &StatusCannotAct{}
|
effect := &StatusCannotAct{}
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ func (e *Effect201) OnSkill() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !carrier.IsMultiInputBattle() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
divisorIndex := len(args) - 1
|
divisorIndex := len(args) - 1
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
divisorIndex = 1
|
divisorIndex = 1
|
||||||
|
|||||||
91
logic/service/fight/effect/none_test.go
Normal file
91
logic/service/fight/effect/none_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package effect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
fightinfo "blazing/logic/service/fight/info"
|
||||||
|
"blazing/logic/service/fight/input"
|
||||||
|
"blazing/modules/player/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEffect201TestInput(hp, maxHP uint32) *input.Input {
|
||||||
|
in := &input.Input{
|
||||||
|
CurPet: []*fightinfo.BattlePetEntity{{
|
||||||
|
Info: model.PetInfo{
|
||||||
|
Hp: hp,
|
||||||
|
MaxHp: maxHP,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
in.AttackValue = fightinfo.NewAttackValue(0)
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEffect201HealAllIgnoredInSingleInputBattle(t *testing.T) {
|
||||||
|
carrier := newEffect201TestInput(40, 100)
|
||||||
|
opponent := newEffect201TestInput(60, 100)
|
||||||
|
carrier.Team = []*input.Input{carrier}
|
||||||
|
carrier.OppTeam = []*input.Input{opponent}
|
||||||
|
|
||||||
|
eff := &Effect201{}
|
||||||
|
eff.SetArgs(carrier, 1, 2)
|
||||||
|
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
|
||||||
|
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
|
||||||
|
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !eff.OnSkill() {
|
||||||
|
t.Fatalf("expected effect to finish successfully")
|
||||||
|
}
|
||||||
|
if got := carrier.CurrentPet().Info.Hp; got != 40 {
|
||||||
|
t.Fatalf("expected single-input full-team heal to be ignored, got hp %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEffect201SingleTargetIgnoredInSingleInputBattle(t *testing.T) {
|
||||||
|
carrier := newEffect201TestInput(40, 100)
|
||||||
|
opponent := newEffect201TestInput(60, 100)
|
||||||
|
carrier.Team = []*input.Input{carrier}
|
||||||
|
carrier.OppTeam = []*input.Input{opponent}
|
||||||
|
|
||||||
|
eff := &Effect201{}
|
||||||
|
eff.SetArgs(carrier, 2)
|
||||||
|
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
|
||||||
|
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
|
||||||
|
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !eff.OnSkill() {
|
||||||
|
t.Fatalf("expected effect to finish successfully")
|
||||||
|
}
|
||||||
|
if got := carrier.CurrentPet().Info.Hp; got != 40 {
|
||||||
|
t.Fatalf("expected single-input single-target heal to be ignored, got hp %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEffect201HealAllWorksInMultiInputBattle(t *testing.T) {
|
||||||
|
carrier := newEffect201TestInput(40, 100)
|
||||||
|
ally := newEffect201TestInput(10, 80)
|
||||||
|
opponent := newEffect201TestInput(60, 100)
|
||||||
|
carrier.Team = []*input.Input{carrier, ally}
|
||||||
|
carrier.OppTeam = []*input.Input{opponent}
|
||||||
|
ally.Team = carrier.Team
|
||||||
|
ally.OppTeam = carrier.OppTeam
|
||||||
|
|
||||||
|
eff := &Effect201{}
|
||||||
|
eff.SetArgs(carrier, 1, 2)
|
||||||
|
eff.EffectNode.EffectContextHolder.Ctx = input.Ctx{
|
||||||
|
LegacySides: input.LegacySides{Our: carrier, Opp: opponent},
|
||||||
|
EffectBinding: input.EffectBinding{Carrier: carrier, Source: carrier},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !eff.OnSkill() {
|
||||||
|
t.Fatalf("expected effect to finish successfully")
|
||||||
|
}
|
||||||
|
if got := carrier.CurrentPet().Info.Hp; got != 90 {
|
||||||
|
t.Fatalf("expected carrier hp 90 after full-team heal, got %d", got)
|
||||||
|
}
|
||||||
|
if got := ally.CurrentPet().Info.Hp; got != 50 {
|
||||||
|
t.Fatalf("expected ally hp 50 after full-team heal, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,3 +5,7 @@ import "blazing/logic/service/fight/input"
|
|||||||
func initskill(id int, e input.Effect) {
|
func initskill(id int, e input.Effect) {
|
||||||
input.InitEffect(input.EffectType.Skill, id, e)
|
input.InitEffect(input.EffectType.Skill, id, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initskillFactory(id int, factory func() input.Effect) {
|
||||||
|
input.InitEffectFactory(input.EffectType.Skill, id, factory)
|
||||||
|
}
|
||||||
|
|||||||
@@ -158,7 +158,10 @@ func registerSelfDamageSkillHitEffects() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for effectID, handler := range handlers {
|
for effectID, handler := range handlers {
|
||||||
initskill(effectID, newSkillHitRegistrarEffect(handler))
|
currentHandler := handler
|
||||||
|
initskillFactory(effectID, func() input.Effect {
|
||||||
|
return newSkillHitRegistrarEffect(currentHandler)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,9 +207,15 @@ func registerSelfDamageOnSkillEffects() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
Type: info.DamageType.Fixed,
|
if target == nil || target.CurrentPet() == nil {
|
||||||
Damage: opponentDamage,
|
return true
|
||||||
|
}
|
||||||
|
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||||
|
Type: info.DamageType.Fixed,
|
||||||
|
Damage: opponentDamage,
|
||||||
|
})
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -223,7 +232,10 @@ func registerSelfDamageOnSkillEffects() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for effectID, handler := range handlers {
|
for effectID, handler := range handlers {
|
||||||
initskill(effectID, newOnSkillRegistrarEffect(handler))
|
currentHandler := handler
|
||||||
|
initskillFactory(effectID, func() input.Effect {
|
||||||
|
return newOnSkillRegistrarEffect(currentHandler)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,9 +247,15 @@ func registerSelfDamageSkillUseEffects() {
|
|||||||
Type: info.DamageType.Fixed,
|
Type: info.DamageType.Fixed,
|
||||||
Damage: damage,
|
Damage: damage,
|
||||||
})
|
})
|
||||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
Type: info.DamageType.Fixed,
|
if target == nil || target.CurrentPet() == nil {
|
||||||
Damage: damage,
|
return true
|
||||||
|
}
|
||||||
|
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||||
|
Type: info.DamageType.Fixed,
|
||||||
|
Damage: damage,
|
||||||
|
})
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -247,9 +265,23 @@ func registerSelfDamageSkillUseEffects() {
|
|||||||
Damage: alpacadecimal.NewFromInt(int64(e.Ctx().Our.CurPet[0].Info.MaxHp)),
|
Damage: alpacadecimal.NewFromInt(int64(e.Ctx().Our.CurPet[0].Info.MaxHp)),
|
||||||
})
|
})
|
||||||
damage := int64(grand.N(250, 300))
|
damage := int64(grand.N(250, 300))
|
||||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
Type: info.DamageType.Fixed,
|
if target == nil {
|
||||||
Damage: alpacadecimal.Min(alpacadecimal.NewFromInt(damage), e.Ctx().Opp.CurPet[0].GetHP().Sub(alpacadecimal.NewFromInt(1))),
|
return true
|
||||||
|
}
|
||||||
|
targetPet := target.CurrentPet()
|
||||||
|
if targetPet == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
remainHP := targetPet.GetHP().Sub(alpacadecimal.NewFromInt(1))
|
||||||
|
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||||
|
Type: info.DamageType.Fixed,
|
||||||
|
Damage: alpacadecimal.Min(alpacadecimal.NewFromInt(damage), remainHP),
|
||||||
|
})
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -274,15 +306,25 @@ func registerSelfDamageSkillUseEffects() {
|
|||||||
randomDamage = grand.N(minDamage, maxDamage)
|
randomDamage = grand.N(minDamage, maxDamage)
|
||||||
}
|
}
|
||||||
|
|
||||||
remainHP := e.Ctx().Opp.CurPet[0].GetHP().Sub(alpacadecimal.NewFromInt(1))
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
|
if target == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
targetPet := target.CurrentPet()
|
||||||
|
if targetPet == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
remainHP := targetPet.GetHP().Sub(alpacadecimal.NewFromInt(1))
|
||||||
|
if remainHP.Cmp(alpacadecimal.Zero) <= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
damage := alpacadecimal.Min(alpacadecimal.NewFromInt(int64(randomDamage)), remainHP)
|
damage := alpacadecimal.Min(alpacadecimal.NewFromInt(int64(randomDamage)), remainHP)
|
||||||
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
|
target.Damage(e.Ctx().Our, &info.DamageZone{
|
||||||
Type: info.DamageType.Fixed,
|
Type: info.DamageType.Fixed,
|
||||||
Damage: damage,
|
Damage: damage,
|
||||||
|
})
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@@ -291,11 +333,17 @@ func registerSelfDamageSkillUseEffects() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAllPropDown(e.Ctx().Our, e.Ctx().Opp, int8(e.Args()[0].IntPart()))
|
forEachEnemyTargetBySkill(e.Ctx().Our, e.Ctx().Opp, e.Ctx().SkillEntity, func(target *input.Input) bool {
|
||||||
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1380, int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart()))
|
if target == nil || target.CurrentPet() == nil {
|
||||||
if sub != nil {
|
return true
|
||||||
e.Ctx().Opp.AddEffect(e.Ctx().Our, sub)
|
}
|
||||||
}
|
applyAllPropDown(e.Ctx().Our, target, int8(e.Args()[0].IntPart()))
|
||||||
|
sub := e.Ctx().Our.InitEffect(input.EffectType.Sub, 1380, int(e.Args()[1].IntPart()), int(e.Args()[2].IntPart()))
|
||||||
|
if sub != nil {
|
||||||
|
target.AddEffect(e.Ctx().Our, sub)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
e.Ctx().Our.Damage(e.Ctx().Our, &info.DamageZone{
|
e.Ctx().Our.Damage(e.Ctx().Our, &info.DamageZone{
|
||||||
Type: info.DamageType.Fixed,
|
Type: info.DamageType.Fixed,
|
||||||
Damage: e.Ctx().Our.CurPet[0].GetHP(),
|
Damage: e.Ctx().Our.CurPet[0].GetHP(),
|
||||||
@@ -305,7 +353,10 @@ func registerSelfDamageSkillUseEffects() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for effectID, handler := range handlers {
|
for effectID, handler := range handlers {
|
||||||
initskill(effectID, newSkillUseRegistrarEffect(handler))
|
currentHandler := handler
|
||||||
|
initskillFactory(effectID, func() input.Effect {
|
||||||
|
return newSkillUseRegistrarEffect(currentHandler)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +390,10 @@ func registerSelfDamageComparePreOnSkillEffects() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for effectID, effect := range effects {
|
for effectID, effect := range effects {
|
||||||
initskill(effectID, effect)
|
currentEffect := effect
|
||||||
|
initskillFactory(effectID, func() input.Effect {
|
||||||
|
return newComparePreOnSkillRegistrarEffect(currentEffect.comparePreHandler, currentEffect.onSkillHandler)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
logic/service/fight/effect/skill_target_helper.go
Normal file
34
logic/service/fight/effect/skill_target_helper.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package effect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blazing/logic/service/fight/info"
|
||||||
|
"blazing/logic/service/fight/input"
|
||||||
|
)
|
||||||
|
|
||||||
|
// forEachEnemyTargetBySkill 在普通情况下对单个目标生效;
|
||||||
|
// 当技能为 AtkType=3(仅自己)且当前目标仍在己方时,改为遍历敌方全部站位。
|
||||||
|
func forEachEnemyTargetBySkill(carrier, target *input.Input, skill *info.SkillEntity, fn func(*input.Input) bool) {
|
||||||
|
if fn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if carrier == nil {
|
||||||
|
if target != nil {
|
||||||
|
fn(target)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if skill == nil || skill.XML.AtkType != 3 || !isSameSideTarget(carrier, target) {
|
||||||
|
if target != nil {
|
||||||
|
fn(target)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, opponent := range carrier.OpponentSlots() {
|
||||||
|
if opponent == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !fn(opponent) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
logic/service/fight/fight_over_payload.go
Normal file
40
logic/service/fight/fight_over_payload.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package fight
|
||||||
|
|
||||||
|
import "blazing/modules/player/model"
|
||||||
|
|
||||||
|
// buildFightOverPayload builds the legacy 2506 payload expected by the flash client.
|
||||||
|
// Regular fight-over packets use a different reason mapping than group fight 7560:
|
||||||
|
// 0=normal end 1=player lost/offline 2=overtime 3=draw 4=system error 5=npc escape.
|
||||||
|
func buildFightOverPayload(over model.FightOverInfo) *model.FightOverInfo {
|
||||||
|
payload := over
|
||||||
|
payload.Reason = model.EnumBattleOverReason(mapUnifiedFightOverReason(over.Reason))
|
||||||
|
return &payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeFightOverReason(reason model.EnumBattleOverReason) model.EnumBattleOverReason {
|
||||||
|
if reason == model.BattleOverReason.DefaultEnd {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return reason
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapUnifiedFightOverReason(reason model.EnumBattleOverReason) uint32 {
|
||||||
|
switch normalizeFightOverReason(reason) {
|
||||||
|
case 0, model.BattleOverReason.Cacthok:
|
||||||
|
return 0
|
||||||
|
case model.BattleOverReason.PlayerOffline:
|
||||||
|
return 1
|
||||||
|
case model.BattleOverReason.PlayerOVerTime:
|
||||||
|
return 2
|
||||||
|
case model.BattleOverReason.NOTwind:
|
||||||
|
return 3
|
||||||
|
case model.BattleOverReason.PlayerEscape:
|
||||||
|
return 5
|
||||||
|
default:
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapFightOverReasonFor2506(reason model.EnumBattleOverReason) model.EnumBattleOverReason {
|
||||||
|
return model.EnumBattleOverReason(mapUnifiedFightOverReason(reason))
|
||||||
|
}
|
||||||
@@ -3,9 +3,12 @@ package fight
|
|||||||
import (
|
import (
|
||||||
"blazing/common/utils"
|
"blazing/common/utils"
|
||||||
|
|
||||||
|
"blazing/logic/service/common"
|
||||||
"blazing/logic/service/fight/action"
|
"blazing/logic/service/fight/action"
|
||||||
"blazing/logic/service/fight/info"
|
"blazing/logic/service/fight/info"
|
||||||
"blazing/logic/service/fight/input"
|
"blazing/logic/service/fight/input"
|
||||||
|
_ "blazing/logic/service/fight/itemover"
|
||||||
|
_ "blazing/logic/service/fight/rule"
|
||||||
"blazing/modules/player/model"
|
"blazing/modules/player/model"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
@@ -132,7 +135,20 @@ func (f *FightC) getSkillParticipants(skillAction *action.SelectSkillAction) (*i
|
|||||||
if skillAction == nil {
|
if skillAction == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return f.GetInputByAction(skillAction, false), f.GetInputByAction(skillAction, true)
|
attacker := f.GetInputByAction(skillAction, false)
|
||||||
|
defender := f.GetInputByAction(skillAction, true)
|
||||||
|
if attacker != nil && defender == attacker && shouldResolveOpponentAsTarget(skillAction.SkillEntity) {
|
||||||
|
if opponent, _ := attacker.OpponentSlotAtOrNextLiving(0); opponent != nil {
|
||||||
|
defender = opponent
|
||||||
|
} else if opponent := f.roundOpponentInput(attacker); opponent != nil {
|
||||||
|
defender = opponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attacker, defender
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldResolveOpponentAsTarget(skill *info.SkillEntity) bool {
|
||||||
|
return skill != nil && skill.XML.AtkType == 3
|
||||||
}
|
}
|
||||||
|
|
||||||
// setEffectSkillContext 统一设置技能阶段 effect 上下文。
|
// setEffectSkillContext 统一设置技能阶段 effect 上下文。
|
||||||
@@ -183,20 +199,63 @@ func (f *FightC) collectAttackValues(inputs []*input.Input) []model.AttackValue
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
attackValue := *fighter.AttackValue
|
attackValue := *fighter.AttackValue
|
||||||
|
if attackValue.SkillID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
attackValue.ActorIndex = uint32(actorIndex)
|
attackValue.ActorIndex = uint32(actorIndex)
|
||||||
values = append(values, attackValue)
|
values = append(values, attackValue)
|
||||||
}
|
}
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildAttackValueForBroadcast(fighter *input.Input, fallbackActorIndex int) model.AttackValue {
|
||||||
|
if fighter == nil {
|
||||||
|
return model.AttackValue{}
|
||||||
|
}
|
||||||
|
if fighter.AttackValue == nil {
|
||||||
|
empty := info.NewAttackValue(fighter.UserID)
|
||||||
|
fighter.AttackValue = empty
|
||||||
|
}
|
||||||
|
attackValue := *fighter.AttackValue
|
||||||
|
attackValue.ActorIndex = uint32(fallbackActorIndex)
|
||||||
|
if attackValue.UserID == 0 && fighter.Player != nil && fighter.Player.GetInfo() != nil {
|
||||||
|
attackValue.UserID = fighter.Player.GetInfo().UserID
|
||||||
|
}
|
||||||
|
return attackValue
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FightC) buildNoteUseSkillOutboundInfo() info.NoteUseSkillOutboundInfo {
|
func (f *FightC) buildNoteUseSkillOutboundInfo() info.NoteUseSkillOutboundInfo {
|
||||||
result := info.NoteUseSkillOutboundInfo{}
|
result := info.NoteUseSkillOutboundInfo{}
|
||||||
result.FirstAttackInfo = append(result.FirstAttackInfo, f.collectAttackValues(f.Our)...)
|
if f.First != nil {
|
||||||
result.SecondAttackInfo = append(result.SecondAttackInfo, f.collectAttackValues(f.Opp)...)
|
result.FirstAttackInfo = f.buildAttackValueForBroadcast(f.First, f.First.TeamSlotIndex())
|
||||||
|
}
|
||||||
|
if f.Second != nil {
|
||||||
|
result.SecondAttackInfo = f.buildAttackValueForBroadcast(f.Second, f.Second.TeamSlotIndex())
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FightC) roundOpponentInput(attacker *input.Input) *input.Input {
|
||||||
|
if attacker == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, opponent := range attacker.OpponentSlots() {
|
||||||
|
if opponent != nil {
|
||||||
|
return opponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldSkipSecondAction(first, second *input.Input) bool {
|
||||||
|
if first == nil || second == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
firstPet := first.CurrentPet()
|
||||||
|
secondPet := second.CurrentPet()
|
||||||
|
return firstPet == nil || firstPet.Info.Hp <= 0 || secondPet == nil || secondPet.Info.Hp <= 0
|
||||||
|
}
|
||||||
|
|
||||||
// enterturn 处理战斗回合逻辑
|
// enterturn 处理战斗回合逻辑
|
||||||
// 回合有先手方和后手方,同时有攻击方和被攻击方
|
// 回合有先手方和后手方,同时有攻击方和被攻击方
|
||||||
func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) {
|
func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) {
|
||||||
@@ -244,9 +303,11 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
|||||||
f.First, _ = f.getSkillParticipants(firstAttack)
|
f.First, _ = f.getSkillParticipants(firstAttack)
|
||||||
f.Second, _ = f.getSkillParticipants(secondAttack)
|
f.Second, _ = f.getSkillParticipants(secondAttack)
|
||||||
case firstAttack != nil:
|
case firstAttack != nil:
|
||||||
f.First, f.Second = f.getSkillParticipants(firstAttack)
|
f.First, _ = f.getSkillParticipants(firstAttack)
|
||||||
|
f.Second = f.roundOpponentInput(f.First)
|
||||||
case secondAttack != nil:
|
case secondAttack != nil:
|
||||||
f.First, f.Second = f.getSkillParticipants(secondAttack)
|
f.First, _ = f.getSkillParticipants(secondAttack)
|
||||||
|
f.Second = f.roundOpponentInput(f.First)
|
||||||
}
|
}
|
||||||
if f.First == nil {
|
if f.First == nil {
|
||||||
f.First = f.primaryOur()
|
f.First = f.primaryOur()
|
||||||
@@ -274,25 +335,32 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if firstAttack == nil && secondAttack == nil {
|
skipActionStage := firstAttack == nil && secondAttack == nil
|
||||||
firstAttack, secondAttack = secondAttack, firstAttack //互换先手权
|
|
||||||
f.First, f.Second = f.Second, f.First
|
|
||||||
}
|
|
||||||
var attacker, defender *input.Input
|
var attacker, defender *input.Input
|
||||||
f.TrueFirst = f.First
|
f.TrueFirst = f.First
|
||||||
//开始回合操作
|
//开始回合操作。若双方本回合都未出手,则只走完整回合流程,不进入动作阶段。
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; !skipActionStage && i < 2; i++ {
|
||||||
var originalSkill *info.SkillEntity //原始技能
|
var originalSkill *info.SkillEntity //原始技能
|
||||||
var currentSkill *info.SkillEntity //当前技能
|
var currentSkill *info.SkillEntity //当前技能
|
||||||
var currentAction *action.SelectSkillAction
|
var currentAction *action.SelectSkillAction
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
currentAction = firstAttack
|
currentAction = firstAttack
|
||||||
|
if currentAction == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
attacker, defender = f.getSkillParticipants(firstAttack)
|
attacker, defender = f.getSkillParticipants(firstAttack)
|
||||||
originalSkill = f.copySkill(firstAttack)
|
originalSkill = f.copySkill(firstAttack)
|
||||||
//先手阶段,先修复后手效果
|
//先手阶段,先修复后手效果
|
||||||
f.Second.RecoverEffect()
|
f.Second.RecoverEffect()
|
||||||
} else {
|
} else {
|
||||||
currentAction = secondAttack
|
currentAction = secondAttack
|
||||||
|
if currentAction == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if shouldSkipSecondAction(f.First, f.Second) {
|
||||||
|
secondAttack = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
attacker, defender = f.getSkillParticipants(secondAttack)
|
attacker, defender = f.getSkillParticipants(secondAttack)
|
||||||
originalSkill = f.copySkill(secondAttack)
|
originalSkill = f.copySkill(secondAttack)
|
||||||
//取消后手历史效果
|
//取消后手历史效果
|
||||||
@@ -331,7 +399,6 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
|||||||
|
|
||||||
}
|
}
|
||||||
//先手权不一定出手
|
//先手权不一定出手
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
f.setActionAttackValue(currentAction)
|
f.setActionAttackValue(currentAction)
|
||||||
|
|
||||||
@@ -422,11 +489,19 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
|||||||
|
|
||||||
attackValueResult := f.buildNoteUseSkillOutboundInfo()
|
attackValueResult := f.buildNoteUseSkillOutboundInfo()
|
||||||
//因为切完才能广播,所以必须和回合结束分开结算
|
//因为切完才能广播,所以必须和回合结束分开结算
|
||||||
f.Broadcast(func(fighter *input.Input) {
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
for _, switchAction := range f.Switch {
|
for _, switchAction := range f.Switch {
|
||||||
if fighter.Player.GetInfo().UserID != switchAction.Reason.UserId {
|
if p.GetInfo().UserID != switchAction.Reason.UserId {
|
||||||
// println("切精灵", switchAction.Reason.UserId, switchAction.Reason.ID)
|
// println("切精灵", switchAction.Reason.UserId, switchAction.Reason.ID)
|
||||||
f.sendFightPacket(fighter.Player, fightPacketChangePetSuccess, &switchAction.Reason)
|
if f.LegacyGroupProtocol {
|
||||||
|
switchedInput := f.getInputByUserID(switchAction.Reason.UserId, int(switchAction.Reason.ActorIndex), false)
|
||||||
|
if switchedInput == nil {
|
||||||
|
switchedInput = f.getInputByUserID(switchAction.Reason.UserId, int(switchAction.ActorIndex), false)
|
||||||
|
}
|
||||||
|
f.sendLegacyGroupChangePetSuccess(p, switchedInput, &switchAction.Reason)
|
||||||
|
} else {
|
||||||
|
f.sendFightPacket(p, fightPacketChangePetSuccess, &switchAction.Reason)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -440,8 +515,14 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction)
|
|||||||
// })
|
// })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if attackValueResult.FirstAttackInfo.UserID != 0 || attackValueResult.SecondAttackInfo.UserID != 0 {
|
||||||
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
|
if !f.LegacyGroupProtocol {
|
||||||
|
f.sendFightPacket(p, fightPacketSkillResult, &attackValueResult)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
f.Broadcast(func(fighter *input.Input) {
|
f.Broadcast(func(fighter *input.Input) {
|
||||||
f.sendFightPacket(fighter.Player, fightPacketSkillResult, &attackValueResult)
|
|
||||||
fighter.CanChange = 0
|
fighter.CanChange = 0
|
||||||
})
|
})
|
||||||
if f.closefight {
|
if f.closefight {
|
||||||
@@ -454,12 +535,7 @@ func (f *FightC) TURNOVER(cur *input.Input) {
|
|||||||
if cur == nil {
|
if cur == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, pet := range cur.BenchPets() {
|
_hasBackup = cur.HasLivingBench()
|
||||||
if pet != nil && pet.Info.Hp > 0 {
|
|
||||||
_hasBackup = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.sendLegacySpriteDie(cur, _hasBackup)
|
f.sendLegacySpriteDie(cur, _hasBackup)
|
||||||
|
|
||||||
f.Broadcast(func(ff *input.Input) {
|
f.Broadcast(func(ff *input.Input) {
|
||||||
@@ -474,11 +550,12 @@ func (f *FightC) TURNOVER(cur *input.Input) {
|
|||||||
if f.IsWin(f.GetInputByPlayer(cur.Player, true)) { //然后检查是否战斗结束
|
if f.IsWin(f.GetInputByPlayer(cur.Player, true)) { //然后检查是否战斗结束
|
||||||
|
|
||||||
f.FightOverInfo.WinnerId = f.GetInputByPlayer(cur.Player, true).UserID
|
f.FightOverInfo.WinnerId = f.GetInputByPlayer(cur.Player, true).UserID
|
||||||
f.FightOverInfo.Reason = model.BattleOverReason.DefaultEnd
|
f.FightOverInfo.Reason = normalizeFightOverReason(model.BattleOverReason.DefaultEnd)
|
||||||
f.WinnerId = f.FightOverInfo.WinnerId
|
f.WinnerId = f.FightOverInfo.WinnerId
|
||||||
f.Reason = model.BattleOverReason.DefaultEnd
|
f.Reason = f.FightOverInfo.Reason
|
||||||
|
|
||||||
f.closefight = true
|
f.closefight = true
|
||||||
// break
|
// break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,16 @@ package fight
|
|||||||
import (
|
import (
|
||||||
"blazing/logic/service/common"
|
"blazing/logic/service/common"
|
||||||
"blazing/logic/service/fight/action"
|
"blazing/logic/service/fight/action"
|
||||||
|
"blazing/logic/service/fight/info"
|
||||||
"blazing/logic/service/fight/input"
|
"blazing/logic/service/fight/input"
|
||||||
"blazing/modules/player/model"
|
"blazing/modules/player/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// <!--
|
||||||
|
// GBTL:
|
||||||
|
// 1. AtkNum:本技能同时攻击数量, 默认:1(不能为0)
|
||||||
|
// 2. AtkType:攻击类型: 0:所有人, 1:仅己方, 2:仅对方, 3:仅自己, 默认:2
|
||||||
|
// -->
|
||||||
const (
|
const (
|
||||||
groupCmdReadyToFight uint32 = 7555
|
groupCmdReadyToFight uint32 = 7555
|
||||||
groupCmdReadyFightFinish uint32 = 7556
|
groupCmdReadyFightFinish uint32 = 7556
|
||||||
@@ -38,6 +44,17 @@ const (
|
|||||||
|
|
||||||
type fightPacketKind uint8
|
type fightPacketKind uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
fightPacketReady fightPacketKind = iota
|
||||||
|
fightPacketStart
|
||||||
|
fightPacketSkillResult
|
||||||
|
fightPacketOver
|
||||||
|
fightPacketChangePetSuccess
|
||||||
|
fightPacketUseItem
|
||||||
|
fightPacketChat
|
||||||
|
fightPacketLoadPercentNotice
|
||||||
|
)
|
||||||
|
|
||||||
type legacyEscapeSuccessInfo struct {
|
type legacyEscapeSuccessInfo struct {
|
||||||
UserID uint32 `struc:"uint32"`
|
UserID uint32 `struc:"uint32"`
|
||||||
Nick string `struc:"[16]byte"`
|
Nick string `struc:"[16]byte"`
|
||||||
@@ -50,6 +67,14 @@ type legacyBoutDoneInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type legacySpriteDieInfo struct {
|
type legacySpriteDieInfo struct {
|
||||||
|
Count uint8 `struc:"uint8"`
|
||||||
|
Side uint8 `struc:"uint8"`
|
||||||
|
ActorIndex uint8 `struc:"uint8"`
|
||||||
|
Flag uint8 `struc:"uint8"`
|
||||||
|
HasBackup uint32 `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyLegacySpriteDieItem struct {
|
||||||
Flag uint8 `struc:"uint8"`
|
Flag uint8 `struc:"uint8"`
|
||||||
Side uint8 `struc:"uint8"`
|
Side uint8 `struc:"uint8"`
|
||||||
ActorIndex uint8 `struc:"uint8"`
|
ActorIndex uint8 `struc:"uint8"`
|
||||||
@@ -57,16 +82,132 @@ type legacySpriteDieInfo struct {
|
|||||||
HasBackup uint32 `struc:"uint32"`
|
HasBackup uint32 `struc:"uint32"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
type legacyGroupReadyToFightInfo struct {
|
||||||
fightPacketReady fightPacketKind = iota
|
Model uint32 `struc:"uint32"`
|
||||||
fightPacketStart
|
GroupOneInfo legacyReadyToFightTeam `struc:""`
|
||||||
fightPacketSkillResult
|
GroupTwoInfo legacyReadyToFightTeam `struc:""`
|
||||||
fightPacketOver
|
}
|
||||||
fightPacketChangePetSuccess
|
|
||||||
fightPacketUseItem
|
type legacyReadyToFightTeam struct {
|
||||||
fightPacketChat
|
InvitorID uint8 `struc:"uint8"`
|
||||||
fightPacketLoadPercentNotice
|
LeaderID uint32 `struc:"uint32"`
|
||||||
)
|
GroupMembCnt uint8 `struc:"sizeof=GroupList"`
|
||||||
|
GroupList []legacyReadyFightUser `struc:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyReadyFightUser struct {
|
||||||
|
UserID uint32 `struc:"uint32"`
|
||||||
|
Nick string `struc:"[16]byte"`
|
||||||
|
MonCnt uint32 `struc:"sizeof=MonList"`
|
||||||
|
MonList []legacyReadyFightPet `struc:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyReadyFightPet struct {
|
||||||
|
ID uint32 `struc:"uint32"`
|
||||||
|
MoveCnt uint32 `struc:"sizeof=MoveList"`
|
||||||
|
MoveList []uint32 `struc:"[]uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupStartInfo struct {
|
||||||
|
IsGank uint8 `struc:"uint8"`
|
||||||
|
GroupOneN uint8 `struc:"sizeof=GroupOne"`
|
||||||
|
GroupOne []legacyGroupStartPet `struc:""`
|
||||||
|
GroupTwoN uint8 `struc:"sizeof=GroupTwo"`
|
||||||
|
GroupTwo []legacyGroupStartPet `struc:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupStartPet struct {
|
||||||
|
Side uint8 `struc:"uint8"`
|
||||||
|
Pos uint8 `struc:"uint8"`
|
||||||
|
UserID uint32 `struc:"uint32"`
|
||||||
|
IsChange uint8 `struc:"uint8"`
|
||||||
|
PetID uint32 `struc:"uint32"`
|
||||||
|
CatchTime uint32 `struc:"uint32"`
|
||||||
|
Hp uint32 `struc:"uint32"`
|
||||||
|
MaxHp uint32 `struc:"uint32"`
|
||||||
|
Level uint32 `struc:"uint32"`
|
||||||
|
Reserve uint32 `struc:"uint32"`
|
||||||
|
Flag uint32 `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupSkillHurtPacket struct {
|
||||||
|
IsGank uint8 `struc:"uint8"`
|
||||||
|
Attack legacyGroupSkillAttackInfo `struc:""`
|
||||||
|
Attacked legacyGroupSkillDefendInfo `struc:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupSkillAttackInfo struct {
|
||||||
|
IsAttackor uint8 `struc:"uint8"`
|
||||||
|
Side uint8 `struc:"uint8"`
|
||||||
|
Pos uint8 `struc:"uint8"`
|
||||||
|
UserID uint32 `struc:"uint32"`
|
||||||
|
StatusList [20]uint8 `struc:"[20]byte"`
|
||||||
|
Reserve1 uint8 `struc:"uint8"`
|
||||||
|
Reserve2 uint8 `struc:"uint8"`
|
||||||
|
BatLvList [6]uint8 `struc:"[6]byte"`
|
||||||
|
PetID uint32 `struc:"uint32"`
|
||||||
|
MoveID uint32 `struc:"uint32"`
|
||||||
|
Hp uint32 `struc:"uint32"`
|
||||||
|
MaxHp uint32 `struc:"uint32"`
|
||||||
|
MoveCnt uint32 `struc:"sizeof=MoveMap"`
|
||||||
|
MoveMap []legacyGroupSkillMoveInfo `struc:""`
|
||||||
|
Flag uint32 `struc:"uint32"`
|
||||||
|
IsCrit uint32 `struc:"uint32"`
|
||||||
|
EffectName uint32 `struc:"uint32"`
|
||||||
|
AtkTimes uint32 `struc:"uint32"`
|
||||||
|
Dmg int32 `struc:"int32"`
|
||||||
|
ChgHp int32 `struc:"int32"`
|
||||||
|
SideEffectLen uint32 `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupSkillDefendInfo struct {
|
||||||
|
IsAttackor uint8 `struc:"uint8"`
|
||||||
|
Side uint8 `struc:"uint8"`
|
||||||
|
Pos uint8 `struc:"uint8"`
|
||||||
|
UserID uint32 `struc:"uint32"`
|
||||||
|
StatusList [20]uint8 `struc:"[20]byte"`
|
||||||
|
Reserve1 uint8 `struc:"uint8"`
|
||||||
|
Reserve2 uint8 `struc:"uint8"`
|
||||||
|
BatLvList [6]uint8 `struc:"[6]byte"`
|
||||||
|
PetID uint32 `struc:"uint32"`
|
||||||
|
MoveID uint32 `struc:"uint32"`
|
||||||
|
Hp uint32 `struc:"uint32"`
|
||||||
|
MaxHp uint32 `struc:"uint32"`
|
||||||
|
MoveCnt uint32 `struc:"sizeof=MoveMap"`
|
||||||
|
MoveMap []legacyGroupSkillMoveInfo `struc:""`
|
||||||
|
Flag uint32 `struc:"uint32"`
|
||||||
|
SideEffectLen uint32 `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupSkillMoveInfo struct {
|
||||||
|
MoveID uint32 `struc:"uint32"`
|
||||||
|
PP uint32 `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupFightOverInfo struct {
|
||||||
|
IsGank uint8 `struc:"uint8"`
|
||||||
|
Reason uint32 `struc:"uint32"`
|
||||||
|
WinnerID uint32 `struc:"uint32"`
|
||||||
|
Reserve uint32 `struc:"uint32"`
|
||||||
|
TwoTimes uint32 `struc:"uint32"`
|
||||||
|
ThreeTimes uint32 `struc:"uint32"`
|
||||||
|
AutoFightTime uint32 `struc:"uint32"`
|
||||||
|
Reserve2 uint32 `struc:"uint32"`
|
||||||
|
EnergyTime uint32 `struc:"uint32"`
|
||||||
|
LearnTimes uint32 `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyGroupChangePetSuccessInfo struct {
|
||||||
|
Side uint8 `struc:"uint8"`
|
||||||
|
Pos uint8 `struc:"uint8"`
|
||||||
|
UserID uint32 `struc:"uint32"`
|
||||||
|
PetID uint32 `struc:"uint32"`
|
||||||
|
CatchTime uint32 `struc:"uint32"`
|
||||||
|
Level uint32 `struc:"uint32"`
|
||||||
|
Hp uint32 `struc:"uint32"`
|
||||||
|
MaxHp uint32 `struc:"uint32"`
|
||||||
|
SkinID uint32 `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
func groupModelByFight(f *FightC) uint32 {
|
func groupModelByFight(f *FightC) uint32 {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
@@ -84,37 +225,15 @@ func groupModelByFight(f *FightC) uint32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FightC) sendLegacyGroupOver(player common.PlayerI, over *model.FightOverInfo) {
|
|
||||||
if player == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if over == nil {
|
|
||||||
over = &model.FightOverInfo{}
|
|
||||||
}
|
|
||||||
f.sendFightPacket(player, fightPacketOver, over)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FightC) fightPacketCmd(kind fightPacketKind) uint32 {
|
func (f *FightC) fightPacketCmd(kind fightPacketKind) uint32 {
|
||||||
switch kind {
|
switch kind {
|
||||||
case fightPacketReady:
|
case fightPacketReady:
|
||||||
if f != nil && f.LegacyGroupProtocol {
|
|
||||||
return groupCmdReadyToFight
|
|
||||||
}
|
|
||||||
return 2503
|
return 2503
|
||||||
case fightPacketStart:
|
case fightPacketStart:
|
||||||
if f != nil && f.LegacyGroupProtocol {
|
|
||||||
return groupCmdStartFight
|
|
||||||
}
|
|
||||||
return 2504
|
return 2504
|
||||||
case fightPacketSkillResult:
|
case fightPacketSkillResult:
|
||||||
if f != nil && f.LegacyGroupProtocol {
|
|
||||||
return groupCmdSkillHurt
|
|
||||||
}
|
|
||||||
return 2505
|
return 2505
|
||||||
case fightPacketOver:
|
case fightPacketOver:
|
||||||
if f != nil && f.LegacyGroupProtocol {
|
|
||||||
return groupCmdFightOver
|
|
||||||
}
|
|
||||||
return 2506
|
return 2506
|
||||||
case fightPacketChangePetSuccess:
|
case fightPacketChangePetSuccess:
|
||||||
if f != nil && f.LegacyGroupProtocol {
|
if f != nil && f.LegacyGroupProtocol {
|
||||||
@@ -152,6 +271,209 @@ func (f *FightC) sendFightPacket(player common.PlayerI, kind fightPacketKind, pa
|
|||||||
player.SendPackCmd(cmd, payload)
|
player.SendPackCmd(cmd, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FightC) sendLegacyGroupReady(player common.PlayerI) {
|
||||||
|
if f == nil || !f.LegacyGroupProtocol || player == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player.SendPackCmd(groupCmdReadyToFight, f.buildLegacyGroupReadyInfo())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyGroupReadyInfo() *legacyGroupReadyToFightInfo {
|
||||||
|
return &legacyGroupReadyToFightInfo{
|
||||||
|
Model: groupModelByFight(f),
|
||||||
|
GroupOneInfo: f.buildLegacyReadyTeam(f.OurPlayers, f.Our),
|
||||||
|
GroupTwoInfo: f.buildLegacyReadyTeam(f.OppPlayers, f.Opp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyReadyTeam(players []common.PlayerI, inputs []*input.Input) legacyReadyToFightTeam {
|
||||||
|
team := legacyReadyToFightTeam{InvitorID: 1}
|
||||||
|
users := make([]legacyReadyFightUser, 0, len(players))
|
||||||
|
for _, p := range players {
|
||||||
|
if p == nil || p.GetInfo() == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
users = append(users, legacyReadyFightUser{
|
||||||
|
UserID: p.GetInfo().UserID,
|
||||||
|
Nick: p.GetInfo().Nick,
|
||||||
|
MonList: collectLegacyReadyPetsByController(inputs, p.GetInfo().UserID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
if fallback := firstNonNilInput(inputs); fallback != nil && fallback.Player != nil && fallback.Player.GetInfo() != nil {
|
||||||
|
info := fallback.Player.GetInfo()
|
||||||
|
users = append(users, legacyReadyFightUser{
|
||||||
|
UserID: info.UserID,
|
||||||
|
Nick: info.Nick,
|
||||||
|
MonList: collectLegacyReadyPetsByController(inputs, info.UserID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for idx := range users {
|
||||||
|
users[idx].MonCnt = uint32(len(users[idx].MonList))
|
||||||
|
}
|
||||||
|
if len(users) > 0 {
|
||||||
|
team.LeaderID = users[0].UserID
|
||||||
|
}
|
||||||
|
team.GroupList = users
|
||||||
|
return team
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectLegacyReadyPetsByController(inputs []*input.Input, controllerID uint32) []legacyReadyFightPet {
|
||||||
|
pets := make([]legacyReadyFightPet, 0, 6)
|
||||||
|
for _, in := range inputs {
|
||||||
|
if in == nil || !in.ControlledBy(controllerID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentPet := in.CurrentPet()
|
||||||
|
if currentPet == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pets = append(pets, buildLegacyReadyFightPet(currentPet))
|
||||||
|
}
|
||||||
|
return pets
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLegacyReadyFightPet(pet *info.BattlePetEntity) legacyReadyFightPet {
|
||||||
|
result := legacyReadyFightPet{}
|
||||||
|
if pet == nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
moves := make([]uint32, 0, len(pet.Info.SkillList))
|
||||||
|
for _, skill := range pet.Info.SkillList {
|
||||||
|
if skill.ID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
moves = append(moves, skill.ID)
|
||||||
|
}
|
||||||
|
result.ID = pet.Info.ID
|
||||||
|
result.MoveList = moves
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstNonNilInput(inputs []*input.Input) *input.Input {
|
||||||
|
for _, in := range inputs {
|
||||||
|
if in != nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) sendLegacyGroupStart(player common.PlayerI) {
|
||||||
|
if f == nil || !f.LegacyGroupProtocol || player == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player.SendPackCmd(groupCmdStartFight, f.buildLegacyGroupStartInfo())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyGroupStartInfo() *legacyGroupStartInfo {
|
||||||
|
return &legacyGroupStartInfo{
|
||||||
|
IsGank: 0,
|
||||||
|
GroupOne: f.collectLegacyGroupStartPets(f.Our, 1),
|
||||||
|
GroupTwo: f.collectLegacyGroupStartPets(f.Opp, 2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) collectLegacyGroupStartPets(inputs []*input.Input, side uint8) []legacyGroupStartPet {
|
||||||
|
ret := make([]legacyGroupStartPet, 0, len(inputs))
|
||||||
|
for pos, in := range inputs {
|
||||||
|
if in == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentPet := in.CurrentPet()
|
||||||
|
if currentPet == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
userID := uint32(0)
|
||||||
|
if in.Player != nil && in.Player.GetInfo() != nil {
|
||||||
|
userID = in.Player.GetInfo().UserID
|
||||||
|
}
|
||||||
|
ret = append(ret, legacyGroupStartPet{
|
||||||
|
Side: side,
|
||||||
|
Pos: uint8(pos),
|
||||||
|
UserID: userID,
|
||||||
|
IsChange: 0,
|
||||||
|
PetID: currentPet.Info.ID,
|
||||||
|
CatchTime: currentPet.Info.CatchTime,
|
||||||
|
Hp: currentPet.Info.Hp,
|
||||||
|
MaxHp: currentPet.Info.MaxHp,
|
||||||
|
Level: currentPet.Info.Level,
|
||||||
|
Reserve: 0,
|
||||||
|
Flag: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) sendLegacyGroupOver(player common.PlayerI, over *model.FightOverInfo) {
|
||||||
|
if f == nil || !f.LegacyGroupProtocol || player == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player.SendPackCmd(groupCmdFightOver, f.buildLegacyGroupOverInfo(over))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyGroupOverInfo(over *model.FightOverInfo) *legacyGroupFightOverInfo {
|
||||||
|
result := &legacyGroupFightOverInfo{}
|
||||||
|
if over != nil {
|
||||||
|
result.Reason = resolveLegacyGroupFightOverReason(over)
|
||||||
|
result.WinnerID = over.WinnerId
|
||||||
|
}
|
||||||
|
if our := f.primaryOurPlayer(); our != nil && our.GetInfo() != nil {
|
||||||
|
playerInfo := our.GetInfo()
|
||||||
|
result.TwoTimes = uint32(playerInfo.TwoTimes)
|
||||||
|
result.ThreeTimes = uint32(playerInfo.ThreeTimes)
|
||||||
|
result.AutoFightTime = playerInfo.AutoFightTime
|
||||||
|
result.EnergyTime = uint32(playerInfo.EnergyTime)
|
||||||
|
result.LearnTimes = playerInfo.LearnTimes
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapLegacyGroupFightOverReason(reason model.EnumBattleOverReason) uint32 {
|
||||||
|
return mapUnifiedFightOverReason(reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveLegacyGroupFightOverReason(over *model.FightOverInfo) uint32 {
|
||||||
|
if over == nil {
|
||||||
|
return mapUnifiedFightOverReason(0)
|
||||||
|
}
|
||||||
|
if over.WinnerId != 0 {
|
||||||
|
return mapUnifiedFightOverReason(0)
|
||||||
|
}
|
||||||
|
return mapLegacyGroupFightOverReason(over.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) sendLegacyGroupChangePetSuccess(player common.PlayerI, in *input.Input, reason *info.ChangePetInfo) {
|
||||||
|
if f == nil || !f.LegacyGroupProtocol || player == nil || in == nil || reason == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
player.SendPackCmd(groupCmdChangePetSuc, f.buildLegacyGroupChangePetSuccessInfo(in, reason))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyGroupChangePetSuccessInfo(in *input.Input, reason *info.ChangePetInfo) *legacyGroupChangePetSuccessInfo {
|
||||||
|
result := &legacyGroupChangePetSuccessInfo{}
|
||||||
|
if in == nil || reason == nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
if !f.isOurPlayerID(in.UserID) {
|
||||||
|
result.Side = 2
|
||||||
|
} else {
|
||||||
|
result.Side = 1
|
||||||
|
}
|
||||||
|
result.Pos = uint8(in.TeamSlotIndex())
|
||||||
|
result.UserID = reason.UserId
|
||||||
|
result.PetID = reason.ID
|
||||||
|
result.CatchTime = reason.CatchTime
|
||||||
|
result.Level = reason.Level
|
||||||
|
result.Hp = reason.Hp
|
||||||
|
result.MaxHp = reason.MaxHp
|
||||||
|
if currentPet := in.CurrentPet(); currentPet != nil {
|
||||||
|
result.SkinID = currentPet.Info.SkinID
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FightC) SendLegacyEscapeSuccess(player common.PlayerI, actorIndex int) {
|
func (f *FightC) SendLegacyEscapeSuccess(player common.PlayerI, actorIndex int) {
|
||||||
if f == nil || !f.LegacyGroupProtocol || player == nil {
|
if f == nil || !f.LegacyGroupProtocol || player == nil {
|
||||||
return
|
return
|
||||||
@@ -166,11 +488,8 @@ func (f *FightC) SendLegacyEscapeSuccess(player common.PlayerI, actorIndex int)
|
|||||||
Side: side,
|
Side: side,
|
||||||
ActorIndex: uint8(actorIndex),
|
ActorIndex: uint8(actorIndex),
|
||||||
}
|
}
|
||||||
f.Broadcast(func(ff *input.Input) {
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
if ff == nil || ff.Player == nil {
|
p.SendPackCmd(groupCmdEscapeSuc, &payload)
|
||||||
return
|
|
||||||
}
|
|
||||||
ff.Player.SendPackCmd(groupCmdEscapeSuc, &payload)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,19 +497,155 @@ func (f *FightC) sendLegacyRoundBroadcast(firstAttack, secondAttack *action.Sele
|
|||||||
if f == nil || !f.LegacyGroupProtocol {
|
if f == nil || !f.LegacyGroupProtocol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if f.legacySkillExecuted(firstAttack) {
|
||||||
|
f.sendLegacyGroupSkillHurt(firstAttack)
|
||||||
|
}
|
||||||
|
if f.legacySkillExecuted(secondAttack) {
|
||||||
|
f.sendLegacyGroupSkillHurt(secondAttack)
|
||||||
|
}
|
||||||
f.sendLegacyGroupBoutDone()
|
f.sendLegacyGroupBoutDone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FightC) legacySkillExecuted(skillAction *action.SelectSkillAction) bool {
|
||||||
|
if f == nil || skillAction == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
attacker := f.GetInputByAction(skillAction, false)
|
||||||
|
return attacker != nil && attacker.AttackValue != nil && attacker.AttackValue.SkillID != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) sendLegacyGroupSkillHurt(skillAction *action.SelectSkillAction) {
|
||||||
|
if f == nil || !f.LegacyGroupProtocol || skillAction == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packet := f.buildLegacyGroupSkillHurtPacket(skillAction)
|
||||||
|
if packet == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
|
p.SendPackCmd(groupCmdSkillHurt, packet)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyGroupSkillHurtPacket(skillAction *action.SelectSkillAction) *legacyGroupSkillHurtPacket {
|
||||||
|
attacker := f.GetInputByAction(skillAction, false)
|
||||||
|
defender := f.GetInputByAction(skillAction, true)
|
||||||
|
if attacker == nil || defender == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &legacyGroupSkillHurtPacket{
|
||||||
|
IsGank: 0,
|
||||||
|
Attack: f.buildLegacyGroupSkillAttackInfo(skillAction, attacker),
|
||||||
|
Attacked: f.buildLegacyGroupSkillDefendInfo(defender),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) fillLegacyGroupSkillCommonFields(
|
||||||
|
self *input.Input,
|
||||||
|
isAttackor uint8,
|
||||||
|
statusList *[20]uint8,
|
||||||
|
batLvList *[6]uint8,
|
||||||
|
) (side uint8, pos uint8, userID uint32, petID uint32, hp uint32, maxHP uint32, moveMap []legacyGroupSkillMoveInfo, flag uint32) {
|
||||||
|
if self == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !f.isOurPlayerID(self.UserID) {
|
||||||
|
side = 2
|
||||||
|
} else {
|
||||||
|
side = 1
|
||||||
|
}
|
||||||
|
pos = uint8(self.TeamSlotIndex())
|
||||||
|
userID = self.UserID
|
||||||
|
attackValue := self.AttackValue
|
||||||
|
if attackValue == nil {
|
||||||
|
attackValue = info.NewAttackValue(self.UserID)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(attackValue.Status) && i < 20; i++ {
|
||||||
|
statusList[i] = uint8(attackValue.Status[i])
|
||||||
|
}
|
||||||
|
for i := 0; i < len(attackValue.Prop) && i < len(batLvList); i++ {
|
||||||
|
batLvList[i] = uint8(attackValue.Prop[i])
|
||||||
|
}
|
||||||
|
currentPet := self.CurrentPet()
|
||||||
|
if currentPet != nil {
|
||||||
|
petID = currentPet.Info.ID
|
||||||
|
hp = currentPet.Info.Hp
|
||||||
|
maxHP = currentPet.Info.MaxHp
|
||||||
|
moveMap = collectLegacyGroupSkillMoves(currentPet.Info.SkillList)
|
||||||
|
} else {
|
||||||
|
hp = clampLegacyInt32ToUint32(attackValue.RemainHp)
|
||||||
|
maxHP = attackValue.MaxHp
|
||||||
|
moveMap = collectLegacyGroupSkillMoves(attackValue.SkillList)
|
||||||
|
}
|
||||||
|
flag = attackValue.State
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyGroupSkillAttackInfo(skillAction *action.SelectSkillAction, self *input.Input) legacyGroupSkillAttackInfo {
|
||||||
|
result := legacyGroupSkillAttackInfo{}
|
||||||
|
if self == nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
result.IsAttackor = 0
|
||||||
|
result.Side, result.Pos, result.UserID, result.PetID, result.Hp, result.MaxHp, result.MoveMap, result.Flag =
|
||||||
|
f.fillLegacyGroupSkillCommonFields(self, result.IsAttackor, &result.StatusList, &result.BatLvList)
|
||||||
|
attackValue := self.AttackValue
|
||||||
|
if attackValue == nil {
|
||||||
|
attackValue = info.NewAttackValue(self.UserID)
|
||||||
|
}
|
||||||
|
if attackValue.SkillID != 0 {
|
||||||
|
result.MoveID = attackValue.SkillID
|
||||||
|
} else if skillAction != nil && skillAction.SkillEntity != nil {
|
||||||
|
result.MoveID = uint32(skillAction.SkillEntity.XML.ID)
|
||||||
|
}
|
||||||
|
result.IsCrit = attackValue.IsCritical
|
||||||
|
result.EffectName = attackValue.State
|
||||||
|
result.AtkTimes = 1
|
||||||
|
result.Dmg = int32(attackValue.LostHp)
|
||||||
|
result.ChgHp = attackValue.GainHp
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FightC) buildLegacyGroupSkillDefendInfo(self *input.Input) legacyGroupSkillDefendInfo {
|
||||||
|
result := legacyGroupSkillDefendInfo{}
|
||||||
|
if self == nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
result.IsAttackor = 1
|
||||||
|
result.Side, result.Pos, result.UserID, result.PetID, result.Hp, result.MaxHp, result.MoveMap, result.Flag =
|
||||||
|
f.fillLegacyGroupSkillCommonFields(self, result.IsAttackor, &result.StatusList, &result.BatLvList)
|
||||||
|
result.MoveID = 0
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectLegacyGroupSkillMoves(skills []model.SkillInfo) []legacyGroupSkillMoveInfo {
|
||||||
|
moves := make([]legacyGroupSkillMoveInfo, 0, len(skills))
|
||||||
|
for _, skill := range skills {
|
||||||
|
if skill.ID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
moves = append(moves, legacyGroupSkillMoveInfo{
|
||||||
|
MoveID: skill.ID,
|
||||||
|
PP: skill.PP,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return moves
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampLegacyInt32ToUint32(v int32) uint32 {
|
||||||
|
if v < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return uint32(v)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FightC) sendLegacyGroupBoutDone() {
|
func (f *FightC) sendLegacyGroupBoutDone() {
|
||||||
if f == nil || !f.LegacyGroupProtocol {
|
if f == nil || !f.LegacyGroupProtocol {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
payload := legacyBoutDoneInfo{Round: f.Round}
|
payload := legacyBoutDoneInfo{Round: f.Round}
|
||||||
f.Broadcast(func(ff *input.Input) {
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
if ff == nil || ff.Player == nil {
|
p.SendPackCmd(groupCmdBoutDone, &payload)
|
||||||
return
|
|
||||||
}
|
|
||||||
ff.Player.SendPackCmd(groupCmdBoutDone, &payload)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,16 +662,13 @@ func (f *FightC) sendLegacySpriteDie(in *input.Input, hasBackup bool) {
|
|||||||
data = 1
|
data = 1
|
||||||
}
|
}
|
||||||
payload := legacySpriteDieInfo{
|
payload := legacySpriteDieInfo{
|
||||||
Flag: 1,
|
Count: 1,
|
||||||
Side: side,
|
Side: side,
|
||||||
ActorIndex: uint8(in.TeamSlotIndex()),
|
ActorIndex: uint8(in.TeamSlotIndex()),
|
||||||
Reserve: 1,
|
Flag: 1,
|
||||||
HasBackup: data,
|
HasBackup: data,
|
||||||
}
|
}
|
||||||
f.Broadcast(func(ff *input.Input) {
|
f.BroadcastPlayers(func(p common.PlayerI) {
|
||||||
if ff == nil || ff.Player == nil {
|
p.SendPackCmd(groupCmdSpriteDie, &payload)
|
||||||
return
|
|
||||||
}
|
|
||||||
ff.Player.SendPackCmd(groupCmdSpriteDie, &payload)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
61
logic/service/fight/group_legacy_test.go
Normal file
61
logic/service/fight/group_legacy_test.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package fight
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"blazing/logic/service/fight/action"
|
||||||
|
fightinfo "blazing/logic/service/fight/info"
|
||||||
|
"blazing/logic/service/fight/input"
|
||||||
|
"blazing/modules/player/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSendLegacyRoundBroadcastSkipsUnexecutedAction(t *testing.T) {
|
||||||
|
ourPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 1001}}
|
||||||
|
oppPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 2002}}
|
||||||
|
|
||||||
|
our := input.NewInput(nil, ourPlayer)
|
||||||
|
our.InitAttackValue()
|
||||||
|
our.AttackValue.SkillID = 111
|
||||||
|
our.SetCurPetAt(0, fightinfo.CreateBattlePetEntity(model.PetInfo{
|
||||||
|
ID: 11,
|
||||||
|
Hp: 80,
|
||||||
|
MaxHp: 100,
|
||||||
|
CatchTime: 101,
|
||||||
|
}))
|
||||||
|
|
||||||
|
opp := input.NewInput(nil, oppPlayer)
|
||||||
|
opp.InitAttackValue()
|
||||||
|
opp.SetCurPetAt(0, fightinfo.CreateBattlePetEntity(model.PetInfo{
|
||||||
|
ID: 22,
|
||||||
|
Hp: 0,
|
||||||
|
MaxHp: 100,
|
||||||
|
CatchTime: 202,
|
||||||
|
}))
|
||||||
|
|
||||||
|
fc := &FightC{
|
||||||
|
Our: []*input.Input{our},
|
||||||
|
Opp: []*input.Input{opp},
|
||||||
|
LegacyGroupProtocol: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
firstAttack := &action.SelectSkillAction{
|
||||||
|
BaseAction: action.BaseAction{PlayerID: ourPlayer.info.UserID, ActorIndex: 0, TargetIndex: 0},
|
||||||
|
}
|
||||||
|
secondAttack := &action.SelectSkillAction{
|
||||||
|
BaseAction: action.BaseAction{PlayerID: oppPlayer.info.UserID, ActorIndex: 0, TargetIndex: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
fc.sendLegacyRoundBroadcast(firstAttack, secondAttack)
|
||||||
|
|
||||||
|
for _, player := range []*stubPlayer{ourPlayer, oppPlayer} {
|
||||||
|
if len(player.sentCmds) != 2 {
|
||||||
|
t.Fatalf("expected one skill packet plus bout done, got %v", player.sentCmds)
|
||||||
|
}
|
||||||
|
if player.sentCmds[0] != groupCmdSkillHurt {
|
||||||
|
t.Fatalf("expected first packet to be skill hurt, got %d", player.sentCmds[0])
|
||||||
|
}
|
||||||
|
if player.sentCmds[1] != groupCmdBoutDone {
|
||||||
|
t.Fatalf("expected second packet to be bout done, got %d", player.sentCmds[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user