package players import ( "context" "joybuywork/internal/app" "joybuywork/internal/consts" "joybuywork/internal/consts/ckey" "joybuywork/internal/plugins/jsonm" "joybuywork/internal/repository/cache" "joybuywork/internal/repository/dao" "joybuywork/internal/repository/dbcheck" "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/stocks" "joybuywork/internal/utils" "math/rand" "strconv" "strings" "time" "joybuywork/proto/go_proto/workerr" "github.com/golang-module/carbon/v2" ) const ( goldSpinEnergy = 100 // 转盘消耗能量 ) const ( goldFailTypeEnergyLack = iota + 1 // 能量不足 goldFailTypeRewardLack // 奖品库存不足 ) // Gold 金币任务交互逻辑 type Gold struct { Player player *model.FissionGold conf *model.FissionCampaignGold blindLang string } func NewGold(ctx context.Context, user *model.FissionUser, opts ...Option) (player *Gold, err error) { player = &Gold{} err = player.Init(ctx, user, consts.IdGold, opts...) if err != nil { return } err = player.initPlayer() if err != nil { return } return } // FinishTask 完成任务 func (s *Gold) FinishTask(tasks ...*model.FissionCampaignTask) (err error) { player, conf := s.GetPlayer(), s.GetConf() var chance int32 var logs = make([]*model.FissionGoldLog, 0, len(tasks)) var goldNum = player.GoldNum for _, task := range tasks { switch task.PropMark { case mtype.FissionCampaignTaskPropMarkChance: chance += task.Chance } val := mtype.GoldLogVal{ TaskId: task.ID, Chance: task.Chance, } log := withdefault.FissionGoldLog(&model.FissionGoldLog{ UserID: player.UserID, CampaignID: player.CampaignID, GoldID: player.ID, Type: mtype.FissionGoldLogTypeDoTask, }) switch task.TaskType { case mtype.FissionCampaignTaskTypeRegister: log.Source = mtype.FissionLogSourceRegister case mtype.FissionCampaignTaskTypeInstall: log.Source = mtype.FissionLogSourceInstall case mtype.FissionCampaignTaskTypeCondAll: log.Source = mtype.FissionLogSourceCondAll case mtype.FissionCampaignTaskTypeCondOne: log.Source = mtype.FissionLogSourceCondOne val.CondId = utils.FissionCampaignTaskMarkCondOneCondId(task.TaskMark) } goldNum += task.Chance log.Val, _ = jsonm.MarshalToString(val) logs = append(logs, log) } provider.NewFissionClue(s.Ctx, player.UserID).AddGold(player.ID, int64(player.GoldNum), int64(chance)) m := s.GetDao().FissionGold if conf.GoldNum > goldNum { _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(m.GoldNum.Add(chance)) if err != nil { return } return s._addLog(logs...) } _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(m.GoldNum.Add(chance), m.NextStep.Value(mtype.FissionGoldNextStepGoldRain)) if err != nil { return } return s._addLog(logs...) } // Clue 金币获得提醒 func (s *Gold) Clue() (old, add int32) { player := s.GetPlayer() clue := provider.NewFissionClue(s.Ctx, player.UserID).GetGold() if clue.GoldId != player.ID { return } old = int32(clue.OldGold) add = int32(clue.Add) return } // Spin 转转盘 func (s *Gold) Spin() (id int64, failType int32, err error) { cpn, user := s.cpn, s.user player, conf := s.GetPlayer(), s.GetConf() if player.Energy < goldSpinEnergy { failType = goldFailTypeEnergyLack return } var logs []*model.FissionGoldLog defer func() { if err != nil || len(logs) == 0 { return } err = s._addLog(logs...) }() if player.LotteryNum == 0 { id = 4 m := s.GetDao().FissionGold _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.Energy.Sub(goldSpinEnergy), m.LotteryNum.Add(1), m.ShowPrice.Add(0.99), m.NextStep.Value(mtype.FissionGoldNextStepLastSpin), ) if err != nil { err = s.BE().MysqlError(err) return } val, _ := jsonm.MarshalToString(&mtype.GoldLogVal{ Cash: 0.99, }) logs = append(logs, withdefault.FissionGoldLog(&model.FissionGoldLog{ CampaignID: cpn.ID, UserID: user.ID, GoldID: player.ID, Type: mtype.FissionGoldLogTypeLottery, Source: mtype.FissionGoldLogSourceLottery, Val: val, })) return } // 有库存限制 if conf.PriceNum > 0 { if conf.Lack == mtype.Yes { failType = goldFailTypeRewardLack return } var stock *stocks.Campaign stock, err = stocks.NewCampaign(s.Ctx, cpn.ID) if err != nil { return } var ok bool ok, err = stock.DecrPoolAndAdd(cpn.ID, 1) if err != nil { return } if !ok { if conf.Lack == mtype.No { confM := s.GetDao().FissionCampaignGold _, err = confM.WithContext(s.Ctx).Where(confM.ID.Eq(conf.ID)).UpdateSimple(confM.Lack.Value(mtype.Yes)) if err != nil { err = s.BE().MysqlError(err) return } _ = cache.NewCampaignGold(s.Ctx).Clear(conf) } failType = goldFailTypeRewardLack return } } cm := cmm.NewCMM(s.Ctx, s.GetDao()) var rewards []*model.FissionCampaignReward rewards, err = cm.UserAppReward(s.user) if err != nil { return } var addRewards = make([]*award.RewardOne, 0, len(rewards)) var ids []int64 for _, reward := range rewards { addRewards = append(addRewards, &award.RewardOne{Reward: reward}) } rewardsRes, _, err := award.NewAwardReward(s.Ctx, s.GetDao(), cpn.ProjectID, user).Batch(addRewards, true) if err != nil { return } for _, reward := range rewardsRes { ids = append(ids, reward.Record.ID) } m := s.GetDao().FissionGold _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.Energy.Sub(goldSpinEnergy), m.LotteryNum.Add(1), m.ShowPrice.Add(0.01), m.NextStep.Value(mtype.FissionGoldNextStepShowReward), ) if err != nil { err = s.BE().MysqlError(err) return } val, _ := jsonm.MarshalToString(&mtype.GoldLogVal{ Cash: 0.01, }) rewardVal, _ := jsonm.MarshalToString(&mtype.GoldLogVal{ RecordIds: ids, }) logs = append(logs, withdefault.FissionGoldLog(&model.FissionGoldLog{ CampaignID: cpn.ID, UserID: user.ID, GoldID: player.ID, Type: mtype.FissionGoldLogTypeLottery, Source: mtype.FissionGoldLogSourceLottery, Val: val, }), withdefault.FissionGoldLog(&model.FissionGoldLog{ CampaignID: cpn.ID, UserID: user.ID, GoldID: player.ID, Type: mtype.FissionGoldLogTypeReward, Source: mtype.FissionGoldLogSourceReward, Val: rewardVal, })) id = 1 return } // GoldRain 弹窗还差最后一次抽奖获得奖品动画 func (s *Gold) GoldRain() (err error) { player := s.player if player.NextStep != mtype.FissionGoldNextStepGoldRain { return } m := s.GetDao().FissionGold _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.NextStep.Value(mtype.FissionGoldNextStepNone), m.Energy.Add(1), ) if err != nil { err = s.BE().MysqlError(err) return } return } // LastSpin 弹窗还差最后一次抽奖获得奖品动画 func (s *Gold) LastSpin() (err error) { player := s.player if player.NextStep != mtype.FissionGoldNextStepLastSpin { return } m := s.GetDao().FissionGold _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.NextStep.Value(mtype.FissionGoldNextStepNone), ) if err != nil { err = s.BE().MysqlError(err) return } return } // ShowReward 播放展示奖品动画 func (s *Gold) ShowReward() (err error) { player := s.player if player.NextStep != mtype.FissionGoldNextStepShowReward { return } m := s.GetDao().FissionGold _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.NextStep.Value(mtype.FissionGoldNextStepNone), ) if err != nil { err = s.BE().MysqlError(err) return } return } // TriggerShare 赠送动画 func (s *Gold) TriggerShare() (err error) { player := s.GetPlayer() if player.NextStep != mtype.FissionGoldNextStepTriggerShare { return } m := s.GetDao().FissionGold _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.ShowPrice.Add(1), m.Energy.Add(100), m.NextStep.Value(mtype.FissionGoldNextStepNone), ) if err != nil { err = s.BE().MysqlError(err) return } return } // OpenGift 播放开盒子动画 func (s *Gold) OpenGift() (openBox, leftBox, rightBox float64, err error) { player, cpn, conf := s.GetPlayer(), s.cpn, s.GetConf() if player.NextStep != mtype.FissionGoldNextStepOpenGift { return } spl := cpn.GetLangMarks() var price = conf.Price if player.Lang != spl[0] { var lang *model.FissionCampaignLang lang, err = cache.NewCampaignLang(s.Ctx).FindByCampaignId(cpn.ID, player.Lang) if err != nil { err = s.BE().CacheError(err) return } if lang == nil { err = workerr.NotFound("not found cash conf") return } if c := lang.GetGold(); c != nil { price = c.Price } } openBox = price - 2 if strings.Contains(strconv.FormatFloat(price, 'f', -1, 64), ".") { leftBox = openBox - float64(rand.Int31n(5)+15) - float64(rand.Int31n(50)+15)/100 rightBox = openBox - float64(rand.Int31n(5)+10) - float64(rand.Int31n(30)+10)/100 } else { leftBox = openBox - float64(rand.Int31n(5)+15) rightBox = openBox - float64(rand.Int31n(5)+10) } m := s.GetDao().FissionGold _, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple( m.ShowPrice.Value(openBox), m.NextStep.Value(mtype.FissionGoldNextStepTriggerShare), m.Energy.Value(99), ) if err != nil { err = s.BE().MysqlError(err) return } val, _ := jsonm.MarshalToString(&mtype.GoldLogVal{ Cash: openBox, }) err = s._addLog(withdefault.FissionGoldLog(&model.FissionGoldLog{ CampaignID: player.CampaignID, UserID: player.UserID, GoldID: player.ID, Type: mtype.FissionGoldLogTypeOpenGift, Source: mtype.FissionGoldLogSourceOpenGift, Val: val, })) return } // RefreshView 刷新预览用户 func (s *Gold) RefreshView() (err error) { if !s.IsView() { err = s.BE().VerifyError("非预览用户无法重新体验") return } player := s.GetPlayer() m := s.GetDao().FissionGoldLog _, err = m.WithContext(s.Ctx).Where(m.GoldID.Eq(player.ID)).Delete() if err != nil { err = s.BE().MysqlError(err) return } playerM := s.GetDao().FissionGold _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).Delete() if err != nil { err = s.BE().MysqlError(err) return } return } // GetConf 获取玩法配置 func (s *Gold) GetConf() *model.FissionCampaignGold { if s.conf == nil { s.conf, _ = cache.NewCampaignGold(s.Ctx).FindByCampaignId(s.user.CampaignID) } return s.conf } // GetPlayer 获取玩家基础数据 func (s *Gold) GetPlayer() *model.FissionGold { return s.player } // 玩家 func (s *Gold) initPlayer() (err error) { user, sp := s.user, s.cpn.GetLangMarks() lang := utils.NotExistGetFirst(user.LangMark, sp) s.blindLang = lang player, err := s._dbFirst(lang) if err != nil { return } defer func() { if err != nil { return } s.player = player }() if player != nil { // 没有过期,或者已经完成了任务 if player.ExpiredIn > time.Now().Unix() || player.LotteryNum >= int8(len(consts.SpinGetId)) { return } // 过期了 playerM := s.GetDao().FissionGold _, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(player.ID)).UpdateSimple(playerM.IsExpired.Value(mtype.Yes)) if err != nil { err = s.BE().MysqlError(err) return } } player, err = s.firstOrCreate(lang) if err != nil { return } return } // 创建玩家基础数据 func (s *Gold) firstOrCreate(lang string) (player *model.FissionGold, err error) { lc := app.NewFollower(s.Ctx, ckey.FissionUserCreatePlayerOnce(s.user.ID)).Lock() defer lc.Release() if lc.Error() != nil { err = lc.Error() return } player, err = s._dbFirst(lang) if err != nil { return } if player != nil { return } return s.createPlayer(lang) } func (s *Gold) createPlayer(lang string) (player *model.FissionGold, err error) { conf := s.GetConf() playerM := dao.FissionGold player = withdefault.FissionGold(&model.FissionGold{ CampaignID: s.user.CampaignID, UserID: s.user.ID, LotteryNum: 0, NextStep: mtype.FissionGoldNextStepOpenGift, Energy: 0, ShowPrice: 0, ExpiredIn: carbon.Now().AddDays(int(conf.ExpiredDay)).Timestamp(), IsExpired: 0, Lang: lang, }) err = playerM.WithContext(s.Ctx).Create(player) if err != nil { err = s.BE().MysqlError(err) return } return } func (s *Gold) _dbFirst(lang string) (*model.FissionGold, error) { playerM := dao.FissionGold player, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID), playerM.Lang.Value(lang), playerM.IsExpired.Eq(mtype.No)).Last() if err = dbcheck.DbError(err); err != nil { return nil, s.BE().MysqlError(err) } return player, nil } func (s *Gold) _addLog(logs ...*model.FissionGoldLog) (err error) { err = s.GetDao().FissionGoldLog.WithContext(s.Ctx).Create(logs...) if err != nil { err = s.BE().MysqlError(err) return } return }