package campaignuser import ( "joybuywork/internal/app" "joybuywork/internal/consts" "joybuywork/internal/repository/dbcheck" "joybuywork/internal/repository/finder" "joybuywork/internal/repository/model" "joybuywork/internal/repository/mtype" "joybuywork/internal/repository/withdefault" "joybuywork/internal/service/cmm" "joybuywork/internal/service/provider" "joybuywork/internal/service/provider/award" "joybuywork/internal/service/provider/fissionaction" "joybuywork/internal/utils" "joybuywork/internal/utils/logic" "joybuywork/internal/utils/verify" pb "joybuywork/proto/go_proto/w_campaign_user" "joybuywork/proto/go_proto/workerr" "fmt" "time" "gits.branchcn.com/backend/microcode/plugins/lucky" "gorm.io/gen/field" ) type rollData struct { MarkType int8 // 格子类型 Step int32 // 下标数据 RewardId int64 // 活动奖品id MapId int64 // 地图int类型id MapHashId string // 地图hash_id RewardTitle string // 奖品名称 ImgUrl string // 奖品地址 Intact bool // 合成了奖品 Lack bool // 缺库存 } const ( campaignUserDiceFailTypeNone = iota // 没有错误 campaignUserDiceFailTypeLack // 缺骰子数 campaignUserDiceFailTypeProps // 道具不足 ) type Dice struct { logic.Logic Req *pb.DiceReq Res *pb.DiceRes isView bool //是否预览用户 prop map[string]*model.FissionDiceProp user *model.FissionUser } func (s *Dice) Run() error { return logic.Run(s) } func (s *Dice) Exec() (err error) { player, run, err := s.player() if err != nil { return } if s._isFixedRoll() { return s.useFixedRoll(player, run) } return s.normal(player, run) } func (s *Dice) player() (player *model.FissionDice, run *model.FissionDiceRun, err error) { cpn, err := finder.Campaign(s.Ctx, s.Req.GetCampaignId()) if err != nil { err = s.BE().CacheError(err) return } if cpn == nil || cpn.IsDel == mtype.Yes { err = workerr.NotFound("not found cpn") return } user, err := finder.FissionUser(s.Ctx, cpn.ID, s.Req.GetUserId()) if err != nil { err = s.BE().CacheError(err) return } if user == nil { err = workerr.UnauthorizedError return } m := s.GetDao().FissionDice player, err = m.WithContext(s.Ctx).Where(m.UserID.Eq(user.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if player == nil { err = workerr.NotFound("not found player") return } run, err = cmm.NewCMM(s.Ctx, s.GetDao()).GetUserMap(user) if err != nil { return } s.isView = user.IsView == mtype.Yes s.user = user return } func (s *Dice) useFixedRoll(player *model.FissionDice, run *model.FissionDiceRun) (err error) { m := s.GetDao().FissionDiceProp prop, err := m.WithContext(s.Ctx).Where(m.UserID.Eq(player.UserID), m.Mark.Eq(mtype.FissionDicePropMarkFixedRoll)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if prop == nil || prop.Num <= 0 { s.Res.FailType = campaignUserDiceFailTypeProps return } _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(prop.ID)).UpdateSimple(m.Num.Sub(1)) if err != nil { err = s.BE().MysqlError(err) return } step := utils.StrToInt(s.Req.GetVal()) - 1 res, err := s._rollMap(run, step) if err != nil || res == nil { return } // 记录日志 err = s._diceRecord(player, res, true) if err != nil { return } // 使用道具埋点 fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).UseProp(time.Now().Unix(), consts.PropNameFixedDice) return s._resp(run, res) } func (s *Dice) normal(player *model.FissionDice, run *model.FissionDiceRun) (err error) { if player.Chance <= 0 { s.Res.FailType = campaignUserDiceFailTypeLack return } step := utils.RandNumber(6) res, err := s._rollMap(run, int(step)) if err != nil || res == nil { return } // 不是预览用户要减次数,并且不是再来一次就减一次 if !s.isView && res.MarkType != mtype.FissionDiceMapMarkTypeChance { m := s.GetDao().FissionDice _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(m.Chance.Sub(1)) if err != nil { err = s.BE().MysqlError(err) return } } // 记录日志 err = s._diceRecord(player, res, false) if err != nil { return } return s._resp(run, res) } // 结果处理 func (s *Dice) _resp(run *model.FissionDiceRun, res *rollData) (err error) { // 更新地图信息 runM := s.GetDao().FissionDiceRun _, err = runM.WithContext(s.Ctx).Where(runM.ID.Eq(run.ID)).UpdateSimple( runM.MapID.Value(res.MapId), runM.MapHashID.Value(res.MapHashId), runM.GoStep.Add(int64(res.Step)+1), ) if err != nil { err = s.BE().MysqlError(err) return } s.Res.MarkType = int32(res.MarkType) s.Res.MapHashId = res.MapHashId s.Res.RewardTitle = res.RewardTitle s.Res.ImgUrl = res.ImgUrl s.Res.IsIntact = res.Intact s.Res.RollNum = res.Step + 1 s.Res.Lack = res.Lack s.Res.RewardId = res.RewardId return } func (s *Dice) _rollMap(run *model.FissionDiceRun, idx int) (res *rollData, err error) { m := s.GetDao().FissionDiceMap list, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(run.CampaignID), m.DmgID.Eq(run.DmgID), m.ID.Gt(run.MapID)).Limit(6).Find() if err != nil { err = s.BE().MysqlError(err) return } if len(list) < 6 { back := 6 - len(list) var backList []*model.FissionDiceMap backList, err = m.WithContext(s.Ctx).Where(m.CampaignID.Eq(run.CampaignID), m.DmgID.Eq(run.DmgID)).Limit(back).Order(m.ID).Find() if err != nil { err = s.BE().MysqlError(err) return } for _, v := range backList { list = append(list, v) } } if len(list) == 0 { app.Logger.Error(s.Ctx, fmt.Sprintf("活动[%d]地图生成异常, 用户id[%d]", run.CampaignID, run.UserID)) err = workerr.ServerError return } var loseMap = make(map[int]*model.FissionDiceMap) for i, v := range list { if verify.InArray(v.MarkType, mtype.FissionDiceMapMarkTypeNone, mtype.FissionDiceMapMarkTypeChance, mtype.FissionDiceMapMarkTypeFixedRoll) { loseMap[i] = v } } var win, lose *rollData for i, v := range loseMap { switch v.MarkType { case mtype.FissionDiceMapMarkTypeNone, mtype.FissionDiceMapMarkTypeChance: lose = &rollData{ MarkType: v.MarkType, Step: int32(i), RewardId: v.RewardID, MapId: v.ID, MapHashId: v.HashID, } break } } win = &rollData{ MarkType: list[idx].MarkType, Step: int32(idx), RewardId: list[idx].RewardID, MapId: list[idx].ID, MapHashId: list[idx].HashID, } // 预览用户直接返回结果 if s.isView { return s._viewWin(run.CampaignID, win) } // 不是固定骰子,要看看走不走概率 if !s._isFixedRoll() { confM := s.GetDao().FissionCampaignDice var conf *model.FissionCampaignDice conf, err = confM.WithContext(s.Ctx).Where(confM.CampaignID.Eq(run.CampaignID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } // 走概率发奖 if conf.AwardType == mtype.FissionCampaignDiceAwardTypeOdds { winner, _ := lucky.NewDiscrete(s.Ctx, []lucky.Reward{ {Share: int(conf.Odds), Mark: true}, {Share: 1000 - int(conf.Odds), Mark: false}, }).Lottery() // 赢不了 if !winner.Bool() { return lose, nil } return s._odds(run, list, win, lose) } } // 如果都没中奖品,那就不用做奖品处理 if !verify.InArray(win.MarkType, mtype.FissionDiceMapMarkTypeNormal, mtype.FissionDiceMapMarkTypeSuper) { return win, nil } res, err = s._reward(run, win, lose) if err != nil { return } return } func (s *Dice) _reward(run *model.FissionDiceRun, win, lose *rollData) (res *rollData, err error) { m := s.GetDao().FissionCampaignReward reward, err := m.WithContext(s.Ctx).Where(m.ID.Eq(win.RewardId)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if reward == nil || reward.IsDel == mtype.Yes { return lose, nil } collect, err := s._getCollect(run, reward) if err != nil { return } return s._rewardInDb(reward, collect, win, lose) } // 完整的奖品处理 func (s *Dice) _intactReward(cpn *model.FissionCampaign, reward *model.FissionCampaignReward, lack bool) (record *model.FissionRewardRecord, err error) { var opts []award.RecordOption if lack { opts = append(opts, award.RewardRecordStatus( mtype.FissionRewardRecordStatusFail, mtype.FissionRewardRecordFailTypeCli, mtype.FissionRewardRecordFailTypeCliLackMsg, )) } record, _, async, err := award.NewAwardReward(s.Ctx, s.GetDao(), cpn.ProjectID, s.user).One(reward, false, opts...) if lack { return } s.AddAfterRun(func() (err error) { for _, do := range async { do() } return }) return } // 概率获取奖品 func (s *Dice) _odds(run *model.FissionDiceRun, diceMaps []*model.FissionDiceMap, win *rollData, lose *rollData) (res *rollData, err error) { var rewardMap = make(map[int64]*model.FissionCampaignReward) var ids []int64 var mapIdx = make(map[int]*model.FissionDiceMap) for idx, v := range diceMaps { if _, ok := rewardMap[v.RewardID]; !ok { ids = append(ids, v.RewardID) rewardMap[v.RewardID] = nil } mapIdx[idx] = v } if len(ids) == 0 { return win, nil } m := s.GetDao().FissionCampaignReward list, err := m.WithContext(s.Ctx).Where(m.ID.In(ids...)).Find() if err != nil { err = s.BE().MysqlError(err) return } var allOdds int var prize []lucky.Reward for _, v := range list { odds := int(v.Odds) prize = append(prize, lucky.Reward{ Share: odds, Mark: v.ID, }) allOdds = allOdds + odds rewardMap[v.ID] = v } reward, err := lucky.NewDiscrete(s.Ctx, prize).Lottery() if err != nil { return } id := reward.Int64() // 概率中奖了,就走到那个中奖的格子 for idx, diceMap := range mapIdx { if diceMap.RewardID == id { win = &rollData{ MarkType: diceMap.MarkType, Step: int32(idx), RewardId: diceMap.RewardID, MapId: diceMap.ID, MapHashId: diceMap.HashID, } break } } collect, err := s._getCollect(run, rewardMap[id]) if err != nil { return } return s._rewardInDb(rewardMap[id], collect, win, lose) } // 获取收集碎片的记录 func (s *Dice) _getCollect(run *model.FissionDiceRun, reward *model.FissionCampaignReward) (collect *model.FissionDiceCollect, err error) { collectM := s.GetDao().FissionDiceCollect collect, err = collectM.WithContext(s.Ctx).Where(collectM.UserID.Eq(run.UserID), collectM.RewardID.Eq(reward.ID)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } if collect == nil { collect = withdefault.FissionDiceCollect(&model.FissionDiceCollect{ UserID: run.UserID, CampaignID: run.CampaignID, RewardID: reward.ID, Num: 0, IntactNum: 0, }) if err = collectM.WithContext(s.Ctx).Create(collect); err != nil { err = s.BE().MysqlError(err) return } } return } // 奖品入库 func (s *Dice) _rewardInDb(reward *model.FissionCampaignReward, collect *model.FissionDiceCollect, win, lose *rollData) (res *rollData, err error) { var intact bool var lack bool if reward.CollectNum == collect.Num+1 { //中奖了 var check bool check, err = award.NewAwardIssVerify(s.Ctx, s.user, reward).DeductStock().RuleFilter().IsOK() if err != nil { return } if !check && s.Req.GetMark() != mtype.FissionDicePropMarkFixedRoll { return lose, nil } intact = true lack = !check } cpn, err := finder.Campaign(s.Ctx, reward.CampaignID) if err != nil { err = s.BE().CacheError(err) return } preward, err := provider.NewRewards(s.Ctx, cpn.ProjectID, reward.RewardID) if err != nil { return } info := preward.AutoLang(reward.RewardID, s.user.LangMark) collectM := s.GetDao().FissionDiceCollect var up []field.AssignExpr var record *model.FissionRewardRecord if intact { record, err = s._intactReward(cpn, reward, lack) if err != nil { return } if lack { // 没有库存,那就不让客户看动画了 up = append(up, collectM.Num.Value(0), collectM.IntactNum.Add(1)) s.AddAfterRun(func() (err error) { // 骰子记录 var diceRecord = withdefault.FissionDiceRecord(&model.FissionDiceRecord{ CreatedAt: record.CreatedAt, CampaignID: record.CampaignID, UserID: record.UserID, Mark: mtype.FissionDiceRecordMarkReward, Data: "", }) diceRecord.SetData(&mtype.DiceRecord{ RewardId: record.RewardID, RecordId: record.ID, }) // 这里事务已经提交了,不能用有事务的dao err = s.Dao().FissionDiceRecord.WithContext(s.Ctx).Create(diceRecord) if err != nil { err = s.BE().MysqlError(err) return } return }) } else { // 有库存,让用户看到合成动画 up = append(up, collectM.Num.Add(1), collectM.RecordID.Value(record.ID)) } } else { // 没有收集完碎片,碎片数 +1 up = append(up, collectM.Num.Add(1)) } _, err = collectM.WithContext(s.Ctx).Where(collectM.ID.Eq(collect.ID)).UpdateSimple(up...) if err != nil { err = s.BE().MysqlError(err) return } win.RewardTitle = info.Title win.ImgUrl = info.ImgUrl win.Intact = intact win.Lack = lack return win, nil } // 预览用户返回的数据 func (s *Dice) _viewWin(cpnId int64, rd *rollData) (win *rollData, err error) { win = rd if win.RewardId == 0 { return } cpn, err := finder.Campaign(s.Ctx, cpnId) if err != nil { err = s.BE().CacheError(err) return } m := s.GetDao().FissionCampaignReward reward, err := m.WithContext(s.Ctx).Where(m.ID.Eq(win.RewardId)).Take() if err = dbcheck.DbError(err); err != nil { err = s.BE().MysqlError(err) return } preward, err := provider.NewRewards(s.Ctx, cpn.ProjectID, reward.RewardID) if err != nil { return } info := preward.AutoLang(reward.RewardID, s.user.LangMark) win.RewardTitle = info.Title win.ImgUrl = info.ImgUrl return } // 加骰子记录 func (s *Dice) _diceRecord(player *model.FissionDice, win *rollData, luckyProp bool) (err error) { if s.Res.FailType != campaignUserDiceFailTypeNone { return } record := withdefault.FissionDiceRecord(&model.FissionDiceRecord{ CampaignID: player.CampaignID, UserID: player.UserID, }) data := &mtype.DiceRecord{ Point: win.Step + 1, RewardId: win.RewardId, } defer func() { err = s.GetDao().FissionDiceRecord.WithContext(s.Ctx).Create(record) if err != nil { err = s.BE().MysqlError(err) } }() // 使用幸运骰子道具 if luckyProp { switch win.MarkType { case mtype.FissionDiceMapMarkTypeNone: record.Mark = mtype.FissionDiceRecordMarkLucky case mtype.FissionDiceMapMarkTypeChance: record.Mark = mtype.FissionDiceRecordMarkLuckyChance data.Chance = 1 default: record.Mark = mtype.FissionDiceRecordMarkLuckyCollect } record.SetData(data) return } switch win.MarkType { case mtype.FissionDiceMapMarkTypeNone: record.Mark = mtype.FissionDiceRecordMarkRoll case mtype.FissionDiceMapMarkTypeChance: record.Mark = mtype.FissionDiceRecordMarkRollChance data.Chance = 1 default: record.Mark = mtype.FissionDiceRecordMarkRollCollect } record.SetData(data) return } // 是否固定骰子 func (s *Dice) _isFixedRoll() bool { return s.Req.GetMark() == mtype.FissionDicePropMarkFixedRoll }