// Package modules - 法宝对战系统模块 // 对齐GDD-03 附.A 法宝对战系统 + GDD-05 4.9 套装系统 package modules import ( "context" "database/sql" "encoding/json" "errors" "math/rand" "github.com/heroiclabs/nakama-common/runtime" "github.com/jackc/pgx/v5" ) // RegisterArtifact 注册法宝相关 RPC。 func RegisterArtifact(initializer runtime.Initializer) error { rpcs := map[string]func(runtime.Initializer) error{ "ArtifactService/RefineArtifact": refineArtifact, "ArtifactService/EquipArtifact": equipArtifact, "ArtifactService/ActivateArtifact": activateArtifact, "ArtifactService/GetArtifactList": getArtifactList, "ArtifactService/GetSetBonus": getSetBonus, } for path, fn := range rpcs { if err := initializer.RegisterRpc(path, fn); err != nil { return err } } return nil } // --- 请求/响应结构 --- type refineArtifactReq struct { ArtifactID string `json:"artifact_id"` Materials []string `json:"materials"` // 消耗材料ID列表 } type equipArtifactReq struct { ArtifactID string `json:"artifact_id"` SlotName string `json:"slot_name"` // weapon/armor/accessory } type activateArtifactReq struct { ArtifactID string `json:"artifact_id"` } type getArtifactListReq struct { CharacterID string `json:"character_id"` } type getSetBonusReq struct { CharacterID string `json:"character_id"` } type artifactData struct { ID string `json:"id"` CharacterID string `json:"character_id"` ItemID string `json:"item_id"` ArtifactType string `json:"artifact_type"` Grade string `json:"grade"` RefinementLevel int32 `json:"refinement_level"` SpiritLevel int32 `json:"spirit_level"` SpiritName string `json:"spirit_name"` Bound bool `json:"bound"` Stats interface{} `json:"stats"` Skills interface{} `json:"skills"` Durability int32 `json:"durability"` MaxDurability int32 `json:"max_durability"` SetID string `json:"set_id"` } type setBonusData struct { SetID string `json:"set_id"` SetName string `json:"set_name"` PieceCount int32 `json:"piece_count"` OwnedPieces int32 `json:"owned_pieces"` Bonus2 interface{} `json:"bonus_2"` Bonus4 interface{} `json:"bonus_4"` Bonus6 interface{} `json:"bonus_6"` IsActive2 bool `json:"is_active_2"` IsActive4 bool `json:"is_active_4"` IsActive6 bool `json:"is_active_6"` } // --- RPC 实现 --- func refineArtifact(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 refineArtifactReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 查询法宝信息 var artifact artifactData err := hhdbPool.QueryRow(ctx, ` SELECT a.id, a.character_id, a.item_id, a.artifact_type, a.grade, a.refinement_level, a.spirit_level, COALESCE(a.spirit_name,''), a.bound, a.stats, a.skills, a.durability, a.max_durability, COALESCE(a.set_id,'') FROM artifacts a JOIN characters c ON a.character_id = c.id WHERE a.id = $1 AND c.player_id = $2 `, req.ArtifactID, uid).Scan( &artifact.ID, &artifact.CharacterID, &artifact.ItemID, &artifact.ArtifactType, &artifact.Grade, &artifact.RefinementLevel, &artifact.SpiritLevel, &artifact.SpiritName, &artifact.Bound, &artifact.Stats, &artifact.Skills, &artifact.Durability, &artifact.MaxDurability, &artifact.SetID, ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return errResp(5001, "artifact not found", traceID) } logger.Error("refine artifact query failed: %v", err) return errResp(9002, "internal error", traceID) } // 检查耐久 if artifact.Durability <= 0 { return errResp(5002, "artifact durability depleted", traceID) } // 检查境界要求 var realmTier int32 err = hhdbPool.QueryRow(ctx, `SELECT realm_tier FROM characters WHERE id = $1`, artifact.CharacterID).Scan(&realmTier) if err != nil { return errResp(9002, "internal error", traceID) } gradeRealmReq := map[string]int32{ "mortal": 1, "yellow": 2, "xuan": 3, "di": 4, "celestial": 5, "immortal": 6, } if realmTier < gradeRealmReq[artifact.Grade] { return errResp(5003, "realm too low for this artifact grade", traceID) } // 计算炼化成功率 (基础85% + 品阶修正 - 炼化等级惩罚) successRate := 0.85 successRate -= float64(artifact.RefinementLevel) * 0.05 if artifact.RefinementLevel >= 7 { successRate -= 0.10 } if rand.Float64() > successRate { // 炼化失败,消耗材料但不提升 return okResp(map[string]interface{}{ "success": false, "message": "炼化失败,材料已消耗", "rate": successRate, }, traceID) } // 炼化成功 newLevel := artifact.RefinementLevel + 1 _, err = hhdbPool.Exec(ctx, ` UPDATE artifacts SET refinement_level = $1, updated_at = NOW() WHERE id = $2 `, newLevel, artifact.ID) if err != nil { logger.Error("refine artifact update failed: %v", err) return errResp(9002, "internal error", traceID) } return okResp(map[string]interface{}{ "success": true, "refinement_level": newLevel, "message": "炼化成功", }, traceID) } func equipArtifact(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 equipArtifactReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 验证法宝归属 var charID string err := hhdbPool.QueryRow(ctx, ` SELECT a.character_id FROM artifacts a JOIN characters c ON a.character_id = c.id WHERE a.id = $1 AND c.player_id = $2 `, req.ArtifactID, uid).Scan(&charID) if err != nil { return errResp(5001, "artifact not found", traceID) } // 装备到指定槽位(替换旧装备) _, err = hhdbPool.Exec(ctx, ` INSERT INTO equipments (character_id, slot_name, inventory_id, updated_at) VALUES ($1, $2, NULL, NOW()) ON CONFLICT (character_id, slot_name) DO UPDATE SET inventory_id = NULL, updated_at = NOW() `, charID, req.SlotName) if err != nil { logger.Error("equip artifact failed: %v", err) return errResp(9002, "internal error", traceID) } return okResp(map[string]interface{}{ "success": true, "message": "法宝装备成功", }, traceID) } func activateArtifact(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 activateArtifactReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 验证法宝归属 var charID string var spiritLevel int32 err := hhdbPool.QueryRow(ctx, ` SELECT a.character_id, a.spirit_level FROM artifacts a JOIN characters c ON a.character_id = c.id WHERE a.id = $1 AND c.player_id = $2 `, req.ArtifactID, uid).Scan(&charID, &spiritLevel) if err != nil { return errResp(5001, "artifact not found", traceID) } if spiritLevel == 0 { return errResp(5004, "artifact has no spirit", traceID) } // 激活法宝(进入战斗状态) return okResp(map[string]interface{}{ "success": true, "spirit_level": spiritLevel, "message": "法宝已激活", }, traceID) } func getArtifactList(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 getArtifactListReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } rows, err := hhdbPool.Query(ctx, ` SELECT a.id, a.character_id, a.item_id, a.artifact_type, a.grade, a.refinement_level, a.spirit_level, COALESCE(a.spirit_name,''), a.bound, a.stats, a.skills, a.durability, a.max_durability, COALESCE(a.set_id,'') FROM artifacts a JOIN characters c ON a.character_id = c.id WHERE c.player_id = $1 AND c.id = $2 ORDER BY a.grade DESC, a.refinement_level DESC `, uid, req.CharacterID) if err != nil { logger.Error("get artifact list failed: %v", err) return errResp(9002, "internal error", traceID) } defer rows.Close() var artifacts []artifactData for rows.Next() { var a artifactData if err := rows.Scan( &a.ID, &a.CharacterID, &a.ItemID, &a.ArtifactType, &a.Grade, &a.RefinementLevel, &a.SpiritLevel, &a.SpiritName, &a.Bound, &a.Stats, &a.Skills, &a.Durability, &a.MaxDurability, &a.SetID, ); err != nil { continue } artifacts = append(artifacts, a) } return okResp(map[string]interface{}{ "artifacts": artifacts, "count": len(artifacts), }, traceID) } func getSetBonus(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 getSetBonusReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 查询角色拥有的套装部件 rows, err := hhdbPool.Query(ctx, ` SELECT a.set_id, COUNT(*) as piece_count FROM artifacts a JOIN characters c ON a.character_id = c.id WHERE c.player_id = $1 AND c.id = $2 AND a.set_id != '' GROUP BY a.set_id `, uid, req.CharacterID) if err != nil { logger.Error("get set bonus failed: %v", err) return errResp(9002, "internal error", traceID) } defer rows.Close() var sets []setBonusData for rows.Next() { var s setBonusData var pieceCount int32 if err := rows.Scan(&s.SetID, &pieceCount); err != nil { continue } s.OwnedPieces = pieceCount // 查询套装详情 var setName string var bonus2, bonus4, bonus6 []byte err = hhdbPool.QueryRow(ctx, ` SELECT name, set_bonus_2, set_bonus_4, set_bonus_6 FROM artifact_sets WHERE id = $1 `, s.SetID).Scan(&setName, &bonus2, &bonus4, &bonus6) if err != nil { continue } s.SetName = setName json.Unmarshal(bonus2, &s.Bonus2) json.Unmarshal(bonus4, &s.Bonus4) json.Unmarshal(bonus6, &s.Bonus6) s.IsActive2 = pieceCount >= 2 s.IsActive4 = pieceCount >= 4 s.IsActive6 = pieceCount >= 6 sets = append(sets, s) } return okResp(map[string]interface{}{ "sets": sets, }, traceID) }