diff --git a/common/data/share/task.go b/common/data/share/task.go index d1ac9b8cd..cab673f30 100644 --- a/common/data/share/task.go +++ b/common/data/share/task.go @@ -12,61 +12,190 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) -// newDailyCounterStore 创建每日计数器缓存实例 -func newDailyCounterStore() *cacheStore[int64] { +// 周期类型定义 +type PeriodType int8 + +const ( + PeriodDaily PeriodType = 1 // 每日 + PeriodWeekly PeriodType = 2 // 每周 + PeriodMonthly PeriodType = 3 // 每月 + PeriodQuarter PeriodType = 4 // 每季 +) + +// 周期函数类型定义 +type ( + // FormatFunc 生成周期格式化字符串的函数 + FormatFunc func(t time.Time) string + + // ExpireFunc 计算周期过期时间的函数 + ExpireFunc func(t time.Time) time.Duration +) + +// Period 周期结构体,聚合周期相关函数和元信息 +type Period struct { + Type PeriodType // 周期类型 + Format FormatFunc // 周期格式化函数 + Expire ExpireFunc // 过期时间计算函数 + Prefix string // 缓存键前缀 +} + +// 周期实例 - 每日 +var DailyPeriod = Period{ + Type: PeriodDaily, + Format: func(t time.Time) string { + return t.Format("20060102") // 格式: YYYYMMDD + }, + Expire: func(t time.Time) time.Duration { + tomorrow := t.AddDate(0, 0, 1) + resetTime := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, time.Local) + return resetTime.Sub(t) + }, + Prefix: "daily:", +} + +// 周期实例 - 每周 +var WeeklyPeriod = Period{ + Type: PeriodWeekly, + Format: func(t time.Time) string { + // 计算当年第几周 (ISO周编号) + year, week := t.ISOWeek() + return fmt.Sprintf("%dW%02d", year, week) // 格式: YYYYWww + }, + Expire: func(t time.Time) time.Duration { + // 计算下周一0点 + weekday := t.Weekday() + daysToNextMonday := (8 - int(weekday)) % 7 + nextMonday := t.AddDate(0, 0, daysToNextMonday) + resetTime := time.Date(nextMonday.Year(), nextMonday.Month(), nextMonday.Day(), 0, 0, 0, 0, time.Local) + return resetTime.Sub(t) + }, + Prefix: "weekly:", +} + +// 周期实例 - 每月 +var MonthlyPeriod = Period{ + Type: PeriodMonthly, + Format: func(t time.Time) string { + return t.Format("200601") // 格式: YYYYMM + }, + Expire: func(t time.Time) time.Duration { + // 计算下月1日0点 + nextMonth := t.AddDate(0, 1, 0) + resetTime := time.Date(nextMonth.Year(), nextMonth.Month(), 1, 0, 0, 0, 0, time.Local) + return resetTime.Sub(t) + }, + Prefix: "monthly:", +} + +// 周期实例 - 每季 +var QuarterlyPeriod = Period{ + Type: PeriodQuarter, + Format: func(t time.Time) string { + quarter := (int(t.Month())-1)/3 + 1 + return fmt.Sprintf("%dQ%d", t.Year(), quarter) // 格式: YYYYQq + }, + Expire: func(t time.Time) time.Duration { + // 计算下季首月1日0点 + quarter := (int(t.Month())-1)/3 + 1 + nextQuarterFirstMonth := quarter*3 + 1 + year := t.Year() + if nextQuarterFirstMonth > 12 { + nextQuarterFirstMonth = 1 + year++ + } + resetTime := time.Date(year, time.Month(nextQuarterFirstMonth), 1, 0, 0, 0, 0, time.Local) + return resetTime.Sub(t) + }, + Prefix: "quarterly:", +} + +// 周期映射表,用于快速查找周期实例 +var periodMap = map[PeriodType]*Period{ + PeriodDaily: &DailyPeriod, + PeriodWeekly: &WeeklyPeriod, + PeriodMonthly: &MonthlyPeriod, + PeriodQuarter: &QuarterlyPeriod, +} + +// GetPeriodByType 根据周期类型获取周期实例 +func GetPeriodByType(periodType PeriodType) (*Period, error) { + period, ok := periodMap[periodType] + if !ok { + return nil, fmt.Errorf("不支持的周期类型: %d", periodType) + } + return period, nil +} + +// newCounterStore 创建计数器缓存实例 +func newCounterStore(period *Period) *cacheStore[int64] { return &cacheStore[int64]{ manager: cool.CacheManager, - prefix: "blazing:dailycounter:", + prefix: fmt.Sprintf("blazing:counter:%s", period.Prefix), } } -// newCounterLimitStore 创建计数器限制配置缓存实例 -func newCounterLimitStore() *cacheStore[int64] { +// newLimitStore 创建限制配置缓存实例 +func newLimitStore() *cacheStore[int64] { return &cacheStore[int64]{ manager: cool.CacheManager, prefix: "blazing:counterlimit:", } } -// counterManager 计数器管理器(每日计数+限制配置+批量查询) -type counterManager struct { - dailyCounterStore *cacheStore[int64] // 每日计数器缓存 - counterLimitStore *cacheStore[int64] // 计数器限制配置缓存 +// CounterManager 计数器管理器 +type CounterManager struct { + limitStore *cacheStore[int64] // 限制配置缓存 + periods map[PeriodType]*cacheStore[int64] // 各周期计数器缓存 } -// newCounterManager 创建计数器管理器实例 -func newCounterManager() *counterManager { - return &counterManager{ - dailyCounterStore: newDailyCounterStore(), - counterLimitStore: newCounterLimitStore(), +// NewCounterManager 创建计数器管理器实例 +func NewCounterManager() *CounterManager { + // 初始化所有周期的缓存存储 + periods := make(map[PeriodType]*cacheStore[int64]) + for _, period := range periodMap { + periods[period.Type] = newCounterStore(period) + } + + return &CounterManager{ + limitStore: newLimitStore(), + periods: periods, } } -// genDailyKey 生成单个奖品类型的每日计数键 -// 格式:{userID}:{rewardType}:{date}(userID和rewardType为uint32) -func (m *counterManager) genDailyKey(userID uint32, rewardType uint32) string { - date := time.Now().Format("20060102") // 当日日期(YYYYMMDD) - return fmt.Sprintf("%d:%d:%s", userID, rewardType, date) +// genKey 生成计数器键 +func (m *CounterManager) genKey(period *Period, userID uint32, rewardType uint32) string { + periodStr := period.Format(time.Now()) + return fmt.Sprintf("%d:%d:%s", userID, rewardType, periodStr) } -// genUserDailyPrefix 生成用户当日所有计数的键前缀(用于扫描) -func (m *counterManager) genUserDailyPrefix(userID uint32) string { - date := time.Now().Format("20060102") - return fmt.Sprintf("%d:*:%s", userID, date) // 格式:{userID}:*:{date} +// genUserPrefix 生成用户某周期所有计数的键前缀 +func (m *CounterManager) genUserPrefix(period *Period, userID uint32) string { + periodStr := period.Format(time.Now()) + return fmt.Sprintf("%d:*:%s", userID, periodStr) } -// GetDailyCount 获取用户当日某奖品的计数 -func (m *counterManager) GetDailyCount(userID uint32, rewardType uint32) (int64, error) { - key := m.genDailyKey(userID, rewardType) - return m.dailyCounterStore.Get(context.Background(), key) +// GetCount 获取用户某周期某奖品的计数 +func (m *CounterManager) GetCount(period *Period, userID uint32, rewardType uint32) (int64, error) { + store, ok := m.periods[period.Type] + if !ok { + return 0, fmt.Errorf("不支持的周期类型: %d", period.Type) + } + + key := m.genKey(period, userID, rewardType) + return store.Get(context.Background(), key) } -// IncrDailyCount 增加用户当日某奖品的计数(自动设置次日0点过期) -func (m *counterManager) IncrDailyCount(userID uint32, rewardType uint32) (int64, error) { - key := m.genDailyKey(userID, rewardType) +// IncrCount 增加用户某周期某奖品的计数(自动设置周期结束过期) +func (m *CounterManager) IncrCount(period *Period, userID uint32, rewardType uint32) (int64, error) { + store, ok := m.periods[period.Type] + if !ok { + return 0, fmt.Errorf("不支持的周期类型: %d", period.Type) + } + + key := m.genKey(period, userID, rewardType) // 获取当前计数(不存在则视为0) - current, err := m.dailyCounterStore.Get(context.Background(), key) + current, err := store.Get(context.Background(), key) if err != nil && err != ErrCacheMiss { return 0, fmt.Errorf("获取当前计数失败: %w", err) } @@ -77,40 +206,43 @@ func (m *counterManager) IncrDailyCount(userID uint32, rewardType uint32) (int64 // 计算新计数 newValue := current + 1 - // 计算过期时间:到次日0点(确保当日数据自动重置) - tomorrow := time.Now().AddDate(0, 0, 1) - resetTime := time.Date(tomorrow.Year(), tomorrow.Month(), tomorrow.Day(), 0, 0, 0, 0, time.Local) - ttl := resetTime.Sub(time.Now()) + // 计算过期时间 + ttl := period.Expire(time.Now()) // 保存新计数并设置过期时间 - if err := m.dailyCounterStore.Set(gctx.New(), key, newValue, ttl); err != nil { + if err := store.Set(gctx.New(), key, newValue, ttl); err != nil { return 0, fmt.Errorf("保存计数失败: %w", err) } return newValue, nil } -// ResetDailyCount 重置用户当日某奖品的计数 -func (m *counterManager) ResetDailyCount(userID uint32, rewardType uint32) error { - key := m.genDailyKey(userID, rewardType) - return m.dailyCounterStore.Del(gctx.New(), key) +// ResetCount 重置用户某周期某奖品的计数 +func (m *CounterManager) ResetCount(period *Period, userID uint32, rewardType uint32) error { + store, ok := m.periods[period.Type] + if !ok { + return fmt.Errorf("不支持的周期类型: %d", period.Type) + } + + key := m.genKey(period, userID, rewardType) + return store.Del(gctx.New(), key) } -// SetCounterLimit 设置某奖品类型的每日限制 -func (m *counterManager) SetCounterLimit(rewardType uint32, limit int64) error { - key := fmt.Sprintf("%d", rewardType) // 限制键直接使用奖品类型(uint32) - return m.counterLimitStore.Set(gctx.New(), key, limit, time.Hour*24*7) // 限制配置保留7天 +// SetLimit 设置某周期某奖品类型的限制 +func (m *CounterManager) SetLimit(period *Period, rewardType uint32, limit int64) error { + key := fmt.Sprintf("%d:%d", period.Type, rewardType) + return m.limitStore.Set(gctx.New(), key, limit, time.Hour*24*7) // 限制配置保留7天 } -// GetCounterLimit 获取某奖品类型的每日限制 -func (m *counterManager) GetCounterLimit(rewardType uint32) (int64, error) { - key := fmt.Sprintf("%d", rewardType) - return m.counterLimitStore.Get(context.Background(), key) +// GetLimit 获取某周期某奖品类型的限制 +func (m *CounterManager) GetLimit(period *Period, rewardType uint32) (int64, error) { + key := fmt.Sprintf("%d:%d", period.Type, rewardType) + return m.limitStore.Get(context.Background(), key) } -// IsExceedLimit 检查用户当日某奖品的计数是否超过限制 -func (m *counterManager) IsExceedLimit(userID uint32, rewardType uint32) (bool, error) { - count, err := m.GetDailyCount(userID, rewardType) +// IsExceedLimit 检查用户某周期某奖品的计数是否超过限制 +func (m *CounterManager) IsExceedLimit(period *Period, userID uint32, rewardType uint32) (bool, error) { + count, err := m.GetCount(period, userID, rewardType) if err != nil && err != ErrCacheMiss { return false, fmt.Errorf("获取计数失败: %w", err) } @@ -119,7 +251,7 @@ func (m *counterManager) IsExceedLimit(userID uint32, rewardType uint32) (bool, count = 0 } - limit, err := m.GetCounterLimit(rewardType) + limit, err := m.GetLimit(period, rewardType) if err != nil { return false, fmt.Errorf("获取限制失败: %w", err) } @@ -127,20 +259,24 @@ func (m *counterManager) IsExceedLimit(userID uint32, rewardType uint32) (bool, return count >= limit, nil } -// GetUserAllDailyCounts 获取用户当日所有奖品类型的计数 -func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64, error) { - // 1. 生成扫描模式(包含缓存前缀,使用*通配符) - // 格式:blazing:dailycounter:{userID}:*:{今日日期} - today := time.Now().Format("20060102") +// GetUserAllCounts 获取用户某周期所有奖品类型的计数 +func (m *CounterManager) GetUserAllCounts(period *Period, userID uint32) (map[uint32]int64, error) { + store, ok := m.periods[period.Type] + if !ok { + return nil, fmt.Errorf("不支持的周期类型: %d", period.Type) + } + + // 1. 生成扫描模式 + periodStr := period.Format(time.Now()) matchPattern := fmt.Sprintf( "%s%d:*:%s", - m.dailyCounterStore.prefix, + store.prefix, userID, - today, + periodStr, ) - // 2. 扫描所有匹配的键(通过Scan方法筛选) - keys, err := m.dailyCounterStore.Scan(context.Background(), matchPattern) + // 2. 扫描所有匹配的键 + keys, err := store.Scan(context.Background(), matchPattern) if err != nil { return nil, fmt.Errorf("扫描键失败: %w", err) } @@ -150,16 +286,16 @@ func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64, // 3. 提取核心键(去除缓存前缀) coreKeys := make([]string, 0, len(keys)) - prefixLen := len(m.dailyCounterStore.prefix) + prefixLen := len(store.prefix) for _, fullKey := range keys { - if strings.HasPrefix(fullKey, m.dailyCounterStore.prefix) { - coreKey := fullKey[prefixLen:] // 核心键格式:userID:rewardType:date + if strings.HasPrefix(fullKey, store.prefix) { + coreKey := fullKey[prefixLen:] // 核心键格式:userID:rewardType:periodStr coreKeys = append(coreKeys, coreKey) } } // 4. 批量获取计数 - values, err := m.dailyCounterStore.MGet(context.Background(), coreKeys) + values, err := store.MGet(context.Background(), coreKeys) if err != nil { return nil, fmt.Errorf("批量获取计数失败: %w", err) } @@ -173,16 +309,16 @@ func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64, continue } - // 验证用户ID(防止异常键干扰) + // 验证用户ID parsedUserID, err := strconv.ParseUint(parts[0], 10, 32) if err != nil || uint32(parsedUserID) != userID { fmt.Printf("[WARN] 忽略用户不匹配的键: %s\n", coreKey) continue } - // 验证日期(确保是今日) - if parts[2] != today { - fmt.Printf("[WARN] 忽略过期键: %s(日期不匹配)\n", coreKey) + // 验证周期 + if parts[2] != periodStr { + fmt.Printf("[WARN] 忽略过期键: %s(周期不匹配)\n", coreKey) continue } @@ -206,5 +342,5 @@ func (m *counterManager) GetUserAllDailyCounts(userID uint32) (map[uint32]int64, return result, nil } -// 全局计数器管理器实例(根据实际需求初始化) -var CounterManager = newCounterManager() +// 全局计数器管理器实例 +var GlobalCounterManager = NewCounterManager() diff --git a/logic/controller/task.go b/logic/controller/task.go index e82745ee0..2fea3eca7 100644 --- a/logic/controller/task.go +++ b/logic/controller/task.go @@ -20,7 +20,7 @@ func (h Controller) AcceptTask(data *task.AcceptTaskInboundInfo, c *entity.Playe ft, ok := ttt[data.TaskId] if ok { //如果找到任务 if ft.Status == 0 { //可以接受 - ft.Status = 1 + ft.Status = 1 //接受 return true } else { return false diff --git a/logic/service/fight/battle/node/attack.go b/logic/service/fight/battle/node/attack.go index 12c69f32b..d7c95d5a5 100644 --- a/logic/service/fight/battle/node/attack.go +++ b/logic/service/fight/battle/node/attack.go @@ -1,10 +1,6 @@ package node -func (this *EffectNode) BeforeUseSkillCheck() bool { - panic("not implemented") // TODO: Implement -} - -func (this *EffectNode) AfterUseSkillCheck() bool { +func (this *EffectNode) UseSkill() bool { panic("not implemented") // TODO: Implement } diff --git a/logic/service/fight/info/nodemanger.go b/logic/service/fight/info/nodemanger.go index 8b4d3278a..7bdedb396 100644 --- a/logic/service/fight/info/nodemanger.go +++ b/logic/service/fight/info/nodemanger.go @@ -1,17 +1,13 @@ package info type Effect interface { - PreBattleStart() bool //战斗开始前 - OnBattleStart() bool //战斗开始 + OnBattleStart() bool //战斗开始 - PreTurnStart() bool //回合开始前 - OnTurnStart() bool //回合开始 + OnTurnStart() bool //回合开始 + + OnActive() bool //效果生效 + UseSkill() bool //使用技能 - PreActive() bool //效果生效前 - OnActive() bool //效果生效 - BeforeUseSkillCheck() bool //使用技能前 - AfterUseSkillCheck() bool //使用技能后 - SkillUseEnd() bool //技能使用结束 BeforeMultiHit() bool //多段攻击前 BeforeHit() bool //命中前 OnCritPreDamage() bool //暴击判定成功且伤害计算前触发 diff --git a/modules/blazing/model/cycle.go b/modules/blazing/model/cycle.go new file mode 100644 index 000000000..ac39c9333 --- /dev/null +++ b/modules/blazing/model/cycle.go @@ -0,0 +1,4 @@ +package model + +// /这里实现周期性任务,比如每日任务,每周任务,每月任务,每年任务,一次性任务 +type PeriodType int diff --git a/modules/blazing/model/task_Daily.go b/modules/blazing/model/task_Daily.go new file mode 100644 index 000000000..a6891c728 --- /dev/null +++ b/modules/blazing/model/task_Daily.go @@ -0,0 +1,48 @@ +package model + +import ( + "blazing/cool" + "time" +) + +const TableNameDailyTask = "DailyTask" + +// DailyTask mapped from table +type DailyTask struct { + *cool.Model + PlayerID uint64 `gorm:"not null;index:idx_DailyTask_by_player_id;comment:'所属玩家ID'" json:"player_id"` + Data string `gorm:"type:text;not null;comment:'全部数据'" json:"data"` +} + +// DailyTaskInfo 单个任务的详细信息,包含任务步骤状态和整体状态 +type DailyTaskInfo struct { + // DailyTaskInfo 任务步骤信息,对应Java的@ArraySerialize(FIXED_LENGTH=20)注解 + // struc:"[20]byte" 确保二进制序列化时固定20字节长度,json标签指定JSON字段名 + DailyTaskInfo []uint32 `struc:"[20]byte" json:"DailyTask_info"` + LastResetTime time.Time `gorm:"not null;comment:'上次重置时间(UTC)'" json:"last_reset_time"` //这里是每天重置 + // Status 任务整体状态:0-未接受,1-已接受,2-已完成未领取,3-已完成已领取 + // json标签指定JSON字段名,与业务状态说明保持一致 + Status byte `json:"status"` +} + +// TableName PlayerInfo's table name +func (*DailyTask) TableName() string { + return TableNamePlayerInfo +} + +// GroupName PlayerInfo's table group +func (*DailyTask) GroupName() string { + return "default" +} + +// NewPlayerInfo create a new PlayerInfo +func NewDailyTask() *DailyTask { + return &DailyTask{ + Model: cool.NewModel(), + } +} + +// init 创建表 +func init() { + cool.CreateTable(&DailyTask{}) +} diff --git a/modules/blazing/service/task.go b/modules/blazing/service/task.go index 98a3c4aa7..d8df1e9d0 100644 --- a/modules/blazing/service/task.go +++ b/modules/blazing/service/task.go @@ -5,6 +5,7 @@ import ( "blazing/modules/blazing/model" "encoding/json" "reflect" + "time" ) func Exec[T, F any](userid uint32, s *cool.Service, processFunc func(F) bool) bool { @@ -33,7 +34,46 @@ func Exec[T, F any](userid uint32, s *cool.Service, processFunc func(F) bool) bo return false } func (s *UserService) TaskExec(t func(map[uint32]model.TaskInfo) bool) (ret bool) { - return Exec[model.Task, map[uint32]model.TaskInfo](s.userid, s.task, t) + return Exec[model.Task](s.userid, s.task, t) + // m := cool.DBM(s.task.Model).Where("player_id", s.userid) + // var tt model.Task + // m.Scan(&tt) + // var ttt map[uint32]model.TaskInfo + // json.Unmarshal([]byte(tt.Data), &ttt) + // ret = t(ttt) + // t1, _ := json.Marshal(&ttt) + // tt.Data = string(t1) + // m.Save(&tt) //退出时保存 + // return +} + +// IsToday 判断给定时间是否是今天 +func IsToday(t time.Time) bool { + // 获取当前时间 + now := time.Now() + + // 比较年、月、日是否相同 + return t.Year() == now.Year() && + t.Month() == now.Month() && + t.Day() == now.Day() +} +func (s *UserService) DailyTaskExec(t func(map[uint32]model.DailyTaskInfo) bool) (ret bool) { + Exec[model.DailyTask](s.userid, s.task, func(tt map[uint32]model.DailyTaskInfo) bool { + + //先重置每日 + for _, v := range tt { + if !IsToday(v.LastResetTime) { + + v.Status = 0 //重置+自动接受每日任务 + v.LastResetTime = time.Now().UTC() + } + + } + + return true + }) + + return Exec[model.DailyTask](s.userid, s.task, t) // m := cool.DBM(s.task.Model).Where("player_id", s.userid) // var tt model.Task // m.Scan(&tt)