// Package modules - 阵法系统模块 // 对齐GDD-03 附.C + GDD-27 二 阵法深化系统 package modules import ( "context" "database/sql" "encoding/json" "errors" "math/rand" "time" "github.com/heroiclabs/nakama-common/runtime" "github.com/jackc/pgx/v5" ) // RegisterFormation 注册阵法相关 RPC。 func RegisterFormation(initializer runtime.Initializer) error { rpcs := map[string]func(runtime.Initializer) 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悟性 float64 err = hhdbPool.QueryRow(ctx, ` SELECT (base_stats->>'悟')::float FROM characters WHERE id = $1 `, charID).Scan(&悟性) if err != nil { 悟性 = 15.0 // 默认值 } breakRate := 0.3 + 悟性*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) }