Fix repeated weapon story unlock notifications by sending only changed stories in diffs

This commit is contained in:
Ilya Groshev
2026-04-16 14:36:05 +03:00
parent a61ae6b93e
commit 0ab2589277
17 changed files with 260 additions and 150 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ func (h *QuestHandler) HandleBigHuntQuestFinish(user *store.UserState, questId i
outcome := h.evaluateFinishOutcome(user, questId) outcome := h.evaluateFinishOutcome(user, questId)
if !isRetired { if !isRetired {
h.applyQuestVictory(user, questId, outcome, nowMillis) h.applyQuestVictory(user, questId, &outcome, nowMillis)
} }
if isRetired && !isAnnihilated && quest.Stamina > 1 { if isRetired && !isAnnihilated && quest.Stamina > 1 {
+1 -1
View File
@@ -44,7 +44,7 @@ func (h *QuestHandler) HandleEventQuestFinish(user *store.UserState, eventQuestC
outcome := h.evaluateFinishOutcome(user, questId) outcome := h.evaluateFinishOutcome(user, questId)
if !isRetired { if !isRetired {
h.applyQuestVictory(user, questId, outcome, nowMillis) h.applyQuestVictory(user, questId, &outcome, nowMillis)
} }
if isRetired && !isAnnihilated && quest.Stamina > 1 { if isRetired && !isAnnihilated && quest.Stamina > 1 {
+1 -1
View File
@@ -42,7 +42,7 @@ func (h *QuestHandler) HandleExtraQuestFinish(user *store.UserState, questId int
outcome := h.evaluateFinishOutcome(user, questId) outcome := h.evaluateFinishOutcome(user, questId)
if !isRetired { if !isRetired {
h.applyQuestVictory(user, questId, outcome, nowMillis) h.applyQuestVictory(user, questId, &outcome, nowMillis)
} }
if isRetired && !isAnnihilated && quest.Stamina > 1 { if isRetired && !isAnnihilated && quest.Stamina > 1 {
+1
View File
@@ -20,6 +20,7 @@ type FinishOutcome struct {
MissionClearCompleteRewards []RewardGrant MissionClearCompleteRewards []RewardGrant
BigWinClearedQuestMissionIds []int32 BigWinClearedQuestMissionIds []int32
IsBigWin bool IsBigWin bool
ChangedWeaponStoryIds []int32
} }
type QuestHandler struct { type QuestHandler struct {
+6 -4
View File
@@ -112,12 +112,14 @@ func (h *QuestHandler) handleQuestStartInternal(user *store.UserState, questId i
user.Quests[questId] = questState user.Quests[questId] = questState
} }
func (h *QuestHandler) applyQuestVictory(user *store.UserState, questId int32, outcome FinishOutcome, nowMillis int64) { func (h *QuestHandler) applyQuestVictory(user *store.UserState, questId int32, outcome *FinishOutcome, nowMillis int64) {
questState := user.Quests[questId] questState := user.Quests[questId]
if !questState.IsRewardGranted { if !questState.IsRewardGranted {
h.applyQuestRewards(user, questId, nowMillis) h.applyQuestRewards(user, questId, nowMillis)
h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeHalfResult, nowMillis) outcome.ChangedWeaponStoryIds = append(outcome.ChangedWeaponStoryIds,
h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeFullResult, nowMillis) h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeHalfResult, nowMillis)...)
outcome.ChangedWeaponStoryIds = append(outcome.ChangedWeaponStoryIds,
h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeFullResult, nowMillis)...)
questState.IsRewardGranted = true questState.IsRewardGranted = true
} }
for _, drop := range outcome.DropRewards { for _, drop := range outcome.DropRewards {
@@ -141,7 +143,7 @@ func (h *QuestHandler) HandleQuestFinish(user *store.UserState, questId int32, i
outcome := h.evaluateFinishOutcome(user, questId) outcome := h.evaluateFinishOutcome(user, questId)
if !isRetired { if !isRetired {
h.applyQuestVictory(user, questId, outcome, nowMillis) h.applyQuestVictory(user, questId, &outcome, nowMillis)
} }
if isRetired && !isAnnihilated && quest.Stamina > 1 { if isRetired && !isAnnihilated && quest.Stamina > 1 {
+13 -7
View File
@@ -316,8 +316,8 @@ func (h *QuestHandler) grantParts(user *store.UserState, partsId int32, nowMilli
} }
} }
func (h *QuestHandler) grantWeaponStoryUnlock(user *store.UserState, weaponId, storyIndex int32, nowMillis int64) { func (h *QuestHandler) grantWeaponStoryUnlock(user *store.UserState, weaponId, storyIndex int32, nowMillis int64) bool {
store.GrantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) return store.GrantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis)
} }
var tutorialCompanionChoices = map[int32]int32{ var tutorialCompanionChoices = map[int32]int32{
@@ -354,11 +354,12 @@ func (h *QuestHandler) BattleDropRewards(questId int32) []masterdata.BattleDropI
return h.BattleDropsByQuestId[questId] return h.BattleDropsByQuestId[questId]
} }
func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserState, questId int32, resultType model.QuestResultType, nowMillis int64) { func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserState, questId int32, resultType model.QuestResultType, nowMillis int64) []int32 {
var changedIds []int32
if resultType == model.QuestResultTypeHalfResult { if resultType == model.QuestResultTypeHalfResult {
questDef, ok := h.QuestById[questId] questDef, ok := h.QuestById[questId]
if !ok { if !ok {
return return nil
} }
rewardGroupId := h.firstClearRewardGroupId(user, questDef) rewardGroupId := h.firstClearRewardGroupId(user, questDef)
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] { for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
@@ -373,22 +374,27 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat
groupId := weapon.WeaponStoryReleaseConditionGroupId groupId := weapon.WeaponStoryReleaseConditionGroupId
for _, cond := range h.ReleaseConditionsByGroupId[groupId] { for _, cond := range h.ReleaseConditionsByGroupId[groupId] {
if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 { if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 {
h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
changedIds = append(changedIds, weaponId)
}
} }
} }
} }
return return changedIds
} }
if resultType == model.QuestResultTypeFullResult { if resultType == model.QuestResultTypeFullResult {
for groupId, conditions := range h.ReleaseConditionsByGroupId { for groupId, conditions := range h.ReleaseConditionsByGroupId {
for _, cond := range conditions { for _, cond := range conditions {
if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId { if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId {
for _, weaponId := range h.WeaponIdsByReleaseConditionGroupId[groupId] { for _, weaponId := range h.WeaponIdsByReleaseConditionGroupId[groupId] {
h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
changedIds = append(changedIds, weaponId)
}
} }
break break
} }
} }
} }
} }
return changedIds
} }
+2 -1
View File
@@ -49,10 +49,11 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
"IUserMaterial", "IUserConsumableItem", "IUserGem", "IUserMaterial", "IUserConsumableItem", "IUserGem",
"IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter", "IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter",
"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility",
"IUserWeaponNote", "IUserWeaponStory", "IUserWeaponNote",
"IUserCageOrnamentReward", "IUserCageOrnamentReward",
}, },
)) ))
userdata.AddWeaponStoryDiff(diff, user, s.granter.DrainChangedStoryWeaponIds())
return &pb.ReceiveRewardResponse{ return &pb.ReceiveRewardResponse{
CageOrnamentReward: []*pb.CageOrnamentReward{ CageOrnamentReward: []*pb.CageOrnamentReward{
+1 -1
View File
@@ -26,7 +26,6 @@ var gachaDiffTables = []string{
"IUserWeaponNote", "IUserWeaponNote",
"IUserWeaponSkill", "IUserWeaponSkill",
"IUserWeaponAbility", "IUserWeaponAbility",
"IUserWeaponStory",
"IUserCharacter", "IUserCharacter",
"IUserMaterial", "IUserMaterial",
} }
@@ -296,6 +295,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
userdata.FullClientTableMap(updatedUser), userdata.FullClientTableMap(updatedUser),
gachaDiffTables, gachaDiffTables,
)) ))
userdata.AddWeaponStoryDiff(diff, updatedUser, s.handler.Granter.DrainChangedStoryWeaponIds())
return &pb.DrawResponse{ return &pb.DrawResponse{
NextGacha: nextGacha, NextGacha: nextGacha,
+41 -35
View File
@@ -8,6 +8,7 @@ import (
"lunar-tear/server/internal/gametime" "lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/questflow" "lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store" "lunar-tear/server/internal/store"
"lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb" emptypb "google.golang.org/protobuf/types/known/emptypb"
) )
@@ -52,6 +53,28 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
outcome = s.engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) outcome = s.engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
}) })
diff := buildSelectedQuestDiff(user, []string{
"IUserQuest",
"IUserQuestMission",
"IUserEventQuestProgressStatus",
"IUserStatus",
"IUserGem",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
})
userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
return &pb.FinishEventQuestResponse{ return &pb.FinishEventQuestResponse{
DropReward: toProtoRewards(outcome.DropRewards), DropReward: toProtoRewards(outcome.DropRewards),
FirstClearReward: toProtoRewards(outcome.FirstClearRewards), FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
@@ -61,27 +84,7 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
IsBigWin: outcome.IsBigWin, IsBigWin: outcome.IsBigWin,
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
UserStatusCampaignReward: []*pb.QuestReward{}, UserStatusCampaignReward: []*pb.QuestReward{},
DiffUserData: buildSelectedQuestDiff(user, []string{ DiffUserData: diff,
"IUserQuest",
"IUserQuestMission",
"IUserEventQuestProgressStatus",
"IUserStatus",
"IUserGem",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserWeaponStory",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
}),
}, nil }, nil
} }
@@ -111,21 +114,24 @@ func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context,
s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
}) })
diff := buildSelectedQuestDiff(user, []string{
"IUserEventQuestProgressStatus",
"IUserCharacter",
"IUserCostume",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
})
userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
return &pb.UpdateEventQuestSceneProgressResponse{ return &pb.UpdateEventQuestSceneProgressResponse{
DiffUserData: buildSelectedQuestDiff(user, []string{ DiffUserData: diff,
"IUserEventQuestProgressStatus",
"IUserCharacter",
"IUserCostume",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
}),
}, nil }, nil
} }
+41 -35
View File
@@ -8,6 +8,7 @@ import (
"lunar-tear/server/internal/gametime" "lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/questflow" "lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store" "lunar-tear/server/internal/store"
"lunar-tear/server/internal/userdata"
) )
func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) { func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) {
@@ -50,6 +51,28 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
outcome = s.engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) outcome = s.engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
}) })
diff := buildSelectedQuestDiff(user, []string{
"IUserQuest",
"IUserQuestMission",
"IUserExtraQuestProgressStatus",
"IUserStatus",
"IUserGem",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
})
userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
return &pb.FinishExtraQuestResponse{ return &pb.FinishExtraQuestResponse{
DropReward: toProtoRewards(outcome.DropRewards), DropReward: toProtoRewards(outcome.DropRewards),
FirstClearReward: toProtoRewards(outcome.FirstClearRewards), FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
@@ -58,27 +81,7 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
IsBigWin: outcome.IsBigWin, IsBigWin: outcome.IsBigWin,
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
UserStatusCampaignReward: []*pb.QuestReward{}, UserStatusCampaignReward: []*pb.QuestReward{},
DiffUserData: buildSelectedQuestDiff(user, []string{ DiffUserData: diff,
"IUserQuest",
"IUserQuestMission",
"IUserExtraQuestProgressStatus",
"IUserStatus",
"IUserGem",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserWeaponStory",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
}),
}, nil }, nil
} }
@@ -119,20 +122,23 @@ func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context,
s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
}) })
diff := buildSelectedQuestDiff(user, []string{
"IUserExtraQuestProgressStatus",
"IUserCharacter",
"IUserCostume",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
})
userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
return &pb.UpdateExtraQuestSceneProgressResponse{ return &pb.UpdateExtraQuestSceneProgressResponse{
DiffUserData: buildSelectedQuestDiff(user, []string{ DiffUserData: diff,
"IUserExtraQuestProgressStatus",
"IUserCharacter",
"IUserCostume",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
}),
}, nil }, nil
} }
+52 -48
View File
@@ -41,30 +41,32 @@ func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, re
s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
}) })
diff := buildSelectedQuestDiff(user, []string{
"IUserMainQuestFlowStatus",
"IUserMainQuestMainFlowStatus",
"IUserMainQuestProgressStatus",
"IUserMainQuestSeasonRoute",
"IUserPortalCageStatus",
"IUserSideStoryQuestSceneProgressStatus",
"IUserQuest",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
})
userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
return &pb.UpdateMainFlowSceneProgressResponse{ return &pb.UpdateMainFlowSceneProgressResponse{
DiffUserData: buildSelectedQuestDiff(user, []string{ DiffUserData: diff,
"IUserMainQuestFlowStatus",
"IUserMainQuestMainFlowStatus",
"IUserMainQuestProgressStatus",
"IUserMainQuestSeasonRoute",
"IUserPortalCageStatus",
"IUserSideStoryQuestSceneProgressStatus",
"IUserQuest",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserWeaponStory",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
}),
}, nil }, nil
} }
@@ -169,6 +171,32 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
outcome = s.engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) outcome = s.engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
}) })
diff := buildSelectedQuestDiff(user, []string{
"IUserQuest",
"IUserQuestMission",
"IUserMainQuestFlowStatus",
"IUserMainQuestMainFlowStatus",
"IUserMainQuestProgressStatus",
"IUserMainQuestSeasonRoute",
"IUserMainQuestReplayFlowStatus",
"IUserStatus",
"IUserGem",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
})
userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
return &pb.FinishMainQuestResponse{ return &pb.FinishMainQuestResponse{
DropReward: toProtoRewards(outcome.DropRewards), DropReward: toProtoRewards(outcome.DropRewards),
FirstClearReward: toProtoRewards(outcome.FirstClearRewards), FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
@@ -179,31 +207,7 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards), ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards),
UserStatusCampaignReward: []*pb.QuestReward{}, UserStatusCampaignReward: []*pb.QuestReward{},
DiffUserData: buildSelectedQuestDiff(user, []string{ DiffUserData: diff,
"IUserQuest",
"IUserQuestMission",
"IUserMainQuestFlowStatus",
"IUserMainQuestMainFlowStatus",
"IUserMainQuestProgressStatus",
"IUserMainQuestSeasonRoute",
"IUserMainQuestReplayFlowStatus",
"IUserStatus",
"IUserGem",
"IUserCharacter",
"IUserCostume",
"IUserCostumeActiveSkill",
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponNote",
"IUserWeaponStory",
"IUserCompanion",
"IUserConsumableItem",
"IUserMaterial",
"IUserImportantItem",
"IUserParts",
"IUserPartsGroupNote",
}),
}, nil }, nil
} }
+1 -1
View File
@@ -32,7 +32,6 @@ var shopDiffTables = []string{
"IUserWeaponSkill", "IUserWeaponSkill",
"IUserWeaponAbility", "IUserWeaponAbility",
"IUserWeaponNote", "IUserWeaponNote",
"IUserWeaponStory",
} }
type ShopServiceServer struct { type ShopServiceServer struct {
@@ -92,6 +91,7 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
tables := userdata.FullClientTableMap(snapshot) tables := userdata.FullClientTableMap(snapshot)
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables)) diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables))
userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds())
return &pb.BuyResponse{ return &pb.BuyResponse{
OverflowPossession: []*pb.Possession{}, OverflowPossession: []*pb.Possession{},
-1
View File
@@ -28,7 +28,6 @@ var startedGameStartTables = []string{
"IUserQuestMission", "IUserQuestMission",
"IUserTutorialProgress", "IUserTutorialProgress",
"IUserWeaponNote", "IUserWeaponNote",
"IUserWeaponStory",
"IUserCostumeActiveSkill", "IUserCostumeActiveSkill",
"IUserDeckTypeNote", "IUserDeckTypeNote",
"IUserDeckSubWeaponGroup", "IUserDeckSubWeaponGroup",
+44 -7
View File
@@ -20,7 +20,6 @@ var weaponDiffTables = []string{
"IUserWeaponAbility", "IUserWeaponAbility",
"IUserMaterial", "IUserMaterial",
"IUserConsumableItem", "IUserConsumableItem",
"IUserWeaponStory",
} }
var limitBreakDiffTables = []string{ var limitBreakDiffTables = []string{
@@ -98,6 +97,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
userId := currentUserId(ctx, s.users, s.sessions) userId := currentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis() nowMillis := gametime.NowMillis()
var changedStoryIds []int32
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid] weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok { if !ok {
@@ -149,6 +149,8 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
weapon.LatestVersion = nowMillis weapon.LatestVersion = nowMillis
user.Weapons[req.UserWeaponUuid] = weapon user.Weapons[req.UserWeaponUuid] = weapon
log.Printf("[WeaponService] EnhanceByMaterial: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level) log.Printf("[WeaponService] EnhanceByMaterial: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level)
changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("weapon enhance by material: %w", err) return nil, fmt.Errorf("weapon enhance by material: %w", err)
@@ -156,6 +158,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
tables := userdata.FullClientTableMap(snapshot) tables := userdata.FullClientTableMap(snapshot)
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables)) diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables))
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
return &pb.EnhanceByMaterialResponse{ return &pb.EnhanceByMaterialResponse{
IsGreatSuccess: false, IsGreatSuccess: false,
@@ -227,6 +230,7 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
userId := currentUserId(ctx, s.users, s.sessions) userId := currentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis() nowMillis := gametime.NowMillis()
var changedStoryIds []int32
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid] weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok { if !ok {
@@ -286,7 +290,7 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
log.Printf("[WeaponService] Evolve: weaponId %d -> %d", wm.WeaponId, evolvedId) log.Printf("[WeaponService] Evolve: weaponId %d -> %d", wm.WeaponId, evolvedId)
s.checkEvolutionStoryUnlocks(user, evolvedId, nowMillis) changedStoryIds = s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis)
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("weapon evolve: %w", err) return nil, fmt.Errorf("weapon evolve: %w", err)
@@ -294,6 +298,7 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
tables := userdata.FullClientTableMap(snapshot) tables := userdata.FullClientTableMap(snapshot)
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables)) diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables))
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
return &pb.EvolveResponse{DiffUserData: diff}, nil return &pb.EvolveResponse{DiffUserData: diff}, nil
} }
@@ -665,6 +670,7 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}) Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"})
var changedStoryIds []int32
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid] weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok { if !ok {
@@ -725,6 +731,8 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
weapon.LatestVersion = nowMillis weapon.LatestVersion = nowMillis
user.Weapons[req.UserWeaponUuid] = weapon user.Weapons[req.UserWeaponUuid] = weapon
log.Printf("[WeaponService] EnhanceByWeapon: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level) log.Printf("[WeaponService] EnhanceByWeapon: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level)
changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("weapon enhance by weapon: %w", err) return nil, fmt.Errorf("weapon enhance by weapon: %w", err)
@@ -732,6 +740,7 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), weaponDiffTables) tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), weaponDiffTables)
diff := tracker.Apply(snapshot, tables) diff := tracker.Apply(snapshot, tables)
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
return &pb.EnhanceByWeaponResponse{ return &pb.EnhanceByWeaponResponse{
IsGreatSuccess: false, IsGreatSuccess: false,
@@ -740,21 +749,49 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
}, nil }, nil
} }
func (s *WeaponServiceServer) checkEvolutionStoryUnlocks(user *store.UserState, weaponId int32, nowMillis int64) { func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) []int32 {
wm, ok := s.catalog.Weapons[weaponId] wm, ok := s.catalog.Weapons[weaponId]
if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 { if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 {
return return nil
} }
evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId] evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId]
conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId] conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId]
changed := false
for _, cond := range conditions { for _, cond := range conditions {
granted := false
switch cond.WeaponStoryReleaseConditionType { switch cond.WeaponStoryReleaseConditionType {
case model.WeaponStoryReleaseConditionTypeAcquisition:
granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
case model.WeaponStoryReleaseConditionTypeReachSpecifiedLevel:
if level >= cond.ConditionValue {
granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
case model.WeaponStoryReleaseConditionTypeReachInitialMaxLevel:
if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
if level >= maxFunc.Evaluate(0) {
granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
}
case model.WeaponStoryReleaseConditionTypeReachOnceEvolvedMaxLevel:
if hasEvo && evoOrder >= 1 {
if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
if level >= maxFunc.Evaluate(0) {
granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
}
}
case model.WeaponStoryReleaseConditionTypeReachSpecifiedEvolutionCount: case model.WeaponStoryReleaseConditionTypeReachSpecifiedEvolutionCount:
if hasEvo && evoOrder >= cond.ConditionValue { if hasEvo && evoOrder >= cond.ConditionValue {
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
} }
case model.WeaponStoryReleaseConditionTypeAcquisition: }
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) if granted {
changed = true
} }
} }
if changed {
return []int32{weaponId}
}
return nil
} }
+24 -7
View File
@@ -104,6 +104,14 @@ type PossessionGranter struct {
WeaponSkillSlots map[int32][]int32 WeaponSkillSlots map[int32][]int32
WeaponAbilitySlots map[int32][]int32 WeaponAbilitySlots map[int32][]int32
ReleaseConditions map[int32][]WeaponStoryReleaseCond ReleaseConditions map[int32][]WeaponStoryReleaseCond
LastChangedStoryWeaponIds []int32
}
func (g *PossessionGranter) DrainChangedStoryWeaponIds() []int32 {
ids := g.LastChangedStoryWeaponIds
g.LastChangedStoryWeaponIds = nil
return ids
} }
func (g *PossessionGranter) GrantFull(user *UserState, possessionType model.PossessionType, possessionId, count int32, nowMillis int64) { func (g *PossessionGranter) GrantFull(user *UserState, possessionType model.PossessionType, possessionId, count int32, nowMillis int64) {
@@ -170,16 +178,24 @@ func (g *PossessionGranter) GrantWeapon(user *UserState, weaponId int32, nowMill
g.populateWeaponSkillsAbilities(user, key, weapon) g.populateWeaponSkillsAbilities(user, key, weapon)
if weapon.WeaponStoryReleaseConditionGroupId != 0 { if weapon.WeaponStoryReleaseConditionGroupId != 0 {
changed := false
for _, cond := range g.ReleaseConditions[weapon.WeaponStoryReleaseConditionGroupId] { for _, cond := range g.ReleaseConditions[weapon.WeaponStoryReleaseConditionGroupId] {
switch cond.WeaponStoryReleaseConditionType { switch cond.WeaponStoryReleaseConditionType {
case model.WeaponStoryReleaseConditionTypeAcquisition: case model.WeaponStoryReleaseConditionTypeAcquisition:
grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) if grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
changed = true
}
case model.WeaponStoryReleaseConditionTypeQuestClear: case model.WeaponStoryReleaseConditionTypeQuestClear:
if qs, ok := user.Quests[cond.ConditionValue]; ok && qs.QuestStateType == model.UserQuestStateTypeCleared { if qs, ok := user.Quests[cond.ConditionValue]; ok && qs.QuestStateType == model.UserQuestStateTypeCleared {
grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) if grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
changed = true
}
} }
} }
} }
if changed {
g.LastChangedStoryWeaponIds = append(g.LastChangedStoryWeaponIds, weaponId)
}
} }
} }
@@ -208,11 +224,11 @@ func (g *PossessionGranter) populateWeaponSkillsAbilities(user *UserState, weapo
} }
} }
func GrantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) { func GrantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) bool {
grantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) return grantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis)
} }
func grantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) { func grantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) bool {
hasWeapon := false hasWeapon := false
for _, row := range user.Weapons { for _, row := range user.Weapons {
if row.WeaponId == weaponId { if row.WeaponId == weaponId {
@@ -222,20 +238,21 @@ func grantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMill
} }
if !hasWeapon { if !hasWeapon {
log.Printf("[grantWeaponStoryUnlock] skipping weaponId=%d (weapon not in user.Weapons)", weaponId) log.Printf("[grantWeaponStoryUnlock] skipping weaponId=%d (weapon not in user.Weapons)", weaponId)
return return false
} }
if user.WeaponStories == nil { if user.WeaponStories == nil {
user.WeaponStories = make(map[int32]WeaponStoryState) user.WeaponStories = make(map[int32]WeaponStoryState)
} }
cur := user.WeaponStories[weaponId] cur := user.WeaponStories[weaponId]
if storyIndex <= cur.ReleasedMaxStoryIndex { if storyIndex <= cur.ReleasedMaxStoryIndex {
return return false
} }
user.WeaponStories[weaponId] = WeaponStoryState{ user.WeaponStories[weaponId] = WeaponStoryState{
WeaponId: weaponId, WeaponId: weaponId,
ReleasedMaxStoryIndex: storyIndex, ReleasedMaxStoryIndex: storyIndex,
LatestVersion: nowMillis, LatestVersion: nowMillis,
} }
return true
} }
func EnsureDefaultDeck(user *UserState, nowMillis int64) { func EnsureDefaultDeck(user *UserState, nowMillis int64) {
@@ -258,6 +258,27 @@ func sortedWeaponStoryRecords(user store.UserState) []map[string]any {
return records return records
} }
func WeaponStoryRecordsForIds(user store.UserState, weaponIds []int32) string {
if len(weaponIds) == 0 {
return "[]"
}
records := make([]map[string]any, 0, len(weaponIds))
for _, weaponId := range weaponIds {
row, ok := user.WeaponStories[weaponId]
if !ok {
continue
}
records = append(records, map[string]any{
"userId": user.UserId,
"weaponId": row.WeaponId,
"releasedMaxStoryIndex": row.ReleasedMaxStoryIndex,
"latestVersion": row.LatestVersion,
})
}
s, _ := encodeJSONMaps(records...)
return s
}
func sortedWeaponNoteRecords(user store.UserState) []map[string]any { func sortedWeaponNoteRecords(user store.UserState) []map[string]any {
weaponIds := make([]int32, 0, len(user.WeaponNotes)) weaponIds := make([]int32, 0, len(user.WeaponNotes))
for id := range user.WeaponNotes { for id := range user.WeaponNotes {
@@ -170,3 +170,13 @@ func BuildDiffFromTablesOrdered(tables map[string]string, order []string) map[st
} }
return diff return diff
} }
func AddWeaponStoryDiff(diff map[string]*pb.DiffData, user store.UserState, weaponIds []int32) {
if len(weaponIds) == 0 {
return
}
diff["IUserWeaponStory"] = &pb.DiffData{
UpdateRecordsJson: WeaponStoryRecordsForIds(user, weaponIds),
DeleteKeysJson: "[]",
}
}