lawless/server/modules/forging.go

397 行
12 KiB
Go

// Package modules - 炼器系统模块
// 对齐GDD-05 4.7 炼器流程详解 + GDD-27 九 天材地宝系统
package modules
import (
"context"
"database/sql"
"encoding/json"
"math/rand"
"github.com/heroiclabs/nakama-common/runtime"
)
// RegisterForging 注册炼器相关 RPC。
func RegisterForging(initializer runtime.Initializer) error {
rpcs := map[string]func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, 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
}