mirror of Walter-Sparrow / lunar-tear
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+260 -150
+1 -1
server/internal/questflow/bighunt_quest.go
··· 35 35 36 36 outcome := h.evaluateFinishOutcome(user, questId) 37 37 if !isRetired { 38 - h.applyQuestVictory(user, questId, outcome, nowMillis) 38 + h.applyQuestVictory(user, questId, &outcome, nowMillis) 39 39 } 40 40 41 41 if isRetired && !isAnnihilated && quest.Stamina > 1 {
+1 -1
server/internal/questflow/event_quest.go
··· 44 44 45 45 outcome := h.evaluateFinishOutcome(user, questId) 46 46 if !isRetired { 47 - h.applyQuestVictory(user, questId, outcome, nowMillis) 47 + h.applyQuestVictory(user, questId, &outcome, nowMillis) 48 48 } 49 49 50 50 if isRetired && !isAnnihilated && quest.Stamina > 1 {
+1 -1
server/internal/questflow/extra_quest.go
··· 42 42 43 43 outcome := h.evaluateFinishOutcome(user, questId) 44 44 if !isRetired { 45 - h.applyQuestVictory(user, questId, outcome, nowMillis) 45 + h.applyQuestVictory(user, questId, &outcome, nowMillis) 46 46 } 47 47 48 48 if isRetired && !isAnnihilated && quest.Stamina > 1 {
+1
server/internal/questflow/handler.go
··· 20 20 MissionClearCompleteRewards []RewardGrant 21 21 BigWinClearedQuestMissionIds []int32 22 22 IsBigWin bool 23 + ChangedWeaponStoryIds []int32 23 24 } 24 25 25 26 type QuestHandler struct {
+6 -4
server/internal/questflow/quest.go
··· 112 112 user.Quests[questId] = questState 113 113 } 114 114 115 - func (h *QuestHandler) applyQuestVictory(user *store.UserState, questId int32, outcome FinishOutcome, nowMillis int64) { 115 + func (h *QuestHandler) applyQuestVictory(user *store.UserState, questId int32, outcome *FinishOutcome, nowMillis int64) { 116 116 questState := user.Quests[questId] 117 117 if !questState.IsRewardGranted { 118 118 h.applyQuestRewards(user, questId, nowMillis) 119 - h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeHalfResult, nowMillis) 120 - h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeFullResult, nowMillis) 119 + outcome.ChangedWeaponStoryIds = append(outcome.ChangedWeaponStoryIds, 120 + h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeHalfResult, nowMillis)...) 121 + outcome.ChangedWeaponStoryIds = append(outcome.ChangedWeaponStoryIds, 122 + h.grantWeaponStoryUnlocksForQuestScene(user, questId, model.QuestResultTypeFullResult, nowMillis)...) 121 123 questState.IsRewardGranted = true 122 124 } 123 125 for _, drop := range outcome.DropRewards { ··· 141 143 142 144 outcome := h.evaluateFinishOutcome(user, questId) 143 145 if !isRetired { 144 - h.applyQuestVictory(user, questId, outcome, nowMillis) 146 + h.applyQuestVictory(user, questId, &outcome, nowMillis) 145 147 } 146 148 147 149 if isRetired && !isAnnihilated && quest.Stamina > 1 {
+13 -7
server/internal/questflow/rewards.go
··· 316 316 } 317 317 } 318 318 319 - func (h *QuestHandler) grantWeaponStoryUnlock(user *store.UserState, weaponId, storyIndex int32, nowMillis int64) { 320 - store.GrantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) 319 + func (h *QuestHandler) grantWeaponStoryUnlock(user *store.UserState, weaponId, storyIndex int32, nowMillis int64) bool { 320 + return store.GrantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) 321 321 } 322 322 323 323 var tutorialCompanionChoices = map[int32]int32{ ··· 354 354 return h.BattleDropsByQuestId[questId] 355 355 } 356 356 357 - func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserState, questId int32, resultType model.QuestResultType, nowMillis int64) { 357 + func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserState, questId int32, resultType model.QuestResultType, nowMillis int64) []int32 { 358 + var changedIds []int32 358 359 if resultType == model.QuestResultTypeHalfResult { 359 360 questDef, ok := h.QuestById[questId] 360 361 if !ok { 361 - return 362 + return nil 362 363 } 363 364 rewardGroupId := h.firstClearRewardGroupId(user, questDef) 364 365 for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] { ··· 373 374 groupId := weapon.WeaponStoryReleaseConditionGroupId 374 375 for _, cond := range h.ReleaseConditionsByGroupId[groupId] { 375 376 if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 { 376 - h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 377 + if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) { 378 + changedIds = append(changedIds, weaponId) 379 + } 377 380 } 378 381 } 379 382 } 380 - return 383 + return changedIds 381 384 } 382 385 if resultType == model.QuestResultTypeFullResult { 383 386 for groupId, conditions := range h.ReleaseConditionsByGroupId { 384 387 for _, cond := range conditions { 385 388 if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId { 386 389 for _, weaponId := range h.WeaponIdsByReleaseConditionGroupId[groupId] { 387 - h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 390 + if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) { 391 + changedIds = append(changedIds, weaponId) 392 + } 388 393 } 389 394 break 390 395 } 391 396 } 392 397 } 393 398 } 399 + return changedIds 394 400 }
+2 -1
server/internal/service/cageornament.go
··· 49 49 "IUserMaterial", "IUserConsumableItem", "IUserGem", 50 50 "IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter", 51 51 "IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", 52 - "IUserWeaponNote", "IUserWeaponStory", 52 + "IUserWeaponNote", 53 53 "IUserCageOrnamentReward", 54 54 }, 55 55 )) 56 + userdata.AddWeaponStoryDiff(diff, user, s.granter.DrainChangedStoryWeaponIds()) 56 57 57 58 return &pb.ReceiveRewardResponse{ 58 59 CageOrnamentReward: []*pb.CageOrnamentReward{
+1 -1
server/internal/service/gacha.go
··· 26 26 "IUserWeaponNote", 27 27 "IUserWeaponSkill", 28 28 "IUserWeaponAbility", 29 - "IUserWeaponStory", 30 29 "IUserCharacter", 31 30 "IUserMaterial", 32 31 } ··· 296 295 userdata.FullClientTableMap(updatedUser), 297 296 gachaDiffTables, 298 297 )) 298 + userdata.AddWeaponStoryDiff(diff, updatedUser, s.handler.Granter.DrainChangedStoryWeaponIds()) 299 299 300 300 return &pb.DrawResponse{ 301 301 NextGacha: nextGacha,
+41 -35
server/internal/service/quest_event.go
··· 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/questflow" 10 10 "lunar-tear/server/internal/store" 11 + "lunar-tear/server/internal/userdata" 11 12 12 13 emptypb "google.golang.org/protobuf/types/known/emptypb" 13 14 ) ··· 52 53 outcome = s.engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) 53 54 }) 54 55 56 + diff := buildSelectedQuestDiff(user, []string{ 57 + "IUserQuest", 58 + "IUserQuestMission", 59 + "IUserEventQuestProgressStatus", 60 + "IUserStatus", 61 + "IUserGem", 62 + "IUserCharacter", 63 + "IUserCostume", 64 + "IUserCostumeActiveSkill", 65 + "IUserWeapon", 66 + "IUserWeaponSkill", 67 + "IUserWeaponAbility", 68 + "IUserWeaponNote", 69 + "IUserCompanion", 70 + "IUserConsumableItem", 71 + "IUserMaterial", 72 + "IUserImportantItem", 73 + "IUserParts", 74 + "IUserPartsGroupNote", 75 + }) 76 + userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds) 77 + 55 78 return &pb.FinishEventQuestResponse{ 56 79 DropReward: toProtoRewards(outcome.DropRewards), 57 80 FirstClearReward: toProtoRewards(outcome.FirstClearRewards), ··· 61 84 IsBigWin: outcome.IsBigWin, 62 85 BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, 63 86 UserStatusCampaignReward: []*pb.QuestReward{}, 64 - DiffUserData: buildSelectedQuestDiff(user, []string{ 65 - "IUserQuest", 66 - "IUserQuestMission", 67 - "IUserEventQuestProgressStatus", 68 - "IUserStatus", 69 - "IUserGem", 70 - "IUserCharacter", 71 - "IUserCostume", 72 - "IUserCostumeActiveSkill", 73 - "IUserWeapon", 74 - "IUserWeaponSkill", 75 - "IUserWeaponAbility", 76 - "IUserWeaponNote", 77 - "IUserWeaponStory", 78 - "IUserCompanion", 79 - "IUserConsumableItem", 80 - "IUserMaterial", 81 - "IUserImportantItem", 82 - "IUserParts", 83 - "IUserPartsGroupNote", 84 - }), 87 + DiffUserData: diff, 85 88 }, nil 86 89 } 87 90 ··· 111 114 s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) 112 115 }) 113 116 117 + diff := buildSelectedQuestDiff(user, []string{ 118 + "IUserEventQuestProgressStatus", 119 + "IUserCharacter", 120 + "IUserCostume", 121 + "IUserWeapon", 122 + "IUserWeaponSkill", 123 + "IUserWeaponAbility", 124 + "IUserCompanion", 125 + "IUserConsumableItem", 126 + "IUserMaterial", 127 + "IUserImportantItem", 128 + "IUserParts", 129 + "IUserPartsGroupNote", 130 + }) 131 + userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds()) 132 + 114 133 return &pb.UpdateEventQuestSceneProgressResponse{ 115 - DiffUserData: buildSelectedQuestDiff(user, []string{ 116 - "IUserEventQuestProgressStatus", 117 - "IUserCharacter", 118 - "IUserCostume", 119 - "IUserWeapon", 120 - "IUserWeaponSkill", 121 - "IUserWeaponAbility", 122 - "IUserCompanion", 123 - "IUserConsumableItem", 124 - "IUserMaterial", 125 - "IUserImportantItem", 126 - "IUserParts", 127 - "IUserPartsGroupNote", 128 - }), 134 + DiffUserData: diff, 129 135 }, nil 130 136 } 131 137
+41 -35
server/internal/service/quest_extra.go
··· 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/questflow" 10 10 "lunar-tear/server/internal/store" 11 + "lunar-tear/server/internal/userdata" 11 12 ) 12 13 13 14 func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) { ··· 50 51 outcome = s.engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) 51 52 }) 52 53 54 + diff := buildSelectedQuestDiff(user, []string{ 55 + "IUserQuest", 56 + "IUserQuestMission", 57 + "IUserExtraQuestProgressStatus", 58 + "IUserStatus", 59 + "IUserGem", 60 + "IUserCharacter", 61 + "IUserCostume", 62 + "IUserCostumeActiveSkill", 63 + "IUserWeapon", 64 + "IUserWeaponSkill", 65 + "IUserWeaponAbility", 66 + "IUserWeaponNote", 67 + "IUserCompanion", 68 + "IUserConsumableItem", 69 + "IUserMaterial", 70 + "IUserImportantItem", 71 + "IUserParts", 72 + "IUserPartsGroupNote", 73 + }) 74 + userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds) 75 + 53 76 return &pb.FinishExtraQuestResponse{ 54 77 DropReward: toProtoRewards(outcome.DropRewards), 55 78 FirstClearReward: toProtoRewards(outcome.FirstClearRewards), ··· 58 81 IsBigWin: outcome.IsBigWin, 59 82 BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, 60 83 UserStatusCampaignReward: []*pb.QuestReward{}, 61 - DiffUserData: buildSelectedQuestDiff(user, []string{ 62 - "IUserQuest", 63 - "IUserQuestMission", 64 - "IUserExtraQuestProgressStatus", 65 - "IUserStatus", 66 - "IUserGem", 67 - "IUserCharacter", 68 - "IUserCostume", 69 - "IUserCostumeActiveSkill", 70 - "IUserWeapon", 71 - "IUserWeaponSkill", 72 - "IUserWeaponAbility", 73 - "IUserWeaponNote", 74 - "IUserWeaponStory", 75 - "IUserCompanion", 76 - "IUserConsumableItem", 77 - "IUserMaterial", 78 - "IUserImportantItem", 79 - "IUserParts", 80 - "IUserPartsGroupNote", 81 - }), 84 + DiffUserData: diff, 82 85 }, nil 83 86 } 84 87 ··· 119 122 s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) 120 123 }) 121 124 125 + diff := buildSelectedQuestDiff(user, []string{ 126 + "IUserExtraQuestProgressStatus", 127 + "IUserCharacter", 128 + "IUserCostume", 129 + "IUserWeapon", 130 + "IUserWeaponSkill", 131 + "IUserWeaponAbility", 132 + "IUserCompanion", 133 + "IUserConsumableItem", 134 + "IUserMaterial", 135 + "IUserImportantItem", 136 + "IUserParts", 137 + "IUserPartsGroupNote", 138 + }) 139 + userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds()) 140 + 122 141 return &pb.UpdateExtraQuestSceneProgressResponse{ 123 - DiffUserData: buildSelectedQuestDiff(user, []string{ 124 - "IUserExtraQuestProgressStatus", 125 - "IUserCharacter", 126 - "IUserCostume", 127 - "IUserWeapon", 128 - "IUserWeaponSkill", 129 - "IUserWeaponAbility", 130 - "IUserCompanion", 131 - "IUserConsumableItem", 132 - "IUserMaterial", 133 - "IUserImportantItem", 134 - "IUserParts", 135 - "IUserPartsGroupNote", 136 - }), 142 + DiffUserData: diff, 137 143 }, nil 138 144 }
+52 -48
server/internal/service/quest_main.go
··· 41 41 s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) 42 42 }) 43 43 44 + diff := buildSelectedQuestDiff(user, []string{ 45 + "IUserMainQuestFlowStatus", 46 + "IUserMainQuestMainFlowStatus", 47 + "IUserMainQuestProgressStatus", 48 + "IUserMainQuestSeasonRoute", 49 + "IUserPortalCageStatus", 50 + "IUserSideStoryQuestSceneProgressStatus", 51 + "IUserQuest", 52 + "IUserCharacter", 53 + "IUserCostume", 54 + "IUserCostumeActiveSkill", 55 + "IUserWeapon", 56 + "IUserWeaponSkill", 57 + "IUserWeaponAbility", 58 + "IUserWeaponNote", 59 + "IUserCompanion", 60 + "IUserConsumableItem", 61 + "IUserMaterial", 62 + "IUserImportantItem", 63 + "IUserParts", 64 + "IUserPartsGroupNote", 65 + }) 66 + userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds()) 67 + 44 68 return &pb.UpdateMainFlowSceneProgressResponse{ 45 - DiffUserData: buildSelectedQuestDiff(user, []string{ 46 - "IUserMainQuestFlowStatus", 47 - "IUserMainQuestMainFlowStatus", 48 - "IUserMainQuestProgressStatus", 49 - "IUserMainQuestSeasonRoute", 50 - "IUserPortalCageStatus", 51 - "IUserSideStoryQuestSceneProgressStatus", 52 - "IUserQuest", 53 - "IUserCharacter", 54 - "IUserCostume", 55 - "IUserCostumeActiveSkill", 56 - "IUserWeapon", 57 - "IUserWeaponSkill", 58 - "IUserWeaponAbility", 59 - "IUserWeaponNote", 60 - "IUserWeaponStory", 61 - "IUserCompanion", 62 - "IUserConsumableItem", 63 - "IUserMaterial", 64 - "IUserImportantItem", 65 - "IUserParts", 66 - "IUserPartsGroupNote", 67 - }), 69 + DiffUserData: diff, 68 70 }, nil 69 71 } 70 72 ··· 169 171 outcome = s.engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) 170 172 }) 171 173 174 + diff := buildSelectedQuestDiff(user, []string{ 175 + "IUserQuest", 176 + "IUserQuestMission", 177 + "IUserMainQuestFlowStatus", 178 + "IUserMainQuestMainFlowStatus", 179 + "IUserMainQuestProgressStatus", 180 + "IUserMainQuestSeasonRoute", 181 + "IUserMainQuestReplayFlowStatus", 182 + "IUserStatus", 183 + "IUserGem", 184 + "IUserCharacter", 185 + "IUserCostume", 186 + "IUserCostumeActiveSkill", 187 + "IUserWeapon", 188 + "IUserWeaponSkill", 189 + "IUserWeaponAbility", 190 + "IUserWeaponNote", 191 + "IUserCompanion", 192 + "IUserConsumableItem", 193 + "IUserMaterial", 194 + "IUserImportantItem", 195 + "IUserParts", 196 + "IUserPartsGroupNote", 197 + }) 198 + userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds) 199 + 172 200 return &pb.FinishMainQuestResponse{ 173 201 DropReward: toProtoRewards(outcome.DropRewards), 174 202 FirstClearReward: toProtoRewards(outcome.FirstClearRewards), ··· 179 207 BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, 180 208 ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards), 181 209 UserStatusCampaignReward: []*pb.QuestReward{}, 182 - DiffUserData: buildSelectedQuestDiff(user, []string{ 183 - "IUserQuest", 184 - "IUserQuestMission", 185 - "IUserMainQuestFlowStatus", 186 - "IUserMainQuestMainFlowStatus", 187 - "IUserMainQuestProgressStatus", 188 - "IUserMainQuestSeasonRoute", 189 - "IUserMainQuestReplayFlowStatus", 190 - "IUserStatus", 191 - "IUserGem", 192 - "IUserCharacter", 193 - "IUserCostume", 194 - "IUserCostumeActiveSkill", 195 - "IUserWeapon", 196 - "IUserWeaponSkill", 197 - "IUserWeaponAbility", 198 - "IUserWeaponNote", 199 - "IUserWeaponStory", 200 - "IUserCompanion", 201 - "IUserConsumableItem", 202 - "IUserMaterial", 203 - "IUserImportantItem", 204 - "IUserParts", 205 - "IUserPartsGroupNote", 206 - }), 210 + DiffUserData: diff, 207 211 }, nil 208 212 } 209 213
+1 -1
server/internal/service/shop.go
··· 32 32 "IUserWeaponSkill", 33 33 "IUserWeaponAbility", 34 34 "IUserWeaponNote", 35 - "IUserWeaponStory", 36 35 } 37 36 38 37 type ShopServiceServer struct { ··· 92 91 93 92 tables := userdata.FullClientTableMap(snapshot) 94 93 diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables)) 94 + userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds()) 95 95 96 96 return &pb.BuyResponse{ 97 97 OverflowPossession: []*pb.Possession{},
-1
server/internal/service/state.go
··· 28 28 "IUserQuestMission", 29 29 "IUserTutorialProgress", 30 30 "IUserWeaponNote", 31 - "IUserWeaponStory", 32 31 "IUserCostumeActiveSkill", 33 32 "IUserDeckTypeNote", 34 33 "IUserDeckSubWeaponGroup",
+44 -7
server/internal/service/weapon.go
··· 20 20 "IUserWeaponAbility", 21 21 "IUserMaterial", 22 22 "IUserConsumableItem", 23 - "IUserWeaponStory", 24 23 } 25 24 26 25 var limitBreakDiffTables = []string{ ··· 98 97 userId := currentUserId(ctx, s.users, s.sessions) 99 98 nowMillis := gametime.NowMillis() 100 99 100 + var changedStoryIds []int32 101 101 snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 102 102 weapon, ok := user.Weapons[req.UserWeaponUuid] 103 103 if !ok { ··· 149 149 weapon.LatestVersion = nowMillis 150 150 user.Weapons[req.UserWeaponUuid] = weapon 151 151 log.Printf("[WeaponService] EnhanceByMaterial: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level) 152 + 153 + changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis) 152 154 }) 153 155 if err != nil { 154 156 return nil, fmt.Errorf("weapon enhance by material: %w", err) ··· 156 158 157 159 tables := userdata.FullClientTableMap(snapshot) 158 160 diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables)) 161 + userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) 159 162 160 163 return &pb.EnhanceByMaterialResponse{ 161 164 IsGreatSuccess: false, ··· 227 230 userId := currentUserId(ctx, s.users, s.sessions) 228 231 nowMillis := gametime.NowMillis() 229 232 233 + var changedStoryIds []int32 230 234 snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 231 235 weapon, ok := user.Weapons[req.UserWeaponUuid] 232 236 if !ok { ··· 286 290 287 291 log.Printf("[WeaponService] Evolve: weaponId %d -> %d", wm.WeaponId, evolvedId) 288 292 289 - s.checkEvolutionStoryUnlocks(user, evolvedId, nowMillis) 293 + changedStoryIds = s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis) 290 294 }) 291 295 if err != nil { 292 296 return nil, fmt.Errorf("weapon evolve: %w", err) ··· 294 298 295 299 tables := userdata.FullClientTableMap(snapshot) 296 300 diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables)) 301 + userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) 297 302 298 303 return &pb.EvolveResponse{DiffUserData: diff}, nil 299 304 } ··· 665 670 Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). 666 671 Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}) 667 672 673 + var changedStoryIds []int32 668 674 snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 669 675 weapon, ok := user.Weapons[req.UserWeaponUuid] 670 676 if !ok { ··· 725 731 weapon.LatestVersion = nowMillis 726 732 user.Weapons[req.UserWeaponUuid] = weapon 727 733 log.Printf("[WeaponService] EnhanceByWeapon: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level) 734 + 735 + changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis) 728 736 }) 729 737 if err != nil { 730 738 return nil, fmt.Errorf("weapon enhance by weapon: %w", err) ··· 732 740 733 741 tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), weaponDiffTables) 734 742 diff := tracker.Apply(snapshot, tables) 743 + userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) 735 744 736 745 return &pb.EnhanceByWeaponResponse{ 737 746 IsGreatSuccess: false, ··· 740 749 }, nil 741 750 } 742 751 743 - func (s *WeaponServiceServer) checkEvolutionStoryUnlocks(user *store.UserState, weaponId int32, nowMillis int64) { 752 + func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) []int32 { 744 753 wm, ok := s.catalog.Weapons[weaponId] 745 754 if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 { 746 - return 755 + return nil 747 756 } 748 757 evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId] 749 758 conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId] 759 + 760 + changed := false 750 761 for _, cond := range conditions { 762 + granted := false 751 763 switch cond.WeaponStoryReleaseConditionType { 764 + case model.WeaponStoryReleaseConditionTypeAcquisition: 765 + granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 766 + case model.WeaponStoryReleaseConditionTypeReachSpecifiedLevel: 767 + if level >= cond.ConditionValue { 768 + granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 769 + } 770 + case model.WeaponStoryReleaseConditionTypeReachInitialMaxLevel: 771 + if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok { 772 + if level >= maxFunc.Evaluate(0) { 773 + granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 774 + } 775 + } 776 + case model.WeaponStoryReleaseConditionTypeReachOnceEvolvedMaxLevel: 777 + if hasEvo && evoOrder >= 1 { 778 + if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok { 779 + if level >= maxFunc.Evaluate(0) { 780 + granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 781 + } 782 + } 783 + } 752 784 case model.WeaponStoryReleaseConditionTypeReachSpecifiedEvolutionCount: 753 785 if hasEvo && evoOrder >= cond.ConditionValue { 754 - store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 786 + granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 755 787 } 756 - case model.WeaponStoryReleaseConditionTypeAcquisition: 757 - store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 788 + } 789 + if granted { 790 + changed = true 758 791 } 759 792 } 793 + if changed { 794 + return []int32{weaponId} 795 + } 796 + return nil 760 797 }
+24 -7
server/internal/store/helpers.go
··· 104 104 WeaponSkillSlots map[int32][]int32 105 105 WeaponAbilitySlots map[int32][]int32 106 106 ReleaseConditions map[int32][]WeaponStoryReleaseCond 107 + 108 + LastChangedStoryWeaponIds []int32 109 + } 110 + 111 + func (g *PossessionGranter) DrainChangedStoryWeaponIds() []int32 { 112 + ids := g.LastChangedStoryWeaponIds 113 + g.LastChangedStoryWeaponIds = nil 114 + return ids 107 115 } 108 116 109 117 func (g *PossessionGranter) GrantFull(user *UserState, possessionType model.PossessionType, possessionId, count int32, nowMillis int64) { ··· 170 178 171 179 g.populateWeaponSkillsAbilities(user, key, weapon) 172 180 if weapon.WeaponStoryReleaseConditionGroupId != 0 { 181 + changed := false 173 182 for _, cond := range g.ReleaseConditions[weapon.WeaponStoryReleaseConditionGroupId] { 174 183 switch cond.WeaponStoryReleaseConditionType { 175 184 case model.WeaponStoryReleaseConditionTypeAcquisition: 176 - grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 185 + if grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) { 186 + changed = true 187 + } 177 188 case model.WeaponStoryReleaseConditionTypeQuestClear: 178 189 if qs, ok := user.Quests[cond.ConditionValue]; ok && qs.QuestStateType == model.UserQuestStateTypeCleared { 179 - grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 190 + if grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) { 191 + changed = true 192 + } 180 193 } 181 194 } 195 + } 196 + if changed { 197 + g.LastChangedStoryWeaponIds = append(g.LastChangedStoryWeaponIds, weaponId) 182 198 } 183 199 } 184 200 } ··· 208 224 } 209 225 } 210 226 211 - func GrantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) { 212 - grantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) 227 + func GrantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) bool { 228 + return grantWeaponStoryUnlock(user, weaponId, storyIndex, nowMillis) 213 229 } 214 230 215 - func grantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) { 231 + func grantWeaponStoryUnlock(user *UserState, weaponId, storyIndex int32, nowMillis int64) bool { 216 232 hasWeapon := false 217 233 for _, row := range user.Weapons { 218 234 if row.WeaponId == weaponId { ··· 222 238 } 223 239 if !hasWeapon { 224 240 log.Printf("[grantWeaponStoryUnlock] skipping weaponId=%d (weapon not in user.Weapons)", weaponId) 225 - return 241 + return false 226 242 } 227 243 if user.WeaponStories == nil { 228 244 user.WeaponStories = make(map[int32]WeaponStoryState) 229 245 } 230 246 cur := user.WeaponStories[weaponId] 231 247 if storyIndex <= cur.ReleasedMaxStoryIndex { 232 - return 248 + return false 233 249 } 234 250 user.WeaponStories[weaponId] = WeaponStoryState{ 235 251 WeaponId: weaponId, 236 252 ReleasedMaxStoryIndex: storyIndex, 237 253 LatestVersion: nowMillis, 238 254 } 255 + return true 239 256 } 240 257 241 258 func EnsureDefaultDeck(user *UserState, nowMillis int64) {
+21
server/internal/userdata/proj_inventory.go
··· 258 258 return records 259 259 } 260 260 261 + func WeaponStoryRecordsForIds(user store.UserState, weaponIds []int32) string { 262 + if len(weaponIds) == 0 { 263 + return "[]" 264 + } 265 + records := make([]map[string]any, 0, len(weaponIds)) 266 + for _, weaponId := range weaponIds { 267 + row, ok := user.WeaponStories[weaponId] 268 + if !ok { 269 + continue 270 + } 271 + records = append(records, map[string]any{ 272 + "userId": user.UserId, 273 + "weaponId": row.WeaponId, 274 + "releasedMaxStoryIndex": row.ReleasedMaxStoryIndex, 275 + "latestVersion": row.LatestVersion, 276 + }) 277 + } 278 + s, _ := encodeJSONMaps(records...) 279 + return s 280 + } 281 + 261 282 func sortedWeaponNoteRecords(user store.UserState) []map[string]any { 262 283 weaponIds := make([]int32, 0, len(user.WeaponNotes)) 263 284 for id := range user.WeaponNotes {
+10
server/internal/userdata/state_projection.go
··· 170 170 } 171 171 return diff 172 172 } 173 + 174 + func AddWeaponStoryDiff(diff map[string]*pb.DiffData, user store.UserState, weaponIds []int32) { 175 + if len(weaponIds) == 0 { 176 + return 177 + } 178 + diff["IUserWeaponStory"] = &pb.DiffData{ 179 + UpdateRecordsJson: WeaponStoryRecordsForIds(user, weaponIds), 180 + DeleteKeysJson: "[]", 181 + } 182 + }