package modules import ( "context" "database/sql" "encoding/json" "github.com/heroiclabs/nakama-common/runtime" ) // RegisterSkill 注册技能相关 RPC。 func RegisterSkill(initializer runtime.Initializer) error { if err := initializer.RegisterRpc("SkillService/LearnSkill", learnSkill); err != nil { return err } if err := initializer.RegisterRpc("SkillService/CreateJadeSlip", createJadeSlip); err != nil { return err } return nil } type learnSkillReq struct { CharacterID string `json:"character_id"` SourceType string `json:"source_type"` SourceID string `json:"source_id"` CustomName string `json:"custom_name"` } type jadeSlipReq struct { CharacterID string `json:"character_id"` SkillInstanceID string `json:"skill_instance_id"` ConsumeProficiency bool `json:"consume_proficiency"` CopyCount int32 `json:"copy_count"` } type skillData struct { SkillInstanceID string `json:"skill_instance_id"` SkillID string `json:"skill_id"` Name string `json:"name"` SourceTag string `json:"source_tag"` CanCopyToJadeSlip bool `json:"can_copy_to_jade_slip"` Proficiency int32 `json:"proficiency"` InstanceData interface{} `json:"instance_data"` } type jadeSlipItemData struct { InventoryID string `json:"inventory_id"` ItemID string `json:"item_id"` Name string `json:"name"` Quantity int32 `json:"quantity"` InstanceData interface{} `json:"instance_data"` ProficiencyConsumed int32 `json:"proficiency_consumed"` } func learnSkill(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) { traceID := newTraceID() var req learnSkillReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(5009, "invalid payload", traceID) } // 校验参数 if req.SkillID == "" { return errResp(5010, "skill_id required", traceID) } // 查询技能信息 var skillName, category, domain, element, alignment, damageType, scalingAttr string var baseTemplateJSON string err := db.QueryRowContext(ctx, ` SELECT name, category, domain, element, alignment, damage_type, scaling_attr, base_template FROM skills WHERE id = $1 `, req.SkillID).Scan(&skillName, &category, &domain, &element, &alignment, &damageType, &scalingAttr, &baseTemplateJSON) if err != nil { return errResp(5011, "skill not found", traceID) } // 创建技能实例 var skillInstanceID string err = db.QueryRowContext(ctx, ` INSERT INTO character_skills ( character_id, skill_id, custom_name, instance_data, source_tag, proficiency, is_unique, lineage ) VALUES ($1, $2, $3, $4, $5, 0, false, '{}') RETURNING id::text `, req.CharacterID, req.SkillID, firstNonEmpty(req.CustomName, skillName), baseTemplateJSON, req.SourceType).Scan(&skillInstanceID) if err != nil { logger.Error("learn skill failed: %v", err) return errResp(9002, "internal error", traceID) } logger.Info("SkillService/LearnSkill success: skill_instance_id=%s skill_id=%s", skillInstanceID, req.SkillID) return okResp(skillData{ SkillInstanceID: skillInstanceID, SkillID: req.SkillID, Name: firstNonEmpty(req.CustomName, skillName), Category: category, Domain: domain, Element: element, DamageType: damageType, SourceTag: req.SourceType, }, traceID) } func createJadeSlip(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) { traceID := newTraceID() var req jadeSlipReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(5014, "invalid payload", traceID) } // 查询技能信息 var skillID, skillName, flow string var proficiency int32 err := db.QueryRowContext(ctx, ` SELECT cs.skill_id, s.name, s.flow, cs.proficiency FROM character_skills cs JOIN skills s ON cs.skill_id = s.id WHERE cs.id = $1 AND cs.character_id = $2 `, req.SkillInstanceID, req.CharacterID).Scan(&skillID, &skillName, &flow, &proficiency) if err != nil { return errResp(5015, "skill not found", traceID) } // 检查是否可拓印 if flow == "unique" || flow == "not Tradable" { return errResp(5016, "skill cannot be copied", traceID) } // 检查熟练度 if proficiency < 240 { return errResp(5017, "insufficient proficiency", traceID) } // 创建玉简物品 var inventoryID string err = db.QueryRowContext(ctx, ` INSERT INTO inventories (character_id, item_id, slot_type, quantity, instance_data, can_trade) VALUES ($1, 'jade_slip', 'bag', 1, $2, true) RETURNING id::text `, req.CharacterID, fmt.Sprintf(`{"skill_id":"%s","skill_name":"%s","source_tag":"JADE_SLIP"}`, skillID, skillName)).Scan(&inventoryID) if err != nil { logger.Error("create jade slip failed: %v", err) return errResp(9002, "internal error", traceID) } // 消耗熟练度 _, err = db.ExecContext(ctx, ` UPDATE character_skills SET proficiency = proficiency - 240, updated_at = NOW() WHERE id = $1 `, req.SkillInstanceID) if err != nil { logger.Error("consume proficiency failed: %v", err) } logger.Info("SkillService/CreateJadeSlip success: inventory_id=%s skill=%s", inventoryID, skillName) return okResp(jadeSlipItemData{ InventoryID: inventoryID, ItemID: "jade_slip", Name: skillName + "玉简", Quantity: 1, ProficiencyConsumed: 240, }, traceID) }