You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
7.5 KiB
Go

package grade
import (
"context"
"git.noahlan.cn/northlan/ntools-go/gorm-zero/gormx"
"github.com/pkg/errors"
"gorm.io/gorm"
"live-service/app/user_center/model"
"live-service/app/user_center/rpc/internal/config"
"live-service/app/user_center/rpc/pb"
)
// Manager 排位管理器
// 段/二级星/星等级(5) 二级段顶级后开始晋级赛1次获胜后升段。
// 战斗积分(累计),抵扣掉段,每个大段抵扣值不同。
// 获胜+星,失败-星
type (
Manager struct {
config *config.GameConfig
userGradeModel model.UserGradeModel
}
ReportReq struct {
UserId int64
Username string
Position int32
Score float32
}
ReportResp struct {
ReportReq
*pb.Grade
Result pb.StatPvPReportResp_GradeResult
Reason pb.StatPvPReportResp_GradeReason
}
)
func NewUserGradeManager(config *config.GameConfig, userGradeModel model.UserGradeModel) *Manager {
return &Manager{
config: config,
userGradeModel: userGradeModel,
}
}
// QueryUser 查询用户段位
func (m *Manager) QueryUser(ctx context.Context, userId int64) (*model.UserGrade, error) {
var resp *model.UserGrade
if err := m.userGradeModel.TransactCtx(ctx, nil, func(tx *gorm.DB) error {
dbModel, err := m.userGradeModel.FindOne(ctx, nil, userId)
if err != nil {
if errors.Is(err, gormx.ErrNotFound) {
dbModel = &model.UserGrade{
UserId: userId,
Grade: model.DefaultUserGrade,
Level: model.DefaultUserGradeLevel,
Star: model.DefaultUserGradeStar,
BravePoint: 0,
}
if err = m.userGradeModel.Insert(ctx, tx, dbModel); err != nil {
return err
}
resp = dbModel
}
return err
}
resp = dbModel
return nil
}); err != nil {
return nil, err
}
return resp, nil
}
// Report 战斗记录
func (m *Manager) Report(ctx context.Context, winItems, lostItems []ReportReq) ([]ReportResp, error) {
// 1. 查询现有段位信息
// 2. 计算段位结果
// 3. 更新并返回
userCountSatisfied := len(winItems)+len(lostItems) >= m.config.Grade.MinUserCount
resp := make([]ReportResp, 0, len(winItems)+len(lostItems))
if err := m.userGradeModel.TransactCtx(ctx, nil, func(tx *gorm.DB) error {
for _, item := range winItems {
if itemResp, err := m.reportItem(ctx, tx, item, true, userCountSatisfied); err != nil {
return err
} else {
resp = append(resp, *itemResp)
}
}
for _, item := range lostItems {
if itemResp, err := m.reportItem(ctx, tx, item, false, userCountSatisfied); err != nil {
return err
} else {
resp = append(resp, *itemResp)
}
}
return nil
}); err != nil {
return nil, err
}
return resp, nil
}
func (m *Manager) reportItem(ctx context.Context, tx *gorm.DB, item ReportReq, win bool, userCountSatisfied bool) (*ReportResp, error) {
var resp *ReportResp
dbModel, err := m.userGradeModel.FindOne(ctx, tx, item.UserId)
if err != nil {
if !errors.Is(err, gormx.ErrNotFound) {
return nil, err
}
}
if dbModel == nil {
// insert
dbModel = &model.UserGrade{
UserId: item.UserId,
}
grade, result, reason := m.calcRank(win, userCountSatisfied,
model.DefaultUserGrade,
model.DefaultUserGradeLevel,
model.DefaultUserGradeStar,
0,
item.Score,
item.Position)
dbModel.Grade = int64(grade.Grade)
dbModel.Level = int64(grade.Level)
dbModel.Star = int64(grade.Star)
dbModel.BravePoint = grade.BravePoint
if err = m.userGradeModel.Insert(ctx, tx, dbModel); err != nil {
return nil, err
}
resp = &ReportResp{
ReportReq: ReportReq{
UserId: dbModel.UserId,
Username: item.Username,
Position: item.Position,
Score: item.Score,
},
Grade: &grade,
Result: result,
Reason: reason,
}
return resp, nil
}
grade, result, reason := m.calcRank(win, userCountSatisfied,
dbModel.Grade,
dbModel.Level,
dbModel.Star,
dbModel.BravePoint,
item.Score,
item.Position)
dbModel.Grade = int64(grade.Grade)
dbModel.Level = int64(grade.Level)
dbModel.Star = int64(grade.Star)
dbModel.BravePoint = grade.BravePoint
if err = m.userGradeModel.UpdateOptimistic(ctx, tx, dbModel); err != nil {
return nil, err
}
resp = &ReportResp{
ReportReq: ReportReq{
UserId: dbModel.UserId,
Username: item.Username,
Position: item.Position,
Score: item.Score,
},
Grade: &grade,
Result: result,
Reason: reason,
}
return resp, nil
}
func (m *Manager) calcRank(win bool, userCountSatisfied bool, grade, level, star, bravePoint int64, score float32, position int32) (g pb.Grade, result pb.StatPvPReportResp_GradeResult, reason pb.StatPvPReportResp_GradeReason) {
g.Grade = int32(grade)
g.Level = int32(level)
g.Star = int32(star)
g.BravePoint = bravePoint + int64(score)
if g.BravePoint > m.config.Grade.MaxBravePoint {
g.BravePoint = m.config.Grade.MaxBravePoint
}
if score < m.config.Grade.WinScoreThreshold {
result = pb.StatPvPReportResp_Keep
reason = pb.StatPvPReportResp_Win
return
}
if !userCountSatisfied {
result = pb.StatPvPReportResp_Keep
reason = pb.StatPvPReportResp_Win
return
}
if win {
reason = pb.StatPvPReportResp_Win // 获胜
if grade != model.MaxUserGrade || level != model.MaxUserGradeLevel-1 || star != model.MaxUserGradeStar {
// 非最高段位
if star == model.MaxUserGradeStar {
g.Star = model.DefaultUserGradeStar
if level == model.MaxUserGradeLevel {
g.Level = model.DefaultUserGradeLevel
// grade
if grade == model.MaxUserGrade {
g.Grade = int32(grade)
result = pb.StatPvPReportResp_Keep
} else {
g.Grade = int32(grade + 1)
result = pb.StatPvPReportResp_GradeUp
}
} else {
g.Level = int32(level + 1)
result = pb.StatPvPReportResp_LevelUp
}
} else {
g.Star = int32(star + 1)
result = pb.StatPvPReportResp_StarUp // 升星
}
}
} else {
// 大段位保护
if grade <= m.config.Grade.ProtectedGradeThreshold {
g.Grade = int32(grade)
g.Level = int32(level)
g.Star = int32(star)
g.BravePoint = bravePoint
reason = pb.StatPvPReportResp_Lost
result = pb.StatPvPReportResp_Keep
return
}
// 名次保护
if position <= m.config.Grade.ProtectedGradeLost {
g.Grade = int32(grade)
g.Level = int32(level)
g.Star = int32(star)
g.BravePoint = bravePoint
reason = pb.StatPvPReportResp_Lost
result = pb.StatPvPReportResp_Keep
return
}
// 段位的骁勇分减扣规则
bravePointCost := m.config.Grade.BravePointCost[grade]
if bravePoint >= bravePointCost {
g.Grade = int32(grade)
g.Level = int32(level)
g.Star = int32(star)
g.BravePoint = bravePoint - bravePointCost
if g.BravePoint < 0 {
g.BravePoint = 0
}
reason = pb.StatPvPReportResp_BravePoint
result = pb.StatPvPReportResp_Keep
return
}
reason = pb.StatPvPReportResp_Lost
if star != model.DefaultUserGradeStar || level != model.DefaultUserGradeLevel || grade != model.DefaultUserGrade {
if star == model.DefaultUserGradeStar {
g.Star = model.MaxUserGradeStar - 1 // 掉小段或掉大段,星星变 max-1
if level == model.DefaultUserGradeLevel {
g.Level = model.MaxUserGradeLevel
if grade == model.DefaultUserGrade {
g.Grade = model.DefaultUserGrade
result = pb.StatPvPReportResp_Keep
} else {
g.Grade = int32(grade - 1)
result = pb.StatPvPReportResp_GradeDown
}
} else {
g.Level = int32(level - 1)
result = pb.StatPvPReportResp_LevelDown
}
} else {
g.Star = int32(star - 1)
result = pb.StatPvPReportResp_StarDown
}
}
}
g.BravePoint = bravePoint + int64(score)
if g.BravePoint > m.config.Grade.MaxBravePoint {
g.BravePoint = m.config.Grade.MaxBravePoint
}
return
}