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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
}