Add authentication server, dev CLI, Docker multi-service setup, and cross-platform improvements

This commit is contained in:
Ilya Groshev
2026-04-21 16:49:44 +03:00
parent 43d6527b42
commit a3fbb1aeba
121 changed files with 4523 additions and 2888 deletions
+44 -133
View File
@@ -11,35 +11,8 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
"lunar-tear/server/internal/userdata"
)
var weaponDiffTables = []string{
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponAwaken",
"IUserMaterial",
"IUserConsumableItem",
}
var limitBreakDiffTables = []string{
"IUserWeapon",
"IUserWeaponSkill",
"IUserWeaponAbility",
"IUserWeaponAwaken",
"IUserMaterial",
"IUserConsumableItem",
"IUserWeaponNote",
}
var weaponAwakenDiffTables = []string{
"IUserWeapon",
"IUserWeaponAwaken",
"IUserMaterial",
"IUserConsumableItem",
}
type WeaponServiceServer struct {
pb.UnimplementedWeaponServiceServer
users store.UserRepository
@@ -55,10 +28,10 @@ func NewWeaponServiceServer(users store.UserRepository, sessions store.SessionRe
func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectRequest) (*pb.ProtectResponse, error) {
log.Printf("[WeaponService] Protect: uuids=%v", req.UserWeaponUuid)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
snapshot, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
s.users.UpdateUser(userId, func(user *store.UserState) {
for _, uuid := range req.UserWeaponUuid {
weapon, ok := user.Weapons[uuid]
if !ok {
@@ -71,17 +44,16 @@ func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectReques
}
})
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"}))
return &pb.ProtectResponse{DiffUserData: diff}, nil
return &pb.ProtectResponse{}, nil
}
func (s *WeaponServiceServer) Unprotect(ctx context.Context, req *pb.UnprotectRequest) (*pb.UnprotectResponse, error) {
log.Printf("[WeaponService] Unprotect: uuids=%v", req.UserWeaponUuid)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
snapshot, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
s.users.UpdateUser(userId, func(user *store.UserState) {
for _, uuid := range req.UserWeaponUuid {
weapon, ok := user.Weapons[uuid]
if !ok {
@@ -94,18 +66,16 @@ func (s *WeaponServiceServer) Unprotect(ctx context.Context, req *pb.UnprotectRe
}
})
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"}))
return &pb.UnprotectResponse{DiffUserData: diff}, nil
return &pb.UnprotectResponse{}, nil
}
func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.EnhanceByMaterialRequest) (*pb.EnhanceByMaterialResponse, error) {
log.Printf("[WeaponService] EnhanceByMaterial: uuid=%s materials=%v", req.UserWeaponUuid, req.Materials)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
var changedStoryIds []int32
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceByMaterial: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -157,35 +127,24 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
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)
s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
})
if err != nil {
return nil, fmt.Errorf("weapon enhance by material: %w", err)
}
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
return &pb.EnhanceByMaterialResponse{
IsGreatSuccess: false,
SurplusEnhanceMaterial: map[int32]int32{},
DiffUserData: diff,
}, nil
}
func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*pb.SellResponse, error) {
log.Printf("[WeaponService] Sell: uuids=%v", req.UserWeaponUuid)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
oldUser, _ := s.users.LoadUser(userId)
tracker := userdata.NewDeleteTracker().
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []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) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
totalGold := int32(0)
for _, uuid := range req.UserWeaponUuid {
weapon, ok := user.Weapons[uuid]
@@ -225,21 +184,16 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
return nil, fmt.Errorf("weapon sell: %w", err)
}
sellDiffTables := []string{"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeaponAwaken", "IUserConsumableItem"}
tables := userdata.ProjectTables(snapshot, sellDiffTables)
diff := tracker.Apply(snapshot, tables)
return &pb.SellResponse{DiffUserData: diff}, nil
return &pb.SellResponse{}, nil
}
func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest) (*pb.EvolveResponse, error) {
log.Printf("[WeaponService] Evolve: uuid=%s", req.UserWeaponUuid)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
var changedStoryIds []int32
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] Evolve: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -298,25 +252,22 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
log.Printf("[WeaponService] Evolve: weaponId %d -> %d", wm.WeaponId, evolvedId)
changedStoryIds = s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis)
s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis)
})
if err != nil {
return nil, fmt.Errorf("weapon evolve: %w", err)
}
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
return &pb.EvolveResponse{DiffUserData: diff}, nil
return &pb.EvolveResponse{}, nil
}
func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceSkillRequest) (*pb.EnhanceSkillResponse, error) {
log.Printf("[WeaponService] EnhanceSkill: uuid=%s skillId=%d addLevel=%d", req.UserWeaponUuid, req.SkillId, req.AddLevelCount)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceSkill: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -330,7 +281,7 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
}
groupRows := s.catalog.SkillGroupsByGroupId[wm.WeaponSkillGroupId]
var skillGroup *masterdata.WeaponSkillGroupRow
var skillGroup *masterdata.EntityMWeaponSkillGroup
for i := range groupRows {
if groupRows[i].SkillId == req.SkillId {
skillGroup = &groupRows[i]
@@ -403,18 +354,16 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
return nil, fmt.Errorf("weapon enhance skill: %w", err)
}
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
return &pb.EnhanceSkillResponse{DiffUserData: diff}, nil
return &pb.EnhanceSkillResponse{}, nil
}
func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.EnhanceAbilityRequest) (*pb.EnhanceAbilityResponse, error) {
log.Printf("[WeaponService] EnhanceAbility: uuid=%s abilityId=%d addLevel=%d", req.UserWeaponUuid, req.AbilityId, req.AddLevelCount)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceAbility: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -428,7 +377,7 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
}
groupRows := s.catalog.AbilityGroupsByGroupId[wm.WeaponAbilityGroupId]
var abilityGroup *masterdata.WeaponAbilityGroupRow
var abilityGroup *masterdata.EntityMWeaponAbilityGroup
for i := range groupRows {
if groupRows[i].AbilityId == req.AbilityId {
abilityGroup = &groupRows[i]
@@ -501,18 +450,16 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
return nil, fmt.Errorf("weapon enhance ability: %w", err)
}
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
return &pb.EnhanceAbilityResponse{DiffUserData: diff}, nil
return &pb.EnhanceAbilityResponse{}, nil
}
func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.LimitBreakByMaterialRequest) (*pb.LimitBreakByMaterialResponse, error) {
log.Printf("[WeaponService] LimitBreakByMaterial: uuid=%s materials=%v", req.UserWeaponUuid, req.Materials)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] LimitBreakByMaterial: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -572,25 +519,16 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
return nil, fmt.Errorf("weapon limit break by material: %w", err)
}
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, limitBreakDiffTables))
return &pb.LimitBreakByMaterialResponse{DiffUserData: diff}, nil
return &pb.LimitBreakByMaterialResponse{}, nil
}
func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.LimitBreakByWeaponRequest) (*pb.LimitBreakByWeaponResponse, error) {
log.Printf("[WeaponService] LimitBreakByWeapon: uuid=%s materialUuids=%v", req.UserWeaponUuid, req.MaterialUserWeaponUuids)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
oldUser, _ := s.users.LoadUser(userId)
tracker := userdata.NewDeleteTracker().
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []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) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] LimitBreakByWeapon: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -658,27 +596,16 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
return nil, fmt.Errorf("weapon limit break by weapon: %w", err)
}
tables := userdata.ProjectTables(snapshot, limitBreakDiffTables)
diff := tracker.Apply(snapshot, tables)
return &pb.LimitBreakByWeaponResponse{DiffUserData: diff}, nil
return &pb.LimitBreakByWeaponResponse{}, nil
}
func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.EnhanceByWeaponRequest) (*pb.EnhanceByWeaponResponse, error) {
log.Printf("[WeaponService] EnhanceByWeapon: uuid=%s materialUuids=%v", req.UserWeaponUuid, req.MaterialUserWeaponUuids)
userId := currentUserId(ctx, s.users, s.sessions)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
oldUser, _ := s.users.LoadUser(userId)
tracker := userdata.NewDeleteTracker().
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"})
var changedStoryIds []int32
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceByWeapon: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -740,77 +667,63 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
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)
s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
})
if err != nil {
return nil, fmt.Errorf("weapon enhance by weapon: %w", err)
}
tables := userdata.ProjectTables(snapshot, weaponDiffTables)
diff := tracker.Apply(snapshot, tables)
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
return &pb.EnhanceByWeaponResponse{
IsGreatSuccess: false,
SurplusEnhanceWeapon: []string{},
DiffUserData: diff,
}, nil
}
func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) []int32 {
func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) {
wm, ok := s.catalog.Weapons[weaponId]
if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 {
return nil
return
}
evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId]
conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId]
changed := false
for _, cond := range conditions {
granted := false
switch cond.WeaponStoryReleaseConditionType {
switch model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) {
case model.WeaponStoryReleaseConditionTypeAcquisition:
granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
case model.WeaponStoryReleaseConditionTypeReachSpecifiedLevel:
if level >= cond.ConditionValue {
granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
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)
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)
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
}
}
case model.WeaponStoryReleaseConditionTypeReachSpecifiedEvolutionCount:
if hasEvo && evoOrder >= cond.ConditionValue {
granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
}
if granted {
changed = true
}
}
if changed {
return []int32{weaponId}
}
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)
userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
_, 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)
@@ -857,7 +770,5 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
return nil, fmt.Errorf("weapon awaken: %w", err)
}
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponAwakenDiffTables))
return &pb.WeaponAwakenResponse{DiffUserData: diff}, nil
return &pb.WeaponAwakenResponse{}, nil
}