mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Add admin API for content reload
This commit is contained in:
@@ -46,6 +46,7 @@ go run ./cmd/wizard --grpc-port 9003 --cdn-port 9080
|
|||||||
| `--grpc-port` | `8003` | gRPC server port |
|
| `--grpc-port` | `8003` | gRPC server port |
|
||||||
| `--cdn-port` | `8080` | CDN server port |
|
| `--cdn-port` | `8080` | CDN server port |
|
||||||
| `--auth-port` | `3000` | Auth server port |
|
| `--auth-port` | `3000` | Auth server port |
|
||||||
|
| `--admin-port` | `0` | Admin webhook port (`0` = disabled). Bound on `127.0.0.1`; only takes effect when `LUNAR_ADMIN_TOKEN` is set in the env. |
|
||||||
|
|
||||||
Custom ports are saved to `.wizard.json` alongside your other settings. On the next run the saved ports are reused automatically — no need to pass the flags again. If you later pass different port flags, the wizard warns you that the ports changed and asks for confirmation before continuing.
|
Custom ports are saved to `.wizard.json` alongside your other settings. On the next run the saved ports are reused automatically — no need to pass the flags again. If you later pass different port flags, the wizard warns you that the ports changed and asks for confirmation before continuing.
|
||||||
|
|
||||||
@@ -172,6 +173,7 @@ make dev ARGS="--grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000"
|
|||||||
| `--grpc.public-addr` | `10.0.2.2:8003` | lunar-tear externally-reachable addr |
|
| `--grpc.public-addr` | `10.0.2.2:8003` | lunar-tear externally-reachable addr |
|
||||||
| `--grpc.octo-url` | `http://10.0.2.2:8080` | Octo CDN base URL passed to lunar-tear |
|
| `--grpc.octo-url` | `http://10.0.2.2:8080` | Octo CDN base URL passed to lunar-tear |
|
||||||
| `--grpc.auth-url` | `http://localhost:3000` | auth server base URL passed to lunar-tear |
|
| `--grpc.auth-url` | `http://localhost:3000` | auth server base URL passed to lunar-tear |
|
||||||
|
| `--admin.listen` | *(empty)* | lunar-tear admin webhook bind. Empty = leave default; webhook only binds when `LUNAR_ADMIN_TOKEN` is set in the env. |
|
||||||
| `--no-color` | `false` | disable colored output |
|
| `--no-color` | `false` | disable colored output |
|
||||||
|
|
||||||
### Ports
|
### Ports
|
||||||
@@ -180,16 +182,39 @@ make dev ARGS="--grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000"
|
|||||||
| -------- | ---- | ------------- | ----------------------------------------------------------- |
|
| -------- | ---- | ------------- | ----------------------------------------------------------- |
|
||||||
| gRPC | 443 | `lunar-tear` | default; configurable with `--listen` (requires patched client) |
|
| gRPC | 443 | `lunar-tear` | default; configurable with `--listen` (requires patched client) |
|
||||||
| HTTP | 8080 | `octo-cdn` | Octo asset API + game web pages |
|
| HTTP | 8080 | `octo-cdn` | Octo asset API + game web pages |
|
||||||
|
| HTTP | 8082 | `lunar-tear` | admin webhook (`/api/admin/master-data/reload`); loopback by default, only binds when `LUNAR_ADMIN_TOKEN` is set |
|
||||||
|
| HTTP | 3000 | `auth-server` | account registration and login |
|
||||||
|
|
||||||
### Game Server Flags (`lunar-tear`)
|
### Game Server Flags (`lunar-tear`)
|
||||||
|
|
||||||
| Flag | Default | Description |
|
| Flag | Default | Description |
|
||||||
| --------------- | ----------------- | ---------------------------------------------------- |
|
| ---------------- | ----------------- | ---------------------------------------------------- |
|
||||||
| `--listen` | `0.0.0.0:443` | gRPC listen address (host:port) |
|
| `--listen` | `0.0.0.0:443` | gRPC listen address (host:port) |
|
||||||
| `--public-addr` | `127.0.0.1:443` | externally-reachable host:port advertised to clients |
|
| `--public-addr` | `127.0.0.1:443` | externally-reachable host:port advertised to clients |
|
||||||
| `--octo-url` | *(required)* | CDN base URL the client uses for assets (e.g. `http://10.0.2.2:8080`) |
|
| `--octo-url` | *(required)* | CDN base URL the client uses for assets (e.g. `http://10.0.2.2:8080`) |
|
||||||
| `--db` | `db/game.db` | SQLite database path |
|
| `--db` | `db/game.db` | SQLite database path |
|
||||||
| `--auth-url` | *(empty)* | Auth server base URL (e.g. `http://localhost:3000`) |
|
| `--auth-url` | *(empty)* | Auth server base URL (e.g. `http://localhost:3000`) |
|
||||||
|
| `--admin-listen` | `127.0.0.1:8082` | Admin webhook listen address. Only binds when `LUNAR_ADMIN_TOKEN` is set. |
|
||||||
|
|
||||||
|
### Live Master Data Reload
|
||||||
|
|
||||||
|
The game server reads its master data from `assets/release/20240404193219.bin.e` at startup. To swap in updated content **without restarting** the server:
|
||||||
|
|
||||||
|
1. Replace `assets/release/20240404193219.bin.e` on disk with your edited copy.
|
||||||
|
2. POST to the admin webhook with a Bearer token matching `LUNAR_ADMIN_TOKEN`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Authorization: Bearer ${LUNAR_ADMIN_TOKEN}" \
|
||||||
|
http://127.0.0.1:8082/api/admin/master-data/reload
|
||||||
|
```
|
||||||
|
|
||||||
|
The server re-reads the file, atomically swaps every in-memory catalog and derived handler, and bumps the file's mtime. The mtime is folded into `GetLatestMasterDataVersion`, so connected clients see a new version string and re-download the file from the CDN on their next poll.
|
||||||
|
|
||||||
|
Security defaults are fail-closed:
|
||||||
|
|
||||||
|
- `LUNAR_ADMIN_TOKEN` **must** be set in the environment, or the webhook listener never binds.
|
||||||
|
- `--admin-listen` defaults to `127.0.0.1:8082` (loopback only). Bind to `0.0.0.0` only if you intend to expose it.
|
||||||
|
- Authentication uses constant-time Bearer-token comparison.
|
||||||
|
|
||||||
### CDN Flags (`octo-cdn`)
|
### CDN Flags (`octo-cdn`)
|
||||||
|
|
||||||
@@ -214,11 +239,22 @@ Each service has its own image and can be deployed independently:
|
|||||||
|
|
||||||
| Service | Image | Default Port | Notes |
|
| Service | Image | Default Port | Notes |
|
||||||
| -------- | --------------------------- | ------------ | ------------------------------ |
|
| -------- | --------------------------- | ------------ | ------------------------------ |
|
||||||
| `server` | `kretts/lunar-tear:latest` | 8003 | gRPC game server |
|
| `server` | `kretts/lunar-tear:latest` | 8003, 8082 | gRPC game server + admin webhook |
|
||||||
| `cdn` | `kretts/octo-cdn:latest` | 8080 | HTTP asset CDN |
|
| `cdn` | `kretts/octo-cdn:latest` | 8080 | HTTP asset CDN |
|
||||||
| `auth` | `kretts/auth-server:latest` | 3000 | Account registration and login |
|
| `auth` | `kretts/auth-server:latest` | 3000 | Account registration and login |
|
||||||
|
|
||||||
The game server is configured via environment variables in the compose file: `LUNAR_LISTEN` (bind address), `LUNAR_PUBLIC_ADDR` (client-facing address), `LUNAR_OCTO_URL`, and `LUNAR_AUTH_URL`. Auth is optional — if `LUNAR_AUTH_URL` is unset the game server starts without it.
|
The game server is configured via environment variables in the compose file:
|
||||||
|
|
||||||
|
| Env var | Description |
|
||||||
|
| --------------------- | -------------------------------------------------------------------------------------------- |
|
||||||
|
| `LUNAR_LISTEN` | gRPC bind address |
|
||||||
|
| `LUNAR_PUBLIC_ADDR` | Client-facing address advertised to the game |
|
||||||
|
| `LUNAR_OCTO_URL` | CDN base URL the client uses for assets |
|
||||||
|
| `LUNAR_AUTH_URL` | Auth server base URL (optional) |
|
||||||
|
| `LUNAR_ADMIN_LISTEN` | Admin webhook bind address inside the container (compose default: `0.0.0.0:8082`) |
|
||||||
|
| `LUNAR_ADMIN_TOKEN` | Bearer token for the admin webhook. **The webhook does not bind unless this is set.** |
|
||||||
|
|
||||||
|
Auth is optional — if `LUNAR_AUTH_URL` is unset the game server starts without it. The admin webhook is published to `127.0.0.1:8082` on the host so the master-data reload endpoint stays loopback-only by default; set `LUNAR_ADMIN_TOKEN` (e.g. via a `.env` file) before bringing the stack up.
|
||||||
|
|
||||||
### Makefile Targets
|
### Makefile Targets
|
||||||
|
|
||||||
|
|||||||
+22
-5
@@ -93,6 +93,10 @@ func main() {
|
|||||||
grpcOctoURL := flag.String("grpc.octo-url", "", "Octo CDN base URL passed to lunar-tear (default: derived from cdn.public-addr)")
|
grpcOctoURL := flag.String("grpc.octo-url", "", "Octo CDN base URL passed to lunar-tear (default: derived from cdn.public-addr)")
|
||||||
grpcAuthURL := flag.String("grpc.auth-url", "", "auth server base URL passed to lunar-tear (default: derived from auth.listen)")
|
grpcAuthURL := flag.String("grpc.auth-url", "", "auth server base URL passed to lunar-tear (default: derived from auth.listen)")
|
||||||
|
|
||||||
|
// admin webhook is opt-in; empty leaves lunar-tear's own default in place
|
||||||
|
// (the listener still only binds if LUNAR_ADMIN_TOKEN is set in the env).
|
||||||
|
adminListen := flag.String("admin.listen", "", "lunar-tear admin webhook listen address (host:port). Empty = leave default; webhook only binds when LUNAR_ADMIN_TOKEN is set in the env.")
|
||||||
|
|
||||||
noColor := flag.Bool("no-color", false, "disable colored output")
|
noColor := flag.Bool("no-color", false, "disable colored output")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -139,11 +143,7 @@ func main() {
|
|||||||
label: "grpc",
|
label: "grpc",
|
||||||
color: colorYellow,
|
color: colorYellow,
|
||||||
cmd: exec.CommandContext(ctx, filepath.Join("bin", "lunar-tear"+ext),
|
cmd: exec.CommandContext(ctx, filepath.Join("bin", "lunar-tear"+ext),
|
||||||
"--listen", *grpcListen,
|
grpcArgs(*grpcListen, *grpcPublicAddr, *grpcDB, *grpcOctoURL, *grpcAuthURL, *adminListen)...,
|
||||||
"--public-addr", *grpcPublicAddr,
|
|
||||||
"--db", *grpcDB,
|
|
||||||
"--octo-url", *grpcOctoURL,
|
|
||||||
"--auth-url", *grpcAuthURL,
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -200,3 +200,20 @@ func prefixLines(wg *sync.WaitGroup, prefix string, r io.Reader) {
|
|||||||
fmt.Printf("%s%s\n", prefix, scanner.Text())
|
fmt.Printf("%s%s\n", prefix, scanner.Text())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// grpcArgs assembles the argv for the lunar-tear subprocess. The admin flag
|
||||||
|
// is appended only when --admin.listen was supplied so we don't override
|
||||||
|
// lunar-tear's own default when the operator hasn't opted in.
|
||||||
|
func grpcArgs(listen, publicAddr, db, octoURL, authURL, adminListen string) []string {
|
||||||
|
args := []string{
|
||||||
|
"--listen", listen,
|
||||||
|
"--public-addr", publicAddr,
|
||||||
|
"--db", db,
|
||||||
|
"--octo-url", octoURL,
|
||||||
|
"--auth-url", authURL,
|
||||||
|
}
|
||||||
|
if adminListen != "" {
|
||||||
|
args = append(args, "--admin-listen", adminListen)
|
||||||
|
}
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// startAdmin spins up the admin webhook used by external content tools to
|
||||||
|
// trigger an in-place re-read of assets/release/20240404193219.bin.e.
|
||||||
|
//
|
||||||
|
// Authentication: Bearer token via the LUNAR_ADMIN_TOKEN environment variable.
|
||||||
|
// If LUNAR_ADMIN_TOKEN is unset or empty the listener does not bind at all
|
||||||
|
// (fail closed), so a fresh deploy never exposes an unauthenticated endpoint.
|
||||||
|
//
|
||||||
|
// The default --admin-listen is 127.0.0.1:8082 so the webhook is only
|
||||||
|
// reachable via loopback unless the operator opts in by binding to 0.0.0.0.
|
||||||
|
func startAdmin(listen string, holder *runtime.Holder) {
|
||||||
|
token := os.Getenv("LUNAR_ADMIN_TOKEN")
|
||||||
|
if token == "" {
|
||||||
|
log.Println("[admin] disabled (no LUNAR_ADMIN_TOKEN set)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expected := []byte("Bearer " + token)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/api/admin/master-data/reload", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got := []byte(r.Header.Get("Authorization"))
|
||||||
|
if len(got) != len(expected) || subtle.ConstantTimeCompare(got, expected) != 1 {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := holder.Reload(); err != nil {
|
||||||
|
log.Printf("[admin] master-data reload failed: %v", err)
|
||||||
|
http.Error(w, "master-data reload failed", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[admin] master-data reloaded by %s", r.RemoteAddr)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"ok":true}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("[admin] webhook listener on %s (token-gated)", listen)
|
||||||
|
go func() {
|
||||||
|
if err := http.ListenAndServe(listen, mux); err != nil {
|
||||||
|
log.Printf("[admin] webhook listener failed: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
@@ -6,10 +6,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gacha"
|
|
||||||
"lunar-tear/server/internal/interceptor"
|
"lunar-tear/server/internal/interceptor"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/questflow"
|
|
||||||
"lunar-tear/server/internal/service"
|
"lunar-tear/server/internal/service"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
@@ -40,27 +38,7 @@ func startGRPC(
|
|||||||
store.UserRepository
|
store.UserRepository
|
||||||
store.SessionRepository
|
store.SessionRepository
|
||||||
},
|
},
|
||||||
questEngine *questflow.QuestHandler,
|
holder *runtime.Holder,
|
||||||
gachaHandler *gacha.GachaHandler,
|
|
||||||
gachaEntries []store.GachaCatalogEntry,
|
|
||||||
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,
|
|
||||||
consumableItemCatalog *masterdata.ConsumableItemCatalog,
|
|
||||||
gameConfig *masterdata.GameConfig,
|
|
||||||
sideStoryCatalog *masterdata.SideStoryCatalog,
|
|
||||||
bigHuntCatalog *masterdata.BigHuntCatalog,
|
|
||||||
) *grpc.Server {
|
) *grpc.Server {
|
||||||
lis, err := net.Listen("tcp", listenAddr)
|
lis, err := net.Listen("tcp", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -74,33 +52,7 @@ func startGRPC(
|
|||||||
grpc.UnknownServiceHandler(interceptor.UnknownService),
|
grpc.UnknownServiceHandler(interceptor.UnknownService),
|
||||||
)
|
)
|
||||||
|
|
||||||
registerServices(grpcServer,
|
registerServices(grpcServer, publicAddr, octoURL, authURL, userStore, holder)
|
||||||
publicAddr,
|
|
||||||
octoURL,
|
|
||||||
authURL,
|
|
||||||
userStore,
|
|
||||||
questEngine,
|
|
||||||
gachaHandler,
|
|
||||||
gachaEntries,
|
|
||||||
cageOrnamentCatalog,
|
|
||||||
loginBonusCatalog,
|
|
||||||
characterViewerCatalog,
|
|
||||||
shopCatalog,
|
|
||||||
costumeCatalog,
|
|
||||||
omikujiCatalog,
|
|
||||||
weaponCatalog,
|
|
||||||
exploreCatalog,
|
|
||||||
gimmickCatalog,
|
|
||||||
characterBoardCatalog,
|
|
||||||
partsCatalog,
|
|
||||||
characterRebirthCatalog,
|
|
||||||
companionCatalog,
|
|
||||||
materialCatalog,
|
|
||||||
consumableItemCatalog,
|
|
||||||
gameConfig,
|
|
||||||
sideStoryCatalog,
|
|
||||||
bigHuntCatalog,
|
|
||||||
)
|
|
||||||
|
|
||||||
reflection.Register(grpcServer)
|
reflection.Register(grpcServer)
|
||||||
|
|
||||||
@@ -124,66 +76,46 @@ func registerServices(
|
|||||||
store.UserRepository
|
store.UserRepository
|
||||||
store.SessionRepository
|
store.SessionRepository
|
||||||
},
|
},
|
||||||
questEngine *questflow.QuestHandler,
|
holder *runtime.Holder,
|
||||||
gachaHandler *gacha.GachaHandler,
|
|
||||||
gachaEntries []store.GachaCatalogEntry,
|
|
||||||
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,
|
|
||||||
consumableItemCatalog *masterdata.ConsumableItemCatalog,
|
|
||||||
gameConfig *masterdata.GameConfig,
|
|
||||||
sideStoryCatalog *masterdata.SideStoryCatalog,
|
|
||||||
bigHuntCatalog *masterdata.BigHuntCatalog,
|
|
||||||
) {
|
) {
|
||||||
pubHost, pubPortStr, _ := net.SplitHostPort(publicAddr)
|
pubHost, pubPortStr, _ := net.SplitHostPort(publicAddr)
|
||||||
pubPort, _ := strconv.Atoi(pubPortStr)
|
pubPort, _ := strconv.Atoi(pubPortStr)
|
||||||
|
|
||||||
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(gachaEntries))
|
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(holder))
|
||||||
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL))
|
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL))
|
||||||
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
|
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
|
||||||
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(pubHost, int32(pubPort), octoURL))
|
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(pubHost, int32(pubPort), octoURL))
|
||||||
pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
|
pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
|
||||||
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, questEngine))
|
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, gachaEntries, gachaHandler))
|
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterGiftServiceServer(srv, service.NewGiftServiceServer(userStore, userStore))
|
pb.RegisterGiftServiceServer(srv, service.NewGiftServiceServer(userStore, userStore))
|
||||||
pb.RegisterGamePlayServiceServer(srv, service.NewGameplayServiceServer())
|
pb.RegisterGamePlayServiceServer(srv, service.NewGameplayServiceServer())
|
||||||
pb.RegisterGimmickServiceServer(srv, service.NewGimmickServiceServer(userStore, userStore, gimmickCatalog))
|
pb.RegisterGimmickServiceServer(srv, service.NewGimmickServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterQuestServiceServer(srv, service.NewQuestServiceServer(userStore, userStore, questEngine))
|
pb.RegisterQuestServiceServer(srv, service.NewQuestServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterNotificationServiceServer(srv, service.NewNotificationServiceServer(userStore, userStore))
|
pb.RegisterNotificationServiceServer(srv, service.NewNotificationServiceServer(userStore, userStore))
|
||||||
pb.RegisterCageOrnamentServiceServer(srv, service.NewCageOrnamentServiceServer(userStore, userStore, cageOrnamentCatalog, questEngine.Granter))
|
pb.RegisterCageOrnamentServiceServer(srv, service.NewCageOrnamentServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterDeckServiceServer(srv, service.NewDeckServiceServer(userStore, userStore))
|
pb.RegisterDeckServiceServer(srv, service.NewDeckServiceServer(userStore, userStore))
|
||||||
pb.RegisterFriendServiceServer(srv, service.NewFriendServiceServer(userStore, userStore))
|
pb.RegisterFriendServiceServer(srv, service.NewFriendServiceServer(userStore, userStore))
|
||||||
pb.RegisterLoginBonusServiceServer(srv, service.NewLoginBonusServiceServer(userStore, userStore, loginBonusCatalog))
|
pb.RegisterLoginBonusServiceServer(srv, service.NewLoginBonusServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterNaviCutInServiceServer(srv, service.NewNaviCutInServiceServer(userStore, userStore))
|
pb.RegisterNaviCutInServiceServer(srv, service.NewNaviCutInServiceServer(userStore, userStore))
|
||||||
pb.RegisterContentsStoryServiceServer(srv, service.NewContentsStoryServiceServer(userStore, userStore))
|
pb.RegisterContentsStoryServiceServer(srv, service.NewContentsStoryServiceServer(userStore, userStore))
|
||||||
pb.RegisterDokanServiceServer(srv, service.NewDokanServiceServer(userStore, userStore))
|
pb.RegisterDokanServiceServer(srv, service.NewDokanServiceServer(userStore, userStore))
|
||||||
pb.RegisterPortalCageServiceServer(srv, service.NewPortalCageServiceServer(userStore, userStore))
|
pb.RegisterPortalCageServiceServer(srv, service.NewPortalCageServiceServer(userStore, userStore))
|
||||||
pb.RegisterCharacterViewerServiceServer(srv, service.NewCharacterViewerServiceServer(userStore, userStore, characterViewerCatalog))
|
pb.RegisterCharacterViewerServiceServer(srv, service.NewCharacterViewerServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterMissionServiceServer(srv, service.NewMissionServiceServer(userStore, userStore))
|
pb.RegisterMissionServiceServer(srv, service.NewMissionServiceServer(userStore, userStore))
|
||||||
pb.RegisterShopServiceServer(srv, service.NewShopServiceServer(userStore, userStore, shopCatalog, questEngine.Granter))
|
pb.RegisterShopServiceServer(srv, service.NewShopServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterCostumeServiceServer(srv, service.NewCostumeServiceServer(userStore, userStore, costumeCatalog, gameConfig))
|
pb.RegisterCostumeServiceServer(srv, service.NewCostumeServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterMovieServiceServer(srv, service.NewMovieServiceServer(userStore, userStore))
|
pb.RegisterMovieServiceServer(srv, service.NewMovieServiceServer(userStore, userStore))
|
||||||
pb.RegisterOmikujiServiceServer(srv, service.NewOmikujiServiceServer(userStore, userStore, omikujiCatalog))
|
pb.RegisterOmikujiServiceServer(srv, service.NewOmikujiServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterWeaponServiceServer(srv, service.NewWeaponServiceServer(userStore, userStore, weaponCatalog, gameConfig))
|
pb.RegisterWeaponServiceServer(srv, service.NewWeaponServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterExploreServiceServer(srv, service.NewExploreServiceServer(userStore, userStore, exploreCatalog))
|
pb.RegisterExploreServiceServer(srv, service.NewExploreServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterCharacterBoardServiceServer(srv, service.NewCharacterBoardServiceServer(userStore, userStore, characterBoardCatalog))
|
pb.RegisterCharacterBoardServiceServer(srv, service.NewCharacterBoardServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterPartsServiceServer(srv, service.NewPartsServiceServer(userStore, userStore, partsCatalog, gameConfig))
|
pb.RegisterPartsServiceServer(srv, service.NewPartsServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterCharacterServiceServer(srv, service.NewCharacterServiceServer(userStore, userStore, characterRebirthCatalog, gameConfig))
|
pb.RegisterCharacterServiceServer(srv, service.NewCharacterServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterCompanionServiceServer(srv, service.NewCompanionServiceServer(userStore, userStore, companionCatalog, gameConfig))
|
pb.RegisterCompanionServiceServer(srv, service.NewCompanionServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterMaterialServiceServer(srv, service.NewMaterialServiceServer(userStore, userStore, materialCatalog, gameConfig))
|
pb.RegisterMaterialServiceServer(srv, service.NewMaterialServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterConsumableItemServiceServer(srv, service.NewConsumableItemServiceServer(userStore, userStore, consumableItemCatalog, gameConfig))
|
pb.RegisterConsumableItemServiceServer(srv, service.NewConsumableItemServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterSideStoryQuestServiceServer(srv, service.NewSideStoryQuestServiceServer(userStore, userStore, sideStoryCatalog))
|
pb.RegisterSideStoryQuestServiceServer(srv, service.NewSideStoryQuestServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, bigHuntCatalog, questEngine))
|
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, holder))
|
||||||
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, bigHuntCatalog, questEngine.Granter))
|
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, holder))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,30 +9,30 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"lunar-tear/server/internal/database"
|
"lunar-tear/server/internal/database"
|
||||||
"lunar-tear/server/internal/gacha"
|
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/masterdata/memorydb"
|
|
||||||
"lunar-tear/server/internal/questflow"
|
|
||||||
"lunar-tear/server/internal/store/sqlite"
|
"lunar-tear/server/internal/store/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const masterDataPath = "assets/release/20240404193219.bin.e"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
listen := flag.String("listen", "0.0.0.0:443", "gRPC listen address (host:port)")
|
listen := flag.String("listen", "0.0.0.0:443", "gRPC listen address (host:port)")
|
||||||
publicAddr := flag.String("public-addr", "127.0.0.1:443", "externally-reachable host:port advertised to clients")
|
publicAddr := flag.String("public-addr", "127.0.0.1:443", "externally-reachable host:port advertised to clients")
|
||||||
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
||||||
octoURL := flag.String("octo-url", "", "Octo CDN base URL the client will use for assets (e.g. http://10.0.2.2:8080)")
|
octoURL := flag.String("octo-url", "", "Octo CDN base URL the client will use for assets (e.g. http://10.0.2.2:8080)")
|
||||||
authURL := flag.String("auth-url", "", "Auth server base URL for Facebook token validation (e.g. http://localhost:3000)")
|
authURL := flag.String("auth-url", "", "Auth server base URL for Facebook token validation (e.g. http://localhost:3000)")
|
||||||
|
adminListen := flag.String("admin-listen", "127.0.0.1:8082", "admin webhook listen address (host:port). Loopback by default; only binds when LUNAR_ADMIN_TOKEN is set.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *octoURL == "" {
|
if *octoURL == "" {
|
||||||
log.Fatalf("--octo-url is required (e.g. http://10.0.2.2:8080)")
|
log.Fatalf("--octo-url is required (e.g. http://10.0.2.2:8080)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := memorydb.Init("assets/release/20240404193219.bin.e"); err != nil {
|
holder, err := runtime.NewHolder(masterDataPath)
|
||||||
log.Fatalf("load master data: %v", err)
|
if err != nil {
|
||||||
|
log.Fatalf("init master data: %v", err)
|
||||||
}
|
}
|
||||||
log.Printf("master data loaded (%d tables)", memorydb.TableCount())
|
|
||||||
|
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
@@ -44,158 +44,11 @@ func main() {
|
|||||||
defer db.Close()
|
defer db.Close()
|
||||||
log.Printf("database opened: %s", *dbPath)
|
log.Printf("database opened: %s", *dbPath)
|
||||||
|
|
||||||
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 := sqlite.New(db, gametime.Now)
|
userStore := sqlite.New(db, gametime.Now)
|
||||||
|
|
||||||
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
grpcServer := startGRPC(*listen, *publicAddr, *octoURL, *authURL, userStore, holder)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("load gacha catalog: %v", err)
|
|
||||||
}
|
|
||||||
log.Printf("gacha catalog loaded: %d entries", len(gachaEntries))
|
|
||||||
|
|
||||||
gachaPool, err := masterdata.LoadGachaPool()
|
startAdmin(*adminListen, holder)
|
||||||
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)
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
consumableItemCatalog, err := masterdata.LoadConsumableItemCatalog()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("load consumable item catalog: %v", err)
|
|
||||||
}
|
|
||||||
log.Printf("consumable item catalog loaded: %d items", len(consumableItemCatalog.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()
|
|
||||||
|
|
||||||
grpcServer := startGRPC(
|
|
||||||
*listen,
|
|
||||||
*publicAddr,
|
|
||||||
*octoURL,
|
|
||||||
*authURL,
|
|
||||||
userStore,
|
|
||||||
questHandler,
|
|
||||||
gachaHandler,
|
|
||||||
gachaEntries,
|
|
||||||
cageOrnamentCatalog,
|
|
||||||
loginBonusCatalog,
|
|
||||||
characterViewerCatalog,
|
|
||||||
shopCatalog,
|
|
||||||
costumeCatalog,
|
|
||||||
omikujiCatalog,
|
|
||||||
weaponCatalog,
|
|
||||||
exploreCatalog,
|
|
||||||
gimmickCatalog,
|
|
||||||
characterBoardCatalog,
|
|
||||||
partsCatalog,
|
|
||||||
characterRebirthCatalog,
|
|
||||||
companionCatalog,
|
|
||||||
materialCatalog,
|
|
||||||
consumableItemCatalog,
|
|
||||||
gameConfig,
|
|
||||||
sideStoryCatalog,
|
|
||||||
bigHuntCatalog,
|
|
||||||
)
|
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
log.Println("shutting down...")
|
log.Println("shutting down...")
|
||||||
|
|||||||
+58
-17
@@ -32,13 +32,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Device string `json:"device"`
|
Device string `json:"device"`
|
||||||
Detail string `json:"detail"`
|
Detail string `json:"detail"`
|
||||||
Summary string `json:"summary"`
|
Summary string `json:"summary"`
|
||||||
GRPCPort int `json:"grpc_port,omitempty"`
|
GRPCPort int `json:"grpc_port,omitempty"`
|
||||||
CDNPort int `json:"cdn_port,omitempty"`
|
CDNPort int `json:"cdn_port,omitempty"`
|
||||||
AuthPort int `json:"auth_port,omitempty"`
|
AuthPort int `json:"auth_port,omitempty"`
|
||||||
|
AdminPort int `json:"admin_port,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -47,10 +48,13 @@ const (
|
|||||||
defaultAuthPort = 3000
|
defaultAuthPort = 3000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ports.Admin is opt-in: 0 means the admin webhook is not configured by the
|
||||||
|
// wizard at all. Other ports always get a default if unset.
|
||||||
type ports struct {
|
type ports struct {
|
||||||
GRPC int
|
GRPC int
|
||||||
CDN int
|
CDN int
|
||||||
Auth int
|
Auth int
|
||||||
|
Admin int
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -59,6 +63,7 @@ func main() {
|
|||||||
grpcPort := flag.Int("grpc-port", defaultGRPCPort, "gRPC server port")
|
grpcPort := flag.Int("grpc-port", defaultGRPCPort, "gRPC server port")
|
||||||
cdnPort := flag.Int("cdn-port", defaultCDNPort, "CDN server port")
|
cdnPort := flag.Int("cdn-port", defaultCDNPort, "CDN server port")
|
||||||
authPort := flag.Int("auth-port", defaultAuthPort, "auth server port")
|
authPort := flag.Int("auth-port", defaultAuthPort, "auth server port")
|
||||||
|
adminPort := flag.Int("admin-port", 0, "admin webhook port (0 = disabled). Bound on 127.0.0.1; only takes effect when LUNAR_ADMIN_TOKEN is set.")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
flagSet := map[string]bool{}
|
flagSet := map[string]bool{}
|
||||||
@@ -80,10 +85,10 @@ func main() {
|
|||||||
|
|
||||||
ip, cfg, firstRun := resolveIP(*preferSaved)
|
ip, cfg, firstRun := resolveIP(*preferSaved)
|
||||||
|
|
||||||
p := resolvePorts(flagSet, *grpcPort, *cdnPort, *authPort, cfg)
|
p := resolvePorts(flagSet, *grpcPort, *cdnPort, *authPort, *adminPort, cfg)
|
||||||
savedPorts := portsFromConfig(cfg)
|
savedPorts := portsFromConfig(cfg)
|
||||||
|
|
||||||
if !firstRun && (p.GRPC != savedPorts.GRPC || p.CDN != savedPorts.CDN || p.Auth != savedPorts.Auth) {
|
if !firstRun && (p.GRPC != savedPorts.GRPC || p.CDN != savedPorts.CDN || p.Auth != savedPorts.Auth || p.Admin != savedPorts.Admin) {
|
||||||
if !warnPortChange(savedPorts, p) {
|
if !warnPortChange(savedPorts, p) {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
@@ -92,6 +97,7 @@ func main() {
|
|||||||
cfg.GRPCPort = p.GRPC
|
cfg.GRPCPort = p.GRPC
|
||||||
cfg.CDNPort = p.CDN
|
cfg.CDNPort = p.CDN
|
||||||
cfg.AuthPort = p.Auth
|
cfg.AuthPort = p.Auth
|
||||||
|
cfg.AdminPort = p.Admin
|
||||||
saveConfig(cfg)
|
saveConfig(cfg)
|
||||||
|
|
||||||
labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Width(14)
|
labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Width(14)
|
||||||
@@ -101,6 +107,9 @@ func main() {
|
|||||||
fmt.Printf(" %s %s\n", labelStyle.Render("Game server:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.GRPC)))
|
fmt.Printf(" %s %s\n", labelStyle.Render("Game server:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.GRPC)))
|
||||||
fmt.Printf(" %s %s\n", labelStyle.Render("CDN:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.CDN)))
|
fmt.Printf(" %s %s\n", labelStyle.Render("CDN:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.CDN)))
|
||||||
fmt.Printf(" %s %s\n", labelStyle.Render("Auth:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.Auth)))
|
fmt.Printf(" %s %s\n", labelStyle.Render("Auth:"), addrStyle.Render(fmt.Sprintf("%s:%d", ip, p.Auth)))
|
||||||
|
if p.Admin > 0 {
|
||||||
|
fmt.Printf(" %s %s\n", labelStyle.Render("Admin webhook:"), addrStyle.Render(fmt.Sprintf("127.0.0.1:%d", p.Admin)))
|
||||||
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
if firstRun || *setupOnly {
|
if firstRun || *setupOnly {
|
||||||
@@ -477,6 +486,22 @@ func warnPortChange(old, new ports) bool {
|
|||||||
}
|
}
|
||||||
return hlStyle.Render(fmt.Sprintf(" %-7s %d → %d", label+":", oldP, newP))
|
return hlStyle.Render(fmt.Sprintf(" %-7s %d → %d", label+":", oldP, newP))
|
||||||
}
|
}
|
||||||
|
// Admin formatting handles the disabled (0) state since the port is
|
||||||
|
// opt-in and we don't want to display "0" to the user.
|
||||||
|
adminLine := func(oldP, newP int) (string, bool) {
|
||||||
|
switch {
|
||||||
|
case oldP == 0 && newP == 0:
|
||||||
|
return "", false
|
||||||
|
case oldP == 0 && newP != 0:
|
||||||
|
return hlStyle.Render(fmt.Sprintf(" %-7s disabled → %d", "Admin:", newP)), true
|
||||||
|
case oldP != 0 && newP == 0:
|
||||||
|
return hlStyle.Render(fmt.Sprintf(" %-7s %d → disabled", "Admin:", oldP)), true
|
||||||
|
case oldP == newP:
|
||||||
|
return dimStyle.Render(fmt.Sprintf(" %-7s %d (unchanged)", "Admin:", oldP)), true
|
||||||
|
default:
|
||||||
|
return hlStyle.Render(fmt.Sprintf(" %-7s %d → %d", "Admin:", oldP, newP)), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
@@ -487,7 +512,12 @@ func warnPortChange(old, new ports) bool {
|
|||||||
b.WriteString(portLine("CDN", old.CDN, new.CDN))
|
b.WriteString(portLine("CDN", old.CDN, new.CDN))
|
||||||
b.WriteString("\n")
|
b.WriteString("\n")
|
||||||
b.WriteString(portLine("Auth", old.Auth, new.Auth))
|
b.WriteString(portLine("Auth", old.Auth, new.Auth))
|
||||||
b.WriteString("\n\n")
|
b.WriteString("\n")
|
||||||
|
if line, show := adminLine(old.Admin, new.Admin); show {
|
||||||
|
b.WriteString(line)
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
b.WriteString("\n")
|
||||||
b.WriteString(dimStyle.Render(" Your APK was patched for the old ports. You may need to re-patch."))
|
b.WriteString(dimStyle.Render(" Your APK was patched for the old ports. You may need to re-patch."))
|
||||||
b.WriteString("\n\n")
|
b.WriteString("\n\n")
|
||||||
fmt.Print(b.String())
|
fmt.Print(b.String())
|
||||||
@@ -821,7 +851,7 @@ func loadConfig() (config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func portsFromConfig(cfg config) ports {
|
func portsFromConfig(cfg config) ports {
|
||||||
p := ports{GRPC: cfg.GRPCPort, CDN: cfg.CDNPort, Auth: cfg.AuthPort}
|
p := ports{GRPC: cfg.GRPCPort, CDN: cfg.CDNPort, Auth: cfg.AuthPort, Admin: cfg.AdminPort}
|
||||||
if p.GRPC == 0 {
|
if p.GRPC == 0 {
|
||||||
p.GRPC = defaultGRPCPort
|
p.GRPC = defaultGRPCPort
|
||||||
}
|
}
|
||||||
@@ -831,10 +861,11 @@ func portsFromConfig(cfg config) ports {
|
|||||||
if p.Auth == 0 {
|
if p.Auth == 0 {
|
||||||
p.Auth = defaultAuthPort
|
p.Auth = defaultAuthPort
|
||||||
}
|
}
|
||||||
|
// Admin is opt-in: leave 0 = disabled.
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolvePorts(flagSet map[string]bool, grpcFlag, cdnFlag, authFlag int, saved config) ports {
|
func resolvePorts(flagSet map[string]bool, grpcFlag, cdnFlag, authFlag, adminFlag int, saved config) ports {
|
||||||
resolve := func(name string, flagVal, savedVal, defaultVal int) int {
|
resolve := func(name string, flagVal, savedVal, defaultVal int) int {
|
||||||
if flagSet[name] {
|
if flagSet[name] {
|
||||||
return flagVal
|
return flagVal
|
||||||
@@ -848,6 +879,9 @@ func resolvePorts(flagSet map[string]bool, grpcFlag, cdnFlag, authFlag int, save
|
|||||||
GRPC: resolve("grpc-port", grpcFlag, saved.GRPCPort, defaultGRPCPort),
|
GRPC: resolve("grpc-port", grpcFlag, saved.GRPCPort, defaultGRPCPort),
|
||||||
CDN: resolve("cdn-port", cdnFlag, saved.CDNPort, defaultCDNPort),
|
CDN: resolve("cdn-port", cdnFlag, saved.CDNPort, defaultCDNPort),
|
||||||
Auth: resolve("auth-port", authFlag, saved.AuthPort, defaultAuthPort),
|
Auth: resolve("auth-port", authFlag, saved.AuthPort, defaultAuthPort),
|
||||||
|
// defaultVal=0 keeps admin opt-in: never enabled unless --admin-port
|
||||||
|
// is passed or a non-zero value was previously saved.
|
||||||
|
Admin: resolve("admin-port", adminFlag, saved.AdminPort, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -874,13 +908,20 @@ func launchDev(ip string, p ports) {
|
|||||||
runQuiet(exec.Command("go", "build", "-o", devBin, "./cmd/dev"), "build dev")
|
runQuiet(exec.Command("go", "build", "-o", devBin, "./cmd/dev"), "build dev")
|
||||||
}).Run()
|
}).Run()
|
||||||
|
|
||||||
cmd := exec.Command(devBin,
|
devArgs := []string{
|
||||||
"--grpc.listen", fmt.Sprintf("0.0.0.0:%d", p.GRPC),
|
"--grpc.listen", fmt.Sprintf("0.0.0.0:%d", p.GRPC),
|
||||||
"--grpc.public-addr", fmt.Sprintf("%s:%d", ip, p.GRPC),
|
"--grpc.public-addr", fmt.Sprintf("%s:%d", ip, p.GRPC),
|
||||||
"--cdn.listen", fmt.Sprintf("0.0.0.0:%d", p.CDN),
|
"--cdn.listen", fmt.Sprintf("0.0.0.0:%d", p.CDN),
|
||||||
"--cdn.public-addr", fmt.Sprintf("%s:%d", ip, p.CDN),
|
"--cdn.public-addr", fmt.Sprintf("%s:%d", ip, p.CDN),
|
||||||
"--auth.listen", fmt.Sprintf("0.0.0.0:%d", p.Auth),
|
"--auth.listen", fmt.Sprintf("0.0.0.0:%d", p.Auth),
|
||||||
)
|
}
|
||||||
|
// Bind admin on loopback only — the wizard is for local dev, and the
|
||||||
|
// webhook should never be exposed to the LAN by accident. Operators who
|
||||||
|
// want a different bind can run cmd/dev directly with --admin.listen.
|
||||||
|
if p.Admin > 0 {
|
||||||
|
devArgs = append(devArgs, "--admin.listen", fmt.Sprintf("127.0.0.1:%d", p.Admin))
|
||||||
|
}
|
||||||
|
cmd := exec.Command(devBin, devArgs...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ services:
|
|||||||
LUNAR_PUBLIC_ADDR: 127.0.0.1:8003
|
LUNAR_PUBLIC_ADDR: 127.0.0.1:8003
|
||||||
LUNAR_OCTO_URL: http://cdn:8080
|
LUNAR_OCTO_URL: http://cdn:8080
|
||||||
LUNAR_AUTH_URL: http://auth:3000
|
LUNAR_AUTH_URL: http://auth:3000
|
||||||
|
LUNAR_ADMIN_LISTEN: 0.0.0.0:8082
|
||||||
|
LUNAR_ADMIN_TOKEN: ${LUNAR_ADMIN_TOKEN:-}
|
||||||
volumes:
|
volumes:
|
||||||
- ./db:/opt/lunar-tear/db
|
- ./db:/opt/lunar-tear/db
|
||||||
- ./assets:/opt/lunar-tear/assets
|
- ./assets:/opt/lunar-tear/assets
|
||||||
ports:
|
ports:
|
||||||
- 8003:8003
|
- 8003:8003
|
||||||
|
- 127.0.0.1:8082:8082
|
||||||
depends_on:
|
depends_on:
|
||||||
- cdn
|
- cdn
|
||||||
- auth
|
- auth
|
||||||
|
|||||||
@@ -9,8 +9,14 @@ if [ -n "${LUNAR_AUTH_URL}" ]; then
|
|||||||
AUTH_FLAG="--auth-url ${LUNAR_AUTH_URL}"
|
AUTH_FLAG="--auth-url ${LUNAR_AUTH_URL}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
ADMIN_FLAG=""
|
||||||
|
if [ -n "${LUNAR_ADMIN_LISTEN}" ]; then
|
||||||
|
ADMIN_FLAG="--admin-listen ${LUNAR_ADMIN_LISTEN}"
|
||||||
|
fi
|
||||||
|
|
||||||
exec ./lunar-tear \
|
exec ./lunar-tear \
|
||||||
--listen "${LUNAR_LISTEN:-0.0.0.0:443}" \
|
--listen "${LUNAR_LISTEN:-0.0.0.0:443}" \
|
||||||
--public-addr "${LUNAR_PUBLIC_ADDR}" \
|
--public-addr "${LUNAR_PUBLIC_ADDR}" \
|
||||||
--octo-url "${LUNAR_OCTO_URL}" \
|
--octo-url "${LUNAR_OCTO_URL}" \
|
||||||
${AUTH_FLAG}
|
${AUTH_FLAG} \
|
||||||
|
${ADMIN_FLAG}
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/gacha"
|
||||||
|
"lunar-tear/server/internal/masterdata"
|
||||||
|
"lunar-tear/server/internal/masterdata/memorydb"
|
||||||
|
"lunar-tear/server/internal/questflow"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildCatalogs runs the full Load*/Build*/Enrich* sequence against whatever
|
||||||
|
// memorydb currently holds and returns a fully populated *Catalogs. Called
|
||||||
|
// once at startup and again on every reload.
|
||||||
|
func buildCatalogs() (*Catalogs, error) {
|
||||||
|
log.Printf("master data loaded (%d tables)", memorydb.TableCount())
|
||||||
|
|
||||||
|
gameConfig, err := masterdata.LoadGameConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load game config: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("game config loaded (goldId=%d, skipTicketId=%d, rebirthGold=%d)",
|
||||||
|
gameConfig.ConsumableItemIdForGold, gameConfig.ConsumableItemIdForQuestSkipTicket, gameConfig.CharacterRebirthConsumeGold)
|
||||||
|
|
||||||
|
partsCatalog, err := masterdata.LoadPartsCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load parts catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("parts catalog loaded: %d parts, %d rarities", len(partsCatalog.PartsById), len(partsCatalog.RarityByRarityType))
|
||||||
|
|
||||||
|
questCatalog, err := masterdata.LoadQuestCatalog(partsCatalog)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load quest catalog: %w", err)
|
||||||
|
}
|
||||||
|
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig)
|
||||||
|
|
||||||
|
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load gacha catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("gacha catalog loaded: %d entries", len(gachaEntries))
|
||||||
|
|
||||||
|
gachaPool, err := masterdata.LoadGachaPool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load gacha pool: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("gacha pool loaded: costumes=%d rarities, weapons=%d rarities, materials=%d",
|
||||||
|
len(gachaPool.CostumesByRarity), len(gachaPool.WeaponsByRarity), len(gachaPool.Materials))
|
||||||
|
|
||||||
|
shopCatalog, err := masterdata.LoadShopCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load shop catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("shop catalog loaded: %d items, %d content groups, %d exchange shops",
|
||||||
|
len(shopCatalog.Items), len(shopCatalog.Contents), len(shopCatalog.ExchangeShopCells))
|
||||||
|
|
||||||
|
gachaPool.BuildShopFeatured(shopCatalog)
|
||||||
|
gachaPool.PruneUnpairedCostumes()
|
||||||
|
gachaPool.BuildFeaturedMapping(gachaEntries)
|
||||||
|
gachaPool.BuildBannerPools(gachaEntries)
|
||||||
|
masterdata.EnrichCatalogPromotions(gachaEntries, gachaPool)
|
||||||
|
|
||||||
|
dupExchange, err := masterdata.LoadDupExchange()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load dup exchange: %w", err)
|
||||||
|
}
|
||||||
|
dupAdded, err := masterdata.EnrichDupExchange(dupExchange, gachaPool)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("enrich dup exchange: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("dup exchange loaded: %d entries (%d derived from limit-break materials)", len(dupExchange), dupAdded)
|
||||||
|
|
||||||
|
gachaHandler := gacha.NewGachaHandler(gachaPool, gameConfig, questHandler.Granter, medalInfo, dupExchange)
|
||||||
|
|
||||||
|
conditionResolver, err := masterdata.LoadConditionResolver()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load condition resolver: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cageOrnamentCatalog := masterdata.LoadCageOrnamentCatalog()
|
||||||
|
loginBonusCatalog := masterdata.LoadLoginBonusCatalog()
|
||||||
|
characterViewerCatalog := masterdata.LoadCharacterViewerCatalog(conditionResolver)
|
||||||
|
omikujiCatalog := masterdata.LoadOmikujiCatalog()
|
||||||
|
|
||||||
|
materialCatalog, err := masterdata.LoadMaterialCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load material catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("material catalog loaded: %d materials", len(materialCatalog.All))
|
||||||
|
|
||||||
|
consumableItemCatalog, err := masterdata.LoadConsumableItemCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load consumable item catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("consumable item catalog loaded: %d items", len(consumableItemCatalog.All))
|
||||||
|
|
||||||
|
costumeCatalog, err := masterdata.LoadCostumeCatalog(materialCatalog)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load costume catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("costume catalog loaded: %d costumes, %d materials, %d rarity curves", len(costumeCatalog.Costumes), len(costumeCatalog.Materials), len(costumeCatalog.ExpByRarity))
|
||||||
|
|
||||||
|
weaponCatalog, err := masterdata.LoadWeaponCatalog(materialCatalog)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load weapon catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("weapon catalog loaded: %d weapons, %d materials, %d enhance configs", len(weaponCatalog.Weapons), len(weaponCatalog.Materials), len(weaponCatalog.ExpByEnhanceId))
|
||||||
|
|
||||||
|
exploreCatalog, err := masterdata.LoadExploreCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load explore catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("explore catalog loaded: %d explores, %d grade assets", len(exploreCatalog.Explores), len(exploreCatalog.GradeAssets))
|
||||||
|
|
||||||
|
gimmickCatalog, err := masterdata.LoadGimmickCatalog(conditionResolver)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load gimmick catalog: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
characterBoardCatalog, err := masterdata.LoadCharacterBoardCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load character board catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("character board catalog loaded: %d panels, %d boards", len(characterBoardCatalog.PanelById), len(characterBoardCatalog.BoardById))
|
||||||
|
|
||||||
|
characterRebirthCatalog, err := masterdata.LoadCharacterRebirthCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load character rebirth catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("character rebirth catalog loaded: %d characters", len(characterRebirthCatalog.StepGroupByCharacterId))
|
||||||
|
|
||||||
|
companionCatalog, err := masterdata.LoadCompanionCatalog()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load companion catalog: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("companion catalog loaded: %d companions, %d categories", len(companionCatalog.CompanionById), len(companionCatalog.GoldCostByCategory))
|
||||||
|
|
||||||
|
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
|
||||||
|
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
|
||||||
|
|
||||||
|
return &Catalogs{
|
||||||
|
GameConfig: gameConfig,
|
||||||
|
Parts: partsCatalog,
|
||||||
|
Quest: questCatalog,
|
||||||
|
GachaEntries: gachaEntries,
|
||||||
|
GachaMedals: medalInfo,
|
||||||
|
GachaPool: gachaPool,
|
||||||
|
Shop: shopCatalog,
|
||||||
|
DupExchange: dupExchange,
|
||||||
|
ConditionResolver: conditionResolver,
|
||||||
|
CageOrnament: cageOrnamentCatalog,
|
||||||
|
LoginBonus: loginBonusCatalog,
|
||||||
|
CharacterViewer: characterViewerCatalog,
|
||||||
|
Omikuji: omikujiCatalog,
|
||||||
|
Material: materialCatalog,
|
||||||
|
ConsumableItem: consumableItemCatalog,
|
||||||
|
Costume: costumeCatalog,
|
||||||
|
Weapon: weaponCatalog,
|
||||||
|
Explore: exploreCatalog,
|
||||||
|
Gimmick: gimmickCatalog,
|
||||||
|
CharacterBoard: characterBoardCatalog,
|
||||||
|
CharacterRebirth: characterRebirthCatalog,
|
||||||
|
Companion: companionCatalog,
|
||||||
|
SideStory: sideStoryCatalog,
|
||||||
|
BigHunt: bigHuntCatalog,
|
||||||
|
QuestHandler: questHandler,
|
||||||
|
GachaHandler: gachaHandler,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
// Package runtime owns the live, hot-swappable view of master data.
|
||||||
|
//
|
||||||
|
// The Holder atomically swaps a *Catalogs aggregate every time the operator
|
||||||
|
// asks the server to re-read assets/release/20240404193219.bin.e (typically via
|
||||||
|
// the admin webhook in cmd/lunar-tear/admin.go). gRPC services hold a *Holder
|
||||||
|
// and call Get() at the start of each RPC, so they always see a consistent
|
||||||
|
// snapshot.
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/gacha"
|
||||||
|
"lunar-tear/server/internal/masterdata"
|
||||||
|
"lunar-tear/server/internal/masterdata/memorydb"
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/questflow"
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Catalogs is an immutable snapshot of every catalog and catalog-derived
|
||||||
|
// handler the server needs at runtime. A new *Catalogs is built from scratch
|
||||||
|
// on every reload and atomically published via Holder.
|
||||||
|
type Catalogs struct {
|
||||||
|
GameConfig *masterdata.GameConfig
|
||||||
|
Parts *masterdata.PartsCatalog
|
||||||
|
Quest *masterdata.QuestCatalog
|
||||||
|
GachaEntries []store.GachaCatalogEntry
|
||||||
|
GachaMedals map[int32]masterdata.GachaMedalInfo
|
||||||
|
GachaPool *masterdata.GachaCatalog
|
||||||
|
Shop *masterdata.ShopCatalog
|
||||||
|
DupExchange map[int32][]model.DupExchangeEntry
|
||||||
|
ConditionResolver *masterdata.ConditionResolver
|
||||||
|
CageOrnament *masterdata.CageOrnamentCatalog
|
||||||
|
LoginBonus *masterdata.LoginBonusCatalog
|
||||||
|
CharacterViewer *masterdata.CharacterViewerCatalog
|
||||||
|
Omikuji *masterdata.OmikujiCatalog
|
||||||
|
Material *masterdata.MaterialCatalog
|
||||||
|
ConsumableItem *masterdata.ConsumableItemCatalog
|
||||||
|
Costume *masterdata.CostumeCatalog
|
||||||
|
Weapon *masterdata.WeaponCatalog
|
||||||
|
Explore *masterdata.ExploreCatalog
|
||||||
|
Gimmick *masterdata.GimmickCatalog
|
||||||
|
CharacterBoard *masterdata.CharacterBoardCatalog
|
||||||
|
CharacterRebirth *masterdata.CharacterRebirthCatalog
|
||||||
|
Companion *masterdata.CompanionCatalog
|
||||||
|
SideStory *masterdata.SideStoryCatalog
|
||||||
|
BigHunt *masterdata.BigHuntCatalog
|
||||||
|
|
||||||
|
// Catalog-derived handlers must rebuild on every reload because they
|
||||||
|
// embed/cache pointers to specific catalog instances.
|
||||||
|
QuestHandler *questflow.QuestHandler
|
||||||
|
GachaHandler *gacha.GachaHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holder owns the current *Catalogs and the bin.e path. Concurrent readers
|
||||||
|
// call Get(); the single-writer Reload() rebuilds and atomically publishes.
|
||||||
|
type Holder struct {
|
||||||
|
binPath string
|
||||||
|
cur atomic.Pointer[Catalogs]
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHolder reads the binary at binPath, builds the initial catalogs, and
|
||||||
|
// returns a ready-to-use Holder. Subsequent calls to Reload() re-read the
|
||||||
|
// same path.
|
||||||
|
func NewHolder(binPath string) (*Holder, error) {
|
||||||
|
h := &Holder{binPath: binPath}
|
||||||
|
if err := h.Reload(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload re-reads the bin.e from disk, rebuilds every catalog and handler,
|
||||||
|
// atomically publishes the new snapshot, and bumps the bin.e mtime so client
|
||||||
|
// caches invalidate (see service/data.go GetLatestMasterDataVersion).
|
||||||
|
func (h *Holder) Reload() error {
|
||||||
|
if err := memorydb.Init(h.binPath); err != nil {
|
||||||
|
return fmt.Errorf("memorydb.Init: %w", err)
|
||||||
|
}
|
||||||
|
c, err := buildCatalogs()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("buildCatalogs: %w", err)
|
||||||
|
}
|
||||||
|
h.cur.Store(c)
|
||||||
|
now := time.Now()
|
||||||
|
if err := os.Chtimes(h.binPath, now, now); err != nil {
|
||||||
|
// Non-fatal: the catalogs swapped fine in-memory; clients may take
|
||||||
|
// longer to invalidate their cached download but server-side state is
|
||||||
|
// already coherent.
|
||||||
|
log.Printf("[runtime] os.Chtimes(%s) failed (clients may not invalidate cache): %v", h.binPath, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the current snapshot. Safe for concurrent callers; the returned
|
||||||
|
// pointer is stable for the duration of the caller's use.
|
||||||
|
func (h *Holder) Get() *Catalogs {
|
||||||
|
return h.cur.Load()
|
||||||
|
}
|
||||||
@@ -4,24 +4,29 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BannerServiceServer struct {
|
type BannerServiceServer struct {
|
||||||
pb.UnimplementedBannerServiceServer
|
pb.UnimplementedBannerServiceServer
|
||||||
catalog []store.GachaCatalogEntry
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBannerServiceServer(catalog []store.GachaCatalogEntry) *BannerServiceServer {
|
func NewBannerServiceServer(holder *runtime.Holder) *BannerServiceServer {
|
||||||
return &BannerServiceServer{catalog: catalog}
|
return &BannerServiceServer{holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BannerServiceServer) GetMamaBanner(ctx context.Context, req *pb.GetMamaBannerRequest) (*pb.GetMamaBannerResponse, error) {
|
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 termLimited []*pb.GachaBanner
|
||||||
var latestChapter *pb.GachaBanner
|
var latestChapter *pb.GachaBanner
|
||||||
for _, entry := range catalog {
|
for _, entry := range catalog {
|
||||||
|
if !gachaActiveAt(entry, nowMillis) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if entry.GachaLabelType == model.GachaLabelPortalCage || entry.GachaLabelType == model.GachaLabelRecycle {
|
if entry.GachaLabelType == model.GachaLabelPortalCage || entry.GachaLabelType == model.GachaLabelRecycle {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,21 +15,22 @@ type CageOrnamentServiceServer struct {
|
|||||||
pb.UnimplementedCageOrnamentServiceServer
|
pb.UnimplementedCageOrnamentServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.CageOrnamentCatalog
|
holder *runtime.Holder
|
||||||
granter *store.PossessionGranter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCageOrnamentServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CageOrnamentCatalog, granter *store.PossessionGranter) *CageOrnamentServiceServer {
|
func NewCageOrnamentServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CageOrnamentServiceServer {
|
||||||
return &CageOrnamentServiceServer{users: users, sessions: sessions, catalog: catalog, granter: granter}
|
return &CageOrnamentServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.ReceiveRewardRequest) (*pb.ReceiveRewardResponse, error) {
|
func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.ReceiveRewardRequest) (*pb.ReceiveRewardResponse, error) {
|
||||||
log.Printf("[CageOrnamentService] ReceiveReward: cageOrnamentId=%d", req.CageOrnamentId)
|
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 {
|
if !ok {
|
||||||
log.Fatalf("[CageOrnamentService] ReceiveReward: no reward for cageOrnamentId=%d", req.CageOrnamentId)
|
log.Fatalf("[CageOrnamentService] ReceiveReward: no reward for cageOrnamentId=%d", req.CageOrnamentId)
|
||||||
}
|
}
|
||||||
|
granter := cat.QuestHandler.Granter
|
||||||
|
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
@@ -39,7 +40,7 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
|
|||||||
AcquisitionDatetime: nowMillis,
|
AcquisitionDatetime: nowMillis,
|
||||||
LatestVersion: 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{
|
return &pb.ReceiveRewardResponse{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,21 +15,23 @@ type CharacterServiceServer struct {
|
|||||||
pb.UnimplementedCharacterServiceServer
|
pb.UnimplementedCharacterServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.CharacterRebirthCatalog
|
holder *runtime.Holder
|
||||||
config *masterdata.GameConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCharacterServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CharacterRebirthCatalog, config *masterdata.GameConfig) *CharacterServiceServer {
|
func NewCharacterServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CharacterServiceServer {
|
||||||
return &CharacterServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
return &CharacterServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthRequest) (*pb.RebirthResponse, error) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
stepGroupId, ok := s.catalog.StepGroupByCharacterId[req.CharacterId]
|
stepGroupId, ok := catalog.StepGroupByCharacterId[req.CharacterId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CharacterService] Rebirth: no step group for characterId=%d", req.CharacterId)
|
log.Printf("[CharacterService] Rebirth: no step group for characterId=%d", req.CharacterId)
|
||||||
return &pb.RebirthResponse{}, nil
|
return &pb.RebirthResponse{}, nil
|
||||||
@@ -40,17 +43,17 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
|
|||||||
targetCount := currentCount + req.RebirthCount
|
targetCount := currentCount + req.RebirthCount
|
||||||
|
|
||||||
for count := currentCount; count < targetCount; count++ {
|
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 {
|
if !ok {
|
||||||
log.Printf("[CharacterService] Rebirth: no step row for groupId=%d beforeCount=%d", stepGroupId, count)
|
log.Printf("[CharacterService] Rebirth: no step row for groupId=%d beforeCount=%d", stepGroupId, count)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
goldId := s.config.ConsumableItemIdForGold
|
goldId := config.ConsumableItemIdForGold
|
||||||
user.ConsumableItems[goldId] = max(user.ConsumableItems[goldId]-s.config.CharacterRebirthConsumeGold, 0)
|
user.ConsumableItems[goldId] = max(user.ConsumableItems[goldId]-config.CharacterRebirthConsumeGold, 0)
|
||||||
log.Printf("[CharacterService] Rebirth: consumed gold=%d", s.config.CharacterRebirthConsumeGold)
|
log.Printf("[CharacterService] Rebirth: consumed gold=%d", config.CharacterRebirthConsumeGold)
|
||||||
|
|
||||||
materials := s.catalog.MaterialsByGroupId[step.CharacterRebirthMaterialGroupId]
|
materials := catalog.MaterialsByGroupId[step.CharacterRebirthMaterialGroupId]
|
||||||
for _, mat := range materials {
|
for _, mat := range materials {
|
||||||
user.Materials[mat.MaterialId] -= mat.Count
|
user.Materials[mat.MaterialId] -= mat.Count
|
||||||
if user.Materials[mat.MaterialId] <= 0 {
|
if user.Materials[mat.MaterialId] <= 0 {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,43 +15,44 @@ type CharacterBoardServiceServer struct {
|
|||||||
pb.UnimplementedCharacterBoardServiceServer
|
pb.UnimplementedCharacterBoardServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.CharacterBoardCatalog
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCharacterBoardServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CharacterBoardCatalog) *CharacterBoardServiceServer {
|
func NewCharacterBoardServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CharacterBoardServiceServer {
|
||||||
return &CharacterBoardServiceServer{users: users, sessions: sessions, catalog: catalog}
|
return &CharacterBoardServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.ReleasePanelRequest) (*pb.ReleasePanelResponse, error) {
|
func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.ReleasePanelRequest) (*pb.ReleasePanelResponse, error) {
|
||||||
log.Printf("[CharacterBoardService] ReleasePanel: panelIds=%v", req.CharacterBoardPanelId)
|
log.Printf("[CharacterBoardService] ReleasePanel: panelIds=%v", req.CharacterBoardPanelId)
|
||||||
|
|
||||||
|
catalog := s.holder.Get().CharacterBoard
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for _, panelId := range req.CharacterBoardPanelId {
|
for _, panelId := range req.CharacterBoardPanelId {
|
||||||
panel, ok := s.catalog.PanelById[panelId]
|
panel, ok := catalog.PanelById[panelId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CharacterBoardService] unknown panelId=%d, skipping", panelId)
|
log.Printf("[CharacterBoardService] unknown panelId=%d, skipping", panelId)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
s.consumeCosts(user, panel)
|
consumeBoardCosts(catalog, user, panel)
|
||||||
s.setReleaseBit(user, panel)
|
setBoardReleaseBit(user, panel)
|
||||||
s.applyEffects(user, panel)
|
applyBoardEffects(catalog, user, panel)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.ReleasePanelResponse{}, nil
|
return &pb.ReleasePanelResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
func consumeBoardCosts(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||||
costs := s.catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId]
|
costs := catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId]
|
||||||
for _, cost := range costs {
|
for _, cost := range costs {
|
||||||
store.DeductPossession(user, model.PossessionType(cost.PossessionType), cost.PossessionId, cost.Count)
|
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
|
boardId := panel.CharacterBoardId
|
||||||
board := user.CharacterBoards[boardId]
|
board := user.CharacterBoards[boardId]
|
||||||
board.CharacterBoardId = boardId
|
board.CharacterBoardId = boardId
|
||||||
@@ -73,26 +75,26 @@ func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel
|
|||||||
user.CharacterBoards[boardId] = board
|
user.CharacterBoards[boardId] = board
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
func applyBoardEffects(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||||
effects := s.catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId]
|
effects := catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId]
|
||||||
for _, eff := range effects {
|
for _, eff := range effects {
|
||||||
switch model.CharacterBoardEffectType(eff.CharacterBoardEffectType) {
|
switch model.CharacterBoardEffectType(eff.CharacterBoardEffectType) {
|
||||||
case model.CharacterBoardEffectTypeAbility:
|
case model.CharacterBoardEffectTypeAbility:
|
||||||
s.applyAbilityEffect(user, eff)
|
applyBoardAbilityEffect(catalog, user, eff)
|
||||||
case model.CharacterBoardEffectTypeStatusUp:
|
case model.CharacterBoardEffectTypeStatusUp:
|
||||||
s.applyStatusUpEffect(user, eff)
|
applyBoardStatusUpEffect(catalog, user, eff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
func applyBoardAbilityEffect(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||||
ability, ok := s.catalog.AbilityById[eff.CharacterBoardEffectId]
|
ability, ok := catalog.AbilityById[eff.CharacterBoardEffectId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CharacterBoardService] unknown abilityId=%d", eff.CharacterBoardEffectId)
|
log.Printf("[CharacterBoardService] unknown abilityId=%d", eff.CharacterBoardEffectId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
characterId := s.resolveCharacterId(ability.CharacterBoardEffectTargetGroupId)
|
characterId := resolveBoardCharacterId(catalog, ability.CharacterBoardEffectTargetGroupId)
|
||||||
if characterId == 0 {
|
if characterId == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -103,21 +105,21 @@ func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState,
|
|||||||
state.AbilityId = ability.AbilityId
|
state.AbilityId = ability.AbilityId
|
||||||
state.Level += eff.EffectValue
|
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
|
state.Level = maxLvl
|
||||||
}
|
}
|
||||||
|
|
||||||
user.CharacterBoardAbilities[key] = state
|
user.CharacterBoardAbilities[key] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
func applyBoardStatusUpEffect(catalog *masterdata.CharacterBoardCatalog, user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||||
statusUp, ok := s.catalog.StatusUpById[eff.CharacterBoardEffectId]
|
statusUp, ok := catalog.StatusUpById[eff.CharacterBoardEffectId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CharacterBoardService] unknown statusUpId=%d", eff.CharacterBoardEffectId)
|
log.Printf("[CharacterBoardService] unknown statusUpId=%d", eff.CharacterBoardEffectId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
characterId := s.resolveCharacterId(statusUp.CharacterBoardEffectTargetGroupId)
|
characterId := resolveBoardCharacterId(catalog, statusUp.CharacterBoardEffectTargetGroupId)
|
||||||
if characterId == 0 {
|
if characterId == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -151,8 +153,8 @@ func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState,
|
|||||||
user.CharacterBoardStatusUps[key] = state
|
user.CharacterBoardStatusUps[key] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) resolveCharacterId(targetGroupId int32) int32 {
|
func resolveBoardCharacterId(catalog *masterdata.CharacterBoardCatalog, targetGroupId int32) int32 {
|
||||||
targets := s.catalog.EffectTargetsByGroupId[targetGroupId]
|
targets := catalog.EffectTargetsByGroupId[targetGroupId]
|
||||||
for _, t := range targets {
|
for _, t := range targets {
|
||||||
if t.TargetValue != 0 {
|
if t.TargetValue != 0 {
|
||||||
return t.TargetValue
|
return t.TargetValue
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -16,11 +16,11 @@ type CharacterViewerServiceServer struct {
|
|||||||
pb.UnimplementedCharacterViewerServiceServer
|
pb.UnimplementedCharacterViewerServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.CharacterViewerCatalog
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCharacterViewerServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CharacterViewerCatalog) *CharacterViewerServiceServer {
|
func NewCharacterViewerServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CharacterViewerServiceServer {
|
||||||
return &CharacterViewerServiceServer{users: users, sessions: sessions, catalog: catalog}
|
return &CharacterViewerServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _ *emptypb.Empty) (*pb.CharacterViewerTopResponse, error) {
|
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))
|
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)
|
log.Printf("[CharacterViewerService] released %d fields for user %d", len(released), userId)
|
||||||
|
|
||||||
return &pb.CharacterViewerTopResponse{
|
return &pb.CharacterViewerTopResponse{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,17 +18,19 @@ type CompanionServiceServer struct {
|
|||||||
pb.UnimplementedCompanionServiceServer
|
pb.UnimplementedCompanionServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.CompanionCatalog
|
holder *runtime.Holder
|
||||||
config *masterdata.GameConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCompanionServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CompanionCatalog, config *masterdata.GameConfig) *CompanionServiceServer {
|
func NewCompanionServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CompanionServiceServer {
|
||||||
return &CompanionServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
return &CompanionServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionEnhanceRequest) (*pb.CompanionEnhanceResponse, error) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -38,7 +41,7 @@ func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionE
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
compDef, ok := s.catalog.CompanionById[companion.CompanionId]
|
compDef, ok := catalog.CompanionById[companion.CompanionId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CompanionService] Enhance: companion master id=%d not found", companion.CompanionId)
|
log.Printf("[CompanionService] Enhance: companion master id=%d not found", companion.CompanionId)
|
||||||
return
|
return
|
||||||
@@ -50,13 +53,13 @@ func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionE
|
|||||||
}
|
}
|
||||||
|
|
||||||
for lvl := companion.Level; lvl < targetLevel; lvl++ {
|
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)
|
goldCost := costFunc.Evaluate(lvl)
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||||
}
|
}
|
||||||
|
|
||||||
matKey := masterdata.CompanionLevelKey{CategoryType: compDef.CompanionCategoryType, Level: lvl}
|
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
|
user.Materials[mat.MaterialId] -= mat.Count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,23 +14,25 @@ type ConsumableItemServiceServer struct {
|
|||||||
pb.UnimplementedConsumableItemServiceServer
|
pb.UnimplementedConsumableItemServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.ConsumableItemCatalog
|
holder *runtime.Holder
|
||||||
config *masterdata.GameConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConsumableItemServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.ConsumableItemCatalog, config *masterdata.GameConfig) *ConsumableItemServiceServer {
|
func NewConsumableItemServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *ConsumableItemServiceServer {
|
||||||
return &ConsumableItemServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
return &ConsumableItemServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) {
|
func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) {
|
||||||
log.Printf("[ConsumableItemService] Sell: %d item(s)", len(req.ConsumableItemPossession))
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
totalGold := int32(0)
|
totalGold := int32(0)
|
||||||
for _, item := range req.ConsumableItemPossession {
|
for _, item := range req.ConsumableItemPossession {
|
||||||
row, ok := s.catalog.All[item.ConsumableItemId]
|
row, ok := catalog.All[item.ConsumableItemId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[ConsumableItemService] Sell: unknown consumableItemId=%d, skipping", item.ConsumableItemId)
|
log.Printf("[ConsumableItemService] Sell: unknown consumableItemId=%d, skipping", item.ConsumableItemId)
|
||||||
continue
|
continue
|
||||||
@@ -53,7 +55,7 @@ func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.Consumab
|
|||||||
}
|
}
|
||||||
|
|
||||||
if totalGold > 0 {
|
if totalGold > 0 {
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||||
log.Printf("[ConsumableItemService] Sell: total gold +%d", totalGold)
|
log.Printf("[ConsumableItemService] Sell: total gold +%d", totalGold)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gameutil"
|
"lunar-tear/server/internal/gameutil"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,17 +21,19 @@ type CostumeServiceServer struct {
|
|||||||
pb.UnimplementedCostumeServiceServer
|
pb.UnimplementedCostumeServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.CostumeCatalog
|
holder *runtime.Holder
|
||||||
config *masterdata.GameConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCostumeServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.CostumeCatalog, config *masterdata.GameConfig) *CostumeServiceServer {
|
func NewCostumeServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *CostumeServiceServer {
|
||||||
return &CostumeServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
return &CostumeServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceRequest) (*pb.EnhanceResponse, error) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -41,7 +44,7 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cm, ok := s.catalog.Costumes[costume.CostumeId]
|
cm, ok := catalog.Costumes[costume.CostumeId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] Enhance: costume master id=%d not found", costume.CostumeId)
|
log.Printf("[CostumeService] Enhance: costume master id=%d not found", costume.CostumeId)
|
||||||
return
|
return
|
||||||
@@ -50,7 +53,7 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
|||||||
totalExp := int32(0)
|
totalExp := int32(0)
|
||||||
totalMaterialCount := int32(0)
|
totalMaterialCount := int32(0)
|
||||||
for materialId, count := range req.Materials {
|
for materialId, count := range req.Materials {
|
||||||
mat, ok := s.catalog.Materials[materialId]
|
mat, ok := catalog.Materials[materialId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] Enhance: material id=%d not found, skipping", materialId)
|
log.Printf("[CostumeService] Enhance: material id=%d not found, skipping", materialId)
|
||||||
continue
|
continue
|
||||||
@@ -66,20 +69,20 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
|||||||
|
|
||||||
expPerUnit := mat.EffectValue
|
expPerUnit := mat.EffectValue
|
||||||
if mat.WeaponType != 0 && mat.WeaponType == cm.SkillfulWeaponType {
|
if mat.WeaponType != 0 && mat.WeaponType == cm.SkillfulWeaponType {
|
||||||
expPerUnit = expPerUnit * s.config.MaterialSameWeaponExpCoefficientPermil / 1000
|
expPerUnit = expPerUnit * config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||||
}
|
}
|
||||||
totalExp += expPerUnit * count
|
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)
|
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)
|
log.Printf("[CostumeService] Enhance: gold cost=%d (materials=%d)", goldCost, totalMaterialCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
costume.Exp += totalExp
|
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)
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -110,7 +116,7 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
awakenRow, ok := s.catalog.AwakenByCostumeId[costume.CostumeId]
|
awakenRow, ok := catalog.AwakenByCostumeId[costume.CostumeId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] Awaken: no awaken data for costumeId=%d", costume.CostumeId)
|
log.Printf("[CostumeService] Awaken: no awaken data for costumeId=%d", costume.CostumeId)
|
||||||
return
|
return
|
||||||
@@ -118,8 +124,8 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
|||||||
|
|
||||||
nextStep := costume.AwakenCount + 1
|
nextStep := costume.AwakenCount + 1
|
||||||
|
|
||||||
if gold, ok := s.catalog.AwakenPriceByGroup[awakenRow.CostumeAwakenPriceGroupId]; ok {
|
if gold, ok := catalog.AwakenPriceByGroup[awakenRow.CostumeAwakenPriceGroupId]; ok {
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= gold
|
user.ConsumableItems[config.ConsumableItemIdForGold] -= gold
|
||||||
log.Printf("[CostumeService] Awaken: gold cost=%d", 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
|
user.Costumes[req.UserCostumeUuid] = costume
|
||||||
log.Printf("[CostumeService] Awaken: costumeId=%d awakenCount=%d", costume.CostumeId, nextStep)
|
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 {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -148,11 +154,11 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
|||||||
|
|
||||||
switch model.CostumeAwakenEffectType(effect.CostumeAwakenEffectType) {
|
switch model.CostumeAwakenEffectType(effect.CostumeAwakenEffectType) {
|
||||||
case model.CostumeAwakenEffectTypeStatusUp:
|
case model.CostumeAwakenEffectTypeStatusUp:
|
||||||
s.applyAwakenStatusUp(user, req.UserCostumeUuid, effect.CostumeAwakenEffectId, nowMillis)
|
applyCostumeAwakenStatusUp(catalog, user, req.UserCostumeUuid, effect.CostumeAwakenEffectId, nowMillis)
|
||||||
case model.CostumeAwakenEffectTypeAbility:
|
case model.CostumeAwakenEffectTypeAbility:
|
||||||
log.Printf("[CostumeService] Awaken: ability effect id=%d (client-resolved)", effect.CostumeAwakenEffectId)
|
log.Printf("[CostumeService] Awaken: ability effect id=%d (client-resolved)", effect.CostumeAwakenEffectId)
|
||||||
case model.CostumeAwakenEffectTypeItemAcquire:
|
case model.CostumeAwakenEffectTypeItemAcquire:
|
||||||
s.applyAwakenItemAcquire(user, effect.CostumeAwakenEffectId, nowMillis)
|
applyCostumeAwakenItemAcquire(catalog, user, effect.CostumeAwakenEffectId, nowMillis)
|
||||||
default:
|
default:
|
||||||
log.Printf("[CostumeService] Awaken: unknown effect type=%d", effect.CostumeAwakenEffectType)
|
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
|
return &pb.AwakenResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) {
|
func applyCostumeAwakenStatusUp(catalog *masterdata.CostumeCatalog, user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) {
|
||||||
rows, ok := s.catalog.AwakenStatusUpByGroup[statusUpGroupId]
|
rows, ok := catalog.AwakenStatusUpByGroup[statusUpGroupId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] Awaken: status up group %d not found", statusUpGroupId)
|
log.Printf("[CostumeService] Awaken: status up group %d not found", statusUpGroupId)
|
||||||
return
|
return
|
||||||
@@ -201,8 +207,8 @@ func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costum
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) applyAwakenItemAcquire(user *store.UserState, itemAcquireId int32, nowMillis int64) {
|
func applyCostumeAwakenItemAcquire(catalog *masterdata.CostumeCatalog, user *store.UserState, itemAcquireId int32, nowMillis int64) {
|
||||||
acq, ok := s.catalog.AwakenItemAcquireById[itemAcquireId]
|
acq, ok := catalog.AwakenItemAcquireById[itemAcquireId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] Awaken: item acquire id=%d not found", itemAcquireId)
|
log.Printf("[CostumeService] Awaken: item acquire id=%d not found", itemAcquireId)
|
||||||
return
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -236,13 +245,13 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cm, ok := s.catalog.Costumes[costume.CostumeId]
|
cm, ok := catalog.Costumes[costume.CostumeId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] EnhanceActiveSkill: costume master id=%d not found", costume.CostumeId)
|
log.Printf("[CostumeService] EnhanceActiveSkill: costume master id=%d not found", costume.CostumeId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
groupRows := s.catalog.ActiveSkillGroupsByGroupId[cm.CostumeActiveSkillGroupId]
|
groupRows := catalog.ActiveSkillGroupsByGroupId[cm.CostumeActiveSkillGroupId]
|
||||||
enhanceMatId := int32(-1)
|
enhanceMatId := int32(-1)
|
||||||
for _, g := range groupRows {
|
for _, g := range groupRows {
|
||||||
if g.CostumeLimitBreakCountLowerLimit <= costume.LimitBreakCount {
|
if g.CostumeLimitBreakCountLowerLimit <= costume.LimitBreakCount {
|
||||||
@@ -259,7 +268,7 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
|||||||
skill := user.CostumeActiveSkills[req.UserCostumeUuid]
|
skill := user.CostumeActiveSkills[req.UserCostumeUuid]
|
||||||
currentLevel := skill.Level
|
currentLevel := skill.Level
|
||||||
|
|
||||||
maxLevelFunc, ok := s.catalog.ActiveSkillMaxLevelByRarity[cm.RarityType]
|
maxLevelFunc, ok := catalog.ActiveSkillMaxLevelByRarity[cm.RarityType]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] EnhanceActiveSkill: no max level func for rarity=%d", cm.RarityType)
|
log.Printf("[CostumeService] EnhanceActiveSkill: no max level func for rarity=%d", cm.RarityType)
|
||||||
return
|
return
|
||||||
@@ -277,7 +286,7 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
|||||||
|
|
||||||
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
||||||
key := [2]int32{enhanceMatId, lvl}
|
key := [2]int32{enhanceMatId, lvl}
|
||||||
mats := s.catalog.ActiveSkillEnhanceMats[key]
|
mats := catalog.ActiveSkillEnhanceMats[key]
|
||||||
for _, mat := range mats {
|
for _, mat := range mats {
|
||||||
cur := user.Materials[mat.MaterialId]
|
cur := user.Materials[mat.MaterialId]
|
||||||
cost := mat.Count
|
cost := mat.Count
|
||||||
@@ -288,9 +297,9 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
|||||||
user.Materials[mat.MaterialId] = cur - cost
|
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)
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -320,12 +332,12 @@ func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBrea
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if costume.LimitBreakCount >= s.config.CostumeLimitBreakAvailableCount {
|
if costume.LimitBreakCount >= config.CostumeLimitBreakAvailableCount {
|
||||||
log.Printf("[CostumeService] LimitBreak: already at max limit break %d", costume.LimitBreakCount)
|
log.Printf("[CostumeService] LimitBreak: already at max limit break %d", costume.LimitBreakCount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cm, ok := s.catalog.Costumes[costume.CostumeId]
|
cm, ok := catalog.Costumes[costume.CostumeId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] LimitBreak: costume master id=%d not found", costume.CostumeId)
|
log.Printf("[CostumeService] LimitBreak: costume master id=%d not found", costume.CostumeId)
|
||||||
return
|
return
|
||||||
@@ -342,9 +354,9 @@ func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBrea
|
|||||||
totalMaterialCount += count
|
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)
|
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||||
log.Printf("[CostumeService] LimitBreak: gold cost=%d", 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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -373,15 +388,15 @@ func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
effectRow, ok := s.catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
effectRow, ok := catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] UnlockLotteryEffectSlot: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
||||||
return
|
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 {
|
for _, mat := range mats {
|
||||||
cur := user.Materials[mat.MaterialId]
|
cur := user.Materials[mat.MaterialId]
|
||||||
cost := mat.Count
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -428,21 +446,21 @@ func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.Dr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
effectRow, ok := s.catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
effectRow, ok := catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] DrawLotteryEffect: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
log.Printf("[CostumeService] DrawLotteryEffect: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oddsPool := s.catalog.LotteryEffectOdds[effectRow.CostumeLotteryEffectOddsGroupId]
|
oddsPool := catalog.LotteryEffectOdds[effectRow.CostumeLotteryEffectOddsGroupId]
|
||||||
if len(oddsPool) == 0 {
|
if len(oddsPool) == 0 {
|
||||||
log.Printf("[CostumeService] DrawLotteryEffect: empty odds pool for groupId=%d", effectRow.CostumeLotteryEffectOddsGroupId)
|
log.Printf("[CostumeService] DrawLotteryEffect: empty odds pool for groupId=%d", effectRow.CostumeLotteryEffectOddsGroupId)
|
||||||
return
|
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 {
|
for _, mat := range mats {
|
||||||
cur := user.Materials[mat.MaterialId]
|
cur := user.Materials[mat.MaterialId]
|
||||||
cost := mat.Count
|
cost := mat.Count
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
@@ -12,6 +13,16 @@ import (
|
|||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"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 {
|
type DataServiceServer struct {
|
||||||
pb.UnimplementedDataServiceServer
|
pb.UnimplementedDataServiceServer
|
||||||
users store.UserRepository
|
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) {
|
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{
|
return &pb.MasterDataGetLatestVersionResponse{
|
||||||
LatestMasterDataVersion: "20240404193219",
|
LatestMasterDataVersion: version,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,17 +22,18 @@ type ExploreServiceServer struct {
|
|||||||
pb.UnimplementedExploreServiceServer
|
pb.UnimplementedExploreServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.ExploreCatalog
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewExploreServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.ExploreCatalog) *ExploreServiceServer {
|
func NewExploreServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *ExploreServiceServer {
|
||||||
return &ExploreServiceServer{users: users, sessions: sessions, catalog: catalog}
|
return &ExploreServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartExploreRequest) (*pb.StartExploreResponse, error) {
|
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)
|
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)
|
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()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, 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 {
|
if req.UseConsumableItemId > 0 && explore.ConsumeItemCount > 0 {
|
||||||
cur := user.ConsumableItems[req.UseConsumableItemId]
|
cur := user.ConsumableItems[req.UseConsumableItemId]
|
||||||
user.ConsumableItems[req.UseConsumableItemId] = cur - explore.ConsumeItemCount
|
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) {
|
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)
|
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 {
|
if !ok {
|
||||||
return nil, fmt.Errorf("explore id=%d not found", req.ExploreId)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gacha"
|
"lunar-tear/server/internal/gacha"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -20,34 +21,33 @@ type GachaServiceServer struct {
|
|||||||
pb.UnimplementedGachaServiceServer
|
pb.UnimplementedGachaServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog []store.GachaCatalogEntry
|
holder *runtime.Holder
|
||||||
handler *gacha.GachaHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGachaServiceServer(
|
func NewGachaServiceServer(
|
||||||
users store.UserRepository,
|
users store.UserRepository,
|
||||||
sessions store.SessionRepository,
|
sessions store.SessionRepository,
|
||||||
catalog []store.GachaCatalogEntry,
|
holder *runtime.Holder,
|
||||||
handler *gacha.GachaHandler,
|
|
||||||
) *GachaServiceServer {
|
) *GachaServiceServer {
|
||||||
return &GachaServiceServer{
|
return &GachaServiceServer{
|
||||||
users: users,
|
users: users,
|
||||||
sessions: sessions,
|
sessions: sessions,
|
||||||
catalog: catalog,
|
holder: holder,
|
||||||
handler: handler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaListRequest) (*pb.GetGachaListResponse, error) {
|
func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaListRequest) (*pb.GetGachaListResponse, error) {
|
||||||
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
user, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
user, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.EnsureMaps()
|
user.EnsureMaps()
|
||||||
s.autoConvertExpiredMedals(user, catalog, nowMillis)
|
autoConvertExpiredMedals(user, catalog, handler, nowMillis)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("update user: %w", err)
|
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))
|
gachaList := make([]*pb.Gacha, 0, len(catalog))
|
||||||
for _, entry := range catalog {
|
for _, entry := range catalog {
|
||||||
|
if !gachaActiveAt(entry, nowMillis) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !matchesGachaLabel(req.GachaLabelType, entry.GachaLabelType) {
|
if !matchesGachaLabel(req.GachaLabelType, entry.GachaLabelType) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -71,7 +74,7 @@ func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaL
|
|||||||
}, nil
|
}, 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 {
|
for _, entry := range catalog {
|
||||||
if entry.GachaMedalId == 0 || entry.EndDatetime == 0 {
|
if entry.GachaMedalId == 0 || entry.EndDatetime == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -84,7 +87,7 @@ func (s *GachaServiceServer) autoConvertExpiredMedals(user *store.UserState, cat
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
medalInfo, ok := s.handler.MedalInfo[entry.GachaId]
|
medalInfo, ok := handler.MedalInfo[entry.GachaId]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
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) {
|
func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaRequest) (*pb.GetGachaResponse, error) {
|
||||||
log.Printf("[GachaService] GetGacha: ids=%v", req.GachaId)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
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))
|
byId := make(map[int32]*pb.Gacha, len(req.GachaId))
|
||||||
for _, wantedId := range req.GachaId {
|
for _, wantedId := range req.GachaId {
|
||||||
for _, entry := range catalog {
|
for _, entry := range catalog {
|
||||||
if entry.GachaId == wantedId {
|
if entry.GachaId != wantedId {
|
||||||
bs := user.Gacha.BannerStates[entry.GachaId]
|
continue
|
||||||
byId[wantedId] = toProtoGacha(entry, &bs)
|
}
|
||||||
|
if !gachaActiveAt(entry, nowMillis) {
|
||||||
break
|
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) {
|
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)
|
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 {
|
if entry == nil {
|
||||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||||
}
|
}
|
||||||
|
handler := cat.GachaHandler
|
||||||
|
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
execCount := req.ExecCount
|
execCount := req.ExecCount
|
||||||
@@ -158,7 +168,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
|
|||||||
var drawResult *gacha.DrawResult
|
var drawResult *gacha.DrawResult
|
||||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
var drawErr error
|
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 {
|
if drawErr != nil {
|
||||||
log.Printf("[GachaService] Draw error: %v", drawErr)
|
log.Printf("[GachaService] Draw error: %v", drawErr)
|
||||||
drawResult = &gacha.DrawResult{}
|
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) {
|
func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBoxGachaRequest) (*pb.ResetBoxGachaResponse, error) {
|
||||||
log.Printf("[GachaService] ResetBoxGacha: gachaId=%d", req.GachaId)
|
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 {
|
if entry == nil {
|
||||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||||
}
|
}
|
||||||
|
handler := cat.GachaHandler
|
||||||
|
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
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)
|
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)
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
maxCount := s.handler.Config.RewardGachaDailyMaxCount
|
maxCount := s.holder.Get().GachaHandler.Config.RewardGachaDailyMaxCount
|
||||||
if maxCount <= 0 {
|
if maxCount <= 0 {
|
||||||
maxCount = model.DefaultDailyDrawLimit
|
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)
|
log.Printf("[GachaService] RewardDraw: placement=%q reward=%q amount=%q", req.PlacementName, req.RewardName, req.RewardAmount)
|
||||||
|
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
handler := s.holder.Get().GachaHandler
|
||||||
|
|
||||||
var items []gacha.DrawnItem
|
var items []gacha.DrawnItem
|
||||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
var drawErr error
|
var drawErr error
|
||||||
items, drawErr = s.handler.HandleRewardDraw(user, 1)
|
items, drawErr = handler.HandleRewardDraw(user, 1)
|
||||||
if drawErr != nil {
|
if drawErr != nil {
|
||||||
log.Printf("[GachaService] RewardDraw error: %v", drawErr)
|
log.Printf("[GachaService] RewardDraw error: %v", drawErr)
|
||||||
}
|
}
|
||||||
@@ -395,6 +408,16 @@ func matchesGachaLabel(labels []int32, label int32) bool {
|
|||||||
return false
|
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 {
|
func toProtoGacha(entry store.GachaCatalogEntry, bs *store.GachaBannerState) *pb.Gacha {
|
||||||
g := &pb.Gacha{
|
g := &pb.Gacha{
|
||||||
GachaId: entry.GachaId,
|
GachaId: entry.GachaId,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -14,13 +14,13 @@ import (
|
|||||||
|
|
||||||
type GimmickServiceServer struct {
|
type GimmickServiceServer struct {
|
||||||
pb.UnimplementedGimmickServiceServer
|
pb.UnimplementedGimmickServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
gimmickCatalog *masterdata.GimmickCatalog
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGimmickServiceServer(users store.UserRepository, sessions store.SessionRepository, gimmickCatalog *masterdata.GimmickCatalog) *GimmickServiceServer {
|
func NewGimmickServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *GimmickServiceServer {
|
||||||
return &GimmickServiceServer{users: users, sessions: sessions, gimmickCatalog: gimmickCatalog}
|
return &GimmickServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.UpdateSequenceRequest) (*pb.UpdateSequenceResponse, error) {
|
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()
|
now := gametime.NowMillis()
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
added := 0
|
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 {
|
if _, exists := user.Gimmick.Sequences[key]; !exists {
|
||||||
user.Gimmick.Sequences[key] = store.GimmickSequenceState{Key: key}
|
user.Gimmick.Sequences[key] = store.GimmickSequenceState{Key: key}
|
||||||
added++
|
added++
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -19,22 +19,23 @@ type LoginBonusServiceServer struct {
|
|||||||
pb.UnimplementedLoginBonusServiceServer
|
pb.UnimplementedLoginBonusServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.LoginBonusCatalog
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoginBonusServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.LoginBonusCatalog) *LoginBonusServiceServer {
|
func NewLoginBonusServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *LoginBonusServiceServer {
|
||||||
return &LoginBonusServiceServer{users: users, sessions: sessions, catalog: catalog}
|
return &LoginBonusServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb.Empty) (*pb.ReceiveStampResponse, error) {
|
func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb.Empty) (*pb.ReceiveStampResponse, error) {
|
||||||
log.Printf("[LoginBonusService] ReceiveStamp")
|
log.Printf("[LoginBonusService] ReceiveStamp")
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
catalog := s.holder.Get().LoginBonus
|
||||||
|
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
now := gametime.NowMillis()
|
now := gametime.NowMillis()
|
||||||
nextStamp := user.LoginBonus.CurrentStampNumber + 1
|
nextStamp := user.LoginBonus.CurrentStampNumber + 1
|
||||||
|
|
||||||
reward, ok := s.catalog.LookupStampReward(
|
reward, ok := catalog.LookupStampReward(
|
||||||
user.LoginBonus.LoginBonusId,
|
user.LoginBonus.LoginBonusId,
|
||||||
user.LoginBonus.CurrentPageNumber,
|
user.LoginBonus.CurrentPageNumber,
|
||||||
nextStamp,
|
nextStamp,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,23 +14,25 @@ type MaterialServiceServer struct {
|
|||||||
pb.UnimplementedMaterialServiceServer
|
pb.UnimplementedMaterialServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.MaterialCatalog
|
holder *runtime.Holder
|
||||||
config *masterdata.GameConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMaterialServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.MaterialCatalog, config *masterdata.GameConfig) *MaterialServiceServer {
|
func NewMaterialServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *MaterialServiceServer {
|
||||||
return &MaterialServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
return &MaterialServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRequest) (*pb.MaterialSellResponse, error) {
|
func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRequest) (*pb.MaterialSellResponse, error) {
|
||||||
log.Printf("[MaterialService] Sell: %d item(s)", len(req.MaterialPossession))
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
totalGold := int32(0)
|
totalGold := int32(0)
|
||||||
for _, item := range req.MaterialPossession {
|
for _, item := range req.MaterialPossession {
|
||||||
mat, ok := s.catalog.All[item.MaterialId]
|
mat, ok := catalog.All[item.MaterialId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[MaterialService] Sell: unknown materialId=%d, skipping", item.MaterialId)
|
log.Printf("[MaterialService] Sell: unknown materialId=%d, skipping", item.MaterialId)
|
||||||
continue
|
continue
|
||||||
@@ -53,7 +55,7 @@ func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if totalGold > 0 {
|
if totalGold > 0 {
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||||
log.Printf("[MaterialService] Sell: total gold +%d", totalGold)
|
log.Printf("[MaterialService] Sell: total gold +%d", totalGold)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -414,24 +414,12 @@ func (s *OctoHTTPServer) serveListBin(w http.ResponseWriter, filePath string) {
|
|||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveDatabaseBinE serves MasterMemory database: /assets/release/{version}/database.bin.e
|
// serveDatabaseBinE serves the master data binary. The URL's {version} segment
|
||||||
// -> assets/release/{version}.bin.e (or assets/release/database.bin.e fallback).
|
// is a cache key (it changes whenever the file's mtime changes, see
|
||||||
func (s *OctoHTTPServer) serveDatabaseBinE(w http.ResponseWriter, r *http.Request, path string) {
|
// DataService.GetLatestMasterDataVersion) but does not select a different file —
|
||||||
parts := strings.Split(path, "/")
|
// there's only ever one bin.e on disk.
|
||||||
var version string
|
func (s *OctoHTTPServer) serveDatabaseBinE(w http.ResponseWriter, r *http.Request, _ string) {
|
||||||
for i, p := range parts {
|
filePath := filepath.Join(s.BaseDir, "assets", "release", "20240404193219.bin.e")
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
http.ServeFile(w, r, filePath)
|
http.ServeFile(w, r, filePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,11 +15,11 @@ type OmikujiServiceServer struct {
|
|||||||
pb.UnimplementedOmikujiServiceServer
|
pb.UnimplementedOmikujiServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.OmikujiCatalog
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOmikujiServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.OmikujiCatalog) *OmikujiServiceServer {
|
func NewOmikujiServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *OmikujiServiceServer {
|
||||||
return &OmikujiServiceServer{users: users, sessions: sessions, catalog: catalog}
|
return &OmikujiServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiDrawRequest) (*pb.OmikujiDrawResponse, error) {
|
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{
|
return &pb.OmikujiDrawResponse{
|
||||||
OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId),
|
OmikujiResultAssetId: s.holder.Get().Omikuji.LookupAssetId(req.OmikujiId),
|
||||||
OmikujiItem: []*pb.OmikujiItem{},
|
OmikujiItem: []*pb.OmikujiItem{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,17 +19,19 @@ type PartsServiceServer struct {
|
|||||||
pb.UnimplementedPartsServiceServer
|
pb.UnimplementedPartsServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.PartsCatalog
|
holder *runtime.Holder
|
||||||
config *masterdata.GameConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPartsServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.PartsCatalog, config *masterdata.GameConfig) *PartsServiceServer {
|
func NewPartsServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *PartsServiceServer {
|
||||||
return &PartsServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
return &PartsServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) {
|
func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) {
|
||||||
log.Printf("[PartsService] Sell: %d part(s)", len(req.UserPartsUuid))
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
@@ -44,13 +47,13 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
partDef, ok := s.catalog.PartsById[part.PartsId]
|
partDef, ok := catalog.PartsById[part.PartsId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[PartsService] Sell: partsId=%d not in catalog, skipping", part.PartsId)
|
log.Printf("[PartsService] Sell: partsId=%d not in catalog, skipping", part.PartsId)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sellFunc, ok := s.catalog.SellPriceByRarity[partDef.RarityType]
|
sellFunc, ok := catalog.SellPriceByRarity[partDef.RarityType]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[PartsService] Sell: no sell price func for rarity=%d, skipping", partDef.RarityType)
|
log.Printf("[PartsService] Sell: no sell price func for rarity=%d, skipping", partDef.RarityType)
|
||||||
continue
|
continue
|
||||||
@@ -68,7 +71,7 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if totalGold > 0 {
|
if totalGold > 0 {
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||||
log.Printf("[PartsService] Sell: total gold +%d", 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) {
|
func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRequest) (*pb.PartsEnhanceResponse, error) {
|
||||||
log.Printf("[PartsService] Enhance: uuid=%s", req.UserPartsUuid)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -99,33 +105,33 @@ func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
partDef, ok := s.catalog.PartsById[part.PartsId]
|
partDef, ok := catalog.PartsById[part.PartsId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[PartsService] Enhance: part master id=%d not found", part.PartsId)
|
log.Printf("[PartsService] Enhance: part master id=%d not found", part.PartsId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rarity, ok := s.catalog.RarityByRarityType[partDef.RarityType]
|
rarity, ok := catalog.RarityByRarityType[partDef.RarityType]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[PartsService] Enhance: rarity type=%d not found", partDef.RarityType)
|
log.Printf("[PartsService] Enhance: rarity type=%d not found", partDef.RarityType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
goldCost := int32(0)
|
goldCost := int32(0)
|
||||||
if prices, ok := s.catalog.PriceByGroupAndLevel[rarity.PartsLevelUpPriceGroupId]; ok {
|
if prices, ok := catalog.PriceByGroupAndLevel[rarity.PartsLevelUpPriceGroupId]; ok {
|
||||||
goldCost = prices[part.Level]
|
goldCost = prices[part.Level]
|
||||||
}
|
}
|
||||||
|
|
||||||
currentGold := user.ConsumableItems[s.config.ConsumableItemIdForGold]
|
currentGold := user.ConsumableItems[config.ConsumableItemIdForGold]
|
||||||
if currentGold < goldCost {
|
if currentGold < goldCost {
|
||||||
log.Printf("[PartsService] Enhance: insufficient gold have=%d need=%d", currentGold, goldCost)
|
log.Printf("[PartsService] Enhance: insufficient gold have=%d need=%d", currentGold, goldCost)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||||
|
|
||||||
successRate := int32(1000)
|
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 {
|
if r, ok := rates[part.Level]; ok {
|
||||||
successRate = r
|
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)",
|
log.Printf("[PartsService] Enhance: SUCCESS partsId=%d level %d -> %d (rate=%d‰, cost=%d gold)",
|
||||||
part.PartsId, part.Level-1, part.Level, successRate, goldCost)
|
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 {
|
} else {
|
||||||
log.Printf("[PartsService] Enhance: FAIL partsId=%d stays level %d (rate=%d‰, cost=%d gold)",
|
log.Printf("[PartsService] Enhance: FAIL partsId=%d stays level %d (rate=%d‰, cost=%d gold)",
|
||||||
part.PartsId, part.Level, successRate, goldCost)
|
part.PartsId, part.Level, successRate, goldCost)
|
||||||
@@ -155,9 +161,9 @@ func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRe
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PartsServiceServer) grantSubStatuses(user *store.UserState, uuid string, part store.PartsState, partDef masterdata.EntityMParts, nowMillis int64) {
|
func grantPartsSubStatuses(catalog *masterdata.PartsCatalog, user *store.UserState, uuid string, part store.PartsState, partDef masterdata.EntityMParts, nowMillis int64) {
|
||||||
unlockLevels := s.catalog.SubStatusUnlockLvls[partDef.RarityType]
|
unlockLevels := catalog.SubStatusUnlockLvls[partDef.RarityType]
|
||||||
pool := s.catalog.SubStatusPool[partDef.PartsStatusSubLotteryGroupId]
|
pool := catalog.SubStatusPool[partDef.PartsStatusSubLotteryGroupId]
|
||||||
if len(pool) == 0 {
|
if len(pool) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -173,13 +179,13 @@ func (s *PartsServiceServer) grantSubStatuses(user *store.UserState, uuid string
|
|||||||
}
|
}
|
||||||
|
|
||||||
pick := pool[rand.Intn(len(pool))]
|
pick := pool[rand.Intn(len(pool))]
|
||||||
def, ok := s.catalog.PartsStatusMainById[pick]
|
def, ok := catalog.PartsStatusMainById[pick]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
statusValue := def.StatusChangeInitialValue
|
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)
|
statusValue = f.Evaluate(part.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -18,34 +18,35 @@ type BigHuntServiceServer struct {
|
|||||||
pb.UnimplementedBigHuntServiceServer
|
pb.UnimplementedBigHuntServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.BigHuntCatalog
|
holder *runtime.Holder
|
||||||
engine *questflow.QuestHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBigHuntServiceServer(
|
func NewBigHuntServiceServer(
|
||||||
users store.UserRepository,
|
users store.UserRepository,
|
||||||
sessions store.SessionRepository,
|
sessions store.SessionRepository,
|
||||||
catalog *masterdata.BigHuntCatalog,
|
holder *runtime.Holder,
|
||||||
engine *questflow.QuestHandler,
|
|
||||||
) *BigHuntServiceServer {
|
) *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) {
|
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",
|
log.Printf("[BigHuntService] StartBigHuntQuest: bossQuestId=%d questId=%d deckNumber=%d isDryRun=%v",
|
||||||
req.BigHuntBossQuestId, req.BigHuntQuestId, req.UserDeckNumber, req.IsDryRun)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
bhQuest, ok := s.catalog.QuestById[req.BigHuntQuestId]
|
bhQuest, ok := catalog.QuestById[req.BigHuntQuestId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId)
|
log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if ok {
|
if ok {
|
||||||
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis)
|
engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
user.BigHuntProgress = store.BigHuntProgress{
|
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",
|
log.Printf("[BigHuntService] FinishBigHuntQuest: bossQuestId=%d questId=%d isRetired=%v",
|
||||||
req.BigHuntBossQuestId, req.BigHuntQuestId, req.IsRetired)
|
req.BigHuntBossQuestId, req.BigHuntQuestId, req.IsRetired)
|
||||||
|
|
||||||
|
cat := s.holder.Get()
|
||||||
|
catalog := cat.BigHunt
|
||||||
|
engine := cat.QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
bhQuest := catalog.QuestById[req.BigHuntQuestId]
|
||||||
bossQuest := s.catalog.BossQuestById[req.BigHuntBossQuestId]
|
bossQuest := catalog.BossQuestById[req.BigHuntBossQuestId]
|
||||||
boss := s.catalog.BossByBossId[bossQuest.BigHuntBossId]
|
boss := catalog.BossByBossId[bossQuest.BigHuntBossId]
|
||||||
|
|
||||||
var scoreInfo *pb.BigHuntScoreInfo
|
var scoreInfo *pb.BigHuntScoreInfo
|
||||||
var scoreRewards []*pb.BigHuntReward
|
var scoreRewards []*pb.BigHuntReward
|
||||||
|
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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 {
|
if req.IsRetired || user.BigHuntProgress.IsDryRun {
|
||||||
user.BigHuntProgress = store.BigHuntProgress{LatestVersion: nowMillis}
|
user.BigHuntProgress = store.BigHuntProgress{LatestVersion: nowMillis}
|
||||||
@@ -108,7 +112,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
|||||||
baseScore := totalDamage
|
baseScore := totalDamage
|
||||||
|
|
||||||
difficultyBonusPermil := int32(0)
|
difficultyBonusPermil := int32(0)
|
||||||
if coeff, ok := s.catalog.ScoreCoefficients[bhQuest.BigHuntQuestScoreCoefficientId]; ok {
|
if coeff, ok := catalog.ScoreCoefficients[bhQuest.BigHuntQuestScoreCoefficientId]; ok {
|
||||||
difficultyBonusPermil = coeff
|
difficultyBonusPermil = coeff
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +142,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
|||||||
}
|
}
|
||||||
|
|
||||||
schedKey := store.BigHuntScheduleScoreKey{
|
schedKey := store.BigHuntScheduleScoreKey{
|
||||||
BigHuntScheduleId: s.catalog.ActiveScheduleId,
|
BigHuntScheduleId: catalog.ActiveScheduleId,
|
||||||
BigHuntBossId: bossQuest.BigHuntBossId,
|
BigHuntBossId: bossQuest.BigHuntBossId,
|
||||||
}
|
}
|
||||||
oldSchedMax := user.BigHuntScheduleMaxScores[schedKey].MaxScore
|
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{
|
scoreInfo = &pb.BigHuntScoreInfo{
|
||||||
UserScore: userScore,
|
UserScore: userScore,
|
||||||
@@ -177,12 +181,12 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isHighScore {
|
if isHighScore {
|
||||||
rewardGroupId := s.catalog.ResolveActiveScoreRewardGroupId(
|
rewardGroupId := catalog.ResolveActiveScoreRewardGroupId(
|
||||||
bossQuest.BigHuntScoreRewardGroupScheduleId, nowMillis)
|
bossQuest.BigHuntScoreRewardGroupScheduleId, nowMillis)
|
||||||
if rewardGroupId > 0 {
|
if rewardGroupId > 0 {
|
||||||
newItems := s.catalog.CollectNewRewards(rewardGroupId, oldMax, userScore)
|
newItems := catalog.CollectNewRewards(rewardGroupId, oldMax, userScore)
|
||||||
for _, item := range newItems {
|
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{
|
scoreRewards = append(scoreRewards, &pb.BigHuntReward{
|
||||||
PossessionType: item.PossessionType,
|
PossessionType: item.PossessionType,
|
||||||
PossessionId: item.PossessionId,
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
bhQuest := catalog.QuestById[req.BigHuntQuestId]
|
||||||
|
|
||||||
var battleBinary []byte
|
var battleBinary []byte
|
||||||
var deckNumber int32
|
var deckNumber int32
|
||||||
|
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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.CurrentQuestSceneId = 0
|
||||||
user.BigHuntProgress.LatestVersion = nowMillis
|
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) {
|
func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb.Empty) (*pb.GetBigHuntTopDataResponse, error) {
|
||||||
log.Printf("[BigHuntService] GetBigHuntTopData")
|
log.Printf("[BigHuntService] GetBigHuntTopData")
|
||||||
|
|
||||||
|
catalog := s.holder.Get().BigHunt
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.LoadUser(userId)
|
user, _ := s.users.LoadUser(userId)
|
||||||
|
|
||||||
@@ -309,13 +317,13 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
|||||||
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
||||||
|
|
||||||
var weeklyScoreResults []*pb.WeeklyScoreResult
|
var weeklyScoreResults []*pb.WeeklyScoreResult
|
||||||
for _, boss := range s.catalog.BossByBossId {
|
for _, boss := range catalog.BossByBossId {
|
||||||
key := store.BigHuntWeeklyScoreKey{
|
key := store.BigHuntWeeklyScoreKey{
|
||||||
BigHuntWeeklyVersion: weeklyVersion,
|
BigHuntWeeklyVersion: weeklyVersion,
|
||||||
AttributeType: boss.AttributeType,
|
AttributeType: boss.AttributeType,
|
||||||
}
|
}
|
||||||
ws := user.BigHuntWeeklyMaxScores[key]
|
ws := user.BigHuntWeeklyMaxScores[key]
|
||||||
gradeIconId := s.catalog.ResolveGradeIconId(boss.BigHuntBossId, ws.MaxScore)
|
gradeIconId := catalog.ResolveGradeIconId(boss.BigHuntBossId, ws.MaxScore)
|
||||||
|
|
||||||
weeklyScoreResults = append(weeklyScoreResults, &pb.WeeklyScoreResult{
|
weeklyScoreResults = append(weeklyScoreResults, &pb.WeeklyScoreResult{
|
||||||
AttributeType: boss.AttributeType,
|
AttributeType: boss.AttributeType,
|
||||||
@@ -330,10 +338,10 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
|||||||
|
|
||||||
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
||||||
|
|
||||||
weeklyRewards := s.resolveWeeklyRewards(user, weeklyVersion, nowMillis)
|
weeklyRewards := resolveBigHuntWeeklyRewards(catalog, user, weeklyVersion, nowMillis)
|
||||||
|
|
||||||
lastWeekVersion := weeklyVersion - 7*24*60*60*1000
|
lastWeekVersion := weeklyVersion - 7*24*60*60*1000
|
||||||
lastWeekRewards := s.resolveWeeklyRewards(user, lastWeekVersion, nowMillis)
|
lastWeekRewards := resolveBigHuntWeeklyRewards(catalog, user, lastWeekVersion, nowMillis)
|
||||||
|
|
||||||
return &pb.GetBigHuntTopDataResponse{
|
return &pb.GetBigHuntTopDataResponse{
|
||||||
WeeklyScoreResult: weeklyScoreResults,
|
WeeklyScoreResult: weeklyScoreResults,
|
||||||
@@ -343,14 +351,14 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
|||||||
}, nil
|
}, 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
|
var rewards []*pb.BigHuntReward
|
||||||
for _, boss := range s.catalog.BossByBossId {
|
for _, boss := range catalog.BossByBossId {
|
||||||
rewardKey := masterdata.BigHuntWeeklyRewardKey{
|
rewardKey := masterdata.BigHuntWeeklyRewardKey{
|
||||||
ScheduleId: 1,
|
ScheduleId: 1,
|
||||||
AttributeType: boss.AttributeType,
|
AttributeType: boss.AttributeType,
|
||||||
}
|
}
|
||||||
rewardGroupId := s.catalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
rewardGroupId := catalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
||||||
if rewardGroupId == 0 {
|
if rewardGroupId == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -359,7 +367,7 @@ func (s *BigHuntServiceServer) resolveWeeklyRewards(user store.UserState, weekly
|
|||||||
AttributeType: boss.AttributeType,
|
AttributeType: boss.AttributeType,
|
||||||
}
|
}
|
||||||
maxScore := user.BigHuntWeeklyMaxScores[weekKey].MaxScore
|
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{
|
rewards = append(rewards, &pb.BigHuntReward{
|
||||||
PossessionType: item.PossessionType,
|
PossessionType: item.PossessionType,
|
||||||
PossessionId: item.PossessionId,
|
PossessionId: item.PossessionId,
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ import (
|
|||||||
func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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))
|
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||||
for i, d := range drops {
|
for i, d := range drops {
|
||||||
pbDrops[i] = &pb.BattleDropReward{
|
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)
|
log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v", req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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{
|
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) {
|
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)
|
log.Printf("[QuestService] RestartEventQuest: chapterId=%d questId=%d", req.EventQuestChapterId, req.QuestId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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{
|
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) {
|
func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context, req *pb.UpdateEventQuestSceneProgressRequest) (*pb.UpdateEventQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateEventQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateEventQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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
|
return &pb.UpdateEventQuestSceneProgressResponse{}, nil
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ import (
|
|||||||
func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) {
|
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)
|
log.Printf("[QuestService] StartExtraQuest: questId=%d deckNumber=%d", req.QuestId, req.UserDeckNumber)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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))
|
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||||
for i, d := range drops {
|
for i, d := range drops {
|
||||||
pbDrops[i] = &pb.BattleDropReward{
|
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)
|
log.Printf("[QuestService] FinishExtraQuest: questId=%d isRetired=%v isAnnihilated=%v", req.QuestId, req.IsRetired, req.IsAnnihilated)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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{
|
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) {
|
func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.RestartExtraQuestRequest) (*pb.RestartExtraQuestResponse, error) {
|
||||||
log.Printf("[QuestService] RestartExtraQuest: questId=%d", req.QuestId)
|
log.Printf("[QuestService] RestartExtraQuest: questId=%d", req.QuestId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var deckNumber int32
|
var deckNumber int32
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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
|
deckNumber = user.Quests[req.QuestId].UserDeckNumber
|
||||||
})
|
})
|
||||||
|
|
||||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
drops := engine.BattleDropRewards(req.QuestId)
|
||||||
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||||
for i, d := range drops {
|
for i, d := range drops {
|
||||||
pbDrops[i] = &pb.BattleDropReward{
|
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) {
|
func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context, req *pb.UpdateExtraQuestSceneProgressRequest) (*pb.UpdateExtraQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateExtraQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateExtraQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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
|
return &pb.UpdateExtraQuestSceneProgressResponse{}, nil
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -17,22 +18,23 @@ type QuestServiceServer struct {
|
|||||||
pb.UnimplementedQuestServiceServer
|
pb.UnimplementedQuestServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
engine *questflow.QuestHandler
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, engine *questflow.QuestHandler) *QuestServiceServer {
|
func NewQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *QuestServiceServer {
|
||||||
if engine == nil {
|
if holder == nil {
|
||||||
panic("quest handler is required")
|
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) {
|
func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, req *pb.UpdateMainFlowSceneProgressRequest) (*pb.UpdateMainFlowSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateMainFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateMainFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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
|
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) {
|
func (s *QuestServiceServer) UpdateReplayFlowSceneProgress(ctx context.Context, req *pb.UpdateReplayFlowSceneProgressRequest) (*pb.UpdateReplayFlowSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateReplayFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateReplayFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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
|
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) {
|
func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, req *pb.UpdateMainQuestSceneProgressRequest) (*pb.UpdateMainQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateMainQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateMainQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleMainQuestSceneProgress(user, req.QuestSceneId)
|
engine.HandleMainQuestSceneProgress(user, req.QuestSceneId)
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.UpdateMainQuestSceneProgressResponse{}, nil
|
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) {
|
func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) {
|
||||||
log.Printf("[QuestService] StartMainQuest: %+v", req)
|
log.Printf("[QuestService] StartMainQuest: %+v", req)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if req.IsReplayFlow {
|
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 {
|
} 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))
|
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||||
for i, d := range drops {
|
for i, d := range drops {
|
||||||
pbDrops[i] = &pb.BattleDropReward{
|
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)
|
req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.StorySkipType)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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{
|
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) {
|
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)
|
log.Printf("[QuestService] RestartMainQuest: questId=%d isMainFlow=%v", req.QuestId, req.IsMainFlow)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var deckNumber int32
|
var deckNumber int32
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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
|
deckNumber = user.Quests[req.QuestId].UserDeckNumber
|
||||||
})
|
})
|
||||||
|
|
||||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
drops := engine.BattleDropRewards(req.QuestId)
|
||||||
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
pbDrops := make([]*pb.BattleDropReward, len(drops))
|
||||||
for i, d := range drops {
|
for i, d := range drops {
|
||||||
pbDrops[i] = &pb.BattleDropReward{
|
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))
|
log.Printf("[QuestService] SkipQuest: questId=%d skipCount=%d useEffectItems=%d", req.QuestId, req.SkipCount, len(req.UseEffectItem))
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
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
|
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{
|
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) {
|
func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteRequest) (*pb.SetRouteResponse, error) {
|
||||||
log.Printf("[QuestService] SetRoute: mainQuestRouteId=%d", req.MainQuestRouteId)
|
log.Printf("[QuestService] SetRoute: mainQuestRouteId=%d", req.MainQuestRouteId)
|
||||||
|
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.MainQuest.CurrentMainQuestRouteId = req.MainQuestRouteId
|
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
|
user.MainQuest.MainQuestSeasonId = seasonId
|
||||||
}
|
}
|
||||||
now := gametime.NowMillis()
|
now := gametime.NowMillis()
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,11 +15,11 @@ type SideStoryQuestServiceServer struct {
|
|||||||
pb.UnimplementedSideStoryQuestServiceServer
|
pb.UnimplementedSideStoryQuestServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.SideStoryCatalog
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.SideStoryCatalog) *SideStoryQuestServiceServer {
|
func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *SideStoryQuestServiceServer {
|
||||||
return &SideStoryQuestServiceServer{users: users, sessions: sessions, catalog: catalog}
|
return &SideStoryQuestServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) {
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
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) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
|
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -15,24 +16,25 @@ import (
|
|||||||
|
|
||||||
type RewardServiceServer struct {
|
type RewardServiceServer struct {
|
||||||
pb.UnimplementedRewardServiceServer
|
pb.UnimplementedRewardServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
bhCatalog *masterdata.BigHuntCatalog
|
holder *runtime.Holder
|
||||||
granter *store.PossessionGranter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRewardServiceServer(
|
func NewRewardServiceServer(
|
||||||
users store.UserRepository,
|
users store.UserRepository,
|
||||||
sessions store.SessionRepository,
|
sessions store.SessionRepository,
|
||||||
bhCatalog *masterdata.BigHuntCatalog,
|
holder *runtime.Holder,
|
||||||
granter *store.PossessionGranter,
|
|
||||||
) *RewardServiceServer {
|
) *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) {
|
func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *emptypb.Empty) (*pb.ReceiveBigHuntRewardResponse, error) {
|
||||||
log.Printf("[RewardService] ReceiveBigHuntReward")
|
log.Printf("[RewardService] ReceiveBigHuntReward")
|
||||||
|
|
||||||
|
cat := s.holder.Get()
|
||||||
|
bhCatalog := cat.BigHunt
|
||||||
|
granter := cat.QuestHandler.Granter
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
||||||
@@ -45,13 +47,13 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
|||||||
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
||||||
isReceived = ws.IsReceivedWeeklyReward
|
isReceived = ws.IsReceivedWeeklyReward
|
||||||
|
|
||||||
for _, boss := range s.bhCatalog.BossByBossId {
|
for _, boss := range bhCatalog.BossByBossId {
|
||||||
key := store.BigHuntWeeklyScoreKey{
|
key := store.BigHuntWeeklyScoreKey{
|
||||||
BigHuntWeeklyVersion: weeklyVersion,
|
BigHuntWeeklyVersion: weeklyVersion,
|
||||||
AttributeType: boss.AttributeType,
|
AttributeType: boss.AttributeType,
|
||||||
}
|
}
|
||||||
wms := user.BigHuntWeeklyMaxScores[key]
|
wms := user.BigHuntWeeklyMaxScores[key]
|
||||||
gradeIcon := s.bhCatalog.ResolveGradeIconId(boss.BigHuntBossId, wms.MaxScore)
|
gradeIcon := bhCatalog.ResolveGradeIconId(boss.BigHuntBossId, wms.MaxScore)
|
||||||
weeklyScoreResults = append(weeklyScoreResults, &pb.WeeklyScoreResult{
|
weeklyScoreResults = append(weeklyScoreResults, &pb.WeeklyScoreResult{
|
||||||
AttributeType: boss.AttributeType,
|
AttributeType: boss.AttributeType,
|
||||||
BeforeMaxScore: wms.MaxScore,
|
BeforeMaxScore: wms.MaxScore,
|
||||||
@@ -64,12 +66,12 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isReceived {
|
if !isReceived {
|
||||||
for _, boss := range s.bhCatalog.BossByBossId {
|
for _, boss := range bhCatalog.BossByBossId {
|
||||||
rewardKey := masterdata.BigHuntWeeklyRewardKey{
|
rewardKey := masterdata.BigHuntWeeklyRewardKey{
|
||||||
ScheduleId: 1,
|
ScheduleId: 1,
|
||||||
AttributeType: boss.AttributeType,
|
AttributeType: boss.AttributeType,
|
||||||
}
|
}
|
||||||
rewardGroupId := s.bhCatalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
rewardGroupId := bhCatalog.ResolveActiveWeeklyRewardGroupId(rewardKey, nowMillis)
|
||||||
if rewardGroupId == 0 {
|
if rewardGroupId == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -80,9 +82,9 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
|||||||
}
|
}
|
||||||
maxScore := user.BigHuntWeeklyMaxScores[weekKey].MaxScore
|
maxScore := user.BigHuntWeeklyMaxScores[weekKey].MaxScore
|
||||||
|
|
||||||
items := s.bhCatalog.CollectNewRewards(rewardGroupId, 0, maxScore)
|
items := bhCatalog.CollectNewRewards(rewardGroupId, 0, maxScore)
|
||||||
for _, item := range items {
|
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{
|
weeklyRewards = append(weeklyRewards, &pb.BigHuntReward{
|
||||||
PossessionType: item.PossessionType,
|
PossessionType: item.PossessionType,
|
||||||
PossessionId: item.PossessionId,
|
PossessionId: item.PossessionId,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
@@ -18,23 +19,25 @@ type ShopServiceServer struct {
|
|||||||
pb.UnimplementedShopServiceServer
|
pb.UnimplementedShopServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.ShopCatalog
|
holder *runtime.Holder
|
||||||
granter *store.PossessionGranter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShopServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.ShopCatalog, granter *store.PossessionGranter) *ShopServiceServer {
|
func NewShopServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *ShopServiceServer {
|
||||||
return &ShopServiceServer{users: users, sessions: sessions, catalog: catalog, granter: granter}
|
return &ShopServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.BuyResponse, error) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for shopItemId, qty := range req.ShopItems {
|
for shopItemId, qty := range req.ShopItems {
|
||||||
item, ok := s.catalog.Items[shopItemId]
|
item, ok := catalog.Items[shopItemId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[ShopService] Buy: unknown shopItemId=%d, skipping", shopItemId)
|
log.Printf("[ShopService] Buy: unknown shopItemId=%d, skipping", shopItemId)
|
||||||
continue
|
continue
|
||||||
@@ -46,8 +49,8 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, content := range s.catalog.Contents[shopItemId] {
|
for _, content := range catalog.Contents[shopItemId] {
|
||||||
s.granter.GrantFull(user,
|
granter.GrantFull(user,
|
||||||
model.PossessionType(content.PossessionType),
|
model.PossessionType(content.PossessionType),
|
||||||
content.PossessionId,
|
content.PossessionId,
|
||||||
content.Count*qty,
|
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 := user.ShopItems[shopItemId]
|
||||||
si.ShopItemId = 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) {
|
func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.RefreshRequest) (*pb.RefreshResponse, error) {
|
||||||
log.Printf("[ShopService] RefreshUserData: isGemUsed=%v", req.IsGemUsed)
|
log.Printf("[ShopService] RefreshUserData: isGemUsed=%v", req.IsGemUsed)
|
||||||
|
|
||||||
|
catalog := s.holder.Get().Shop
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if len(user.ShopReplaceableLineup) == 0 && len(s.catalog.ItemShopPool) > 0 {
|
if len(user.ShopReplaceableLineup) == 0 && len(catalog.ItemShopPool) > 0 {
|
||||||
for i, itemId := range s.catalog.ItemShopPool {
|
for i, itemId := range catalog.ItemShopPool {
|
||||||
slot := int32(i + 1)
|
slot := int32(i + 1)
|
||||||
user.ShopReplaceableLineup[slot] = store.UserShopReplaceableLineupState{
|
user.ShopReplaceableLineup[slot] = store.UserShopReplaceableLineupState{
|
||||||
SlotNumber: slot,
|
SlotNumber: slot,
|
||||||
@@ -93,7 +97,7 @@ func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.Refresh
|
|||||||
if req.IsGemUsed {
|
if req.IsGemUsed {
|
||||||
user.ShopReplaceable.LineupUpdateCount++
|
user.ShopReplaceable.LineupUpdateCount++
|
||||||
user.ShopReplaceable.LatestLineupUpdateDatetime = nowMillis
|
user.ShopReplaceable.LatestLineupUpdateDatetime = nowMillis
|
||||||
for _, itemId := range s.catalog.ItemShopPool {
|
for _, itemId := range catalog.ItemShopPool {
|
||||||
if si, ok := user.ShopItems[itemId]; ok {
|
if si, ok := user.ShopItems[itemId]; ok {
|
||||||
si.BoughtCount = 0
|
si.BoughtCount = 0
|
||||||
si.LatestVersion = nowMillis
|
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",
|
log.Printf("[ShopService] CreatePurchaseTransaction: shopId=%d shopItemId=%d productId=%s",
|
||||||
req.ShopId, req.ShopItemId, req.ProductId)
|
req.ShopId, req.ShopItemId, req.ProductId)
|
||||||
|
|
||||||
|
cat := s.holder.Get()
|
||||||
|
catalog := cat.Shop
|
||||||
|
granter := cat.QuestHandler.Granter
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
item, ok := s.catalog.Items[req.ShopItemId]
|
item, ok := catalog.Items[req.ShopItemId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[ShopService] CreatePurchaseTransaction: unknown shopItemId=%d", req.ShopItemId)
|
log.Printf("[ShopService] CreatePurchaseTransaction: unknown shopItemId=%d", req.ShopItemId)
|
||||||
return
|
return
|
||||||
@@ -134,8 +141,8 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
|
|||||||
log.Printf("[ShopService] CreatePurchaseTransaction: deduct failed: %v", err)
|
log.Printf("[ShopService] CreatePurchaseTransaction: deduct failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, content := range s.catalog.Contents[req.ShopItemId] {
|
for _, content := range catalog.Contents[req.ShopItemId] {
|
||||||
s.granter.GrantFull(user,
|
granter.GrantFull(user,
|
||||||
model.PossessionType(content.PossessionType),
|
model.PossessionType(content.PossessionType),
|
||||||
content.PossessionId,
|
content.PossessionId,
|
||||||
content.Count,
|
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 := user.ShopItems[req.ShopItemId]
|
||||||
si.ShopItemId = req.ShopItemId
|
si.ShopItemId = req.ShopItemId
|
||||||
si.BoughtCount++
|
si.BoughtCount++
|
||||||
if item.ShopItemLimitedStockId > 0 {
|
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
|
si.BoughtCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,12 +189,12 @@ func (s *ShopServiceServer) PurchaseGooglePlayStoreProduct(ctx context.Context,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShopServiceServer) applyContentEffects(user *store.UserState, shopItemId, qty int32, nowMillis int64) {
|
func applyShopContentEffects(catalog *masterdata.ShopCatalog, user *store.UserState, shopItemId, qty int32, nowMillis int64) {
|
||||||
for _, effect := range s.catalog.Effects[shopItemId] {
|
for _, effect := range catalog.Effects[shopItemId] {
|
||||||
switch effect.EffectTargetType {
|
switch effect.EffectTargetType {
|
||||||
case model.EffectTargetStaminaRecovery:
|
case model.EffectTargetStaminaRecovery:
|
||||||
maxMillis := s.catalog.MaxStaminaMillis[user.Status.Level]
|
maxMillis := catalog.MaxStaminaMillis[user.Status.Level]
|
||||||
millis := s.resolveEffectMillis(effect.EffectValueType, effect.EffectValue, user.Status.Level)
|
millis := resolveShopEffectMillis(catalog, effect.EffectValueType, effect.EffectValue, user.Status.Level)
|
||||||
store.RecoverStamina(user, millis*qty, maxMillis, nowMillis)
|
store.RecoverStamina(user, millis*qty, maxMillis, nowMillis)
|
||||||
default:
|
default:
|
||||||
log.Printf("[ShopService] unhandled effect: shopItemId=%d targetType=%d", shopItemId, effect.EffectTargetType)
|
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 {
|
switch effectValueType {
|
||||||
case model.EffectValueFixed:
|
case model.EffectValueFixed:
|
||||||
return effectValue
|
return effectValue
|
||||||
case model.EffectValuePermil:
|
case model.EffectValuePermil:
|
||||||
maxMillis := s.catalog.MaxStaminaMillis[userLevel]
|
maxMillis := catalog.MaxStaminaMillis[userLevel]
|
||||||
return effectValue * maxMillis / 1000
|
return effectValue * maxMillis / 1000
|
||||||
default:
|
default:
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,17 +16,18 @@ type TutorialServiceServer struct {
|
|||||||
pb.UnimplementedTutorialServiceServer
|
pb.UnimplementedTutorialServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
engine *questflow.QuestHandler
|
holder *runtime.Holder
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTutorialServiceServer(users store.UserRepository, sessions store.SessionRepository, engine *questflow.QuestHandler) *TutorialServiceServer {
|
func NewTutorialServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *TutorialServiceServer {
|
||||||
return &TutorialServiceServer{users: users, sessions: sessions, engine: engine}
|
return &TutorialServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb.SetTutorialProgressRequest) (*pb.SetTutorialProgressResponse, error) {
|
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)
|
log.Printf("[TutorialService] SetTutorialProgress: type=%d phase=%d choice=%d", req.TutorialType, req.ProgressPhase, req.ChoiceId)
|
||||||
userId := CurrentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
engine := s.holder.Get().QuestHandler
|
||||||
var grants []questflow.RewardGrant
|
var grants []questflow.RewardGrant
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
existing, exists := user.Tutorials[req.TutorialType]
|
existing, exists := user.Tutorials[req.TutorialType]
|
||||||
@@ -36,7 +38,7 @@ func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb
|
|||||||
ChoiceId: req.ChoiceId,
|
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 {
|
if req.TutorialType == int32(model.TutorialTypeMenuFirst) && req.ProgressPhase == 20 {
|
||||||
store.EnsureDefaultDeck(user, nowMillis)
|
store.EnsureDefaultDeck(user, nowMillis)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"lunar-tear/server/internal/gameutil"
|
"lunar-tear/server/internal/gameutil"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/runtime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,12 +18,11 @@ type WeaponServiceServer struct {
|
|||||||
pb.UnimplementedWeaponServiceServer
|
pb.UnimplementedWeaponServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
catalog *masterdata.WeaponCatalog
|
holder *runtime.Holder
|
||||||
config *masterdata.GameConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWeaponServiceServer(users store.UserRepository, sessions store.SessionRepository, catalog *masterdata.WeaponCatalog, config *masterdata.GameConfig) *WeaponServiceServer {
|
func NewWeaponServiceServer(users store.UserRepository, sessions store.SessionRepository, holder *runtime.Holder) *WeaponServiceServer {
|
||||||
return &WeaponServiceServer{users: users, sessions: sessions, catalog: catalog, config: config}
|
return &WeaponServiceServer{users: users, sessions: sessions, holder: holder}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectRequest) (*pb.ProtectResponse, error) {
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -82,7 +85,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceByMaterial: weapon master id=%d not found", weapon.WeaponId)
|
log.Printf("[WeaponService] EnhanceByMaterial: weapon master id=%d not found", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
@@ -91,7 +94,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
|||||||
totalExp := int32(0)
|
totalExp := int32(0)
|
||||||
totalMaterialCount := int32(0)
|
totalMaterialCount := int32(0)
|
||||||
for materialId, count := range req.Materials {
|
for materialId, count := range req.Materials {
|
||||||
mat, ok := s.catalog.Materials[materialId]
|
mat, ok := catalog.Materials[materialId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceByMaterial: material id=%d not found, skipping", materialId)
|
log.Printf("[WeaponService] EnhanceByMaterial: material id=%d not found, skipping", materialId)
|
||||||
continue
|
continue
|
||||||
@@ -107,19 +110,19 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
|||||||
|
|
||||||
expPerUnit := mat.EffectValue
|
expPerUnit := mat.EffectValue
|
||||||
if mat.WeaponType != 0 && mat.WeaponType == wm.WeaponType {
|
if mat.WeaponType != 0 && mat.WeaponType == wm.WeaponType {
|
||||||
expPerUnit = expPerUnit * s.config.MaterialSameWeaponExpCoefficientPermil / 1000
|
expPerUnit = expPerUnit * config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||||
}
|
}
|
||||||
totalExp += expPerUnit * count
|
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)
|
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)
|
log.Printf("[WeaponService] EnhanceByMaterial: gold cost=%d (materials=%d)", goldCost, totalMaterialCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
weapon.Exp += totalExp
|
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)
|
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
|
user.Weapons[req.UserWeaponUuid] = weapon
|
||||||
log.Printf("[WeaponService] EnhanceByMaterial: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("weapon enhance by material: %w", err)
|
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) {
|
func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*pb.SellResponse, error) {
|
||||||
log.Printf("[WeaponService] Sell: uuids=%v", req.UserWeaponUuid)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, 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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] Sell: weapon master id=%d not found, skipping", weapon.WeaponId)
|
log.Printf("[WeaponService] Sell: weapon master id=%d not found, skipping", weapon.WeaponId)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if sellFunc, ok := s.catalog.SellPriceByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
if sellFunc, ok := catalog.SellPriceByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
|
||||||
totalGold += sellFunc.Evaluate(weapon.Level)
|
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 {
|
for itemId, count := range medals {
|
||||||
user.ConsumableItems[itemId] += count
|
user.ConsumableItems[itemId] += count
|
||||||
}
|
}
|
||||||
@@ -176,7 +182,7 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
|
|||||||
}
|
}
|
||||||
|
|
||||||
if totalGold > 0 {
|
if totalGold > 0 {
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] += totalGold
|
user.ConsumableItems[config.ConsumableItemIdForGold] += totalGold
|
||||||
log.Printf("[WeaponService] Sell: granted %d gold", 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) {
|
func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest) (*pb.EvolveResponse, error) {
|
||||||
log.Printf("[WeaponService] Evolve: uuid=%s", req.UserWeaponUuid)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -200,20 +209,20 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] Evolve: weapon master id=%d not found", weapon.WeaponId)
|
log.Printf("[WeaponService] Evolve: weapon master id=%d not found", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
evolvedId, ok := s.catalog.EvolutionNextWeaponId[weapon.WeaponId]
|
evolvedId, ok := catalog.EvolutionNextWeaponId[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] Evolve: no evolution for weaponId=%d", weapon.WeaponId)
|
log.Printf("[WeaponService] Evolve: no evolution for weaponId=%d", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
totalMaterialCount := int32(0)
|
totalMaterialCount := int32(0)
|
||||||
mats := s.catalog.EvolutionMaterials[wm.WeaponEvolutionMaterialGroupId]
|
mats := catalog.EvolutionMaterials[wm.WeaponEvolutionMaterialGroupId]
|
||||||
for _, mat := range mats {
|
for _, mat := range mats {
|
||||||
cur := user.Materials[mat.MaterialId]
|
cur := user.Materials[mat.MaterialId]
|
||||||
cost := mat.Count
|
cost := mat.Count
|
||||||
@@ -225,9 +234,9 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
|||||||
totalMaterialCount += cost
|
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)
|
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||||
log.Printf("[WeaponService] Evolve: gold cost=%d", 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
|
weapon.LatestVersion = nowMillis
|
||||||
user.Weapons[req.UserWeaponUuid] = weapon
|
user.Weapons[req.UserWeaponUuid] = weapon
|
||||||
|
|
||||||
evolvedMaster, ok := s.catalog.Weapons[evolvedId]
|
evolvedMaster, ok := catalog.Weapons[evolvedId]
|
||||||
if ok {
|
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))
|
abilities := make([]store.WeaponAbilityState, len(slots))
|
||||||
for i, slot := range slots {
|
for i, slot := range slots {
|
||||||
abilities[i] = store.WeaponAbilityState{
|
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)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("weapon evolve: %w", err)
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -274,13 +286,13 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceSkill: weapon master id=%d not found", weapon.WeaponId)
|
log.Printf("[WeaponService] EnhanceSkill: weapon master id=%d not found", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
groupRows := s.catalog.SkillGroupsByGroupId[wm.WeaponSkillGroupId]
|
groupRows := catalog.SkillGroupsByGroupId[wm.WeaponSkillGroupId]
|
||||||
var skillGroup *masterdata.EntityMWeaponSkillGroup
|
var skillGroup *masterdata.EntityMWeaponSkillGroup
|
||||||
for i := range groupRows {
|
for i := range groupRows {
|
||||||
if groupRows[i].SkillId == req.SkillId {
|
if groupRows[i].SkillId == req.SkillId {
|
||||||
@@ -306,7 +318,7 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maxLevelFunc, ok := s.catalog.SkillMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
maxLevelFunc, ok := catalog.SkillMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceSkill: no max skill level func for enhanceId=%d", wm.WeaponSpecificEnhanceId)
|
log.Printf("[WeaponService] EnhanceSkill: no max skill level func for enhanceId=%d", wm.WeaponSpecificEnhanceId)
|
||||||
return
|
return
|
||||||
@@ -326,7 +338,7 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
|||||||
enhanceMatId := skillGroup.WeaponSkillEnhancementMaterialId
|
enhanceMatId := skillGroup.WeaponSkillEnhancementMaterialId
|
||||||
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
||||||
key := [2]int32{enhanceMatId, lvl}
|
key := [2]int32{enhanceMatId, lvl}
|
||||||
mats := s.catalog.SkillEnhanceMats[key]
|
mats := catalog.SkillEnhanceMats[key]
|
||||||
for _, mat := range mats {
|
for _, mat := range mats {
|
||||||
cur := user.Materials[mat.MaterialId]
|
cur := user.Materials[mat.MaterialId]
|
||||||
cost := mat.Count
|
cost := mat.Count
|
||||||
@@ -337,9 +349,9 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
|||||||
user.Materials[mat.MaterialId] = cur - cost
|
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)
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -370,13 +385,13 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceAbility: weapon master id=%d not found", weapon.WeaponId)
|
log.Printf("[WeaponService] EnhanceAbility: weapon master id=%d not found", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
groupRows := s.catalog.AbilityGroupsByGroupId[wm.WeaponAbilityGroupId]
|
groupRows := catalog.AbilityGroupsByGroupId[wm.WeaponAbilityGroupId]
|
||||||
var abilityGroup *masterdata.EntityMWeaponAbilityGroup
|
var abilityGroup *masterdata.EntityMWeaponAbilityGroup
|
||||||
for i := range groupRows {
|
for i := range groupRows {
|
||||||
if groupRows[i].AbilityId == req.AbilityId {
|
if groupRows[i].AbilityId == req.AbilityId {
|
||||||
@@ -402,7 +417,7 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maxLevelFunc, ok := s.catalog.AbilityMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
maxLevelFunc, ok := catalog.AbilityMaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceAbility: no max ability level func for enhanceId=%d", wm.WeaponSpecificEnhanceId)
|
log.Printf("[WeaponService] EnhanceAbility: no max ability level func for enhanceId=%d", wm.WeaponSpecificEnhanceId)
|
||||||
return
|
return
|
||||||
@@ -422,7 +437,7 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
|||||||
enhanceMatId := abilityGroup.WeaponAbilityEnhancementMaterialId
|
enhanceMatId := abilityGroup.WeaponAbilityEnhancementMaterialId
|
||||||
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
for lvl := currentLevel; lvl < currentLevel+addCount; lvl++ {
|
||||||
key := [2]int32{enhanceMatId, lvl}
|
key := [2]int32{enhanceMatId, lvl}
|
||||||
mats := s.catalog.AbilityEnhanceMats[key]
|
mats := catalog.AbilityEnhanceMats[key]
|
||||||
for _, mat := range mats {
|
for _, mat := range mats {
|
||||||
cur := user.Materials[mat.MaterialId]
|
cur := user.Materials[mat.MaterialId]
|
||||||
cost := mat.Count
|
cost := mat.Count
|
||||||
@@ -433,9 +448,9 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
|||||||
user.Materials[mat.MaterialId] = cur - cost
|
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)
|
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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -466,18 +484,18 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if weapon.LimitBreakCount >= s.config.WeaponLimitBreakAvailableCount {
|
if weapon.LimitBreakCount >= config.WeaponLimitBreakAvailableCount {
|
||||||
log.Printf("[WeaponService] LimitBreakByMaterial: already at max limit break %d", weapon.LimitBreakCount)
|
log.Printf("[WeaponService] LimitBreakByMaterial: already at max limit break %d", weapon.LimitBreakCount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] LimitBreakByMaterial: weapon master id=%d not found", weapon.WeaponId)
|
log.Printf("[WeaponService] LimitBreakByMaterial: weapon master id=%d not found", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
remaining := s.config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
remaining := config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
||||||
|
|
||||||
totalMaterialCount := int32(0)
|
totalMaterialCount := int32(0)
|
||||||
for materialId, count := range req.Materials {
|
for materialId, count := range req.Materials {
|
||||||
@@ -496,9 +514,9 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
|
|||||||
totalMaterialCount += count
|
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)
|
goldCost := costFunc.Evaluate(totalMaterialCount)
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||||
log.Printf("[WeaponService] LimitBreakByMaterial: gold cost=%d", 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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -535,18 +556,18 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if weapon.LimitBreakCount >= s.config.WeaponLimitBreakAvailableCount {
|
if weapon.LimitBreakCount >= config.WeaponLimitBreakAvailableCount {
|
||||||
log.Printf("[WeaponService] LimitBreakByWeapon: already at max limit break %d", weapon.LimitBreakCount)
|
log.Printf("[WeaponService] LimitBreakByWeapon: already at max limit break %d", weapon.LimitBreakCount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] LimitBreakByWeapon: weapon master id=%d not found", weapon.WeaponId)
|
log.Printf("[WeaponService] LimitBreakByWeapon: weapon master id=%d not found", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
remaining := s.config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
remaining := config.WeaponLimitBreakAvailableCount - weapon.LimitBreakCount
|
||||||
|
|
||||||
consumedCount := int32(0)
|
consumedCount := int32(0)
|
||||||
for _, uuid := range req.MaterialUserWeaponUuids {
|
for _, uuid := range req.MaterialUserWeaponUuids {
|
||||||
@@ -560,7 +581,7 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if medals, ok := s.catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
if medals, ok := catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
||||||
for itemId, count := range medals {
|
for itemId, count := range medals {
|
||||||
user.ConsumableItems[itemId] += count
|
user.ConsumableItems[itemId] += count
|
||||||
}
|
}
|
||||||
@@ -573,9 +594,9 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
|||||||
consumedCount++
|
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)
|
goldCost := costFunc.Evaluate(consumedCount)
|
||||||
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= goldCost
|
user.ConsumableItems[config.ConsumableItemIdForGold] -= goldCost
|
||||||
log.Printf("[WeaponService] LimitBreakByWeapon: gold cost=%d", 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) {
|
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)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -612,7 +636,7 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wm, ok := s.catalog.Weapons[weapon.WeaponId]
|
wm, ok := catalog.Weapons[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceByWeapon: weapon master id=%d not found", weapon.WeaponId)
|
log.Printf("[WeaponService] EnhanceByWeapon: weapon master id=%d not found", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
@@ -627,19 +651,19 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
matMaster, ok := s.catalog.Weapons[matWeapon.WeaponId]
|
matMaster, ok := catalog.Weapons[matWeapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] EnhanceByWeapon: material weapon master id=%d not found, skipping", matWeapon.WeaponId)
|
log.Printf("[WeaponService] EnhanceByWeapon: material weapon master id=%d not found, skipping", matWeapon.WeaponId)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
baseExp := s.catalog.BaseExpByEnhanceId[matMaster.WeaponSpecificEnhanceId]
|
baseExp := catalog.BaseExpByEnhanceId[matMaster.WeaponSpecificEnhanceId]
|
||||||
if matMaster.WeaponType != 0 && matMaster.WeaponType == wm.WeaponType {
|
if matMaster.WeaponType != 0 && matMaster.WeaponType == wm.WeaponType {
|
||||||
baseExp = baseExp * s.config.MaterialSameWeaponExpCoefficientPermil / 1000
|
baseExp = baseExp * config.MaterialSameWeaponExpCoefficientPermil / 1000
|
||||||
}
|
}
|
||||||
totalExp += baseExp
|
totalExp += baseExp
|
||||||
|
|
||||||
if medals, ok := s.catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
if medals, ok := catalog.MedalsByWeaponId[matWeapon.WeaponId]; ok {
|
||||||
for itemId, count := range medals {
|
for itemId, count := range medals {
|
||||||
user.ConsumableItems[itemId] += count
|
user.ConsumableItems[itemId] += count
|
||||||
}
|
}
|
||||||
@@ -652,14 +676,14 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
|||||||
consumedCount++
|
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)
|
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)
|
log.Printf("[WeaponService] EnhanceByWeapon: gold cost=%d (weapons=%d)", goldCost, consumedCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
weapon.Exp += totalExp
|
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)
|
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
|
user.Weapons[req.UserWeaponUuid] = weapon
|
||||||
log.Printf("[WeaponService] EnhanceByWeapon: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("weapon enhance by weapon: %w", err)
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) {
|
func checkWeaponStoryUnlocks(catalog *masterdata.WeaponCatalog, user *store.UserState, weaponId, level int32, nowMillis int64) {
|
||||||
wm, ok := s.catalog.Weapons[weaponId]
|
wm, ok := catalog.Weapons[weaponId]
|
||||||
if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 {
|
if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId]
|
evoOrder, hasEvo := catalog.EvolutionOrder[weaponId]
|
||||||
conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId]
|
conditions := catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId]
|
||||||
|
|
||||||
for _, cond := range conditions {
|
for _, cond := range conditions {
|
||||||
switch model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) {
|
switch model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) {
|
||||||
@@ -696,14 +720,14 @@ func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, wea
|
|||||||
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
||||||
}
|
}
|
||||||
case model.WeaponStoryReleaseConditionTypeReachInitialMaxLevel:
|
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) {
|
if level >= maxFunc.Evaluate(0) {
|
||||||
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case model.WeaponStoryReleaseConditionTypeReachOnceEvolvedMaxLevel:
|
case model.WeaponStoryReleaseConditionTypeReachOnceEvolvedMaxLevel:
|
||||||
if hasEvo && evoOrder >= 1 {
|
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) {
|
if level >= maxFunc.Evaluate(0) {
|
||||||
store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
|
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) {
|
func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRequest) (*pb.WeaponAwakenResponse, error) {
|
||||||
log.Printf("[WeaponService] Awaken: uuid=%s", req.UserWeaponUuid)
|
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)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -730,7 +757,7 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
awakenRow, ok := s.catalog.AwakenByWeaponId[weapon.WeaponId]
|
awakenRow, ok := catalog.AwakenByWeaponId[weapon.WeaponId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[WeaponService] Awaken: no awaken data for weaponId=%d", weapon.WeaponId)
|
log.Printf("[WeaponService] Awaken: no awaken data for weaponId=%d", weapon.WeaponId)
|
||||||
return
|
return
|
||||||
@@ -741,7 +768,7 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mats := s.catalog.AwakenMaterialsByGroupId[awakenRow.WeaponAwakenMaterialGroupId]
|
mats := catalog.AwakenMaterialsByGroupId[awakenRow.WeaponAwakenMaterialGroupId]
|
||||||
for _, mat := range mats {
|
for _, mat := range mats {
|
||||||
cur := user.Materials[mat.MaterialId]
|
cur := user.Materials[mat.MaterialId]
|
||||||
cost := mat.Count
|
cost := mat.Count
|
||||||
@@ -753,7 +780,7 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if awakenRow.ConsumeGold > 0 {
|
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)
|
log.Printf("[WeaponService] Awaken: gold cost=%d", awakenRow.ConsumeGold)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user