Initial commit

This commit is contained in:
Ilya Groshev
2026-04-14 09:28:26 +03:00
commit 02f511f40c
161 changed files with 21541 additions and 0 deletions
+205
View File
@@ -0,0 +1,205 @@
package main
import (
"context"
"fmt"
"log"
"net"
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gacha"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/service"
"lunar-tear/server/internal/store/memory"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
)
type loggingListener struct {
net.Listener
}
func (l loggingListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err != nil {
log.Printf("[gRPC] Accept error: %v", err)
return nil, err
}
log.Printf("[gRPC] New connection from %v", conn.RemoteAddr())
return conn, nil
}
func startGRPC(
host string,
octoURL string,
userStore *memory.MemoryStore,
questEngine *questflow.QuestHandler,
gachaHandler *gacha.GachaHandler,
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
loginBonusCatalog *masterdata.LoginBonusCatalog,
characterViewerCatalog *masterdata.CharacterViewerCatalog,
shopCatalog *masterdata.ShopCatalog,
costumeCatalog *masterdata.CostumeCatalog,
omikujiCatalog *masterdata.OmikujiCatalog,
weaponCatalog *masterdata.WeaponCatalog,
exploreCatalog *masterdata.ExploreCatalog,
gimmickCatalog *masterdata.GimmickCatalog,
characterBoardCatalog *masterdata.CharacterBoardCatalog,
partsCatalog *masterdata.PartsCatalog,
characterRebirthCatalog *masterdata.CharacterRebirthCatalog,
companionCatalog *masterdata.CompanionCatalog,
materialCatalog *masterdata.MaterialCatalog,
gameConfig *masterdata.GameConfig,
sideStoryCatalog *masterdata.SideStoryCatalog,
bigHuntCatalog *masterdata.BigHuntCatalog,
) {
lis, err := net.Listen("tcp", ":443")
if err != nil {
log.Fatalf("failed to listen on :443: %v", err)
}
lis = loggingListener{Listener: lis}
grpcServer := grpc.NewServer(
grpc.ChainUnaryInterceptor(loggingInterceptor, timeSyncInterceptor),
grpc.UnknownServiceHandler(loggingUnknownService),
)
registerServices(grpcServer,
host,
octoURL,
userStore,
questEngine,
gachaHandler,
cageOrnamentCatalog,
loginBonusCatalog,
characterViewerCatalog,
shopCatalog,
costumeCatalog,
omikujiCatalog,
weaponCatalog,
exploreCatalog,
gimmickCatalog,
characterBoardCatalog,
partsCatalog,
characterRebirthCatalog,
companionCatalog,
materialCatalog,
gameConfig,
sideStoryCatalog,
bigHuntCatalog,
)
reflection.Register(grpcServer)
log.Printf("gRPC server listening on :443")
log.Printf("client host address: %s:443", host)
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
func registerServices(
srv *grpc.Server,
host string,
octoURL string,
userStore *memory.MemoryStore,
questEngine *questflow.QuestHandler,
gachaHandler *gacha.GachaHandler,
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
loginBonusCatalog *masterdata.LoginBonusCatalog,
characterViewerCatalog *masterdata.CharacterViewerCatalog,
shopCatalog *masterdata.ShopCatalog,
costumeCatalog *masterdata.CostumeCatalog,
omikujiCatalog *masterdata.OmikujiCatalog,
weaponCatalog *masterdata.WeaponCatalog,
exploreCatalog *masterdata.ExploreCatalog,
gimmickCatalog *masterdata.GimmickCatalog,
characterBoardCatalog *masterdata.CharacterBoardCatalog,
partsCatalog *masterdata.PartsCatalog,
characterRebirthCatalog *masterdata.CharacterRebirthCatalog,
companionCatalog *masterdata.CompanionCatalog,
materialCatalog *masterdata.MaterialCatalog,
gameConfig *masterdata.GameConfig,
sideStoryCatalog *masterdata.SideStoryCatalog,
bigHuntCatalog *masterdata.BigHuntCatalog,
) {
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(userStore))
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore))
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(host, int32(443), octoURL))
pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, questEngine))
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, userStore, gachaHandler))
pb.RegisterGiftServiceServer(srv, service.NewGiftServiceServer(userStore, userStore))
pb.RegisterGamePlayServiceServer(srv, service.NewGameplayServiceServer())
pb.RegisterGimmickServiceServer(srv, service.NewGimmickServiceServer(userStore, userStore, gimmickCatalog))
pb.RegisterQuestServiceServer(srv, service.NewQuestServiceServer(userStore, userStore, questEngine))
pb.RegisterNotificationServiceServer(srv, service.NewNotificationServiceServer(userStore, userStore))
pb.RegisterCageOrnamentServiceServer(srv, service.NewCageOrnamentServiceServer(userStore, userStore, cageOrnamentCatalog, questEngine.Granter))
pb.RegisterDeckServiceServer(srv, service.NewDeckServiceServer(userStore, userStore))
pb.RegisterFriendServiceServer(srv, service.NewFriendServiceServer(userStore, userStore))
pb.RegisterLoginBonusServiceServer(srv, service.NewLoginBonusServiceServer(userStore, userStore, loginBonusCatalog))
pb.RegisterNaviCutInServiceServer(srv, service.NewNaviCutInServiceServer(userStore, userStore))
pb.RegisterContentsStoryServiceServer(srv, service.NewContentsStoryServiceServer(userStore, userStore))
pb.RegisterDokanServiceServer(srv, service.NewDokanServiceServer(userStore, userStore))
pb.RegisterPortalCageServiceServer(srv, service.NewPortalCageServiceServer(userStore, userStore))
pb.RegisterCharacterViewerServiceServer(srv, service.NewCharacterViewerServiceServer(userStore, userStore, characterViewerCatalog))
pb.RegisterMissionServiceServer(srv, service.NewMissionServiceServer(userStore, userStore))
pb.RegisterShopServiceServer(srv, service.NewShopServiceServer(userStore, userStore, shopCatalog, questEngine.Granter))
pb.RegisterCostumeServiceServer(srv, service.NewCostumeServiceServer(userStore, userStore, costumeCatalog, gameConfig))
pb.RegisterMovieServiceServer(srv, service.NewMovieServiceServer(userStore, userStore))
pb.RegisterOmikujiServiceServer(srv, service.NewOmikujiServiceServer(userStore, userStore, omikujiCatalog))
pb.RegisterWeaponServiceServer(srv, service.NewWeaponServiceServer(userStore, userStore, weaponCatalog, gameConfig))
pb.RegisterExploreServiceServer(srv, service.NewExploreServiceServer(userStore, userStore, exploreCatalog))
pb.RegisterCharacterBoardServiceServer(srv, service.NewCharacterBoardServiceServer(userStore, userStore, characterBoardCatalog))
pb.RegisterPartsServiceServer(srv, service.NewPartsServiceServer(userStore, userStore, partsCatalog, gameConfig))
pb.RegisterCharacterServiceServer(srv, service.NewCharacterServiceServer(userStore, userStore, characterRebirthCatalog, gameConfig))
pb.RegisterCompanionServiceServer(srv, service.NewCompanionServiceServer(userStore, userStore, companionCatalog, gameConfig))
pb.RegisterMaterialServiceServer(srv, service.NewMaterialServiceServer(userStore, userStore, materialCatalog, gameConfig))
pb.RegisterSideStoryQuestServiceServer(srv, service.NewSideStoryQuestServiceServer(userStore, userStore, sideStoryCatalog))
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, bigHuntCatalog, questEngine))
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, bigHuntCatalog, questEngine.Granter))
}
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
log.Printf(">>> %s", info.FullMethod)
resp, err := handler(ctx, req)
if err != nil {
log.Printf("<<< %s ERROR: %v", info.FullMethod, err)
} else {
log.Printf("<<< %s OK", info.FullMethod)
}
return resp, err
}
func timeSyncInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
resp, err := handler(ctx, req)
switch info.FullMethod {
case "/apb.api.user.UserService/Auth",
"/apb.api.user.UserService/RegisterUser",
"/apb.api.user.UserService/TransferUser":
default:
grpc.SetTrailer(ctx, metadata.Pairs(
"x-apb-response-datetime", fmt.Sprintf("%d", gametime.NowMillis()),
))
}
return resp, err
}
func loggingUnknownService(_ any, stream grpc.ServerStream) error {
fullMethod, ok := grpc.MethodFromServerStream(stream)
if !ok {
fullMethod = "<unknown>"
}
log.Printf(">>> %s", fullMethod)
err := status.Errorf(codes.Unimplemented, "unknown service or method %s", fullMethod)
log.Printf("<<< %s ERROR: %v", fullMethod, err)
return err
}
+24
View File
@@ -0,0 +1,24 @@
package main
import (
"fmt"
"log"
"net/http"
"lunar-tear/server/internal/service"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
func startHTTP(port int, resourcesBaseURL string) {
octoServer := service.NewOctoHTTPServer(resourcesBaseURL)
h2s := &http2.Server{}
octoHandler := h2c.NewHandler(octoServer.Handler(), h2s)
log.Printf("Octo HTTP server listening on :%d (HTTP/1.1 + h2c)", port)
srv := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: octoHandler}
http2.ConfigureServer(srv, h2s)
if err := srv.ListenAndServe(); err != nil {
log.Fatalf("HTTP server on %d failed: %v", port, err)
}
}
+191
View File
@@ -0,0 +1,191 @@
package main
import (
"flag"
"log"
"os"
"strconv"
"strings"
"lunar-tear/server/internal/gacha"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store/memory"
)
func main() {
httpPort := flag.Int("http-port", 8080, "HTTP server port (Octo API)")
host := flag.String("host", "127.0.0.1", "hostname the client will connect to")
scene := flag.Int("scene", 0, "Bootstrap to scene N (0 = fresh start)")
starterItems := flag.Bool("starter-items", false, "Grant starter items to new users")
flag.Parse()
octoURL := "http://" + *host + ":" + strconv.Itoa(*httpPort)
prefix := octoURL + "/"
padLen := 43 - len(prefix)
resourcesBaseURL := ""
if padLen < 1 {
log.Printf("[config] host:port too long for 43-char resource URL; list.bin will be served unchanged")
} else {
resourcesBaseURL = prefix + strings.Repeat("r", padLen)
}
go startHTTP(*httpPort, resourcesBaseURL)
snapshotDir := "snapshots"
if err := os.MkdirAll(snapshotDir, 0755); err != nil {
log.Fatalf("create snapshot dir: %v", err)
}
gameConfig, err := masterdata.LoadGameConfig()
if err != nil {
log.Fatalf("load game config: %v", 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 {
log.Fatalf("load parts catalog: %v", err)
}
log.Printf("parts catalog loaded: %d parts, %d rarities", len(partsCatalog.PartsById), len(partsCatalog.RarityByRarityType))
questCatalog, err := masterdata.LoadQuestCatalog(partsCatalog)
if err != nil {
log.Fatalf("load quest catalog: %v", err)
}
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig)
userStore := memory.New(gametime.Now,
memory.WithSnapshotDir(snapshotDir),
memory.WithSceneId(int32(*scene)),
memory.WithStarterItems(*starterItems),
)
if *scene != 0 {
log.Printf("bootstrap scene: %d (from snapshot)", *scene)
}
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
if err != nil {
log.Fatalf("load gacha catalog: %v", err)
}
log.Printf("gacha catalog loaded: %d entries", len(gachaEntries))
gachaPool, err := masterdata.LoadGachaPool()
if err != nil {
log.Fatalf("load gacha pool: %v", 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 {
log.Fatalf("load shop catalog: %v", 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)
userStore.ReplaceCatalog(gachaEntries)
dupExchange, err := masterdata.LoadDupExchange()
if err != nil {
log.Fatalf("load dup exchange: %v", err)
}
dupAdded, err := masterdata.EnrichDupExchange(dupExchange, gachaPool)
if err != nil {
log.Fatalf("enrich dup exchange: %v", 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 {
log.Fatalf("load condition resolver: %v", err)
}
cageOrnamentCatalog := masterdata.LoadCageOrnamentCatalog()
loginBonusCatalog := masterdata.LoadLoginBonusCatalog()
characterViewerCatalog := masterdata.LoadCharacterViewerCatalog(conditionResolver)
omikujiCatalog := masterdata.LoadOmikujiCatalog()
materialCatalog, err := masterdata.LoadMaterialCatalog()
if err != nil {
log.Fatalf("load material catalog: %v", err)
}
log.Printf("material catalog loaded: %d materials", len(materialCatalog.All))
costumeCatalog, err := masterdata.LoadCostumeCatalog(materialCatalog)
if err != nil {
log.Fatalf("load costume catalog: %v", 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 {
log.Fatalf("load weapon catalog: %v", 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 {
log.Fatalf("load explore catalog: %v", 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 {
log.Fatalf("load gimmick catalog: %v", err)
}
characterBoardCatalog, err := masterdata.LoadCharacterBoardCatalog()
if err != nil {
log.Fatalf("load character board catalog: %v", err)
}
log.Printf("character board catalog loaded: %d panels, %d boards", len(characterBoardCatalog.PanelById), len(characterBoardCatalog.BoardById))
characterRebirthCatalog, err := masterdata.LoadCharacterRebirthCatalog()
if err != nil {
log.Fatalf("load character rebirth catalog: %v", err)
}
log.Printf("character rebirth catalog loaded: %d characters", len(characterRebirthCatalog.StepGroupByCharacterId))
companionCatalog, err := masterdata.LoadCompanionCatalog()
if err != nil {
log.Fatalf("load companion catalog: %v", err)
}
log.Printf("companion catalog loaded: %d companions, %d categories", len(companionCatalog.CompanionById), len(companionCatalog.GoldCostByCategory))
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
startGRPC(
*host,
octoURL,
userStore,
questHandler,
gachaHandler,
cageOrnamentCatalog,
loginBonusCatalog,
characterViewerCatalog,
shopCatalog,
costumeCatalog,
omikujiCatalog,
weaponCatalog,
exploreCatalog,
gimmickCatalog,
characterBoardCatalog,
partsCatalog,
characterRebirthCatalog,
companionCatalog,
materialCatalog,
gameConfig,
sideStoryCatalog,
bigHuntCatalog,
)
}