diff --git a/server/internal/questflow/bighunt_quest.go b/server/internal/questflow/bighunt_quest.go index c37918d..781fe5b 100644 --- a/server/internal/questflow/bighunt_quest.go +++ b/server/internal/questflow/bighunt_quest.go @@ -35,7 +35,7 @@ func (h *QuestHandler) HandleBigHuntQuestFinish(user *store.UserState, questId i outcome := h.evaluateFinishOutcome(user, questId) if !isRetired { - h.applyQuestVictory(user, questId, outcome, nowMillis) + h.applyQuestVictory(user, questId, &outcome, nowMillis) } if isRetired && !isAnnihilated && quest.Stamina > 1 { diff --git a/server/internal/questflow/event_quest.go b/server/internal/questflow/event_quest.go index 58437d1..d23aec0 100644 --- a/server/internal/questflow/event_quest.go +++ b/server/internal/questflow/event_quest.go @@ -44,7 +44,7 @@ func (h *QuestHandler) HandleEventQuestFinish(user *store.UserState, eventQuestC outcome := h.evaluateFinishOutcome(user, questId) if !isRetired { - h.applyQuestVictory(user, questId, outcome, nowMillis) + h.applyQuestVictory(user, questId, &outcome, nowMillis) } if isRetired && !isAnnihilated && quest.Stamina > 1 { diff --git a/server/internal/questflow/extra_quest.go b/server/internal/questflow/extra_quest.go index 16cef74..58c6cb6 100644 --- a/server/internal/questflow/extra_quest.go +++ b/server/internal/questflow/extra_quest.go @@ -42,7 +42,7 @@ func (h *QuestHandler) HandleExtraQuestFinish(user *store.UserState, questId int outcome := h.evaluateFinishOutcome(user, questId) if !isRetired { - h.applyQuestVictory(user, questId, outcome, nowMillis) + h.applyQuestVictory(user, questId, &outcome, nowMillis) } if isRetired && !isAnnihilated && quest.Stamina > 1 { diff --git a/server/internal/questflow/handler.go b/server/internal/questflow/handler.go index c8f51cf..13c3eae 100644 --- a/server/internal/questflow/handler.go +++ b/server/internal/questflow/handler.go @@ -20,6 +20,7 @@ type FinishOutcome struct { MissionClearCompleteRewards []RewardGrant BigWinClearedQuestMissionIds []int32 IsBigWin bool + ChangedWeaponStoryIds []int32 } type QuestHandler struct { diff --git a/server/internal/questflow/quest.go b/server/internal/questflow/quest.go index 4c6626c..500b115 100644 --- a/server/internal/questflow/quest.go +++ b/server/internal/questflow/quest.go @@ -112,12 +112,14 @@ func (h *QuestHandler) handleQuestStartInternal(user *store.UserState, questId i 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] if !questState.IsRewardGranted { h.applyQuestRewards(user, questId, nowMillis) - h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeHalfResult, nowMillis) - h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeFullResult, nowMillis) + outcome.ChangedWeaponStoryIds = append(outcome.ChangedWeaponStoryIds, + h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeHalfResult, nowMillis)...) + outcome.ChangedWeaponStoryIds = append(outcome.ChangedWeaponStoryIds, + h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeFullResult, nowMillis)...) questState.IsRewardGranted = true } for _, drop := range outcome.DropRewards { @@ -141,7 +143,7 @@ func (h *QuestHandler) HandleQuestFinish(user *store.UserState, questId int32, i outcome := h.evaluateFinishOutcome(user, questId) if !isRetired { - h.applyQuestVictory(user, questId, outcome, nowMillis) + h.applyQuestVictory(user, questId, &outcome, nowMillis) } if isRetired && !isAnnihilated && quest.Stamina > 1 { diff --git a/server/internal/questflow/rewards.go b/server/internal/questflow/rewards.go index cbf6f28..cb39793 100644 --- a/server/internal/questflow/rewards.go +++ b/server/internal/questflow/rewards.go @@ -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) { - store.GrantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) +func (h *QuestHandler) grantWeaponStoryUnlock(user *store.UserState, weaponId, storyIndex int32, nowMillis int64) bool { + return store.GrantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) } var tutorialCompanionChoices = map[int32]int32{ @@ -354,11 +354,12 @@ func (h *QuestHandler) BattleDropRewards(questId int32) []masterdata.BattleDropI 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 { questDef, ok := h.QuestById[questId] if !ok { - return + return nil } rewardGroupId := h.firstClearRewardGroupId(user, questDef) for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] { @@ -373,22 +374,27 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat groupId := weapon.WeaponStoryReleaseConditionGroupId for _, cond := range h.ReleaseConditionsByGroupId[groupId] { 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 { for groupId, conditions := range h.ReleaseConditionsByGroupId { for _, cond := range conditions { if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId { 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 } } } } + return changedIds } diff --git a/server/internal/service/cageornament.go b/server/internal/service/cageornament.go index 8e7e924..2518bcf 100644 --- a/server/internal/service/cageornament.go +++ b/server/internal/service/cageornament.go @@ -49,10 +49,11 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R "IUserMaterial", "IUserConsumableItem", "IUserGem", "IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter", "IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", - "IUserWeaponNote", "IUserWeaponStory", + "IUserWeaponNote", "IUserCageOrnamentReward", }, )) + userdata.AddWeaponStoryDiff(diff, user, s.granter.DrainChangedStoryWeaponIds()) return &pb.ReceiveRewardResponse{ CageOrnamentReward: []*pb.CageOrnamentReward{ diff --git a/server/internal/service/gacha.go b/server/internal/service/gacha.go index 38a0b5a..95bbee8 100644 --- a/server/internal/service/gacha.go +++ b/server/internal/service/gacha.go @@ -26,7 +26,6 @@ var gachaDiffTables = []string{ "IUserWeaponNote", "IUserWeaponSkill", "IUserWeaponAbility", - "IUserWeaponStory", "IUserCharacter", "IUserMaterial", } @@ -296,6 +295,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb userdata.FullClientTableMap(updatedUser), gachaDiffTables, )) + userdata.AddWeaponStoryDiff(diff, updatedUser, s.handler.Granter.DrainChangedStoryWeaponIds()) return &pb.DrawResponse{ NextGacha: nextGacha, diff --git a/server/internal/service/quest_event.go b/server/internal/service/quest_event.go index 14cfe40..5fb8323 100644 --- a/server/internal/service/quest_event.go +++ b/server/internal/service/quest_event.go @@ -8,6 +8,7 @@ import ( "lunar-tear/server/internal/gametime" "lunar-tear/server/internal/questflow" "lunar-tear/server/internal/store" + "lunar-tear/server/internal/userdata" 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) }) + 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{ DropReward: toProtoRewards(outcome.DropRewards), FirstClearReward: toProtoRewards(outcome.FirstClearRewards), @@ -61,27 +84,7 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis IsBigWin: outcome.IsBigWin, BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, UserStatusCampaignReward: []*pb.QuestReward{}, - DiffUserData: buildSelectedQuestDiff(user, []string{ - "IUserQuest", - "IUserQuestMission", - "IUserEventQuestProgressStatus", - "IUserStatus", - "IUserGem", - "IUserCharacter", - "IUserCostume", - "IUserCostumeActiveSkill", - "IUserWeapon", - "IUserWeaponSkill", - "IUserWeaponAbility", - "IUserWeaponNote", - "IUserWeaponStory", - "IUserCompanion", - "IUserConsumableItem", - "IUserMaterial", - "IUserImportantItem", - "IUserParts", - "IUserPartsGroupNote", - }), + DiffUserData: diff, }, nil } @@ -111,21 +114,24 @@ func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context, 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{ - DiffUserData: buildSelectedQuestDiff(user, []string{ - "IUserEventQuestProgressStatus", - "IUserCharacter", - "IUserCostume", - "IUserWeapon", - "IUserWeaponSkill", - "IUserWeaponAbility", - "IUserCompanion", - "IUserConsumableItem", - "IUserMaterial", - "IUserImportantItem", - "IUserParts", - "IUserPartsGroupNote", - }), + DiffUserData: diff, }, nil } diff --git a/server/internal/service/quest_extra.go b/server/internal/service/quest_extra.go index 02b6bee..e89ab69 100644 --- a/server/internal/service/quest_extra.go +++ b/server/internal/service/quest_extra.go @@ -8,6 +8,7 @@ import ( "lunar-tear/server/internal/gametime" "lunar-tear/server/internal/questflow" "lunar-tear/server/internal/store" + "lunar-tear/server/internal/userdata" ) 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) }) + 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{ DropReward: toProtoRewards(outcome.DropRewards), FirstClearReward: toProtoRewards(outcome.FirstClearRewards), @@ -58,27 +81,7 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis IsBigWin: outcome.IsBigWin, BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, UserStatusCampaignReward: []*pb.QuestReward{}, - DiffUserData: buildSelectedQuestDiff(user, []string{ - "IUserQuest", - "IUserQuestMission", - "IUserExtraQuestProgressStatus", - "IUserStatus", - "IUserGem", - "IUserCharacter", - "IUserCostume", - "IUserCostumeActiveSkill", - "IUserWeapon", - "IUserWeaponSkill", - "IUserWeaponAbility", - "IUserWeaponNote", - "IUserWeaponStory", - "IUserCompanion", - "IUserConsumableItem", - "IUserMaterial", - "IUserImportantItem", - "IUserParts", - "IUserPartsGroupNote", - }), + DiffUserData: diff, }, nil } @@ -119,20 +122,23 @@ func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context, 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{ - DiffUserData: buildSelectedQuestDiff(user, []string{ - "IUserExtraQuestProgressStatus", - "IUserCharacter", - "IUserCostume", - "IUserWeapon", - "IUserWeaponSkill", - "IUserWeaponAbility", - "IUserCompanion", - "IUserConsumableItem", - "IUserMaterial", - "IUserImportantItem", - "IUserParts", - "IUserPartsGroupNote", - }), + DiffUserData: diff, }, nil } diff --git a/server/internal/service/quest_main.go b/server/internal/service/quest_main.go index 1d02501..c1e3a41 100644 --- a/server/internal/service/quest_main.go +++ b/server/internal/service/quest_main.go @@ -41,30 +41,32 @@ func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, re 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{ - DiffUserData: buildSelectedQuestDiff(user, []string{ - "IUserMainQuestFlowStatus", - "IUserMainQuestMainFlowStatus", - "IUserMainQuestProgressStatus", - "IUserMainQuestSeasonRoute", - "IUserPortalCageStatus", - "IUserSideStoryQuestSceneProgressStatus", - "IUserQuest", - "IUserCharacter", - "IUserCostume", - "IUserCostumeActiveSkill", - "IUserWeapon", - "IUserWeaponSkill", - "IUserWeaponAbility", - "IUserWeaponNote", - "IUserWeaponStory", - "IUserCompanion", - "IUserConsumableItem", - "IUserMaterial", - "IUserImportantItem", - "IUserParts", - "IUserPartsGroupNote", - }), + DiffUserData: diff, }, 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) }) + 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{ DropReward: toProtoRewards(outcome.DropRewards), FirstClearReward: toProtoRewards(outcome.FirstClearRewards), @@ -179,31 +207,7 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards), UserStatusCampaignReward: []*pb.QuestReward{}, - DiffUserData: buildSelectedQuestDiff(user, []string{ - "IUserQuest", - "IUserQuestMission", - "IUserMainQuestFlowStatus", - "IUserMainQuestMainFlowStatus", - "IUserMainQuestProgressStatus", - "IUserMainQuestSeasonRoute", - "IUserMainQuestReplayFlowStatus", - "IUserStatus", - "IUserGem", - "IUserCharacter", - "IUserCostume", - "IUserCostumeActiveSkill", - "IUserWeapon", - "IUserWeaponSkill", - "IUserWeaponAbility", - "IUserWeaponNote", - "IUserWeaponStory", - "IUserCompanion", - "IUserConsumableItem", - "IUserMaterial", - "IUserImportantItem", - "IUserParts", - "IUserPartsGroupNote", - }), + DiffUserData: diff, }, nil } diff --git a/server/internal/service/shop.go b/server/internal/service/shop.go index cfdac7b..f081377 100644 --- a/server/internal/service/shop.go +++ b/server/internal/service/shop.go @@ -32,7 +32,6 @@ var shopDiffTables = []string{ "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeaponNote", - "IUserWeaponStory", } type ShopServiceServer struct { @@ -92,6 +91,7 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu tables := userdata.FullClientTableMap(snapshot) diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables)) + userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds()) return &pb.BuyResponse{ OverflowPossession: []*pb.Possession{}, diff --git a/server/internal/service/state.go b/server/internal/service/state.go index 290b31a..ea10b1e 100644 --- a/server/internal/service/state.go +++ b/server/internal/service/state.go @@ -28,7 +28,6 @@ var startedGameStartTables = []string{ "IUserQuestMission", "IUserTutorialProgress", "IUserWeaponNote", - "IUserWeaponStory", "IUserCostumeActiveSkill", "IUserDeckTypeNote", "IUserDeckSubWeaponGroup", diff --git a/server/internal/service/weapon.go b/server/internal/service/weapon.go index 6844a56..f87c4c6 100644 --- a/server/internal/service/weapon.go +++ b/server/internal/service/weapon.go @@ -20,7 +20,6 @@ var weaponDiffTables = []string{ "IUserWeaponAbility", "IUserMaterial", "IUserConsumableItem", - "IUserWeaponStory", } var limitBreakDiffTables = []string{ @@ -98,6 +97,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh userId := currentUserId(ctx, s.users, s.sessions) nowMillis := gametime.NowMillis() + var changedStoryIds []int32 snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { weapon, ok := user.Weapons[req.UserWeaponUuid] if !ok { @@ -149,6 +149,8 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh weapon.LatestVersion = nowMillis user.Weapons[req.UserWeaponUuid] = weapon 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 { 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) diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables)) + userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) return &pb.EnhanceByMaterialResponse{ IsGreatSuccess: false, @@ -227,6 +230,7 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest) userId := currentUserId(ctx, s.users, s.sessions) nowMillis := gametime.NowMillis() + var changedStoryIds []int32 snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { weapon, ok := user.Weapons[req.UserWeaponUuid] 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) - s.checkEvolutionStoryUnlocks(user, evolvedId, nowMillis) + changedStoryIds = s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis) }) if err != nil { 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) diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables)) + userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) 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("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}) + var changedStoryIds []int32 snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { weapon, ok := user.Weapons[req.UserWeaponUuid] if !ok { @@ -725,6 +731,8 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan weapon.LatestVersion = nowMillis user.Weapons[req.UserWeaponUuid] = weapon 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 { 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) diff := tracker.Apply(snapshot, tables) + userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) return &pb.EnhanceByWeaponResponse{ IsGreatSuccess: false, @@ -740,21 +749,49 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan }, 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] if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 { - return + return nil } evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId] conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId] + + changed := false for _, cond := range conditions { + granted := false 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: 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 } diff --git a/server/internal/store/helpers.go b/server/internal/store/helpers.go index efb62e0..8ac780f 100644 --- a/server/internal/store/helpers.go +++ b/server/internal/store/helpers.go @@ -104,6 +104,14 @@ type PossessionGranter struct { WeaponSkillSlots map[int32][]int32 WeaponAbilitySlots map[int32][]int32 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) { @@ -170,16 +178,24 @@ func (g *PossessionGranter) GrantWeapon(user *UserState, weaponId int32, nowMill g.populateWeaponSkillsAbilities(user, key, weapon) if weapon.WeaponStoryReleaseConditionGroupId != 0 { + changed := false for _, cond := range g.ReleaseConditions[weapon.WeaponStoryReleaseConditionGroupId] { switch cond.WeaponStoryReleaseConditionType { case model.WeaponStoryReleaseConditionTypeAcquisition: - grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) + if grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) { + changed = true + } case model.WeaponStoryReleaseConditionTypeQuestClear: 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) { - grantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) +func GrantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) bool { + 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 for _, row := range user.Weapons { if row.WeaponId == weaponId { @@ -222,20 +238,21 @@ func grantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMill } if !hasWeapon { log.Printf("[grantWeaponStoryUnlock] skipping weaponId=%d (weapon not in user.Weapons)", weaponId) - return + return false } if user.WeaponStories == nil { user.WeaponStories = make(map[int32]WeaponStoryState) } cur := user.WeaponStories[weaponId] if storyIndex <= cur.ReleasedMaxStoryIndex { - return + return false } user.WeaponStories[weaponId] = WeaponStoryState{ WeaponId: weaponId, ReleasedMaxStoryIndex: storyIndex, LatestVersion: nowMillis, } + return true } func EnsureDefaultDeck(user *UserState, nowMillis int64) { diff --git a/server/internal/userdata/proj_inventory.go b/server/internal/userdata/proj_inventory.go index cdaf2ff..0c08707 100644 --- a/server/internal/userdata/proj_inventory.go +++ b/server/internal/userdata/proj_inventory.go @@ -258,6 +258,27 @@ func sortedWeaponStoryRecords(user store.UserState) []map[string]any { 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 { weaponIds := make([]int32, 0, len(user.WeaponNotes)) for id := range user.WeaponNotes { diff --git a/server/internal/userdata/state_projection.go b/server/internal/userdata/state_projection.go index e257c80..7a29e2c 100644 --- a/server/internal/userdata/state_projection.go +++ b/server/internal/userdata/state_projection.go @@ -170,3 +170,13 @@ func BuildDiffFromTablesOrdered(tables map[string]string, order []string) map[st } 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: "[]", + } +}