Migrate accessories to user data

- Add user_accessory as the per-user accessory inventory table.
- Migrate old accessory wear records to user-owned accessory ids, including legacy common_accessory_m lookups for old installs.
- Allocate default accessories for newly created users and update accessory reads to use user_accessory at runtime.
- Validate accessory ownership when wearing accessories and keep existing accessory-based responses working.

Signed-off-by: Sean Du <do4suki@gmail.com>
This commit is contained in:
2026-06-13 07:36:21 +08:00
parent 17336de1b7
commit cb0200ff29
11 changed files with 436 additions and 72 deletions
+123
View File
@@ -0,0 +1,123 @@
package usermodel
import (
"fmt"
"sort"
"xorm.io/xorm"
)
type UserAccessory struct {
AccessoryOwningUserID int `xorm:"accessory_owning_user_id pk autoincr"`
UserID int `xorm:"user_id index"`
AccessoryID int `xorm:"accessory_id index"`
Exp int `xorm:"exp"`
}
func (UserAccessory) TableName() string {
return "user_accessory"
}
type accessoryTemplateRow struct {
AccessoryID int `xorm:"accessory_id"`
}
type userAccessoryCountRow struct {
AccessoryID int `xorm:"accessory_id"`
Count int `xorm:"cnt"`
}
func LoadAccessoryTemplateIDs(mainSession xorm.Interface) ([]int, error) {
rows := []accessoryTemplateRow{}
err := mainSession.Table("accessory_m").
Where("is_material = ? AND rarity = ?", 0, 4).
Cols("accessory_id").
OrderBy("accessory_id ASC").
Find(&rows)
if err != nil {
return nil, err
}
ids := make([]int, 0, len(rows))
for _, row := range rows {
ids = append(ids, row.AccessoryID)
}
return ids, nil
}
func EnsureUserAccessories(userSession *xorm.Session, mainSession xorm.Interface, userID int, minCount map[int]int) error {
accessoryIDs, err := LoadAccessoryTemplateIDs(mainSession)
if err != nil {
return err
}
existingRows := []userAccessoryCountRow{}
err = userSession.Table(new(UserAccessory)).
Select("accessory_id, COUNT(*) AS cnt").
Where("user_id = ?", userID).
GroupBy("accessory_id").
Find(&existingRows)
if err != nil {
return err
}
existingCount := make(map[int]int, len(existingRows))
for _, row := range existingRows {
existingCount[row.AccessoryID] = row.Count
}
for _, accessoryID := range accessoryIDs {
targetCount := 1
if count, ok := minCount[accessoryID]; ok && count > targetCount {
targetCount = count
}
for existingCount[accessoryID] < targetCount {
_, err = userSession.Insert(&UserAccessory{
UserID: userID,
AccessoryID: accessoryID,
Exp: 0,
})
if err != nil {
return err
}
existingCount[accessoryID]++
}
}
return nil
}
func BuildAccessoryOwningUserIDMap(userSession *xorm.Session, userID int) (map[int][]int, error) {
rows := []UserAccessory{}
err := userSession.Table(new(UserAccessory)).
Where("user_id = ?", userID).
OrderBy("accessory_id ASC, accessory_owning_user_id ASC").
Find(&rows)
if err != nil {
return nil, err
}
result := make(map[int][]int)
for _, row := range rows {
result[row.AccessoryID] = append(result[row.AccessoryID], row.AccessoryOwningUserID)
}
return result, nil
}
func ResolveAccessoryOwningUserIDsByAccessoryID(owningIDs map[int][]int, requiredCounts map[int]int) (map[int][]int, error) {
result := make(map[int][]int, len(requiredCounts))
for accessoryID, count := range requiredCounts {
ids := owningIDs[accessoryID]
if len(ids) < count {
return nil, fmt.Errorf("insufficient user accessories for accessory_id=%d need=%d have=%d", accessoryID, count, len(ids))
}
result[accessoryID] = append([]int(nil), ids[:count]...)
}
for accessoryID := range result {
sort.Ints(result[accessoryID])
}
return result, nil
}