一些检测仍在等待运行
Docs Build / build-and-deploy (push) Waiting to run
- 移除 ConfigManager 配置管理器类 - 移除 GameManager 全局单例管理器类 - 移除 NetworkManager 网络连接管理器类 - 移除 CharacterData 和 ItemData 数据模型类 - 移除 BagScene、BattleScene、LobbyScene 等场景脚本 - 移除 EncounterBubble 和 EventFeedPanel UI组件脚本 - 更新代理邀请文档中的服务器连接方式 - 更新同步状态表格中的代理任务分配信息 - 添加 MiMo 任务完成总结和审查修复记录
428 行
12 KiB
Go
428 行
12 KiB
Go
// 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(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error){
|
||
"TribulationService/PrepareTribulation": prepareTribulation,
|
||
"TribulationService/StartTribulation": tribulationStart,
|
||
"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 tribulationStart(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.NotificationSend{
|
||
{
|
||
Code: 2001,
|
||
Content: map[string]interface{}{"type": "tribulation_helper_invite", "inviter_id": req.CharacterID},
|
||
Persistent: true,
|
||
Subject: "渡劫护法邀请",
|
||
UserID: req.HelperID,
|
||
},
|
||
})
|
||
if err != nil {
|
||
logger.Error("send notification failed: %v", err)
|
||
}
|
||
|
||
return okResp(map[string]interface{}{
|
||
"success": true,
|
||
"helper_name": helperName,
|
||
"message": "护法邀请已发送",
|
||
}, traceID)
|
||
}
|