323 行
9.1 KiB
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)
|
||
}
|