Add admin API for content reload

This commit is contained in:
Ilya Groshev
2026-04-28 21:22:28 +03:00
parent 9be0df4c30
commit 3fe564cb1d
36 changed files with 992 additions and 638 deletions
+170
View File
@@ -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
}
+104
View File
@@ -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()
}
+10 -5
View File
@@ -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
}
+8 -7
View File
@@ -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{
+13 -10
View File
@@ -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 {
+25 -23
View File
@@ -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
+5 -5
View File
@@ -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{
+11 -8
View File
@@ -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
}
}
+9 -7
View File
@@ -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)
}
})
+55 -37
View File
@@ -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
+19 -2
View File
@@ -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
}
+10 -8
View File
@@ -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()
+43 -20
View File
@@ -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,
+7 -7
View File
@@ -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++
+6 -5
View File
@@ -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,
+9 -7
View File
@@ -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)
}
})
+6 -18
View File
@@ -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)
}
+5 -5
View File
@@ -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
}
+25 -19
View File
@@ -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)
}
+36 -28
View File
@@ -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,
+9 -5
View File
@@ -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
+10 -6
View File
@@ -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
+25 -16
View File
@@ -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()
+5 -5
View File
@@ -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]
+15 -13
View File
@@ -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,
+29 -22
View File
@@ -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
+6 -4
View File
@@ -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)
}
+91 -64
View File
@@ -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)
}