Add weapon awakening functionality

This commit is contained in:
Ilya Groshev
2026-04-16 15:39:22 +03:00
parent 190ff66ebd
commit 61507599fc
5 changed files with 157 additions and 5 deletions
+44
View File
@@ -94,6 +94,27 @@ type WeaponAbilityEnhanceMaterialRow struct {
SortOrder int32 `json:"SortOrder"` SortOrder int32 `json:"SortOrder"`
} }
type WeaponAwakenRow struct {
WeaponId int32 `json:"WeaponId"`
WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"`
WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"`
ConsumeGold int32 `json:"ConsumeGold"`
LevelLimitUp int32 `json:"LevelLimitUp"`
}
type WeaponAwakenEffectGroupRow struct {
WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"`
WeaponAwakenEffectType int32 `json:"WeaponAwakenEffectType"`
WeaponAwakenEffectId int32 `json:"WeaponAwakenEffectId"`
}
type WeaponAwakenMaterialGroupRow struct {
WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"`
MaterialId int32 `json:"MaterialId"`
Count int32 `json:"Count"`
SortOrder int32 `json:"SortOrder"`
}
type weaponRarityEnhanceRow struct { type weaponRarityEnhanceRow struct {
RarityType int32 `json:"RarityType"` RarityType int32 `json:"RarityType"`
BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"` BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"`
@@ -137,6 +158,9 @@ type WeaponCatalog struct {
LimitBreakCostByMaterialByEnhanceId map[int32]NumericalFunc LimitBreakCostByMaterialByEnhanceId map[int32]NumericalFunc
BaseExpByEnhanceId map[int32]int32 BaseExpByEnhanceId map[int32]int32
ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow
AwakenByWeaponId map[int32]WeaponAwakenRow
AwakenMaterialsByGroupId map[int32][]WeaponAwakenMaterialGroupRow
} }
func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) { func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
@@ -199,6 +223,15 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
return nil, fmt.Errorf("load weapon story release condition table: %w", err) return nil, fmt.Errorf("load weapon story release condition table: %w", err)
} }
awakenRows, err := utils.ReadJSON[WeaponAwakenRow]("EntityMWeaponAwakenTable.json")
if err != nil {
return nil, fmt.Errorf("load weapon awaken table: %w", err)
}
awakenMatRows, err := utils.ReadJSON[WeaponAwakenMaterialGroupRow]("EntityMWeaponAwakenMaterialGroupTable.json")
if err != nil {
return nil, fmt.Errorf("load weapon awaken material group table: %w", err)
}
catalog := &WeaponCatalog{ catalog := &WeaponCatalog{
Weapons: make(map[int32]WeaponMasterRow, len(weapons)), Weapons: make(map[int32]WeaponMasterRow, len(weapons)),
Materials: matCatalog.ByType[model.MaterialTypeWeaponEnhancement], Materials: matCatalog.ByType[model.MaterialTypeWeaponEnhancement],
@@ -225,6 +258,9 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
LimitBreakCostByMaterialByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), LimitBreakCostByMaterialByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)), BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)),
ReleaseConditionsByGroupId: make(map[int32][]WeaponStoryReleaseConditionRow), ReleaseConditionsByGroupId: make(map[int32][]WeaponStoryReleaseConditionRow),
AwakenByWeaponId: make(map[int32]WeaponAwakenRow, len(awakenRows)),
AwakenMaterialsByGroupId: make(map[int32][]WeaponAwakenMaterialGroupRow),
} }
for _, w := range weapons { for _, w := range weapons {
@@ -353,6 +389,14 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
catalog.ReleaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c) catalog.ReleaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c)
} }
for _, row := range awakenRows {
catalog.AwakenByWeaponId[row.WeaponId] = row
}
for _, row := range awakenMatRows {
catalog.AwakenMaterialsByGroupId[row.WeaponAwakenMaterialGroupId] = append(
catalog.AwakenMaterialsByGroupId[row.WeaponAwakenMaterialGroupId], row)
}
// Rarity-based enhancement fallback: for weapons with WeaponSpecificEnhanceId == 0, // Rarity-based enhancement fallback: for weapons with WeaponSpecificEnhanceId == 0,
// use EntityMWeaponRarityTable curves via synthetic enhance IDs (-RarityType). // use EntityMWeaponRarityTable curves via synthetic enhance IDs (-RarityType).
rarityByType := make(map[int32]weaponRarityEnhanceRow, len(rarityEnhanceRows)) rarityByType := make(map[int32]weaponRarityEnhanceRow, len(rarityEnhanceRows))
+8
View File
@@ -21,3 +21,11 @@ const (
CostumeAwakenEffectTypeAbility CostumeAwakenEffectType = 2 CostumeAwakenEffectTypeAbility CostumeAwakenEffectType = 2
CostumeAwakenEffectTypeItemAcquire CostumeAwakenEffectType = 3 CostumeAwakenEffectTypeItemAcquire CostumeAwakenEffectType = 3
) )
type WeaponAwakenEffectType int32
const (
WeaponAwakenEffectTypeUnknown WeaponAwakenEffectType = 0
WeaponAwakenEffectTypeStatusUp WeaponAwakenEffectType = 1
WeaponAwakenEffectTypeAbility WeaponAwakenEffectType = 2
)
+78 -4
View File
@@ -18,6 +18,7 @@ var weaponDiffTables = []string{
"IUserWeapon", "IUserWeapon",
"IUserWeaponSkill", "IUserWeaponSkill",
"IUserWeaponAbility", "IUserWeaponAbility",
"IUserWeaponAwaken",
"IUserMaterial", "IUserMaterial",
"IUserConsumableItem", "IUserConsumableItem",
} }
@@ -26,11 +27,19 @@ var limitBreakDiffTables = []string{
"IUserWeapon", "IUserWeapon",
"IUserWeaponSkill", "IUserWeaponSkill",
"IUserWeaponAbility", "IUserWeaponAbility",
"IUserWeaponAwaken",
"IUserMaterial", "IUserMaterial",
"IUserConsumableItem", "IUserConsumableItem",
"IUserWeaponNote", "IUserWeaponNote",
} }
var weaponAwakenDiffTables = []string{
"IUserWeapon",
"IUserWeaponAwaken",
"IUserMaterial",
"IUserConsumableItem",
}
type WeaponServiceServer struct { type WeaponServiceServer struct {
pb.UnimplementedWeaponServiceServer pb.UnimplementedWeaponServiceServer
users store.UserRepository users store.UserRepository
@@ -176,7 +185,8 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
tracker := userdata.NewDeleteTracker(). tracker := userdata.NewDeleteTracker().
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}). Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
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"}).
Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"})
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
totalGold := int32(0) totalGold := int32(0)
@@ -206,6 +216,7 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
delete(user.Weapons, uuid) delete(user.Weapons, uuid)
delete(user.WeaponSkills, uuid) delete(user.WeaponSkills, uuid)
delete(user.WeaponAbilities, uuid) delete(user.WeaponAbilities, uuid)
delete(user.WeaponAwakens, uuid)
} }
if totalGold > 0 { if totalGold > 0 {
@@ -217,7 +228,7 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
return nil, fmt.Errorf("weapon sell: %w", err) return nil, fmt.Errorf("weapon sell: %w", err)
} }
sellDiffTables := []string{"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserConsumableItem"} sellDiffTables := []string{"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeaponAwaken", "IUserConsumableItem"}
tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), sellDiffTables) tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), sellDiffTables)
diff := tracker.Apply(snapshot, tables) diff := tracker.Apply(snapshot, tables)
@@ -583,7 +594,8 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
tracker := userdata.NewDeleteTracker(). tracker := userdata.NewDeleteTracker().
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}). Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
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"}).
Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"})
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]
@@ -626,6 +638,7 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
delete(user.Weapons, uuid) delete(user.Weapons, uuid)
delete(user.WeaponSkills, uuid) delete(user.WeaponSkills, uuid)
delete(user.WeaponAbilities, uuid) delete(user.WeaponAbilities, uuid)
delete(user.WeaponAwakens, uuid)
consumedCount++ consumedCount++
} }
@@ -668,7 +681,8 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
tracker := userdata.NewDeleteTracker(). tracker := userdata.NewDeleteTracker().
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}). Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
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"}).
Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"})
var changedStoryIds []int32 var changedStoryIds []int32
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
@@ -714,6 +728,7 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
delete(user.Weapons, uuid) delete(user.Weapons, uuid)
delete(user.WeaponSkills, uuid) delete(user.WeaponSkills, uuid)
delete(user.WeaponAbilities, uuid) delete(user.WeaponAbilities, uuid)
delete(user.WeaponAwakens, uuid)
consumedCount++ consumedCount++
} }
@@ -795,3 +810,62 @@ func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, wea
} }
return nil return nil
} }
func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRequest) (*pb.WeaponAwakenResponse, error) {
log.Printf("[WeaponService] Awaken: uuid=%s", req.UserWeaponUuid)
userId := currentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] Awaken: weapon uuid=%s not found", req.UserWeaponUuid)
return
}
awakenRow, ok := s.catalog.AwakenByWeaponId[weapon.WeaponId]
if !ok {
log.Printf("[WeaponService] Awaken: no awaken data for weaponId=%d", weapon.WeaponId)
return
}
if _, already := user.WeaponAwakens[req.UserWeaponUuid]; already {
log.Printf("[WeaponService] Awaken: weapon uuid=%s already awakened", req.UserWeaponUuid)
return
}
mats := s.catalog.AwakenMaterialsByGroupId[awakenRow.WeaponAwakenMaterialGroupId]
for _, mat := range mats {
cur := user.Materials[mat.MaterialId]
cost := mat.Count
if cur < cost {
log.Printf("[WeaponService] Awaken: insufficient material id=%d have=%d need=%d", mat.MaterialId, cur, cost)
cost = cur
}
user.Materials[mat.MaterialId] = cur - cost
}
if awakenRow.ConsumeGold > 0 {
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= awakenRow.ConsumeGold
log.Printf("[WeaponService] Awaken: gold cost=%d", awakenRow.ConsumeGold)
}
user.WeaponAwakens[req.UserWeaponUuid] = store.WeaponAwakenState{
UserWeaponUuid: req.UserWeaponUuid,
LatestVersion: nowMillis,
}
weapon.LatestVersion = nowMillis
user.Weapons[req.UserWeaponUuid] = weapon
log.Printf("[WeaponService] Awaken: weaponId=%d awakened", weapon.WeaponId)
})
if err != nil {
return nil, fmt.Errorf("weapon awaken: %w", err)
}
tables := userdata.FullClientTableMap(snapshot)
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponAwakenDiffTables))
return &pb.WeaponAwakenResponse{DiffUserData: diff}, nil
}
+9
View File
@@ -83,6 +83,7 @@ type UserState struct {
CostumeActiveSkills map[string]CostumeActiveSkillState CostumeActiveSkills map[string]CostumeActiveSkillState
WeaponSkills map[string][]WeaponSkillState // key: userWeaponUuid WeaponSkills map[string][]WeaponSkillState // key: userWeaponUuid
WeaponAbilities map[string][]WeaponAbilityState // key: userWeaponUuid WeaponAbilities map[string][]WeaponAbilityState // key: userWeaponUuid
WeaponAwakens map[string]WeaponAwakenState // key: userWeaponUuid
DeckTypeNotes map[model.DeckType]DeckTypeNoteState DeckTypeNotes map[model.DeckType]DeckTypeNoteState
WeaponNotes map[int32]WeaponNoteState WeaponNotes map[int32]WeaponNoteState
DeckSubWeapons map[string][]string DeckSubWeapons map[string][]string
@@ -205,6 +206,9 @@ func (u *UserState) EnsureMaps() {
if u.WeaponAbilities == nil { if u.WeaponAbilities == nil {
u.WeaponAbilities = make(map[string][]WeaponAbilityState) u.WeaponAbilities = make(map[string][]WeaponAbilityState)
} }
if u.WeaponAwakens == nil {
u.WeaponAwakens = make(map[string]WeaponAwakenState)
}
if u.DeckTypeNotes == nil { if u.DeckTypeNotes == nil {
u.DeckTypeNotes = make(map[model.DeckType]DeckTypeNoteState) u.DeckTypeNotes = make(map[model.DeckType]DeckTypeNoteState)
} }
@@ -462,6 +466,11 @@ type WeaponAbilityState struct {
Level int32 Level int32
} }
type WeaponAwakenState struct {
UserWeaponUuid string
LatestVersion int64
}
type DeckTypeNoteState struct { type DeckTypeNoteState struct {
DeckType model.DeckType DeckType model.DeckType
MaxDeckPower int32 MaxDeckPower int32
+18 -1
View File
@@ -101,13 +101,16 @@ func init() {
s, _ := encodeJSONMaps(sortedCageOrnamentRewardRecords(user)...) s, _ := encodeJSONMaps(sortedCageOrnamentRewardRecords(user)...)
return s return s
}) })
register("IUserWeaponAwaken", func(user store.UserState) string {
s, _ := encodeJSONMaps(SortedWeaponAwakenRecords(user)...)
return s
})
registerStatic( registerStatic(
"IUserCostumeLevelBonusReleaseStatus", "IUserCostumeLevelBonusReleaseStatus",
"IUserCostumeLotteryEffect", "IUserCostumeLotteryEffect",
"IUserCostumeLotteryEffectAbility", "IUserCostumeLotteryEffectAbility",
"IUserCostumeLotteryEffectStatusUp", "IUserCostumeLotteryEffectStatusUp",
"IUserCostumeLotteryEffectPending", "IUserCostumeLotteryEffectPending",
"IUserWeaponAwaken",
"IUserPartsPresetTag", "IUserPartsPresetTag",
"IUserPartsStatusSub", "IUserPartsStatusSub",
) )
@@ -532,6 +535,20 @@ func SortedWeaponAbilityRecords(user store.UserState) []map[string]any {
return records return records
} }
func SortedWeaponAwakenRecords(user store.UserState) []map[string]any {
keys := sortedStringKeys(user.WeaponAwakens)
records := make([]map[string]any, 0, len(keys))
for _, key := range keys {
row := user.WeaponAwakens[key]
records = append(records, map[string]any{
"userId": user.UserId,
"userWeaponUuid": row.UserWeaponUuid,
"latestVersion": row.LatestVersion,
})
}
return records
}
func exploreRecord(user store.UserState) map[string]any { func exploreRecord(user store.UserState) map[string]any {
return map[string]any{ return map[string]any{
"userId": user.UserId, "userId": user.UserId,