lawless/server/modules/rebirth.go

323 行
9.1 KiB
Go

// Package modules - 种族转生/转化系统模块
// 对齐GDD-11 种族转生与转化系统
package modules
import (
"context"
"database/sql"
"encoding/json"
"math/rand"
"time"
"github.com/heroiclabs/nakama-common/runtime"
"github.com/jackc/pgx/v5"
)
// RegisterRebirth 注册转生相关 RPC。
func RegisterRebirth(initializer runtime.Initializer) error {
rpcs := map[string]func(runtime.Initializer) 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)
}