一些检测仍在等待运行
Docs Build / build-and-deploy (push) Waiting to run
- 移除 ConfigManager 配置管理器类 - 移除 GameManager 全局单例管理器类 - 移除 NetworkManager 网络连接管理器类 - 移除 CharacterData 和 ItemData 数据模型类 - 移除 BagScene、BattleScene、LobbyScene 等场景脚本 - 移除 EncounterBubble 和 EventFeedPanel UI组件脚本 - 更新代理邀请文档中的服务器连接方式 - 更新同步状态表格中的代理任务分配信息 - 添加 MiMo 任务完成总结和审查修复记录
271 行
7.7 KiB
Go
271 行
7.7 KiB
Go
// Package modules - 阵法系统模块
|
|
// 对齐GDD-03 附.C + GDD-27 二 阵法深化系统
|
|
package modules
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"math/rand"
|
|
|
|
"github.com/heroiclabs/nakama-common/runtime"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// RegisterFormation 注册阵法相关 RPC。
|
|
func RegisterFormation(initializer runtime.Initializer) error {
|
|
rpcs := map[string]func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error){
|
|
"FormationService/DeployFormation": deployFormation,
|
|
"FormationService/BreakFormation": breakFormation,
|
|
"FormationService/GetFormations": getFormations,
|
|
}
|
|
for path, fn := range rpcs {
|
|
if err := initializer.RegisterRpc(path, fn); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type deployFormationReq struct {
|
|
FormationType string `json:"formation_type"` // attack/defense/control/encapsulate/support
|
|
Grade string `json:"grade"` // mortal/yellow/xuan/di/celestial/immortal
|
|
Materials []string `json:"materials"` // 阵旗/材料ID列表
|
|
}
|
|
|
|
type breakFormationReq struct {
|
|
FormationID string `json:"formation_id"`
|
|
}
|
|
|
|
type getFormationsReq struct {
|
|
CharacterID string `json:"character_id"`
|
|
LocationType string `json:"location_type"` // character/guild/zone
|
|
}
|
|
|
|
type formationData struct {
|
|
ID string `json:"id"`
|
|
FormationType string `json:"formation_type"`
|
|
Grade string `json:"grade"`
|
|
Level int32 `json:"level"`
|
|
Stats interface{} `json:"stats"`
|
|
IsActive bool `json:"is_active"`
|
|
LocationType string `json:"location_type"`
|
|
}
|
|
|
|
// 阵法品阶 → 成功率/效果系数映射
|
|
var formationGradeCoeff = map[string]struct {
|
|
successRate float64
|
|
effectCoef float64
|
|
}{
|
|
"mortal": {0.95, 1.0},
|
|
"yellow": {0.85, 1.3},
|
|
"xuan": {0.75, 1.8},
|
|
"di": {0.65, 2.5},
|
|
"celestial": {0.55, 3.5},
|
|
"immortal": {0.45, 5.0},
|
|
}
|
|
|
|
func deployFormation(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
|
traceID := newTraceID()
|
|
uid := userIDFromCtx(ctx)
|
|
if uid == "" {
|
|
return errResp(1001, "missing token", traceID)
|
|
}
|
|
|
|
var req deployFormationReq
|
|
if err := json.Unmarshal([]byte(payload), &req); err != nil {
|
|
return errResp(2001, "invalid payload", traceID)
|
|
}
|
|
|
|
// 获取角色ID
|
|
var charID string
|
|
var realmTier int32
|
|
err := hhdbPool.QueryRow(ctx, `
|
|
SELECT id, realm_tier FROM characters WHERE player_id = $1 AND status = 'active'
|
|
ORDER BY created_at DESC LIMIT 1
|
|
`, uid).Scan(&charID, &realmTier)
|
|
if err != nil {
|
|
return errResp(4002, "character not found", traceID)
|
|
}
|
|
|
|
// 检查境界要求
|
|
gradeRealmReq := map[string]int32{
|
|
"mortal": 1, "yellow": 2, "xuan": 3, "di": 4, "celestial": 5, "immortal": 6,
|
|
}
|
|
if realmTier < gradeRealmReq[req.Grade] {
|
|
return errResp(5003, "realm too low for this formation grade", traceID)
|
|
}
|
|
|
|
// 检查是否在战斗中(战斗中不可布阵)
|
|
var inBattle bool
|
|
err = hhdbPool.QueryRow(ctx, `
|
|
SELECT EXISTS(
|
|
SELECT 1 FROM battles
|
|
WHERE (attacker_id = $1 OR defender_id = $1)
|
|
AND status = 'in_progress'
|
|
)
|
|
`, charID).Scan(&inBattle)
|
|
if err == nil && inBattle {
|
|
return errResp(5004, "cannot deploy formation in battle", traceID)
|
|
}
|
|
|
|
// 计算布阵成功率
|
|
coeff, ok := formationGradeCoeff[req.Grade]
|
|
if !ok {
|
|
return errResp(2001, "invalid grade", traceID)
|
|
}
|
|
successRate := coeff.successRate
|
|
if rand.Float64() > successRate {
|
|
return okResp(map[string]interface{}{
|
|
"success": false,
|
|
"message": "布阵失败,材料已消耗",
|
|
"rate": successRate,
|
|
}, traceID)
|
|
}
|
|
|
|
// 创建阵法
|
|
formationID := genUUID()
|
|
_, err = hhdbPool.Exec(ctx, `
|
|
INSERT INTO formations (id, character_id, formation_type, name, grade, level, stats, is_active, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, 1, '{}', true, NOW())
|
|
`, formationID, charID, req.FormationType, req.FormationType+"阵", req.Grade)
|
|
if err != nil {
|
|
logger.Error("deploy formation insert failed: %v", err)
|
|
return errResp(9002, "internal error", traceID)
|
|
}
|
|
|
|
return okResp(map[string]interface{}{
|
|
"success": true,
|
|
"formation_id": formationID,
|
|
"formation_type": req.FormationType,
|
|
"grade": req.Grade,
|
|
"effect_coef": coeff.effectCoef,
|
|
"message": "布阵成功",
|
|
}, traceID)
|
|
}
|
|
|
|
func breakFormation(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
|
traceID := newTraceID()
|
|
uid := userIDFromCtx(ctx)
|
|
if uid == "" {
|
|
return errResp(1001, "missing token", traceID)
|
|
}
|
|
|
|
var req breakFormationReq
|
|
if err := json.Unmarshal([]byte(payload), &req); err != nil {
|
|
return errResp(2001, "invalid payload", traceID)
|
|
}
|
|
|
|
// 验证阵法归属
|
|
var charID string
|
|
err := hhdbPool.QueryRow(ctx, `
|
|
SELECT f.character_id FROM formations f
|
|
JOIN characters c ON f.character_id = c.id
|
|
WHERE f.id = $1 AND c.player_id = $2
|
|
`, req.FormationID, uid).Scan(&charID)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return errResp(5001, "formation not found", traceID)
|
|
}
|
|
return errResp(9002, "internal error", traceID)
|
|
}
|
|
|
|
// 破阵判定(基于悟性)
|
|
var wisdom float64
|
|
err = hhdbPool.QueryRow(ctx, `
|
|
SELECT (base_stats->>'悟')::float FROM characters WHERE id = $1
|
|
`, charID).Scan(&wisdom)
|
|
if err != nil {
|
|
wisdom = 15.0 // 默认值
|
|
}
|
|
|
|
breakRate := 0.3 + wisdom*0.01 // 基础30% + 悟性加成
|
|
if breakRate > 0.95 {
|
|
breakRate = 0.95
|
|
}
|
|
|
|
success := rand.Float64() < breakRate
|
|
|
|
if success {
|
|
// 破阵成功,删除阵法
|
|
_, err = hhdbPool.Exec(ctx, `DELETE FROM formations WHERE id = $1`, req.FormationID)
|
|
if err != nil {
|
|
return errResp(9002, "internal error", traceID)
|
|
}
|
|
return okResp(map[string]interface{}{
|
|
"success": true,
|
|
"message": "破阵成功",
|
|
}, traceID)
|
|
}
|
|
|
|
// 破阵失败,获得debuff
|
|
return okResp(map[string]interface{}{
|
|
"success": false,
|
|
"message": "破阵失败,受到阵法反噬",
|
|
"debuff": map[string]interface{}{
|
|
"type": "formation_backlash",
|
|
"duration": 1800, // 30游戏秒
|
|
"penalty": "全属性-5%",
|
|
},
|
|
}, traceID)
|
|
}
|
|
|
|
func getFormations(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
|
traceID := newTraceID()
|
|
uid := userIDFromCtx(ctx)
|
|
if uid == "" {
|
|
return errResp(1001, "missing token", traceID)
|
|
}
|
|
|
|
var req getFormationsReq
|
|
if err := json.Unmarshal([]byte(payload), &req); err != nil {
|
|
return errResp(2001, "invalid payload", traceID)
|
|
}
|
|
|
|
var rows pgx.Rows
|
|
var err error
|
|
|
|
switch req.LocationType {
|
|
case "character":
|
|
rows, err = hhdbPool.Query(ctx, `
|
|
SELECT f.id, f.formation_type, f.grade, f.level, f.stats, f.is_active, 'character'
|
|
FROM formations f
|
|
JOIN characters c ON f.character_id = c.id
|
|
WHERE c.player_id = $1 AND c.id = $2 AND f.zone_id IS NULL AND f.guild_id IS NULL
|
|
ORDER BY f.grade DESC
|
|
`, uid, req.CharacterID)
|
|
case "guild":
|
|
rows, err = hhdbPool.Query(ctx, `
|
|
SELECT f.id, f.formation_type, f.grade, f.level, f.stats, f.is_active, 'guild'
|
|
FROM formations f
|
|
JOIN characters c ON f.character_id = c.id
|
|
JOIN guild_members gm ON gm.character_id = c.id
|
|
WHERE c.player_id = $1 AND f.guild_id IS NOT NULL
|
|
ORDER BY f.grade DESC
|
|
`, uid)
|
|
default:
|
|
return errResp(2001, "invalid location_type", traceID)
|
|
}
|
|
|
|
if err != nil {
|
|
logger.Error("get formations failed: %v", err)
|
|
return errResp(9002, "internal error", traceID)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var formations []formationData
|
|
for rows.Next() {
|
|
var f formationData
|
|
if err := rows.Scan(&f.ID, &f.FormationType, &f.Grade, &f.Level, &f.Stats, &f.IsActive, &f.LocationType); err != nil {
|
|
continue
|
|
}
|
|
formations = append(formations, f)
|
|
}
|
|
|
|
return okResp(map[string]interface{}{
|
|
"formations": formations,
|
|
"count": len(formations),
|
|
}, traceID)
|
|
}
|