mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Add admin API for content reload
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"lunar-tear/server/internal/gacha"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/masterdata/memorydb"
|
||||
"lunar-tear/server/internal/questflow"
|
||||
)
|
||||
|
||||
// buildCatalogs runs the full Load*/Build*/Enrich* sequence against whatever
|
||||
// memorydb currently holds and returns a fully populated *Catalogs. Called
|
||||
// once at startup and again on every reload.
|
||||
func buildCatalogs() (*Catalogs, error) {
|
||||
log.Printf("master data loaded (%d tables)", memorydb.TableCount())
|
||||
|
||||
gameConfig, err := masterdata.LoadGameConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load game config: %w", err)
|
||||
}
|
||||
log.Printf("game config loaded (goldId=%d, skipTicketId=%d, rebirthGold=%d)",
|
||||
gameConfig.ConsumableItemIdForGold, gameConfig.ConsumableItemIdForQuestSkipTicket, gameConfig.CharacterRebirthConsumeGold)
|
||||
|
||||
partsCatalog, err := masterdata.LoadPartsCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load parts catalog: %w", err)
|
||||
}
|
||||
log.Printf("parts catalog loaded: %d parts, %d rarities", len(partsCatalog.PartsById), len(partsCatalog.RarityByRarityType))
|
||||
|
||||
questCatalog, err := masterdata.LoadQuestCatalog(partsCatalog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load quest catalog: %w", err)
|
||||
}
|
||||
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig)
|
||||
|
||||
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load gacha catalog: %w", err)
|
||||
}
|
||||
log.Printf("gacha catalog loaded: %d entries", len(gachaEntries))
|
||||
|
||||
gachaPool, err := masterdata.LoadGachaPool()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load gacha pool: %w", err)
|
||||
}
|
||||
log.Printf("gacha pool loaded: costumes=%d rarities, weapons=%d rarities, materials=%d",
|
||||
len(gachaPool.CostumesByRarity), len(gachaPool.WeaponsByRarity), len(gachaPool.Materials))
|
||||
|
||||
shopCatalog, err := masterdata.LoadShopCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load shop catalog: %w", err)
|
||||
}
|
||||
log.Printf("shop catalog loaded: %d items, %d content groups, %d exchange shops",
|
||||
len(shopCatalog.Items), len(shopCatalog.Contents), len(shopCatalog.ExchangeShopCells))
|
||||
|
||||
gachaPool.BuildShopFeatured(shopCatalog)
|
||||
gachaPool.PruneUnpairedCostumes()
|
||||
gachaPool.BuildFeaturedMapping(gachaEntries)
|
||||
gachaPool.BuildBannerPools(gachaEntries)
|
||||
masterdata.EnrichCatalogPromotions(gachaEntries, gachaPool)
|
||||
|
||||
dupExchange, err := masterdata.LoadDupExchange()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load dup exchange: %w", err)
|
||||
}
|
||||
dupAdded, err := masterdata.EnrichDupExchange(dupExchange, gachaPool)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("enrich dup exchange: %w", err)
|
||||
}
|
||||
log.Printf("dup exchange loaded: %d entries (%d derived from limit-break materials)", len(dupExchange), dupAdded)
|
||||
|
||||
gachaHandler := gacha.NewGachaHandler(gachaPool, gameConfig, questHandler.Granter, medalInfo, dupExchange)
|
||||
|
||||
conditionResolver, err := masterdata.LoadConditionResolver()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load condition resolver: %w", err)
|
||||
}
|
||||
|
||||
cageOrnamentCatalog := masterdata.LoadCageOrnamentCatalog()
|
||||
loginBonusCatalog := masterdata.LoadLoginBonusCatalog()
|
||||
characterViewerCatalog := masterdata.LoadCharacterViewerCatalog(conditionResolver)
|
||||
omikujiCatalog := masterdata.LoadOmikujiCatalog()
|
||||
|
||||
materialCatalog, err := masterdata.LoadMaterialCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load material catalog: %w", err)
|
||||
}
|
||||
log.Printf("material catalog loaded: %d materials", len(materialCatalog.All))
|
||||
|
||||
consumableItemCatalog, err := masterdata.LoadConsumableItemCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load consumable item catalog: %w", err)
|
||||
}
|
||||
log.Printf("consumable item catalog loaded: %d items", len(consumableItemCatalog.All))
|
||||
|
||||
costumeCatalog, err := masterdata.LoadCostumeCatalog(materialCatalog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load costume catalog: %w", err)
|
||||
}
|
||||
log.Printf("costume catalog loaded: %d costumes, %d materials, %d rarity curves", len(costumeCatalog.Costumes), len(costumeCatalog.Materials), len(costumeCatalog.ExpByRarity))
|
||||
|
||||
weaponCatalog, err := masterdata.LoadWeaponCatalog(materialCatalog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load weapon catalog: %w", err)
|
||||
}
|
||||
log.Printf("weapon catalog loaded: %d weapons, %d materials, %d enhance configs", len(weaponCatalog.Weapons), len(weaponCatalog.Materials), len(weaponCatalog.ExpByEnhanceId))
|
||||
|
||||
exploreCatalog, err := masterdata.LoadExploreCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load explore catalog: %w", err)
|
||||
}
|
||||
log.Printf("explore catalog loaded: %d explores, %d grade assets", len(exploreCatalog.Explores), len(exploreCatalog.GradeAssets))
|
||||
|
||||
gimmickCatalog, err := masterdata.LoadGimmickCatalog(conditionResolver)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load gimmick catalog: %w", err)
|
||||
}
|
||||
|
||||
characterBoardCatalog, err := masterdata.LoadCharacterBoardCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character board catalog: %w", err)
|
||||
}
|
||||
log.Printf("character board catalog loaded: %d panels, %d boards", len(characterBoardCatalog.PanelById), len(characterBoardCatalog.BoardById))
|
||||
|
||||
characterRebirthCatalog, err := masterdata.LoadCharacterRebirthCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load character rebirth catalog: %w", err)
|
||||
}
|
||||
log.Printf("character rebirth catalog loaded: %d characters", len(characterRebirthCatalog.StepGroupByCharacterId))
|
||||
|
||||
companionCatalog, err := masterdata.LoadCompanionCatalog()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load companion catalog: %w", err)
|
||||
}
|
||||
log.Printf("companion catalog loaded: %d companions, %d categories", len(companionCatalog.CompanionById), len(companionCatalog.GoldCostByCategory))
|
||||
|
||||
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
|
||||
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
|
||||
|
||||
return &Catalogs{
|
||||
GameConfig: gameConfig,
|
||||
Parts: partsCatalog,
|
||||
Quest: questCatalog,
|
||||
GachaEntries: gachaEntries,
|
||||
GachaMedals: medalInfo,
|
||||
GachaPool: gachaPool,
|
||||
Shop: shopCatalog,
|
||||
DupExchange: dupExchange,
|
||||
ConditionResolver: conditionResolver,
|
||||
CageOrnament: cageOrnamentCatalog,
|
||||
LoginBonus: loginBonusCatalog,
|
||||
CharacterViewer: characterViewerCatalog,
|
||||
Omikuji: omikujiCatalog,
|
||||
Material: materialCatalog,
|
||||
ConsumableItem: consumableItemCatalog,
|
||||
Costume: costumeCatalog,
|
||||
Weapon: weaponCatalog,
|
||||
Explore: exploreCatalog,
|
||||
Gimmick: gimmickCatalog,
|
||||
CharacterBoard: characterBoardCatalog,
|
||||
CharacterRebirth: characterRebirthCatalog,
|
||||
Companion: companionCatalog,
|
||||
SideStory: sideStoryCatalog,
|
||||
BigHunt: bigHuntCatalog,
|
||||
QuestHandler: questHandler,
|
||||
GachaHandler: gachaHandler,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Package runtime owns the live, hot-swappable view of master data.
|
||||
//
|
||||
// The Holder atomically swaps a *Catalogs aggregate every time the operator
|
||||
// asks the server to re-read assets/release/20240404193219.bin.e (typically via
|
||||
// the admin webhook in cmd/lunar-tear/admin.go). gRPC services hold a *Holder
|
||||
// and call Get() at the start of each RPC, so they always see a consistent
|
||||
// snapshot.
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"lunar-tear/server/internal/gacha"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/masterdata/memorydb"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/questflow"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
// Catalogs is an immutable snapshot of every catalog and catalog-derived
|
||||
// handler the server needs at runtime. A new *Catalogs is built from scratch
|
||||
// on every reload and atomically published via Holder.
|
||||
type Catalogs struct {
|
||||
GameConfig *masterdata.GameConfig
|
||||
Parts *masterdata.PartsCatalog
|
||||
Quest *masterdata.QuestCatalog
|
||||
GachaEntries []store.GachaCatalogEntry
|
||||
GachaMedals map[int32]masterdata.GachaMedalInfo
|
||||
GachaPool *masterdata.GachaCatalog
|
||||
Shop *masterdata.ShopCatalog
|
||||
DupExchange map[int32][]model.DupExchangeEntry
|
||||
ConditionResolver *masterdata.ConditionResolver
|
||||
CageOrnament *masterdata.CageOrnamentCatalog
|
||||
LoginBonus *masterdata.LoginBonusCatalog
|
||||
CharacterViewer *masterdata.CharacterViewerCatalog
|
||||
Omikuji *masterdata.OmikujiCatalog
|
||||
Material *masterdata.MaterialCatalog
|
||||
ConsumableItem *masterdata.ConsumableItemCatalog
|
||||
Costume *masterdata.CostumeCatalog
|
||||
Weapon *masterdata.WeaponCatalog
|
||||
Explore *masterdata.ExploreCatalog
|
||||
Gimmick *masterdata.GimmickCatalog
|
||||
CharacterBoard *masterdata.CharacterBoardCatalog
|
||||
CharacterRebirth *masterdata.CharacterRebirthCatalog
|
||||
Companion *masterdata.CompanionCatalog
|
||||
SideStory *masterdata.SideStoryCatalog
|
||||
BigHunt *masterdata.BigHuntCatalog
|
||||
|
||||
// Catalog-derived handlers must rebuild on every reload because they
|
||||
// embed/cache pointers to specific catalog instances.
|
||||
QuestHandler *questflow.QuestHandler
|
||||
GachaHandler *gacha.GachaHandler
|
||||
}
|
||||
|
||||
// Holder owns the current *Catalogs and the bin.e path. Concurrent readers
|
||||
// call Get(); the single-writer Reload() rebuilds and atomically publishes.
|
||||
type Holder struct {
|
||||
binPath string
|
||||
cur atomic.Pointer[Catalogs]
|
||||
}
|
||||
|
||||
// NewHolder reads the binary at binPath, builds the initial catalogs, and
|
||||
// returns a ready-to-use Holder. Subsequent calls to Reload() re-read the
|
||||
// same path.
|
||||
func NewHolder(binPath string) (*Holder, error) {
|
||||
h := &Holder{binPath: binPath}
|
||||
if err := h.Reload(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Reload re-reads the bin.e from disk, rebuilds every catalog and handler,
|
||||
// atomically publishes the new snapshot, and bumps the bin.e mtime so client
|
||||
// caches invalidate (see service/data.go GetLatestMasterDataVersion).
|
||||
func (h *Holder) Reload() error {
|
||||
if err := memorydb.Init(h.binPath); err != nil {
|
||||
return fmt.Errorf("memorydb.Init: %w", err)
|
||||
}
|
||||
c, err := buildCatalogs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("buildCatalogs: %w", err)
|
||||
}
|
||||
h.cur.Store(c)
|
||||
now := time.Now()
|
||||
if err := os.Chtimes(h.binPath, now, now); err != nil {
|
||||
// Non-fatal: the catalogs swapped fine in-memory; clients may take
|
||||
// longer to invalidate their cached download but server-side state is
|
||||
// already coherent.
|
||||
log.Printf("[runtime] os.Chtimes(%s) failed (clients may not invalidate cache): %v", h.binPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the current snapshot. Safe for concurrent callers; the returned
|
||||
// pointer is stable for the duration of the caller's use.
|
||||
func (h *Holder) Get() *Catalogs {
|
||||
return h.cur.Load()
|
||||
}
|
||||
@@ -4,24 +4,29 @@ import (
|
||||
"context"
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/store"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
)
|
||||
|
||||
type BannerServiceServer struct {
|
||||
pb.UnimplementedBannerServiceServer
|
||||
catalog []store.GachaCatalogEntry
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewBannerServiceServer(catalog []store.GachaCatalogEntry) *BannerServiceServer {
|
||||
return &BannerServiceServer{catalog: catalog}
|
||||
func NewBannerServiceServer(holder *runtime.Holder) *BannerServiceServer {
|
||||
return &BannerServiceServer{holder: holder}
|
||||
}
|
||||
|
||||
func (s *BannerServiceServer) GetMamaBanner(ctx context.Context, req *pb.GetMamaBannerRequest) (*pb.GetMamaBannerResponse, error) {
|
||||
catalog := s.catalog
|
||||
catalog := s.holder.Get().GachaEntries
|
||||
nowMillis := gametime.NowMillis()
|
||||
var termLimited []*pb.GachaBanner
|
||||
var latestChapter *pb.GachaBanner
|
||||
for _, entry := range catalog {
|
||||
if !gachaActiveAt(entry, nowMillis) {
|
||||
continue
|
||||
}
|
||||
if entry.GachaLabelType == model.GachaLabelPortalCage || entry.GachaLabelType == model.GachaLabelRecycle {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -15,21 +15,22 @@ type CageOrnamentServiceServer struct {
|
||||
pb.UnimplementedCageOrnamentServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.CageOrnamentCatalog
|
||||
granter *store.PossessionGranter
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewCageOrnamentServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CageOrnamentCatalog, granter *store.PossessionGranter) *CageOrnamentServiceServer {
|
||||
return &CageOrnamentServiceServer{users: users, sessions: sessions, catalog: catalog, granter: granter}
|
||||
func NewCageOrnamentServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CageOrnamentServiceServer {
|
||||
return &CageOrnamentServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.ReceiveRewardRequest) (*pb.ReceiveRewardResponse, error) {
|
||||
log.Printf("[CageOrnamentService] ReceiveReward: cageOrnamentId=%d", req.CageOrnamentId)
|
||||
|
||||
reward, ok := s.catalog.LookupReward(req.CageOrnamentId)
|
||||
cat := s.holder.Get()
|
||||
reward, ok := cat.CageOrnament.LookupReward(req.CageOrnamentId)
|
||||
if !ok {
|
||||
log.Fatalf("[CageOrnamentService] ReceiveReward: no reward for cageOrnamentId=%d", req.CageOrnamentId)
|
||||
}
|
||||
granter := cat.QuestHandler.Granter
|
||||
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
@@ -39,7 +40,7 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
|
||||
AcquisitionDatetime: nowMillis,
|
||||
LatestVersion: nowMillis,
|
||||
}
|
||||
s.granter.GrantFull(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis)
|
||||
granter.GrantFull(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis)
|
||||
})
|
||||
|
||||
return &pb.ReceiveRewardResponse{
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -14,21 +15,23 @@ type CharacterServiceServer struct {
|
||||
pb.UnimplementedCharacterServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.CharacterRebirthCatalog
|
||||
config *masterdata.GameConfig
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewCharacterServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CharacterRebirthCatalog, config *masterdata.GameConfig) *CharacterServiceServer {
|
||||
return &CharacterServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
||||
func NewCharacterServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CharacterServiceServer {
|
||||
return &CharacterServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthRequest) (*pb.RebirthResponse, error) {
|
||||
log.Printf("[CharacterService] Rebirth: characterId=%d rebirthCount=%d", req.CharacterId, req.RebirthCount)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.CharacterRebirth
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
stepGroupId, ok := s.catalog.StepGroupByCharacterId[req.CharacterId]
|
||||
stepGroupId, ok := catalog.StepGroupByCharacterId[req.CharacterId]
|
||||
if !ok {
|
||||
log.Printf("[CharacterService] Rebirth: no step group for characterId=%d", req.CharacterId)
|
||||
return &pb.RebirthResponse{}, nil
|
||||
@@ -40,17 +43,17 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
|
||||
targetCount := currentCount + req.RebirthCount
|
||||
|
||||
for count := currentCount; count < targetCount; count++ {
|
||||
step, ok := s.catalog.StepByGroupAndCount[masterdata.StepKey{GroupId: stepGroupId, BeforeRebirthCount: count}]
|
||||
step, ok := catalog.StepByGroupAndCount[masterdata.StepKey{GroupId: stepGroupId, BeforeRebirthCount: count}]
|
||||
if !ok {
|
||||
log.Printf("[CharacterService] Rebirth: no step row for groupId=%d beforeCount=%d", stepGroupId, count)
|
||||
return
|
||||
}
|
||||
|
||||
goldId := s.config.ConsumableItemIdForGold
|
||||
user.ConsumableItems[goldId] = max(user.ConsumableItems[goldId]-s.config.CharacterRebirthConsumeGold, 0)
|
||||
log.Printf("[CharacterService] Rebirth: consumed gold=%d", s.config.CharacterRebirthConsumeGold)
|
||||
goldId := config.ConsumableItemIdForGold
|
||||
user.ConsumableItems[goldId] = max(user.ConsumableItems[goldId]-config.CharacterRebirthConsumeGold, 0)
|
||||
log.Printf("[CharacterService] Rebirth: consumed gold=%d", config.CharacterRebirthConsumeGold)
|
||||
|
||||
materials := s.catalog.MaterialsByGroupId[step.CharacterRebirthMaterialGroupId]
|
||||
materials := catalog.MaterialsByGroupId[step.CharacterRebirthMaterialGroupId]
|
||||
for _, mat := range materials {
|
||||
user.Materials[mat.MaterialId] -= mat.Count
|
||||
if user.Materials[mat.MaterialId] <= 0 {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -14,43 +15,44 @@ type CharacterBoardServiceServer struct {
|
||||
pb.UnimplementedCharacterBoardServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.CharacterBoardCatalog
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewCharacterBoardServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CharacterBoardCatalog) *CharacterBoardServiceServer {
|
||||
return &CharacterBoardServiceServer{users: users, sessions: sessions, catalog: catalog}
|
||||
func NewCharacterBoardServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CharacterBoardServiceServer {
|
||||
return &CharacterBoardServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.ReleasePanelRequest) (*pb.ReleasePanelResponse, error) {
|
||||
log.Printf("[CharacterBoardService] ReleasePanel: panelIds=%v", req.CharacterBoardPanelId)
|
||||
|
||||
catalog := s.holder.Get().CharacterBoard
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
for _, panelId := range req.CharacterBoardPanelId {
|
||||
panel, ok := s.catalog.PanelById[panelId]
|
||||
panel, ok := catalog.PanelById[panelId]
|
||||
if !ok {
|
||||
log.Printf("[CharacterBoardService] unknown panelId=%d, skipping", panelId)
|
||||
continue
|
||||
}
|
||||
|
||||
s.consumeCosts(user, panel)
|
||||
s.setReleaseBit(user, panel)
|
||||
s.applyEffects(user, panel)
|
||||
consumeBoardCosts(catalog, user, panel)
|
||||
setBoardReleaseBit(user, panel)
|
||||
applyBoardEffects(catalog, user, panel)
|
||||
}
|
||||
})
|
||||
|
||||
return &pb.ReleasePanelResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||
costs := s.catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId]
|
||||
func consumeBoardCosts(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||
costs := catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId]
|
||||
for _, cost := range costs {
|
||||
store.DeductPossession(user, model.PossessionType(cost.PossessionType), cost.PossessionId, cost.Count)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||
func setBoardReleaseBit(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||
boardId := panel.CharacterBoardId
|
||||
board := user.CharacterBoards[boardId]
|
||||
board.CharacterBoardId = boardId
|
||||
@@ -73,26 +75,26 @@ func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel
|
||||
user.CharacterBoards[boardId] = board
|
||||
}
|
||||
|
||||
func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||
effects := s.catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId]
|
||||
func applyBoardEffects(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||
effects := catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId]
|
||||
for _, eff := range effects {
|
||||
switch model.CharacterBoardEffectType(eff.CharacterBoardEffectType) {
|
||||
case model.CharacterBoardEffectTypeAbility:
|
||||
s.applyAbilityEffect(user, eff)
|
||||
applyBoardAbilityEffect(catalog, user, eff)
|
||||
case model.CharacterBoardEffectTypeStatusUp:
|
||||
s.applyStatusUpEffect(user, eff)
|
||||
applyBoardStatusUpEffect(catalog, user, eff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||
ability, ok := s.catalog.AbilityById[eff.CharacterBoardEffectId]
|
||||
func applyBoardAbilityEffect(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||
ability, ok := catalog.AbilityById[eff.CharacterBoardEffectId]
|
||||
if !ok {
|
||||
log.Printf("[CharacterBoardService] unknown abilityId=%d", eff.CharacterBoardEffectId)
|
||||
return
|
||||
}
|
||||
|
||||
characterId := s.resolveCharacterId(ability.CharacterBoardEffectTargetGroupId)
|
||||
characterId := resolveBoardCharacterId(catalog, ability.CharacterBoardEffectTargetGroupId)
|
||||
if characterId == 0 {
|
||||
return
|
||||
}
|
||||
@@ -103,21 +105,21 @@ func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState,
|
||||
state.AbilityId = ability.AbilityId
|
||||
state.Level += eff.EffectValue
|
||||
|
||||
if maxLvl, ok := s.catalog.AbilityMaxLevel[key]; ok && state.Level > maxLvl {
|
||||
if maxLvl, ok := catalog.AbilityMaxLevel[key]; ok && state.Level > maxLvl {
|
||||
state.Level = maxLvl
|
||||
}
|
||||
|
||||
user.CharacterBoardAbilities[key] = state
|
||||
}
|
||||
|
||||
func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||
statusUp, ok := s.catalog.StatusUpById[eff.CharacterBoardEffectId]
|
||||
func applyBoardStatusUpEffect(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||
statusUp, ok := catalog.StatusUpById[eff.CharacterBoardEffectId]
|
||||
if !ok {
|
||||
log.Printf("[CharacterBoardService] unknown statusUpId=%d", eff.CharacterBoardEffectId)
|
||||
return
|
||||
}
|
||||
|
||||
characterId := s.resolveCharacterId(statusUp.CharacterBoardEffectTargetGroupId)
|
||||
characterId := resolveBoardCharacterId(catalog, statusUp.CharacterBoardEffectTargetGroupId)
|
||||
if characterId == 0 {
|
||||
return
|
||||
}
|
||||
@@ -151,8 +153,8 @@ func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState,
|
||||
user.CharacterBoardStatusUps[key] = state
|
||||
}
|
||||
|
||||
func (s *CharacterBoardServiceServer) resolveCharacterId(targetGroupId int32) int32 {
|
||||
targets := s.catalog.EffectTargetsByGroupId[targetGroupId]
|
||||
func resolveBoardCharacterId(catalog *masterdata.CharacterBoardCatalog, targetGroupId int32) int32 {
|
||||
targets := catalog.EffectTargetsByGroupId[targetGroupId]
|
||||
for _, t := range targets {
|
||||
if t.TargetValue != 0 {
|
||||
return t.TargetValue
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -16,11 +16,11 @@ type CharacterViewerServiceServer struct {
|
||||
pb.UnimplementedCharacterViewerServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.CharacterViewerCatalog
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewCharacterViewerServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CharacterViewerCatalog) *CharacterViewerServiceServer {
|
||||
return &CharacterViewerServiceServer{users: users, sessions: sessions, catalog: catalog}
|
||||
func NewCharacterViewerServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CharacterViewerServiceServer {
|
||||
return &CharacterViewerServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _ *emptypb.Empty) (*pb.CharacterViewerTopResponse, error) {
|
||||
@@ -32,7 +32,7 @@ func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _
|
||||
panic(fmt.Sprintf("CharacterViewerTop: no user for userId=%d: %v", userId, err))
|
||||
}
|
||||
|
||||
released := s.catalog.ReleasedFieldIds(user)
|
||||
released := s.holder.Get().CharacterViewer.ReleasedFieldIds(user)
|
||||
log.Printf("[CharacterViewerService] released %d fields for user %d", len(released), userId)
|
||||
|
||||
return &pb.CharacterViewerTopResponse{
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -17,17 +18,19 @@ type CompanionServiceServer struct {
|
||||
pb.UnimplementedCompanionServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.CompanionCatalog
|
||||
config *masterdata.GameConfig
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewCompanionServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CompanionCatalog, config *masterdata.GameConfig) *CompanionServiceServer {
|
||||
return &CompanionServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
||||
func NewCompanionServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CompanionServiceServer {
|
||||
return &CompanionServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionEnhanceRequest) (*pb.CompanionEnhanceResponse, error) {
|
||||
log.Printf("[CompanionService] Enhance: uuid=%s addLevel=%d", req.UserCompanionUuid, req.AddLevelCount)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Companion
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -38,7 +41,7 @@ func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionE
|
||||
return
|
||||
}
|
||||
|
||||
compDef, ok := s.catalog.CompanionById[companion.CompanionId]
|
||||
compDef, ok := catalog.CompanionById[companion.CompanionId]
|
||||
if !ok {
|
||||
log.Printf("[CompanionService] Enhance: companion master id=%d not found", companion.CompanionId)
|
||||
return
|
||||
@@ -50,13 +53,13 @@ func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionE
|
||||
}
|
||||
|
||||
for lvl := companion.Level; lvl < targetLevel; lvl++ {
|
||||
if costFunc, ok := s.catalog.GoldCostByCategory[compDef.CompanionCategoryType]; ok {
|
||||
if costFunc, ok := catalog.GoldCostByCategory[compDef.CompanionCategoryType]; ok {
|
||||
goldCost := costFunc.Evaluate(lvl)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
}
|
||||
|
||||
matKey := masterdata.CompanionLevelKey{CategoryType: compDef.CompanionCategoryType, Level: lvl}
|
||||
if mat, ok := s.catalog.MaterialsByKey[matKey]; ok {
|
||||
if mat, ok := catalog.MaterialsByKey[matKey]; ok {
|
||||
user.Materials[mat.MaterialId] -= mat.Count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -14,23 +14,25 @@ type ConsumableItemServiceServer struct {
|
||||
pb.UnimplementedConsumableItemServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.ConsumableItemCatalog
|
||||
config *masterdata.GameConfig
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewConsumableItemServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.ConsumableItemCatalog, config *masterdata.GameConfig) *ConsumableItemServiceServer {
|
||||
return &ConsumableItemServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
||||
func NewConsumableItemServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *ConsumableItemServiceServer {
|
||||
return &ConsumableItemServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) {
|
||||
log.Printf("[ConsumableItemService] Sell: %d item(s)", len(req.ConsumableItemPossession))
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.ConsumableItem
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
totalGold := int32(0)
|
||||
for _, item := range req.ConsumableItemPossession {
|
||||
row, ok := s.catalog.All[item.ConsumableItemId]
|
||||
row, ok := catalog.All[item.ConsumableItemId]
|
||||
if !ok {
|
||||
log.Printf("[ConsumableItemService] Sell: unknown consumableItemId=%d, skipping", item.ConsumableItemId)
|
||||
continue
|
||||
@@ -53,7 +55,7 @@ func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.Consumab
|
||||
}
|
||||
|
||||
if totalGold > 0 {
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||
log.Printf("[ConsumableItemService] Sell: total gold +%d", totalGold)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"lunar-tear/server/internal/gameutil"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -20,17 +21,19 @@ type CostumeServiceServer struct {
|
||||
pb.UnimplementedCostumeServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.CostumeCatalog
|
||||
config *masterdata.GameConfig
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewCostumeServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CostumeCatalog, config *masterdata.GameConfig) *CostumeServiceServer {
|
||||
return &CostumeServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
||||
func NewCostumeServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CostumeServiceServer {
|
||||
return &CostumeServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceRequest) (*pb.EnhanceResponse, error) {
|
||||
log.Printf("[CostumeService] Enhance: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Costume
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -41,7 +44,7 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
||||
return
|
||||
}
|
||||
|
||||
cm, ok := s.catalog.Costumes[costume.CostumeId]
|
||||
cm, ok := catalog.Costumes[costume.CostumeId]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] Enhance: costume master id=%d not found", costume.CostumeId)
|
||||
return
|
||||
@@ -50,7 +53,7 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
||||
totalExp := int32(0)
|
||||
totalMaterialCount := int32(0)
|
||||
for materialId, count := range req.Materials {
|
||||
mat, ok := s.catalog.Materials[materialId]
|
||||
mat, ok := catalog.Materials[materialId]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] Enhance: material id=%d not found, skipping", materialId)
|
||||
continue
|
||||
@@ -66,20 +69,20 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
||||
|
||||
expPerUnit := mat.EffectValue
|
||||
if mat.WeaponType != 0 && mat.WeaponType == cm.SkillfulWeaponType {
|
||||
expPerUnit = expPerUnit * s.config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||
expPerUnit = expPerUnit * config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||
}
|
||||
totalExp += expPerUnit * count
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.EnhanceCostByRarity[cm.RarityType]; ok && totalMaterialCount > 0 {
|
||||
if costFunc, ok := catalog.EnhanceCostByRarity[cm.RarityType]; ok && totalMaterialCount > 0 {
|
||||
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
log.Printf("[CostumeService] Enhance: gold cost=%d (materials=%d)", goldCost, totalMaterialCount)
|
||||
}
|
||||
|
||||
costume.Exp += totalExp
|
||||
|
||||
if thresholds, ok := s.catalog.ExpByRarity[cm.RarityType]; ok {
|
||||
if thresholds, ok := catalog.ExpByRarity[cm.RarityType]; ok {
|
||||
costume.Level, costume.Exp = gameutil.LevelAndCap(costume.Exp, thresholds)
|
||||
}
|
||||
|
||||
@@ -100,6 +103,9 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
||||
func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest) (*pb.AwakenResponse, error) {
|
||||
log.Printf("[CostumeService] Awaken: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Costume
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -110,7 +116,7 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
||||
return
|
||||
}
|
||||
|
||||
awakenRow, ok := s.catalog.AwakenByCostumeId[costume.CostumeId]
|
||||
awakenRow, ok := catalog.AwakenByCostumeId[costume.CostumeId]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] Awaken: no awaken data for costumeId=%d", costume.CostumeId)
|
||||
return
|
||||
@@ -118,8 +124,8 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
||||
|
||||
nextStep := costume.AwakenCount + 1
|
||||
|
||||
if gold, ok := s.catalog.AwakenPriceByGroup[awakenRow.CostumeAwakenPriceGroupId]; ok {
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= gold
|
||||
if gold, ok := catalog.AwakenPriceByGroup[awakenRow.CostumeAwakenPriceGroupId]; ok {
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= gold
|
||||
log.Printf("[CostumeService] Awaken: gold cost=%d", gold)
|
||||
}
|
||||
|
||||
@@ -137,7 +143,7 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
||||
user.Costumes[req.UserCostumeUuid] = costume
|
||||
log.Printf("[CostumeService] Awaken: costumeId=%d awakenCount=%d", costume.CostumeId, nextStep)
|
||||
|
||||
effectSteps, ok := s.catalog.AwakenEffectsByGroupAndStep[awakenRow.CostumeAwakenEffectGroupId]
|
||||
effectSteps, ok := catalog.AwakenEffectsByGroupAndStep[awakenRow.CostumeAwakenEffectGroupId]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -148,11 +154,11 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
||||
|
||||
switch model.CostumeAwakenEffectType(effect.CostumeAwakenEffectType) {
|
||||
case model.CostumeAwakenEffectTypeStatusUp:
|
||||
s.applyAwakenStatusUp(user, req.UserCostumeUuid, effect.CostumeAwakenEffectId, nowMillis)
|
||||
applyCostumeAwakenStatusUp(catalog, user, req.UserCostumeUuid, effect.CostumeAwakenEffectId, nowMillis)
|
||||
case model.CostumeAwakenEffectTypeAbility:
|
||||
log.Printf("[CostumeService] Awaken: ability effect id=%d (client-resolved)", effect.CostumeAwakenEffectId)
|
||||
case model.CostumeAwakenEffectTypeItemAcquire:
|
||||
s.applyAwakenItemAcquire(user, effect.CostumeAwakenEffectId, nowMillis)
|
||||
applyCostumeAwakenItemAcquire(catalog, user, effect.CostumeAwakenEffectId, nowMillis)
|
||||
default:
|
||||
log.Printf("[CostumeService] Awaken: unknown effect type=%d", effect.CostumeAwakenEffectType)
|
||||
}
|
||||
@@ -164,8 +170,8 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
||||
return &pb.AwakenResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) {
|
||||
rows, ok := s.catalog.AwakenStatusUpByGroup[statusUpGroupId]
|
||||
func applyCostumeAwakenStatusUp(catalog *masterdata.CostumeCatalog, user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) {
|
||||
rows, ok := catalog.AwakenStatusUpByGroup[statusUpGroupId]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] Awaken: status up group %d not found", statusUpGroupId)
|
||||
return
|
||||
@@ -201,8 +207,8 @@ func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costum
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CostumeServiceServer) applyAwakenItemAcquire(user *store.UserState, itemAcquireId int32, nowMillis int64) {
|
||||
acq, ok := s.catalog.AwakenItemAcquireById[itemAcquireId]
|
||||
func applyCostumeAwakenItemAcquire(catalog *masterdata.CostumeCatalog, user *store.UserState, itemAcquireId int32, nowMillis int64) {
|
||||
acq, ok := catalog.AwakenItemAcquireById[itemAcquireId]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] Awaken: item acquire id=%d not found", itemAcquireId)
|
||||
return
|
||||
@@ -226,6 +232,9 @@ func (s *CostumeServiceServer) applyAwakenItemAcquire(user *store.UserState, ite
|
||||
func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.EnhanceActiveSkillRequest) (*pb.EnhanceActiveSkillResponse, error) {
|
||||
log.Printf("[CostumeService] EnhanceActiveSkill: uuid=%s addLevel=%d", req.UserCostumeUuid, req.AddLevelCount)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Costume
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -236,13 +245,13 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
||||
return
|
||||
}
|
||||
|
||||
cm, ok := s.catalog.Costumes[costume.CostumeId]
|
||||
cm, ok := catalog.Costumes[costume.CostumeId]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] EnhanceActiveSkill: costume master id=%d not found", costume.CostumeId)
|
||||
return
|
||||
}
|
||||
|
||||
groupRows := s.catalog.ActiveSkillGroupsByGroupId[cm.CostumeActiveSkillGroupId]
|
||||
groupRows := catalog.ActiveSkillGroupsByGroupId[cm.CostumeActiveSkillGroupId]
|
||||
enhanceMatId := int32(-1)
|
||||
for _, g := range groupRows {
|
||||
if g.CostumeLimitBreakCountLowerLimit <= costume.LimitBreakCount {
|
||||
@@ -259,7 +268,7 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
||||
skill := user.CostumeActiveSkills[req.UserCostumeUuid]
|
||||
currentLevel := skill.Level
|
||||
|
||||
maxLevelFunc, ok := s.catalog.ActiveSkillMaxLevelByRarity[cm.RarityType]
|
||||
maxLevelFunc, ok := catalog.ActiveSkillMaxLevelByRarity[cm.RarityType]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] EnhanceActiveSkill: no max level func for rarity=%d", cm.RarityType)
|
||||
return
|
||||
@@ -277,7 +286,7 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
||||
|
||||
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
||||
key := [2]int32{enhanceMatId, lvl}
|
||||
mats := s.catalog.ActiveSkillEnhanceMats[key]
|
||||
mats := catalog.ActiveSkillEnhanceMats[key]
|
||||
for _, mat := range mats {
|
||||
cur := user.Materials[mat.MaterialId]
|
||||
cost := mat.Count
|
||||
@@ -288,9 +297,9 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
||||
user.Materials[mat.MaterialId] = cur - cost
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.ActiveSkillCostByRarity[cm.RarityType]; ok {
|
||||
if costFunc, ok := catalog.ActiveSkillCostByRarity[cm.RarityType]; ok {
|
||||
goldCost := costFunc.Evaluate(lvl + 1)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,6 +319,9 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
||||
func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBreakRequest) (*pb.LimitBreakResponse, error) {
|
||||
log.Printf("[CostumeService] LimitBreak: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Costume
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -320,12 +332,12 @@ func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBrea
|
||||
return
|
||||
}
|
||||
|
||||
if costume.LimitBreakCount >= s.config.CostumeLimitBreakAvailableCount {
|
||||
if costume.LimitBreakCount >= config.CostumeLimitBreakAvailableCount {
|
||||
log.Printf("[CostumeService] LimitBreak: already at max limit break %d", costume.LimitBreakCount)
|
||||
return
|
||||
}
|
||||
|
||||
cm, ok := s.catalog.Costumes[costume.CostumeId]
|
||||
cm, ok := catalog.Costumes[costume.CostumeId]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] LimitBreak: costume master id=%d not found", costume.CostumeId)
|
||||
return
|
||||
@@ -342,9 +354,9 @@ func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBrea
|
||||
totalMaterialCount += count
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.LimitBreakCostByRarity[cm.RarityType]; ok && totalMaterialCount > 0 {
|
||||
if costFunc, ok := catalog.LimitBreakCostByRarity[cm.RarityType]; ok && totalMaterialCount > 0 {
|
||||
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
log.Printf("[CostumeService] LimitBreak: gold cost=%d", goldCost)
|
||||
}
|
||||
|
||||
@@ -363,6 +375,9 @@ func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBrea
|
||||
func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req *pb.UnlockLotteryEffectSlotRequest) (*pb.UnlockLotteryEffectSlotResponse, error) {
|
||||
log.Printf("[CostumeService] UnlockLotteryEffectSlot: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Costume
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -373,15 +388,15 @@ func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req
|
||||
return
|
||||
}
|
||||
|
||||
effectRow, ok := s.catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||
effectRow, ok := catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] UnlockLotteryEffectSlot: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
||||
return
|
||||
}
|
||||
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= s.config.CostumeLotteryEffectUnlockSlotConsumeGold
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= config.CostumeLotteryEffectUnlockSlotConsumeGold
|
||||
|
||||
mats := s.catalog.LotteryEffectMats[effectRow.CostumeLotteryEffectUnlockMaterialGroupId]
|
||||
mats := catalog.LotteryEffectMats[effectRow.CostumeLotteryEffectUnlockMaterialGroupId]
|
||||
for _, mat := range mats {
|
||||
cur := user.Materials[mat.MaterialId]
|
||||
cost := mat.Count
|
||||
@@ -418,6 +433,9 @@ func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req
|
||||
func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.DrawLotteryEffectRequest) (*pb.DrawLotteryEffectResponse, error) {
|
||||
log.Printf("[CostumeService] DrawLotteryEffect: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Costume
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -428,21 +446,21 @@ func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.Dr
|
||||
return
|
||||
}
|
||||
|
||||
effectRow, ok := s.catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||
effectRow, ok := catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||
if !ok {
|
||||
log.Printf("[CostumeService] DrawLotteryEffect: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
||||
return
|
||||
}
|
||||
|
||||
oddsPool := s.catalog.LotteryEffectOdds[effectRow.CostumeLotteryEffectOddsGroupId]
|
||||
oddsPool := catalog.LotteryEffectOdds[effectRow.CostumeLotteryEffectOddsGroupId]
|
||||
if len(oddsPool) == 0 {
|
||||
log.Printf("[CostumeService] DrawLotteryEffect: empty odds pool for groupId=%d", effectRow.CostumeLotteryEffectOddsGroupId)
|
||||
return
|
||||
}
|
||||
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= s.config.CostumeLotteryEffectDrawSlotConsumeGold
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= config.CostumeLotteryEffectDrawSlotConsumeGold
|
||||
|
||||
mats := s.catalog.LotteryEffectMats[effectRow.CostumeLotteryEffectDrawMaterialGroupId]
|
||||
mats := catalog.LotteryEffectMats[effectRow.CostumeLotteryEffectDrawMaterialGroupId]
|
||||
for _, mat := range mats {
|
||||
cur := user.Materials[mat.MaterialId]
|
||||
cost := mat.Count
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/store"
|
||||
@@ -12,6 +13,16 @@ import (
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// masterDataBinPath is the canonical location of the encrypted master data
|
||||
// file. The mtime of this file is folded into the version string so the
|
||||
// client invalidates its cache as soon as an admin reload swaps it in.
|
||||
const masterDataBinPath = "assets/release/20240404193219.bin.e"
|
||||
|
||||
// masterDataBaseVersion preserves the historical "yyyymmddHHMMSS" value the
|
||||
// client has always seen; we suffix it with the file mtime to force a
|
||||
// re-download when content changes.
|
||||
const masterDataBaseVersion = "20240404193219"
|
||||
|
||||
type DataServiceServer struct {
|
||||
pb.UnimplementedDataServiceServer
|
||||
users store.UserRepository
|
||||
@@ -23,9 +34,15 @@ func NewDataServiceServer(users store.UserRepository, sessions store.SessionRepo
|
||||
}
|
||||
|
||||
func (s *DataServiceServer) GetLatestMasterDataVersion(ctx context.Context, _ *emptypb.Empty) (*pb.MasterDataGetLatestVersionResponse, error) {
|
||||
log.Printf("[DataService] GetLatestMasterDataVersion")
|
||||
version := masterDataBaseVersion
|
||||
if info, err := os.Stat(masterDataBinPath); err == nil {
|
||||
version = fmt.Sprintf("%s_%d", masterDataBaseVersion, info.ModTime().UnixMilli())
|
||||
} else {
|
||||
log.Printf("[DataService] stat %s: %v (falling back to base version)", masterDataBinPath, err)
|
||||
}
|
||||
log.Printf("[DataService] GetLatestMasterDataVersion -> %s", version)
|
||||
return &pb.MasterDataGetLatestVersionResponse{
|
||||
LatestMasterDataVersion: "20240404193219",
|
||||
LatestMasterDataVersion: version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -22,17 +22,18 @@ type ExploreServiceServer struct {
|
||||
pb.UnimplementedExploreServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.ExploreCatalog
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewExploreServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.ExploreCatalog) *ExploreServiceServer {
|
||||
return &ExploreServiceServer{users: users, sessions: sessions, catalog: catalog}
|
||||
func NewExploreServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *ExploreServiceServer {
|
||||
return &ExploreServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartExploreRequest) (*pb.StartExploreResponse, error) {
|
||||
log.Printf("[ExploreService] StartExplore: exploreId=%d useConsumableItemId=%d", req.ExploreId, req.UseConsumableItemId)
|
||||
|
||||
if _, ok := s.catalog.Explores[req.ExploreId]; !ok {
|
||||
catalog := s.holder.Get().Explore
|
||||
if _, ok := catalog.Explores[req.ExploreId]; !ok {
|
||||
return nil, fmt.Errorf("explore id=%d not found", req.ExploreId)
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartEx
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
explore := s.catalog.Explores[req.ExploreId]
|
||||
explore := catalog.Explores[req.ExploreId]
|
||||
if req.UseConsumableItemId > 0 && explore.ConsumeItemCount > 0 {
|
||||
cur := user.ConsumableItems[req.UseConsumableItemId]
|
||||
user.ConsumableItems[req.UseConsumableItemId] = cur - explore.ConsumeItemCount
|
||||
@@ -64,12 +65,13 @@ func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartEx
|
||||
func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.FinishExploreRequest) (*pb.FinishExploreResponse, error) {
|
||||
log.Printf("[ExploreService] FinishExplore: exploreId=%d score=%d", req.ExploreId, req.Score)
|
||||
|
||||
explore, ok := s.catalog.Explores[req.ExploreId]
|
||||
catalog := s.holder.Get().Explore
|
||||
explore, ok := catalog.Explores[req.ExploreId]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("explore id=%d not found", req.ExploreId)
|
||||
}
|
||||
|
||||
assetGradeIconId := s.catalog.GradeForScore(req.ExploreId, req.Score)
|
||||
assetGradeIconId := catalog.GradeForScore(req.ExploreId, req.Score)
|
||||
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"lunar-tear/server/internal/gacha"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -20,34 +21,33 @@ type GachaServiceServer struct {
|
||||
pb.UnimplementedGachaServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog []store.GachaCatalogEntry
|
||||
handler *gacha.GachaHandler
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewGachaServiceServer(
|
||||
users store.UserRepository,
|
||||
sessions store.SessionRepository,
|
||||
catalog []store.GachaCatalogEntry,
|
||||
handler *gacha.GachaHandler,
|
||||
holder *runtime.Holder,
|
||||
) *GachaServiceServer {
|
||||
return &GachaServiceServer{
|
||||
users: users,
|
||||
sessions: sessions,
|
||||
catalog: catalog,
|
||||
handler: handler,
|
||||
holder: holder,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaListRequest) (*pb.GetGachaListResponse, error) {
|
||||
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
|
||||
|
||||
catalog := s.catalog
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.GachaEntries
|
||||
handler := cat.GachaHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
user, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
user.EnsureMaps()
|
||||
s.autoConvertExpiredMedals(user, catalog, nowMillis)
|
||||
autoConvertExpiredMedals(user, catalog, handler, nowMillis)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("update user: %w", err)
|
||||
@@ -55,6 +55,9 @@ func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaL
|
||||
|
||||
gachaList := make([]*pb.Gacha, 0, len(catalog))
|
||||
for _, entry := range catalog {
|
||||
if !gachaActiveAt(entry, nowMillis) {
|
||||
continue
|
||||
}
|
||||
if !matchesGachaLabel(req.GachaLabelType, entry.GachaLabelType) {
|
||||
continue
|
||||
}
|
||||
@@ -71,7 +74,7 @@ func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaL
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *GachaServiceServer) autoConvertExpiredMedals(user *store.UserState, catalog []store.GachaCatalogEntry, nowMillis int64) {
|
||||
func autoConvertExpiredMedals(user *store.UserState, catalog []store.GachaCatalogEntry, handler *gacha.GachaHandler, nowMillis int64) {
|
||||
for _, entry := range catalog {
|
||||
if entry.GachaMedalId == 0 || entry.EndDatetime == 0 {
|
||||
continue
|
||||
@@ -84,7 +87,7 @@ func (s *GachaServiceServer) autoConvertExpiredMedals(user *store.UserState, cat
|
||||
continue
|
||||
}
|
||||
|
||||
medalInfo, ok := s.handler.MedalInfo[entry.GachaId]
|
||||
medalInfo, ok := handler.MedalInfo[entry.GachaId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -117,7 +120,8 @@ func (s *GachaServiceServer) autoConvertExpiredMedals(user *store.UserState, cat
|
||||
func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaRequest) (*pb.GetGachaResponse, error) {
|
||||
log.Printf("[GachaService] GetGacha: ids=%v", req.GachaId)
|
||||
|
||||
catalog := s.catalog
|
||||
catalog := s.holder.Get().GachaEntries
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
user, err := s.users.LoadUser(userId)
|
||||
@@ -128,11 +132,15 @@ func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaReque
|
||||
byId := make(map[int32]*pb.Gacha, len(req.GachaId))
|
||||
for _, wantedId := range req.GachaId {
|
||||
for _, entry := range catalog {
|
||||
if entry.GachaId == wantedId {
|
||||
bs := user.Gacha.BannerStates[entry.GachaId]
|
||||
byId[wantedId] = toProtoGacha(entry, &bs)
|
||||
if entry.GachaId != wantedId {
|
||||
continue
|
||||
}
|
||||
if !gachaActiveAt(entry, nowMillis) {
|
||||
break
|
||||
}
|
||||
bs := user.Gacha.BannerStates[entry.GachaId]
|
||||
byId[wantedId] = toProtoGacha(entry, &bs)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +152,12 @@ func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaReque
|
||||
func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb.DrawResponse, error) {
|
||||
log.Printf("[GachaService] Draw: gachaId=%d phaseId=%d execCount=%d", req.GachaId, req.GachaPricePhaseId, req.ExecCount)
|
||||
|
||||
entry := findCatalogEntry(s.catalog, req.GachaId)
|
||||
cat := s.holder.Get()
|
||||
entry := findCatalogEntry(cat.GachaEntries, req.GachaId)
|
||||
if entry == nil {
|
||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||
}
|
||||
handler := cat.GachaHandler
|
||||
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
execCount := req.ExecCount
|
||||
@@ -158,7 +168,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
|
||||
var drawResult *gacha.DrawResult
|
||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
var drawErr error
|
||||
drawResult, drawErr = s.handler.HandleDraw(user, *entry, req.GachaPricePhaseId, execCount)
|
||||
drawResult, drawErr = handler.HandleDraw(user, *entry, req.GachaPricePhaseId, execCount)
|
||||
if drawErr != nil {
|
||||
log.Printf("[GachaService] Draw error: %v", drawErr)
|
||||
drawResult = &gacha.DrawResult{}
|
||||
@@ -285,14 +295,16 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
|
||||
func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBoxGachaRequest) (*pb.ResetBoxGachaResponse, error) {
|
||||
log.Printf("[GachaService] ResetBoxGacha: gachaId=%d", req.GachaId)
|
||||
|
||||
entry := findCatalogEntry(s.catalog, req.GachaId)
|
||||
cat := s.holder.Get()
|
||||
entry := findCatalogEntry(cat.GachaEntries, req.GachaId)
|
||||
if entry == nil {
|
||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||
}
|
||||
handler := cat.GachaHandler
|
||||
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
if resetErr := s.handler.HandleResetBox(user, *entry); resetErr != nil {
|
||||
if resetErr := handler.HandleResetBox(user, *entry); resetErr != nil {
|
||||
log.Printf("[GachaService] ResetBoxGacha error: %v", resetErr)
|
||||
}
|
||||
})
|
||||
@@ -315,7 +327,7 @@ func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Em
|
||||
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||
}
|
||||
|
||||
maxCount := s.handler.Config.RewardGachaDailyMaxCount
|
||||
maxCount := s.holder.Get().GachaHandler.Config.RewardGachaDailyMaxCount
|
||||
if maxCount <= 0 {
|
||||
maxCount = model.DefaultDailyDrawLimit
|
||||
}
|
||||
@@ -337,11 +349,12 @@ func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawR
|
||||
log.Printf("[GachaService] RewardDraw: placement=%q reward=%q amount=%q", req.PlacementName, req.RewardName, req.RewardAmount)
|
||||
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
handler := s.holder.Get().GachaHandler
|
||||
|
||||
var items []gacha.DrawnItem
|
||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
var drawErr error
|
||||
items, drawErr = s.handler.HandleRewardDraw(user, 1)
|
||||
items, drawErr = handler.HandleRewardDraw(user, 1)
|
||||
if drawErr != nil {
|
||||
log.Printf("[GachaService] RewardDraw error: %v", drawErr)
|
||||
}
|
||||
@@ -395,6 +408,16 @@ func matchesGachaLabel(labels []int32, label int32) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func gachaActiveAt(entry store.GachaCatalogEntry, nowMillis int64) bool {
|
||||
if entry.StartDatetime != 0 && nowMillis < entry.StartDatetime {
|
||||
return false
|
||||
}
|
||||
if entry.EndDatetime != 0 && nowMillis >= entry.EndDatetime {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func toProtoGacha(entry store.GachaCatalogEntry, bs *store.GachaBannerState) *pb.Gacha {
|
||||
g := &pb.Gacha{
|
||||
GachaId: entry.GachaId,
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -14,13 +14,13 @@ import (
|
||||
|
||||
type GimmickServiceServer struct {
|
||||
pb.UnimplementedGimmickServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
gimmickCatalog *masterdata.GimmickCatalog
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewGimmickServiceServer(users store.UserRepository, sessions store.SessionRepository, gimmickCatalog *masterdata.GimmickCatalog) *GimmickServiceServer {
|
||||
return &GimmickServiceServer{users: users, sessions: sessions, gimmickCatalog: gimmickCatalog}
|
||||
func NewGimmickServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *GimmickServiceServer {
|
||||
return &GimmickServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.UpdateSequenceRequest) (*pb.UpdateSequenceResponse, error) {
|
||||
@@ -80,7 +80,7 @@ func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *empt
|
||||
now := gametime.NowMillis()
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
added := 0
|
||||
for _, key := range s.gimmickCatalog.ActiveScheduleKeys(*user, now) {
|
||||
for _, key := range s.holder.Get().Gimmick.ActiveScheduleKeys(*user, now) {
|
||||
if _, exists := user.Gimmick.Sequences[key]; !exists {
|
||||
user.Gimmick.Sequences[key] = store.GimmickSequenceState{Key: key}
|
||||
added++
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -19,22 +19,23 @@ type LoginBonusServiceServer struct {
|
||||
pb.UnimplementedLoginBonusServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.LoginBonusCatalog
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewLoginBonusServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.LoginBonusCatalog) *LoginBonusServiceServer {
|
||||
return &LoginBonusServiceServer{users: users, sessions: sessions, catalog: catalog}
|
||||
func NewLoginBonusServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *LoginBonusServiceServer {
|
||||
return &LoginBonusServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb.Empty) (*pb.ReceiveStampResponse, error) {
|
||||
log.Printf("[LoginBonusService] ReceiveStamp")
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
catalog := s.holder.Get().LoginBonus
|
||||
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
now := gametime.NowMillis()
|
||||
nextStamp := user.LoginBonus.CurrentStampNumber + 1
|
||||
|
||||
reward, ok := s.catalog.LookupStampReward(
|
||||
reward, ok := catalog.LookupStampReward(
|
||||
user.LoginBonus.LoginBonusId,
|
||||
user.LoginBonus.CurrentPageNumber,
|
||||
nextStamp,
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -14,23 +14,25 @@ type MaterialServiceServer struct {
|
||||
pb.UnimplementedMaterialServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.MaterialCatalog
|
||||
config *masterdata.GameConfig
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewMaterialServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.MaterialCatalog, config *masterdata.GameConfig) *MaterialServiceServer {
|
||||
return &MaterialServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
||||
func NewMaterialServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *MaterialServiceServer {
|
||||
return &MaterialServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRequest) (*pb.MaterialSellResponse, error) {
|
||||
log.Printf("[MaterialService] Sell: %d item(s)", len(req.MaterialPossession))
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Material
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
totalGold := int32(0)
|
||||
for _, item := range req.MaterialPossession {
|
||||
mat, ok := s.catalog.All[item.MaterialId]
|
||||
mat, ok := catalog.All[item.MaterialId]
|
||||
if !ok {
|
||||
log.Printf("[MaterialService] Sell: unknown materialId=%d, skipping", item.MaterialId)
|
||||
continue
|
||||
@@ -53,7 +55,7 @@ func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRe
|
||||
}
|
||||
|
||||
if totalGold > 0 {
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||
log.Printf("[MaterialService] Sell: total gold +%d", totalGold)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -414,24 +414,12 @@ func (s *OctoHTTPServer) serveListBin(w http.ResponseWriter, filePath string) {
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
// serveDatabaseBinE serves MasterMemory database: /assets/release/{version}/database.bin.e
|
||||
// -> assets/release/{version}.bin.e (or assets/release/database.bin.e fallback).
|
||||
func (s *OctoHTTPServer) serveDatabaseBinE(w http.ResponseWriter, r *http.Request, path string) {
|
||||
parts := strings.Split(path, "/")
|
||||
var version string
|
||||
for i, p := range parts {
|
||||
if p == "release" && i+1 < len(parts) {
|
||||
version = parts[i+1]
|
||||
break
|
||||
}
|
||||
}
|
||||
filePath := filepath.Join(s.BaseDir, "assets", "release", "database.bin.e")
|
||||
if version != "" {
|
||||
vPath := filepath.Join(s.BaseDir, "assets", "release", version+".bin.e")
|
||||
if _, err := os.Stat(vPath); err == nil {
|
||||
filePath = vPath
|
||||
}
|
||||
}
|
||||
// serveDatabaseBinE serves the master data binary. The URL's {version} segment
|
||||
// is a cache key (it changes whenever the file's mtime changes, see
|
||||
// DataService.GetLatestMasterDataVersion) but does not select a different file —
|
||||
// there's only ever one bin.e on disk.
|
||||
func (s *OctoHTTPServer) serveDatabaseBinE(w http.ResponseWriter, r *http.Request, _ string) {
|
||||
filePath := filepath.Join(s.BaseDir, "assets", "release", "20240404193219.bin.e")
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -15,11 +15,11 @@ type OmikujiServiceServer struct {
|
||||
pb.UnimplementedOmikujiServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.OmikujiCatalog
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewOmikujiServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.OmikujiCatalog) *OmikujiServiceServer {
|
||||
return &OmikujiServiceServer{users: users, sessions: sessions, catalog: catalog}
|
||||
func NewOmikujiServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *OmikujiServiceServer {
|
||||
return &OmikujiServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiDrawRequest) (*pb.OmikujiDrawResponse, error) {
|
||||
@@ -36,7 +36,7 @@ func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiD
|
||||
}
|
||||
|
||||
return &pb.OmikujiDrawResponse{
|
||||
OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId),
|
||||
OmikujiResultAssetId: s.holder.Get().Omikuji.LookupAssetId(req.OmikujiId),
|
||||
OmikujiItem: []*pb.OmikujiItem{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -18,17 +19,19 @@ type PartsServiceServer struct {
|
||||
pb.UnimplementedPartsServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.PartsCatalog
|
||||
config *masterdata.GameConfig
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewPartsServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.PartsCatalog, config *masterdata.GameConfig) *PartsServiceServer {
|
||||
return &PartsServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
||||
func NewPartsServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *PartsServiceServer {
|
||||
return &PartsServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) {
|
||||
log.Printf("[PartsService] Sell: %d part(s)", len(req.UserPartsUuid))
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Parts
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
@@ -44,13 +47,13 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
|
||||
continue
|
||||
}
|
||||
|
||||
partDef, ok := s.catalog.PartsById[part.PartsId]
|
||||
partDef, ok := catalog.PartsById[part.PartsId]
|
||||
if !ok {
|
||||
log.Printf("[PartsService] Sell: partsId=%d not in catalog, skipping", part.PartsId)
|
||||
continue
|
||||
}
|
||||
|
||||
sellFunc, ok := s.catalog.SellPriceByRarity[partDef.RarityType]
|
||||
sellFunc, ok := catalog.SellPriceByRarity[partDef.RarityType]
|
||||
if !ok {
|
||||
log.Printf("[PartsService] Sell: no sell price func for rarity=%d, skipping", partDef.RarityType)
|
||||
continue
|
||||
@@ -68,7 +71,7 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
|
||||
}
|
||||
|
||||
if totalGold > 0 {
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||
log.Printf("[PartsService] Sell: total gold +%d", totalGold)
|
||||
}
|
||||
})
|
||||
@@ -82,6 +85,9 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
|
||||
func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRequest) (*pb.PartsEnhanceResponse, error) {
|
||||
log.Printf("[PartsService] Enhance: uuid=%s", req.UserPartsUuid)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Parts
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -99,33 +105,33 @@ func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRe
|
||||
return
|
||||
}
|
||||
|
||||
partDef, ok := s.catalog.PartsById[part.PartsId]
|
||||
partDef, ok := catalog.PartsById[part.PartsId]
|
||||
if !ok {
|
||||
log.Printf("[PartsService] Enhance: part master id=%d not found", part.PartsId)
|
||||
return
|
||||
}
|
||||
|
||||
rarity, ok := s.catalog.RarityByRarityType[partDef.RarityType]
|
||||
rarity, ok := catalog.RarityByRarityType[partDef.RarityType]
|
||||
if !ok {
|
||||
log.Printf("[PartsService] Enhance: rarity type=%d not found", partDef.RarityType)
|
||||
return
|
||||
}
|
||||
|
||||
goldCost := int32(0)
|
||||
if prices, ok := s.catalog.PriceByGroupAndLevel[rarity.PartsLevelUpPriceGroupId]; ok {
|
||||
if prices, ok := catalog.PriceByGroupAndLevel[rarity.PartsLevelUpPriceGroupId]; ok {
|
||||
goldCost = prices[part.Level]
|
||||
}
|
||||
|
||||
currentGold := user.ConsumableItems[s.config.ConsumableItemIdForGold]
|
||||
currentGold := user.ConsumableItems[config.ConsumableItemIdForGold]
|
||||
if currentGold < goldCost {
|
||||
log.Printf("[PartsService] Enhance: insufficient gold have=%d need=%d", currentGold, goldCost)
|
||||
return
|
||||
}
|
||||
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
|
||||
successRate := int32(1000)
|
||||
if rates, ok := s.catalog.RateByGroupAndLevel[rarity.PartsLevelUpRateGroupId]; ok {
|
||||
if rates, ok := catalog.RateByGroupAndLevel[rarity.PartsLevelUpRateGroupId]; ok {
|
||||
if r, ok := rates[part.Level]; ok {
|
||||
successRate = r
|
||||
}
|
||||
@@ -137,7 +143,7 @@ func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRe
|
||||
log.Printf("[PartsService] Enhance: SUCCESS partsId=%d level %d -> %d (rate=%d‰, cost=%d gold)",
|
||||
part.PartsId, part.Level-1, part.Level, successRate, goldCost)
|
||||
|
||||
s.grantSubStatuses(user, req.UserPartsUuid, part, partDef, nowMillis)
|
||||
grantPartsSubStatuses(catalog, user, req.UserPartsUuid, part, partDef, nowMillis)
|
||||
} else {
|
||||
log.Printf("[PartsService] Enhance: FAIL partsId=%d stays level %d (rate=%d‰, cost=%d gold)",
|
||||
part.PartsId, part.Level, successRate, goldCost)
|
||||
@@ -155,9 +161,9 @@ func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PartsServiceServer) grantSubStatuses(user *store.UserState, uuid string, part store.PartsState, partDef masterdata.EntityMParts, nowMillis int64) {
|
||||
unlockLevels := s.catalog.SubStatusUnlockLvls[partDef.RarityType]
|
||||
pool := s.catalog.SubStatusPool[partDef.PartsStatusSubLotteryGroupId]
|
||||
func grantPartsSubStatuses(catalog *masterdata.PartsCatalog, user *store.UserState, uuid string, part store.PartsState, partDef masterdata.EntityMParts, nowMillis int64) {
|
||||
unlockLevels := catalog.SubStatusUnlockLvls[partDef.RarityType]
|
||||
pool := catalog.SubStatusPool[partDef.PartsStatusSubLotteryGroupId]
|
||||
if len(pool) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -173,13 +179,13 @@ func (s *PartsServiceServer) grantSubStatuses(user *store.UserState, uuid string
|
||||
}
|
||||
|
||||
pick := pool[rand.Intn(len(pool))]
|
||||
def, ok := s.catalog.PartsStatusMainById[pick]
|
||||
def, ok := catalog.PartsStatusMainById[pick]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
statusValue := def.StatusChangeInitialValue
|
||||
if f, ok := s.catalog.FuncResolver.Resolve(def.StatusNumericalFunctionId); ok {
|
||||
if f, ok := catalog.FuncResolver.Resolve(def.StatusNumericalFunctionId); ok {
|
||||
statusValue = f.Evaluate(part.Level)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/questflow"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -18,34 +18,35 @@ type BigHuntServiceServer struct {
|
||||
pb.UnimplementedBigHuntServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.BigHuntCatalog
|
||||
engine *questflow.QuestHandler
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewBigHuntServiceServer(
|
||||
users store.UserRepository,
|
||||
sessions store.SessionRepository,
|
||||
catalog *masterdata.BigHuntCatalog,
|
||||
engine *questflow.QuestHandler,
|
||||
holder *runtime.Holder,
|
||||
) *BigHuntServiceServer {
|
||||
return &BigHuntServiceServer{users: users, sessions: sessions, catalog: catalog, engine: engine}
|
||||
return &BigHuntServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.StartBigHuntQuestRequest) (*pb.StartBigHuntQuestResponse, error) {
|
||||
log.Printf("[BigHuntService] StartBigHuntQuest: bossQuestId=%d questId=%d deckNumber=%d isDryRun=%v",
|
||||
req.BigHuntBossQuestId, req.BigHuntQuestId, req.UserDeckNumber, req.IsDryRun)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.BigHunt
|
||||
engine := cat.QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
bhQuest, ok := s.catalog.QuestById[req.BigHuntQuestId]
|
||||
bhQuest, ok := catalog.QuestById[req.BigHuntQuestId]
|
||||
if !ok {
|
||||
log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId)
|
||||
}
|
||||
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
if ok {
|
||||
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis)
|
||||
engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis)
|
||||
}
|
||||
|
||||
user.BigHuntProgress = store.BigHuntProgress{
|
||||
@@ -85,18 +86,21 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
||||
log.Printf("[BigHuntService] FinishBigHuntQuest: bossQuestId=%d questId=%d isRetired=%v",
|
||||
req.BigHuntBossQuestId, req.BigHuntQuestId, req.IsRetired)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.BigHunt
|
||||
engine := cat.QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
||||
bossQuest := s.catalog.BossQuestById[req.BigHuntBossQuestId]
|
||||
boss := s.catalog.BossByBossId[bossQuest.BigHuntBossId]
|
||||
bhQuest := catalog.QuestById[req.BigHuntQuestId]
|
||||
bossQuest := catalog.BossQuestById[req.BigHuntBossQuestId]
|
||||
boss := catalog.BossByBossId[bossQuest.BigHuntBossId]
|
||||
|
||||
var scoreInfo *pb.BigHuntScoreInfo
|
||||
var scoreRewards []*pb.BigHuntReward
|
||||
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleBigHuntQuestFinish(user, bhQuest.QuestId, req.IsRetired, false, nowMillis)
|
||||
engine.HandleBigHuntQuestFinish(user, bhQuest.QuestId, req.IsRetired, false, nowMillis)
|
||||
|
||||
if req.IsRetired || user.BigHuntProgress.IsDryRun {
|
||||
user.BigHuntProgress = store.BigHuntProgress{LatestVersion: nowMillis}
|
||||
@@ -108,7 +112,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
||||
baseScore := totalDamage
|
||||
|
||||
difficultyBonusPermil := int32(0)
|
||||
if coeff, ok := s.catalog.ScoreCoefficients[bhQuest.BigHuntQuestScoreCoefficientId]; ok {
|
||||
if coeff, ok := catalog.ScoreCoefficients[bhQuest.BigHuntQuestScoreCoefficientId]; ok {
|
||||
difficultyBonusPermil = coeff
|
||||
}
|
||||
|
||||
@@ -138,7 +142,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
||||
}
|
||||
|
||||
schedKey := store.BigHuntScheduleScoreKey{
|
||||
BigHuntScheduleId: s.catalog.ActiveScheduleId,
|
||||
BigHuntScheduleId: catalog.ActiveScheduleId,
|
||||
BigHuntBossId: bossQuest.BigHuntBossId,
|
||||
}
|
||||
oldSchedMax := user.BigHuntScheduleMaxScores[schedKey].MaxScore
|
||||
@@ -163,7 +167,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
||||
}
|
||||
}
|
||||
|
||||
assetGradeIconId := s.catalog.ResolveGradeIconId(bossQuest.BigHuntBossId, userScore)
|
||||
assetGradeIconId := catalog.ResolveGradeIconId(bossQuest.BigHuntBossId, userScore)
|
||||
|
||||
scoreInfo = &pb.BigHuntScoreInfo{
|
||||
UserScore: userScore,
|
||||
@@ -177,12 +181,12 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
||||
}
|
||||
|
||||
if isHighScore {
|
||||
rewardGroupId := s.catalog.ResolveActiveScoreRewardGroupId(
|
||||
rewardGroupId := catalog.ResolveActiveScoreRewardGroupId(
|
||||
bossQuest.BigHuntScoreRewardGroupScheduleId, nowMillis)
|
||||
if rewardGroupId > 0 {
|
||||
newItems := s.catalog.CollectNewRewards(rewardGroupId, oldMax, userScore)
|
||||
newItems := catalog.CollectNewRewards(rewardGroupId, oldMax, userScore)
|
||||
for _, item := range newItems {
|
||||
s.engine.Granter.GrantFull(user, model.PossessionType(item.PossessionType), item.PossessionId, item.Count, nowMillis)
|
||||
engine.Granter.GrantFull(user, model.PossessionType(item.PossessionType), item.PossessionId, item.Count, nowMillis)
|
||||
scoreRewards = append(scoreRewards, &pb.BigHuntReward{
|
||||
PossessionType: item.PossessionType,
|
||||
PossessionId: item.PossessionId,
|
||||
@@ -216,16 +220,19 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
||||
func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.RestartBigHuntQuestRequest) (*pb.RestartBigHuntQuestResponse, error) {
|
||||
log.Printf("[BigHuntService] RestartBigHuntQuest: bossQuestId=%d questId=%d", req.BigHuntBossQuestId, req.BigHuntQuestId)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.BigHunt
|
||||
engine := cat.QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
||||
bhQuest := catalog.QuestById[req.BigHuntQuestId]
|
||||
|
||||
var battleBinary []byte
|
||||
var deckNumber int32
|
||||
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, user.BigHuntDeckNumber, nowMillis)
|
||||
engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, user.BigHuntDeckNumber, nowMillis)
|
||||
|
||||
user.BigHuntProgress.CurrentQuestSceneId = 0
|
||||
user.BigHuntProgress.LatestVersion = nowMillis
|
||||
@@ -302,6 +309,7 @@ func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *p
|
||||
func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb.Empty) (*pb.GetBigHuntTopDataResponse, error) {
|
||||
log.Printf("[BigHuntService] GetBigHuntTopData")
|
||||
|
||||
catalog := s.holder.Get().BigHunt
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
user, _ := s.users.LoadUser(userId)
|
||||
|
||||
@@ -309,13 +317,13 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
||||
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
||||
|
||||
var weeklyScoreResults []*pb.WeeklyScoreResult
|
||||
for _, boss := range s.catalog.BossByBossId {
|
||||
for _, boss := range catalog.BossByBossId {
|
||||
key := store.BigHuntWeeklyScoreKey{
|
||||
BigHuntWeeklyVersion: weeklyVersion,
|
||||
AttributeType: boss.AttributeType,
|
||||
}
|
||||
ws := user.BigHuntWeeklyMaxScores[key]
|
||||
gradeIconId := s.catalog.ResolveGradeIconId(boss.BigHuntBossId, ws.MaxScore)
|
||||
gradeIconId := catalog.ResolveGradeIconId(boss.BigHuntBossId, ws.MaxScore)
|
||||
|
||||
weeklyScoreResults = append(weeklyScoreResults, &pb.WeeklyScoreResult{
|
||||
AttributeType: boss.AttributeType,
|
||||
@@ -330,10 +338,10 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
||||
|
||||
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
||||
|
||||
weeklyRewards := s.resolveWeeklyRewards(user, weeklyVersion, nowMillis)
|
||||
weeklyRewards := resolveBigHuntWeeklyRewards(catalog, user, weeklyVersion, nowMillis)
|
||||
|
||||
lastWeekVersion := weeklyVersion - 7*24*60*60*1000
|
||||
lastWeekRewards := s.resolveWeeklyRewards(user, lastWeekVersion, nowMillis)
|
||||
lastWeekRewards := resolveBigHuntWeeklyRewards(catalog, user, lastWeekVersion, nowMillis)
|
||||
|
||||
return &pb.GetBigHuntTopDataResponse{
|
||||
WeeklyScoreResult: weeklyScoreResults,
|
||||
@@ -343,14 +351,14 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *BigHuntServiceServer) resolveWeeklyRewards(user store.UserState, weeklyVersion, nowMillis int64) []*pb.BigHuntReward {
|
||||
func resolveBigHuntWeeklyRewards(catalog *masterdata.BigHuntCatalog, user store.UserState, weeklyVersion, nowMillis int64) []*pb.BigHuntReward {
|
||||
var rewards []*pb.BigHuntReward
|
||||
for _, boss := range s.catalog.BossByBossId {
|
||||
for _, boss := range catalog.BossByBossId {
|
||||
rewardKey := masterdata.BigHuntWeeklyRewardKey{
|
||||
ScheduleId: 1,
|
||||
AttributeType: boss.AttributeType,
|
||||
}
|
||||
rewardGroupId := s.catalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
||||
rewardGroupId := catalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
||||
if rewardGroupId == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -359,7 +367,7 @@ func (s *BigHuntServiceServer) resolveWeeklyRewards(user store.UserState, weekly
|
||||
AttributeType: boss.AttributeType,
|
||||
}
|
||||
maxScore := user.BigHuntWeeklyMaxScores[weekKey].MaxScore
|
||||
for _, item := range s.catalog.CollectNewRewards(rewardGroupId, 0, maxScore) {
|
||||
for _, item := range catalog.CollectNewRewards(rewardGroupId, 0, maxScore) {
|
||||
rewards = append(rewards, &pb.BigHuntReward{
|
||||
PossessionType: item.PossessionType,
|
||||
PossessionId: item.PossessionId,
|
||||
|
||||
@@ -15,13 +15,14 @@ import (
|
||||
func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) {
|
||||
log.Printf("[QuestService] StartEventQuest: chapterId=%d questId=%d isBattleOnly=%v", req.EventQuestChapterId, req.QuestId, req.IsBattleOnly)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleEventQuestStart(user, req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||
engine.HandleEventQuestStart(user, req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||
})
|
||||
|
||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
||||
drops := engine.BattleDropRewards(req.QuestId)
|
||||
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||
for i, d := range drops {
|
||||
pbDrops[i] = &pb.BattleDropReward{
|
||||
@@ -40,10 +41,11 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
|
||||
log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v", req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated)
|
||||
|
||||
nowMillis := gametime.NowMillis()
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
var outcome questflow.FinishOutcome
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
outcome = s.engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||
outcome = engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||
})
|
||||
|
||||
return &pb.FinishEventQuestResponse{
|
||||
@@ -61,9 +63,10 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
|
||||
func (s *QuestServiceServer) RestartEventQuest(ctx context.Context, req *pb.RestartEventQuestRequest) (*pb.RestartEventQuestResponse, error) {
|
||||
log.Printf("[QuestService] RestartEventQuest: chapterId=%d questId=%d", req.EventQuestChapterId, req.QuestId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleEventQuestRestart(user, req.EventQuestChapterId, req.QuestId, gametime.NowMillis())
|
||||
engine.HandleEventQuestRestart(user, req.EventQuestChapterId, req.QuestId, gametime.NowMillis())
|
||||
})
|
||||
|
||||
return &pb.RestartEventQuestResponse{
|
||||
@@ -74,9 +77,10 @@ func (s *QuestServiceServer) RestartEventQuest(ctx context.Context, req *pb.Rest
|
||||
func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context, req *pb.UpdateEventQuestSceneProgressRequest) (*pb.UpdateEventQuestSceneProgressResponse, error) {
|
||||
log.Printf("[QuestService] UpdateEventQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
})
|
||||
|
||||
return &pb.UpdateEventQuestSceneProgressResponse{}, nil
|
||||
|
||||
@@ -13,13 +13,14 @@ import (
|
||||
func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) {
|
||||
log.Printf("[QuestService] StartExtraQuest: questId=%d deckNumber=%d", req.QuestId, req.UserDeckNumber)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleExtraQuestStart(user, req.QuestId, req.UserDeckNumber, nowMillis)
|
||||
engine.HandleExtraQuestStart(user, req.QuestId, req.UserDeckNumber, nowMillis)
|
||||
})
|
||||
|
||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
||||
drops := engine.BattleDropRewards(req.QuestId)
|
||||
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||
for i, d := range drops {
|
||||
pbDrops[i] = &pb.BattleDropReward{
|
||||
@@ -38,10 +39,11 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
|
||||
log.Printf("[QuestService] FinishExtraQuest: questId=%d isRetired=%v isAnnihilated=%v", req.QuestId, req.IsRetired, req.IsAnnihilated)
|
||||
|
||||
nowMillis := gametime.NowMillis()
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
var outcome questflow.FinishOutcome
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
outcome = s.engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||
outcome = engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||
})
|
||||
|
||||
return &pb.FinishExtraQuestResponse{
|
||||
@@ -58,14 +60,15 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
|
||||
func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.RestartExtraQuestRequest) (*pb.RestartExtraQuestResponse, error) {
|
||||
log.Printf("[QuestService] RestartExtraQuest: questId=%d", req.QuestId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
var deckNumber int32
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleExtraQuestRestart(user, req.QuestId, gametime.NowMillis())
|
||||
engine.HandleExtraQuestRestart(user, req.QuestId, gametime.NowMillis())
|
||||
deckNumber = user.Quests[req.QuestId].UserDeckNumber
|
||||
})
|
||||
|
||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
||||
drops := engine.BattleDropRewards(req.QuestId)
|
||||
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||
for i, d := range drops {
|
||||
pbDrops[i] = &pb.BattleDropReward{
|
||||
@@ -84,9 +87,10 @@ func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.Rest
|
||||
func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context, req *pb.UpdateExtraQuestSceneProgressRequest) (*pb.UpdateExtraQuestSceneProgressResponse, error) {
|
||||
log.Printf("[QuestService] UpdateExtraQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
})
|
||||
|
||||
return &pb.UpdateExtraQuestSceneProgressResponse{}, nil
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/questflow"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -17,22 +18,23 @@ type QuestServiceServer struct {
|
||||
pb.UnimplementedQuestServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
engine *questflow.QuestHandler
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, engine *questflow.QuestHandler) *QuestServiceServer {
|
||||
if engine == nil {
|
||||
panic("quest handler is required")
|
||||
func NewQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *QuestServiceServer {
|
||||
if holder == nil {
|
||||
panic("runtime holder is required")
|
||||
}
|
||||
return &QuestServiceServer{users: users, sessions: sessions, engine: engine}
|
||||
return &QuestServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, req *pb.UpdateMainFlowSceneProgressRequest) (*pb.UpdateMainFlowSceneProgressResponse, error) {
|
||||
log.Printf("[QuestService] UpdateMainFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
})
|
||||
|
||||
return &pb.UpdateMainFlowSceneProgressResponse{}, nil
|
||||
@@ -41,9 +43,10 @@ func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, re
|
||||
func (s *QuestServiceServer) UpdateReplayFlowSceneProgress(ctx context.Context, req *pb.UpdateReplayFlowSceneProgressRequest) (*pb.UpdateReplayFlowSceneProgressResponse, error) {
|
||||
log.Printf("[QuestService] UpdateReplayFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleReplayFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
engine.HandleReplayFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||
})
|
||||
|
||||
return &pb.UpdateReplayFlowSceneProgressResponse{}, nil
|
||||
@@ -52,9 +55,10 @@ func (s *QuestServiceServer) UpdateReplayFlowSceneProgress(ctx context.Context,
|
||||
func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, req *pb.UpdateMainQuestSceneProgressRequest) (*pb.UpdateMainQuestSceneProgressResponse, error) {
|
||||
log.Printf("[QuestService] UpdateMainQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleMainQuestSceneProgress(user, req.QuestSceneId)
|
||||
engine.HandleMainQuestSceneProgress(user, req.QuestSceneId)
|
||||
})
|
||||
|
||||
return &pb.UpdateMainQuestSceneProgressResponse{}, nil
|
||||
@@ -63,17 +67,18 @@ func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, r
|
||||
func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) {
|
||||
log.Printf("[QuestService] StartMainQuest: %+v", req)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
if req.IsReplayFlow {
|
||||
s.engine.HandleQuestStartReplay(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||
engine.HandleQuestStartReplay(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||
} else {
|
||||
s.engine.HandleQuestStart(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||
engine.HandleQuestStart(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||
}
|
||||
})
|
||||
|
||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
||||
drops := engine.BattleDropRewards(req.QuestId)
|
||||
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||
for i, d := range drops {
|
||||
pbDrops[i] = &pb.BattleDropReward{
|
||||
@@ -108,10 +113,11 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
|
||||
req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.StorySkipType)
|
||||
|
||||
nowMillis := gametime.NowMillis()
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
var outcome questflow.FinishOutcome
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
outcome = s.engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||
outcome = engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||
})
|
||||
|
||||
return &pb.FinishMainQuestResponse{
|
||||
@@ -130,14 +136,15 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
|
||||
func (s *QuestServiceServer) RestartMainQuest(ctx context.Context, req *pb.RestartMainQuestRequest) (*pb.RestartMainQuestResponse, error) {
|
||||
log.Printf("[QuestService] RestartMainQuest: questId=%d isMainFlow=%v", req.QuestId, req.IsMainFlow)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
var deckNumber int32
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
s.engine.HandleQuestRestart(user, req.QuestId, gametime.NowMillis())
|
||||
engine.HandleQuestRestart(user, req.QuestId, gametime.NowMillis())
|
||||
deckNumber = user.Quests[req.QuestId].UserDeckNumber
|
||||
})
|
||||
|
||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
||||
drops := engine.BattleDropRewards(req.QuestId)
|
||||
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||
for i, d := range drops {
|
||||
pbDrops[i] = &pb.BattleDropReward{
|
||||
@@ -162,6 +169,7 @@ func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestReq
|
||||
log.Printf("[QuestService] SkipQuest: questId=%d skipCount=%d useEffectItems=%d", req.QuestId, req.SkipCount, len(req.UseEffectItem))
|
||||
|
||||
nowMillis := gametime.NowMillis()
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
var outcome questflow.FinishOutcome
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
@@ -172,7 +180,7 @@ func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestReq
|
||||
user.ConsumableItems[item.ConsumableItemId] = 0
|
||||
}
|
||||
}
|
||||
outcome = s.engine.HandleQuestSkip(user, req.QuestId, req.SkipCount, nowMillis)
|
||||
outcome = engine.HandleQuestSkip(user, req.QuestId, req.SkipCount, nowMillis)
|
||||
})
|
||||
|
||||
return &pb.SkipQuestResponse{
|
||||
@@ -184,10 +192,11 @@ func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestReq
|
||||
func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteRequest) (*pb.SetRouteResponse, error) {
|
||||
log.Printf("[QuestService] SetRoute: mainQuestRouteId=%d", req.MainQuestRouteId)
|
||||
|
||||
engine := s.holder.Get().QuestHandler
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
user.MainQuest.CurrentMainQuestRouteId = req.MainQuestRouteId
|
||||
if seasonId, ok := s.engine.SeasonIdByRouteId[req.MainQuestRouteId]; ok {
|
||||
if seasonId, ok := engine.SeasonIdByRouteId[req.MainQuestRouteId]; ok {
|
||||
user.MainQuest.MainQuestSeasonId = seasonId
|
||||
}
|
||||
now := gametime.NowMillis()
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
pb "lunar-tear/server/gen/proto"
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -15,11 +15,11 @@ type SideStoryQuestServiceServer struct {
|
||||
pb.UnimplementedSideStoryQuestServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.SideStoryCatalog
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.SideStoryCatalog) *SideStoryQuestServiceServer {
|
||||
return &SideStoryQuestServiceServer{users: users, sessions: sessions, catalog: catalog}
|
||||
func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *SideStoryQuestServiceServer {
|
||||
return &SideStoryQuestServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) {
|
||||
@@ -27,7 +27,7 @@ func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Con
|
||||
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
firstSceneId := s.catalog.FirstSceneByQuestId[req.SideStoryQuestId]
|
||||
firstSceneId := s.holder.Get().SideStory.FirstSceneByQuestId[req.SideStoryQuestId]
|
||||
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -15,24 +16,25 @@ import (
|
||||
|
||||
type RewardServiceServer struct {
|
||||
pb.UnimplementedRewardServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
bhCatalog *masterdata.BigHuntCatalog
|
||||
granter *store.PossessionGranter
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewRewardServiceServer(
|
||||
users store.UserRepository,
|
||||
sessions store.SessionRepository,
|
||||
bhCatalog *masterdata.BigHuntCatalog,
|
||||
granter *store.PossessionGranter,
|
||||
holder *runtime.Holder,
|
||||
) *RewardServiceServer {
|
||||
return &RewardServiceServer{users: users, sessions: sessions, bhCatalog: bhCatalog, granter: granter}
|
||||
return &RewardServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *emptypb.Empty) (*pb.ReceiveBigHuntRewardResponse, error) {
|
||||
log.Printf("[RewardService] ReceiveBigHuntReward")
|
||||
|
||||
cat := s.holder.Get()
|
||||
bhCatalog := cat.BigHunt
|
||||
granter := cat.QuestHandler.Granter
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
||||
@@ -45,13 +47,13 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
||||
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
||||
isReceived = ws.IsReceivedWeeklyReward
|
||||
|
||||
for _, boss := range s.bhCatalog.BossByBossId {
|
||||
for _, boss := range bhCatalog.BossByBossId {
|
||||
key := store.BigHuntWeeklyScoreKey{
|
||||
BigHuntWeeklyVersion: weeklyVersion,
|
||||
AttributeType: boss.AttributeType,
|
||||
}
|
||||
wms := user.BigHuntWeeklyMaxScores[key]
|
||||
gradeIcon := s.bhCatalog.ResolveGradeIconId(boss.BigHuntBossId, wms.MaxScore)
|
||||
gradeIcon := bhCatalog.ResolveGradeIconId(boss.BigHuntBossId, wms.MaxScore)
|
||||
weeklyScoreResults = append(weeklyScoreResults, &pb.WeeklyScoreResult{
|
||||
AttributeType: boss.AttributeType,
|
||||
BeforeMaxScore: wms.MaxScore,
|
||||
@@ -64,12 +66,12 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
||||
}
|
||||
|
||||
if !isReceived {
|
||||
for _, boss := range s.bhCatalog.BossByBossId {
|
||||
for _, boss := range bhCatalog.BossByBossId {
|
||||
rewardKey := masterdata.BigHuntWeeklyRewardKey{
|
||||
ScheduleId: 1,
|
||||
AttributeType: boss.AttributeType,
|
||||
}
|
||||
rewardGroupId := s.bhCatalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
||||
rewardGroupId := bhCatalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
||||
if rewardGroupId == 0 {
|
||||
continue
|
||||
}
|
||||
@@ -80,9 +82,9 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
||||
}
|
||||
maxScore := user.BigHuntWeeklyMaxScores[weekKey].MaxScore
|
||||
|
||||
items := s.bhCatalog.CollectNewRewards(rewardGroupId, 0, maxScore)
|
||||
items := bhCatalog.CollectNewRewards(rewardGroupId, 0, maxScore)
|
||||
for _, item := range items {
|
||||
s.granter.GrantFull(user, model.PossessionType(item.PossessionType), item.PossessionId, item.Count, nowMillis)
|
||||
granter.GrantFull(user, model.PossessionType(item.PossessionType), item.PossessionId, item.Count, nowMillis)
|
||||
weeklyRewards = append(weeklyRewards, &pb.BigHuntReward{
|
||||
PossessionType: item.PossessionType,
|
||||
PossessionId: item.PossessionId,
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -18,23 +19,25 @@ type ShopServiceServer struct {
|
||||
pb.UnimplementedShopServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.ShopCatalog
|
||||
granter *store.PossessionGranter
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewShopServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.ShopCatalog, granter *store.PossessionGranter) *ShopServiceServer {
|
||||
return &ShopServiceServer{users: users, sessions: sessions, catalog: catalog, granter: granter}
|
||||
func NewShopServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *ShopServiceServer {
|
||||
return &ShopServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.BuyResponse, error) {
|
||||
log.Printf("[ShopService] Buy: shopId=%d items=%v", req.ShopId, req.ShopItems)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Shop
|
||||
granter := cat.QuestHandler.Granter
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
for shopItemId, qty := range req.ShopItems {
|
||||
item, ok := s.catalog.Items[shopItemId]
|
||||
item, ok := catalog.Items[shopItemId]
|
||||
if !ok {
|
||||
log.Printf("[ShopService] Buy: unknown shopItemId=%d, skipping", shopItemId)
|
||||
continue
|
||||
@@ -46,8 +49,8 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
|
||||
continue
|
||||
}
|
||||
|
||||
for _, content := range s.catalog.Contents[shopItemId] {
|
||||
s.granter.GrantFull(user,
|
||||
for _, content := range catalog.Contents[shopItemId] {
|
||||
granter.GrantFull(user,
|
||||
model.PossessionType(content.PossessionType),
|
||||
content.PossessionId,
|
||||
content.Count*qty,
|
||||
@@ -55,7 +58,7 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
|
||||
)
|
||||
}
|
||||
|
||||
s.applyContentEffects(user, shopItemId, qty, nowMillis)
|
||||
applyShopContentEffects(catalog, user, shopItemId, qty, nowMillis)
|
||||
|
||||
si := user.ShopItems[shopItemId]
|
||||
si.ShopItemId = shopItemId
|
||||
@@ -76,12 +79,13 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
|
||||
func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.RefreshRequest) (*pb.RefreshResponse, error) {
|
||||
log.Printf("[ShopService] RefreshUserData: isGemUsed=%v", req.IsGemUsed)
|
||||
|
||||
catalog := s.holder.Get().Shop
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
if len(user.ShopReplaceableLineup) == 0 && len(s.catalog.ItemShopPool) > 0 {
|
||||
for i, itemId := range s.catalog.ItemShopPool {
|
||||
if len(user.ShopReplaceableLineup) == 0 && len(catalog.ItemShopPool) > 0 {
|
||||
for i, itemId := range catalog.ItemShopPool {
|
||||
slot := int32(i + 1)
|
||||
user.ShopReplaceableLineup[slot] = store.UserShopReplaceableLineupState{
|
||||
SlotNumber: slot,
|
||||
@@ -93,7 +97,7 @@ func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.Refresh
|
||||
if req.IsGemUsed {
|
||||
user.ShopReplaceable.LineupUpdateCount++
|
||||
user.ShopReplaceable.LatestLineupUpdateDatetime = nowMillis
|
||||
for _, itemId := range s.catalog.ItemShopPool {
|
||||
for _, itemId := range catalog.ItemShopPool {
|
||||
if si, ok := user.ShopItems[itemId]; ok {
|
||||
si.BoughtCount = 0
|
||||
si.LatestVersion = nowMillis
|
||||
@@ -120,11 +124,14 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
|
||||
log.Printf("[ShopService] CreatePurchaseTransaction: shopId=%d shopItemId=%d productId=%s",
|
||||
req.ShopId, req.ShopItemId, req.ProductId)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Shop
|
||||
granter := cat.QuestHandler.Granter
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
item, ok := s.catalog.Items[req.ShopItemId]
|
||||
item, ok := catalog.Items[req.ShopItemId]
|
||||
if !ok {
|
||||
log.Printf("[ShopService] CreatePurchaseTransaction: unknown shopItemId=%d", req.ShopItemId)
|
||||
return
|
||||
@@ -134,8 +141,8 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
|
||||
log.Printf("[ShopService] CreatePurchaseTransaction: deduct failed: %v", err)
|
||||
}
|
||||
|
||||
for _, content := range s.catalog.Contents[req.ShopItemId] {
|
||||
s.granter.GrantFull(user,
|
||||
for _, content := range catalog.Contents[req.ShopItemId] {
|
||||
granter.GrantFull(user,
|
||||
model.PossessionType(content.PossessionType),
|
||||
content.PossessionId,
|
||||
content.Count,
|
||||
@@ -143,13 +150,13 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
|
||||
)
|
||||
}
|
||||
|
||||
s.applyContentEffects(user, req.ShopItemId, 1, nowMillis)
|
||||
applyShopContentEffects(catalog, user, req.ShopItemId, 1, nowMillis)
|
||||
|
||||
si := user.ShopItems[req.ShopItemId]
|
||||
si.ShopItemId = req.ShopItemId
|
||||
si.BoughtCount++
|
||||
if item.ShopItemLimitedStockId > 0 {
|
||||
if maxCount, ok := s.catalog.LimitedStock[item.ShopItemLimitedStockId]; ok && si.BoughtCount >= maxCount {
|
||||
if maxCount, ok := catalog.LimitedStock[item.ShopItemLimitedStockId]; ok && si.BoughtCount >= maxCount {
|
||||
si.BoughtCount = 0
|
||||
}
|
||||
}
|
||||
@@ -182,12 +189,12 @@ func (s *ShopServiceServer) PurchaseGooglePlayStoreProduct(ctx context.Context,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ShopServiceServer) applyContentEffects(user *store.UserState, shopItemId, qty int32, nowMillis int64) {
|
||||
for _, effect := range s.catalog.Effects[shopItemId] {
|
||||
func applyShopContentEffects(catalog *masterdata.ShopCatalog, user *store.UserState, shopItemId, qty int32, nowMillis int64) {
|
||||
for _, effect := range catalog.Effects[shopItemId] {
|
||||
switch effect.EffectTargetType {
|
||||
case model.EffectTargetStaminaRecovery:
|
||||
maxMillis := s.catalog.MaxStaminaMillis[user.Status.Level]
|
||||
millis := s.resolveEffectMillis(effect.EffectValueType, effect.EffectValue, user.Status.Level)
|
||||
maxMillis := catalog.MaxStaminaMillis[user.Status.Level]
|
||||
millis := resolveShopEffectMillis(catalog, effect.EffectValueType, effect.EffectValue, user.Status.Level)
|
||||
store.RecoverStamina(user, millis*qty, maxMillis, nowMillis)
|
||||
default:
|
||||
log.Printf("[ShopService] unhandled effect: shopItemId=%d targetType=%d", shopItemId, effect.EffectTargetType)
|
||||
@@ -195,12 +202,12 @@ func (s *ShopServiceServer) applyContentEffects(user *store.UserState, shopItemI
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ShopServiceServer) resolveEffectMillis(effectValueType, effectValue, userLevel int32) int32 {
|
||||
func resolveShopEffectMillis(catalog *masterdata.ShopCatalog, effectValueType, effectValue, userLevel int32) int32 {
|
||||
switch effectValueType {
|
||||
case model.EffectValueFixed:
|
||||
return effectValue
|
||||
case model.EffectValuePermil:
|
||||
maxMillis := s.catalog.MaxStaminaMillis[userLevel]
|
||||
maxMillis := catalog.MaxStaminaMillis[userLevel]
|
||||
return effectValue * maxMillis / 1000
|
||||
default:
|
||||
return 0
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"lunar-tear/server/internal/gametime"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/questflow"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -15,17 +16,18 @@ type TutorialServiceServer struct {
|
||||
pb.UnimplementedTutorialServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
engine *questflow.QuestHandler
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewTutorialServiceServer(users store.UserRepository, sessions store.SessionRepository, engine *questflow.QuestHandler) *TutorialServiceServer {
|
||||
return &TutorialServiceServer{users: users, sessions: sessions, engine: engine}
|
||||
func NewTutorialServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *TutorialServiceServer {
|
||||
return &TutorialServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb.SetTutorialProgressRequest) (*pb.SetTutorialProgressResponse, error) {
|
||||
log.Printf("[TutorialService] SetTutorialProgress: type=%d phase=%d choice=%d", req.TutorialType, req.ProgressPhase, req.ChoiceId)
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
engine := s.holder.Get().QuestHandler
|
||||
var grants []questflow.RewardGrant
|
||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
existing, exists := user.Tutorials[req.TutorialType]
|
||||
@@ -36,7 +38,7 @@ func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb
|
||||
ChoiceId: req.ChoiceId,
|
||||
}
|
||||
}
|
||||
grants = s.engine.ApplyTutorialReward(user, model.TutorialType(req.TutorialType), req.ChoiceId, nowMillis)
|
||||
grants = engine.ApplyTutorialReward(user, model.TutorialType(req.TutorialType), req.ChoiceId, nowMillis)
|
||||
if req.TutorialType == int32(model.TutorialTypeMenuFirst) && req.ProgressPhase == 20 {
|
||||
store.EnsureDefaultDeck(user, nowMillis)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"lunar-tear/server/internal/gameutil"
|
||||
"lunar-tear/server/internal/masterdata"
|
||||
"lunar-tear/server/internal/model"
|
||||
"lunar-tear/server/internal/runtime"
|
||||
"lunar-tear/server/internal/store"
|
||||
)
|
||||
|
||||
@@ -17,12 +18,11 @@ type WeaponServiceServer struct {
|
||||
pb.UnimplementedWeaponServiceServer
|
||||
users store.UserRepository
|
||||
sessions store.SessionRepository
|
||||
catalog *masterdata.WeaponCatalog
|
||||
config *masterdata.GameConfig
|
||||
holder *runtime.Holder
|
||||
}
|
||||
|
||||
func NewWeaponServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.WeaponCatalog, config *masterdata.GameConfig) *WeaponServiceServer {
|
||||
return &WeaponServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
||||
func NewWeaponServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *WeaponServiceServer {
|
||||
return &WeaponServiceServer{users: users, sessions: sessions, holder: holder}
|
||||
}
|
||||
|
||||
func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectRequest) (*pb.ProtectResponse, error) {
|
||||
@@ -72,6 +72,9 @@ func (s *WeaponServiceServer) Unprotect(ctx context.Context, req *pb.UnprotectRe
|
||||
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)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -82,7 +85,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
||||
return
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceByMaterial: weapon master id=%d not found", weapon.WeaponId)
|
||||
return
|
||||
@@ -91,7 +94,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
||||
totalExp := int32(0)
|
||||
totalMaterialCount := int32(0)
|
||||
for materialId, count := range req.Materials {
|
||||
mat, ok := s.catalog.Materials[materialId]
|
||||
mat, ok := catalog.Materials[materialId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceByMaterial: material id=%d not found, skipping", materialId)
|
||||
continue
|
||||
@@ -107,19 +110,19 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
||||
|
||||
expPerUnit := mat.EffectValue
|
||||
if mat.WeaponType != 0 && mat.WeaponType == wm.WeaponType {
|
||||
expPerUnit = expPerUnit * s.config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||
expPerUnit = expPerUnit * config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||
}
|
||||
totalExp += expPerUnit * count
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.GoldCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && totalMaterialCount > 0 {
|
||||
if costFunc, ok := catalog.GoldCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && totalMaterialCount > 0 {
|
||||
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
log.Printf("[WeaponService] EnhanceByMaterial: gold cost=%d (materials=%d)", goldCost, totalMaterialCount)
|
||||
}
|
||||
|
||||
weapon.Exp += totalExp
|
||||
if thresholds, ok := s.catalog.ExpByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if thresholds, ok := catalog.ExpByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds)
|
||||
}
|
||||
|
||||
@@ -127,7 +130,7 @@ 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)
|
||||
|
||||
s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
|
||||
checkWeaponStoryUnlocks(catalog, user, weapon.WeaponId, weapon.Level, nowMillis)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weapon enhance by material: %w", err)
|
||||
@@ -142,6 +145,9 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
||||
func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*pb.SellResponse, error) {
|
||||
log.Printf("[WeaponService] Sell: uuids=%v", req.UserWeaponUuid)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
|
||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||
@@ -153,17 +159,17 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
|
||||
continue
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] Sell: weapon master id=%d not found, skipping", weapon.WeaponId)
|
||||
continue
|
||||
}
|
||||
|
||||
if sellFunc, ok := s.catalog.SellPriceByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if sellFunc, ok := catalog.SellPriceByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
totalGold += sellFunc.Evaluate(weapon.Level)
|
||||
}
|
||||
|
||||
if medals, ok := s.catalog.MedalsByWeaponId[weapon.WeaponId]; ok {
|
||||
if medals, ok := catalog.MedalsByWeaponId[weapon.WeaponId]; ok {
|
||||
for itemId, count := range medals {
|
||||
user.ConsumableItems[itemId] += count
|
||||
}
|
||||
@@ -176,7 +182,7 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
|
||||
}
|
||||
|
||||
if totalGold > 0 {
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||
log.Printf("[WeaponService] Sell: granted %d gold", totalGold)
|
||||
}
|
||||
})
|
||||
@@ -190,6 +196,9 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
|
||||
func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest) (*pb.EvolveResponse, error) {
|
||||
log.Printf("[WeaponService] Evolve: uuid=%s", req.UserWeaponUuid)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -200,20 +209,20 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
||||
return
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] Evolve: weapon master id=%d not found", weapon.WeaponId)
|
||||
return
|
||||
}
|
||||
|
||||
evolvedId, ok := s.catalog.EvolutionNextWeaponId[weapon.WeaponId]
|
||||
evolvedId, ok := catalog.EvolutionNextWeaponId[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] Evolve: no evolution for weaponId=%d", weapon.WeaponId)
|
||||
return
|
||||
}
|
||||
|
||||
totalMaterialCount := int32(0)
|
||||
mats := s.catalog.EvolutionMaterials[wm.WeaponEvolutionMaterialGroupId]
|
||||
mats := catalog.EvolutionMaterials[wm.WeaponEvolutionMaterialGroupId]
|
||||
for _, mat := range mats {
|
||||
cur := user.Materials[mat.MaterialId]
|
||||
cost := mat.Count
|
||||
@@ -225,9 +234,9 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
||||
totalMaterialCount += cost
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.EvolutionCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && totalMaterialCount > 0 {
|
||||
if costFunc, ok := catalog.EvolutionCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && totalMaterialCount > 0 {
|
||||
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
log.Printf("[WeaponService] Evolve: gold cost=%d", goldCost)
|
||||
}
|
||||
|
||||
@@ -235,9 +244,9 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
||||
weapon.LatestVersion = nowMillis
|
||||
user.Weapons[req.UserWeaponUuid] = weapon
|
||||
|
||||
evolvedMaster, ok := s.catalog.Weapons[evolvedId]
|
||||
evolvedMaster, ok := catalog.Weapons[evolvedId]
|
||||
if ok {
|
||||
if slots, ok := s.catalog.AbilitySlots[evolvedMaster.WeaponAbilityGroupId]; ok {
|
||||
if slots, ok := catalog.AbilitySlots[evolvedMaster.WeaponAbilityGroupId]; ok {
|
||||
abilities := make([]store.WeaponAbilityState, len(slots))
|
||||
for i, slot := range slots {
|
||||
abilities[i] = store.WeaponAbilityState{
|
||||
@@ -252,7 +261,7 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
||||
|
||||
log.Printf("[WeaponService] Evolve: weaponId %d -> %d", wm.WeaponId, evolvedId)
|
||||
|
||||
s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis)
|
||||
checkWeaponStoryUnlocks(catalog, user, evolvedId, weapon.Level, nowMillis)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weapon evolve: %w", err)
|
||||
@@ -264,6 +273,9 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
||||
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)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -274,13 +286,13 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
||||
return
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceSkill: weapon master id=%d not found", weapon.WeaponId)
|
||||
return
|
||||
}
|
||||
|
||||
groupRows := s.catalog.SkillGroupsByGroupId[wm.WeaponSkillGroupId]
|
||||
groupRows := catalog.SkillGroupsByGroupId[wm.WeaponSkillGroupId]
|
||||
var skillGroup *masterdata.EntityMWeaponSkillGroup
|
||||
for i := range groupRows {
|
||||
if groupRows[i].SkillId == req.SkillId {
|
||||
@@ -306,7 +318,7 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
||||
return
|
||||
}
|
||||
|
||||
maxLevelFunc, ok := s.catalog.SkillMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
||||
maxLevelFunc, ok := catalog.SkillMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceSkill: no max skill level func for enhanceId=%d", wm.WeaponSpecificEnhanceId)
|
||||
return
|
||||
@@ -326,7 +338,7 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
||||
enhanceMatId := skillGroup.WeaponSkillEnhancementMaterialId
|
||||
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
||||
key := [2]int32{enhanceMatId, lvl}
|
||||
mats := s.catalog.SkillEnhanceMats[key]
|
||||
mats := catalog.SkillEnhanceMats[key]
|
||||
for _, mat := range mats {
|
||||
cur := user.Materials[mat.MaterialId]
|
||||
cost := mat.Count
|
||||
@@ -337,9 +349,9 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
||||
user.Materials[mat.MaterialId] = cur - cost
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.SkillCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if costFunc, ok := catalog.SkillCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
goldCost := costFunc.Evaluate(lvl + 1)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,6 +372,9 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
||||
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)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -370,13 +385,13 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
||||
return
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceAbility: weapon master id=%d not found", weapon.WeaponId)
|
||||
return
|
||||
}
|
||||
|
||||
groupRows := s.catalog.AbilityGroupsByGroupId[wm.WeaponAbilityGroupId]
|
||||
groupRows := catalog.AbilityGroupsByGroupId[wm.WeaponAbilityGroupId]
|
||||
var abilityGroup *masterdata.EntityMWeaponAbilityGroup
|
||||
for i := range groupRows {
|
||||
if groupRows[i].AbilityId == req.AbilityId {
|
||||
@@ -402,7 +417,7 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
||||
return
|
||||
}
|
||||
|
||||
maxLevelFunc, ok := s.catalog.AbilityMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
||||
maxLevelFunc, ok := catalog.AbilityMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceAbility: no max ability level func for enhanceId=%d", wm.WeaponSpecificEnhanceId)
|
||||
return
|
||||
@@ -422,7 +437,7 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
||||
enhanceMatId := abilityGroup.WeaponAbilityEnhancementMaterialId
|
||||
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
||||
key := [2]int32{enhanceMatId, lvl}
|
||||
mats := s.catalog.AbilityEnhanceMats[key]
|
||||
mats := catalog.AbilityEnhanceMats[key]
|
||||
for _, mat := range mats {
|
||||
cur := user.Materials[mat.MaterialId]
|
||||
cost := mat.Count
|
||||
@@ -433,9 +448,9 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
||||
user.Materials[mat.MaterialId] = cur - cost
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.AbilityCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if costFunc, ok := catalog.AbilityCostByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
goldCost := costFunc.Evaluate(lvl + 1)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,6 +471,9 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
||||
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)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -466,18 +484,18 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
|
||||
return
|
||||
}
|
||||
|
||||
if weapon.LimitBreakCount >= s.config.WeaponLimitBreakAvailableCount {
|
||||
if weapon.LimitBreakCount >= config.WeaponLimitBreakAvailableCount {
|
||||
log.Printf("[WeaponService] LimitBreakByMaterial: already at max limit break %d", weapon.LimitBreakCount)
|
||||
return
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] LimitBreakByMaterial: weapon master id=%d not found", weapon.WeaponId)
|
||||
return
|
||||
}
|
||||
|
||||
remaining := s.config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
||||
remaining := config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
||||
|
||||
totalMaterialCount := int32(0)
|
||||
for materialId, count := range req.Materials {
|
||||
@@ -496,9 +514,9 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
|
||||
totalMaterialCount += count
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.LimitBreakCostByMaterialByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && totalMaterialCount > 0 {
|
||||
if costFunc, ok := catalog.LimitBreakCostByMaterialByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && totalMaterialCount > 0 {
|
||||
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
log.Printf("[WeaponService] LimitBreakByMaterial: gold cost=%d", goldCost)
|
||||
}
|
||||
|
||||
@@ -525,6 +543,9 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
|
||||
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)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -535,18 +556,18 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
||||
return
|
||||
}
|
||||
|
||||
if weapon.LimitBreakCount >= s.config.WeaponLimitBreakAvailableCount {
|
||||
if weapon.LimitBreakCount >= config.WeaponLimitBreakAvailableCount {
|
||||
log.Printf("[WeaponService] LimitBreakByWeapon: already at max limit break %d", weapon.LimitBreakCount)
|
||||
return
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] LimitBreakByWeapon: weapon master id=%d not found", weapon.WeaponId)
|
||||
return
|
||||
}
|
||||
|
||||
remaining := s.config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
||||
remaining := config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
||||
|
||||
consumedCount := int32(0)
|
||||
for _, uuid := range req.MaterialUserWeaponUuids {
|
||||
@@ -560,7 +581,7 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
||||
continue
|
||||
}
|
||||
|
||||
if medals, ok := s.catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
||||
if medals, ok := catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
||||
for itemId, count := range medals {
|
||||
user.ConsumableItems[itemId] += count
|
||||
}
|
||||
@@ -573,9 +594,9 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
||||
consumedCount++
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.LimitBreakCostByWeaponByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && consumedCount > 0 {
|
||||
if costFunc, ok := catalog.LimitBreakCostByWeaponByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && consumedCount > 0 {
|
||||
goldCost := costFunc.Evaluate(consumedCount)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
log.Printf("[WeaponService] LimitBreakByWeapon: gold cost=%d", goldCost)
|
||||
}
|
||||
|
||||
@@ -602,6 +623,9 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
||||
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)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -612,7 +636,7 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
||||
return
|
||||
}
|
||||
|
||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
||||
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceByWeapon: weapon master id=%d not found", weapon.WeaponId)
|
||||
return
|
||||
@@ -627,19 +651,19 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
||||
continue
|
||||
}
|
||||
|
||||
matMaster, ok := s.catalog.Weapons[matWeapon.WeaponId]
|
||||
matMaster, ok := catalog.Weapons[matWeapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] EnhanceByWeapon: material weapon master id=%d not found, skipping", matWeapon.WeaponId)
|
||||
continue
|
||||
}
|
||||
|
||||
baseExp := s.catalog.BaseExpByEnhanceId[matMaster.WeaponSpecificEnhanceId]
|
||||
baseExp := catalog.BaseExpByEnhanceId[matMaster.WeaponSpecificEnhanceId]
|
||||
if matMaster.WeaponType != 0 && matMaster.WeaponType == wm.WeaponType {
|
||||
baseExp = baseExp * s.config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||
baseExp = baseExp * config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||
}
|
||||
totalExp += baseExp
|
||||
|
||||
if medals, ok := s.catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
||||
if medals, ok := catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
||||
for itemId, count := range medals {
|
||||
user.ConsumableItems[itemId] += count
|
||||
}
|
||||
@@ -652,14 +676,14 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
||||
consumedCount++
|
||||
}
|
||||
|
||||
if costFunc, ok := s.catalog.EnhanceCostByWeaponByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && consumedCount > 0 {
|
||||
if costFunc, ok := catalog.EnhanceCostByWeaponByEnhanceId[wm.WeaponSpecificEnhanceId]; ok && consumedCount > 0 {
|
||||
goldCost := costFunc.Evaluate(consumedCount)
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||
log.Printf("[WeaponService] EnhanceByWeapon: gold cost=%d (weapons=%d)", goldCost, consumedCount)
|
||||
}
|
||||
|
||||
weapon.Exp += totalExp
|
||||
if thresholds, ok := s.catalog.ExpByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if thresholds, ok := catalog.ExpByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
weapon.Level, weapon.Exp = gameutil.LevelAndCap(weapon.Exp, thresholds)
|
||||
}
|
||||
|
||||
@@ -667,7 +691,7 @@ 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)
|
||||
|
||||
s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
|
||||
checkWeaponStoryUnlocks(catalog, user, weapon.WeaponId, weapon.Level, nowMillis)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weapon enhance by weapon: %w", err)
|
||||
@@ -679,13 +703,13 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) {
|
||||
wm, ok := s.catalog.Weapons[weaponId]
|
||||
func checkWeaponStoryUnlocks(catalog *masterdata.WeaponCatalog, user *store.UserState, weaponId, level int32, nowMillis int64) {
|
||||
wm, ok := catalog.Weapons[weaponId]
|
||||
if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 {
|
||||
return
|
||||
}
|
||||
evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId]
|
||||
conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId]
|
||||
evoOrder, hasEvo := catalog.EvolutionOrder[weaponId]
|
||||
conditions := catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId]
|
||||
|
||||
for _, cond := range conditions {
|
||||
switch model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) {
|
||||
@@ -696,14 +720,14 @@ func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, wea
|
||||
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
||||
}
|
||||
case model.WeaponStoryReleaseConditionTypeReachInitialMaxLevel:
|
||||
if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if level >= maxFunc.Evaluate(0) {
|
||||
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
||||
}
|
||||
}
|
||||
case model.WeaponStoryReleaseConditionTypeReachOnceEvolvedMaxLevel:
|
||||
if hasEvo && evoOrder >= 1 {
|
||||
if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if maxFunc, ok := catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||
if level >= maxFunc.Evaluate(0) {
|
||||
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
||||
}
|
||||
@@ -720,6 +744,9 @@ func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, wea
|
||||
func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRequest) (*pb.WeaponAwakenResponse, error) {
|
||||
log.Printf("[WeaponService] Awaken: uuid=%s", req.UserWeaponUuid)
|
||||
|
||||
cat := s.holder.Get()
|
||||
catalog := cat.Weapon
|
||||
config := cat.GameConfig
|
||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||
nowMillis := gametime.NowMillis()
|
||||
|
||||
@@ -730,7 +757,7 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
|
||||
return
|
||||
}
|
||||
|
||||
awakenRow, ok := s.catalog.AwakenByWeaponId[weapon.WeaponId]
|
||||
awakenRow, ok := catalog.AwakenByWeaponId[weapon.WeaponId]
|
||||
if !ok {
|
||||
log.Printf("[WeaponService] Awaken: no awaken data for weaponId=%d", weapon.WeaponId)
|
||||
return
|
||||
@@ -741,7 +768,7 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
|
||||
return
|
||||
}
|
||||
|
||||
mats := s.catalog.AwakenMaterialsByGroupId[awakenRow.WeaponAwakenMaterialGroupId]
|
||||
mats := catalog.AwakenMaterialsByGroupId[awakenRow.WeaponAwakenMaterialGroupId]
|
||||
for _, mat := range mats {
|
||||
cur := user.Materials[mat.MaterialId]
|
||||
cost := mat.Count
|
||||
@@ -753,7 +780,7 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
|
||||
}
|
||||
|
||||
if awakenRow.ConsumeGold > 0 {
|
||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= awakenRow.ConsumeGold
|
||||
user.ConsumableItems[config.ConsumableItemIdForGold] -= awakenRow.ConsumeGold
|
||||
log.Printf("[WeaponService] Awaken: gold cost=%d", awakenRow.ConsumeGold)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user