diff --git a/logic/service/fight/boss/NewSeIdx_26.go b/logic/service/fight/boss/NewSeIdx_26.go index 83bbf1e9e..df68ea7ca 100644 --- a/logic/service/fight/boss/NewSeIdx_26.go +++ b/logic/service/fight/boss/NewSeIdx_26.go @@ -11,16 +11,9 @@ type NewSel26 struct { } func (e *NewSel26) TurnStart(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) { - if !e.IsOwner() { - return - } - e.Ctx().Our.CurPet[0].Info.Prop[int(e.Args()[0].IntPart())] += uint32(e.Args()[1].IntPart()) } + func (e *NewSel26) TurnEnd() { - if !e.IsOwner() { - return - } - e.Ctx().Our.CurPet[0].Info.Prop[int(e.Args()[0].IntPart())] -= uint32(e.Args()[1].IntPart()) } func init() { input.InitEffect(input.EffectType.NewSel, 26, &NewSel26{}) diff --git a/logic/service/fight/input/input.go b/logic/service/fight/input/input.go index 1b050c94a..472bfa1ee 100644 --- a/logic/service/fight/input/input.go +++ b/logic/service/fight/input/input.go @@ -145,6 +145,28 @@ func (our *Input) SetCurPetAt(index int, pet *info.BattlePetEntity) { our.CurPet[index] = pet } +func applyInitPetEffectBonus(pet *info.BattlePetEntity, effect model.PetEffectInfo) { + if pet == nil || effect.EID != 26 || len(effect.Args) < 2 { + return + } + + for i := 0; i+1 < len(effect.Args); i += 2 { + attr := effect.Args[i] + value := effect.Args[i+1] + if value <= 0 { + continue + } + + switch attr { + case 0: + pet.Info.MaxHp += uint32(value) + pet.Info.Hp += uint32(value) + case 1, 2, 3, 4, 5: + pet.Info.Prop[attr-1] += uint32(value) + } + } +} + // 非原地交换:收集非0血量精灵 + 0血量精灵,拼接后返回 func (our *Input) SortPet() { var nonZeroHP []*info.BattlePetEntity // 收集血量>0的精灵(保持原顺序) @@ -167,6 +189,7 @@ func (our *Input) SortPet() { t.Duration(-1) + applyInitPetEffectBonus(s, e1) our.AddEffect(our, t) } @@ -338,9 +361,6 @@ func (our *Input) Parseskill(skill *action.SelectSkillAction) { args := xmlres.EffectArgs[v] t := our.InitEffect(EffectType.Skill, v, temparg[:args]...) - - - //这里是给双方添加buff if t != nil { // t.SetArgs(our, temparg[:args]...) //设置入参,施加方永远是我方 diff --git a/logic/service/item/petuse.go b/logic/service/item/petuse.go index c2bff532a..518841510 100644 --- a/logic/service/item/petuse.go +++ b/logic/service/item/petuse.go @@ -14,6 +14,8 @@ import ( type PetItemHandler func(itemid uint32, ctx *model.PetInfo) bool +const maxHPUpEffectIdx uint16 = 60000 + // RangeHandler 范围ID处理器(用于ID区间匹配) type RangeHandler struct { Start uint32 @@ -114,7 +116,26 @@ func nvfunc(itemid uint32, onpet *model.PetInfo) bool { func handleNewSeIdxPetItem(itemid uint32, onpet *model.PetInfo) errorcode.ErrorCode { itemCfg, ok := xmlres.ItemsMAP[int(itemid)] - if !ok || itemCfg.NewSeIdx == 0 { + if !ok { + return errorcode.ErrorCodes.ErrItemUnusable + } + if itemCfg.NewSeIdx == 0 { + if itemCfg.MaxHPUp > 0 { + for _, eff := range onpet.EffectInfo { + if eff.Status == 8 { + return errorcode.ErrorCodes.ErrCannotInjectPillAgain + } + } + + onpet.EffectInfo = append(onpet.EffectInfo, model.PetEffectInfo{ + ItemID: itemid, + Idx: maxHPUpEffectIdx, + Status: 8, + EID: 26, + Args: []int{0, itemCfg.MaxHPUp}, + }) + return 0 + } return errorcode.ErrorCodes.ErrItemUnusable } diff --git a/modules/config/model/sign.go b/modules/config/model/sign.go index abbd0a238..1d43015fd 100644 --- a/modules/config/model/sign.go +++ b/modules/config/model/sign.go @@ -1,17 +1,20 @@ package model -import ( - "blazing/cool" -) +import "blazing/cool" const TableNameSignIn = "config_sign_in" -// SignIn 签到活动配置表。 +const ( + SignTypeTotal uint32 = 1 + SignTypeContinuous uint32 = 2 +) + +// SignIn 签到阶段配置表。 type SignIn struct { - *cool.Model - SignInID uint32 `gorm:"not null;index:idx_sign_in_id;comment:'签到活动ID'" json:"sign_in_id"` - Status uint32 `gorm:"not null;default:0;comment:'签到状态(0-未启用 1-启用)'" json:"status"` - RewardScript string `gorm:"type:varchar(2048);default:'';comment:'签到奖励配置(JSON)'" json:"reward_script"` + *BaseConfig + SignType uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage;comment:'签到类别(1-累计 2-连续)'" json:"sign_type"` + StageDays uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage;comment:'签到阶段天数(1/3/7/14/30)'" json:"stage_days"` + CdkID uint32 `gorm:"not null;uniqueIndex;comment:'绑定的CDK配置ID'" json:"cdk_id"` } func (*SignIn) TableName() string { @@ -23,7 +26,7 @@ func (*SignIn) GroupName() string { } func NewSignIn() *SignIn { - return &SignIn{Model: cool.NewModel()} + return &SignIn{BaseConfig: NewBaseConfig()} } func init() { diff --git a/modules/config/service/cdk.go b/modules/config/service/cdk.go index 9928f1d29..66465e219 100644 --- a/modules/config/service/cdk.go +++ b/modules/config/service/cdk.go @@ -8,14 +8,13 @@ import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/grand" "github.com/google/uuid" -) // 1. 扩展字符集:数字+大小写字母+安全符号(避开URL/输入易冲突的符号,如/、?、&) +) const charsetWithSymbol = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz" func Generate16CharSecure() string { result := make([]byte, 16) for i := 0; i < 16; i++ { - result[i] = charsetWithSymbol[grand.N(0, len(charsetWithSymbol)-1)] } return string(result) @@ -38,22 +37,30 @@ func NewCdkService() *CdkService { }, } } + func (s *CdkService) Get(id string) *model.CDKConfig { var item *model.CDKConfig dbm_notenable(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Scan(&item) - return item - } + +func (s *CdkService) GetByID(id uint32) *model.CDKConfig { + if id == 0 { + return nil + } + + var item *model.CDKConfig + dbm_notenable(s.Model).Where("id", id).Scan(&item) + return item +} + func (s *CdkService) All() []model.CDKConfig { var item []model.CDKConfig dbm_notenable(s.Model).WhereLT("exchange_remain_count", 0).Scan(&item) - return item - } -func (s *CdkService) Set(id string) bool { +func (s *CdkService) Set(id string) bool { res, err := cool.DBM(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Decrement("exchange_remain_count", 1) if err != nil { return false @@ -62,7 +69,5 @@ func (s *CdkService) Set(id string) bool { if rows == 0 { return false } - return true - } diff --git a/modules/config/service/sign.go b/modules/config/service/sign.go index ba3ba03e8..fa9b3f239 100644 --- a/modules/config/service/sign.go +++ b/modules/config/service/sign.go @@ -3,35 +3,77 @@ package service import ( "blazing/cool" "blazing/modules/config/model" + "context" + "fmt" + "sort" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" ) +var signStageDays = map[uint32]struct{}{ + 1: {}, + 3: {}, + 7: {}, + 14: {}, + 30: {}, +} + type SignInService struct { *cool.Service } -func (s *SignInService) GetActive(signInID uint32) *model.SignIn { - m := cool.DBM(s.Model) - if signInID != 0 { - m.Where("sign_in_id", signInID) - } - m.Where("status", 1) - m.Order("sign_in_id", "asc") - - var out *model.SignIn - m.Scan(&out) - return out -} - func NewSignInService() *SignInService { return &SignInService{ &cool.Service{ Model: model.NewSignIn(), - ListQueryOp: &cool.QueryOp{ - FieldEQ: []string{"sign_in_id", "status"}, - }, PageQueryOp: &cool.QueryOp{ - FieldEQ: []string{"sign_in_id", "status"}, + FieldEQ: []string{"sign_type", "stage_days", "cdk_id", "is_enable"}, + KeyWordField: []string{"remark"}, + }, + ListQueryOp: &cool.QueryOp{ + FieldEQ: []string{"sign_type", "stage_days", "cdk_id", "is_enable"}, }, }, } } + +func (s *SignInService) ModifyBefore(ctx context.Context, method string, param g.MapStrAny) (err error) { + if method == "Delete" { + return nil + } + + signType := gconv.Uint32(param["sign_type"]) + if signType != model.SignTypeTotal && signType != model.SignTypeContinuous { + return fmt.Errorf("签到类别非法,只支持1(累计)或2(连续)") + } + + stageDays := gconv.Uint32(param["stage_days"]) + if _, ok := signStageDays[stageDays]; !ok { + return fmt.Errorf("签到阶段仅支持1、3、7、14、30天") + } + + cdkID := gconv.Uint32(param["cdk_id"]) + if cdkID == 0 { + return fmt.Errorf("cdk_id不能为空") + } + if NewCdkService().GetByID(cdkID) == nil { + return fmt.Errorf("绑定的CDK不存在") + } + return nil +} + +func (s *SignInService) GetEnabled() []model.SignIn { + var items []model.SignIn + dbm_enable(s.Model).Scan(&items) + sort.Slice(items, func(i, j int) bool { + if items[i].SignType != items[j].SignType { + return items[i].SignType < items[j].SignType + } + if items[i].StageDays != items[j].StageDays { + return items[i].StageDays < items[j].StageDays + } + return items[i].CdkID < items[j].CdkID + }) + return items +} diff --git a/modules/player/controller/app/sign.go b/modules/player/controller/app/sign.go index ee5f73db7..7b0e3e801 100644 --- a/modules/player/controller/app/sign.go +++ b/modules/player/controller/app/sign.go @@ -28,17 +28,15 @@ func init() { } type SignStateReq struct { - g.Meta `path:"/state" method:"GET"` - UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"` - Session string `json:"session" v:"required#session不能为空"` - SignInID uint32 `json:"sign_in_id" d:"1" v:"min:1#签到活动ID非法"` + g.Meta `path:"/state" method:"GET"` + UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"` + Session string `json:"session" v:"required#session不能为空"` } type SignClaimReq struct { - g.Meta `path:"/claim" method:"POST"` - UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"` - Session string `json:"session" v:"required#session不能为空"` - SignInID uint32 `json:"sign_in_id" d:"1" v:"min:1#签到活动ID非法"` + g.Meta `path:"/claim" method:"POST"` + UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"` + Session string `json:"session" v:"required#session不能为空"` } func (c *SignController) State(ctx context.Context, req *SignStateReq) (res *cool.BaseRes, err error) { @@ -49,7 +47,7 @@ func (c *SignController) State(ctx context.Context, req *SignStateReq) (res *coo return cool.Fail(err.Error()), nil } - state, err := playerservice.NewSignService(req.UserID).GetState(req.SignInID) + state, err := playerservice.NewSignService(req.UserID).GetState() if err != nil { return cool.Fail(err.Error()), nil } @@ -64,7 +62,7 @@ func (c *SignController) Claim(ctx context.Context, req *SignClaimReq) (res *coo return cool.Fail(err.Error()), nil } - result, err := playerservice.NewSignService(req.UserID).Claim(req.SignInID) + result, err := playerservice.NewSignService(req.UserID).Claim() if err != nil { return cool.Fail(err.Error()), nil } diff --git a/modules/player/service/sign.go b/modules/player/service/sign.go index faf5f54a1..577359015 100644 --- a/modules/player/service/sign.go +++ b/modules/player/service/sign.go @@ -4,63 +4,39 @@ import ( "blazing/common/data" "blazing/cool" baseservice "blazing/modules/base/service" + configmodel "blazing/modules/config/model" configservice "blazing/modules/config/service" "blazing/modules/player/model" - "context" - "encoding/json" - "errors" "fmt" "sort" - "strconv" - "strings" "time" - - "github.com/pointernil/bitset32" ) -// SignRewardItem 定义单日签到奖励中的物品条目。 -type SignRewardItem struct { - ItemID uint32 `json:"item_id"` - Count int64 `json:"count"` +const signRecordID uint32 = 1 + +// SignStageState 表示一个签到阶段的当前状态。 +type SignStageState struct { + SignType uint32 `json:"sign_type"` + StageDays uint32 `json:"stage_days"` + CdkID uint32 `json:"cdk_id"` + Reached bool `json:"reached"` + Claimed bool `json:"claimed"` } -// SignRewardDay 定义单日签到奖励。 -type SignRewardDay struct { - Day uint32 `json:"day"` - GiftItemIDs []uint32 `json:"gift_item_ids,omitempty"` - Items []SignRewardItem `json:"items,omitempty"` - PetRewardIDs []uint32 `json:"pet_reward_ids,omitempty"` - TitleRewardIDs []uint32 `json:"title_reward_ids,omitempty"` - Coins int64 `json:"coins,omitempty"` - Gold int64 `json:"gold,omitempty"` - FreeGold int64 `json:"free_gold,omitempty"` - ExpPool int64 `json:"exp_pool,omitempty"` - EVPool int64 `json:"ev_pool,omitempty"` -} - -// SignRewardPreview 用于返回签到面板里的奖励和领取状态。 -type SignRewardPreview struct { - SignRewardDay - Signed bool `json:"signed"` -} - -// SignState 表示玩家当前活动的签到状态。 +// SignState 表示玩家当前签到进度和阶段状态。 type SignState struct { - SignInID uint32 `json:"sign_in_id"` - Status uint32 `json:"status"` - TotalDays uint32 `json:"total_days"` - ContinuousDays uint32 `json:"continuous_days"` - LastSignDate string `json:"last_sign_date"` - TodaySigned bool `json:"today_signed"` - Completed bool `json:"completed"` - CanSignToday bool `json:"can_sign_today"` - NextSignDay uint32 `json:"next_sign_day"` - SignedDays []uint32 `json:"signed_days"` - Rewards []SignRewardPreview `json:"rewards"` + TotalDays uint32 `json:"total_days"` + ContinuousDays uint32 `json:"continuous_days"` + LastSignDate string `json:"last_sign_date"` + TodaySigned bool `json:"today_signed"` + Stages []SignStageState `json:"stages"` } -// SignGrantResult 表示本次签到实际发放的奖励。 -type SignGrantResult struct { +// SignRewardResult 表示一次签到后自动发放的阶段奖励。 +type SignRewardResult struct { + SignType uint32 `json:"sign_type"` + StageDays uint32 `json:"stage_days"` + CdkID uint32 `json:"cdk_id"` Items []data.ItemInfo `json:"items,omitempty"` PetIDs []uint32 `json:"pet_ids,omitempty"` TitleIDs []uint32 `json:"title_ids,omitempty"` @@ -69,17 +45,12 @@ type SignGrantResult struct { FreeGold int64 `json:"free_gold,omitempty"` ExpPool int64 `json:"exp_pool,omitempty"` EVPool int64 `json:"ev_pool,omitempty"` - RewardDay uint32 `json:"reward_day"` } // SignClaimResult 表示签到后的完整结果。 type SignClaimResult struct { - State *SignState `json:"state"` - Reward *SignGrantResult `json:"reward"` -} - -type signRewardPayload struct { - Days []SignRewardDay `json:"days"` + State *SignState `json:"state"` + Rewards []SignRewardResult `json:"rewards,omitempty"` } // SignService 管理玩家签到进度。 @@ -94,53 +65,26 @@ func NewSignService(id uint32) *SignService { Service: &cool.Service{ Model: model.NewSignInRecord(), ListQueryOp: &cool.QueryOp{ - FieldEQ: []string{"player_id", "sign_in_id", "is_completed"}, + FieldEQ: []string{"player_id", "is_completed"}, }, PageQueryOp: &cool.QueryOp{ - FieldEQ: []string{"player_id", "sign_in_id", "is_completed"}, + FieldEQ: []string{"player_id", "is_completed"}, }, }, }, } } -func (s *SignService) GetState(signInID uint32) (*SignState, error) { - cfg := configservice.NewSignInService().GetActive(signInID) - if cfg == nil { - return nil, fmt.Errorf("签到活动不存在或未启用") - } - - rewards, err := parseRewardDays(cfg.RewardScript) +func (s *SignService) GetState() (*SignState, error) { + record, err := s.getRecord() if err != nil { return nil, err } - if len(rewards) == 0 { - return nil, fmt.Errorf("签到活动未配置奖励") - } - - record, err := s.getRecord(cfg.SignInID) - if err != nil { - return nil, err - } - - return buildSignState(cfg.SignInID, cfg.Status, rewards, record), nil + return s.buildState(record), nil } -func (s *SignService) Claim(signInID uint32) (*SignClaimResult, error) { - cfg := configservice.NewSignInService().GetActive(signInID) - if cfg == nil { - return nil, fmt.Errorf("签到活动不存在或未启用") - } - - rewards, err := parseRewardDays(cfg.RewardScript) - if err != nil { - return nil, err - } - if len(rewards) == 0 { - return nil, fmt.Errorf("签到活动未配置奖励") - } - - record, isNew, err := s.getOrInitRecord(cfg.SignInID) +func (s *SignService) Claim() (*SignClaimResult, error) { + record, isNew, err := s.getOrInitRecord() if err != nil { return nil, err } @@ -150,53 +94,210 @@ func (s *SignService) Claim(signInID uint32) (*SignClaimResult, error) { return nil, fmt.Errorf("今天已经签到过了") } - nextDay, reward := nextRewardDay(rewards, record) - if reward == nil || nextDay == 0 { - return nil, fmt.Errorf("当前签到活动已全部完成") - } - prevDate := record.LastSignDate - progress := progressBitset(record.SignInProgress) - progress.Set(uint(nextDay - 1)) - record.SignInProgress = progress.Bytes() - record.TotalDays = uint32(progress.Count()) record.LastSignDate = today + record.TotalDays++ if isYesterday(prevDate, today) { record.ContinuousDays++ } else { record.ContinuousDays = 1 } - _, pendingReward := nextRewardDay(rewards, record) - record.IsCompleted = pendingReward == nil + rewards, err := s.grantReachedStageRewards(record) + if err != nil { + return nil, err + } if err := s.saveRecord(record, isNew); err != nil { return nil, err } - grant, err := s.applyReward(*reward) - if err != nil { - cool.Logger.Error(context.TODO(), "sign reward apply failed", s.userid, cfg.SignInID, nextDay, err) - return nil, err - } - grant.RewardDay = nextDay - - state := buildSignState(cfg.SignInID, cfg.Status, rewards, record) - return &SignClaimResult{State: state, Reward: grant}, nil + return &SignClaimResult{ + State: s.buildState(record), + Rewards: rewards, + }, nil } -func (s *SignService) getRecord(signInID uint32) (*model.SignInRecord, error) { - var out *model.SignInRecord - if err := s.dbm(s.Model).Where("sign_in_id", signInID).Scan(&out); err != nil { - return nil, err +func (s *SignService) grantReachedStageRewards(record *model.SignInRecord) ([]SignRewardResult, error) { + configs := configservice.NewSignInService().GetEnabled() + if len(configs) == 0 { + return nil, nil } - if out != nil && out.SignInProgress == nil { - out.SignInProgress = []uint32{} + + cdkLogService := NewCdkService(s.userid) + infoService := NewInfoService(s.userid) + playerInfo := infoService.GetLogin() + if playerInfo == nil { + return nil, fmt.Errorf("玩家角色不存在") + } + + infoDirty := false + results := make([]SignRewardResult, 0) + for _, cfg := range configs { + if !stageReached(cfg.SignType, cfg.StageDays, record) { + continue + } + if !cdkLogService.CanGet(cfg.CdkID) { + continue + } + + reward, changed, err := s.applyCdkReward(cfg.CdkID, playerInfo) + if err != nil { + return nil, err + } + reward.SignType = cfg.SignType + reward.StageDays = cfg.StageDays + reward.CdkID = cfg.CdkID + results = append(results, *reward) + if changed { + infoDirty = true + } + cdkLogService.Log(cfg.CdkID) + } + + if infoDirty { + infoService.Save(*playerInfo) + } + + sort.Slice(results, func(i, j int) bool { + if results[i].SignType != results[j].SignType { + return results[i].SignType < results[j].SignType + } + if results[i].StageDays != results[j].StageDays { + return results[i].StageDays < results[j].StageDays + } + return results[i].CdkID < results[j].CdkID + }) + return results, nil +} + +func (s *SignService) applyCdkReward(cdkID uint32, playerInfo *model.PlayerInfo) (*SignRewardResult, bool, error) { + cfg := configservice.NewCdkService().GetByID(cdkID) + if cfg == nil { + return nil, false, fmt.Errorf("绑定的CDK不存在") + } + if cfg.BindUserId != 0 && cfg.BindUserId != s.userid { + return nil, false, fmt.Errorf("CDK已绑定其他用户") + } + if !cfg.ValidEndTime.IsZero() && cfg.ValidEndTime.Before(time.Now()) { + return nil, false, fmt.Errorf("绑定的CDK已过期") + } + + result := &SignRewardResult{} + var ( + infoDirty bool + bagItems []data.ItemInfo + ) + + appendRewardItem := func(itemID uint32, count int64) { + if itemID == 0 || count <= 0 { + return + } + switch itemID { + case 1: + result.Coins += count + playerInfo.Coins += count + infoDirty = true + case 3: + result.ExpPool += count + playerInfo.ExpPool += count + infoDirty = true + case 5: + result.Gold += count + case 9: + result.EVPool += count + playerInfo.EVPool += count + infoDirty = true + default: + bagItems = append(bagItems, data.ItemInfo{ItemId: int64(itemID), ItemCnt: count}) + } + } + + for _, rewardID := range cfg.ItemRewardIds { + itemInfo := configservice.NewItemService().GetItemCount(rewardID) + appendRewardItem(uint32(itemInfo.ItemId), itemInfo.ItemCnt) + } + + if result.Gold != 0 { + baseservice.NewBaseSysUserService().UpdateGold(s.userid, result.Gold*100) + } + if result.FreeGold != 0 { + baseservice.NewBaseSysUserService().UpdateFreeGold(s.userid, result.FreeGold*100) + } + if len(bagItems) > 0 { + items, err := NewItemService(s.userid).AddItems(bagItems) + if err != nil { + return nil, false, err + } + result.Items = items + } + + for _, rewardID := range cfg.ElfRewardIds { + pet := configservice.NewPetRewardService().Get(rewardID) + if pet == nil { + continue + } + petInfo := model.GenPetInfo(int(pet.MonID), int(pet.DV), int(pet.Nature), int(pet.Effect), int(pet.Lv), nil, 0) + if _, err := NewPetService(s.userid).PetAdd(petInfo, 0); err != nil { + return nil, false, err + } + result.PetIDs = append(result.PetIDs, uint32(pet.MonID)) + } + + if cfg.TitleRewardIds != 0 { + NewTitleService(s.userid).Give(cfg.TitleRewardIds) + result.TitleIDs = append(result.TitleIDs, cfg.TitleRewardIds) + } + + return result, infoDirty, nil +} + +func (s *SignService) buildState(record *model.SignInRecord) *SignState { + state := &SignState{ + Stages: make([]SignStageState, 0), + } + if record != nil { + state.TotalDays = record.TotalDays + state.ContinuousDays = record.ContinuousDays + state.LastSignDate = record.LastSignDate + state.TodaySigned = record.LastSignDate == currentDateString() + } + + cdkLogService := NewCdkService(s.userid) + configs := configservice.NewSignInService().GetEnabled() + for _, cfg := range configs { + state.Stages = append(state.Stages, SignStageState{ + SignType: cfg.SignType, + StageDays: cfg.StageDays, + CdkID: cfg.CdkID, + Reached: stageReached(cfg.SignType, cfg.StageDays, record), + Claimed: !cdkLogService.CanGet(cfg.CdkID), + }) + } + return state +} + +func stageReached(signType, stageDays uint32, record *model.SignInRecord) bool { + if record == nil || stageDays == 0 { + return false + } + switch signType { + case configmodel.SignTypeContinuous: + return record.ContinuousDays >= stageDays + default: + return record.TotalDays >= stageDays + } +} + +func (s *SignService) getRecord() (*model.SignInRecord, error) { + var out *model.SignInRecord + if err := s.dbm(s.Model).Where("sign_in_id", signRecordID).Scan(&out); err != nil { + return nil, err } return out, nil } -func (s *SignService) getOrInitRecord(signInID uint32) (*model.SignInRecord, bool, error) { - record, err := s.getRecord(signInID) +func (s *SignService) getOrInitRecord() (*model.SignInRecord, bool, error) { + record, err := s.getRecord() if err != nil { return nil, false, err } @@ -210,7 +311,7 @@ func (s *SignService) getOrInitRecord(signInID uint32) (*model.SignInRecord, boo IsVip: cool.Config.ServerInfo.IsVip, }, PlayerID: s.userid, - SignInID: signInID, + SignInID: signRecordID, IsCompleted: false, ContinuousDays: 0, TotalDays: 0, @@ -220,320 +321,24 @@ func (s *SignService) getOrInitRecord(signInID uint32) (*model.SignInRecord, boo } func (s *SignService) saveRecord(record *model.SignInRecord, isNew bool) error { - if record == nil { - return errors.New("签到记录为空") - } - data := map[string]any{ "player_id": record.PlayerID, "sign_in_id": record.SignInID, - "is_completed": record.IsCompleted, + "is_completed": false, "continuous_days": record.ContinuousDays, "total_days": record.TotalDays, "last_sign_date": record.LastSignDate, - "sign_in_progress": record.SignInProgress, + "sign_in_progress": []uint32{}, "is_vip": cool.Config.ServerInfo.IsVip, } - if isNew { _, err := cool.DBM(s.Model).Data(data).Insert() return err } - - _, err := s.dbm(s.Model).Where("sign_in_id", record.SignInID).Data(data).Update() + _, err := s.dbm(s.Model).Where("sign_in_id", signRecordID).Data(data).Update() return err } -func (s *SignService) applyReward(reward SignRewardDay) (*SignGrantResult, error) { - result := &SignGrantResult{} - infoService := NewInfoService(s.userid) - playerInfo := infoService.GetLogin() - if playerInfo == nil { - return nil, fmt.Errorf("玩家角色不存在") - } - - var ( - needSaveInfo bool - bagItems []data.ItemInfo - ) - - appendRewardItem := func(itemID uint32, count int64) { - if itemID == 0 || count <= 0 { - return - } - switch itemID { - case 1: - result.Coins += count - case 3: - result.ExpPool += count - case 5: - result.Gold += count - case 9: - result.EVPool += count - default: - bagItems = append(bagItems, data.ItemInfo{ItemId: int64(itemID), ItemCnt: count}) - } - } - - for _, giftID := range reward.GiftItemIDs { - gift := configservice.NewItemService().GetItemCount(giftID) - appendRewardItem(uint32(gift.ItemId), gift.ItemCnt) - } - for _, item := range reward.Items { - appendRewardItem(item.ItemID, item.Count) - } - - result.Coins += reward.Coins - result.Gold += reward.Gold - result.FreeGold += reward.FreeGold - result.ExpPool += reward.ExpPool - result.EVPool += reward.EVPool - - if result.Coins != 0 { - playerInfo.Coins += result.Coins - needSaveInfo = true - } - if result.ExpPool != 0 { - playerInfo.ExpPool += result.ExpPool - needSaveInfo = true - } - if result.EVPool != 0 { - playerInfo.EVPool += result.EVPool - needSaveInfo = true - } - if needSaveInfo { - infoService.Save(*playerInfo) - } - - if result.Gold != 0 { - baseservice.NewBaseSysUserService().UpdateGold(s.userid, result.Gold*100) - } - if result.FreeGold != 0 { - baseservice.NewBaseSysUserService().UpdateFreeGold(s.userid, result.FreeGold*100) - } - - if len(bagItems) > 0 { - items, err := NewItemService(s.userid).AddItems(bagItems) - if err != nil { - return nil, err - } - result.Items = items - } - - for _, petRewardID := range reward.PetRewardIDs { - cfg := configservice.NewPetRewardService().Get(petRewardID) - if cfg == nil || cfg.MonID == 0 { - continue - } - petInfo := model.GenPetInfo(int(cfg.MonID), int(cfg.DV), int(cfg.Nature), int(cfg.Effect), int(cfg.Lv), nil, 0) - if _, err := NewPetService(s.userid).PetAdd(petInfo, 0); err != nil { - return nil, err - } - result.PetIDs = append(result.PetIDs, uint32(cfg.MonID)) - } - - for _, titleID := range reward.TitleRewardIDs { - if titleID == 0 { - continue - } - NewTitleService(s.userid).Give(titleID) - result.TitleIDs = append(result.TitleIDs, titleID) - } - - return result, nil -} - -func buildSignState(signInID, status uint32, rewards []SignRewardDay, record *model.SignInRecord) *SignState { - today := currentDateString() - progress := progressBitset(nil) - state := &SignState{ - SignInID: signInID, - Status: status, - } - - if record != nil { - progress = progressBitset(record.SignInProgress) - state.ContinuousDays = record.ContinuousDays - state.LastSignDate = record.LastSignDate - state.TodaySigned = record.LastSignDate == today - state.Completed = record.IsCompleted - } - - state.SignedDays = signedDays(progress) - state.TotalDays = uint32(len(state.SignedDays)) - if record != nil && record.TotalDays > state.TotalDays { - state.TotalDays = record.TotalDays - } - - state.Rewards = make([]SignRewardPreview, 0, len(rewards)) - for _, reward := range rewards { - state.Rewards = append(state.Rewards, SignRewardPreview{ - SignRewardDay: reward, - Signed: progress.Test(uint(reward.Day - 1)), - }) - } - - nextDay, _ := nextRewardDay(rewards, record) - state.NextSignDay = nextDay - if len(rewards) > 0 && nextDay == 0 { - state.Completed = true - } - state.CanSignToday = !state.TodaySigned && !state.Completed && nextDay != 0 - return state -} - -func nextRewardDay(rewards []SignRewardDay, record *model.SignInRecord) (uint32, *SignRewardDay) { - progress := progressBitset(nil) - if record != nil { - progress = progressBitset(record.SignInProgress) - } - for i := range rewards { - if rewards[i].Day == 0 { - continue - } - if !progress.Test(uint(rewards[i].Day - 1)) { - return rewards[i].Day, &rewards[i] - } - } - return 0, nil -} - -func parseRewardDays(raw string) ([]SignRewardDay, error) { - raw = strings.TrimSpace(raw) - if raw == "" { - return nil, nil - } - - var payload signRewardPayload - if err := json.Unmarshal([]byte(raw), &payload); err == nil && len(payload.Days) > 0 { - return normalizeRewardDays(payload.Days), nil - } - - var list []SignRewardDay - if err := json.Unmarshal([]byte(raw), &list); err == nil && len(list) > 0 { - return normalizeRewardDays(list), nil - } - - var dayMap map[string]SignRewardDay - if err := json.Unmarshal([]byte(raw), &dayMap); err == nil && len(dayMap) > 0 { - list = make([]SignRewardDay, 0, len(dayMap)) - for key, reward := range dayMap { - if reward.Day == 0 { - day, convErr := strconv.ParseUint(key, 10, 32) - if convErr != nil { - return nil, fmt.Errorf("签到奖励配置中的 day 非法: %s", key) - } - reward.Day = uint32(day) - } - list = append(list, reward) - } - return normalizeRewardDays(list), nil - } - - return nil, fmt.Errorf("签到奖励配置格式不支持") -} - -func normalizeRewardDays(input []SignRewardDay) []SignRewardDay { - merged := make(map[uint32]*SignRewardDay) - for _, reward := range input { - if reward.Day == 0 { - continue - } - current, ok := merged[reward.Day] - if !ok { - copyReward := reward - copyReward.GiftItemIDs = append([]uint32{}, reward.GiftItemIDs...) - copyReward.Items = append([]SignRewardItem{}, reward.Items...) - copyReward.PetRewardIDs = append([]uint32{}, reward.PetRewardIDs...) - copyReward.TitleRewardIDs = append([]uint32{}, reward.TitleRewardIDs...) - merged[reward.Day] = ©Reward - continue - } - current.GiftItemIDs = append(current.GiftItemIDs, reward.GiftItemIDs...) - current.Items = append(current.Items, reward.Items...) - current.PetRewardIDs = append(current.PetRewardIDs, reward.PetRewardIDs...) - current.TitleRewardIDs = append(current.TitleRewardIDs, reward.TitleRewardIDs...) - current.Coins += reward.Coins - current.Gold += reward.Gold - current.FreeGold += reward.FreeGold - current.ExpPool += reward.ExpPool - current.EVPool += reward.EVPool - } - - days := make([]uint32, 0, len(merged)) - for day := range merged { - days = append(days, day) - } - sort.Slice(days, func(i, j int) bool { return days[i] < days[j] }) - - result := make([]SignRewardDay, 0, len(days)) - for _, day := range days { - reward := merged[day] - reward.GiftItemIDs = uniqueUint32(reward.GiftItemIDs) - reward.PetRewardIDs = uniqueUint32(reward.PetRewardIDs) - reward.TitleRewardIDs = uniqueUint32(reward.TitleRewardIDs) - reward.Items = normalizeRewardItems(reward.Items) - result = append(result, *reward) - } - return result -} - -func normalizeRewardItems(items []SignRewardItem) []SignRewardItem { - merged := make(map[uint32]int64) - for _, item := range items { - if item.ItemID == 0 || item.Count <= 0 { - continue - } - merged[item.ItemID] += item.Count - } - ids := make([]uint32, 0, len(merged)) - for itemID := range merged { - ids = append(ids, itemID) - } - sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) - - result := make([]SignRewardItem, 0, len(ids)) - for _, itemID := range ids { - result = append(result, SignRewardItem{ItemID: itemID, Count: merged[itemID]}) - } - return result -} - -func uniqueUint32(values []uint32) []uint32 { - seen := make(map[uint32]struct{}, len(values)) - result := make([]uint32, 0, len(values)) - for _, value := range values { - if value == 0 { - continue - } - if _, ok := seen[value]; ok { - continue - } - seen[value] = struct{}{} - result = append(result, value) - } - sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) - return result -} - -func progressBitset(progress []uint32) *bitset32.BitSet32 { - if len(progress) == 0 { - return bitset32.New(0) - } - return bitset32.From(progress) -} - -func signedDays(progress *bitset32.BitSet32) []uint32 { - if progress == nil { - return []uint32{} - } - result := make([]uint32, 0, progress.Count()) - for idx, ok := progress.NextSet(0); ok; idx, ok = progress.NextSet(idx + 1) { - result = append(result, uint32(idx+1)) - } - return result -} - func currentDateString() string { return time.Now().Format("2006-01-02") } diff --git a/modules/player/service/sign_test.go b/modules/player/service/sign_test.go index f560cb9cd..6d43c3366 100644 --- a/modules/player/service/sign_test.go +++ b/modules/player/service/sign_test.go @@ -1,60 +1 @@ package service - -import ( - "blazing/modules/player/model" - "testing" - - "github.com/pointernil/bitset32" -) - -func TestParseRewardDays(t *testing.T) { - rewards, err := parseRewardDays(`{"days":[{"day":2,"coins":10,"items":[{"item_id":1001,"count":1}],"gift_item_ids":[8]},{"day":1,"gold":2},{"day":2,"coins":5,"items":[{"item_id":1001,"count":2}]}]}`) - if err != nil { - t.Fatalf("parseRewardDays returned error: %v", err) - } - if len(rewards) != 2 { - t.Fatalf("expected 2 reward days, got %d", len(rewards)) - } - if rewards[0].Day != 1 || rewards[0].Gold != 2 { - t.Fatalf("unexpected first reward: %+v", rewards[0]) - } - if rewards[1].Day != 2 { - t.Fatalf("unexpected second reward day: %+v", rewards[1]) - } - if rewards[1].Coins != 15 { - t.Fatalf("expected merged coins to be 15, got %d", rewards[1].Coins) - } - if len(rewards[1].Items) != 1 || rewards[1].Items[0].Count != 3 { - t.Fatalf("expected merged item count to be 3, got %+v", rewards[1].Items) - } -} - -func TestParseRewardDaysMapFormat(t *testing.T) { - rewards, err := parseRewardDays(`{"1":{"coins":1},"3":{"ev_pool":9}}`) - if err != nil { - t.Fatalf("parseRewardDays returned error: %v", err) - } - if len(rewards) != 2 { - t.Fatalf("expected 2 reward days, got %d", len(rewards)) - } - if rewards[0].Day != 1 || rewards[0].Coins != 1 { - t.Fatalf("unexpected day 1 reward: %+v", rewards[0]) - } - if rewards[1].Day != 3 || rewards[1].EVPool != 9 { - t.Fatalf("unexpected day 3 reward: %+v", rewards[1]) - } -} - -func TestNextRewardDay(t *testing.T) { - rewards := []SignRewardDay{{Day: 1}, {Day: 2}, {Day: 3}} - progress := bitset32.New(0).Set(0).Set(1) - record := &model.SignInRecord{SignInProgress: progress.Bytes()} - - day, reward := nextRewardDay(rewards, record) - if reward == nil { - t.Fatal("expected next reward, got nil") - } - if day != 3 || reward.Day != 3 { - t.Fatalf("expected next day to be 3, got day=%d reward=%+v", day, reward) - } -}