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.

625 lines
14 KiB
Go

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/provider/cachecurd"
"joybuywork/internal/service/provider/fissionaction"
stos "joybuywork/internal/service/provider/s2s"
"time"
"gorm.io/gen/field"
)
const (
CoinDeductStatusLack = iota + 1 // 缺乏金币
CoinDeductStatusMax // 达到投放上限
CoinDeductStatusEnd // 当前场次已结束
)
const (
CoinGainStatusNotOpen = iota + 1 // 未开奖
CoinGainStatusLackJoin // 参与次数不足
CoinGainStatusExpired // 领奖超时
CoinGainStatusGot // 已领取
CoinGainStatusFail // 领取失败
)
type CoinStatus struct {
GetCoin int64 // 预计获得金币数
UseNum int32 // 投放次数
AllCoin int64 // 总奖池金额
OpenSec int64 // 剩余开奖时间(秒)
CanGet int32 // 是否可以领取瓜分了
End int32 // 是否没有下一场了
}
// Coin 金币池交互逻辑
type Coin struct {
Player
player *model.FissionCoin
conf *model.FissionCampaignCoin
nowScene *model.FissionCoinScene
end int32
preSceneOk int8
lackKey string
}
func NewCoin(ctx context.Context, user *model.FissionUser, opts ...Option) (player *Coin, err error) {
player = &Coin{}
err = player.Init(ctx, user, consts.IdCoin, opts...)
if err != nil {
return
}
err = player.initNowScene()
if err != nil {
return
}
err = player.initPlayer()
if err != nil {
return
}
return
}
func (s *Coin) FinishTask(tasks ...*model.FissionCampaignTask) (err error) {
player := s.GetPlayer()
var logs []*model.FissionCoinLog
for _, task := range tasks {
if task.AwardMode == mtype.FissionCampaignTaskAwardModeManual || task.PropMark == mtype.FissionCampaignTaskPropMarkChance {
continue
}
var source = mtype.FissionCoinLogSourceFinishTask
if task.TaskType == mtype.FissionCampaignTaskTypeCondAll {
source = mtype.FissionCoinLogSourceFinishAllTask
}
log := withdefault.FissionCoinLog(&model.FissionCoinLog{
UserID: player.UserID,
CampaignID: player.CampaignID,
CoinID: player.ID,
SceneID: player.SceneID,
Type: mtype.FissionCoinLogTypeReward,
Source: source,
})
log.SetVal(mtype.CoinLogVal{
TaskId: task.ID,
RewardId: task.RewardID,
})
logs = append(logs, log)
}
if len(logs) == 0 {
return
}
return s._addLog(logs...)
}
func (s *Coin) Deduct() (useNum, status int32, err error) {
player := s.GetPlayer()
if s.IsView() {
useNum = 2
return
}
nowScene := s.GetNowScene()
if nowScene.OpenTime <= time.Now().Unix() {
status = CoinDeductStatusEnd
return
}
conf, lackKey := s.GetConf(), s.lackKey
if player.UseNum >= conf.UseLimit {
status = CoinDeductStatusMax
return
}
// 看看是不是有
if app.Redis.Cache.Exists(s.Ctx, lackKey).Val() != 0 {
status = CoinDeductStatusLack
return
}
deductM := s.GetDao().FissionPointDeduct
deduct, err := deductM.WithContext(s.Ctx).Where(deductM.UserID.Eq(player.UserID)).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if deduct == nil {
deduct = withdefault.FissionPointsDeduct(&model.FissionPointDeduct{
UserID: player.UserID,
})
if err = s.GetDao().FissionPointDeduct.WithContext(s.Ctx).Create(deduct); err != nil {
err = s.BE().MysqlError(err)
return
}
}
s2s, err := stos.NewPointsChange(s.Ctx, s.cpn, s.user, conf.URLID)
if err != nil {
return
}
res, err := s2s.CoinDeduct(player, conf.UseCoin, deduct.ID)
if err != nil {
return
}
if res.State == stos.PointsChangeLack {
app.Redis.Cache.Set(s.Ctx, lackKey, 1, 10*time.Second)
status = CoinDeductStatusLack
return
}
m := s.GetDao().FissionCoin
var up = []field.AssignExpr{
m.UseCoin.Add(conf.UseCoin),
m.UseNum.Add(1),
}
if player.Status == mtype.FissionCoinStatusWait {
up = append(up, m.Status.Value(mtype.FissionCoinStatusJoin))
}
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(up...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
_, err = s._sceneCoin(player.SceneID, conf.UseCoin, player.UseNum == 0)
if err != nil {
return
}
val, _ := jsonm.MarshalToString(&mtype.CoinLogVal{
CoinNum: int64(conf.UseCoin),
RelateId: res.RelateId,
})
err = s._addLog(withdefault.FissionCoinLog(&model.FissionCoinLog{
CampaignID: player.CampaignID,
UserID: player.UserID,
CoinID: player.ID,
SceneID: player.SceneID,
Type: mtype.FissionCoinLogTypeUse,
Source: mtype.FissionCoinLogSourcePutIn,
Val: val,
}))
if err != nil {
return
}
useNum = player.UseNum + 1
s.player.UseNum += 1
fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).UseCoin(time.Now().Unix(), player.SceneID, int64(useNum))
_, err = deductM.WithContext(s.Ctx).Delete(deduct)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
func (s *Coin) Gain() (getCoin int64, status int32, err error) {
if s.IsView() {
getCoin = 99999
return
}
nowScene := s.GetNowScene()
if nowScene.OpenTime > time.Now().Unix() {
status = CoinGainStatusNotOpen
return
}
player, conf := s.GetPlayer(), s.GetConf()
if player.Status == mtype.FissionCoinStatusWait {
status = CoinGainStatusLackJoin
return
}
if player.Status == mtype.FissionCoinStatusOk {
status = CoinGainStatusGot
return
}
if player.Status == mtype.FissionCoinStatusExpired {
status = CoinGainStatusExpired
return
}
s2s, err := stos.NewPointsChange(s.Ctx, s.cpn, s.user, conf.URLID)
if err != nil {
return
}
_, getCoin, err = s._getCoin(player.SceneID, player.UseNum)
if err != nil {
return
}
res, err := s2s.CoinGain(player, int32(getCoin))
if err != nil {
return
}
if res.State != stos.PointsChangeSuccess {
status = CoinGainStatusFail
return
}
m := s.GetDao().FissionCoin
_, err = m.WithContext(s.Ctx).Where(m.ID.Eq(player.ID)).UpdateSimple(
m.GetCoin.Add(int32(getCoin)),
m.Status.Value(mtype.FissionCoinStatusOk),
)
if err != nil {
err = s.BE().MysqlError(err)
return
}
sceneData, err := s._sceneData(player.SceneID)
if err != nil {
return
}
val, _ := jsonm.MarshalToString(&mtype.CoinLogVal{
CoinNum: getCoin,
RelateId: res.RelateId,
})
err = s._addLog(withdefault.FissionCoinLog(&model.FissionCoinLog{
CampaignID: player.CampaignID,
UserID: player.UserID,
CoinID: player.ID,
SceneID: player.SceneID,
Type: mtype.FissionCoinLogTypeAdd,
Source: mtype.FissionCoinLogSourceGetOk,
Val: val,
}))
if err != nil {
return
}
_, err = sceneData.Incr(cachecurd.CoinSceneGetNum, 1)
if err != nil {
err = s.BE().CacheError(err)
return
}
_, err = sceneData.Incr(cachecurd.CoinSceneGetTotal, getCoin)
if err != nil {
err = s.BE().CacheError(err)
return
}
fissionaction.NewMarkPoint(s.Ctx).InApp(s.user).GetCoin(time.Now().Unix(), player.SceneID)
return
}
// GetStatus 获取用户状态
func (s *Coin) GetStatus() (status CoinStatus, err error) {
nowScene, player := s.GetNowScene(), s.GetPlayer()
userCoin, getCoin, err := s._getCoin(nowScene.ID, player.UseNum)
if err != nil {
return
}
status = CoinStatus{
GetCoin: getCoin,
UseNum: player.UseNum,
AllCoin: int64(nowScene.InitCoin) + userCoin,
OpenSec: nowScene.OpenTime - time.Now().Unix(),
CanGet: 0,
End: s.end,
}
if status.OpenSec < 0 {
status.OpenSec = 0
}
// 开奖了
if nowScene.OpenTime <= time.Now().Unix() && nowScene.RewardTime > time.Now().Unix() && player.Status == mtype.FissionCoinStatusJoin {
status.CanGet = 1
}
return
}
// GetConf 获取玩法配置
func (s *Coin) GetConf() *model.FissionCampaignCoin {
if s.conf == nil {
s.conf, _ = cache.NewCampaignCoin(s.Ctx).FindByCampaignId(s.user.CampaignID)
}
return s.conf
}
// GetNowScene 当前场次数据
func (s *Coin) GetNowScene() *model.FissionCoinScene {
return s.nowScene
}
// GetNowScene 当前场次数据
func (s *Coin) initNowScene() (err error) {
m := s.GetDao().FissionCoinScene
nowScene, err := m.WithContext(s.Ctx).Where(m.CampaignID.Eq(s.cpn.ID), m.OpenTime.Gte(time.Now().Unix())).Order(m.OpenTime).First()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
if nowScene == nil {
nowScene, _ = m.WithContext(s.Ctx).Where(m.CampaignID.Eq(s.cpn.ID)).Last()
s.end = 1
}
s.nowScene = nowScene
return
}
// GetPlayer 获取玩家基础数据
func (s *Coin) GetPlayer() *model.FissionCoin {
return s.player
}
// 玩家
func (s *Coin) initPlayer() (err error) {
player, err := s._dbFirst()
if err != nil {
return
}
defer func() {
if err != nil {
return
}
s.player = player
s.lackKey = ckey.FissionCoinLack(player.UserID)
}()
if player != nil {
return
}
player, err = s.firstOrCreate()
if err != nil {
return
}
return
}
// 创建玩家基础数据
func (s *Coin) firstOrCreate() (player *model.FissionCoin, 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()
if err != nil {
return
}
if player != nil {
return
}
return s.createPlayer()
}
func (s *Coin) createPlayer() (player *model.FissionCoin, err error) {
nowScene := s.GetNowScene()
playerM := dao.FissionCoin
player = withdefault.FissionCoin(&model.FissionCoin{
CampaignID: s.user.CampaignID,
UserID: s.user.ID,
SceneID: nowScene.ID,
PreSceneOk: s.preSceneOk,
})
err = playerM.WithContext(s.Ctx).Create(player)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
func (s *Coin) _dbFirst() (player *model.FissionCoin, err error) {
nowScene := s.GetNowScene()
playerM := dao.FissionCoin
player, err = playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID), playerM.SceneID.Eq(nowScene.ID)).Take()
if err = dbcheck.DbError(err); err != nil {
return nil, s.BE().MysqlError(err)
}
if nowScene.PreScene == 0 || (player != nil && player.PreSceneOk == mtype.Yes) || s.end == 1 {
return
}
lastPlayer, err := playerM.WithContext(s.Ctx).Where(playerM.UserID.Eq(s.user.ID)).Last()
if err = dbcheck.DbError(err); err != nil {
return nil, s.BE().MysqlError(err)
}
if lastPlayer == nil {
s.preSceneOk = mtype.Yes
return
}
// 不是上一场
if lastPlayer.SceneID != nowScene.PreScene {
if lastPlayer.Status == mtype.FissionCoinStatusJoin {
var scene *model.FissionCoinScene
m := s.GetDao().FissionCoinScene
scene, err = m.WithContext(s.Ctx).Where(m.ID.Eq(lastPlayer.SceneID)).Take()
if err = dbcheck.DbError(err); err != nil {
err = s.BE().MysqlError(err)
return
}
_, err = playerM.WithContext(s.Ctx).Where(playerM.ID.Eq(lastPlayer.ID)).UpdateSimple(playerM.Status.Value(mtype.FissionCoinStatusExpired))
if err != nil {
err = s.BE().MysqlError(err)
return
}
val, _ := jsonm.MarshalToString(&mtype.CoinLogVal{
CoinNum: s._count(scene.UserCoin, scene.JoinNum, lastPlayer.UseNum),
})
err = s._addLog(withdefault.FissionCoinLog(&model.FissionCoinLog{
CreatedAt: scene.RewardTime + consts.DaySec,
CampaignID: lastPlayer.CampaignID,
UserID: lastPlayer.UserID,
CoinID: lastPlayer.ID,
SceneID: lastPlayer.SceneID,
Type: mtype.FissionCoinLogTypeAdd,
Source: mtype.FissionCoinLogSourceGetExpired,
Val: val,
}))
if err != nil {
return
}
}
s.preSceneOk = mtype.Yes
return
}
// 如果这个用户上一场没结算,要拿来结算一下
if lastPlayer.Status == mtype.FissionCoinStatusJoin {
m := s.GetDao().FissionCoinScene
var preScene *model.FissionCoinScene
preScene, err = m.WithContext(s.Ctx).Where(m.ID.Eq(lastPlayer.SceneID)).Take()
if err = dbcheck.DbError(err); err != nil {
return nil, s.BE().MysqlError(err)
}
if preScene.RewardTime > time.Now().Unix() {
player = lastPlayer
s.nowScene = preScene
}
return
}
s.preSceneOk = mtype.Yes
return player, nil
}
func (s *Coin) _sceneCoin(sceneId int64, add int32, first bool) (num int64, err error) {
sceneData, err := s._sceneData(sceneId)
if err != nil {
return
}
if add > 0 {
_, _ = sceneData.Incr(cachecurd.CoinSceneJoinNum, 1)
if first {
_, _ = sceneData.Incr(cachecurd.CoinSceneJoinUserNum, 1)
}
return sceneData.Incr(cachecurd.CoinSceneUserCoin, int64(add))
}
return sceneData.GetInt64(cachecurd.CoinSceneUserCoin)
}
// 奖池金币总数,用户获得的金币数
func (s *Coin) _getCoin(sceneId int64, useNum int32) (userCoin, getCoin int64, err error) {
sceneData, err := s._sceneData(sceneId)
if err != nil {
return
}
userCoin, _ = sceneData.GetInt64(cachecurd.CoinSceneUserCoin)
joinNum, _ := sceneData.GetInt64(cachecurd.CoinSceneJoinNum)
getCoin = s._count(userCoin, joinNum, useNum)
return
}
func (s *Coin) _sceneData(sceneId int64) (sceneData *cachecurd.HMap, err error) {
conf, key := s.GetConf(), ckey.FissionCoinSceneData(sceneId)
return cachecurd.NewHMap(s.Ctx, key,
cachecurd.SetCoinSceneBuild(s.cpn.ID, sceneId),
cachecurd.SetHMapExp(time.Duration(conf.OpenDays+1)*consts.TimeDay),
)
}
func (s *Coin) _addLog(logs ...*model.FissionCoinLog) (err error) {
if len(logs) == 0 {
return
}
err = s.GetDao().FissionCoinLog.WithContext(s.Ctx).Create(logs...)
if err != nil {
err = s.BE().MysqlError(err)
return
}
return
}
func (s *Coin) _count(userCoin, joinNum int64, useNum int32) (getCoin int64) {
scene := s.GetNowScene()
var oneCoin int64
if joinNum > 0 || scene.InitUse > 0 {
oneCoin = (userCoin + int64(scene.InitCoin)) / (joinNum + int64(scene.InitUse))
if oneCoin == 0 {
oneCoin = 1
}
}
return int64(useNum) * oneCoin
}