diff --git a/server/internal/masterdata/consumableitem.go b/server/internal/masterdata/consumableitem.go index 34e1924..1ef1f7f 100644 --- a/server/internal/masterdata/consumableitem.go +++ b/server/internal/masterdata/consumableitem.go @@ -7,7 +7,8 @@ import ( ) type ConsumableItemCatalog struct { - All map[int32]EntityMConsumableItem + All map[int32]EntityMConsumableItem + Effects map[int32][]EntityMConsumableItemEffect } func LoadConsumableItemCatalog() (*ConsumableItemCatalog, error) { @@ -15,12 +16,20 @@ func LoadConsumableItemCatalog() (*ConsumableItemCatalog, error) { if err != nil { return nil, fmt.Errorf("load consumable item table: %w", err) } + effects, err := utils.ReadTable[EntityMConsumableItemEffect]("m_consumable_item_effect") + if err != nil { + return nil, fmt.Errorf("load consumable item effect table: %w", err) + } catalog := &ConsumableItemCatalog{ - All: make(map[int32]EntityMConsumableItem, len(rows)), + All: make(map[int32]EntityMConsumableItem, len(rows)), + Effects: make(map[int32][]EntityMConsumableItemEffect, len(effects)), } for _, row := range rows { catalog.All[row.ConsumableItemId] = row } + for _, e := range effects { + catalog.Effects[e.ConsumableItemId] = append(catalog.Effects[e.ConsumableItemId], e) + } return catalog, nil } diff --git a/server/internal/service/consumableitem.go b/server/internal/service/consumableitem.go index ba652f9..b4f14a0 100644 --- a/server/internal/service/consumableitem.go +++ b/server/internal/service/consumableitem.go @@ -6,6 +6,8 @@ import ( "log" pb "lunar-tear/server/gen/proto" + "lunar-tear/server/internal/gametime" + "lunar-tear/server/internal/model" "lunar-tear/server/internal/runtime" "lunar-tear/server/internal/store" ) @@ -21,6 +23,49 @@ func NewConsumableItemServiceServer(users store.UserRepository, sessions store.S return &ConsumableItemServiceServer{users: users, sessions: sessions, holder: holder} } +func (s *ConsumableItemServiceServer) UseEffectItem(ctx context.Context, req *pb.ConsumableItemUseEffectItemRequest) (*pb.ConsumableItemUseEffectItemResponse, error) { + log.Printf("[ConsumableItemService] UseEffectItem: consumableItemId=%d count=%d", req.ConsumableItemId, req.Count) + + cat := s.holder.Get() + catalog := cat.ConsumableItem + userId := CurrentUserId(ctx, s.users, s.sessions) + nowMillis := gametime.NowMillis() + + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { + if _, ok := catalog.All[req.ConsumableItemId]; !ok { + log.Printf("[ConsumableItemService] UseEffectItem: unknown consumableItemId=%d", req.ConsumableItemId) + return + } + cur := user.ConsumableItems[req.ConsumableItemId] + if cur < req.Count { + log.Printf("[ConsumableItemService] UseEffectItem: insufficient consumableItemId=%d have=%d need=%d", req.ConsumableItemId, cur, req.Count) + return + } + + user.ConsumableItems[req.ConsumableItemId] -= req.Count + if user.ConsumableItems[req.ConsumableItemId] <= 0 { + delete(user.ConsumableItems, req.ConsumableItemId) + } + + maxStaminaMillis := cat.Shop.MaxStaminaMillis[user.Status.Level] + for _, effect := range catalog.Effects[req.ConsumableItemId] { + switch effect.EffectTargetType { + case model.EffectTargetStaminaRecovery: + millis := store.ResolveStaminaEffectMillis(effect.EffectValueType, effect.EffectValue, maxStaminaMillis) + store.RecoverStamina(user, millis*req.Count, maxStaminaMillis, nowMillis) + default: + log.Printf("[ConsumableItemService] UseEffectItem: unhandled effect targetType=%d valueType=%d value=%d itemId=%d", + effect.EffectTargetType, effect.EffectValueType, effect.EffectValue, req.ConsumableItemId) + } + } + }) + if err != nil { + return nil, fmt.Errorf("consumable item use effect item: %w", err) + } + + return &pb.ConsumableItemUseEffectItemResponse{}, nil +} + func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) { log.Printf("[ConsumableItemService] Sell: %d item(s)", len(req.ConsumableItemPossession)) diff --git a/server/internal/service/parts.go b/server/internal/service/parts.go index b0eadfb..4b03731 100644 --- a/server/internal/service/parts.go +++ b/server/internal/service/parts.go @@ -26,6 +26,50 @@ func NewPartsServiceServer(users store.UserRepository, sessions store.SessionRep return &PartsServiceServer{users: users, sessions: sessions, holder: holder} } +func (s *PartsServiceServer) Protect(ctx context.Context, req *pb.PartsProtectRequest) (*pb.PartsProtectResponse, error) { + log.Printf("[PartsService] Protect: uuids=%v", req.UserPartsUuid) + + userId := CurrentUserId(ctx, s.users, s.sessions) + nowMillis := gametime.NowMillis() + + s.users.UpdateUser(userId, func(user *store.UserState) { + for _, uuid := range req.UserPartsUuid { + part, ok := user.Parts[uuid] + if !ok { + log.Printf("[PartsService] Protect: part uuid=%s not found", uuid) + continue + } + part.IsProtected = true + part.LatestVersion = nowMillis + user.Parts[uuid] = part + } + }) + + return &pb.PartsProtectResponse{}, nil +} + +func (s *PartsServiceServer) Unprotect(ctx context.Context, req *pb.PartsUnprotectRequest) (*pb.PartsUnprotectResponse, error) { + log.Printf("[PartsService] Unprotect: uuids=%v", req.UserPartsUuid) + + userId := CurrentUserId(ctx, s.users, s.sessions) + nowMillis := gametime.NowMillis() + + s.users.UpdateUser(userId, func(user *store.UserState) { + for _, uuid := range req.UserPartsUuid { + part, ok := user.Parts[uuid] + if !ok { + log.Printf("[PartsService] Unprotect: part uuid=%s not found", uuid) + continue + } + part.IsProtected = false + part.LatestVersion = nowMillis + user.Parts[uuid] = part + } + }) + + return &pb.PartsUnprotectResponse{}, nil +} + func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) { log.Printf("[PartsService] Sell: %d part(s)", len(req.UserPartsUuid)) diff --git a/server/internal/service/shop.go b/server/internal/service/shop.go index 0fad974..4859363 100644 --- a/server/internal/service/shop.go +++ b/server/internal/service/shop.go @@ -194,22 +194,10 @@ func applyShopContentEffects(catalog *masterdata.ShopCatalog, user *store.UserSt switch effect.EffectTargetType { case model.EffectTargetStaminaRecovery: maxMillis := catalog.MaxStaminaMillis[user.Status.Level] - millis := resolveShopEffectMillis(catalog, effect.EffectValueType, effect.EffectValue, user.Status.Level) + millis := store.ResolveStaminaEffectMillis(effect.EffectValueType, effect.EffectValue, maxMillis) store.RecoverStamina(user, millis*qty, maxMillis, nowMillis) default: log.Printf("[ShopService] unhandled effect: shopItemId=%d targetType=%d", shopItemId, effect.EffectTargetType) } } } - -func resolveShopEffectMillis(catalog *masterdata.ShopCatalog, effectValueType, effectValue, userLevel int32) int32 { - switch effectValueType { - case model.EffectValueFixed: - return effectValue - case model.EffectValuePermil: - maxMillis := catalog.MaxStaminaMillis[userLevel] - return effectValue * maxMillis / 1000 - default: - return 0 - } -} diff --git a/server/internal/store/stamina.go b/server/internal/store/stamina.go index 549e82d..5e23d6d 100644 --- a/server/internal/store/stamina.go +++ b/server/internal/store/stamina.go @@ -1,6 +1,10 @@ package store -import "log" +import ( + "log" + + "lunar-tear/server/internal/model" +) const StaminaRecoveryDivisor int64 = 180 @@ -39,3 +43,14 @@ func ReplenishStamina(user *UserState, maxStaminaMillis int32, nowMillis int64) user.Status.StaminaUpdateDatetime = nowMillis log.Printf("[ReplenishStamina] set to %d", maxStaminaMillis) } + +func ResolveStaminaEffectMillis(effectValueType, effectValue, maxStaminaMillis int32) int32 { + switch effectValueType { + case model.EffectValueFixed: + return effectValue * 1000 + case model.EffectValuePermil: + return effectValue * maxStaminaMillis / 1000 + default: + return 0 + } +}