lawless/server/modules/tribulation.go

427 行
12 KiB
Go

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

// Package modules - 渡劫系统模块
// 对齐GDD-12 渡劫破镜与境界掉落系统 + T005审阅报告
package modules
import (
"context"
"database/sql"
"encoding/json"
"math/rand"
"time"
"github.com/heroiclabs/nakama-common/runtime"
"github.com/jackc/pgx/v5"
)
// RegisterTribulation 注册渡劫相关 RPC。
func RegisterTribulation(initializer runtime.Initializer) error {
rpcs := map[string]func(runtime.Initializer) error{
"TribulationService/PrepareTribulation": prepareTribulation,
"TribulationService/StartTribulation": startTribulation,
"TribulationService/GetTribulationInfo": getTribulationInfo,
"TribulationService/InviteHelper": inviteHelper,
}
for path, fn := range rpcs {
if err := initializer.RegisterRpc(path, fn); err != nil {
return err
}
}
return nil
}
// --- 请求/响应结构 ---
type prepareTribulationReq struct {
CharacterID string `json:"character_id"`
TargetRealm int32 `json:"target_realm"` // 目标境界tier
TargetMinor int32 `json:"target_minor"` // 目标小境界
Pills []string `json:"pills"` // 使用的丹药ID
Artifacts []string `json:"artifacts"` // 使用的法宝ID
Helpers []string `json:"helpers"` // 护法玩家ID
}
type startTribulationReq struct {
CharacterID string `json:"character_id"`
SessionType string `json:"session_type"` // manual/auto
}
type getTribulationInfoReq struct {
CharacterID string `json:"character_id"`
}
type inviteHelperReq struct {
CharacterID string `json:"character_id"`
HelperID string `json:"helper_id"` // 被邀请玩家ID
}
type tribulationInfoData struct {
CharacterID string `json:"character_id"`
CurrentRealm int32 `json:"current_realm"`
CurrentMinor int32 `json:"current_minor"`
TargetRealm int32 `json:"target_realm"`
TargetMinor int32 `json:"target_minor"`
BaseSuccessRate float64 `json:"base_success_rate"`
ModifiedRate float64 `json:"modified_rate"`
TribulationType string `json:"tribulation_type"` // thunder/fire/heart_devil/human/earth/life/chaos
Helpers []string `json:"helpers"`
Pills []string `json:"pills"`
Artifacts []string `json:"artifacts"`
IsMajorBreakthrough bool `json:"is_major_breakthrough"`
PreparationStatus string `json:"preparation_status"`
}
// --- 渡劫类型判定 ---
func determineTribulationType(realmTier int32, karmaValue int32, sinValue int32, sanValue int32) string {
// 大境界突破3的倍数触发三劫连环
if realmTier%3 == 0 && realmTier > 1 {
return "triple" // 雷罚→业火→心魔
}
// 高罪孽触发天罚天劫
if sinValue >= 500 {
return "heavenly_punishment"
}
// 低SAN触发旧日注视克苏鲁系
if sanValue < 30 {
return "old_gaze"
}
// 默认雷罚天劫
return "thunder"
}
func calculateSuccessRate(baseRate float64, pills []string, artifacts []string, karmaValue int32, sinValue int32, dantoxLevel int32, purity float64) float64 {
modified := baseRate
// 丹药加成
modified += float64(len(pills)) * 0.05
// 法宝加成
modified += float64(len(artifacts)) * 0.03
// 功德加成
if karmaValue >= 1000 {
modified += 0.10
} else if karmaValue >= 500 {
modified += 0.05
}
// 罪孽惩罚
if sinValue >= 2000 {
modified -= 0.30
} else if sinValue >= 500 {
modified -= 0.15
}
// 丹毒惩罚
if dantoxLevel > 100 {
modified -= 0.15
} else if dantoxLevel > 60 {
modified -= 0.08
}
// 纯净度加成
if purity >= 0.9 {
modified += 0.15
} else if purity >= 0.7 {
modified += 0.08
} else if purity < 0.3 {
modified -= 0.15
}
// 限制范围
if modified < 0.05 {
modified = 0.05
}
if modified > 0.95 {
modified = 0.95
}
return modified
}
// --- RPC 实现 ---
func prepareTribulation(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 prepareTribulationReq
if err := json.Unmarshal([]byte(payload), &req); err != nil {
return errResp(2001, "invalid payload", traceID)
}
// 查询角色信息
var charInfo struct {
ID string
RealmTier int32
MinorRealm int32
KarmaValue int32
SinValue int32
DantoxLevel int32
Purity float64
SanValue int32
}
err := hhdbPool.QueryRow(ctx, `
SELECT id, realm_tier, minor_realm, karma_value, crime_score, dantox_level, energy_purity, san_current
FROM characters WHERE id = $1 AND player_id = $2
`, req.CharacterID, uid).Scan(
&charInfo.ID, &charInfo.RealmTier, &charInfo.MinorRealm,
&charInfo.KarmaValue, &charInfo.SinValue, &charInfo.DantoxLevel,
&charInfo.Purity, &charInfo.SanValue,
)
if err != nil {
return errResp(4002, "character not found", traceID)
}
// 检查是否可渡劫
if charInfo.RealmTier >= 9 {
return errResp(5010, "already at max realm", traceID)
}
// 确定渡劫类型
tribType := determineTribulationType(charInfo.RealmTier, charInfo.KarmaValue, charInfo.SinValue, charInfo.SanValue)
// 计算成功率
var realmInfo struct {
BaseSuccessRate float64
}
err = hhdbPool.QueryRow(ctx, `
SELECT base_success_rate FROM realms WHERE tier = $1
`, charInfo.RealmTier).Scan(&realmInfo.BaseSuccessRate)
if err != nil {
realmInfo.BaseSuccessRate = 0.50
}
modifiedRate := calculateSuccessRate(
realmInfo.BaseSuccessRate,
req.Pills, req.Artifacts,
charInfo.KarmaValue, charInfo.SinValue,
charInfo.DantoxLevel, charInfo.Purity,
)
isMajor := charInfo.RealmTier%3 == 0
return okResp(tribulationInfoData{
CharacterID: charInfo.ID,
CurrentRealm: charInfo.RealmTier,
CurrentMinor: charInfo.MinorRealm,
TargetRealm: req.TargetRealm,
TargetMinor: req.TargetMinor,
BaseSuccessRate: realmInfo.BaseSuccessRate,
ModifiedRate: modifiedRate,
TribulationType: tribType,
Helpers: req.Helpers,
Pills: req.Pills,
Artifacts: req.Artifacts,
IsMajorBreakthrough: isMajor,
PreparationStatus: "ready",
}, traceID)
}
func startTribulation(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 startTribulationReq
if err := json.Unmarshal([]byte(payload), &req); err != nil {
return errResp(2001, "invalid payload", traceID)
}
// 查询角色信息
var charID string
var realmTier, minorRealm int32
var karmaValue, sinValue, dantoxLevel, sanCurrent int32
var purity float64
err := hhdbPool.QueryRow(ctx, `
SELECT id, realm_tier, minor_realm, karma_value, crime_score, dantox_level, energy_purity, san_current
FROM characters WHERE id = $1 AND player_id = $2
`, req.CharacterID, uid).Scan(
&charID, &realmTier, &minorRealm,
&karmaValue, &sinValue, &dantoxLevel,
&purity, &sanCurrent,
)
if err != nil {
return errResp(4002, "character not found", traceID)
}
// 确定渡劫类型
tribType := determineTribulationType(realmTier, karmaValue, sinValue, sanCurrent)
// 获取基础成功率
var baseRate float64
err = hhdbPool.QueryRow(ctx, `SELECT base_success_rate FROM realms WHERE tier = $1`, realmTier).Scan(&baseRate)
if err != nil {
baseRate = 0.50
}
modifiedRate := calculateSuccessRate(baseRate, nil, nil, karmaValue, sinValue, dantoxLevel, purity)
// 判定渡劫结果
success := rand.Float64() < modifiedRate
var result string
var penalties map[string]interface{}
if success {
result = "success"
// 成功:境界提升
newMinor := minorRealm + 1
newRealm := realmTier
if newMinor > 3 {
newMinor = 1
newRealm = realmTier + 1
}
_, err = hhdbPool.Exec(ctx, `
UPDATE characters SET realm_tier = $1, minor_realm = $2, updated_at = NOW()
WHERE id = $3
`, newRealm, newMinor, charID)
if err != nil {
logger.Error("tribulation success update failed: %v", err)
return errResp(9002, "internal error", traceID)
}
penalties = map[string]interface{}{
"realm_tier": newRealm,
"minor_realm": newMinor,
}
} else {
result = "fail"
// 失败:境界掉落
newMinor := minorRealm - 1
newRealm := realmTier
if newMinor < 1 {
newMinor = 3
newRealm = realmTier - 1
if newRealm < 1 {
newRealm = 1
newMinor = 1
}
}
_, err = hhdbPool.Exec(ctx, `
UPDATE characters SET realm_tier = $1, minor_realm = $2, updated_at = NOW()
WHERE id = $3
`, newRealm, newMinor, charID)
if err != nil {
logger.Error("tribulation fail update failed: %v", err)
return errResp(9002, "internal error", traceID)
}
penalties = map[string]interface{}{
"realm_tier": newRealm,
"minor_realm": newMinor,
"debuff": "天劫余威:全属性-10%,持续6-24游戏时",
}
}
// 记录渡劫记录
_, err = hhdbPool.Exec(ctx, `
INSERT INTO tribulation_records
(character_id, realm_tier, minor_realm, tribulation_type, base_success_rate, modified_success_rate, result, penalties, san_snapshot, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
`, charID, realmTier, minorRealm, tribType, baseRate, modifiedRate, result, penalties, sanCurrent)
if err != nil {
logger.Error("tribulation record insert failed: %v", err)
}
return okResp(map[string]interface{}{
"success": success,
"result": result,
"tribulation_type": tribType,
"base_rate": baseRate,
"modified_rate": modifiedRate,
"penalties": penalties,
}, traceID)
}
func getTribulationInfo(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 getTribulationInfoReq
if err := json.Unmarshal([]byte(payload), &req); err != nil {
return errResp(2001, "invalid payload", traceID)
}
// 查询最近的渡劫记录
var record struct {
TribulationType string
BaseRate float64
ModifiedRate float64
Result string
CreatedAt time.Time
}
err := hhdbPool.QueryRow(ctx, `
SELECT tribulation_type, base_success_rate, modified_success_rate, result, created_at
FROM tribulation_records
WHERE character_id = $1
ORDER BY created_at DESC LIMIT 1
`, req.CharacterID).Scan(
&record.TribulationType, &record.BaseRate,
&record.ModifiedRate, &record.Result, &record.CreatedAt,
)
if err != nil && err != pgx.ErrNoRows {
logger.Error("get tribulation info failed: %v", err)
return errResp(9002, "internal error", traceID)
}
return okResp(map[string]interface{}{
"character_id": req.CharacterID,
"last_tribulation": record,
}, traceID)
}
func inviteHelper(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 inviteHelperReq
if err := json.Unmarshal([]byte(payload), &req); err != nil {
return errResp(2001, "invalid payload", traceID)
}
// 验证被邀请者存在
var helperName string
err := hhdbPool.QueryRow(ctx, `
SELECT name FROM characters WHERE id = $1 AND status = 'active'
`, req.HelperID).Scan(&helperName)
if err != nil {
return errResp(5001, "helper not found", traceID)
}
// 发送邀请通知通过Nakama通知系统
_, err = nk.NotificationsSend(ctx, []runtime.NotificationRequest{
{
Code: 2001,
Content: map[string]interface{}{"type": "tribulation_helper_invite", "inviter_id": req.CharacterID},
Persistent: true,
Subject: "渡劫护法邀请",
},
}, []string{req.HelperID})
if err != nil {
logger.Error("send notification failed: %v", err)
}
return okResp(map[string]interface{}{
"success": true,
"helper_name": helperName,
"message": "护法邀请已发送",
}, traceID)
}