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) 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) 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) (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 { // 段位的骁勇分减扣规则 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 }