Fix stale story-unlock and post-evolve weapon state

This commit is contained in:
Ilya Groshev
2026-05-01 16:22:24 +03:00
parent 3fe564cb1d
commit 20d8e4d3df
4 changed files with 145 additions and 11 deletions
+27
View File
@@ -42,6 +42,8 @@ type WeaponCatalog struct {
BaseExpByEnhanceId map[int32]int32 BaseExpByEnhanceId map[int32]int32
ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup
LevelingEnhanceIdByWeaponId map[int32]int32
AwakenByWeaponId map[int32]EntityMWeaponAwaken AwakenByWeaponId map[int32]EntityMWeaponAwaken
AwakenMaterialsByGroupId map[int32][]EntityMWeaponAwakenMaterialGroup AwakenMaterialsByGroupId map[int32][]EntityMWeaponAwakenMaterialGroup
} }
@@ -142,6 +144,8 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)), BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)),
ReleaseConditionsByGroupId: make(map[int32][]EntityMWeaponStoryReleaseConditionGroup), ReleaseConditionsByGroupId: make(map[int32][]EntityMWeaponStoryReleaseConditionGroup),
LevelingEnhanceIdByWeaponId: make(map[int32]int32, len(weapons)),
AwakenByWeaponId: make(map[int32]EntityMWeaponAwaken, len(awakenRows)), AwakenByWeaponId: make(map[int32]EntityMWeaponAwaken, len(awakenRows)),
AwakenMaterialsByGroupId: make(map[int32][]EntityMWeaponAwakenMaterialGroup), AwakenMaterialsByGroupId: make(map[int32][]EntityMWeaponAwakenMaterialGroup),
} }
@@ -342,5 +346,28 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
} }
log.Printf("[WeaponCatalog] rarity fallback: assigned synthetic enhance IDs to %d weapons", fallbackCount) log.Printf("[WeaponCatalog] rarity fallback: assigned synthetic enhance IDs to %d weapons", fallbackCount)
// Build LevelingEnhanceIdByWeaponId: every member of an evolution chain
// inherits the chain root's (post-rarity-fallback) WeaponSpecificEnhanceId
// so cumulative exp stays consistent across Evolve. Weapons not in any
// chain map to their own enhance id.
for _, rows := range grouped {
if len(rows) == 0 {
continue
}
rootMaster, ok := catalog.Weapons[rows[0].WeaponId]
if !ok {
continue
}
rootEnhanceId := rootMaster.WeaponSpecificEnhanceId
for _, row := range rows {
catalog.LevelingEnhanceIdByWeaponId[row.WeaponId] = rootEnhanceId
}
}
for wid, w := range catalog.Weapons {
if _, exists := catalog.LevelingEnhanceIdByWeaponId[wid]; !exists {
catalog.LevelingEnhanceIdByWeaponId[wid] = w.WeaponSpecificEnhanceId
}
}
return catalog, nil return catalog, nil
} }
+83 -4
View File
@@ -122,8 +122,26 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
} }
weapon.Exp += totalExp weapon.Exp += totalExp
if thresholds, ok := catalog.ExpByEnhanceId[wm.WeaponSpecificEnhanceId]; ok { levelingEnhanceId := catalog.LevelingEnhanceIdByWeaponId[weapon.WeaponId]
if thresholds, ok := catalog.ExpByEnhanceId[levelingEnhanceId]; ok {
weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds) weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds)
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
cap := maxFunc.Evaluate(weapon.LimitBreakCount)
if weapon.Level > cap {
weapon.Level = cap
if int(cap) >= 0 && int(cap) < len(thresholds) {
weapon.Exp = thresholds[cap]
}
}
}
}
note := user.WeaponNotes[weapon.WeaponId]
if note.MaxLevel < weapon.Level {
note.WeaponId = weapon.WeaponId
note.MaxLevel = weapon.Level
note.LatestVersion = nowMillis
user.WeaponNotes[weapon.WeaponId] = note
} }
weapon.LatestVersion = nowMillis weapon.LatestVersion = nowMillis
@@ -240,10 +258,52 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
log.Printf("[WeaponService] Evolve: gold cost=%d", goldCost) log.Printf("[WeaponService] Evolve: gold cost=%d", goldCost)
} }
oldWeaponId := wm.WeaponId
weapon.WeaponId = evolvedId weapon.WeaponId = evolvedId
weapon.LatestVersion = nowMillis weapon.LatestVersion = nowMillis
user.Weapons[req.UserWeaponUuid] = weapon user.Weapons[req.UserWeaponUuid] = weapon
note, hasNote := user.WeaponNotes[evolvedId]
if !hasNote {
note = store.WeaponNoteState{
WeaponId: evolvedId,
MaxLevel: weapon.Level,
MaxLimitBreakCount: weapon.LimitBreakCount,
FirstAcquisitionDatetime: nowMillis,
LatestVersion: nowMillis,
}
user.WeaponNotes[evolvedId] = note
} else {
changed := false
if note.MaxLevel < weapon.Level {
note.MaxLevel = weapon.Level
changed = true
}
if note.MaxLimitBreakCount < weapon.LimitBreakCount {
note.MaxLimitBreakCount = weapon.LimitBreakCount
changed = true
}
if changed {
note.WeaponId = evolvedId
note.LatestVersion = nowMillis
user.WeaponNotes[evolvedId] = note
}
}
if oldStory, hasOldStory := user.WeaponStories[oldWeaponId]; hasOldStory {
newStory, hasNewStory := user.WeaponStories[evolvedId]
if !hasNewStory || newStory.ReleasedMaxStoryIndex < oldStory.ReleasedMaxStoryIndex {
if user.WeaponStories == nil {
user.WeaponStories = make(map[int32]store.WeaponStoryState)
}
user.WeaponStories[evolvedId] = store.WeaponStoryState{
WeaponId: evolvedId,
ReleasedMaxStoryIndex: oldStory.ReleasedMaxStoryIndex,
LatestVersion: nowMillis,
}
}
}
evolvedMaster, ok := catalog.Weapons[evolvedId] evolvedMaster, ok := catalog.Weapons[evolvedId]
if ok { if ok {
if slots, ok := catalog.AbilitySlots[evolvedMaster.WeaponAbilityGroupId]; ok { if slots, ok := catalog.AbilitySlots[evolvedMaster.WeaponAbilityGroupId]; ok {
@@ -259,7 +319,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", oldWeaponId, evolvedId)
checkWeaponStoryUnlocks(catalog, user, evolvedId, weapon.Level, nowMillis) checkWeaponStoryUnlocks(catalog, user, evolvedId, weapon.Level, nowMillis)
}) })
@@ -657,7 +717,8 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
continue continue
} }
baseExp := catalog.BaseExpByEnhanceId[matMaster.WeaponSpecificEnhanceId] matLevelingEnhanceId := catalog.LevelingEnhanceIdByWeaponId[matWeapon.WeaponId]
baseExp := catalog.BaseExpByEnhanceId[matLevelingEnhanceId]
if matMaster.WeaponType != 0 && matMaster.WeaponType == wm.WeaponType { if matMaster.WeaponType != 0 && matMaster.WeaponType == wm.WeaponType {
baseExp = baseExp * config.MaterialSameWeaponExpCoefficientPermil / 1000 baseExp = baseExp * config.MaterialSameWeaponExpCoefficientPermil / 1000
} }
@@ -683,8 +744,26 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
} }
weapon.Exp += totalExp weapon.Exp += totalExp
if thresholds, ok := catalog.ExpByEnhanceId[wm.WeaponSpecificEnhanceId]; ok { levelingEnhanceId := catalog.LevelingEnhanceIdByWeaponId[weapon.WeaponId]
if thresholds, ok := catalog.ExpByEnhanceId[levelingEnhanceId]; ok {
weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds) weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds)
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
cap := maxFunc.Evaluate(weapon.LimitBreakCount)
if weapon.Level > cap {
weapon.Level = cap
if int(cap) >= 0 && int(cap) < len(thresholds) {
weapon.Exp = thresholds[cap]
}
}
}
}
note := user.WeaponNotes[weapon.WeaponId]
if note.MaxLevel < weapon.Level {
note.WeaponId = weapon.WeaponId
note.MaxLevel = weapon.Level
note.LatestVersion = nowMillis
user.WeaponNotes[weapon.WeaponId] = note
} }
weapon.LatestVersion = nowMillis weapon.LatestVersion = nowMillis
+6 -7
View File
@@ -294,17 +294,16 @@ func ComputeDelta(before, after *store.UserState, changedTables []string) map[st
diff := make(map[string]*pb.DiffData, len(changedTables)) diff := make(map[string]*pb.DiffData, len(changedTables))
for _, table := range changedTables { for _, table := range changedTables {
afterJSON := projectTable(table, *after) afterJSON := projectTable(table, *after)
updates := afterJSON
deleteKeys := "[]" deleteKeys := "[]"
if kf := keyFieldsForTable(table); len(kf) > 0 { if kf := keyFieldsForTable(table); len(kf) > 0 {
beforeJSON := projectTable(table, *before) beforeRecs := parseJSONRecords(projectTable(table, *before))
deleteKeys = ComputeDeleteKeys( afterRecs := parseJSONRecords(afterJSON)
parseJSONRecords(beforeJSON), updates = ComputeUpdateRecords(beforeRecs, afterRecs, kf)
parseJSONRecords(afterJSON), deleteKeys = ComputeDeleteKeys(beforeRecs, afterRecs, kf)
kf,
)
} }
diff[table] = &pb.DiffData{ diff[table] = &pb.DiffData{
UpdateRecordsJson: afterJSON, UpdateRecordsJson: updates,
DeleteKeysJson: deleteKeys, DeleteKeysJson: deleteKeys,
} }
} }
+29
View File
@@ -3,6 +3,7 @@ package userdata
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect"
"strings" "strings"
pb "lunar-tear/server/gen/proto" pb "lunar-tear/server/gen/proto"
@@ -120,6 +121,34 @@ func ComputeDeleteKeys(oldRecords, newRecords []map[string]any, keyFields []stri
return string(b) return string(b)
} }
func ComputeUpdateRecords(oldRecords, newRecords []map[string]any, keyFields []string) string {
if len(newRecords) == 0 {
return "[]"
}
oldByKey := make(map[string]map[string]any, len(oldRecords))
for _, r := range oldRecords {
oldByKey[compositeKey(r, keyFields)] = r
}
var changed []map[string]any
for _, r := range newRecords {
prev, exists := oldByKey[compositeKey(r, keyFields)]
if !exists || !reflect.DeepEqual(prev, r) {
changed = append(changed, r)
}
}
if len(changed) == 0 {
return "[]"
}
b, err := json.Marshal(changed)
if err != nil {
return "[]"
}
return string(b)
}
func compositeKey(record map[string]any, fields []string) string { func compositeKey(record map[string]any, fields []string) string {
var sb strings.Builder var sb strings.Builder
for i, f := range fields { for i, f := range fields {