// Package modules - 种族转生/转化系统模块 // 对齐GDD-11 种族转生与转化系统 package modules import ( "context" "database/sql" "encoding/json" "math/rand" "time" "github.com/heroiclabs/nakama-common/runtime" ) // RegisterRebirth 注册转生相关 RPC。 func RegisterRebirth(initializer runtime.Initializer) error { rpcs := map[string]func(context.Context, runtime.Logger, *sql.DB, runtime.NakamaModule, string) (string, error){ "RebirthService/StartRebirth": startRebirth, "RebirthService/GetRebirthInfo": getRebirthInfo, "RebirthService/CancelRebirth": cancelRebirth, "RebirthService/GetRebirthHistory": getRebirthHistory, } for path, fn := range rpcs { if err := initializer.RegisterRpc(path, fn); err != nil { return err } } return nil } type startRebirthReq struct { TargetRaceID string `json:"target_race_id"` // 目标种族ID } type getRebirthInfoReq struct { CharacterID string `json:"character_id"` } type cancelRebirthReq struct { CharacterID string `json:"character_id"` } type getRebirthHistoryReq struct { CharacterID string `json:"character_id"` } type rebirthInfoData struct { CharacterID string `json:"character_id"` CurrentRace string `json:"current_race"` TargetRace string `json:"target_race"` Progress int32 `json:"progress"` // 0-100 Status string `json:"status"` // inactive/active/completed/cancelled RebirthCount int32 `json:"rebirth_count"` MarkData interface{} `json:"mark_data"` // 转生印记数据 ResidualTalents interface{} `json:"residual_talents"` // 残响天赋 StartTime *time.Time `json:"start_time"` EndTime *time.Time `json:"end_time"` SanDecay float64 `json:"san_decay"` // SAN缓降速度 StatPenalty interface{} `json:"stat_penalty"` // 属性惩罚 } // 转生概率配置(按层级) var rebirthProbConfig = map[string]map[int32]struct { probability float64 costMulti float64 }{ // 他族→人族 "to_human": { 1: {0.001, 0.1}, // 层级0:极低概率,禁止触发 2: {0.003, 1.0}, // 层级2:正常触发起点 3: {0.0015, 0.8}, // 层级3:概率下降 4: {0.0005, 0.5}, // 层级4:高境界低概率 5: {0.0001, 0.3}, // 层级5:极高境界极低概率 }, // 人族→他族 "from_human": { 1: {0.001, 0.5}, // 层级1:低概率低成本 2: {0.005, 1.0}, // 层级2:概率上升 3: {0.015, 1.5}, // 层级3:高概率高消耗 4: {0.03, 2.0}, // 层级4:接近显著概率 5: {0.05, 3.0}, // 层级5:最高概率但代价巨大 }, } // 转生印记配置 var rebirthMarkConfig = map[string]struct { MarkName string MarkDesc string StatBonus map[string]float64 }{ "to_human": { MarkName: "人道残响印", MarkDesc: "角色周身偶现原种族虚影残片,战斗时概率触发前世一瞬", StatBonus: map[string]float64{ "cultivation_speed": 0.25, "breakthrough_rate": 0.15, "learning_speed": 0.10, }, }, "from_human": { MarkName: "种族归源印", MarkDesc: "根据目标种族定制视觉表现", StatBonus: map[string]float64{ "core_stat": 0.15, }, }, } func startRebirth(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 startRebirthReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 获取角色信息 var charID string var raceID string var realmTier int32 var rebirthCount int32 err := hhdbPool.QueryRow(ctx, ` SELECT c.id, c.race_id, c.realm_tier, COALESCE(cr.rebirth_count, 0) FROM characters c LEFT JOIN character_race_states cr ON c.id = cr.character_id WHERE c.player_id = $1 AND c.status = 'active' ORDER BY c.created_at DESC LIMIT 1 `, uid).Scan(&charID, &raceID, &realmTier, &rebirthCount) if err != nil { return errResp(4002, "character not found", traceID) } // 确定转生方向 rebirthDirection := "from_human" if raceID != "human" && req.TargetRaceID == "human" { rebirthDirection = "to_human" } // 获取转生概率配置 config, ok := rebirthProbConfig[rebirthDirection] if !ok { return errResp(8001, "invalid rebirth direction", traceID) } probConfig, ok := config[realmTier] if !ok { probConfig = config[2] // 默认层级2 } // 检查是否已在转生中 var activeCount int32 err = hhdbPool.QueryRow(ctx, ` SELECT COUNT(*) FROM character_race_states WHERE character_id = $1 AND conversion_cooldown_until > NOW() `, charID).Scan(&activeCount) if err == nil && activeCount > 0 { return errResp(8002, "already in rebirth process", traceID) } // 判定是否触发转生 if rand.Float64() > probConfig.probability { return okResp(map[string]interface{}{ "triggered": false, "probability": probConfig.probability, "message": "未触发转生机遇", }, traceID) } // 触发转生,开始进度期 markConfig := rebirthMarkConfig[rebirthDirection] _, err = hhdbPool.Exec(ctx, ` UPDATE character_race_states SET main_race_id = $1, rebirth_count = rebirth_count + 1, conversion_cooldown_until = NOW() + INTERVAL '3 days', updated_at = NOW() WHERE character_id = $2 `, req.TargetRaceID, charID) if err != nil { logger.Error("start rebirth update failed: %v", err) return errResp(9002, "internal error", traceID) } // 设置转生进度期状态 _, err = hhdbPool.Exec(ctx, ` UPDATE characters SET realm_tier = GREATEST(realm_tier - 1, 1), minor_realm = 3, updated_at = NOW() WHERE id = $1 `, charID) if err != nil { logger.Error("rebirth realm rollback failed: %v", err) } return okResp(map[string]interface{}{ "triggered": true, "rebirth_id": genUUID(), "target_race": req.TargetRaceID, "mark_name": markConfig.MarkName, "mark_desc": markConfig.MarkDesc, "stat_bonus": markConfig.StatBonus, "duration_hours": 72, // 3天现实时间 "san_decay": 1.5, "stat_penalty": map[string]interface{}{ "expedition_efficiency": -0.30, "combat_stats": -0.15, "inner_power": -0.20, }, "message": "转生已启动,进入进度期", }, traceID) } func getRebirthInfo(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 getRebirthInfoReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 查询角色转生信息 var raceID string var rebirthCount int32 var cooldownUntil *time.Time err := hhdbPool.QueryRow(ctx, ` SELECT c.race_id, COALESCE(cr.rebirth_count, 0), cr.conversion_cooldown_until FROM characters c LEFT JOIN character_race_states cr ON c.id = cr.character_id WHERE c.id = $1 `, req.CharacterID).Scan(&raceID, &rebirthCount, &cooldownUntil) if err != nil { return errResp(4002, "character not found", traceID) } status := "inactive" if cooldownUntil != nil && cooldownUntil.After(time.Now()) { status = "active" } return okResp(rebirthInfoData{ CharacterID: req.CharacterID, CurrentRace: raceID, RebirthCount: rebirthCount, Status: status, SanDecay: 1.5, }, traceID) } func cancelRebirth(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 cancelRebirthReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 取消转生进度 _, err := hhdbPool.Exec(ctx, ` UPDATE character_race_states SET conversion_cooldown_until = NULL, updated_at = NOW() WHERE character_id = $1 `, req.CharacterID) if err != nil { logger.Error("cancel rebirth failed: %v", err) return errResp(9002, "internal error", traceID) } return okResp(map[string]interface{}{ "success": true, "message": "转生已取消", }, traceID) } func getRebirthHistory(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 getRebirthHistoryReq if err := json.Unmarshal([]byte(payload), &req); err != nil { return errResp(2001, "invalid payload", traceID) } // 查询转生历史 var birthRace string var mainRace string var rebirthCount int32 err := hhdbPool.QueryRow(ctx, ` SELECT c.birth_race_id, c.race_id, COALESCE(cr.rebirth_count, 0) FROM characters c LEFT JOIN character_race_states cr ON c.id = cr.character_id WHERE c.id = $1 `, req.CharacterID).Scan(&birthRace, &mainRace, &rebirthCount) if err != nil { return errResp(4002, "character not found", traceID) } return okResp(map[string]interface{}{ "character_id": req.CharacterID, "birth_race": birthRace, "current_race": mainRace, "rebirth_count": rebirthCount, }, traceID) }