refactor: 重构签到系统和战斗特效逻辑
This commit is contained in:
@@ -11,16 +11,9 @@ type NewSel26 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *NewSel26) TurnStart(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) {
|
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() {
|
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() {
|
func init() {
|
||||||
input.InitEffect(input.EffectType.NewSel, 26, &NewSel26{})
|
input.InitEffect(input.EffectType.NewSel, 26, &NewSel26{})
|
||||||
|
|||||||
@@ -145,6 +145,28 @@ func (our *Input) SetCurPetAt(index int, pet *info.BattlePetEntity) {
|
|||||||
our.CurPet[index] = pet
|
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血量精灵,拼接后返回
|
// 非原地交换:收集非0血量精灵 + 0血量精灵,拼接后返回
|
||||||
func (our *Input) SortPet() {
|
func (our *Input) SortPet() {
|
||||||
var nonZeroHP []*info.BattlePetEntity // 收集血量>0的精灵(保持原顺序)
|
var nonZeroHP []*info.BattlePetEntity // 收集血量>0的精灵(保持原顺序)
|
||||||
@@ -167,6 +189,7 @@ func (our *Input) SortPet() {
|
|||||||
|
|
||||||
t.Duration(-1)
|
t.Duration(-1)
|
||||||
|
|
||||||
|
applyInitPetEffectBonus(s, e1)
|
||||||
our.AddEffect(our, t)
|
our.AddEffect(our, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,9 +361,6 @@ func (our *Input) Parseskill(skill *action.SelectSkillAction) {
|
|||||||
args := xmlres.EffectArgs[v]
|
args := xmlres.EffectArgs[v]
|
||||||
t := our.InitEffect(EffectType.Skill, v, temparg[:args]...)
|
t := our.InitEffect(EffectType.Skill, v, temparg[:args]...)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//这里是给双方添加buff
|
//这里是给双方添加buff
|
||||||
if t != nil {
|
if t != nil {
|
||||||
// t.SetArgs(our, temparg[:args]...) //设置入参,施加方永远是我方
|
// t.SetArgs(our, temparg[:args]...) //设置入参,施加方永远是我方
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
|
|
||||||
type PetItemHandler func(itemid uint32, ctx *model.PetInfo) bool
|
type PetItemHandler func(itemid uint32, ctx *model.PetInfo) bool
|
||||||
|
|
||||||
|
const maxHPUpEffectIdx uint16 = 60000
|
||||||
|
|
||||||
// RangeHandler 范围ID处理器(用于ID区间匹配)
|
// RangeHandler 范围ID处理器(用于ID区间匹配)
|
||||||
type RangeHandler struct {
|
type RangeHandler struct {
|
||||||
Start uint32
|
Start uint32
|
||||||
@@ -114,7 +116,26 @@ func nvfunc(itemid uint32, onpet *model.PetInfo) bool {
|
|||||||
|
|
||||||
func handleNewSeIdxPetItem(itemid uint32, onpet *model.PetInfo) errorcode.ErrorCode {
|
func handleNewSeIdxPetItem(itemid uint32, onpet *model.PetInfo) errorcode.ErrorCode {
|
||||||
itemCfg, ok := xmlres.ItemsMAP[int(itemid)]
|
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
|
return errorcode.ErrorCodes.ErrItemUnusable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import "blazing/cool"
|
||||||
"blazing/cool"
|
|
||||||
)
|
|
||||||
|
|
||||||
const TableNameSignIn = "config_sign_in"
|
const TableNameSignIn = "config_sign_in"
|
||||||
|
|
||||||
// SignIn 签到活动配置表。
|
const (
|
||||||
|
SignTypeTotal uint32 = 1
|
||||||
|
SignTypeContinuous uint32 = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// SignIn 签到阶段配置表。
|
||||||
type SignIn struct {
|
type SignIn struct {
|
||||||
*cool.Model
|
*BaseConfig
|
||||||
SignInID uint32 `gorm:"not null;index:idx_sign_in_id;comment:'签到活动ID'" json:"sign_in_id"`
|
SignType uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage;comment:'签到类别(1-累计 2-连续)'" json:"sign_type"`
|
||||||
Status uint32 `gorm:"not null;default:0;comment:'签到状态(0-未启用 1-启用)'" json:"status"`
|
StageDays uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage;comment:'签到阶段天数(1/3/7/14/30)'" json:"stage_days"`
|
||||||
RewardScript string `gorm:"type:varchar(2048);default:'';comment:'签到奖励配置(JSON)'" json:"reward_script"`
|
CdkID uint32 `gorm:"not null;uniqueIndex;comment:'绑定的CDK配置ID'" json:"cdk_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SignIn) TableName() string {
|
func (*SignIn) TableName() string {
|
||||||
@@ -23,7 +26,7 @@ func (*SignIn) GroupName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSignIn() *SignIn {
|
func NewSignIn() *SignIn {
|
||||||
return &SignIn{Model: cool.NewModel()}
|
return &SignIn{BaseConfig: NewBaseConfig()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -8,14 +8,13 @@ import (
|
|||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/util/grand"
|
"github.com/gogf/gf/v2/util/grand"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
) // 1. 扩展字符集:数字+大小写字母+安全符号(避开URL/输入易冲突的符号,如/、?、&)
|
)
|
||||||
|
|
||||||
const charsetWithSymbol = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"
|
const charsetWithSymbol = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz"
|
||||||
|
|
||||||
func Generate16CharSecure() string {
|
func Generate16CharSecure() string {
|
||||||
result := make([]byte, 16)
|
result := make([]byte, 16)
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
|
|
||||||
result[i] = charsetWithSymbol[grand.N(0, len(charsetWithSymbol)-1)]
|
result[i] = charsetWithSymbol[grand.N(0, len(charsetWithSymbol)-1)]
|
||||||
}
|
}
|
||||||
return string(result)
|
return string(result)
|
||||||
@@ -38,22 +37,30 @@ func NewCdkService() *CdkService {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CdkService) Get(id string) *model.CDKConfig {
|
func (s *CdkService) Get(id string) *model.CDKConfig {
|
||||||
var item *model.CDKConfig
|
var item *model.CDKConfig
|
||||||
dbm_notenable(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Scan(&item)
|
dbm_notenable(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Scan(&item)
|
||||||
|
|
||||||
return 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 {
|
func (s *CdkService) All() []model.CDKConfig {
|
||||||
var item []model.CDKConfig
|
var item []model.CDKConfig
|
||||||
dbm_notenable(s.Model).WhereLT("exchange_remain_count", 0).Scan(&item)
|
dbm_notenable(s.Model).WhereLT("exchange_remain_count", 0).Scan(&item)
|
||||||
|
|
||||||
return 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)
|
res, err := cool.DBM(s.Model).Where("cdk_code", id).WhereNot("exchange_remain_count", 0).Decrement("exchange_remain_count", 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
@@ -62,7 +69,5 @@ func (s *CdkService) Set(id string) bool {
|
|||||||
if rows == 0 {
|
if rows == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,35 +3,77 @@ package service
|
|||||||
import (
|
import (
|
||||||
"blazing/cool"
|
"blazing/cool"
|
||||||
"blazing/modules/config/model"
|
"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 {
|
type SignInService struct {
|
||||||
*cool.Service
|
*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 {
|
func NewSignInService() *SignInService {
|
||||||
return &SignInService{
|
return &SignInService{
|
||||||
&cool.Service{
|
&cool.Service{
|
||||||
Model: model.NewSignIn(),
|
Model: model.NewSignIn(),
|
||||||
ListQueryOp: &cool.QueryOp{
|
|
||||||
FieldEQ: []string{"sign_in_id", "status"},
|
|
||||||
},
|
|
||||||
PageQueryOp: &cool.QueryOp{
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,17 +28,15 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignStateReq struct {
|
type SignStateReq struct {
|
||||||
g.Meta `path:"/state" method:"GET"`
|
g.Meta `path:"/state" method:"GET"`
|
||||||
UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"`
|
UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"`
|
||||||
Session string `json:"session" v:"required#session不能为空"`
|
Session string `json:"session" v:"required#session不能为空"`
|
||||||
SignInID uint32 `json:"sign_in_id" d:"1" v:"min:1#签到活动ID非法"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignClaimReq struct {
|
type SignClaimReq struct {
|
||||||
g.Meta `path:"/claim" method:"POST"`
|
g.Meta `path:"/claim" method:"POST"`
|
||||||
UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"`
|
UserID uint32 `json:"user_id" v:"required|min:1#用户ID不能为空|用户ID非法"`
|
||||||
Session string `json:"session" v:"required#session不能为空"`
|
Session string `json:"session" v:"required#session不能为空"`
|
||||||
SignInID uint32 `json:"sign_in_id" d:"1" v:"min:1#签到活动ID非法"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SignController) State(ctx context.Context, req *SignStateReq) (res *cool.BaseRes, err error) {
|
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
|
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 {
|
if err != nil {
|
||||||
return cool.Fail(err.Error()), 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
|
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 {
|
if err != nil {
|
||||||
return cool.Fail(err.Error()), nil
|
return cool.Fail(err.Error()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,63 +4,39 @@ import (
|
|||||||
"blazing/common/data"
|
"blazing/common/data"
|
||||||
"blazing/cool"
|
"blazing/cool"
|
||||||
baseservice "blazing/modules/base/service"
|
baseservice "blazing/modules/base/service"
|
||||||
|
configmodel "blazing/modules/config/model"
|
||||||
configservice "blazing/modules/config/service"
|
configservice "blazing/modules/config/service"
|
||||||
"blazing/modules/player/model"
|
"blazing/modules/player/model"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pointernil/bitset32"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SignRewardItem 定义单日签到奖励中的物品条目。
|
const signRecordID uint32 = 1
|
||||||
type SignRewardItem struct {
|
|
||||||
ItemID uint32 `json:"item_id"`
|
// SignStageState 表示一个签到阶段的当前状态。
|
||||||
Count int64 `json:"count"`
|
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 定义单日签到奖励。
|
// SignState 表示玩家当前签到进度和阶段状态。
|
||||||
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 表示玩家当前活动的签到状态。
|
|
||||||
type SignState struct {
|
type SignState struct {
|
||||||
SignInID uint32 `json:"sign_in_id"`
|
TotalDays uint32 `json:"total_days"`
|
||||||
Status uint32 `json:"status"`
|
ContinuousDays uint32 `json:"continuous_days"`
|
||||||
TotalDays uint32 `json:"total_days"`
|
LastSignDate string `json:"last_sign_date"`
|
||||||
ContinuousDays uint32 `json:"continuous_days"`
|
TodaySigned bool `json:"today_signed"`
|
||||||
LastSignDate string `json:"last_sign_date"`
|
Stages []SignStageState `json:"stages"`
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignGrantResult 表示本次签到实际发放的奖励。
|
// SignRewardResult 表示一次签到后自动发放的阶段奖励。
|
||||||
type SignGrantResult struct {
|
type SignRewardResult struct {
|
||||||
|
SignType uint32 `json:"sign_type"`
|
||||||
|
StageDays uint32 `json:"stage_days"`
|
||||||
|
CdkID uint32 `json:"cdk_id"`
|
||||||
Items []data.ItemInfo `json:"items,omitempty"`
|
Items []data.ItemInfo `json:"items,omitempty"`
|
||||||
PetIDs []uint32 `json:"pet_ids,omitempty"`
|
PetIDs []uint32 `json:"pet_ids,omitempty"`
|
||||||
TitleIDs []uint32 `json:"title_ids,omitempty"`
|
TitleIDs []uint32 `json:"title_ids,omitempty"`
|
||||||
@@ -69,17 +45,12 @@ type SignGrantResult struct {
|
|||||||
FreeGold int64 `json:"free_gold,omitempty"`
|
FreeGold int64 `json:"free_gold,omitempty"`
|
||||||
ExpPool int64 `json:"exp_pool,omitempty"`
|
ExpPool int64 `json:"exp_pool,omitempty"`
|
||||||
EVPool int64 `json:"ev_pool,omitempty"`
|
EVPool int64 `json:"ev_pool,omitempty"`
|
||||||
RewardDay uint32 `json:"reward_day"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignClaimResult 表示签到后的完整结果。
|
// SignClaimResult 表示签到后的完整结果。
|
||||||
type SignClaimResult struct {
|
type SignClaimResult struct {
|
||||||
State *SignState `json:"state"`
|
State *SignState `json:"state"`
|
||||||
Reward *SignGrantResult `json:"reward"`
|
Rewards []SignRewardResult `json:"rewards,omitempty"`
|
||||||
}
|
|
||||||
|
|
||||||
type signRewardPayload struct {
|
|
||||||
Days []SignRewardDay `json:"days"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignService 管理玩家签到进度。
|
// SignService 管理玩家签到进度。
|
||||||
@@ -94,53 +65,26 @@ func NewSignService(id uint32) *SignService {
|
|||||||
Service: &cool.Service{
|
Service: &cool.Service{
|
||||||
Model: model.NewSignInRecord(),
|
Model: model.NewSignInRecord(),
|
||||||
ListQueryOp: &cool.QueryOp{
|
ListQueryOp: &cool.QueryOp{
|
||||||
FieldEQ: []string{"player_id", "sign_in_id", "is_completed"},
|
FieldEQ: []string{"player_id", "is_completed"},
|
||||||
},
|
},
|
||||||
PageQueryOp: &cool.QueryOp{
|
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) {
|
func (s *SignService) GetState() (*SignState, error) {
|
||||||
cfg := configservice.NewSignInService().GetActive(signInID)
|
record, err := s.getRecord()
|
||||||
if cfg == nil {
|
|
||||||
return nil, fmt.Errorf("签到活动不存在或未启用")
|
|
||||||
}
|
|
||||||
|
|
||||||
rewards, err := parseRewardDays(cfg.RewardScript)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(rewards) == 0 {
|
return s.buildState(record), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignService) Claim(signInID uint32) (*SignClaimResult, error) {
|
func (s *SignService) Claim() (*SignClaimResult, error) {
|
||||||
cfg := configservice.NewSignInService().GetActive(signInID)
|
record, isNew, err := s.getOrInitRecord()
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -150,53 +94,210 @@ func (s *SignService) Claim(signInID uint32) (*SignClaimResult, error) {
|
|||||||
return nil, fmt.Errorf("今天已经签到过了")
|
return nil, fmt.Errorf("今天已经签到过了")
|
||||||
}
|
}
|
||||||
|
|
||||||
nextDay, reward := nextRewardDay(rewards, record)
|
|
||||||
if reward == nil || nextDay == 0 {
|
|
||||||
return nil, fmt.Errorf("当前签到活动已全部完成")
|
|
||||||
}
|
|
||||||
|
|
||||||
prevDate := record.LastSignDate
|
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.LastSignDate = today
|
||||||
|
record.TotalDays++
|
||||||
if isYesterday(prevDate, today) {
|
if isYesterday(prevDate, today) {
|
||||||
record.ContinuousDays++
|
record.ContinuousDays++
|
||||||
} else {
|
} else {
|
||||||
record.ContinuousDays = 1
|
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 {
|
if err := s.saveRecord(record, isNew); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
grant, err := s.applyReward(*reward)
|
return &SignClaimResult{
|
||||||
if err != nil {
|
State: s.buildState(record),
|
||||||
cool.Logger.Error(context.TODO(), "sign reward apply failed", s.userid, cfg.SignInID, nextDay, err)
|
Rewards: rewards,
|
||||||
return nil, err
|
}, nil
|
||||||
}
|
|
||||||
grant.RewardDay = nextDay
|
|
||||||
|
|
||||||
state := buildSignState(cfg.SignInID, cfg.Status, rewards, record)
|
|
||||||
return &SignClaimResult{State: state, Reward: grant}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignService) getRecord(signInID uint32) (*model.SignInRecord, error) {
|
func (s *SignService) grantReachedStageRewards(record *model.SignInRecord) ([]SignRewardResult, error) {
|
||||||
var out *model.SignInRecord
|
configs := configservice.NewSignInService().GetEnabled()
|
||||||
if err := s.dbm(s.Model).Where("sign_in_id", signInID).Scan(&out); err != nil {
|
if len(configs) == 0 {
|
||||||
return nil, err
|
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
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignService) getOrInitRecord(signInID uint32) (*model.SignInRecord, bool, error) {
|
func (s *SignService) getOrInitRecord() (*model.SignInRecord, bool, error) {
|
||||||
record, err := s.getRecord(signInID)
|
record, err := s.getRecord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
@@ -210,7 +311,7 @@ func (s *SignService) getOrInitRecord(signInID uint32) (*model.SignInRecord, boo
|
|||||||
IsVip: cool.Config.ServerInfo.IsVip,
|
IsVip: cool.Config.ServerInfo.IsVip,
|
||||||
},
|
},
|
||||||
PlayerID: s.userid,
|
PlayerID: s.userid,
|
||||||
SignInID: signInID,
|
SignInID: signRecordID,
|
||||||
IsCompleted: false,
|
IsCompleted: false,
|
||||||
ContinuousDays: 0,
|
ContinuousDays: 0,
|
||||||
TotalDays: 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 {
|
func (s *SignService) saveRecord(record *model.SignInRecord, isNew bool) error {
|
||||||
if record == nil {
|
|
||||||
return errors.New("签到记录为空")
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"player_id": record.PlayerID,
|
"player_id": record.PlayerID,
|
||||||
"sign_in_id": record.SignInID,
|
"sign_in_id": record.SignInID,
|
||||||
"is_completed": record.IsCompleted,
|
"is_completed": false,
|
||||||
"continuous_days": record.ContinuousDays,
|
"continuous_days": record.ContinuousDays,
|
||||||
"total_days": record.TotalDays,
|
"total_days": record.TotalDays,
|
||||||
"last_sign_date": record.LastSignDate,
|
"last_sign_date": record.LastSignDate,
|
||||||
"sign_in_progress": record.SignInProgress,
|
"sign_in_progress": []uint32{},
|
||||||
"is_vip": cool.Config.ServerInfo.IsVip,
|
"is_vip": cool.Config.ServerInfo.IsVip,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNew {
|
if isNew {
|
||||||
_, err := cool.DBM(s.Model).Data(data).Insert()
|
_, err := cool.DBM(s.Model).Data(data).Insert()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
_, err := s.dbm(s.Model).Where("sign_in_id", signRecordID).Data(data).Update()
|
||||||
_, err := s.dbm(s.Model).Where("sign_in_id", record.SignInID).Data(data).Update()
|
|
||||||
return err
|
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 {
|
func currentDateString() string {
|
||||||
return time.Now().Format("2006-01-02")
|
return time.Now().Format("2006-01-02")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1 @@
|
|||||||
package service
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user