Files
bl/modules/player/service/sign.go
xinian 40ec827342
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor: 重构战斗属性和特效应用逻辑
2026-04-06 03:11:38 +08:00

407 lines
10 KiB
Go

package service
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"
"fmt"
"sort"
"time"
)
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"`
}
// SignState 表示玩家当前签到进度和阶段状态。
type SignState struct {
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"`
}
// 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"`
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"`
}
// SignClaimResult 表示签到后的完整结果。
type SignClaimResult struct {
State *SignState `json:"state"`
Rewards []SignRewardResult `json:"rewards,omitempty"`
}
// SignResetResult 表示管理端执行的签到重置结果。
type SignResetResult struct {
SignRecordRows int64 `json:"sign_record_rows"`
CdkLogRows int64 `json:"cdk_log_rows"`
ResetCdkIDs []uint32 `json:"reset_cdk_ids"`
}
// SignService 管理玩家签到进度。
type SignService struct {
BaseService
}
func NewSignService(id uint32) *SignService {
return &SignService{
BaseService: BaseService{
userid: id,
Service: &cool.Service{
Model: model.NewSignInRecord(),
ListQueryOp: &cool.QueryOp{
FieldEQ: []string{"player_id", "is_completed"},
},
PageQueryOp: &cool.QueryOp{
FieldEQ: []string{"player_id", "is_completed"},
},
},
},
}
}
func (s *SignService) GetState() (*SignState, error) {
record, err := s.getRecord()
if err != nil {
return nil, err
}
return s.buildState(record), nil
}
func (s *SignService) Claim() (*SignClaimResult, error) {
record, isNew, err := s.getOrInitRecord()
if err != nil {
return nil, err
}
today := currentDateString()
if record.LastSignDate == today {
return nil, fmt.Errorf("今天已经签到过了")
}
prevDate := record.LastSignDate
record.LastSignDate = today
record.TotalDays++
if isYesterday(prevDate, today) {
record.ContinuousDays++
} else {
record.ContinuousDays = 1
}
rewards, err := s.grantReachedStageRewards(record)
if err != nil {
return nil, err
}
if err := s.saveRecord(record, isNew); err != nil {
return nil, err
}
return &SignClaimResult{
State: s.buildState(record),
Rewards: rewards,
}, nil
}
func (s *SignService) ResetAll() (*SignResetResult, error) {
result := &SignResetResult{}
signRes, err := cool.DBM(model.NewSignInRecord()).Delete()
if err != nil {
return nil, err
}
if signRes != nil {
result.SignRecordRows, _ = signRes.RowsAffected()
}
configs := configservice.NewSignInService().GetEnabled()
cdkIDs := make([]uint32, 0, len(configs))
seen := make(map[uint32]struct{}, len(configs))
for _, cfg := range configs {
if cfg.CdkID == 0 {
continue
}
if _, ok := seen[cfg.CdkID]; ok {
continue
}
seen[cfg.CdkID] = struct{}{}
cdkIDs = append(cdkIDs, cfg.CdkID)
}
sort.Slice(cdkIDs, func(i, j int) bool { return cdkIDs[i] < cdkIDs[j] })
result.ResetCdkIDs = cdkIDs
if len(cdkIDs) > 0 {
cdkRes, err := cool.DBM(model.NewCdkLog()).WhereIn("code_id", cdkIDs).Delete()
if err != nil {
return nil, err
}
if cdkRes != nil {
result.CdkLogRows, _ = cdkRes.RowsAffected()
}
}
return result, nil
}
func (s *SignService) grantReachedStageRewards(record *model.SignInRecord) ([]SignRewardResult, error) {
configs := configservice.NewSignInService().GetEnabled()
if len(configs) == 0 {
return nil, nil
}
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() (*model.SignInRecord, bool, error) {
record, err := s.getRecord()
if err != nil {
return nil, false, err
}
if record != nil {
return record, false, nil
}
return &model.SignInRecord{
Base: model.Base{
Model: cool.NewModel(),
IsVip: cool.Config.ServerInfo.IsVip,
},
PlayerID: s.userid,
SignInID: signRecordID,
IsCompleted: false,
ContinuousDays: 0,
TotalDays: 0,
LastSignDate: "",
SignInProgress: []uint32{},
}, true, nil
}
func (s *SignService) saveRecord(record *model.SignInRecord, isNew bool) error {
data := map[string]any{
"player_id": record.PlayerID,
"sign_in_id": record.SignInID,
"is_completed": false,
"continuous_days": record.ContinuousDays,
"total_days": record.TotalDays,
"last_sign_date": record.LastSignDate,
"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", signRecordID).Data(data).Update()
return err
}
func currentDateString() string {
return time.Now().Format("2006-01-02")
}
func isYesterday(previousDate, currentDate string) bool {
if previousDate == "" || currentDate == "" {
return false
}
prev, err := time.ParseInLocation("2006-01-02", previousDate, time.Local)
if err != nil {
return false
}
curr, err := time.ParseInLocation("2006-01-02", currentDate, time.Local)
if err != nil {
return false
}
return prev.Add(24 * time.Hour).Equal(curr)
}