// Package modules - 炼器系统模块 // 对齐GDD-05 4.7 炼器流程详解 + GDD-27 九 天材地宝系统 package modules import ( "context" "database/sql" "encoding/json" "math/rand" "time" "github.com/heroiclabs/nakama-common/runtime" "github.com/jackc/pgx/v5" ) // RegisterForging 注册炼器相关 RPC。 func RegisterForging(initializer runtime.Initializer) error { rpcs := map[string]func(runtime.Initializer) error{ "ForgingService/CraftEquipment": craftEquipment, "ForgingService/GetBlueprints": getBlueprints, "ForgingService/RepairEquipment": repairEquipment, } for path, fn := range rpcs { if err := initializer.RegisterRpc(path, fn); err != nil { return err } } return nil } // --- 请求/响应结构 --- type craftEquipmentReq struct { BlueprintID string `json:"blueprint_id"` // 图纸ID Materials []string `json:"materials"` // 材料ID列表 Technique string `json:"technique"` // 刚猛/柔韧/均衡/精密/狂暴 } type getBlueprintsReq struct { CharacterID string `json:"character_id"` Grade string `json:"grade"` // 可选:筛选品阶 } type repairEquipmentReq struct { EquipmentID string `json:"equipment_id"` Materials []string `json:"materials"` } type blueprintData struct { ID string `json:"id"` Name string `json:"name"` Grade string `json:"grade"` // mortal/yellow/xuan/di/celestial/immortal EquipType string `json:"equip_type"` // weapon/armor/accessory RequiredMaterials interface{} `json:"required_materials"` RequiredLevel int32 `json:"required_level"` SuccessRate float64 `json:"success_rate"` BaseStats interface{} `json:"base_stats"` TechniqueBonus map[string]map[string]float64 `json:"technique_bonus"` } type craftedEquipData struct { BlueprintID string `json:"blueprint_id"` BlueprintName string `json:"blueprint_name"` Grade string `json:"grade"` EquipType string `json:"equip_type"` Success bool `json:"success"` Quality string `json:"quality"` // normal/fine/excellent/perfect Stats interface{} `json:"stats"` Skills interface{} `json:"skills"` SetID string `json:"set_id"` ExpGain int32 `json:"exp_gain"` } // --- 图纸配置 --- var defaultBlueprints = map[string]blueprintData{ "bp_iron_sword": { ID: "bp_iron_sword", Name: "铁剑图纸", Grade: "mortal", EquipType: "weapon", RequiredMaterials: []string{"mineral_xuantie"}, RequiredLevel: 1, SuccessRate: 0.95, BaseStats: map[string]interface{}{"atk": 10, "def": 0}, TechniqueBonus: map[string]map[string]float64{ "strong": {"atk": 1.15, "def": 0.95}, "flexible": {"atk": 0.95, "def": 1.1}, "balanced": {"atk": 1.0, "def": 1.0}, "precise": {"atk": 1.05, "def": 1.05}, "frenzy": {"atk": 1.2, "def": 0.9, "crit": 0.1}, }, }, "bp_steel_armor": { ID: "bp_steel_armor", Name: "玄铁甲图纸", Grade: "yellow", EquipType: "armor", RequiredMaterials: []string{"mineral_xuantie", "mineral_hantie"}, RequiredLevel: 2, SuccessRate: 0.85, BaseStats: map[string]interface{}{"atk": 0, "def": 25, "hp": 100}, TechniqueBonus: map[string]map[string]float64{ "strong": {"atk": 1.0, "def": 1.1}, "flexible": {"atk": 0.95, "def": 1.15}, "balanced": {"atk": 1.0, "def": 1.1}, "precise": {"atk": 1.0, "def": 1.12}, "frenzy": {"atk": 1.05, "def": 1.0}, }, }, "bp_spirit_blade": { ID: "bp_spirit_blade", Name: "灵器刀图纸", Grade: "xuan", EquipType: "weapon", RequiredMaterials: []string{"mineral_miinyin", "mineral_jingjin"}, RequiredLevel: 3, SuccessRate: 0.75, BaseStats: map[string]interface{}{"atk": 45, "def": 5, "crit": 0.05}, TechniqueBonus: map[string]map[string]float64{ "strong": {"atk": 1.2, "def": 0.9}, "flexible": {"atk": 1.0, "def": 1.1}, "balanced": {"atk": 1.1, "def": 1.05}, "precise": {"atk": 1.15, "def": 1.0, "crit": 0.05}, "frenzy": {"atk": 1.25, "def": 0.85, "crit": 0.15}, }, }, "bp_dragon_scale": { ID: "bp_dragon_scale", Name: "龙鳞甲图纸", Grade: "di", EquipType: "armor", RequiredMaterials: []string{"mineral_longlinshi", "mineral_jingjin"}, RequiredLevel: 4, SuccessRate: 0.65, BaseStats: map[string]interface{}{"atk": 10, "def": 80, "hp": 500, "element_resist": 0.15}, TechniqueBonus: map[string]map[string]float64{ "strong": {"atk": 1.1, "def": 1.1}, "flexible": {"atk": 0.95, "def": 1.2}, "balanced": {"atk": 1.05, "def": 1.15}, "precise": {"atk": 1.0, "def": 1.18}, "frenzy": {"atk": 1.15, "def": 1.0}, }, }, "bp_celestial_sword": { ID: "bp_celestial_sword", Name: "天品神剑图纸", Grade: "celestial", EquipType: "weapon", RequiredMaterials: []string{"mineral_tianwaiyuntie", "mineral_jingjin", "herb_tianlingye"}, RequiredLevel: 5, SuccessRate: 0.50, BaseStats: map[string]interface{}{"atk": 120, "def": 15, "crit": 0.1, "element_dmg": 0.2}, TechniqueBonus: map[string]map[string]float64{ "strong": {"atk": 1.25, "def": 0.85}, "flexible": {"atk": 1.0, "def": 1.15}, "balanced": {"atk": 1.15, "def": 1.05}, "precise": {"atk": 1.2, "def": 1.0, "crit": 0.1}, "frenzy": {"atk": 1.3, "def": 0.8, "crit": 0.2}, }, }, "bp_immortal_artifact": { ID: "bp_immortal_artifact", Name: "仙器图纸", Grade: "immortal", EquipType: "weapon", RequiredMaterials: []string{"mineral_hundunqingjin", "mineral_hundunzhishi", "herb_hundunlingzhi"}, RequiredLevel: 6, SuccessRate: 0.35, BaseStats: map[string]interface{}{"atk": 250, "def": 30, "crit": 0.15, "element_dmg": 0.35, "special": "chaos_damage"}, TechniqueBonus: map[string]map[string]float64{ "strong": {"atk": 1.3, "def": 0.8}, "flexible": {"atk": 1.0, "def": 1.2}, "balanced": {"atk": 1.15, "def": 1.1}, "precise": {"atk": 1.2, "def": 1.05, "crit": 0.1}, "frenzy": {"atk": 1.35, "def": 0.75, "crit": 0.25}, }, }, } // --- RPC 实现 --- func craftEquipment(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 craftEquipmentReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 获取图纸 bp, ok := defaultBlueprints[req.BlueprintID] if !ok { return errResp(6101, "blueprint not found", traceID) } // 获取角色信息 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[bp.Grade] { return errResp(6102, "realm too low for this blueprint", traceID) } // 计算成功率 successRate := bp.SuccessRate // 手法加成 if techniqueBonus, ok := bp.TechniqueBonus[req.Technique]; ok { if atkBonus, ok := techniqueBonus["atk"]; ok && atkBonus > 1.0 { successRate *= 0.95 // 高攻击手法成功率略降 } } // 限制范围 if successRate < 0.1 { successRate = 0.1 } if successRate > 0.99 { successRate = 0.99 } // 判定成功/失败 success := rand.Float64() < successRate var result craftedEquipData result.BlueprintID = req.BlueprintID result.BlueprintName = bp.Name result.Grade = bp.Grade result.EquipType = bp.EquipType result.Success = success if success { // 成功:产出装备 quality := "normal" if rand.Float64() < 0.15 { quality = "fine" } if rand.Float64() < 0.03 { quality = "excellent" } if rand.Float64() < 0.003 { quality = "perfect" } result.Quality = quality result.Stats = bp.BaseStats result.ExpGain = 15 + int32(realmTier*8) // 根据品质加成属性 qualityMulti := map[string]float64{ "normal": 1.0, "fine": 1.1, "excellent": 1.2, "perfect": 1.3, } multi := qualityMulti[quality] // 应用手法加成 if techniqueBonus, ok := bp.TechniqueBonus[req.Technique]; ok { result.Stats = applyTechniqueBonus(result.Stats, techniqueBonus, multi) } // 更新角色经验 _, err = hhdbPool.Exec(ctx, ` UPDATE characters SET exp = exp + $1, updated_at = NOW() WHERE id = $2 `, result.ExpGain, charID) if err != nil { logger.Error("craft equip update failed: %v", err) return errResp(9002, "internal error", traceID) } } else { // 失败:消耗材料,产出废品 result.Quality = "normal" result.Stats = map[string]interface{}{"atk": 0, "def": 0} result.ExpGain = 5 _, err = hhdbPool.Exec(ctx, ` UPDATE characters SET exp = exp + $1, updated_at = NOW() WHERE id = $2 `, result.ExpGain, charID) if err != nil { logger.Error("craft equip fail update failed: %v", err) return errResp(9002, "internal error", traceID) } } return okResp(result, traceID) } func getBlueprints(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 getBlueprintsReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 获取角色境界 var realmTier int32 err := hhdbPool.QueryRow(ctx, ` SELECT realm_tier FROM characters WHERE player_id = $1 AND status = 'active' ORDER BY created_at DESC LIMIT 1 `, uid).Scan(&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, } var blueprints []blueprintData for _, bp := range defaultBlueprints { if realmTier >= gradeRealmReq[bp.Grade] { if req.Grade == "" || req.Grade == bp.Grade { blueprints = append(blueprints, bp) } } } return okResp(map[string]interface{}{ "blueprints": blueprints, "count": len(blueprints), }, traceID) } func repairEquipment(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 repairEquipmentReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 验证装备归属 var charID string var durability, maxDurability int32 err := hhdbPool.QueryRow(ctx, ` SELECT a.character_id, a.durability, a.max_durability FROM artifacts a JOIN characters c ON a.character_id = c.id WHERE a.id = $1 AND c.player_id = $2 `, req.EquipmentID, uid).Scan(&charID, &durability, &maxDurability) if err != nil { return errResp(6103, "equipment not found", traceID) } if durability >= maxDurability { return errResp(6104, "equipment already at max durability", traceID) } // 修复:恢复耐久 newDurability := durability + int32(len(req.Materials)*10) if newDurability > maxDurability { newDurability = maxDurability } _, err = hhdbPool.Exec(ctx, ` UPDATE artifacts SET durability = $1, updated_at = NOW() WHERE id = $2 `, newDurability, req.EquipmentID) if err != nil { logger.Error("repair equipment failed: %v", err) return errResp(9002, "internal error", traceID) } return okResp(map[string]interface{}{ "success": true, "repaired_durability": newDurability, "max_durability": maxDurability, "message": "装备修复成功", }, traceID) } // --- 辅助函数 --- func applyTechniqueBonus(stats interface{}, techniqueBonus map[string]float64, qualityMulti float64) map[string]interface{} { statsMap, ok := stats.(map[string]interface{}) if !ok { return map[string]interface{}{} } result := make(map[string]interface{}) for k, v := range statsMap { if floatVal, ok := v.(float64); ok { bonus := 1.0 if k == "atk" { bonus = techniqueBonus["atk"] } else if k == "def" { bonus = techniqueBonus["def"] } else if k == "crit" { if critBonus, ok := techniqueBonus["crit"]; ok { bonus = 1.0 + critBonus } } result[k] = floatVal * bonus * qualityMulti } else { result[k] = v } } return result }