mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 13:53:41 +03:00
640 lines
19 KiB
Go
640 lines
19 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
pb "lunar-tear/server/gen/proto"
|
|
"lunar-tear/server/internal/gacha"
|
|
"lunar-tear/server/internal/gametime"
|
|
"lunar-tear/server/internal/model"
|
|
"lunar-tear/server/internal/runtime"
|
|
"lunar-tear/server/internal/store"
|
|
|
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
type GachaServiceServer struct {
|
|
pb.UnimplementedGachaServiceServer
|
|
users store.UserRepository
|
|
sessions store.SessionRepository
|
|
holder *runtime.Holder
|
|
}
|
|
|
|
func NewGachaServiceServer(
|
|
users store.UserRepository,
|
|
sessions store.SessionRepository,
|
|
holder *runtime.Holder,
|
|
) *GachaServiceServer {
|
|
return &GachaServiceServer{
|
|
users: users,
|
|
sessions: sessions,
|
|
holder: holder,
|
|
}
|
|
}
|
|
|
|
func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaListRequest) (*pb.GetGachaListResponse, error) {
|
|
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
|
|
|
|
cat := s.holder.Get()
|
|
catalog := cat.GachaEntries
|
|
handler := cat.GachaHandler
|
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
|
nowMillis := gametime.NowMillis()
|
|
|
|
user, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
user.EnsureMaps()
|
|
autoConvertExpiredMedals(user, catalog, handler, nowMillis)
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update user: %w", err)
|
|
}
|
|
|
|
gachaList := make([]*pb.Gacha, 0, len(catalog))
|
|
for _, entry := range catalog {
|
|
if !gachaActiveAt(entry, nowMillis) {
|
|
continue
|
|
}
|
|
if !matchesGachaLabel(req.GachaLabelType, entry.GachaLabelType) {
|
|
continue
|
|
}
|
|
if entry.GachaLabelType == model.GachaLabelPortalCage || entry.GachaLabelType == model.GachaLabelRecycle {
|
|
continue
|
|
}
|
|
bs := user.Gacha.BannerStates[entry.GachaId]
|
|
gachaList = append(gachaList, toProtoGacha(entry, &bs))
|
|
}
|
|
|
|
return &pb.GetGachaListResponse{
|
|
Gacha: gachaList,
|
|
ConvertedGachaMedal: toProtoConvertedGachaMedal(user.Gacha.ConvertedGachaMedal),
|
|
}, nil
|
|
}
|
|
|
|
func autoConvertExpiredMedals(user *store.UserState, catalog []store.GachaCatalogEntry, handler *gacha.GachaHandler, nowMillis int64) {
|
|
for _, entry := range catalog {
|
|
if entry.GachaMedalId == 0 || entry.EndDatetime == 0 {
|
|
continue
|
|
}
|
|
if nowMillis < entry.EndDatetime {
|
|
continue
|
|
}
|
|
bs, exists := user.Gacha.BannerStates[entry.GachaId]
|
|
if !exists || bs.MedalCount <= 0 {
|
|
continue
|
|
}
|
|
|
|
medalInfo, ok := handler.MedalInfo[entry.GachaId]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
conversionRate := medalInfo.ConversionRate
|
|
if conversionRate <= 0 {
|
|
conversionRate = 1
|
|
}
|
|
bookmarkCount := bs.MedalCount * conversionRate
|
|
|
|
user.ConsumableItems[medalInfo.ConsumableItemId] += bookmarkCount
|
|
|
|
user.Gacha.ConvertedGachaMedal.ConvertedMedalPossession = append(
|
|
user.Gacha.ConvertedGachaMedal.ConvertedMedalPossession,
|
|
store.ConsumableItemState{
|
|
ConsumableItemId: medalInfo.ConsumableItemId,
|
|
Count: bookmarkCount,
|
|
},
|
|
)
|
|
|
|
originalCount := bs.MedalCount
|
|
bs.MedalCount = 0
|
|
user.Gacha.BannerStates[entry.GachaId] = bs
|
|
|
|
log.Printf("[GachaService] auto-converted %d medals for gacha %d -> %d bookmarks (item %d)",
|
|
originalCount, entry.GachaId, bookmarkCount, medalInfo.ConsumableItemId)
|
|
}
|
|
}
|
|
|
|
func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaRequest) (*pb.GetGachaResponse, error) {
|
|
log.Printf("[GachaService] GetGacha: ids=%v", req.GachaId)
|
|
|
|
catalog := s.holder.Get().GachaEntries
|
|
nowMillis := gametime.NowMillis()
|
|
|
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
|
user, err := s.users.LoadUser(userId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
|
}
|
|
|
|
byId := make(map[int32]*pb.Gacha, len(req.GachaId))
|
|
for _, wantedId := range req.GachaId {
|
|
for _, entry := range catalog {
|
|
if entry.GachaId != wantedId {
|
|
continue
|
|
}
|
|
if !gachaActiveAt(entry, nowMillis) {
|
|
break
|
|
}
|
|
bs := user.Gacha.BannerStates[entry.GachaId]
|
|
byId[wantedId] = toProtoGacha(entry, &bs)
|
|
break
|
|
}
|
|
}
|
|
|
|
return &pb.GetGachaResponse{
|
|
Gacha: byId,
|
|
}, nil
|
|
}
|
|
|
|
func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb.DrawResponse, error) {
|
|
log.Printf("[GachaService] Draw: gachaId=%d phaseId=%d execCount=%d", req.GachaId, req.GachaPricePhaseId, req.ExecCount)
|
|
|
|
cat := s.holder.Get()
|
|
entry := findCatalogEntry(cat.GachaEntries, req.GachaId)
|
|
if entry == nil {
|
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
|
}
|
|
handler := cat.GachaHandler
|
|
|
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
|
execCount := req.ExecCount
|
|
if execCount <= 0 {
|
|
execCount = 1
|
|
}
|
|
|
|
var drawResult *gacha.DrawResult
|
|
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
var drawErr error
|
|
drawResult, drawErr = handler.HandleDraw(user, *entry, req.GachaPricePhaseId, execCount)
|
|
if drawErr != nil {
|
|
log.Printf("[GachaService] Draw error: %v", drawErr)
|
|
drawResult = &gacha.DrawResult{}
|
|
}
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update user: %w", err)
|
|
}
|
|
|
|
for i, item := range drawResult.Items {
|
|
if bonus, ok := drawResult.BonusItems[i]; ok {
|
|
log.Printf("[GachaService] drawn[%d]: type=%d id=%d rarity=%d + bonus type=%d id=%d rarity=%d",
|
|
i, item.PossessionType, item.PossessionId, item.RarityType,
|
|
bonus.PossessionType, bonus.PossessionId, bonus.RarityType)
|
|
} else {
|
|
log.Printf("[GachaService] drawn[%d]: type=%d id=%d rarity=%d",
|
|
i, item.PossessionType, item.PossessionId, item.RarityType)
|
|
}
|
|
}
|
|
|
|
gachaResults := make([]*pb.DrawGachaOddsItem, 0, len(drawResult.Items))
|
|
dupMap := make(map[int]gacha.DuplicateInfo)
|
|
for _, d := range drawResult.DuplicateInfos {
|
|
dupMap[d.Index] = d
|
|
}
|
|
bonusDupMap := make(map[int]gacha.DuplicateInfo)
|
|
for _, d := range drawResult.BonusDuplicateInfos {
|
|
bonusDupMap[d.Index] = d
|
|
}
|
|
|
|
costumePT := int32(model.PossessionTypeCostume)
|
|
weaponPT := int32(model.PossessionTypeWeapon)
|
|
isMaterialDraw := model.IsMaterialBanner(entry.GachaLabelType)
|
|
|
|
ownedCostumes := make(map[int32]bool, len(updatedUser.Costumes))
|
|
for _, c := range updatedUser.Costumes {
|
|
ownedCostumes[c.CostumeId] = true
|
|
}
|
|
ownedWeapons := make(map[int32]bool, len(updatedUser.Weapons))
|
|
for _, w := range updatedUser.Weapons {
|
|
ownedWeapons[w.WeaponId] = true
|
|
}
|
|
|
|
for i, item := range drawResult.Items {
|
|
isNew := !isOwnedByType(item, ownedCostumes, ownedWeapons, updatedUser)
|
|
|
|
var oddsItem *pb.DrawGachaOddsItem
|
|
|
|
if isMaterialDraw {
|
|
oddsItem = &pb.DrawGachaOddsItem{
|
|
GachaItem: &pb.GachaItem{
|
|
PossessionType: item.PossessionType,
|
|
PossessionId: item.PossessionId,
|
|
Count: 1,
|
|
IsNew: isNew,
|
|
},
|
|
GachaItemBonus: &pb.GachaItem{},
|
|
}
|
|
} else if bonus, hasBonusWeapon := drawResult.BonusItems[i]; hasBonusWeapon {
|
|
oddsItem = &pb.DrawGachaOddsItem{
|
|
GachaItem: &pb.GachaItem{
|
|
PossessionType: costumePT,
|
|
PossessionId: item.PossessionId,
|
|
Count: 1,
|
|
IsNew: isNew,
|
|
},
|
|
GachaItemBonus: &pb.GachaItem{
|
|
PossessionType: weaponPT,
|
|
PossessionId: bonus.PossessionId,
|
|
Count: 1,
|
|
IsNew: !ownedWeapons[bonus.PossessionId],
|
|
},
|
|
}
|
|
} else {
|
|
oddsItem = &pb.DrawGachaOddsItem{
|
|
GachaItem: &pb.GachaItem{
|
|
PossessionType: item.PossessionType,
|
|
PossessionId: item.PossessionId,
|
|
Count: 1,
|
|
IsNew: isNew,
|
|
},
|
|
GachaItemBonus: &pb.GachaItem{},
|
|
}
|
|
}
|
|
|
|
if drawResult.MedalBonus > 0 && entry.MedalConsumableItemId != 0 {
|
|
oddsItem.MedalBonus = &pb.GachaBonus{
|
|
PossessionType: int32(model.PossessionTypeConsumableItem),
|
|
PossessionId: entry.MedalConsumableItemId,
|
|
Count: 0,
|
|
}
|
|
}
|
|
|
|
if dup, ok := dupMap[i]; ok {
|
|
applyDuplicationBonus(oddsItem, dup)
|
|
}
|
|
if bdup, ok := bonusDupMap[i]; ok {
|
|
applyDuplicationBonus(oddsItem, bdup)
|
|
}
|
|
|
|
gachaResults = append(gachaResults, oddsItem)
|
|
}
|
|
|
|
var bonuses []*pb.GachaBonus
|
|
for _, b := range drawResult.Bonuses {
|
|
bonuses = append(bonuses, &pb.GachaBonus{
|
|
PossessionType: b.PossessionType,
|
|
PossessionId: b.PossessionId,
|
|
Count: b.Count,
|
|
})
|
|
}
|
|
|
|
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
|
|
nextGacha := toProtoGacha(*entry, &bs)
|
|
|
|
return &pb.DrawResponse{
|
|
NextGacha: nextGacha,
|
|
GachaResult: gachaResults,
|
|
GachaBonus: bonuses,
|
|
MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{},
|
|
}, nil
|
|
}
|
|
|
|
func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBoxGachaRequest) (*pb.ResetBoxGachaResponse, error) {
|
|
log.Printf("[GachaService] ResetBoxGacha: gachaId=%d", req.GachaId)
|
|
|
|
cat := s.holder.Get()
|
|
entry := findCatalogEntry(cat.GachaEntries, req.GachaId)
|
|
if entry == nil {
|
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
|
}
|
|
handler := cat.GachaHandler
|
|
|
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
|
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
if resetErr := handler.HandleResetBox(user, *entry); resetErr != nil {
|
|
log.Printf("[GachaService] ResetBoxGacha error: %v", resetErr)
|
|
}
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update user: %w", err)
|
|
}
|
|
|
|
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
|
|
|
|
return &pb.ResetBoxGachaResponse{
|
|
Gacha: toProtoGacha(*entry, &bs),
|
|
}, nil
|
|
}
|
|
|
|
func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Empty) (*pb.GetRewardGachaResponse, error) {
|
|
log.Printf("[GachaService] GetRewardGacha")
|
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
|
user, err := s.users.LoadUser(userId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
|
}
|
|
|
|
maxCount := s.holder.Get().GachaHandler.Config.RewardGachaDailyMaxCount
|
|
if maxCount <= 0 {
|
|
maxCount = model.DefaultDailyDrawLimit
|
|
}
|
|
|
|
todayStart := gametime.StartOfDayMillis()
|
|
drawCount := user.Gacha.TodaysCurrentDrawCount
|
|
if user.Gacha.LastRewardDrawDate < todayStart {
|
|
drawCount = 0
|
|
}
|
|
|
|
return &pb.GetRewardGachaResponse{
|
|
Available: drawCount < maxCount,
|
|
TodaysCurrentDrawCount: drawCount,
|
|
DailyMaxCount: maxCount,
|
|
}, nil
|
|
}
|
|
|
|
func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawRequest) (*pb.RewardDrawResponse, error) {
|
|
log.Printf("[GachaService] RewardDraw: placement=%q reward=%q amount=%q", req.PlacementName, req.RewardName, req.RewardAmount)
|
|
|
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
|
handler := s.holder.Get().GachaHandler
|
|
|
|
var items []gacha.DrawnItem
|
|
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
var drawErr error
|
|
items, drawErr = handler.HandleRewardDraw(user, 1)
|
|
if drawErr != nil {
|
|
log.Printf("[GachaService] RewardDraw error: %v", drawErr)
|
|
}
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("update user: %w", err)
|
|
}
|
|
|
|
ownedCostumes := make(map[int32]bool, len(updatedUser.Costumes))
|
|
for _, c := range updatedUser.Costumes {
|
|
ownedCostumes[c.CostumeId] = true
|
|
}
|
|
ownedWeapons := make(map[int32]bool, len(updatedUser.Weapons))
|
|
for _, w := range updatedUser.Weapons {
|
|
ownedWeapons[w.WeaponId] = true
|
|
}
|
|
|
|
results := make([]*pb.RewardGachaItem, 0, len(items))
|
|
for _, item := range items {
|
|
results = append(results, &pb.RewardGachaItem{
|
|
PossessionType: item.PossessionType,
|
|
PossessionId: item.PossessionId,
|
|
Count: 1,
|
|
IsNew: !isOwnedByType(item, ownedCostumes, ownedWeapons, updatedUser),
|
|
})
|
|
}
|
|
|
|
return &pb.RewardDrawResponse{
|
|
RewardGachaResult: results,
|
|
}, nil
|
|
}
|
|
|
|
func findCatalogEntry(catalog []store.GachaCatalogEntry, gachaId int32) *store.GachaCatalogEntry {
|
|
for i := range catalog {
|
|
if catalog[i].GachaId == gachaId {
|
|
return &catalog[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func matchesGachaLabel(labels []int32, label int32) bool {
|
|
if len(labels) == 0 {
|
|
return true
|
|
}
|
|
for _, candidate := range labels {
|
|
if candidate == label {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func gachaActiveAt(entry store.GachaCatalogEntry, nowMillis int64) bool {
|
|
if entry.StartDatetime != 0 && nowMillis < entry.StartDatetime {
|
|
return false
|
|
}
|
|
if entry.EndDatetime != 0 && nowMillis >= entry.EndDatetime {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func toProtoGacha(entry store.GachaCatalogEntry, bs *store.GachaBannerState) *pb.Gacha {
|
|
g := &pb.Gacha{
|
|
GachaId: entry.GachaId,
|
|
GachaLabelType: entry.GachaLabelType,
|
|
GachaModeType: entry.GachaModeType,
|
|
GachaAutoResetType: entry.GachaAutoResetType,
|
|
GachaAutoResetPeriod: entry.GachaAutoResetPeriod,
|
|
NextAutoResetDatetime: safeTimestamp(entry.NextAutoResetDatetime),
|
|
GachaUnlockCondition: []*pb.GachaUnlockCondition{{GachaUnlockConditionType: model.GachaUnlockNone, ConditionValue: 0}},
|
|
IsUserGachaUnlock: entry.IsUserGachaUnlock,
|
|
StartDatetime: safeTimestamp(entry.StartDatetime),
|
|
EndDatetime: safeTimestamp(entry.EndDatetime),
|
|
RelatedMainQuestChapterId: entry.RelatedMainQuestChapterId,
|
|
RelatedEventQuestChapterId: entry.RelatedEventQuestChapterId,
|
|
PromotionMovieAssetId: entry.PromotionMovieAssetId,
|
|
GachaMedalId: entry.GachaMedalId,
|
|
GachaDecorationType: entry.GachaDecorationType,
|
|
SortOrder: entry.SortOrder,
|
|
IsInactive: entry.IsInactive,
|
|
InformationId: entry.InformationId,
|
|
}
|
|
|
|
g.GachaPricePhase = buildProtoPricePhases(entry, bs)
|
|
|
|
promotionItems := buildProtoPromotionItems(entry)
|
|
|
|
switch entry.GachaModeType {
|
|
case model.GachaModeBox:
|
|
boxNumber := int32(1)
|
|
if bs != nil && bs.BoxNumber > 0 {
|
|
boxNumber = bs.BoxNumber
|
|
}
|
|
phaseId := int32(0)
|
|
if len(entry.PricePhases) > 0 {
|
|
phaseId = entry.PricePhases[0].PhaseId
|
|
}
|
|
g.GachaMode = &pb.Gacha_GachaModeBoxComposition{
|
|
GachaModeBoxComposition: &pb.GachaModeBoxComposition{
|
|
GachaBoxGroupId: entry.GroupId,
|
|
BoxNumber: boxNumber,
|
|
CurrentBoxNumber: boxNumber,
|
|
NaviCharacterCommentAssetName: "production",
|
|
GachaAssetName: entry.BannerAssetName,
|
|
GachaPricePhaseId: phaseId,
|
|
PromotionGachaOddsItem: promotionItems,
|
|
GachaDescriptionTextId: entry.DescriptionTextId,
|
|
},
|
|
}
|
|
case model.GachaModeStepup:
|
|
stepNumber := int32(1)
|
|
loopCount := int32(0)
|
|
if bs != nil {
|
|
if bs.StepNumber > 0 {
|
|
stepNumber = bs.StepNumber
|
|
}
|
|
loopCount = bs.LoopCount
|
|
}
|
|
g.GachaMode = &pb.Gacha_GachaModeStepupComposition{
|
|
GachaModeStepupComposition: &pb.GachaModeStepupComposition{
|
|
GachaStepGroupId: entry.GroupId,
|
|
StepNumber: 1,
|
|
CurrentStepNumber: stepNumber,
|
|
NaviCharacterCommentAssetName: "production",
|
|
GachaAssetName: entry.BannerAssetName,
|
|
PromotionGachaOddsItem: promotionItems,
|
|
CurrentLoopCount: loopCount,
|
|
},
|
|
}
|
|
default:
|
|
g.GachaMode = &pb.Gacha_GachaModeBasic{
|
|
GachaModeBasic: &pb.GachaModeBasic{
|
|
NaviCharacterCommentAssetName: "production",
|
|
GachaAssetName: entry.BannerAssetName,
|
|
PromotionGachaOddsItem: promotionItems,
|
|
},
|
|
}
|
|
}
|
|
|
|
return g
|
|
}
|
|
|
|
func buildProtoPricePhases(entry store.GachaCatalogEntry, bs *store.GachaBannerState) []*pb.GachaPricePhase {
|
|
phases := make([]*pb.GachaPricePhase, 0, len(entry.PricePhases))
|
|
|
|
for _, p := range entry.PricePhases {
|
|
isEnabled := true
|
|
if entry.GachaModeType == model.GachaModeStepup && bs != nil {
|
|
currentStep := bs.StepNumber
|
|
if currentStep <= 0 {
|
|
currentStep = 1
|
|
}
|
|
isEnabled = p.StepNumber == currentStep
|
|
}
|
|
|
|
var bonuses []*pb.GachaBonus
|
|
for _, b := range p.Bonuses {
|
|
bonuses = append(bonuses, &pb.GachaBonus{
|
|
PossessionType: b.PossessionType,
|
|
PossessionId: b.PossessionId,
|
|
Count: b.Count,
|
|
})
|
|
}
|
|
|
|
limitExec := p.LimitExecCount
|
|
if limitExec <= 0 {
|
|
limitExec = 999
|
|
}
|
|
|
|
phases = append(phases, &pb.GachaPricePhase{
|
|
GachaPricePhaseId: p.PhaseId,
|
|
IsEnabled: isEnabled,
|
|
EndDatetime: safeTimestamp(entry.EndDatetime),
|
|
PriceType: p.PriceType,
|
|
PriceId: p.PriceId,
|
|
Price: p.Price,
|
|
RegularPrice: p.RegularPrice,
|
|
DrawCount: p.DrawCount,
|
|
LimitExecCount: limitExec,
|
|
EachMaxExecCount: p.DrawCount,
|
|
GachaBonus: bonuses,
|
|
GachaOddsFixedRarity: &pb.GachaOddsFixedRarity{
|
|
FixedRarityTypeLowerLimit: p.FixedRarityMin,
|
|
FixedCount: p.FixedCount,
|
|
},
|
|
GachaBadgeType: model.GachaBadgeTypeNone,
|
|
})
|
|
}
|
|
|
|
return phases
|
|
}
|
|
|
|
func buildProtoPromotionItems(entry store.GachaCatalogEntry) []*pb.GachaOddsItem {
|
|
if len(entry.PromotionItems) == 0 {
|
|
return nil
|
|
}
|
|
isMaterial := model.IsMaterialBanner(entry.GachaLabelType)
|
|
|
|
items := make([]*pb.GachaOddsItem, 0, len(entry.PromotionItems))
|
|
for i, pi := range entry.PromotionItems {
|
|
bonus := &pb.GachaItem{}
|
|
if !isMaterial && pi.BonusPossessionType != 0 {
|
|
bonus = &pb.GachaItem{
|
|
PossessionType: pi.BonusPossessionType,
|
|
PossessionId: pi.BonusPossessionId,
|
|
Count: 1,
|
|
}
|
|
}
|
|
items = append(items, &pb.GachaOddsItem{
|
|
GachaItem: &pb.GachaItem{
|
|
PossessionType: pi.PossessionType,
|
|
PossessionId: pi.PossessionId,
|
|
Count: 1,
|
|
PromotionOrder: int32(i + 1),
|
|
},
|
|
GachaItemBonus: bonus,
|
|
MaxDrawableCount: 999,
|
|
IsTarget: pi.IsTarget,
|
|
})
|
|
}
|
|
return items
|
|
}
|
|
|
|
func toProtoConvertedGachaMedal(state store.ConvertedGachaMedalState) *pb.ConvertedGachaMedal {
|
|
items := make([]*pb.ConsumableItemPossession, 0, len(state.ConvertedMedalPossession))
|
|
for _, item := range state.ConvertedMedalPossession {
|
|
items = append(items, &pb.ConsumableItemPossession{
|
|
ConsumableItemId: item.ConsumableItemId,
|
|
Count: item.Count,
|
|
})
|
|
}
|
|
|
|
obtain := &pb.ConsumableItemPossession{
|
|
ConsumableItemId: 0,
|
|
Count: 0,
|
|
}
|
|
if state.ObtainPossession != nil {
|
|
obtain.ConsumableItemId = state.ObtainPossession.ConsumableItemId
|
|
obtain.Count = state.ObtainPossession.Count
|
|
}
|
|
|
|
return &pb.ConvertedGachaMedal{
|
|
ConvertedMedalPossession: items,
|
|
ObtainPossession: obtain,
|
|
}
|
|
}
|
|
|
|
func safeTimestamp(unixMillis int64) *timestamppb.Timestamp {
|
|
if unixMillis == 0 {
|
|
return ×tamppb.Timestamp{Seconds: 0}
|
|
}
|
|
return timestamppb.New(time.UnixMilli(unixMillis))
|
|
}
|
|
|
|
func applyDuplicationBonus(oddsItem *pb.DrawGachaOddsItem, dup gacha.DuplicateInfo) {
|
|
if oddsItem.DuplicationBonusGrade == 0 {
|
|
oddsItem.DuplicationBonusGrade = dup.Grade
|
|
}
|
|
for _, b := range dup.Bonuses {
|
|
oddsItem.DuplicationBonus = append(oddsItem.DuplicationBonus, &pb.GachaBonus{
|
|
PossessionType: b.PossessionType,
|
|
PossessionId: b.PossessionId,
|
|
Count: b.Count,
|
|
})
|
|
}
|
|
}
|
|
|
|
func isOwnedByType(item gacha.DrawnItem, costumes, weapons map[int32]bool, user store.UserState) bool {
|
|
switch item.PossessionType {
|
|
case int32(model.PossessionTypeCostume):
|
|
return costumes[item.PossessionId]
|
|
case int32(model.PossessionTypeWeapon):
|
|
return weapons[item.PossessionId]
|
|
case int32(model.PossessionTypeMaterial):
|
|
return user.Materials[item.PossessionId] > 0
|
|
case int32(model.PossessionTypeWeaponEnhanced):
|
|
return user.ConsumableItems[item.PossessionId] > 0
|
|
}
|
|
return false
|
|
}
|