diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
index 5376648..f7915ec 100644
--- a/.github/workflows/docker-image.yml
+++ b/.github/workflows/docker-image.yml
@@ -1,4 +1,4 @@
-name: Build and Push lunar-tear to Docker Hub
+name: Build and Push Docker images to Docker Hub
on:
push:
@@ -17,10 +17,26 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Build and push Docker image
+ - name: Build and push game server image
uses: docker/build-push-action@v5
with:
context: ./server
file: ./server/Dockerfile
push: true
tags: kretts/lunar-tear:latest
+
+ - name: Build and push CDN image
+ uses: docker/build-push-action@v5
+ with:
+ context: ./server
+ file: ./server/Dockerfile.cdn
+ push: true
+ tags: kretts/octo-cdn:latest
+
+ - name: Build and push auth server image
+ uses: docker/build-push-action@v5
+ with:
+ context: ./server
+ file: ./server/Dockerfile.auth
+ push: true
+ tags: kretts/auth-server:latest
diff --git a/.gitignore b/.gitignore
index e95f0c7..6313ba1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,10 @@ server/bin/
server/tmp/
server/lunar-tear
server/import-snapshot
+server/auth-server
+server/claim-account
+server/octo-cdn
+server/dev
__pycache__/
diff --git a/README.md b/README.md
index 0f2dbc8..42ada2a 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ Or manually:
```bash
cd server
mkdir -p db
-goose -dir migrations sqlite3 db/game.db up
+goose -dir migrations -allow-missing sqlite3 db/game.db up
```
### Importing a Snapshot
@@ -65,48 +65,124 @@ go run ./cmd/import-snapshot \
### Run
+The server is split into two binaries: a gRPC game server and an HTTP asset CDN. Both must be running for the client to work.
+
+**Start the CDN** (serves asset bundles, list.bin, master data, web pages):
+
+```bash
+cd server
+go run ./cmd/octo-cdn \
+ --listen 0.0.0.0:8080 \
+ --public-addr 10.0.2.2:8080
+```
+
+**Start the game server** (gRPC, points the client at the CDN):
+
```bash
cd server
go run ./cmd/lunar-tear \
- --host 10.0.2.2 \
- --http-port 8080 \
- --grpc-port 8003
+ --listen 0.0.0.0:8003 \
+ --public-addr 10.0.2.2:8003 \
+ --octo-url http://10.0.2.2:8080
```
-The default gRPC port is 443, which requires `sudo` (privileged port). Use `--grpc-port` with a high port to avoid this. If you do need port 443, either use `sudo` or grant the binary the capability on Linux:
+The default listen address is `0.0.0.0:443`, which requires `sudo` (privileged port). Use `--listen` with a high port to avoid this. If you do need port 443, either use `sudo` or grant the binary the capability on Linux:
```bash
go build -o lunar-tear ./cmd/lunar-tear
sudo setcap cap_net_bind_service=+ep ./lunar-tear
-./lunar-tear --host 10.0.2.2 --http-port 8080
+./lunar-tear --public-addr 10.0.2.2:443 --octo-url http://10.0.2.2:8080
```
+The CDN can run on a completely separate machine — just set `--octo-url` on the game server and `--public-addr` on the CDN to the externally-reachable address.
+
+### Run All Services At Once
+
+Instead of starting each service individually, use the dev runner to launch all three (auth, CDN, game server) with a single command. No Docker required — works on macOS, Linux, and Windows.
+
+```bash
+cd server
+make dev
+```
+
+Or directly:
+
+```bash
+cd server
+go run ./cmd/dev
+```
+
+Each service's output is prefixed with a colored label (`[auth]`, `[cdn]`, `[grpc]`). Press Ctrl+C to shut everything down.
+
+Override defaults with namespaced flags:
+
+```bash
+go run ./cmd/dev --grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000 --cdn.public-addr 192.168.1.50:8080
+```
+
+Or via `make`:
+
+```bash
+make dev ARGS="--grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000"
+```
+
+| Flag | Default | Description |
+| --------------------- | ------------------ | ---------------------------------------- |
+| `--auth.listen` | `0.0.0.0:3000` | auth-server listen address |
+| `--auth.db` | `db/auth.db` | auth-server SQLite database path |
+| `--cdn.listen` | `0.0.0.0:8080` | octo-cdn local bind address |
+| `--cdn.public-addr` | `10.0.2.2:8080` | octo-cdn externally-reachable addr |
+| `--grpc.listen` | `0.0.0.0:8003` | lunar-tear gRPC listen address |
+| `--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.auth-url` | `http://localhost:3000` | auth server base URL passed to lunar-tear |
+| `--no-color` | `false` | disable colored output |
+
### Ports
-| Protocol | Port | Notes |
-| -------- | ---- | ----------------------------------------------------------- |
-| gRPC | 443 | default; configurable with `--grpc-port` (requires patched client) |
-| HTTP | 8080 | Octo asset API + game web pages (`--http-port` flag) |
+| Protocol | Port | Binary | Notes |
+| -------- | ---- | ------------- | ----------------------------------------------------------- |
+| gRPC | 443 | `lunar-tear` | default; configurable with `--listen` (requires patched client) |
+| HTTP | 8080 | `octo-cdn` | Octo asset API + game web pages |
-### Flags
+### Game Server Flags (`lunar-tear`)
-| Flag | Default | Description |
-| ------------- | ------------ | ---------------------------------------------------- |
-| `--host` | `127.0.0.1` | hostname/IP given to the client |
-| `--http-port` | `8080` | HTTP/Octo server port |
-| `--grpc-port` | `443` | gRPC server port (client must be patched to match) |
-| `--db` | `db/game.db` | SQLite database path |
+| Flag | Default | Description |
+| --------------- | ----------------- | ---------------------------------------------------- |
+| `--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 |
+| `--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 |
+| `--auth-url` | *(empty)* | Auth server base URL (e.g. `http://localhost:3000`) |
+
+### CDN Flags (`octo-cdn`)
+
+| Flag | Default | Description |
+| --------------- | ----------------- | -------------------------------------------------------- |
+| `--listen` | `0.0.0.0:8080` | local bind address |
+| `--public-addr` | `127.0.0.1:8080` | externally-reachable address (used in list.bin rewriting) |
+| `--assets-dir` | `.` | root directory containing the `assets/` tree |
### Docker
-Migrations run automatically on container start.
+Three services are available via Docker Compose: the game server (`lunar-tear`), the CDN (`octo-cdn`), and the auth server (`auth-server`). Migrations run automatically on game server start.
```bash
cd server
docker compose up -d
```
-The `db/` directory is mounted as a volume so the database persists across restarts. Make sure `assets/` is populated before starting.
+The `db/` directory is mounted as a volume so both `game.db` and `auth.db` persist across restarts. Make sure `assets/` is populated before starting.
+
+Each service has its own image and can be deployed independently:
+
+| Service | Image | Default Port | Notes |
+| -------- | --------------------------- | ------------ | ------------------------------ |
+| `server` | `kretts/lunar-tear:latest` | 8003 | gRPC game server |
+| `cdn` | `kretts/octo-cdn:latest` | 8080 | HTTP asset CDN |
+| `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.
### Makefile Targets
@@ -115,11 +191,54 @@ All targets run from the `server/` directory.
| Target | Description |
| -------------- | ------------------------------------------------------- |
| `make proto` | Regenerate protobuf stubs |
-| `make build` | Build the server binary |
+| `make build` | Build the game server binary |
+| `make build-cdn` | Build the CDN binary |
+| `make build-auth` | Build the auth server binary |
| `make build-import` | Build the import-snapshot tool |
+| `make build-claim-account` | Build the claim-account tool |
+| `make dev` | Run all three services with one command |
| `make migrate` | Run goose migrations on `db/game.db` |
| `make import` | Import a snapshot (`SNAPSHOT=... UUID=...` required) |
+## Claim Account
+
+Transfers an existing game account to the most recently connected client. Looks up a player by their in-game name, assigns the new client's UUID to that account, and deletes the empty account the new client created.
+
+Useful when a new client connects and creates a throwaway account, but you want it to load an existing account instead.
+
+```bash
+cd server
+go run ./cmd/claim-account --name "PlayerName" --db db/game.db
+```
+
+| Flag | Default | Description |
+| -------- | ------------ | ---------------------------------------------------- |
+| `--name` | *(required)* | In-game player name to claim |
+| `--db` | `db/game.db` | SQLite database path |
+
+## Auth Server
+
+A separate HTTP server that handles player account registration and login. The patched client's Facebook login button is redirected to this server, which presents a username/password form. Tokens issued here are validated by the game server to link or recover accounts.
+
+### Run
+
+```bash
+cd server
+go run ./cmd/auth-server \
+ --listen 0.0.0.0:3000 \
+ --db db/auth.db
+```
+
+The `--secret` flag accepts a hex-encoded HMAC key. If omitted, a random key is generated on startup and printed to the console — pass it back on the next restart to keep existing tokens valid.
+
+### Flags
+
+| Flag | Default | Description |
+| ---------- | --------------- | -------------------------------------------- |
+| `--listen` | `0.0.0.0:3000` | HTTP listen address (host:port) |
+| `--db` | `db/auth.db` | SQLite database path for auth users |
+| `--secret` | *(generated)* | Hex-encoded HMAC secret for token signing |
+
## ⚠️ Legal Disclaimer
**Lunar Tear** is a fan-made, non-commercial **preservation and research project** dedicated to keeping a certain discontinued mobile game playable for educational and archival purposes.
diff --git a/server/Dockerfile.auth b/server/Dockerfile.auth
new file mode 100644
index 0000000..b5d57c5
--- /dev/null
+++ b/server/Dockerfile.auth
@@ -0,0 +1,20 @@
+FROM alpine:latest AS builder
+
+WORKDIR /usr/local/src
+COPY . .
+
+RUN apk add --no-cache go
+
+RUN go build -o auth-server ./cmd/auth-server
+
+FROM alpine:latest
+
+WORKDIR /opt/auth-server
+
+RUN chown 1000:1000 /opt/auth-server
+
+USER 1000
+
+COPY --from=builder /usr/local/src/auth-server .
+
+ENTRYPOINT ["./auth-server"]
diff --git a/server/Dockerfile.cdn b/server/Dockerfile.cdn
new file mode 100644
index 0000000..3cc92d4
--- /dev/null
+++ b/server/Dockerfile.cdn
@@ -0,0 +1,30 @@
+FROM alpine:latest AS builder
+
+WORKDIR /usr/local/src
+COPY . .
+
+RUN apk add --no-cache \
+ protobuf \
+ protobuf-dev \
+ protoc \
+ make \
+ go \
+ libcap
+
+RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest &&\
+ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest &&\
+ PATH="$PATH:$(go env GOPATH)/bin" make proto &&\
+ go build -o octo-cdn ./cmd/octo-cdn &&\
+ setcap cap_net_bind_service=+ep ./octo-cdn
+
+FROM alpine:latest
+
+WORKDIR /opt/octo-cdn
+
+RUN chown 1000:1000 /opt/octo-cdn
+
+USER 1000
+
+COPY --from=builder /usr/local/src/octo-cdn .
+
+ENTRYPOINT ["./octo-cdn"]
diff --git a/server/Makefile b/server/Makefile
index 91347ee..be9f0f4 100644
--- a/server/Makefile
+++ b/server/Makefile
@@ -3,19 +3,40 @@
# (generating all would put them in one package and cause name clashes).
PROTO_USED = proto/banner.proto proto/battle.proto proto/bighunt.proto proto/cageornament.proto proto/character.proto proto/characterboard.proto proto/characterviewer.proto proto/companion.proto proto/config.proto proto/consumableitem.proto proto/contentsstory.proto proto/costume.proto proto/data.proto proto/deck.proto proto/dokan.proto proto/explore.proto proto/friend.proto proto/gacha.proto proto/gameplay.proto proto/gift.proto proto/gimmick.proto proto/labyrinth.proto proto/loginbonus.proto proto/material.proto proto/mission.proto proto/movie.proto proto/navicutin.proto proto/omikuji.proto proto/notification.proto proto/parts.proto proto/portalcage.proto proto/pvp.proto proto/quest.proto proto/reward.proto proto/shop.proto proto/sidestoryquest.proto proto/tutorial.proto proto/user.proto proto/weapon.proto
+EXE =
+ifeq ($(OS),Windows_NT)
+ EXE = .exe
+endif
+
proto:
protoc -I . $(PROTO_USED) --go_out=. --go_opt=module=lunar-tear/server --go-grpc_out=. --go-grpc_opt=module=lunar-tear/server
@echo "Generated in gen/proto/"
build:
- go build -o lunar-tear ./cmd/lunar-tear
+ go build -o lunar-tear$(EXE) ./cmd/lunar-tear
+
+build-cdn:
+ go build -o octo-cdn$(EXE) ./cmd/octo-cdn
build-import:
- go build -o import-snapshot ./cmd/import-snapshot
+ go build -o import-snapshot$(EXE) ./cmd/import-snapshot
+
+build-auth:
+ go build -o auth-server$(EXE) ./cmd/auth-server
+
+build-claim-account:
+ go build -o claim-account$(EXE) ./cmd/claim-account
+
+dev:
+ go run ./cmd/dev $(ARGS)
migrate:
+ifeq ($(OS),Windows_NT)
+ if not exist db mkdir db
+else
mkdir -p db
- goose -dir migrations sqlite3 db/game.db up
+endif
+ goose -dir migrations -allow-missing sqlite3 db/game.db up
import:
ifndef SNAPSHOT
@@ -26,4 +47,4 @@ ifndef UUID
endif
go run ./cmd/import-snapshot --snapshot $(SNAPSHOT) --uuid $(UUID)
-.PHONY: proto build build-import migrate import
+.PHONY: proto build build-cdn build-auth build-import build-claim-account dev migrate import
diff --git a/server/cmd/auth-server/handlers.go b/server/cmd/auth-server/handlers.go
new file mode 100644
index 0000000..d71375b
--- /dev/null
+++ b/server/cmd/auth-server/handlers.go
@@ -0,0 +1,212 @@
+package main
+
+import (
+ "embed"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "log"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+//go:embed login.html
+var loginFS embed.FS
+
+var loginTmpl = template.Must(template.ParseFS(loginFS, "login.html"))
+
+type Handlers struct {
+ store *AuthStore
+ tok *TokenService
+}
+
+func NewHandlers(store *AuthStore, tok *TokenService) *Handlers {
+ return &Handlers{store: store, tok: tok}
+}
+
+type loginPageData struct {
+ RedirectURI string
+ State string
+ Error string
+ Username string
+}
+
+func isOAuthPath(path string) bool {
+ // Match /v{N}/dialog/oauth or /v{N}.{M}/dialog/oauth
+ parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
+ if len(parts) != 3 {
+ return false
+ }
+ return strings.HasPrefix(parts[0], "v") && parts[1] == "dialog" && parts[2] == "oauth"
+}
+
+func isMePath(path string) bool {
+ p := strings.TrimPrefix(path, "/")
+ if p == "me" {
+ return true
+ }
+ parts := strings.Split(p, "/")
+ return len(parts) == 2 && strings.HasPrefix(parts[0], "v") && parts[1] == "me"
+}
+
+func (h *Handlers) HandleOAuth(w http.ResponseWriter, r *http.Request) {
+ if isMePath(r.URL.Path) {
+ h.HandleMe(w, r)
+ return
+ }
+
+ if !isOAuthPath(r.URL.Path) {
+ http.NotFound(w, r)
+ return
+ }
+
+ switch r.Method {
+ case http.MethodGet:
+ h.oauthGet(w, r)
+ case http.MethodPost:
+ h.oauthPost(w, r)
+ default:
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ }
+}
+
+func (h *Handlers) oauthGet(w http.ResponseWriter, r *http.Request) {
+ data := loginPageData{
+ RedirectURI: r.URL.Query().Get("redirect_uri"),
+ State: r.URL.Query().Get("state"),
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ if err := loginTmpl.Execute(w, data); err != nil {
+ log.Printf("render login page: %v", err)
+ }
+}
+
+func (h *Handlers) oauthPost(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ http.Error(w, "bad form", http.StatusBadRequest)
+ return
+ }
+
+ username := strings.TrimSpace(r.FormValue("username"))
+ password := r.FormValue("password")
+ action := r.FormValue("action")
+ redirectURI := r.FormValue("redirect_uri")
+ state := r.FormValue("state")
+
+ renderErr := func(msg string) {
+ data := loginPageData{
+ RedirectURI: redirectURI,
+ State: state,
+ Error: msg,
+ Username: username,
+ }
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusOK)
+ if err := loginTmpl.Execute(w, data); err != nil {
+ log.Printf("render login page: %v", err)
+ }
+ }
+
+ if username == "" || password == "" {
+ renderErr("Username and password are required.")
+ return
+ }
+
+ var user AuthUser
+ var err error
+
+ switch action {
+ case "register":
+ user, err = h.store.CreateUser(username, password)
+ if err == ErrUserExists {
+ renderErr("Username is already taken.")
+ return
+ }
+ if err != nil {
+ log.Printf("create user: %v", err)
+ renderErr("Server error. Try again.")
+ return
+ }
+ log.Printf("registered user %q (id=%d)", user.Username, user.ID)
+
+ case "login":
+ user, err = h.store.VerifyUser(username, password)
+ if err == ErrInvalidCreds {
+ renderErr("Invalid username or password.")
+ return
+ }
+ if err != nil {
+ log.Printf("verify user: %v", err)
+ renderErr("Server error. Try again.")
+ return
+ }
+ log.Printf("authenticated user %q (id=%d)", user.Username, user.ID)
+
+ default:
+ renderErr("Invalid action.")
+ return
+ }
+
+ token, err := h.tok.Generate(user)
+ if err != nil {
+ log.Printf("generate token: %v", err)
+ renderErr("Server error. Try again.")
+ return
+ }
+
+ payload := fmt.Sprintf(`{"user_id":"%d"}`, user.ID)
+ b64 := base64.StdEncoding.EncodeToString([]byte(payload))
+
+ fragment := url.Values{}
+ fragment.Set("access_token", token)
+ fragment.Set("token_type", "bearer")
+ fragment.Set("expires_in", strconv.FormatInt(int64(tokenTTL.Seconds()), 10))
+ fragment.Set("signed_request", "0."+b64)
+ if state != "" {
+ fragment.Set("state", state)
+ }
+
+ target := redirectURI + "?" + fragment.Encode()
+ log.Printf("redirecting to %s", target)
+ http.Redirect(w, r, target, http.StatusFound)
+}
+
+func (h *Handlers) HandleCheckUsername(w http.ResponseWriter, r *http.Request) {
+ username := strings.TrimSpace(r.URL.Query().Get("username"))
+ w.Header().Set("Content-Type", "application/json")
+ if username == "" {
+ json.NewEncoder(w).Encode(map[string]bool{"exists": false})
+ return
+ }
+ json.NewEncoder(w).Encode(map[string]bool{"exists": h.store.UserExists(username)})
+}
+
+func (h *Handlers) HandleMe(w http.ResponseWriter, r *http.Request) {
+ token := r.URL.Query().Get("access_token")
+ if token == "" {
+ auth := r.Header.Get("Authorization")
+ if strings.HasPrefix(auth, "Bearer ") {
+ token = auth[7:]
+ }
+ }
+
+ if token == "" {
+ http.Error(w, `{"error":{"message":"missing access_token","type":"OAuthException","code":190}}`, http.StatusUnauthorized)
+ return
+ }
+
+ claims, err := h.tok.Validate(token)
+ if err != nil {
+ http.Error(w, fmt.Sprintf(`{"error":{"message":"%s","type":"OAuthException","code":190}}`, err), http.StatusUnauthorized)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]string{
+ "id": strconv.FormatInt(claims.Sub, 10),
+ "name": claims.Name,
+ })
+}
diff --git a/server/cmd/auth-server/login.html b/server/cmd/auth-server/login.html
new file mode 100644
index 0000000..362ea9c
--- /dev/null
+++ b/server/cmd/auth-server/login.html
@@ -0,0 +1,199 @@
+
+
+
+
+
+Lunar Tear – Login
+
+
+
+
+
+
+
diff --git a/server/cmd/auth-server/main.go b/server/cmd/auth-server/main.go
new file mode 100644
index 0000000..feeaca3
--- /dev/null
+++ b/server/cmd/auth-server/main.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "crypto/rand"
+ "database/sql"
+ "encoding/hex"
+ "flag"
+ "log"
+ "net/http"
+
+ _ "modernc.org/sqlite"
+)
+
+func main() {
+ listen := flag.String("listen", "0.0.0.0:3000", "HTTP listen address (host:port)")
+ dbPath := flag.String("db", "db/auth.db", "SQLite database path for auth users")
+ secret := flag.String("secret", "", "HMAC secret for tokens (auto-generated if empty)")
+ flag.Parse()
+
+ hmacSecret := []byte(*secret)
+ if len(hmacSecret) == 0 {
+ hmacSecret = make([]byte, 32)
+ if _, err := rand.Read(hmacSecret); err != nil {
+ log.Fatalf("generate secret: %v", err)
+ }
+ log.Printf("generated HMAC secret: %s", hex.EncodeToString(hmacSecret))
+ log.Printf("pass --secret=%s to reuse across restarts", hex.EncodeToString(hmacSecret))
+ }
+
+ db, err := sql.Open("sqlite", *dbPath)
+ if err != nil {
+ log.Fatalf("open database: %v", err)
+ }
+ defer db.Close()
+
+ store, err := NewAuthStore(db)
+ if err != nil {
+ log.Fatalf("init auth store: %v", err)
+ }
+
+ tok := NewTokenService(hmacSecret)
+ h := NewHandlers(store, tok)
+
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", h.HandleOAuth)
+ mux.HandleFunc("/me", h.HandleMe)
+ mux.HandleFunc("/check-username", h.HandleCheckUsername)
+
+ log.Printf("auth server listening on %s", *listen)
+ if err := http.ListenAndServe(*listen, mux); err != nil {
+ log.Fatalf("listen: %v", err)
+ }
+}
diff --git a/server/cmd/auth-server/store.go b/server/cmd/auth-server/store.go
new file mode 100644
index 0000000..960255c
--- /dev/null
+++ b/server/cmd/auth-server/store.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "database/sql"
+ "errors"
+ "fmt"
+ "time"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+var (
+ ErrUserExists = errors.New("username already taken")
+ ErrInvalidCreds = errors.New("invalid username or password")
+)
+
+type AuthUser struct {
+ ID int64
+ Username string
+}
+
+type AuthStore struct {
+ db *sql.DB
+}
+
+func NewAuthStore(db *sql.DB) (*AuthStore, error) {
+ _, err := db.Exec(`
+ CREATE TABLE IF NOT EXISTS auth_users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL UNIQUE,
+ password BLOB NOT NULL,
+ created_at TEXT NOT NULL
+ )
+ `)
+ if err != nil {
+ return nil, fmt.Errorf("create auth_users table: %w", err)
+ }
+ return &AuthStore{db: db}, nil
+}
+
+func (s *AuthStore) CreateUser(username, password string) (AuthUser, error) {
+ hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ return AuthUser{}, fmt.Errorf("hash password: %w", err)
+ }
+
+ res, err := s.db.Exec(
+ `INSERT INTO auth_users (username, password, created_at) VALUES (?, ?, ?)`,
+ username, hash, time.Now().UTC().Format(time.RFC3339),
+ )
+ if err != nil {
+ if isUniqueViolation(err) {
+ return AuthUser{}, ErrUserExists
+ }
+ return AuthUser{}, fmt.Errorf("insert user: %w", err)
+ }
+
+ id, _ := res.LastInsertId()
+ return AuthUser{ID: id, Username: username}, nil
+}
+
+func (s *AuthStore) VerifyUser(username, password string) (AuthUser, error) {
+ var id int64
+ var hash []byte
+ err := s.db.QueryRow(
+ `SELECT id, password FROM auth_users WHERE username = ?`, username,
+ ).Scan(&id, &hash)
+ if err != nil {
+ return AuthUser{}, ErrInvalidCreds
+ }
+
+ if err := bcrypt.CompareHashAndPassword(hash, []byte(password)); err != nil {
+ return AuthUser{}, ErrInvalidCreds
+ }
+
+ return AuthUser{ID: id, Username: username}, nil
+}
+
+func (s *AuthStore) UserExists(username string) bool {
+ var n int
+ err := s.db.QueryRow(`SELECT 1 FROM auth_users WHERE username = ?`, username).Scan(&n)
+ return err == nil
+}
+
+func isUniqueViolation(err error) bool {
+ return err != nil && (errors.Is(err, sql.ErrNoRows) ||
+ // modernc.org/sqlite returns error strings containing "UNIQUE constraint failed"
+ fmt.Sprintf("%v", err) == fmt.Sprintf("%v", err) &&
+ contains(err.Error(), "UNIQUE constraint failed"))
+}
+
+func contains(s, substr string) bool {
+ return len(s) >= len(substr) && searchStr(s, substr)
+}
+
+func searchStr(s, sub string) bool {
+ for i := 0; i <= len(s)-len(sub); i++ {
+ if s[i:i+len(sub)] == sub {
+ return true
+ }
+ }
+ return false
+}
diff --git a/server/cmd/auth-server/token.go b/server/cmd/auth-server/token.go
new file mode 100644
index 0000000..30ded2a
--- /dev/null
+++ b/server/cmd/auth-server/token.go
@@ -0,0 +1,102 @@
+package main
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "time"
+)
+
+const tokenTTL = 24 * time.Hour
+
+var (
+ ErrTokenInvalid = errors.New("invalid token")
+ ErrTokenExpired = errors.New("token expired")
+)
+
+type TokenClaims struct {
+ Sub int64 `json:"sub"`
+ Name string `json:"name"`
+ Iat int64 `json:"iat"`
+ Exp int64 `json:"exp"`
+}
+
+type TokenService struct {
+ secret []byte
+}
+
+func NewTokenService(secret []byte) *TokenService {
+ return &TokenService{secret: secret}
+}
+
+func (t *TokenService) Generate(user AuthUser) (string, error) {
+ now := time.Now().Unix()
+ claims := TokenClaims{
+ Sub: user.ID,
+ Name: user.Username,
+ Iat: now,
+ Exp: now + int64(tokenTTL.Seconds()),
+ }
+
+ payload, err := json.Marshal(claims)
+ if err != nil {
+ return "", fmt.Errorf("marshal claims: %w", err)
+ }
+
+ enc := base64.RawURLEncoding
+ payloadB64 := enc.EncodeToString(payload)
+
+ mac := hmac.New(sha256.New, t.secret)
+ mac.Write(payload)
+ sig := enc.EncodeToString(mac.Sum(nil))
+
+ return payloadB64 + "." + sig, nil
+}
+
+func (t *TokenService) Validate(token string) (TokenClaims, error) {
+ dot := -1
+ for i := range token {
+ if token[i] == '.' {
+ dot = i
+ break
+ }
+ }
+ if dot < 0 {
+ return TokenClaims{}, ErrTokenInvalid
+ }
+
+ payloadB64 := token[:dot]
+ sigB64 := token[dot+1:]
+
+ enc := base64.RawURLEncoding
+
+ payload, err := enc.DecodeString(payloadB64)
+ if err != nil {
+ return TokenClaims{}, ErrTokenInvalid
+ }
+
+ sig, err := enc.DecodeString(sigB64)
+ if err != nil {
+ return TokenClaims{}, ErrTokenInvalid
+ }
+
+ mac := hmac.New(sha256.New, t.secret)
+ mac.Write(payload)
+ if !hmac.Equal(mac.Sum(nil), sig) {
+ return TokenClaims{}, ErrTokenInvalid
+ }
+
+ var claims TokenClaims
+ if err := json.Unmarshal(payload, &claims); err != nil {
+ return TokenClaims{}, ErrTokenInvalid
+ }
+
+ if time.Now().Unix() > claims.Exp {
+ return TokenClaims{}, ErrTokenExpired
+ }
+
+ return claims, nil
+}
diff --git a/server/cmd/claim-account/main.go b/server/cmd/claim-account/main.go
new file mode 100644
index 0000000..f93a32f
--- /dev/null
+++ b/server/cmd/claim-account/main.go
@@ -0,0 +1,166 @@
+package main
+
+import (
+ "database/sql"
+ "flag"
+ "fmt"
+ "log"
+
+ "lunar-tear/server/internal/database"
+)
+
+var childTables = []string{
+ "user_cage_ornament_rewards",
+ "user_shop_replaceable_lineup",
+ "user_shop_items",
+ "user_gacha_banner_box_drew_counts",
+ "user_gacha_banners",
+ "user_gacha_converted_medals",
+ "user_gifts",
+ "user_dokan_confirmed",
+ "user_drawn_omikuji",
+ "user_contents_stories",
+ "user_viewed_movies",
+ "user_navi_cutin_played",
+ "user_auto_sale_settings",
+ "user_explore_scores",
+ "user_tutorials",
+ "user_premium_items",
+ "user_important_items",
+ "user_materials",
+ "user_consumable_items",
+ "user_gimmick_unlocks",
+ "user_gimmick_sequences",
+ "user_gimmick_ornament_progress",
+ "user_gimmick_progress",
+ "user_big_hunt_weekly_statuses",
+ "user_big_hunt_weekly_max_scores",
+ "user_big_hunt_schedule_max_scores",
+ "user_big_hunt_statuses",
+ "user_big_hunt_max_scores",
+ "user_quest_limit_content_status",
+ "user_side_story_quests",
+ "user_missions",
+ "user_quest_missions",
+ "user_quests",
+ "user_deck_type_notes",
+ "user_deck_parts",
+ "user_deck_sub_weapons",
+ "user_decks",
+ "user_deck_characters",
+ "user_parts_presets",
+ "user_parts_group_notes",
+ "user_parts",
+ "user_thoughts",
+ "user_companions",
+ "user_weapon_notes",
+ "user_weapon_stories",
+ "user_weapon_awakens",
+ "user_weapon_abilities",
+ "user_weapon_skills",
+ "user_weapons",
+ "user_costume_awaken_status_ups",
+ "user_costume_active_skills",
+ "user_costumes",
+ "user_character_rebirths",
+ "user_character_board_status_ups",
+ "user_character_board_abilities",
+ "user_character_boards",
+ "user_characters",
+ "user_gacha",
+ "user_shop_replaceable",
+ "user_explore",
+ "user_guerrilla_free_open",
+ "user_portal_cage",
+ "user_notification",
+ "user_battle",
+ "user_big_hunt_state",
+ "user_side_story_active",
+ "user_extra_quest",
+ "user_event_quest",
+ "user_main_quest",
+ "user_login_bonus",
+ "user_login",
+ "user_profile",
+ "user_gem",
+ "user_status",
+ "user_setting",
+ "sessions",
+}
+
+func main() {
+ dbPath := flag.String("db", "db/game.db", "SQLite database path")
+ name := flag.String("name", "", "In-game player name to look up in user_profile (required)")
+ flag.Parse()
+
+ if *name == "" {
+ log.Fatal("--name flag is required")
+ }
+
+ db, err := database.Open(*dbPath)
+ if err != nil {
+ log.Fatalf("open database: %v", err)
+ }
+ defer db.Close()
+
+ var targetId int64
+ err = db.QueryRow(`SELECT user_id FROM user_profile WHERE name = ?`, *name).Scan(&targetId)
+ if err == sql.ErrNoRows {
+ log.Fatalf("no user found with name %q", *name)
+ }
+ if err != nil {
+ log.Fatalf("query user_profile: %v", err)
+ }
+
+ var targetUuid string
+ err = db.QueryRow(`SELECT uuid FROM users WHERE user_id = ?`, targetId).Scan(&targetUuid)
+ if err != nil {
+ log.Fatalf("query target uuid: %v", err)
+ }
+
+ var latestId int64
+ var latestUuid string
+ err = db.QueryRow(`SELECT user_id, uuid FROM users ORDER BY user_id DESC LIMIT 1`).Scan(&latestId, &latestUuid)
+ if err != nil {
+ log.Fatalf("query latest user: %v", err)
+ }
+
+ if targetId == latestId {
+ log.Printf("user %q (id=%d) is already the most recent user, nothing to do", *name, targetId)
+ return
+ }
+
+ log.Printf("target: id=%d uuid=%s (name=%q)", targetId, targetUuid, *name)
+ log.Printf("latest: id=%d uuid=%s (will be deleted)", latestId, latestUuid)
+
+ tx, err := db.Begin()
+ if err != nil {
+ log.Fatalf("begin transaction: %v", err)
+ }
+ defer tx.Rollback()
+
+ for _, t := range childTables {
+ if _, err := tx.Exec(fmt.Sprintf(`DELETE FROM %s WHERE user_id = ?`, t), latestId); err != nil {
+ log.Fatalf("delete from %s: %v", t, err)
+ }
+ }
+ if _, err := tx.Exec(`DELETE FROM users WHERE user_id = ?`, latestId); err != nil {
+ log.Fatalf("delete latest user: %v", err)
+ }
+
+ if _, err := tx.Exec(`UPDATE users SET uuid = ? WHERE user_id = ?`, latestUuid, targetId); err != nil {
+ log.Fatalf("update target uuid: %v", err)
+ }
+
+ if _, err := tx.Exec(`DELETE FROM sessions WHERE user_id = ?`, targetId); err != nil {
+ log.Fatalf("delete stale sessions: %v", err)
+ }
+
+ if err := tx.Commit(); err != nil {
+ log.Fatalf("commit: %v", err)
+ }
+
+ fmt.Printf("claimed account:\n")
+ fmt.Printf(" user %d (%s): uuid changed %s -> %s\n", targetId, *name, targetUuid, latestUuid)
+ fmt.Printf(" user %d: deleted\n", latestId)
+}
diff --git a/server/cmd/dev/color_unix.go b/server/cmd/dev/color_unix.go
new file mode 100644
index 0000000..20eb45b
--- /dev/null
+++ b/server/cmd/dev/color_unix.go
@@ -0,0 +1,13 @@
+//go:build !windows
+
+package main
+
+import (
+ "os"
+
+ "golang.org/x/term"
+)
+
+func colorSupported() bool {
+ return term.IsTerminal(int(os.Stdout.Fd()))
+}
diff --git a/server/cmd/dev/color_windows.go b/server/cmd/dev/color_windows.go
new file mode 100644
index 0000000..671e10c
--- /dev/null
+++ b/server/cmd/dev/color_windows.go
@@ -0,0 +1,22 @@
+//go:build windows
+
+package main
+
+import (
+ "os"
+
+ "golang.org/x/sys/windows"
+ "golang.org/x/term"
+)
+
+func colorSupported() bool {
+ fd := os.Stdout.Fd()
+ if !term.IsTerminal(int(fd)) {
+ return false
+ }
+ var mode uint32
+ if windows.GetConsoleMode(windows.Handle(fd), &mode) != nil {
+ return false
+ }
+ return windows.SetConsoleMode(windows.Handle(fd), mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil
+}
diff --git a/server/cmd/dev/main.go b/server/cmd/dev/main.go
new file mode 100644
index 0000000..707d5b4
--- /dev/null
+++ b/server/cmd/dev/main.go
@@ -0,0 +1,147 @@
+package main
+
+import (
+ "bufio"
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "os/signal"
+ "sync"
+ "syscall"
+)
+
+var (
+ colorReset = "\033[0m"
+ colorRed = "\033[31m"
+ colorGreen = "\033[32m"
+ colorYellow = "\033[33m"
+ colorCyan = "\033[36m"
+)
+
+type service struct {
+ label string
+ color string
+ cmd *exec.Cmd
+}
+
+func main() {
+ // auth-server flags
+ authListen := flag.String("auth.listen", "0.0.0.0:3000", "auth-server listen address (host:port)")
+ authDB := flag.String("auth.db", "db/auth.db", "auth-server SQLite database path")
+
+ // octo-cdn flags
+ cdnListen := flag.String("cdn.listen", "0.0.0.0:8080", "octo-cdn local bind address")
+ cdnPublicAddr := flag.String("cdn.public-addr", "10.0.2.2:8080", "octo-cdn externally-reachable address")
+
+ // lunar-tear (grpc) flags
+ grpcListen := flag.String("grpc.listen", "0.0.0.0:8003", "lunar-tear gRPC listen address (host:port)")
+ grpcPublicAddr := flag.String("grpc.public-addr", "10.0.2.2:8003", "lunar-tear externally-reachable address")
+ 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)")
+
+ noColor := flag.Bool("no-color", false, "disable colored output")
+ flag.Parse()
+
+ if *grpcOctoURL == "" {
+ *grpcOctoURL = fmt.Sprintf("http://%s", *cdnPublicAddr)
+ }
+ if *grpcAuthURL == "" {
+ *grpcAuthURL = fmt.Sprintf("http://%s", *authListen)
+ }
+
+ if *noColor || !colorSupported() {
+ colorReset = ""
+ colorRed = ""
+ colorGreen = ""
+ colorYellow = ""
+ colorCyan = ""
+ }
+
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer stop()
+
+ services := []service{
+ {
+ label: "auth",
+ color: colorGreen,
+ cmd: exec.CommandContext(ctx, "go", "run", "./cmd/auth-server",
+ "--listen", *authListen,
+ "--db", *authDB,
+ ),
+ },
+ {
+ label: "cdn",
+ color: colorCyan,
+ cmd: exec.CommandContext(ctx, "go", "run", "./cmd/octo-cdn",
+ "--listen", *cdnListen,
+ "--public-addr", *cdnPublicAddr,
+ ),
+ },
+ {
+ label: "grpc",
+ color: colorYellow,
+ cmd: exec.CommandContext(ctx, "go", "run", "./cmd/lunar-tear",
+ "--listen", *grpcListen,
+ "--public-addr", *grpcPublicAddr,
+ "--octo-url", *grpcOctoURL,
+ "--auth-url", *grpcAuthURL,
+ ),
+ },
+ }
+
+ var wg sync.WaitGroup
+ errCh := make(chan error, len(services))
+
+ for i := range services {
+ svc := &services[i]
+ stdout, err := svc.cmd.StdoutPipe()
+ if err != nil {
+ log.Fatalf("[%s] stdout pipe: %v", svc.label, err)
+ }
+ stderr, err := svc.cmd.StderrPipe()
+ if err != nil {
+ log.Fatalf("[%s] stderr pipe: %v", svc.label, err)
+ }
+
+ if err := svc.cmd.Start(); err != nil {
+ log.Fatalf("[%s] start: %v", svc.label, err)
+ }
+
+ prefix := fmt.Sprintf("%s[%s]%s ", svc.color, svc.label, colorReset)
+ wg.Add(2)
+ go prefixLines(&wg, prefix, stdout)
+ go prefixLines(&wg, prefix, stderr)
+
+ wg.Add(1)
+ go func(s *service) {
+ defer wg.Done()
+ if err := s.cmd.Wait(); err != nil {
+ errCh <- fmt.Errorf("[%s] %w", s.label, err)
+ }
+ }(svc)
+
+ log.Printf("%s%s started (pid %d)%s", svc.color, svc.label, svc.cmd.Process.Pid, colorReset)
+ }
+
+ select {
+ case <-ctx.Done():
+ log.Println("shutting down all services...")
+ case err := <-errCh:
+ log.Printf("%s%s%s", colorRed, err, colorReset)
+ stop()
+ }
+
+ wg.Wait()
+}
+
+func prefixLines(wg *sync.WaitGroup, prefix string, r io.Reader) {
+ defer wg.Done()
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ fmt.Printf("%s%s\n", prefix, scanner.Text())
+ }
+}
diff --git a/server/cmd/lunar-tear/grpc.go b/server/cmd/lunar-tear/grpc.go
index 7abd6a8..876f2a9 100644
--- a/server/cmd/lunar-tear/grpc.go
+++ b/server/cmd/lunar-tear/grpc.go
@@ -1,24 +1,20 @@
package main
import (
- "context"
- "fmt"
"log"
"net"
+ "strconv"
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gacha"
- "lunar-tear/server/internal/gametime"
+ "lunar-tear/server/internal/interceptor"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/service"
"lunar-tear/server/internal/store"
"google.golang.org/grpc"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
- "google.golang.org/grpc/status"
)
type loggingListener struct {
@@ -36,9 +32,10 @@ func (l loggingListener) Accept() (net.Conn, error) {
}
func startGRPC(
- host string,
- grpcPort int,
+ listenAddr string,
+ publicAddr string,
octoURL string,
+ authURL string,
userStore interface {
store.UserRepository
store.SessionRepository
@@ -64,23 +61,23 @@ func startGRPC(
gameConfig *masterdata.GameConfig,
sideStoryCatalog *masterdata.SideStoryCatalog,
bigHuntCatalog *masterdata.BigHuntCatalog,
-) {
- addr := fmt.Sprintf(":%d", grpcPort)
- lis, err := net.Listen("tcp", addr)
+) *grpc.Server {
+ lis, err := net.Listen("tcp", listenAddr)
if err != nil {
- log.Fatalf("failed to listen on %s: %v", addr, err)
+ log.Fatalf("failed to listen on %s: %v", listenAddr, err)
}
lis = loggingListener{Listener: lis}
+ diffInterceptor := interceptor.NewDiffInterceptor(userStore, userStore)
grpcServer := grpc.NewServer(
- grpc.ChainUnaryInterceptor(loggingInterceptor, timeSyncInterceptor),
- grpc.UnknownServiceHandler(loggingUnknownService),
+ grpc.ChainUnaryInterceptor(interceptor.Platform, interceptor.Logging, diffInterceptor, interceptor.TimeSync),
+ grpc.UnknownServiceHandler(interceptor.UnknownService),
)
registerServices(grpcServer,
- host,
- grpcPort,
+ publicAddr,
octoURL,
+ authURL,
userStore,
questEngine,
gachaHandler,
@@ -107,19 +104,22 @@ func startGRPC(
reflection.Register(grpcServer)
- log.Printf("gRPC server listening on %s", addr)
- log.Printf("client host address: %s:%d", host, grpcPort)
+ log.Printf("gRPC server listening on %s", lis.Addr())
+ log.Printf("public address: %s", publicAddr)
- if err := grpcServer.Serve(lis); err != nil {
- log.Fatalf("failed to serve: %v", err)
- }
+ go func() {
+ if err := grpcServer.Serve(lis); err != nil {
+ log.Printf("gRPC server stopped: %v", err)
+ }
+ }()
+ return grpcServer
}
func registerServices(
srv *grpc.Server,
- host string,
- grpcPort int,
+ publicAddr string,
octoURL string,
+ authURL string,
userStore interface {
store.UserRepository
store.SessionRepository
@@ -146,10 +146,13 @@ func registerServices(
sideStoryCatalog *masterdata.SideStoryCatalog,
bigHuntCatalog *masterdata.BigHuntCatalog,
) {
+ pubHost, pubPortStr, _ := net.SplitHostPort(publicAddr)
+ pubPort, _ := strconv.Atoi(pubPortStr)
+
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(gachaEntries))
- pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore))
+ pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL))
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
- pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(host, int32(grpcPort), octoURL))
+ pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(pubHost, int32(pubPort), octoURL))
pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, questEngine))
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, gachaEntries, gachaHandler))
@@ -184,39 +187,3 @@ func registerServices(
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, bigHuntCatalog, questEngine))
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, bigHuntCatalog, questEngine.Granter))
}
-
-func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
- log.Printf(">>> %s", info.FullMethod)
- resp, err := handler(ctx, req)
- if err != nil {
- log.Printf("<<< %s ERROR: %v", info.FullMethod, err)
- } else {
- log.Printf("<<< %s OK", info.FullMethod)
- }
- return resp, err
-}
-
-func timeSyncInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
- resp, err := handler(ctx, req)
- switch info.FullMethod {
- case "/apb.api.user.UserService/Auth",
- "/apb.api.user.UserService/RegisterUser",
- "/apb.api.user.UserService/TransferUser":
- default:
- grpc.SetTrailer(ctx, metadata.Pairs(
- "x-apb-response-datetime", fmt.Sprintf("%d", gametime.NowMillis()),
- ))
- }
- return resp, err
-}
-
-func loggingUnknownService(_ any, stream grpc.ServerStream) error {
- fullMethod, ok := grpc.MethodFromServerStream(stream)
- if !ok {
- fullMethod = ""
- }
- log.Printf(">>> %s", fullMethod)
- err := status.Errorf(codes.Unimplemented, "unknown service or method %s", fullMethod)
- log.Printf("<<< %s ERROR: %v", fullMethod, err)
- return err
-}
diff --git a/server/cmd/lunar-tear/http.go b/server/cmd/lunar-tear/http.go
deleted file mode 100644
index c6e9ba6..0000000
--- a/server/cmd/lunar-tear/http.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package main
-
-import (
- "fmt"
- "log"
- "net/http"
-
- "lunar-tear/server/internal/service"
-
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
-)
-
-func startHTTP(port int, resourcesBaseURL string) {
- octoServer := service.NewOctoHTTPServer(resourcesBaseURL)
- h2s := &http2.Server{}
- octoHandler := h2c.NewHandler(octoServer.Handler(), h2s)
- log.Printf("Octo HTTP server listening on :%d (HTTP/1.1 + h2c)", port)
- srv := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: octoHandler}
- http2.ConfigureServer(srv, h2s)
- if err := srv.ListenAndServe(); err != nil {
- log.Fatalf("HTTP server on %d failed: %v", port, err)
- }
-}
diff --git a/server/cmd/lunar-tear/main.go b/server/cmd/lunar-tear/main.go
index 5d20bd4..69f1953 100644
--- a/server/cmd/lunar-tear/main.go
+++ b/server/cmd/lunar-tear/main.go
@@ -1,37 +1,41 @@
package main
import (
+ "context"
"flag"
"log"
- "strconv"
- "strings"
+ "os"
+ "os/signal"
+ "syscall"
"lunar-tear/server/internal/database"
"lunar-tear/server/internal/gacha"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
+ "lunar-tear/server/internal/masterdata/memorydb"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store/sqlite"
)
func main() {
- httpPort := flag.Int("http-port", 8080, "HTTP server port (Octo API)")
- grpcPort := flag.Int("grpc-port", 443, "gRPC server port")
- host := flag.String("host", "127.0.0.1", "hostname the client will connect to")
+ 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")
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)")
+ authURL := flag.String("auth-url", "", "Auth server base URL for Facebook token validation (e.g. http://localhost:3000)")
flag.Parse()
- octoURL := "http://" + *host + ":" + strconv.Itoa(*httpPort)
- prefix := octoURL + "/"
- padLen := 43 - len(prefix)
- resourcesBaseURL := ""
- if padLen < 1 {
- log.Printf("[config] host:port too long for 43-char resource URL; list.bin will be served unchanged")
- } else {
- resourcesBaseURL = prefix + strings.Repeat("r", padLen)
+ if *octoURL == "" {
+ log.Fatalf("--octo-url is required (e.g. http://10.0.2.2:8080)")
}
- go startHTTP(*httpPort, resourcesBaseURL)
+ if err := memorydb.Init("assets/release/20240404193219.bin.e"); err != nil {
+ log.Fatalf("load master data: %v", err)
+ }
+ log.Printf("master data loaded (%d tables)", memorydb.TableCount())
+
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer stop()
db, err := database.Open(*dbPath)
if err != nil {
@@ -164,10 +168,11 @@ func main() {
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
- startGRPC(
- *host,
- *grpcPort,
- octoURL,
+ grpcServer := startGRPC(
+ *listen,
+ *publicAddr,
+ *octoURL,
+ *authURL,
userStore,
questHandler,
gachaHandler,
@@ -191,4 +196,12 @@ func main() {
sideStoryCatalog,
bigHuntCatalog,
)
+
+ <-ctx.Done()
+ log.Println("shutting down...")
+
+ grpcServer.GracefulStop()
+ database.Checkpoint(db)
+
+ log.Println("shutdown complete")
}
diff --git a/server/cmd/octo-cdn/main.go b/server/cmd/octo-cdn/main.go
new file mode 100644
index 0000000..112d509
--- /dev/null
+++ b/server/cmd/octo-cdn/main.go
@@ -0,0 +1,72 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "os/signal"
+ "strings"
+ "syscall"
+
+ "lunar-tear/server/internal/service"
+
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/h2c"
+)
+
+func main() {
+ listen := flag.String("listen", "0.0.0.0:8080", "local bind address (host:port)")
+ publicAddr := flag.String("public-addr", "127.0.0.1:8080", "externally-reachable host:port used for list.bin URL rewriting")
+ assetsDir := flag.String("assets-dir", ".", "root directory containing the assets/ tree")
+ flag.Parse()
+
+ // Build resourcesBaseURL from public-addr (must be exactly 43 chars to fit in list.bin protobuf).
+ prefix := "http://" + *publicAddr + "/"
+ padLen := 43 - len(prefix)
+ resourcesBaseURL := ""
+ if padLen < 1 {
+ log.Printf("[config] public-addr too long for 43-char resource URL; list.bin will be served unchanged")
+ } else {
+ resourcesBaseURL = prefix + strings.Repeat("r", padLen)
+ }
+
+ octoServer := service.NewOctoHTTPServer(resourcesBaseURL, *assetsDir)
+ h2s := &http2.Server{}
+ handler := h2c.NewHandler(octoServer.Handler(), h2s)
+
+ srv := &http.Server{
+ Addr: *listen,
+ Handler: handler,
+ }
+ http2.ConfigureServer(srv, h2s)
+
+ // Resolve actual listen address for logging (useful when port is 0).
+ lis, err := net.Listen("tcp", *listen)
+ if err != nil {
+ log.Fatalf("failed to listen on %s: %v", *listen, err)
+ }
+ log.Printf("Octo CDN listening on %s (HTTP/1.1 + h2c)", lis.Addr())
+ log.Printf("public address: %s", *publicAddr)
+ if *assetsDir != "." {
+ log.Printf("assets directory: %s", *assetsDir)
+ }
+
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer stop()
+
+ go func() {
+ if err := srv.Serve(lis); err != nil && err != http.ErrServerClosed {
+ fmt.Fprintf(os.Stderr, "HTTP server error: %v\n", err)
+ os.Exit(1)
+ }
+ }()
+
+ <-ctx.Done()
+ log.Println("shutting down...")
+ srv.Shutdown(context.Background())
+ log.Println("shutdown complete")
+}
diff --git a/server/docker-compose.yaml b/server/docker-compose.yaml
index d3b62cd..797528c 100644
--- a/server/docker-compose.yaml
+++ b/server/docker-compose.yaml
@@ -1,15 +1,41 @@
+name: lunar-tear
+
services:
server:
build: .
image: kretts/lunar-tear:latest
environment:
- LUNAR_HOST: 127.0.0.1
- LUNAR_HTTP_PORT: 8080
- LUNAR_GRPC_PORT: 8003
+ LUNAR_LISTEN: 0.0.0.0:8003
+ LUNAR_PUBLIC_ADDR: 127.0.0.1:8003
+ LUNAR_OCTO_URL: http://cdn:8080
+ LUNAR_AUTH_URL: http://auth:3000
volumes:
- - ./assets:/opt/lunar-tear/assets
- ./db:/opt/lunar-tear/db
+ - ./assets:/opt/lunar-tear/assets
ports:
- 8003:8003
+ depends_on:
+ - cdn
+ - auth
+
+ cdn:
+ build:
+ context: .
+ dockerfile: Dockerfile.cdn
+ image: kretts/octo-cdn:latest
+ command: ["--listen", "0.0.0.0:8080", "--public-addr", "10.0.2.2:8080"]
+ volumes:
+ - ./assets:/opt/octo-cdn/assets
+ ports:
- 8080:8080
+ auth:
+ build:
+ context: .
+ dockerfile: Dockerfile.auth
+ image: kretts/auth-server:latest
+ command: ["--listen", "0.0.0.0:3000", "--db", "db/auth.db"]
+ volumes:
+ - ./db:/opt/auth-server/db
+ ports:
+ - 3000:3000
diff --git a/server/entrypoint.sh b/server/entrypoint.sh
index 1906180..df7ee90 100755
--- a/server/entrypoint.sh
+++ b/server/entrypoint.sh
@@ -4,4 +4,13 @@ set -e
mkdir -p db
goose -dir migrations sqlite3 db/game.db up
-exec ./lunar-tear --host "${LUNAR_HOST}" --http-port "${LUNAR_HTTP_PORT}" --grpc-port "${LUNAR_GRPC_PORT:-443}"
+AUTH_FLAG=""
+if [ -n "${LUNAR_AUTH_URL}" ]; then
+ AUTH_FLAG="--auth-url ${LUNAR_AUTH_URL}"
+fi
+
+exec ./lunar-tear \
+ --listen "${LUNAR_LISTEN:-0.0.0.0:443}" \
+ --public-addr "${LUNAR_PUBLIC_ADDR}" \
+ --octo-url "${LUNAR_OCTO_URL}" \
+ ${AUTH_FLAG}
diff --git a/server/go.mod b/server/go.mod
index 2fdd93a..71c5ac6 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -4,30 +4,28 @@ go 1.25.0
require (
github.com/google/uuid v1.6.0
+ github.com/pierrec/lz4/v4 v4.1.26
github.com/vmihailenco/msgpack/v5 v5.4.1
- golang.org/x/net v0.50.0
+ golang.org/x/crypto v0.50.0
+ golang.org/x/net v0.52.0
+ golang.org/x/sys v0.43.0
+ golang.org/x/term v0.42.0
google.golang.org/grpc v1.79.1
google.golang.org/protobuf v1.36.11
+ modernc.org/sqlite v1.48.2
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mfridman/interpolate v0.0.2 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
- github.com/pressly/goose/v3 v3.27.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
- github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
- go.uber.org/multierr v1.11.0 // indirect
- golang.org/x/sync v0.19.0 // indirect
- golang.org/x/sys v0.42.0 // indirect
- golang.org/x/text v0.34.0 // indirect
+ golang.org/x/text v0.36.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
modernc.org/libc v1.70.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
- modernc.org/sqlite v1.48.2 // indirect
)
diff --git a/server/go.sum b/server/go.sum
index e52cbf7..17f46ef 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -12,22 +12,22 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
-github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
+github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
-github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
-github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
@@ -46,17 +46,23 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
-golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
-golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
-golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
+golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
+golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
+golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
+golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
+golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
+golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
-golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
-golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
+golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
+golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
+golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
+golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
+golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
+golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
@@ -67,11 +73,31 @@ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBN
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
+modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
+modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
+modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
+modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
+modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
+modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
+modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
+modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
+modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
+modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
+modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
+modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
+modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
+modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
+modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
+modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
+modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
diff --git a/server/internal/database/database.go b/server/internal/database/database.go
index 7649895..06330fd 100644
--- a/server/internal/database/database.go
+++ b/server/internal/database/database.go
@@ -3,6 +3,7 @@ package database
import (
"database/sql"
"fmt"
+ "log"
"os"
"path/filepath"
@@ -40,3 +41,9 @@ func Open(path string) (*sql.DB, error) {
return db, nil
}
+
+func Checkpoint(db *sql.DB) {
+ if _, err := db.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil {
+ log.Printf("WAL checkpoint: %v", err)
+ }
+}
diff --git a/server/internal/interceptor/diff.go b/server/internal/interceptor/diff.go
new file mode 100644
index 0000000..21be977
--- /dev/null
+++ b/server/internal/interceptor/diff.go
@@ -0,0 +1,130 @@
+package interceptor
+
+import (
+ "context"
+ "log"
+ "reflect"
+ "sort"
+ "strings"
+ "sync"
+
+ pb "lunar-tear/server/gen/proto"
+ "lunar-tear/server/internal/service"
+ "lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/userdata"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+)
+
+type diffUserDataGetter interface {
+ GetDiffUserData() map[string]*pb.DiffData
+}
+
+var (
+ diffFieldCache sync.Map // map[reflect.Type]bool
+ diffDataMapTyp = reflect.TypeFor[map[string]*pb.DiffData]()
+)
+
+func hasDiffField(resp any) bool {
+ t := reflect.TypeOf(resp)
+ if cached, ok := diffFieldCache.Load(t); ok {
+ return cached.(bool)
+ }
+ elem := t
+ if elem.Kind() == reflect.Pointer {
+ elem = elem.Elem()
+ }
+ f, ok := elem.FieldByName("DiffUserData")
+ result := ok && f.Type == diffDataMapTyp
+ diffFieldCache.Store(t, result)
+ return result
+}
+
+func NewDiffInterceptor(
+ users store.UserRepository,
+ sessions store.SessionRepository,
+) grpc.UnaryServerInterceptor {
+ return func(
+ ctx context.Context,
+ req any,
+ info *grpc.UnaryServerInfo,
+ handler grpc.UnaryHandler,
+ ) (any, error) {
+ if skipDiffForMethod(info.FullMethod) {
+ return handler(ctx, req)
+ }
+
+ userId := service.CurrentUserId(ctx, users, sessions)
+ if userId == 0 {
+ return handler(ctx, req)
+ }
+
+ before, err := users.LoadUser(userId)
+ if err != nil {
+ return handler(ctx, req)
+ }
+
+ resp, handlerErr := handler(ctx, req)
+ if handlerErr != nil || resp == nil {
+ return resp, handlerErr
+ }
+
+ if !hasDiffField(resp) {
+ return resp, nil
+ }
+
+ if getter, ok := resp.(diffUserDataGetter); ok {
+ if existing := getter.GetDiffUserData(); len(existing) > 0 {
+ setUpdateNamesTrailer(ctx, existing)
+ return resp, nil
+ }
+ }
+
+ after, err := users.LoadUser(userId)
+ if err != nil {
+ return resp, nil
+ }
+
+ changed := userdata.ChangedTables(&before, &after)
+ if len(changed) == 0 {
+ return resp, nil
+ }
+
+ diff := userdata.ComputeDelta(&before, &after, changed)
+ reflect.ValueOf(resp).Elem().FieldByName("DiffUserData").Set(reflect.ValueOf(diff))
+ setUpdateNamesTrailer(ctx, diff)
+
+ return resp, nil
+ }
+}
+
+func skipDiffForMethod(method string) bool {
+ switch method {
+ case "/apb.api.user.UserService/Auth",
+ "/apb.api.user.UserService/RegisterUser",
+ "/apb.api.user.UserService/TransferUser",
+ "/apb.api.user.UserService/TransferUserByFacebook",
+ "/apb.api.config.ConfigService/GetConfig",
+ "/apb.api.data.DataService/GetLatestMasterDataVersion",
+ "/apb.api.data.DataService/GetUserDataNameV2",
+ "/apb.api.data.DataService/GetUserData":
+ return true
+ }
+ return false
+}
+
+func setUpdateNamesTrailer(ctx context.Context, diff map[string]*pb.DiffData) {
+ if len(diff) == 0 {
+ return
+ }
+ keys := make([]string, 0, len(diff))
+ for key := range diff {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ value := strings.Join(keys, ",")
+ if err := grpc.SetTrailer(ctx, metadata.Pairs("x-apb-update-user-data-names", value)); err != nil {
+ log.Printf("[DiffInterceptor] failed to set trailer: %v", err)
+ }
+}
diff --git a/server/internal/interceptor/logging.go b/server/internal/interceptor/logging.go
new file mode 100644
index 0000000..39dd6dd
--- /dev/null
+++ b/server/internal/interceptor/logging.go
@@ -0,0 +1,32 @@
+package interceptor
+
+import (
+ "context"
+ "log"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+func Logging(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
+ log.Printf(">>> %s", info.FullMethod)
+ resp, err := handler(ctx, req)
+ if err != nil {
+ log.Printf("<<< %s ERROR: %v", info.FullMethod, err)
+ } else {
+ log.Printf("<<< %s OK", info.FullMethod)
+ }
+ return resp, err
+}
+
+func UnknownService(_ any, stream grpc.ServerStream) error {
+ fullMethod, ok := grpc.MethodFromServerStream(stream)
+ if !ok {
+ fullMethod = ""
+ }
+ log.Printf(">>> %s", fullMethod)
+ err := status.Errorf(codes.Unimplemented, "unknown service or method %s", fullMethod)
+ log.Printf("<<< %s ERROR: %v", fullMethod, err)
+ return err
+}
diff --git a/server/internal/interceptor/platform.go b/server/internal/interceptor/platform.go
new file mode 100644
index 0000000..6756e2f
--- /dev/null
+++ b/server/internal/interceptor/platform.go
@@ -0,0 +1,15 @@
+package interceptor
+
+import (
+ "context"
+
+ "lunar-tear/server/internal/model"
+
+ "google.golang.org/grpc"
+)
+
+func Platform(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
+ p := model.ClientPlatformFromHeaders(ctx)
+ ctx = model.NewContextWithPlatform(ctx, p)
+ return handler(ctx, req)
+}
diff --git a/server/internal/interceptor/timesync.go b/server/internal/interceptor/timesync.go
new file mode 100644
index 0000000..9f2b978
--- /dev/null
+++ b/server/internal/interceptor/timesync.go
@@ -0,0 +1,25 @@
+package interceptor
+
+import (
+ "context"
+ "fmt"
+
+ "lunar-tear/server/internal/gametime"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+)
+
+func TimeSync(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
+ resp, err := handler(ctx, req)
+ switch info.FullMethod {
+ case "/apb.api.user.UserService/Auth",
+ "/apb.api.user.UserService/RegisterUser",
+ "/apb.api.user.UserService/TransferUser":
+ default:
+ grpc.SetTrailer(ctx, metadata.Pairs(
+ "x-apb-response-datetime", fmt.Sprintf("%d", gametime.NowMillis()),
+ ))
+ }
+ return resp, err
+}
diff --git a/server/internal/masterdata/bighunt.go b/server/internal/masterdata/bighunt.go
index 96100e2..6d24c4e 100644
--- a/server/internal/masterdata/bighunt.go
+++ b/server/internal/masterdata/bighunt.go
@@ -8,15 +8,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type bigHuntBossQuestRow struct {
- BigHuntBossQuestId int32 `json:"BigHuntBossQuestId"`
- BigHuntBossId int32 `json:"BigHuntBossId"`
- BigHuntQuestGroupId int32 `json:"BigHuntQuestGroupId"`
- BigHuntBossQuestScoreCoefficientId int32 `json:"BigHuntBossQuestScoreCoefficientId"`
- BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"`
- DailyChallengeCount int32 `json:"DailyChallengeCount"`
-}
-
type BigHuntBossQuestRow struct {
BigHuntBossQuestId int32
BigHuntBossId int32
@@ -25,97 +16,39 @@ type BigHuntBossQuestRow struct {
DailyChallengeCount int32
}
-type bigHuntQuestRow struct {
- BigHuntQuestId int32 `json:"BigHuntQuestId"`
- QuestId int32 `json:"QuestId"`
- BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"`
-}
-
type BigHuntQuestRow struct {
BigHuntQuestId int32
QuestId int32
BigHuntQuestScoreCoefficientId int32
}
-type bigHuntQuestScoreCoefficientRow struct {
- BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"`
- ScoreDifficultBonusPermil int32 `json:"ScoreDifficultBonusPermil"`
-}
-
-type bigHuntBossRow struct {
- BigHuntBossId int32 `json:"BigHuntBossId"`
- BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"`
- AttributeType int32 `json:"AttributeType"`
-}
-
type BigHuntBossRow struct {
BigHuntBossId int32
BigHuntBossGradeGroupId int32
AttributeType int32
}
-type bigHuntBossGradeGroupRow struct {
- BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"`
- NecessaryScore int64 `json:"NecessaryScore"`
- AssetGradeIconId int32 `json:"AssetGradeIconId"`
-}
-
type GradeThreshold struct {
NecessaryScore int64
AssetGradeIconId int32
}
-type bigHuntScheduleRow struct {
- BigHuntScheduleId int32 `json:"BigHuntScheduleId"`
- ChallengeStartDatetime int64 `json:"ChallengeStartDatetime"`
- ChallengeEndDatetime int64 `json:"ChallengeEndDatetime"`
-}
-
-type scoreRewardScheduleRow struct {
- BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"`
- GroupIndex int32 `json:"GroupIndex"`
- BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
- StartDatetime int64 `json:"StartDatetime"`
-}
-
type ScoreRewardScheduleEntry struct {
BigHuntScoreRewardGroupId int32
StartDatetime int64
}
-type scoreRewardGroupRow struct {
- BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
- NecessaryScore int64 `json:"NecessaryScore"`
- BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"`
-}
-
type ScoreRewardThreshold struct {
NecessaryScore int64
BigHuntRewardGroupId int32
}
-type rewardGroupRow struct {
- BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"`
- SortOrder int32 `json:"SortOrder"`
- PossessionType int32 `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
type RewardItem struct {
PossessionType int32
PossessionId int32
Count int32
}
-type weeklyRewardScheduleRow struct {
- BigHuntWeeklyAttributeScoreRewardGroupScheduleId int32 `json:"BigHuntWeeklyAttributeScoreRewardGroupScheduleId"`
- AttributeType int32 `json:"AttributeType"`
- GroupIndex int32 `json:"GroupIndex"`
- BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
- StartDatetime int64 `json:"StartDatetime"`
-}
-
type BigHuntWeeklyRewardKey struct {
ScheduleId int32
AttributeType int32
@@ -189,7 +122,7 @@ func (c *BigHuntCatalog) CollectNewRewards(scoreRewardGroupId int32, oldMax, new
}
func LoadBigHuntCatalog() *BigHuntCatalog {
- bossQuestRows, err := utils.ReadJSON[bigHuntBossQuestRow]("EntityMBigHuntBossQuestTable.json")
+ bossQuestRows, err := utils.ReadTable[EntityMBigHuntBossQuest]("m_big_hunt_boss_quest")
if err != nil {
log.Fatalf("load big hunt boss quest table: %v", err)
}
@@ -204,7 +137,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
}
}
- questRows, err := utils.ReadJSON[bigHuntQuestRow]("EntityMBigHuntQuestTable.json")
+ questRows, err := utils.ReadTable[EntityMBigHuntQuest]("m_big_hunt_quest")
if err != nil {
log.Fatalf("load big hunt quest table: %v", err)
}
@@ -217,7 +150,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
}
}
- coeffRows, err := utils.ReadJSON[bigHuntQuestScoreCoefficientRow]("EntityMBigHuntQuestScoreCoefficientTable.json")
+ coeffRows, err := utils.ReadTable[EntityMBigHuntQuestScoreCoefficient]("m_big_hunt_quest_score_coefficient")
if err != nil {
log.Fatalf("load big hunt quest score coefficient table: %v", err)
}
@@ -226,7 +159,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
scoreCoefficients[r.BigHuntQuestScoreCoefficientId] = r.ScoreDifficultBonusPermil
}
- bossRows, err := utils.ReadJSON[bigHuntBossRow]("EntityMBigHuntBossTable.json")
+ bossRows, err := utils.ReadTable[EntityMBigHuntBoss]("m_big_hunt_boss")
if err != nil {
log.Fatalf("load big hunt boss table: %v", err)
}
@@ -239,7 +172,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
}
}
- gradeRows, err := utils.ReadJSON[bigHuntBossGradeGroupRow]("EntityMBigHuntBossGradeGroupTable.json")
+ gradeRows, err := utils.ReadTable[EntityMBigHuntBossGradeGroup]("m_big_hunt_boss_grade_group")
if err != nil {
log.Fatalf("load big hunt boss grade group table: %v", err)
}
@@ -256,7 +189,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
})
}
- scheduleRows, err := utils.ReadJSON[bigHuntScheduleRow]("EntityMBigHuntScheduleTable.json")
+ scheduleRows, err := utils.ReadTable[EntityMBigHuntSchedule]("m_big_hunt_schedule")
if err != nil {
log.Fatalf("load big hunt schedule table: %v", err)
}
@@ -274,7 +207,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
}
}
- rewardSchedRows, err := utils.ReadJSON[scoreRewardScheduleRow]("EntityMBigHuntScoreRewardGroupScheduleTable.json")
+ rewardSchedRows, err := utils.ReadTable[EntityMBigHuntScoreRewardGroupSchedule]("m_big_hunt_score_reward_group_schedule")
if err != nil {
log.Fatalf("load big hunt score reward group schedule table: %v", err)
}
@@ -294,7 +227,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
})
}
- rewardGroupRows, err := utils.ReadJSON[scoreRewardGroupRow]("EntityMBigHuntScoreRewardGroupTable.json")
+ rewardGroupRows, err := utils.ReadTable[EntityMBigHuntScoreRewardGroup]("m_big_hunt_score_reward_group")
if err != nil {
log.Fatalf("load big hunt score reward group table: %v", err)
}
@@ -314,7 +247,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
})
}
- rewardItemRows, err := utils.ReadJSON[rewardGroupRow]("EntityMBigHuntRewardGroupTable.json")
+ rewardItemRows, err := utils.ReadTable[EntityMBigHuntRewardGroup]("m_big_hunt_reward_group")
if err != nil {
log.Fatalf("load big hunt reward group table: %v", err)
}
@@ -327,7 +260,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
})
}
- weeklySchedRows, err := utils.ReadJSON[weeklyRewardScheduleRow]("EntityMBigHuntWeeklyAttributeScoreRewardGroupScheduleTable.json")
+ weeklySchedRows, err := utils.ReadTable[EntityMBigHuntWeeklyAttributeScoreRewardGroupSchedule]("m_big_hunt_weekly_attribute_score_reward_group_schedule")
if err != nil {
log.Fatalf("load big hunt weekly attribute score reward group schedule table: %v", err)
}
diff --git a/server/internal/masterdata/cageornament.go b/server/internal/masterdata/cageornament.go
index b1fd85f..64645c7 100644
--- a/server/internal/masterdata/cageornament.go
+++ b/server/internal/masterdata/cageornament.go
@@ -5,18 +5,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type cageOrnament struct {
- CageOrnamentId int32 `json:"CageOrnamentId"`
- CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"`
-}
-
-type cageOrnamentRewardRow struct {
- CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"`
- PossessionType int32 `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
type CageOrnamentReward struct {
PossessionType int32
PossessionId int32
@@ -38,11 +26,11 @@ func (c *CageOrnamentCatalog) LookupReward(cageOrnamentId int32) (CageOrnamentRe
}
func LoadCageOrnamentCatalog() *CageOrnamentCatalog {
- ornaments, err := utils.ReadJSON[cageOrnament]("EntityMCageOrnamentTable.json")
+ ornaments, err := utils.ReadTable[EntityMCageOrnament]("m_cage_ornament")
if err != nil {
log.Fatalf("load cage ornament table: %v", err)
}
- rewards, err := utils.ReadJSON[cageOrnamentRewardRow]("EntityMCageOrnamentRewardTable.json")
+ rewards, err := utils.ReadTable[EntityMCageOrnamentReward]("m_cage_ornament_reward")
if err != nil {
log.Fatalf("load cage ornament reward table: %v", err)
}
diff --git a/server/internal/masterdata/character_rebirth.go b/server/internal/masterdata/character_rebirth.go
index 821e9da..0a67908 100644
--- a/server/internal/masterdata/character_rebirth.go
+++ b/server/internal/masterdata/character_rebirth.go
@@ -6,24 +6,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type CharacterRebirthRow struct {
- CharacterId int32 `json:"CharacterId"`
- CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"`
-}
-
-type CharacterRebirthStepRow struct {
- CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"`
- BeforeRebirthCount int32 `json:"BeforeRebirthCount"`
- CostumeLevelLimitUp int32 `json:"CostumeLevelLimitUp"`
- CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"`
-}
-
-type CharacterRebirthMaterialRow struct {
- CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
-}
-
type StepKey struct {
GroupId int32
BeforeRebirthCount int32
@@ -31,22 +13,22 @@ type StepKey struct {
type CharacterRebirthCatalog struct {
StepGroupByCharacterId map[int32]int32
- StepByGroupAndCount map[StepKey]CharacterRebirthStepRow
- MaterialsByGroupId map[int32][]CharacterRebirthMaterialRow
+ StepByGroupAndCount map[StepKey]EntityMCharacterRebirthStepGroup
+ MaterialsByGroupId map[int32][]EntityMCharacterRebirthMaterialGroup
}
func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
- rebirthRows, err := utils.ReadJSON[CharacterRebirthRow]("EntityMCharacterRebirthTable.json")
+ rebirthRows, err := utils.ReadTable[EntityMCharacterRebirth]("m_character_rebirth")
if err != nil {
return nil, fmt.Errorf("load character rebirth table: %w", err)
}
- stepRows, err := utils.ReadJSON[CharacterRebirthStepRow]("EntityMCharacterRebirthStepGroupTable.json")
+ stepRows, err := utils.ReadTable[EntityMCharacterRebirthStepGroup]("m_character_rebirth_step_group")
if err != nil {
return nil, fmt.Errorf("load character rebirth step group table: %w", err)
}
- materialRows, err := utils.ReadJSON[CharacterRebirthMaterialRow]("EntityMCharacterRebirthMaterialGroupTable.json")
+ materialRows, err := utils.ReadTable[EntityMCharacterRebirthMaterialGroup]("m_character_rebirth_material_group")
if err != nil {
return nil, fmt.Errorf("load character rebirth material group table: %w", err)
}
@@ -56,12 +38,12 @@ func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
stepGroupByCharacterId[r.CharacterId] = r.CharacterRebirthStepGroupId
}
- stepByGroupAndCount := make(map[StepKey]CharacterRebirthStepRow, len(stepRows))
+ stepByGroupAndCount := make(map[StepKey]EntityMCharacterRebirthStepGroup, len(stepRows))
for _, s := range stepRows {
stepByGroupAndCount[StepKey{GroupId: s.CharacterRebirthStepGroupId, BeforeRebirthCount: s.BeforeRebirthCount}] = s
}
- materialsByGroupId := make(map[int32][]CharacterRebirthMaterialRow)
+ materialsByGroupId := make(map[int32][]EntityMCharacterRebirthMaterialGroup)
for _, m := range materialRows {
materialsByGroupId[m.CharacterRebirthMaterialGroupId] = append(materialsByGroupId[m.CharacterRebirthMaterialGroupId], m)
}
diff --git a/server/internal/masterdata/characterboard.go b/server/internal/masterdata/characterboard.go
index 9420a19..b41269a 100644
--- a/server/internal/masterdata/characterboard.go
+++ b/server/internal/masterdata/characterboard.go
@@ -7,66 +7,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type CharacterBoardPanelRow struct {
- CharacterBoardPanelId int32 `json:"CharacterBoardPanelId"`
- CharacterBoardId int32 `json:"CharacterBoardId"`
- CharacterBoardPanelUnlockConditionGroupId int32 `json:"CharacterBoardPanelUnlockConditionGroupId"`
- CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"`
- CharacterBoardPanelReleaseRewardGroupId int32 `json:"CharacterBoardPanelReleaseRewardGroupId"`
- CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"`
- SortOrder int32 `json:"SortOrder"`
- ParentCharacterBoardPanelId int32 `json:"ParentCharacterBoardPanelId"`
- PlaceIndex int32 `json:"PlaceIndex"`
-}
-
-type CharacterBoardReleasePossessionRow struct {
- CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"`
- PossessionType int32 `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type CharacterBoardReleaseEffectRow struct {
- CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"`
- SortOrder int32 `json:"SortOrder"`
- CharacterBoardEffectType int32 `json:"CharacterBoardEffectType"`
- CharacterBoardEffectId int32 `json:"CharacterBoardEffectId"`
- EffectValue int32 `json:"EffectValue"`
-}
-
-type CharacterBoardRow struct {
- CharacterBoardId int32 `json:"CharacterBoardId"`
- CharacterBoardGroupId int32 `json:"CharacterBoardGroupId"`
- CharacterBoardUnlockConditionGroupId int32 `json:"CharacterBoardUnlockConditionGroupId"`
- ReleaseRank int32 `json:"ReleaseRank"`
-}
-
-type CharacterBoardStatusUpRow struct {
- CharacterBoardStatusUpId int32 `json:"CharacterBoardStatusUpId"`
- CharacterBoardStatusUpType int32 `json:"CharacterBoardStatusUpType"`
- CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
-}
-
-type CharacterBoardAbilityRow struct {
- CharacterBoardAbilityId int32 `json:"CharacterBoardAbilityId"`
- CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
- AbilityId int32 `json:"AbilityId"`
-}
-
-type CharacterBoardAbilityMaxLevelRow struct {
- CharacterId int32 `json:"CharacterId"`
- AbilityId int32 `json:"AbilityId"`
- MaxLevel int32 `json:"MaxLevel"`
-}
-
-type CharacterBoardEffectTargetRow struct {
- CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
- GroupIndex int32 `json:"GroupIndex"`
- CharacterBoardEffectTargetType int32 `json:"CharacterBoardEffectTargetType"`
- TargetValue int32 `json:"TargetValue"`
-}
-
type CharacterBoardAssignmentRow struct {
CharacterId int32 `json:"CharacterId"`
CharacterBoardCategoryId int32 `json:"CharacterBoardCategoryId"`
@@ -83,68 +23,68 @@ type CharacterBoardGroupRow struct {
}
type CharacterBoardCatalog struct {
- PanelById map[int32]CharacterBoardPanelRow
- PanelsByBoardId map[int32][]CharacterBoardPanelRow
- ReleaseCostsByGroupId map[int32][]CharacterBoardReleasePossessionRow
- ReleaseEffectsByGroupId map[int32][]CharacterBoardReleaseEffectRow
- StatusUpById map[int32]CharacterBoardStatusUpRow
- AbilityById map[int32]CharacterBoardAbilityRow
+ PanelById map[int32]EntityMCharacterBoardPanel
+ PanelsByBoardId map[int32][]EntityMCharacterBoardPanel
+ ReleaseCostsByGroupId map[int32][]EntityMCharacterBoardPanelReleasePossessionGroup
+ ReleaseEffectsByGroupId map[int32][]EntityMCharacterBoardPanelReleaseEffectGroup
+ StatusUpById map[int32]EntityMCharacterBoardStatusUp
+ AbilityById map[int32]EntityMCharacterBoardAbility
AbilityMaxLevel map[store.CharacterBoardAbilityKey]int32
- EffectTargetsByGroupId map[int32][]CharacterBoardEffectTargetRow
- BoardById map[int32]CharacterBoardRow
+ EffectTargetsByGroupId map[int32][]EntityMCharacterBoardEffectTargetGroup
+ BoardById map[int32]EntityMCharacterBoard
}
func LoadCharacterBoardCatalog() (*CharacterBoardCatalog, error) {
- panels, err := utils.ReadJSON[CharacterBoardPanelRow]("EntityMCharacterBoardPanelTable.json")
+ panels, err := utils.ReadTable[EntityMCharacterBoardPanel]("m_character_board_panel")
if err != nil {
return nil, fmt.Errorf("load character board panel table: %w", err)
}
- costs, err := utils.ReadJSON[CharacterBoardReleasePossessionRow]("EntityMCharacterBoardPanelReleasePossessionGroupTable.json")
+ costs, err := utils.ReadTable[EntityMCharacterBoardPanelReleasePossessionGroup]("m_character_board_panel_release_possession_group")
if err != nil {
return nil, fmt.Errorf("load character board release possession table: %w", err)
}
- effects, err := utils.ReadJSON[CharacterBoardReleaseEffectRow]("EntityMCharacterBoardPanelReleaseEffectGroupTable.json")
+ effects, err := utils.ReadTable[EntityMCharacterBoardPanelReleaseEffectGroup]("m_character_board_panel_release_effect_group")
if err != nil {
return nil, fmt.Errorf("load character board release effect table: %w", err)
}
- boards, err := utils.ReadJSON[CharacterBoardRow]("EntityMCharacterBoardTable.json")
+ boards, err := utils.ReadTable[EntityMCharacterBoard]("m_character_board")
if err != nil {
return nil, fmt.Errorf("load character board table: %w", err)
}
- statusUps, err := utils.ReadJSON[CharacterBoardStatusUpRow]("EntityMCharacterBoardStatusUpTable.json")
+ statusUps, err := utils.ReadTable[EntityMCharacterBoardStatusUp]("m_character_board_status_up")
if err != nil {
return nil, fmt.Errorf("load character board status up table: %w", err)
}
- abilities, err := utils.ReadJSON[CharacterBoardAbilityRow]("EntityMCharacterBoardAbilityTable.json")
+ abilities, err := utils.ReadTable[EntityMCharacterBoardAbility]("m_character_board_ability")
if err != nil {
return nil, fmt.Errorf("load character board ability table: %w", err)
}
- abilityMaxLevels, err := utils.ReadJSON[CharacterBoardAbilityMaxLevelRow]("EntityMCharacterBoardAbilityMaxLevelTable.json")
+ abilityMaxLevels, err := utils.ReadTable[EntityMCharacterBoardAbilityMaxLevel]("m_character_board_ability_max_level")
if err != nil {
return nil, fmt.Errorf("load character board ability max level table: %w", err)
}
- targets, err := utils.ReadJSON[CharacterBoardEffectTargetRow]("EntityMCharacterBoardEffectTargetGroupTable.json")
+ targets, err := utils.ReadTable[EntityMCharacterBoardEffectTargetGroup]("m_character_board_effect_target_group")
if err != nil {
return nil, fmt.Errorf("load character board effect target table: %w", err)
}
catalog := &CharacterBoardCatalog{
- PanelById: make(map[int32]CharacterBoardPanelRow, len(panels)),
- PanelsByBoardId: make(map[int32][]CharacterBoardPanelRow),
- ReleaseCostsByGroupId: make(map[int32][]CharacterBoardReleasePossessionRow),
- ReleaseEffectsByGroupId: make(map[int32][]CharacterBoardReleaseEffectRow),
- StatusUpById: make(map[int32]CharacterBoardStatusUpRow, len(statusUps)),
- AbilityById: make(map[int32]CharacterBoardAbilityRow, len(abilities)),
+ PanelById: make(map[int32]EntityMCharacterBoardPanel, len(panels)),
+ PanelsByBoardId: make(map[int32][]EntityMCharacterBoardPanel),
+ ReleaseCostsByGroupId: make(map[int32][]EntityMCharacterBoardPanelReleasePossessionGroup),
+ ReleaseEffectsByGroupId: make(map[int32][]EntityMCharacterBoardPanelReleaseEffectGroup),
+ StatusUpById: make(map[int32]EntityMCharacterBoardStatusUp, len(statusUps)),
+ AbilityById: make(map[int32]EntityMCharacterBoardAbility, len(abilities)),
AbilityMaxLevel: make(map[store.CharacterBoardAbilityKey]int32, len(abilityMaxLevels)),
- EffectTargetsByGroupId: make(map[int32][]CharacterBoardEffectTargetRow),
- BoardById: make(map[int32]CharacterBoardRow, len(boards)),
+ EffectTargetsByGroupId: make(map[int32][]EntityMCharacterBoardEffectTargetGroup),
+ BoardById: make(map[int32]EntityMCharacterBoard, len(boards)),
}
for _, p := range panels {
diff --git a/server/internal/masterdata/characterviewer.go b/server/internal/masterdata/characterviewer.go
index d086b38..29a7a50 100644
--- a/server/internal/masterdata/characterviewer.go
+++ b/server/internal/masterdata/characterviewer.go
@@ -9,11 +9,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type characterViewerField struct {
- CharacterViewerFieldId int32 `json:"CharacterViewerFieldId"`
- ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"`
-}
-
type characterViewerFieldEntry struct {
FieldId int32
RequiredQuestId int32
@@ -39,7 +34,7 @@ func (c *CharacterViewerCatalog) ReleasedFieldIds(user store.UserState) []int32
}
func LoadCharacterViewerCatalog(resolver *ConditionResolver) *CharacterViewerCatalog {
- fields, err := utils.ReadJSON[characterViewerField]("EntityMCharacterViewerFieldTable.json")
+ fields, err := utils.ReadTable[EntityMCharacterViewerField]("m_character_viewer_field")
if err != nil {
log.Fatalf("load character viewer field table: %v", err)
}
diff --git a/server/internal/masterdata/companion.go b/server/internal/masterdata/companion.go
index ba0ffcd..5509bf3 100644
--- a/server/internal/masterdata/companion.go
+++ b/server/internal/masterdata/companion.go
@@ -6,23 +6,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type companionRow struct {
- CompanionId int32 `json:"CompanionId"`
- CompanionCategoryType int32 `json:"CompanionCategoryType"`
-}
-
-type companionCategoryRow struct {
- CompanionCategoryType int32 `json:"CompanionCategoryType"`
- EnhancementCostNumericalFunctionId int32 `json:"EnhancementCostNumericalFunctionId"`
-}
-
-type companionEnhancementMaterialRow struct {
- CompanionCategoryType int32 `json:"CompanionCategoryType"`
- Level int32 `json:"Level"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
-}
-
type CompanionLevelKey struct {
CategoryType int32
Level int32
@@ -34,23 +17,23 @@ type CompanionMaterialCost struct {
}
type CompanionCatalog struct {
- CompanionById map[int32]companionRow
+ CompanionById map[int32]EntityMCompanion
GoldCostByCategory map[int32]NumericalFunc
MaterialsByKey map[CompanionLevelKey]CompanionMaterialCost
}
func LoadCompanionCatalog() (*CompanionCatalog, error) {
- companions, err := utils.ReadJSON[companionRow]("EntityMCompanionTable.json")
+ companions, err := utils.ReadTable[EntityMCompanion]("m_companion")
if err != nil {
return nil, fmt.Errorf("load companion table: %w", err)
}
- categories, err := utils.ReadJSON[companionCategoryRow]("EntityMCompanionCategoryTable.json")
+ categories, err := utils.ReadTable[EntityMCompanionCategory]("m_companion_category")
if err != nil {
return nil, fmt.Errorf("load companion category table: %w", err)
}
- materials, err := utils.ReadJSON[companionEnhancementMaterialRow]("EntityMCompanionEnhancementMaterialTable.json")
+ materials, err := utils.ReadTable[EntityMCompanionEnhancementMaterial]("m_companion_enhancement_material")
if err != nil {
return nil, fmt.Errorf("load companion enhancement material table: %w", err)
}
@@ -60,7 +43,7 @@ func LoadCompanionCatalog() (*CompanionCatalog, error) {
return nil, fmt.Errorf("load function resolver: %w", err)
}
- companionById := make(map[int32]companionRow, len(companions))
+ companionById := make(map[int32]EntityMCompanion, len(companions))
for _, c := range companions {
companionById[c.CompanionId] = c
}
diff --git a/server/internal/masterdata/conditions.go b/server/internal/masterdata/conditions.go
index aa30dd8..8014a72 100644
--- a/server/internal/masterdata/conditions.go
+++ b/server/internal/masterdata/conditions.go
@@ -7,19 +7,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type evaluateCondition struct {
- EvaluateConditionId int32 `json:"EvaluateConditionId"`
- EvaluateConditionFunctionType model.EvaluateConditionFunctionType `json:"EvaluateConditionFunctionType"`
- EvaluateConditionEvaluateType model.EvaluateConditionEvaluateType `json:"EvaluateConditionEvaluateType"`
- EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"`
-}
-
-type evaluateConditionValueGroup struct {
- EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"`
- GroupIndex int32 `json:"GroupIndex"`
- Value int64 `json:"Value"`
-}
-
const defaultGroupIndex = 1
type ConditionResolver struct {
@@ -27,16 +14,16 @@ type ConditionResolver struct {
}
func LoadConditionResolver() (*ConditionResolver, error) {
- conditions, err := utils.ReadJSON[evaluateCondition]("EntityMEvaluateConditionTable.json")
+ conditions, err := utils.ReadTable[EntityMEvaluateCondition]("m_evaluate_condition")
if err != nil {
return nil, fmt.Errorf("load evaluate condition table: %w", err)
}
- valueGroups, err := utils.ReadJSON[evaluateConditionValueGroup]("EntityMEvaluateConditionValueGroupTable.json")
+ valueGroups, err := utils.ReadTable[EntityMEvaluateConditionValueGroup]("m_evaluate_condition_value_group")
if err != nil {
return nil, fmt.Errorf("load evaluate condition value group table: %w", err)
}
- condById := make(map[int32]evaluateCondition, len(conditions))
+ condById := make(map[int32]EntityMEvaluateCondition, len(conditions))
for _, c := range conditions {
condById[c.EvaluateConditionId] = c
}
@@ -52,8 +39,8 @@ func LoadConditionResolver() (*ConditionResolver, error) {
resolved := make(map[int32]int32)
for _, c := range conditions {
- if c.EvaluateConditionFunctionType == model.EvaluateConditionFunctionTypeQuestClear &&
- c.EvaluateConditionEvaluateType == model.EvaluateConditionEvaluateTypeIdContain {
+ if model.EvaluateConditionFunctionType(c.EvaluateConditionFunctionType) == model.EvaluateConditionFunctionTypeQuestClear &&
+ model.EvaluateConditionEvaluateType(c.EvaluateConditionEvaluateType) == model.EvaluateConditionEvaluateTypeIdContain {
if questId, ok := vgByKey[vgKey{c.EvaluateConditionValueGroupId, defaultGroupIndex}]; ok {
resolved[c.EvaluateConditionId] = int32(questId)
}
diff --git a/server/internal/masterdata/config.go b/server/internal/masterdata/config.go
index 9e8f1b7..5b8eb35 100644
--- a/server/internal/masterdata/config.go
+++ b/server/internal/masterdata/config.go
@@ -7,11 +7,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type configRow struct {
- ConfigKey string `json:"ConfigKey"`
- Value string `json:"Value"`
-}
-
type GameConfig struct {
ConsumableItemIdForGold int32
ConsumableItemIdForMedal int32
@@ -41,7 +36,7 @@ type GameConfig struct {
}
func LoadGameConfig() (*GameConfig, error) {
- rows, err := utils.ReadJSON[configRow]("EntityMConfigTable.json")
+ rows, err := utils.ReadTable[EntityMConfig]("m_config")
if err != nil {
return nil, fmt.Errorf("load config table: %w", err)
}
diff --git a/server/internal/masterdata/consumableitem.go b/server/internal/masterdata/consumableitem.go
index 2e18691..34e1924 100644
--- a/server/internal/masterdata/consumableitem.go
+++ b/server/internal/masterdata/consumableitem.go
@@ -6,23 +6,18 @@ import (
"lunar-tear/server/internal/utils"
)
-type ConsumableItemRow struct {
- ConsumableItemId int32 `json:"ConsumableItemId"`
- SellPrice int32 `json:"SellPrice"`
-}
-
type ConsumableItemCatalog struct {
- All map[int32]ConsumableItemRow
+ All map[int32]EntityMConsumableItem
}
func LoadConsumableItemCatalog() (*ConsumableItemCatalog, error) {
- rows, err := utils.ReadJSON[ConsumableItemRow]("EntityMConsumableItemTable.json")
+ rows, err := utils.ReadTable[EntityMConsumableItem]("m_consumable_item")
if err != nil {
return nil, fmt.Errorf("load consumable item table: %w", err)
}
catalog := &ConsumableItemCatalog{
- All: make(map[int32]ConsumableItemRow, len(rows)),
+ All: make(map[int32]EntityMConsumableItem, len(rows)),
}
for _, row := range rows {
catalog.All[row.ConsumableItemId] = row
diff --git a/server/internal/masterdata/costume.go b/server/internal/masterdata/costume.go
index 98b8eda..148eef6 100644
--- a/server/internal/masterdata/costume.go
+++ b/server/internal/masterdata/costume.go
@@ -8,132 +8,37 @@ import (
"lunar-tear/server/internal/utils"
)
-type CostumeMasterRow struct {
- CostumeId int32 `json:"CostumeId"`
- CharacterId int32 `json:"CharacterId"`
- SkillfulWeaponType int32 `json:"SkillfulWeaponType"`
- RarityType int32 `json:"RarityType"`
- CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
- CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"`
-}
-
-type costumeRarityRow struct {
- RarityType int32 `json:"RarityType"`
- CostumeLimitBreakMaterialRarityGroupId int32 `json:"CostumeLimitBreakMaterialRarityGroupId"`
- RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
- EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
- LimitBreakCostNumericalFunctionId int32 `json:"LimitBreakCostNumericalFunctionId"`
- MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
- ActiveSkillMaxLevelNumericalFunctionId int32 `json:"ActiveSkillMaxLevelNumericalFunctionId"`
- ActiveSkillEnhancementCostNumericalFunctionId int32 `json:"ActiveSkillEnhancementCostNumericalFunctionId"`
-}
-
-type CostumeAwakenRow struct {
- CostumeId int32 `json:"CostumeId"`
- CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"`
- CostumeAwakenStepMaterialGroupId int32 `json:"CostumeAwakenStepMaterialGroupId"`
- CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"`
-}
-
-type costumeAwakenPriceRow struct {
- CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"`
- AwakenStepLowerLimit int32 `json:"AwakenStepLowerLimit"`
- Gold int32 `json:"Gold"`
-}
-
-type CostumeAwakenEffectRow struct {
- CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"`
- AwakenStep int32 `json:"AwakenStep"`
- CostumeAwakenEffectType int32 `json:"CostumeAwakenEffectType"`
- CostumeAwakenEffectId int32 `json:"CostumeAwakenEffectId"`
-}
-
-type CostumeAwakenStatusUpRow struct {
- CostumeAwakenStatusUpGroupId int32 `json:"CostumeAwakenStatusUpGroupId"`
- SortOrder int32 `json:"SortOrder"`
- StatusKindType int32 `json:"StatusKindType"`
- StatusCalculationType int32 `json:"StatusCalculationType"`
- EffectValue int32 `json:"EffectValue"`
-}
-
-type CostumeAwakenItemAcquireRow struct {
- CostumeAwakenItemAcquireId int32 `json:"CostumeAwakenItemAcquireId"`
- PossessionType int32 `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
-type CostumeActiveSkillGroupRow struct {
- CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"`
- CostumeLimitBreakCountLowerLimit int32 `json:"CostumeLimitBreakCountLowerLimit"`
- CostumeActiveSkillId int32 `json:"CostumeActiveSkillId"`
- CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"`
-}
-
-type CostumeActiveSkillEnhanceMaterialRow struct {
- CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"`
- SkillLevel int32 `json:"SkillLevel"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type CostumeLotteryEffectRow struct {
- CostumeId int32 `json:"CostumeId"`
- SlotNumber int32 `json:"SlotNumber"`
- CostumeLotteryEffectOddsGroupId int32 `json:"CostumeLotteryEffectOddsGroupId"`
- CostumeLotteryEffectUnlockMaterialGroupId int32 `json:"CostumeLotteryEffectUnlockMaterialGroupId"`
- CostumeLotteryEffectDrawMaterialGroupId int32 `json:"CostumeLotteryEffectDrawMaterialGroupId"`
- CostumeLotteryEffectReleaseScheduleId int32 `json:"CostumeLotteryEffectReleaseScheduleId"`
-}
-
-type CostumeLotteryEffectMaterialGroupRow struct {
- CostumeLotteryEffectMaterialGroupId int32 `json:"CostumeLotteryEffectMaterialGroupId"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type CostumeLotteryEffectOddsRow struct {
- CostumeLotteryEffectOddsGroupId int32 `json:"CostumeLotteryEffectOddsGroupId"`
- OddsNumber int32 `json:"OddsNumber"`
- Weight int32 `json:"Weight"`
- CostumeLotteryEffectType int32 `json:"CostumeLotteryEffectType"`
- CostumeLotteryEffectTargetId int32 `json:"CostumeLotteryEffectTargetId"`
- RarityType int32 `json:"RarityType"`
-}
-
type CostumeCatalog struct {
- Costumes map[int32]CostumeMasterRow
- Materials map[int32]MaterialRow
+ Costumes map[int32]EntityMCostume
+ Materials map[int32]EntityMMaterial
ExpByRarity map[int32][]int32
EnhanceCostByRarity map[int32]NumericalFunc
MaxLevelByRarity map[int32]NumericalFunc
LimitBreakCostByRarity map[int32]NumericalFunc
- AwakenByCostumeId map[int32]CostumeAwakenRow
+ AwakenByCostumeId map[int32]EntityMCostumeAwaken
AwakenPriceByGroup map[int32]int32
- AwakenEffectsByGroupAndStep map[int32]map[int32]CostumeAwakenEffectRow
- AwakenStatusUpByGroup map[int32][]CostumeAwakenStatusUpRow
- AwakenItemAcquireById map[int32]CostumeAwakenItemAcquireRow
+ AwakenEffectsByGroupAndStep map[int32]map[int32]EntityMCostumeAwakenEffectGroup
+ AwakenStatusUpByGroup map[int32][]EntityMCostumeAwakenStatusUpGroup
+ AwakenItemAcquireById map[int32]EntityMCostumeAwakenItemAcquire
- ActiveSkillGroupsByGroupId map[int32][]CostumeActiveSkillGroupRow // sorted by CostumeLimitBreakCountLowerLimit desc
- ActiveSkillEnhanceMats map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel]
+ ActiveSkillGroupsByGroupId map[int32][]EntityMCostumeActiveSkillGroup // sorted by CostumeLimitBreakCountLowerLimit desc
+ ActiveSkillEnhanceMats map[[2]int32][]EntityMCostumeActiveSkillEnhancementMaterial // key: [enhancementMaterialId, skillLevel]
ActiveSkillMaxLevelByRarity map[int32]NumericalFunc
ActiveSkillCostByRarity map[int32]NumericalFunc
- LotteryEffects map[[2]int32]CostumeLotteryEffectRow // key: [costumeId, slotNumber]
- LotteryEffectMats map[int32][]CostumeLotteryEffectMaterialGroupRow // key: materialGroupId (both unlock and draw)
- LotteryEffectOdds map[int32][]CostumeLotteryEffectOddsRow // key: oddsGroupId
+ LotteryEffects map[[2]int32]EntityMCostumeLotteryEffect // key: [costumeId, slotNumber]
+ LotteryEffectMats map[int32][]EntityMCostumeLotteryEffectMaterialGroup // key: materialGroupId (both unlock and draw)
+ LotteryEffectOdds map[int32][]EntityMCostumeLotteryEffectOddsGroup // key: oddsGroupId
}
func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
- costumes, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json")
+ costumes, err := utils.ReadTable[EntityMCostume]("m_costume")
if err != nil {
return nil, fmt.Errorf("load costume table: %w", err)
}
- rarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json")
+ rarities, err := utils.ReadTable[EntityMCostumeRarity]("m_costume_rarity")
if err != nil {
return nil, fmt.Errorf("load costume rarity table: %w", err)
}
@@ -148,71 +53,71 @@ func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
return nil, fmt.Errorf("load function resolver: %w", err)
}
- awakenRows, err := utils.ReadJSON[CostumeAwakenRow]("EntityMCostumeAwakenTable.json")
+ awakenRows, err := utils.ReadTable[EntityMCostumeAwaken]("m_costume_awaken")
if err != nil {
return nil, fmt.Errorf("load costume awaken table: %w", err)
}
- awakenPriceRows, err := utils.ReadJSON[costumeAwakenPriceRow]("EntityMCostumeAwakenPriceGroupTable.json")
+ awakenPriceRows, err := utils.ReadTable[EntityMCostumeAwakenPriceGroup]("m_costume_awaken_price_group")
if err != nil {
return nil, fmt.Errorf("load costume awaken price table: %w", err)
}
- awakenEffectRows, err := utils.ReadJSON[CostumeAwakenEffectRow]("EntityMCostumeAwakenEffectGroupTable.json")
+ awakenEffectRows, err := utils.ReadTable[EntityMCostumeAwakenEffectGroup]("m_costume_awaken_effect_group")
if err != nil {
return nil, fmt.Errorf("load costume awaken effect table: %w", err)
}
- awakenStatusUpRows, err := utils.ReadJSON[CostumeAwakenStatusUpRow]("EntityMCostumeAwakenStatusUpGroupTable.json")
+ awakenStatusUpRows, err := utils.ReadTable[EntityMCostumeAwakenStatusUpGroup]("m_costume_awaken_status_up_group")
if err != nil {
return nil, fmt.Errorf("load costume awaken status up table: %w", err)
}
- awakenItemAcquireRows, err := utils.ReadJSON[CostumeAwakenItemAcquireRow]("EntityMCostumeAwakenItemAcquireTable.json")
+ awakenItemAcquireRows, err := utils.ReadTable[EntityMCostumeAwakenItemAcquire]("m_costume_awaken_item_acquire")
if err != nil {
return nil, fmt.Errorf("load costume awaken item acquire table: %w", err)
}
- activeSkillGroupRows, err := utils.ReadJSON[CostumeActiveSkillGroupRow]("EntityMCostumeActiveSkillGroupTable.json")
+ activeSkillGroupRows, err := utils.ReadTable[EntityMCostumeActiveSkillGroup]("m_costume_active_skill_group")
if err != nil {
return nil, fmt.Errorf("load costume active skill group table: %w", err)
}
- activeSkillMatRows, err := utils.ReadJSON[CostumeActiveSkillEnhanceMaterialRow]("EntityMCostumeActiveSkillEnhancementMaterialTable.json")
+ activeSkillMatRows, err := utils.ReadTable[EntityMCostumeActiveSkillEnhancementMaterial]("m_costume_active_skill_enhancement_material")
if err != nil {
return nil, fmt.Errorf("load costume active skill enhancement material table: %w", err)
}
- lotteryEffectRows, err := utils.ReadJSON[CostumeLotteryEffectRow]("EntityMCostumeLotteryEffectTable.json")
+ lotteryEffectRows, err := utils.ReadTable[EntityMCostumeLotteryEffect]("m_costume_lottery_effect")
if err != nil {
return nil, fmt.Errorf("load costume lottery effect table: %w", err)
}
- lotteryEffectMatRows, err := utils.ReadJSON[CostumeLotteryEffectMaterialGroupRow]("EntityMCostumeLotteryEffectMaterialGroupTable.json")
+ lotteryEffectMatRows, err := utils.ReadTable[EntityMCostumeLotteryEffectMaterialGroup]("m_costume_lottery_effect_material_group")
if err != nil {
return nil, fmt.Errorf("load costume lottery effect material group table: %w", err)
}
- lotteryEffectOddsRows, err := utils.ReadJSON[CostumeLotteryEffectOddsRow]("EntityMCostumeLotteryEffectOddsGroupTable.json")
+ lotteryEffectOddsRows, err := utils.ReadTable[EntityMCostumeLotteryEffectOddsGroup]("m_costume_lottery_effect_odds_group")
if err != nil {
return nil, fmt.Errorf("load costume lottery effect odds group table: %w", err)
}
catalog := &CostumeCatalog{
- Costumes: make(map[int32]CostumeMasterRow, len(costumes)),
+ Costumes: make(map[int32]EntityMCostume, len(costumes)),
Materials: matCatalog.ByType[model.MaterialTypeCostumeEnhancement],
ExpByRarity: make(map[int32][]int32, len(rarities)),
EnhanceCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
MaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
LimitBreakCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
- AwakenByCostumeId: make(map[int32]CostumeAwakenRow, len(awakenRows)),
+ AwakenByCostumeId: make(map[int32]EntityMCostumeAwaken, len(awakenRows)),
AwakenPriceByGroup: make(map[int32]int32, len(awakenPriceRows)),
- AwakenEffectsByGroupAndStep: make(map[int32]map[int32]CostumeAwakenEffectRow),
- AwakenStatusUpByGroup: make(map[int32][]CostumeAwakenStatusUpRow),
- AwakenItemAcquireById: make(map[int32]CostumeAwakenItemAcquireRow, len(awakenItemAcquireRows)),
+ AwakenEffectsByGroupAndStep: make(map[int32]map[int32]EntityMCostumeAwakenEffectGroup),
+ AwakenStatusUpByGroup: make(map[int32][]EntityMCostumeAwakenStatusUpGroup),
+ AwakenItemAcquireById: make(map[int32]EntityMCostumeAwakenItemAcquire, len(awakenItemAcquireRows)),
- ActiveSkillGroupsByGroupId: make(map[int32][]CostumeActiveSkillGroupRow),
- ActiveSkillEnhanceMats: make(map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow),
+ ActiveSkillGroupsByGroupId: make(map[int32][]EntityMCostumeActiveSkillGroup),
+ ActiveSkillEnhanceMats: make(map[[2]int32][]EntityMCostumeActiveSkillEnhancementMaterial),
ActiveSkillMaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
ActiveSkillCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
- LotteryEffects: make(map[[2]int32]CostumeLotteryEffectRow, len(lotteryEffectRows)),
- LotteryEffectMats: make(map[int32][]CostumeLotteryEffectMaterialGroupRow),
- LotteryEffectOdds: make(map[int32][]CostumeLotteryEffectOddsRow),
+ LotteryEffects: make(map[[2]int32]EntityMCostumeLotteryEffect, len(lotteryEffectRows)),
+ LotteryEffectMats: make(map[int32][]EntityMCostumeLotteryEffectMaterialGroup),
+ LotteryEffectOdds: make(map[int32][]EntityMCostumeLotteryEffectOddsGroup),
}
for _, row := range costumes {
@@ -259,7 +164,7 @@ func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
for _, row := range awakenEffectRows {
m, ok := catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId]
if !ok {
- m = make(map[int32]CostumeAwakenEffectRow)
+ m = make(map[int32]EntityMCostumeAwakenEffectGroup)
catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId] = m
}
m[row.AwakenStep] = row
diff --git a/server/internal/masterdata/dup_exchange.go b/server/internal/masterdata/dup_exchange.go
index 9fa9c64..5c442d9 100644
--- a/server/internal/masterdata/dup_exchange.go
+++ b/server/internal/masterdata/dup_exchange.go
@@ -5,17 +5,10 @@ import (
"lunar-tear/server/internal/utils"
)
-type costumeDupRow struct {
- CostumeId int32 `json:"CostumeId"`
- PossessionType int32 `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
func LoadDupExchange() (map[int32][]model.DupExchangeEntry, error) {
result := make(map[int32][]model.DupExchangeEntry)
- costumeRows, err := utils.ReadJSON[costumeDupRow]("EntityMCostumeDuplicationExchangePossessionGroupTable.json")
+ costumeRows, err := utils.ReadTable[EntityMCostumeDuplicationExchangePossessionGroup]("m_costume_duplication_exchange_possession_group")
if err != nil {
return nil, err
}
@@ -30,20 +23,10 @@ func LoadDupExchange() (map[int32][]model.DupExchangeEntry, error) {
return result, nil
}
-type lbMaterialRow struct {
- CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
- MaterialId int32 `json:"MaterialId"`
-}
-
-type costumeLBRef struct {
- CostumeId int32 `json:"CostumeId"`
- CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
-}
-
const dupExchangeFallbackCount int32 = 10
func EnrichDupExchange(dupMap map[int32][]model.DupExchangeEntry, pool *GachaCatalog) (int, error) {
- lbRows, err := utils.ReadJSON[lbMaterialRow]("EntityMCostumeLimitBreakMaterialGroupTable.json")
+ lbRows, err := utils.ReadTable[EntityMCostumeLimitBreakMaterialGroup]("m_costume_limit_break_material_group")
if err != nil {
return 0, err
}
@@ -52,7 +35,7 @@ func EnrichDupExchange(dupMap map[int32][]model.DupExchangeEntry, pool *GachaCat
groupToMaterial[r.CostumeLimitBreakMaterialGroupId] = r.MaterialId
}
- costumeRows, err := utils.ReadJSON[costumeLBRef]("EntityMCostumeTable.json")
+ costumeRows, err := utils.ReadTable[EntityMCostume]("m_costume")
if err != nil {
return 0, err
}
diff --git a/server/internal/masterdata/entities.go b/server/internal/masterdata/entities.go
new file mode 100644
index 0000000..66a1093
--- /dev/null
+++ b/server/internal/masterdata/entities.go
@@ -0,0 +1,1106 @@
+// Code generated by scripts/gen_entities.py from schemas.json. DO NOT EDIT.
+
+package masterdata
+
+// EntityMBattle is table key "m_battle".
+type EntityMBattle struct {
+ _ struct{} `msgpack:",array"`
+ BattleId int32
+ BattleNpcId int64
+ DeckType int32
+ BattleNpcDeckNumber int32
+}
+
+// EntityMBattleDropReward is table key "m_battle_drop_reward".
+type EntityMBattleDropReward struct {
+ _ struct{} `msgpack:",array"`
+ BattleDropRewardId int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+}
+
+// EntityMBattleGroup is table key "m_battle_group".
+type EntityMBattleGroup struct {
+ _ struct{} `msgpack:",array"`
+ BattleGroupId int32
+ WaveNumber int32
+ BattleId int32
+ WaveStartActAssetId int32
+ WaveEndActAssetId int32
+ BattleCameraControllerAssetId int32
+ BattlePointIndex int32
+ BattleStartCameraType int32
+}
+
+// EntityMBattleNpcDeck is table key "m_battle_npc_deck".
+type EntityMBattleNpcDeck struct {
+ _ struct{} `msgpack:",array"`
+ BattleNpcId int64
+ DeckType int32
+ BattleNpcDeckNumber int32
+ BattleNpcDeckCharacterUuid01 string
+ BattleNpcDeckCharacterUuid02 string
+ BattleNpcDeckCharacterUuid03 string
+ Name string
+ Power int32
+}
+
+// EntityMBattleNpcDeckCharacterDropCategory is table key "m_battle_npc_deck_character_drop_category".
+type EntityMBattleNpcDeckCharacterDropCategory struct {
+ _ struct{} `msgpack:",array"`
+ BattleNpcId int64
+ BattleNpcDeckCharacterUuid string
+ BattleDropCategoryId int32
+}
+
+// EntityMBattleRentalDeck is table key "m_battle_rental_deck".
+type EntityMBattleRentalDeck struct {
+ _ struct{} `msgpack:",array"`
+ BattleGroupId int32
+ BattleNpcId int64
+ DeckType int32
+ BattleNpcDeckNumber int32
+}
+
+// EntityMBigHuntBoss is table key "m_big_hunt_boss".
+type EntityMBigHuntBoss struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntBossId int32
+ BigHuntBossGradeGroupId int32
+ NameBigHuntBossTextId int32
+ BigHuntBossAssetId int32
+ AttributeType int32
+}
+
+// EntityMBigHuntBossGradeGroup is table key "m_big_hunt_boss_grade_group".
+type EntityMBigHuntBossGradeGroup struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntBossGradeGroupId int32
+ NecessaryScore int64
+ AssetGradeIconId int32
+}
+
+// EntityMBigHuntBossQuest is table key "m_big_hunt_boss_quest".
+type EntityMBigHuntBossQuest struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntBossQuestId int32
+ BigHuntBossId int32
+ BigHuntQuestGroupId int32
+ BigHuntBossQuestScoreCoefficientId int32
+ BigHuntScoreRewardGroupScheduleId int32
+ BigHuntLinkId int32
+ DailyChallengeCount int32
+}
+
+// EntityMBigHuntQuest is table key "m_big_hunt_quest".
+type EntityMBigHuntQuest struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntQuestId int32
+ QuestId int32
+ BigHuntQuestScoreCoefficientId int32
+}
+
+// EntityMBigHuntQuestScoreCoefficient is table key "m_big_hunt_quest_score_coefficient".
+type EntityMBigHuntQuestScoreCoefficient struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntQuestScoreCoefficientId int32
+ ScoreDifficultBonusPermil int32
+}
+
+// EntityMBigHuntRewardGroup is table key "m_big_hunt_reward_group".
+type EntityMBigHuntRewardGroup struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntRewardGroupId int32
+ SortOrder int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+}
+
+// EntityMBigHuntSchedule is table key "m_big_hunt_schedule".
+type EntityMBigHuntSchedule struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntScheduleId int32
+ NoticeStartDatetime int64
+ ChallengeStartDatetime int64
+ ChallengeEndDatetime int64
+ SeasonAssetId int32
+}
+
+// EntityMBigHuntScoreRewardGroup is table key "m_big_hunt_score_reward_group".
+type EntityMBigHuntScoreRewardGroup struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntScoreRewardGroupId int32
+ NecessaryScore int64
+ BigHuntRewardGroupId int32
+}
+
+// EntityMBigHuntScoreRewardGroupSchedule is table key "m_big_hunt_score_reward_group_schedule".
+type EntityMBigHuntScoreRewardGroupSchedule struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntScoreRewardGroupScheduleId int32
+ GroupIndex int32
+ BigHuntScoreRewardGroupId int32
+ StartDatetime int64
+}
+
+// EntityMBigHuntWeeklyAttributeScoreRewardGroupSchedule is table key "m_big_hunt_weekly_attribute_score_reward_group_schedule".
+type EntityMBigHuntWeeklyAttributeScoreRewardGroupSchedule struct {
+ _ struct{} `msgpack:",array"`
+ BigHuntWeeklyAttributeScoreRewardGroupScheduleId int32
+ AttributeType int32
+ GroupIndex int32
+ BigHuntScoreRewardGroupId int32
+ StartDatetime int64
+}
+
+// EntityMCageOrnament is table key "m_cage_ornament".
+type EntityMCageOrnament struct {
+ _ struct{} `msgpack:",array"`
+ CageOrnamentId int32
+ StartDatetime int64
+ EndDatetime int64
+ CageOrnamentRewardId int32
+}
+
+// EntityMCageOrnamentReward is table key "m_cage_ornament_reward".
+type EntityMCageOrnamentReward struct {
+ _ struct{} `msgpack:",array"`
+ CageOrnamentRewardId int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+}
+
+// EntityMCatalogCostume is table key "m_catalog_costume".
+type EntityMCatalogCostume struct {
+ _ struct{} `msgpack:",array"`
+ CostumeId int32
+ SortOrder int32
+ CatalogTermId int32
+}
+
+// EntityMCatalogWeapon is table key "m_catalog_weapon".
+type EntityMCatalogWeapon struct {
+ _ struct{} `msgpack:",array"`
+ WeaponId int32
+ SortOrder int32
+ CatalogTermId int32
+}
+
+// EntityMCharacterBoard is table key "m_character_board".
+type EntityMCharacterBoard struct {
+ _ struct{} `msgpack:",array"`
+ CharacterBoardId int32
+ CharacterBoardGroupId int32
+ CharacterBoardUnlockConditionGroupId int32
+ ReleaseRank int32
+}
+
+// EntityMCharacterBoardAbility is table key "m_character_board_ability".
+type EntityMCharacterBoardAbility struct {
+ _ struct{} `msgpack:",array"`
+ CharacterBoardAbilityId int32
+ CharacterBoardEffectTargetGroupId int32
+ AbilityId int32
+}
+
+// EntityMCharacterBoardAbilityMaxLevel is table key "m_character_board_ability_max_level".
+type EntityMCharacterBoardAbilityMaxLevel struct {
+ _ struct{} `msgpack:",array"`
+ CharacterId int32
+ AbilityId int32
+ MaxLevel int32
+}
+
+// EntityMCharacterBoardEffectTargetGroup is table key "m_character_board_effect_target_group".
+type EntityMCharacterBoardEffectTargetGroup struct {
+ _ struct{} `msgpack:",array"`
+ CharacterBoardEffectTargetGroupId int32
+ GroupIndex int32
+ CharacterBoardEffectTargetType int32
+ TargetValue int32
+}
+
+// EntityMCharacterBoardPanel is table key "m_character_board_panel".
+type EntityMCharacterBoardPanel struct {
+ _ struct{} `msgpack:",array"`
+ CharacterBoardPanelId int32
+ CharacterBoardId int32
+ CharacterBoardPanelUnlockConditionGroupId int32
+ CharacterBoardPanelReleasePossessionGroupId int32
+ CharacterBoardPanelReleaseRewardGroupId int32
+ CharacterBoardPanelReleaseEffectGroupId int32
+ SortOrder int32
+ ParentCharacterBoardPanelId int32
+ PlaceIndex int32
+}
+
+// EntityMCharacterBoardPanelReleaseEffectGroup is table key "m_character_board_panel_release_effect_group".
+type EntityMCharacterBoardPanelReleaseEffectGroup struct {
+ _ struct{} `msgpack:",array"`
+ CharacterBoardPanelReleaseEffectGroupId int32
+ SortOrder int32
+ CharacterBoardEffectType int32
+ CharacterBoardEffectId int32
+ EffectValue int32
+}
+
+// EntityMCharacterBoardPanelReleasePossessionGroup is table key "m_character_board_panel_release_possession_group".
+type EntityMCharacterBoardPanelReleasePossessionGroup struct {
+ _ struct{} `msgpack:",array"`
+ CharacterBoardPanelReleasePossessionGroupId int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMCharacterBoardStatusUp is table key "m_character_board_status_up".
+type EntityMCharacterBoardStatusUp struct {
+ _ struct{} `msgpack:",array"`
+ CharacterBoardStatusUpId int32
+ CharacterBoardStatusUpType int32
+ CharacterBoardEffectTargetGroupId int32
+}
+
+// EntityMCharacterRebirth is table key "m_character_rebirth".
+type EntityMCharacterRebirth struct {
+ _ struct{} `msgpack:",array"`
+ CharacterId int32
+ CharacterRebirthStepGroupId int32
+ SortOrder int32
+ CharacterAssignmentType int32
+}
+
+// EntityMCharacterRebirthMaterialGroup is table key "m_character_rebirth_material_group".
+type EntityMCharacterRebirthMaterialGroup struct {
+ _ struct{} `msgpack:",array"`
+ CharacterRebirthMaterialGroupId int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMCharacterRebirthStepGroup is table key "m_character_rebirth_step_group".
+type EntityMCharacterRebirthStepGroup struct {
+ _ struct{} `msgpack:",array"`
+ CharacterRebirthStepGroupId int32
+ BeforeRebirthCount int32
+ CostumeLevelLimitUp int32
+ CharacterRebirthMaterialGroupId int32
+}
+
+// EntityMCharacterViewerField is table key "m_character_viewer_field".
+type EntityMCharacterViewerField struct {
+ _ struct{} `msgpack:",array"`
+ CharacterViewerFieldId int32
+ ReleaseEvaluateConditionId int32
+ PublishDatetime int64
+ CharacterViewerFieldAssetId int32
+ AssetBackgroundId int32
+ SortOrder int32
+}
+
+// EntityMCompanion is table key "m_companion".
+type EntityMCompanion struct {
+ _ struct{} `msgpack:",array"`
+ CompanionId int32
+ AttributeType int32
+ CompanionCategoryType int32
+ CompanionBaseStatusId int32
+ CompanionStatusCalculationId int32
+ SkillId int32
+ CompanionAbilityGroupId int32
+ ActorId int32
+ ActorSkeletonId int32
+ AssetVariationId int32
+ CharacterMoverBattleActorAiId int32
+}
+
+// EntityMCompanionCategory is table key "m_companion_category".
+type EntityMCompanionCategory struct {
+ _ struct{} `msgpack:",array"`
+ CompanionCategoryType int32
+ EnhancementCostNumericalFunctionId int32
+}
+
+// EntityMCompanionEnhancementMaterial is table key "m_companion_enhancement_material".
+type EntityMCompanionEnhancementMaterial struct {
+ _ struct{} `msgpack:",array"`
+ CompanionCategoryType int32
+ Level int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMConfig is table key "m_config".
+type EntityMConfig struct {
+ _ struct{} `msgpack:",array"`
+ ConfigKey string
+ Value string
+}
+
+// EntityMConsumableItem is table key "m_consumable_item".
+type EntityMConsumableItem struct {
+ _ struct{} `msgpack:",array"`
+ ConsumableItemId int32
+ ConsumableItemType int32
+ SortOrder int32
+ SellPrice int32
+ ConsumableItemTermId int32
+ AssetName string
+ AssetCategoryId int32
+ AssetVariationId int32
+}
+
+// EntityMCostume is table key "m_costume".
+type EntityMCostume struct {
+ _ struct{} `msgpack:",array"`
+ CostumeId int32
+ CharacterId int32
+ ActorId int32
+ CostumeAssetCategoryType int32
+ ActorSkeletonId int32
+ AssetVariationId int32
+ SkillfulWeaponType int32
+ RarityType int32
+ CostumeBaseStatusId int32
+ CostumeStatusCalculationId int32
+ CostumeLimitBreakMaterialGroupId int32
+ CostumeAbilityGroupId int32
+ CostumeActiveSkillGroupId int32
+ CounterSkillDetailId int32
+ CharacterMoverBattleActorAiId int32
+ CostumeDefaultSkillGroupId int32
+ CostumeLevelBonusId int32
+ DefaultActorSkillAiId int32
+ CostumeEmblemAssetId int32
+ BattleActorSkillAiGroupId int32
+}
+
+// EntityMCostumeActiveSkillEnhancementMaterial is table key "m_costume_active_skill_enhancement_material".
+type EntityMCostumeActiveSkillEnhancementMaterial struct {
+ _ struct{} `msgpack:",array"`
+ CostumeActiveSkillEnhancementMaterialId int32
+ SkillLevel int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMCostumeActiveSkillGroup is table key "m_costume_active_skill_group".
+type EntityMCostumeActiveSkillGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeActiveSkillGroupId int32
+ CostumeLimitBreakCountLowerLimit int32
+ CostumeActiveSkillId int32
+ CostumeActiveSkillEnhancementMaterialId int32
+}
+
+// EntityMCostumeAwaken is table key "m_costume_awaken".
+type EntityMCostumeAwaken struct {
+ _ struct{} `msgpack:",array"`
+ CostumeId int32
+ CostumeAwakenEffectGroupId int32
+ CostumeAwakenStepMaterialGroupId int32
+ CostumeAwakenPriceGroupId int32
+}
+
+// EntityMCostumeAwakenEffectGroup is table key "m_costume_awaken_effect_group".
+type EntityMCostumeAwakenEffectGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeAwakenEffectGroupId int32
+ AwakenStep int32
+ CostumeAwakenEffectType int32
+ CostumeAwakenEffectId int32
+}
+
+// EntityMCostumeAwakenItemAcquire is table key "m_costume_awaken_item_acquire".
+type EntityMCostumeAwakenItemAcquire struct {
+ _ struct{} `msgpack:",array"`
+ CostumeAwakenItemAcquireId int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+}
+
+// EntityMCostumeAwakenPriceGroup is table key "m_costume_awaken_price_group".
+type EntityMCostumeAwakenPriceGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeAwakenPriceGroupId int32
+ AwakenStepLowerLimit int32
+ Gold int32
+}
+
+// EntityMCostumeAwakenStatusUpGroup is table key "m_costume_awaken_status_up_group".
+type EntityMCostumeAwakenStatusUpGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeAwakenStatusUpGroupId int32
+ SortOrder int32
+ StatusKindType int32
+ StatusCalculationType int32
+ EffectValue int32
+}
+
+// EntityMCostumeDuplicationExchangePossessionGroup is table key "m_costume_duplication_exchange_possession_group".
+type EntityMCostumeDuplicationExchangePossessionGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeId int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+}
+
+// EntityMCostumeLimitBreakMaterialGroup is table key "m_costume_limit_break_material_group".
+type EntityMCostumeLimitBreakMaterialGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeLimitBreakMaterialGroupId int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+ CostumeOverflowExchangePossessionGroupId int32
+}
+
+// EntityMCostumeLotteryEffect is table key "m_costume_lottery_effect".
+type EntityMCostumeLotteryEffect struct {
+ _ struct{} `msgpack:",array"`
+ CostumeId int32
+ SlotNumber int32
+ CostumeLotteryEffectOddsGroupId int32
+ CostumeLotteryEffectUnlockMaterialGroupId int32
+ CostumeLotteryEffectDrawMaterialGroupId int32
+ CostumeLotteryEffectReleaseScheduleId int32
+}
+
+// EntityMCostumeLotteryEffectMaterialGroup is table key "m_costume_lottery_effect_material_group".
+type EntityMCostumeLotteryEffectMaterialGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeLotteryEffectMaterialGroupId int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMCostumeLotteryEffectOddsGroup is table key "m_costume_lottery_effect_odds_group".
+type EntityMCostumeLotteryEffectOddsGroup struct {
+ _ struct{} `msgpack:",array"`
+ CostumeLotteryEffectOddsGroupId int32
+ OddsNumber int32
+ Weight int32
+ CostumeLotteryEffectType int32
+ CostumeLotteryEffectTargetId int32
+ RarityType int32
+}
+
+// EntityMCostumeRarity is table key "m_costume_rarity".
+type EntityMCostumeRarity struct {
+ _ struct{} `msgpack:",array"`
+ RarityType int32
+ CostumeLimitBreakMaterialRarityGroupId int32
+ EnhancementCostByMaterialNumericalFunctionId int32
+ LimitBreakCostNumericalFunctionId int32
+ MaxLevelNumericalFunctionId int32
+ RequiredExpForLevelUpNumericalParameterMapId int32
+ ActiveSkillMaxLevelNumericalFunctionId int32
+ ActiveSkillEnhancementCostNumericalFunctionId int32
+}
+
+// EntityMEvaluateCondition is table key "m_evaluate_condition".
+type EntityMEvaluateCondition struct {
+ _ struct{} `msgpack:",array"`
+ EvaluateConditionId int32
+ EvaluateConditionFunctionType int32
+ EvaluateConditionEvaluateType int32
+ EvaluateConditionValueGroupId int32
+ NameEvaluateConditionTextId int32
+}
+
+// EntityMEvaluateConditionValueGroup is table key "m_evaluate_condition_value_group".
+type EntityMEvaluateConditionValueGroup struct {
+ _ struct{} `msgpack:",array"`
+ EvaluateConditionValueGroupId int32
+ GroupIndex int32
+ Value int64
+}
+
+// EntityMExplore is table key "m_explore".
+type EntityMExplore struct {
+ _ struct{} `msgpack:",array"`
+ ExploreId int32
+ ExploreUnlockConditionId int32
+ StartDatetime int64
+ ConsumeItemCount int32
+ RewardLotteryCount int32
+}
+
+// EntityMExploreGradeAsset is table key "m_explore_grade_asset".
+type EntityMExploreGradeAsset struct {
+ _ struct{} `msgpack:",array"`
+ ExploreGradeId int32
+ AssetGradeIconId int32
+}
+
+// EntityMExploreGradeScore is table key "m_explore_grade_score".
+type EntityMExploreGradeScore struct {
+ _ struct{} `msgpack:",array"`
+ ExploreId int32
+ NecessaryScore int32
+ ExploreGradeId int32
+}
+
+// EntityMGachaMedal is table key "m_gacha_medal".
+type EntityMGachaMedal struct {
+ _ struct{} `msgpack:",array"`
+ GachaMedalId int32
+ CeilingCount int32
+ ConsumableItemId int32
+ ShopTransitionGachaId int32
+ AutoConvertDatetime int64
+ ConversionRate int32
+}
+
+// EntityMGimmickSequenceSchedule is table key "m_gimmick_sequence_schedule".
+type EntityMGimmickSequenceSchedule struct {
+ _ struct{} `msgpack:",array"`
+ GimmickSequenceScheduleId int32
+ StartDatetime int64
+ EndDatetime int64
+ FirstGimmickSequenceId int32
+ ReleaseEvaluateConditionId int32
+}
+
+// EntityMLoginBonusStamp is table key "m_login_bonus_stamp".
+type EntityMLoginBonusStamp struct {
+ _ struct{} `msgpack:",array"`
+ LoginBonusId int32
+ LowerPageNumber int32
+ StampNumber int32
+ RewardPossessionType int32
+ RewardPossessionId int32
+ RewardCount int32
+}
+
+// EntityMMainQuestChapter is table key "m_main_quest_chapter".
+type EntityMMainQuestChapter struct {
+ _ struct{} `msgpack:",array"`
+ MainQuestChapterId int32
+ MainQuestRouteId int32
+ SortOrder int32
+ MainQuestSequenceGroupId int32
+ PortalCageCharacterGroupId int32
+ StartDatetime int64
+ IsInvisibleInLibrary bool
+ JoinLibraryChapterId int32
+}
+
+// EntityMMainQuestRoute is table key "m_main_quest_route".
+type EntityMMainQuestRoute struct {
+ _ struct{} `msgpack:",array"`
+ MainQuestRouteId int32
+ MainQuestSeasonId int32
+ SortOrder int32
+ CharacterId int32
+}
+
+// EntityMMainQuestSequence is table key "m_main_quest_sequence".
+type EntityMMainQuestSequence struct {
+ _ struct{} `msgpack:",array"`
+ MainQuestSequenceId int32
+ SortOrder int32
+ QuestId int32
+}
+
+// EntityMMaterial is table key "m_material".
+type EntityMMaterial struct {
+ _ struct{} `msgpack:",array"`
+ MaterialId int32
+ MaterialType int32
+ RarityType int32
+ WeaponType int32
+ AttributeType int32
+ EffectValue int32
+ SellPrice int32
+ AssetName string
+ AssetCategoryId int32
+ AssetVariationId int32
+ MaterialSaleObtainPossessionId int32
+}
+
+// EntityMMomBanner is table key "m_mom_banner".
+type EntityMMomBanner struct {
+ _ struct{} `msgpack:",array"`
+ MomBannerId int32
+ SortOrderDesc int32
+ DestinationDomainType int32
+ DestinationDomainId int32
+ BannerAssetName string
+ IsEmphasis bool
+ StartDatetime int64
+ EndDatetime int64
+ TargetUserStatusType int32
+}
+
+// EntityMNumericalFunction is table key "m_numerical_function".
+type EntityMNumericalFunction struct {
+ _ struct{} `msgpack:",array"`
+ NumericalFunctionId int32
+ NumericalFunctionType int32
+ NumericalFunctionParameterGroupId int32
+}
+
+// EntityMNumericalFunctionParameterGroup is table key "m_numerical_function_parameter_group".
+type EntityMNumericalFunctionParameterGroup struct {
+ _ struct{} `msgpack:",array"`
+ NumericalFunctionParameterGroupId int32
+ ParameterIndex int32
+ ParameterValue int32
+}
+
+// EntityMNumericalParameterMap is table key "m_numerical_parameter_map".
+type EntityMNumericalParameterMap struct {
+ _ struct{} `msgpack:",array"`
+ NumericalParameterMapId int32
+ ParameterKey int32
+ ParameterValue int32
+}
+
+// EntityMOmikuji is table key "m_omikuji".
+type EntityMOmikuji struct {
+ _ struct{} `msgpack:",array"`
+ OmikujiId int32
+ StartDatetime int64
+ EndDatetime int64
+ OmikujiAssetId int32
+}
+
+// EntityMParts is table key "m_parts".
+type EntityMParts struct {
+ _ struct{} `msgpack:",array"`
+ PartsId int32
+ RarityType int32
+ PartsGroupId int32
+ PartsStatusMainLotteryGroupId int32
+ PartsStatusSubLotteryGroupId int32
+ PartsInitialLotteryId int32
+}
+
+// EntityMPartsLevelUpPriceGroup is table key "m_parts_level_up_price_group".
+type EntityMPartsLevelUpPriceGroup struct {
+ _ struct{} `msgpack:",array"`
+ PartsLevelUpPriceGroupId int32
+ LevelLowerLimit int32
+ Gold int32
+}
+
+// EntityMPartsLevelUpRateGroup is table key "m_parts_level_up_rate_group".
+type EntityMPartsLevelUpRateGroup struct {
+ _ struct{} `msgpack:",array"`
+ PartsLevelUpRateGroupId int32
+ LevelLowerLimit int32
+ SuccessRatePermil int32
+}
+
+// EntityMPartsRarity is table key "m_parts_rarity".
+type EntityMPartsRarity struct {
+ _ struct{} `msgpack:",array"`
+ RarityType int32
+ PartsLevelUpRateGroupId int32
+ PartsLevelUpPriceGroupId int32
+ SellPriceNumericalFunctionId int32
+}
+
+// EntityMQuest is table key "m_quest".
+type EntityMQuest struct {
+ _ struct{} `msgpack:",array"`
+ QuestId int32
+ NameQuestTextId int32
+ PictureBookNameQuestTextId int32
+ QuestReleaseConditionListId int32
+ StoryQuestTextId int32
+ QuestDisplayAttributeGroupId int32
+ RecommendedDeckPower int32
+ QuestFirstClearRewardGroupId int32
+ QuestPickupRewardGroupId int32
+ QuestDeckRestrictionGroupId int32
+ QuestMissionGroupId int32
+ Stamina int32
+ UserExp int32
+ CharacterExp int32
+ CostumeExp int32
+ Gold int32
+ DailyClearableCount int32
+ IsRunInTheBackground bool
+ IsCountedAsQuest bool
+ QuestBonusId int32
+ IsNotShowAfterClear bool
+ IsBigWinTarget bool
+ IsUsableSkipTicket bool
+ QuestReplayFlowRewardGroupId int32
+ InvisibleQuestMissionGroupId int32
+ FieldEffectGroupId int32
+}
+
+// EntityMQuestFirstClearRewardGroup is table key "m_quest_first_clear_reward_group".
+type EntityMQuestFirstClearRewardGroup struct {
+ _ struct{} `msgpack:",array"`
+ QuestFirstClearRewardGroupId int32
+ QuestFirstClearRewardType int32
+ SortOrder int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+ IsPickup bool
+}
+
+// EntityMQuestFirstClearRewardSwitch is table key "m_quest_first_clear_reward_switch".
+type EntityMQuestFirstClearRewardSwitch struct {
+ _ struct{} `msgpack:",array"`
+ QuestId int32
+ QuestFirstClearRewardGroupId int32
+ SwitchConditionClearQuestId int32
+}
+
+// EntityMQuestMission is table key "m_quest_mission".
+type EntityMQuestMission struct {
+ _ struct{} `msgpack:",array"`
+ QuestMissionId int32
+ QuestMissionConditionType int32
+ ConditionValue int32
+ QuestMissionRewardId int32
+ QuestMissionConditionValueGroupId int32
+}
+
+// EntityMQuestMissionGroup is table key "m_quest_mission_group".
+type EntityMQuestMissionGroup struct {
+ _ struct{} `msgpack:",array"`
+ QuestMissionGroupId int32
+ SortOrder int32
+ QuestMissionId int32
+}
+
+// EntityMQuestMissionReward is table key "m_quest_mission_reward".
+type EntityMQuestMissionReward struct {
+ _ struct{} `msgpack:",array"`
+ QuestMissionRewardId int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+}
+
+// EntityMQuestPickupRewardGroup is table key "m_quest_pickup_reward_group".
+type EntityMQuestPickupRewardGroup struct {
+ _ struct{} `msgpack:",array"`
+ QuestPickupRewardGroupId int32
+ SortOrder int32
+ BattleDropRewardId int32
+}
+
+// EntityMQuestReplayFlowRewardGroup is table key "m_quest_replay_flow_reward_group".
+type EntityMQuestReplayFlowRewardGroup struct {
+ _ struct{} `msgpack:",array"`
+ QuestReplayFlowRewardGroupId int32
+ SortOrder int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+}
+
+// EntityMQuestScene is table key "m_quest_scene".
+type EntityMQuestScene struct {
+ _ struct{} `msgpack:",array"`
+ QuestSceneId int32
+ QuestId int32
+ SortOrder int32
+ QuestSceneType int32
+ AssetBackgroundId int32
+ EventMapNumberUpper int32
+ EventMapNumberLower int32
+ IsMainFlowQuestTarget bool
+ IsBattleOnlyTarget bool
+ QuestResultType int32
+ IsStorySkipTarget bool
+}
+
+// EntityMQuestSceneBattle is table key "m_quest_scene_battle".
+type EntityMQuestSceneBattle struct {
+ _ struct{} `msgpack:",array"`
+ QuestSceneId int32
+ BattleGroupId int32
+ BattleDropBoxGroupId int32
+ BattleFieldLocaleSettingIndex int32
+ BattleEventGroupId int32
+ PostProcessConfigurationIndex int32
+}
+
+// EntityMShop is table key "m_shop".
+type EntityMShop struct {
+ _ struct{} `msgpack:",array"`
+ ShopId int32
+ ShopGroupType int32
+ SortOrderInShopGroup int32
+ ShopType int32
+ NameShopTextId int32
+ ShopUpdatableLabelType int32
+ ShopExchangeType int32
+ ShopItemCellGroupId int32
+ RelatedMainFunctionType int32
+ StartDatetime int64
+ EndDatetime int64
+ LimitedOpenId int32
+}
+
+// EntityMShopItem is table key "m_shop_item".
+type EntityMShopItem struct {
+ _ struct{} `msgpack:",array"`
+ ShopItemId int32
+ NameShopTextId int32
+ DescriptionShopTextId int32
+ ShopItemContentType int32
+ PriceType int32
+ PriceId int32
+ Price int32
+ RegularPrice int32
+ ShopPromotionType int32
+ ShopItemLimitedStockId int32
+ AssetCategoryId int32
+ AssetVariationId int32
+ ShopItemDecorationType int32
+}
+
+// EntityMShopItemCell is table key "m_shop_item_cell".
+type EntityMShopItemCell struct {
+ _ struct{} `msgpack:",array"`
+ ShopItemCellId int32
+ StepNumber int32
+ ShopItemId int32
+}
+
+// EntityMShopItemCellGroup is table key "m_shop_item_cell_group".
+type EntityMShopItemCellGroup struct {
+ _ struct{} `msgpack:",array"`
+ ShopItemCellGroupId int32
+ ShopItemCellId int32
+ SortOrder int32
+ ShopItemCellTermId int32
+}
+
+// EntityMShopItemContentEffect is table key "m_shop_item_content_effect".
+type EntityMShopItemContentEffect struct {
+ _ struct{} `msgpack:",array"`
+ ShopItemId int32
+ EffectTargetType int32
+ EffectValueType int32
+ EffectValue int32
+}
+
+// EntityMShopItemContentPossession is table key "m_shop_item_content_possession".
+type EntityMShopItemContentPossession struct {
+ _ struct{} `msgpack:",array"`
+ ShopItemId int32
+ PossessionType int32
+ PossessionId int32
+ SortOrder int32
+ Count int32
+}
+
+// EntityMShopItemLimitedStock is table key "m_shop_item_limited_stock".
+type EntityMShopItemLimitedStock struct {
+ _ struct{} `msgpack:",array"`
+ ShopItemLimitedStockId int32
+ MaxCount int32
+ ShopItemAutoResetType int32
+ ShopItemAutoResetPeriod int32
+}
+
+// EntityMSideStoryQuestScene is table key "m_side_story_quest_scene".
+type EntityMSideStoryQuestScene struct {
+ _ struct{} `msgpack:",array"`
+ SideStoryQuestId int32
+ SideStoryQuestSceneId int32
+ SortOrder int32
+ AssetBackgroundId int32
+ EventMapNumberUpper int32
+ EventMapNumberLower int32
+}
+
+// EntityMTutorialUnlockCondition is table key "m_tutorial_unlock_condition".
+type EntityMTutorialUnlockCondition struct {
+ _ struct{} `msgpack:",array"`
+ TutorialType int32
+ TutorialUnlockConditionType int32
+ ConditionValue int32
+}
+
+// EntityMUserLevel is table key "m_user_level".
+type EntityMUserLevel struct {
+ _ struct{} `msgpack:",array"`
+ UserLevel int32
+ MaxStamina int32
+}
+
+// EntityMUserQuestSceneGrantPossession is table key "m_user_quest_scene_grant_possession".
+type EntityMUserQuestSceneGrantPossession struct {
+ _ struct{} `msgpack:",array"`
+ QuestSceneId int32
+ PossessionType int32
+ PossessionId int32
+ Count int32
+ IsGift bool
+ IsDebug bool
+}
+
+// EntityMWeapon is table key "m_weapon".
+type EntityMWeapon struct {
+ _ struct{} `msgpack:",array"`
+ WeaponId int32
+ WeaponCategoryType int32
+ WeaponType int32
+ AssetVariationId int32
+ RarityType int32
+ AttributeType int32
+ IsRestrictDiscard bool
+ WeaponBaseStatusId int32
+ WeaponStatusCalculationId int32
+ WeaponSkillGroupId int32
+ WeaponAbilityGroupId int32
+ WeaponEvolutionMaterialGroupId int32
+ WeaponEvolutionGrantPossessionGroupId int32
+ WeaponStoryReleaseConditionGroupId int32
+ WeaponSpecificEnhanceId int32
+ WeaponSpecificLimitBreakMaterialGroupId int32
+ CharacterWalkaroundRangeType int32
+ IsRecyclable bool
+}
+
+// EntityMWeaponAbilityEnhancementMaterial is table key "m_weapon_ability_enhancement_material".
+type EntityMWeaponAbilityEnhancementMaterial struct {
+ _ struct{} `msgpack:",array"`
+ WeaponAbilityEnhancementMaterialId int32
+ AbilityLevel int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMWeaponAbilityGroup is table key "m_weapon_ability_group".
+type EntityMWeaponAbilityGroup struct {
+ _ struct{} `msgpack:",array"`
+ WeaponAbilityGroupId int32
+ SlotNumber int32
+ AbilityId int32
+ WeaponAbilityEnhancementMaterialId int32
+}
+
+// EntityMWeaponAwaken is table key "m_weapon_awaken".
+type EntityMWeaponAwaken struct {
+ _ struct{} `msgpack:",array"`
+ WeaponId int32
+ WeaponAwakenEffectGroupId int32
+ WeaponAwakenMaterialGroupId int32
+ ConsumeGold int32
+ LevelLimitUp int32
+}
+
+// EntityMWeaponAwakenMaterialGroup is table key "m_weapon_awaken_material_group".
+type EntityMWeaponAwakenMaterialGroup struct {
+ _ struct{} `msgpack:",array"`
+ WeaponAwakenMaterialGroupId int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMWeaponConsumeExchangeConsumableItemGroup is table key "m_weapon_consume_exchange_consumable_item_group".
+type EntityMWeaponConsumeExchangeConsumableItemGroup struct {
+ _ struct{} `msgpack:",array"`
+ WeaponId int32
+ ConsumableItemId int32
+ Count int32
+}
+
+// EntityMWeaponEvolutionGroup is table key "m_weapon_evolution_group".
+type EntityMWeaponEvolutionGroup struct {
+ _ struct{} `msgpack:",array"`
+ WeaponEvolutionGroupId int32
+ EvolutionOrder int32
+ WeaponId int32
+}
+
+// EntityMWeaponEvolutionMaterialGroup is table key "m_weapon_evolution_material_group".
+type EntityMWeaponEvolutionMaterialGroup struct {
+ _ struct{} `msgpack:",array"`
+ WeaponEvolutionMaterialGroupId int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMWeaponRarity is table key "m_weapon_rarity".
+type EntityMWeaponRarity struct {
+ _ struct{} `msgpack:",array"`
+ RarityType int32
+ BaseEnhancementObtainedExp int32
+ SellPriceNumericalFunctionId int32
+ MaxLevelNumericalFunctionId int32
+ MaxSkillLevelNumericalFunctionId int32
+ MaxAbilityLevelNumericalFunctionId int32
+ RequiredExpForLevelUpNumericalParameterMapId int32
+ EnhancementCostByWeaponNumericalFunctionId int32
+ EnhancementCostByMaterialNumericalFunctionId int32
+ SkillEnhancementCostNumericalFunctionId int32
+ AbilityEnhancementCostNumericalFunctionId int32
+ LimitBreakCostByWeaponNumericalFunctionId int32
+ LimitBreakCostByMaterialNumericalFunctionId int32
+ EvolutionCostNumericalFunctionId int32
+}
+
+// EntityMWeaponSkillEnhancementMaterial is table key "m_weapon_skill_enhancement_material".
+type EntityMWeaponSkillEnhancementMaterial struct {
+ _ struct{} `msgpack:",array"`
+ WeaponSkillEnhancementMaterialId int32
+ SkillLevel int32
+ MaterialId int32
+ Count int32
+ SortOrder int32
+}
+
+// EntityMWeaponSkillGroup is table key "m_weapon_skill_group".
+type EntityMWeaponSkillGroup struct {
+ _ struct{} `msgpack:",array"`
+ WeaponSkillGroupId int32
+ SlotNumber int32
+ SkillId int32
+ WeaponSkillEnhancementMaterialId int32
+}
+
+// EntityMWeaponSpecificEnhance is table key "m_weapon_specific_enhance".
+type EntityMWeaponSpecificEnhance struct {
+ _ struct{} `msgpack:",array"`
+ WeaponSpecificEnhanceId int32
+ BaseEnhancementObtainedExp int32
+ SellPriceNumericalFunctionId int32
+ MaxLevelNumericalFunctionId int32
+ MaxSkillLevelNumericalFunctionId int32
+ MaxAbilityLevelNumericalFunctionId int32
+ RequiredExpForLevelUpNumericalParameterMapId int32
+ EnhancementCostByWeaponNumericalFunctionId int32
+ EnhancementCostByMaterialNumericalFunctionId int32
+ SkillEnhancementCostNumericalFunctionId int32
+ AbilityEnhancementCostNumericalFunctionId int32
+ LimitBreakCostByWeaponNumericalFunctionId int32
+ LimitBreakCostByMaterialNumericalFunctionId int32
+ EvolutionCostNumericalFunctionId int32
+}
+
+// EntityMWeaponStoryReleaseConditionGroup is table key "m_weapon_story_release_condition_group".
+type EntityMWeaponStoryReleaseConditionGroup struct {
+ _ struct{} `msgpack:",array"`
+ WeaponStoryReleaseConditionGroupId int32
+ StoryIndex int32
+ WeaponStoryReleaseConditionType int32
+ ConditionValue int32
+ WeaponStoryReleaseConditionOperationGroupId int32
+}
diff --git a/server/internal/masterdata/explore.go b/server/internal/masterdata/explore.go
index 0935087..83d2bb7 100644
--- a/server/internal/masterdata/explore.go
+++ b/server/internal/masterdata/explore.go
@@ -7,48 +7,31 @@ import (
"lunar-tear/server/internal/utils"
)
-type ExploreRow struct {
- ExploreId int32 `json:"ExploreId"`
- ConsumeItemCount int32 `json:"ConsumeItemCount"`
- RewardLotteryCount int32 `json:"RewardLotteryCount"`
-}
-
-type ExploreGradeScoreRow struct {
- ExploreId int32 `json:"ExploreId"`
- NecessaryScore int32 `json:"NecessaryScore"`
- ExploreGradeId int32 `json:"ExploreGradeId"`
-}
-
-type ExploreGradeAssetRow struct {
- ExploreGradeId int32 `json:"ExploreGradeId"`
- AssetGradeIconId int32 `json:"AssetGradeIconId"`
-}
-
type ExploreCatalog struct {
- Explores map[int32]ExploreRow
- GradeScores map[int32][]ExploreGradeScoreRow // keyed by ExploreId, sorted desc by NecessaryScore
- GradeAssets map[int32]int32 // gradeId -> assetGradeIconId
+ Explores map[int32]EntityMExplore
+ GradeScores map[int32][]EntityMExploreGradeScore // keyed by ExploreId, sorted desc by NecessaryScore
+ GradeAssets map[int32]int32 // gradeId -> assetGradeIconId
}
func LoadExploreCatalog() (*ExploreCatalog, error) {
- explores, err := utils.ReadJSON[ExploreRow]("EntityMExploreTable.json")
+ explores, err := utils.ReadTable[EntityMExplore]("m_explore")
if err != nil {
return nil, fmt.Errorf("load explore table: %w", err)
}
- gradeScores, err := utils.ReadJSON[ExploreGradeScoreRow]("EntityMExploreGradeScoreTable.json")
+ gradeScores, err := utils.ReadTable[EntityMExploreGradeScore]("m_explore_grade_score")
if err != nil {
return nil, fmt.Errorf("load explore grade score table: %w", err)
}
- gradeAssets, err := utils.ReadJSON[ExploreGradeAssetRow]("EntityMExploreGradeAssetTable.json")
+ gradeAssets, err := utils.ReadTable[EntityMExploreGradeAsset]("m_explore_grade_asset")
if err != nil {
return nil, fmt.Errorf("load explore grade asset table: %w", err)
}
catalog := &ExploreCatalog{
- Explores: make(map[int32]ExploreRow, len(explores)),
- GradeScores: make(map[int32][]ExploreGradeScoreRow),
+ Explores: make(map[int32]EntityMExplore, len(explores)),
+ GradeScores: make(map[int32][]EntityMExploreGradeScore),
GradeAssets: make(map[int32]int32, len(gradeAssets)),
}
diff --git a/server/internal/masterdata/gacha.go b/server/internal/masterdata/gacha.go
index a42913f..0648fef 100644
--- a/server/internal/masterdata/gacha.go
+++ b/server/internal/masterdata/gacha.go
@@ -10,24 +10,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type gachaMedalRow struct {
- GachaMedalId int32 `json:"GachaMedalId"`
- ShopTransitionGachaId int32 `json:"ShopTransitionGachaId"`
- ConsumableItemId int32 `json:"ConsumableItemId"`
- AutoConvertDatetime int64 `json:"AutoConvertDatetime"`
- ConversionRate int32 `json:"ConversionRate"`
-}
-
-type momBannerRow struct {
- MomBannerId int32 `json:"MomBannerId"`
- SortOrderDesc int32 `json:"SortOrderDesc"`
- DestinationDomainType int32 `json:"DestinationDomainType"`
- DestinationDomainId int32 `json:"DestinationDomainId"`
- BannerAssetName string `json:"BannerAssetName"`
- StartDatetime int64 `json:"StartDatetime"`
- EndDatetime int64 `json:"EndDatetime"`
-}
-
type GachaMedalInfo struct {
GachaMedalId int32
ConsumableItemId int32
@@ -38,16 +20,16 @@ type GachaMedalInfo struct {
const chapterGachaIdBase int32 = 200000
func LoadGachaCatalog() ([]store.GachaCatalogEntry, map[int32]GachaMedalInfo, error) {
- medals, err := utils.ReadJSON[gachaMedalRow]("EntityMGachaMedalTable.json")
+ medals, err := utils.ReadTable[EntityMGachaMedal]("m_gacha_medal")
if err != nil {
return nil, nil, fmt.Errorf("load gacha medal table: %w", err)
}
- banners, err := utils.ReadJSON[momBannerRow]("EntityMMomBannerTable.json")
+ banners, err := utils.ReadTable[EntityMMomBanner]("m_mom_banner")
if err != nil {
return nil, nil, fmt.Errorf("load mom banner table: %w", err)
}
- gachaToMedal := make(map[int32]gachaMedalRow)
+ gachaToMedal := make(map[int32]EntityMGachaMedal)
medalInfoByGacha := make(map[int32]GachaMedalInfo)
for _, m := range medals {
gachaToMedal[m.ShopTransitionGachaId] = m
@@ -59,7 +41,7 @@ func LoadGachaCatalog() ([]store.GachaCatalogEntry, map[int32]GachaMedalInfo, er
}
}
- stepupSteps := make(map[int32][]momBannerRow)
+ stepupSteps := make(map[int32][]EntityMMomBanner)
var entries []store.GachaCatalogEntry
for _, b := range banners {
diff --git a/server/internal/masterdata/gacha_pool.go b/server/internal/masterdata/gacha_pool.go
index 6ea0c49..0f4d5bd 100644
--- a/server/internal/masterdata/gacha_pool.go
+++ b/server/internal/masterdata/gacha_pool.go
@@ -46,58 +46,28 @@ type GachaCatalog struct {
ShopFeaturedByMedal map[int32][]ShopFeaturedEntry // consumableId -> paired entries
}
-type costumePoolRow struct {
- CostumeId int32 `json:"CostumeId"`
- CharacterId int32 `json:"CharacterId"`
- SkillfulWeaponType int32 `json:"SkillfulWeaponType"`
- RarityType int32 `json:"RarityType"`
-}
-
-type weaponPoolRow struct {
- WeaponId int32 `json:"WeaponId"`
- WeaponType int32 `json:"WeaponType"`
- RarityType int32 `json:"RarityType"`
- IsRestrictDiscard bool `json:"IsRestrictDiscard"`
-}
-
-type catalogCostumeRow struct {
- CostumeId int32 `json:"CostumeId"`
- CatalogTermId int32 `json:"CatalogTermId"`
-}
-
-type catalogWeaponRow struct {
- WeaponId int32 `json:"WeaponId"`
- CatalogTermId int32 `json:"CatalogTermId"`
-}
-
-type materialPoolRow struct {
- MaterialId int32 `json:"MaterialId"`
- MaterialType int32 `json:"MaterialType"`
- RarityType int32 `json:"RarityType"`
-}
-
func LoadGachaPool() (*GachaCatalog, error) {
- costumes, err := utils.ReadJSON[costumePoolRow]("EntityMCostumeTable.json")
+ costumes, err := utils.ReadTable[EntityMCostume]("m_costume")
if err != nil {
return nil, fmt.Errorf("load costume table: %w", err)
}
- weapons, err := utils.ReadJSON[weaponPoolRow]("EntityMWeaponTable.json")
+ weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon")
if err != nil {
return nil, fmt.Errorf("load weapon table: %w", err)
}
- catalogCostumes, err := utils.ReadJSON[catalogCostumeRow]("EntityMCatalogCostumeTable.json")
+ catalogCostumes, err := utils.ReadTable[EntityMCatalogCostume]("m_catalog_costume")
if err != nil {
return nil, fmt.Errorf("load catalog costume table: %w", err)
}
- catalogWeapons, err := utils.ReadJSON[catalogWeaponRow]("EntityMCatalogWeaponTable.json")
+ catalogWeapons, err := utils.ReadTable[EntityMCatalogWeapon]("m_catalog_weapon")
if err != nil {
return nil, fmt.Errorf("load catalog weapon table: %w", err)
}
- materials, err := utils.ReadJSON[materialPoolRow]("EntityMMaterialTable.json")
+ materials, err := utils.ReadTable[EntityMMaterial]("m_material")
if err != nil {
return nil, fmt.Errorf("load material table: %w", err)
}
- evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json")
+ evoGroupRows, err := utils.ReadTable[EntityMWeaponEvolutionGroup]("m_weapon_evolution_group")
if err != nil {
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
}
@@ -414,8 +384,8 @@ func (pool *GachaCatalog) BuildBannerPools(entries []store.GachaCatalogEntry) {
len(pool.BannerPools), len(allFeaturedCostumes), len(allFeaturedWeapons))
}
-func buildEvolvedWeaponSet(rows []WeaponEvolutionGroupRow) map[int32]bool {
- grouped := make(map[int32][]WeaponEvolutionGroupRow)
+func buildEvolvedWeaponSet(rows []EntityMWeaponEvolutionGroup) map[int32]bool {
+ grouped := make(map[int32][]EntityMWeaponEvolutionGroup)
for _, r := range rows {
grouped[r.WeaponEvolutionGroupId] = append(grouped[r.WeaponEvolutionGroupId], r)
}
diff --git a/server/internal/masterdata/gimmick.go b/server/internal/masterdata/gimmick.go
index eb8ca76..1a320ee 100644
--- a/server/internal/masterdata/gimmick.go
+++ b/server/internal/masterdata/gimmick.go
@@ -9,14 +9,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type gimmickScheduleRow struct {
- GimmickSequenceScheduleId int32 `json:"GimmickSequenceScheduleId"`
- StartDatetime int64 `json:"StartDatetime"`
- EndDatetime int64 `json:"EndDatetime"`
- FirstGimmickSequenceId int32 `json:"FirstGimmickSequenceId"`
- ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"`
-}
-
type gimmickScheduleEntry struct {
ScheduleId int32
StartDatetime int64
@@ -30,7 +22,7 @@ type GimmickCatalog struct {
}
func LoadGimmickCatalog(resolver *ConditionResolver) (*GimmickCatalog, error) {
- rows, err := utils.ReadJSON[gimmickScheduleRow]("EntityMGimmickSequenceScheduleTable.json")
+ rows, err := utils.ReadTable[EntityMGimmickSequenceSchedule]("m_gimmick_sequence_schedule")
if err != nil {
return nil, fmt.Errorf("load gimmick sequence schedule table: %w", err)
}
diff --git a/server/internal/masterdata/loginbonus.go b/server/internal/masterdata/loginbonus.go
index 11122de..13387e2 100644
--- a/server/internal/masterdata/loginbonus.go
+++ b/server/internal/masterdata/loginbonus.go
@@ -5,15 +5,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type loginBonusStamp struct {
- LoginBonusId int32 `json:"LoginBonusId"`
- LowerPageNumber int32 `json:"LowerPageNumber"`
- StampNumber int32 `json:"StampNumber"`
- RewardPossessionType int32 `json:"RewardPossessionType"`
- RewardPossessionId int32 `json:"RewardPossessionId"`
- RewardCount int32 `json:"RewardCount"`
-}
-
type loginBonusStampKey struct {
LoginBonusId int32
LowerPageNumber int32
@@ -36,7 +27,7 @@ func (c *LoginBonusCatalog) LookupStampReward(loginBonusId, pageNumber, stampNum
}
func LoadLoginBonusCatalog() *LoginBonusCatalog {
- stamps, err := utils.ReadJSON[loginBonusStamp]("EntityMLoginBonusStampTable.json")
+ stamps, err := utils.ReadTable[EntityMLoginBonusStamp]("m_login_bonus_stamp")
if err != nil {
log.Fatalf("load login bonus stamp table: %v", err)
}
diff --git a/server/internal/masterdata/material.go b/server/internal/masterdata/material.go
index f1f13f0..fd8e4c1 100644
--- a/server/internal/masterdata/material.go
+++ b/server/internal/masterdata/material.go
@@ -7,29 +7,15 @@ import (
"lunar-tear/server/internal/utils"
)
-type MaterialRow struct {
- MaterialId int32 `json:"MaterialId"`
- MaterialType model.MaterialType `json:"MaterialType"`
- WeaponType int32 `json:"WeaponType"`
- EffectValue int32 `json:"EffectValue"`
- SellPrice int32 `json:"SellPrice"`
-}
-
-type numericalParameterMapRow struct {
- NumericalParameterMapId int32 `json:"NumericalParameterMapId"`
- ParameterKey int32 `json:"ParameterKey"`
- ParameterValue int32 `json:"ParameterValue"`
-}
-
-func LoadParameterMap() ([]numericalParameterMapRow, error) {
- rows, err := utils.ReadJSON[numericalParameterMapRow]("EntityMNumericalParameterMapTable.json")
+func LoadParameterMap() ([]EntityMNumericalParameterMap, error) {
+ rows, err := utils.ReadTable[EntityMNumericalParameterMap]("m_numerical_parameter_map")
if err != nil {
return nil, fmt.Errorf("load numerical parameter map table: %w", err)
}
return rows, nil
}
-func BuildExpThresholds(paramMapRows []numericalParameterMapRow, mapId int32) []int32 {
+func BuildExpThresholds(paramMapRows []EntityMNumericalParameterMap, mapId int32) []int32 {
maxKey := int32(0)
for _, r := range paramMapRows {
if r.NumericalParameterMapId == mapId && r.ParameterKey > maxKey {
@@ -46,26 +32,27 @@ func BuildExpThresholds(paramMapRows []numericalParameterMapRow, mapId int32) []
}
type MaterialCatalog struct {
- All map[int32]MaterialRow
- ByType map[model.MaterialType]map[int32]MaterialRow
+ All map[int32]EntityMMaterial
+ ByType map[model.MaterialType]map[int32]EntityMMaterial
}
func LoadMaterialCatalog() (*MaterialCatalog, error) {
- rows, err := utils.ReadJSON[MaterialRow]("EntityMMaterialTable.json")
+ rows, err := utils.ReadTable[EntityMMaterial]("m_material")
if err != nil {
return nil, fmt.Errorf("load material table: %w", err)
}
catalog := &MaterialCatalog{
- All: make(map[int32]MaterialRow, len(rows)),
- ByType: make(map[model.MaterialType]map[int32]MaterialRow),
+ All: make(map[int32]EntityMMaterial, len(rows)),
+ ByType: make(map[model.MaterialType]map[int32]EntityMMaterial),
}
for _, row := range rows {
catalog.All[row.MaterialId] = row
- if catalog.ByType[row.MaterialType] == nil {
- catalog.ByType[row.MaterialType] = make(map[int32]MaterialRow)
+ mt := model.MaterialType(row.MaterialType)
+ if catalog.ByType[mt] == nil {
+ catalog.ByType[mt] = make(map[int32]EntityMMaterial)
}
- catalog.ByType[row.MaterialType][row.MaterialId] = row
+ catalog.ByType[mt][row.MaterialId] = row
}
return catalog, nil
}
diff --git a/server/internal/masterdata/memorydb/memorydb.go b/server/internal/masterdata/memorydb/memorydb.go
new file mode 100644
index 0000000..c62f6b6
--- /dev/null
+++ b/server/internal/masterdata/memorydb/memorydb.go
@@ -0,0 +1,257 @@
+package memorydb
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "encoding/binary"
+ "encoding/hex"
+ "fmt"
+ "os"
+
+ "github.com/pierrec/lz4/v4"
+ "github.com/vmihailenco/msgpack/v5"
+)
+
+var tables map[string][]byte
+
+const (
+ aesKeyHex = "36436230313332314545356536624265"
+ aesIVHex = "45666341656634434165356536446141"
+ lz4ExtCode = int8(99)
+)
+
+func Init(binPath string) error {
+ encrypted, err := os.ReadFile(binPath)
+ if err != nil {
+ return fmt.Errorf("read bin.e: %w", err)
+ }
+
+ decrypted, err := decrypt(encrypted)
+ if err != nil {
+ return fmt.Errorf("decrypt: %w", err)
+ }
+
+ toc, dataBlob, err := parseHeader(decrypted)
+ if err != nil {
+ return fmt.Errorf("parse header: %w", err)
+ }
+
+ tables = make(map[string][]byte, len(toc))
+ for name, offLen := range toc {
+ off := offLen[0]
+ length := offLen[1]
+ if off+length > len(dataBlob) {
+ return fmt.Errorf("table %q: offset %d + length %d exceeds data blob size %d", name, off, length, len(dataBlob))
+ }
+ tables[name] = dataBlob[off : off+length]
+ }
+
+ return nil
+}
+
+func TableCount() int {
+ return len(tables)
+}
+
+func TableBytes(key string) ([]byte, bool) {
+ b, ok := tables[key]
+ return b, ok
+}
+
+func ReadTable[T any](key string) ([]T, error) {
+ raw, ok := TableBytes(key)
+ if !ok {
+ return nil, fmt.Errorf("table %q not found in master data", key)
+ }
+ return decompressAndUnmarshal[T](raw)
+}
+
+func decrypt(data []byte) ([]byte, error) {
+ key, err := hex.DecodeString(aesKeyHex)
+ if err != nil {
+ return nil, fmt.Errorf("decode key: %w", err)
+ }
+ iv, err := hex.DecodeString(aesIVHex)
+ if err != nil {
+ return nil, fmt.Errorf("decode iv: %w", err)
+ }
+
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, fmt.Errorf("new cipher: %w", err)
+ }
+
+ if len(data)%aes.BlockSize != 0 {
+ return nil, fmt.Errorf("ciphertext length %d is not a multiple of block size %d", len(data), aes.BlockSize)
+ }
+
+ decrypted := make([]byte, len(data))
+ cbc := cipher.NewCBCDecrypter(block, iv)
+ cbc.CryptBlocks(decrypted, data)
+
+ decrypted, err = pkcs7Unpad(decrypted, aes.BlockSize)
+ if err != nil {
+ return nil, fmt.Errorf("unpad: %w", err)
+ }
+ return decrypted, nil
+}
+
+func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
+ if len(data) == 0 {
+ return nil, fmt.Errorf("empty data")
+ }
+ padLen := int(data[len(data)-1])
+ if padLen == 0 || padLen > blockSize || padLen > len(data) {
+ return nil, fmt.Errorf("invalid padding length %d", padLen)
+ }
+ for _, b := range data[len(data)-padLen:] {
+ if int(b) != padLen {
+ return nil, fmt.Errorf("invalid padding byte")
+ }
+ }
+ return data[:len(data)-padLen], nil
+}
+
+func parseHeader(data []byte) (map[string][2]int, []byte, error) {
+ // Decode the header (first msgpack object) using Decode into interface{},
+ // then compute how many bytes it consumed to find the data blob start.
+ r := bytes.NewReader(data)
+ dec := msgpack.NewDecoder(r)
+ dec.UseLooseInterfaceDecoding(true)
+
+ var headerRaw interface{}
+ if err := dec.Decode(&headerRaw); err != nil {
+ return nil, nil, fmt.Errorf("decode header: %w", err)
+ }
+
+ headerMap, ok := headerRaw.(map[string]interface{})
+ if !ok {
+ return nil, nil, fmt.Errorf("header is not a map, got %T", headerRaw)
+ }
+
+ toc := make(map[string][2]int, len(headerMap))
+ for name, val := range headerMap {
+ arr, ok := val.([]interface{})
+ if !ok || len(arr) != 2 {
+ return nil, nil, fmt.Errorf("table %q: expected [offset, length] array, got %T", name, val)
+ }
+ offset, err := toInt(arr[0])
+ if err != nil {
+ return nil, nil, fmt.Errorf("table %q offset: %w", name, err)
+ }
+ length, err := toInt(arr[1])
+ if err != nil {
+ return nil, nil, fmt.Errorf("table %q length: %w", name, err)
+ }
+ toc[name] = [2]int{offset, length}
+ }
+
+ consumed := int(int64(len(data)) - int64(r.Len()))
+ return toc, data[consumed:], nil
+}
+
+func toInt(v interface{}) (int, error) {
+ switch n := v.(type) {
+ case int8:
+ return int(n), nil
+ case int16:
+ return int(n), nil
+ case int32:
+ return int(n), nil
+ case int64:
+ return int(n), nil
+ case uint8:
+ return int(n), nil
+ case uint16:
+ return int(n), nil
+ case uint32:
+ return int(n), nil
+ case uint64:
+ return int(n), nil
+ default:
+ return 0, fmt.Errorf("cannot convert %T to int", v)
+ }
+}
+
+func decompressAndUnmarshal[T any](raw []byte) ([]T, error) {
+ // Peek at the raw msgpack to check if it's an ext type (LZ4 compressed)
+ // or a plain array.
+ if len(raw) == 0 {
+ return nil, nil
+ }
+
+ // Try to decode as ext type first
+ dec := msgpack.NewDecoder(bytes.NewReader(raw))
+ code, extData, err := decodeExt(dec)
+ if err == nil && code == lz4ExtCode {
+ uncompressedSize, lz4Data, err := readLZ4ExtHeader(extData)
+ if err != nil {
+ return nil, fmt.Errorf("read lz4 ext header: %w", err)
+ }
+
+ decompressed := make([]byte, uncompressedSize)
+ n, err := lz4.UncompressBlock(lz4Data, decompressed)
+ if err != nil {
+ return nil, fmt.Errorf("lz4 decompress: %w", err)
+ }
+ decompressed = decompressed[:n]
+
+ var result []T
+ if err := msgpack.Unmarshal(decompressed, &result); err != nil {
+ return nil, fmt.Errorf("unmarshal decompressed table: %w", err)
+ }
+ return result, nil
+ }
+
+ // Not LZ4 compressed, try as plain array
+ var result []T
+ if err := msgpack.Unmarshal(raw, &result); err != nil {
+ return nil, fmt.Errorf("unmarshal plain table: %w", err)
+ }
+ return result, nil
+}
+
+func decodeExt(dec *msgpack.Decoder) (int8, []byte, error) {
+ var ext msgpack.RawMessage
+ if err := dec.Decode(&ext); err != nil {
+ return 0, nil, err
+ }
+ // ext is the full msgpack ext bytes including the header.
+ // Re-decode just the header to get the type code, then the body is the rest.
+ innerDec := msgpack.NewDecoder(bytes.NewReader(ext))
+ extID, extLen, err := innerDec.DecodeExtHeader()
+ if err != nil {
+ return 0, nil, err
+ }
+ extData := make([]byte, extLen)
+ if _, err := innerDec.Buffered().Read(extData); err != nil {
+ return 0, nil, fmt.Errorf("read ext data: %w", err)
+ }
+ return extID, extData, nil
+}
+
+func readLZ4ExtHeader(data []byte) (int, []byte, error) {
+ if len(data) == 0 {
+ return 0, nil, fmt.Errorf("empty ext data")
+ }
+ tag := data[0]
+ switch {
+ case tag == 0xd2: // big-endian int32
+ if len(data) < 5 {
+ return 0, nil, fmt.Errorf("not enough data for int32 size")
+ }
+ size := int(int32(binary.BigEndian.Uint32(data[1:5])))
+ return size, data[5:], nil
+ case tag == 0xce: // big-endian uint32
+ if len(data) < 5 {
+ return 0, nil, fmt.Errorf("not enough data for uint32 size")
+ }
+ size := int(binary.BigEndian.Uint32(data[1:5]))
+ return size, data[5:], nil
+ case tag <= 0x7f: // positive fixint
+ return int(tag), data[1:], nil
+ default:
+ return 0, nil, fmt.Errorf("unexpected tag 0x%02x in LZ4 ext header", tag)
+ }
+}
diff --git a/server/internal/masterdata/numericalfunc.go b/server/internal/masterdata/numericalfunc.go
index 0c4e3b6..55ad7fd 100644
--- a/server/internal/masterdata/numericalfunc.go
+++ b/server/internal/masterdata/numericalfunc.go
@@ -8,18 +8,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type numericalFunctionRow struct {
- NumericalFunctionId int32 `json:"NumericalFunctionId"`
- NumericalFunctionType int32 `json:"NumericalFunctionType"`
- NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"`
-}
-
-type numericalFunctionParameterRow struct {
- NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"`
- ParameterIndex int32 `json:"ParameterIndex"`
- ParameterValue int32 `json:"ParameterValue"`
-}
-
type NumericalFunc struct {
Type model.NumericalFunctionType
Params []int32
@@ -61,17 +49,17 @@ type FunctionResolver struct {
}
func LoadFunctionResolver() (*FunctionResolver, error) {
- funcRows, err := utils.ReadJSON[numericalFunctionRow]("EntityMNumericalFunctionTable.json")
+ funcRows, err := utils.ReadTable[EntityMNumericalFunction]("m_numerical_function")
if err != nil {
return nil, fmt.Errorf("load numerical function table: %w", err)
}
- paramRows, err := utils.ReadJSON[numericalFunctionParameterRow]("EntityMNumericalFunctionParameterGroupTable.json")
+ paramRows, err := utils.ReadTable[EntityMNumericalFunctionParameterGroup]("m_numerical_function_parameter_group")
if err != nil {
return nil, fmt.Errorf("load numerical function parameter group table: %w", err)
}
- paramsByGroup := make(map[int32][]numericalFunctionParameterRow, len(paramRows))
+ paramsByGroup := make(map[int32][]EntityMNumericalFunctionParameterGroup, len(paramRows))
for _, r := range paramRows {
paramsByGroup[r.NumericalFunctionParameterGroupId] = append(
paramsByGroup[r.NumericalFunctionParameterGroupId], r)
diff --git a/server/internal/masterdata/omikuji.go b/server/internal/masterdata/omikuji.go
index de2063d..546802f 100644
--- a/server/internal/masterdata/omikuji.go
+++ b/server/internal/masterdata/omikuji.go
@@ -5,11 +5,6 @@ import (
"lunar-tear/server/internal/utils"
)
-type omikujiEntry struct {
- OmikujiId int32 `json:"OmikujiId"`
- OmikujiAssetId int32 `json:"OmikujiAssetId"`
-}
-
type OmikujiCatalog struct {
assetIds map[int32]int32
}
@@ -22,7 +17,7 @@ func (c *OmikujiCatalog) LookupAssetId(omikujiId int32) int32 {
}
func LoadOmikujiCatalog() *OmikujiCatalog {
- entries, err := utils.ReadJSON[omikujiEntry]("EntityMOmikujiTable.json")
+ entries, err := utils.ReadTable[EntityMOmikuji]("m_omikuji")
if err != nil {
log.Fatalf("load omikuji table: %v", err)
}
diff --git a/server/internal/masterdata/parts.go b/server/internal/masterdata/parts.go
index ee7ce09..f2b00c8 100644
--- a/server/internal/masterdata/parts.go
+++ b/server/internal/masterdata/parts.go
@@ -7,63 +7,37 @@ import (
"lunar-tear/server/internal/utils"
)
-type PartsRow struct {
- PartsId int32 `json:"PartsId"`
- RarityType model.RarityType `json:"RarityType"`
- PartsGroupId int32 `json:"PartsGroupId"`
- PartsStatusMainLotteryGroupId int32 `json:"PartsStatusMainLotteryGroupId"`
-}
-
-type PartsRarityRow struct {
- RarityType model.RarityType `json:"RarityType"`
- PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"`
- PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"`
- SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
-}
-
-type partsLevelUpRateRow struct {
- PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"`
- LevelLowerLimit int32 `json:"LevelLowerLimit"`
- SuccessRatePermil int32 `json:"SuccessRatePermil"`
-}
-
-type partsLevelUpPriceRow struct {
- PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"`
- LevelLowerLimit int32 `json:"LevelLowerLimit"`
- Gold int32 `json:"Gold"`
-}
-
type PartsCatalog struct {
- PartsById map[int32]PartsRow
+ PartsById map[int32]EntityMParts
DefaultPartsStatusMainByLotteryGroup map[int32]int32
- RarityByRarityType map[model.RarityType]PartsRarityRow
+ RarityByRarityType map[model.RarityType]EntityMPartsRarity
RateByGroupAndLevel map[int32]map[int32]int32
PriceByGroupAndLevel map[int32]map[int32]int32
SellPriceByRarity map[model.RarityType]NumericalFunc
}
func LoadPartsCatalog() (*PartsCatalog, error) {
- partsRows, err := utils.ReadJSON[PartsRow]("EntityMPartsTable.json")
+ partsRows, err := utils.ReadTable[EntityMParts]("m_parts")
if err != nil {
return nil, fmt.Errorf("load parts table: %w", err)
}
- rarityRows, err := utils.ReadJSON[PartsRarityRow]("EntityMPartsRarityTable.json")
+ rarityRows, err := utils.ReadTable[EntityMPartsRarity]("m_parts_rarity")
if err != nil {
return nil, fmt.Errorf("load parts rarity table: %w", err)
}
- rateRows, err := utils.ReadJSON[partsLevelUpRateRow]("EntityMPartsLevelUpRateGroupTable.json")
+ rateRows, err := utils.ReadTable[EntityMPartsLevelUpRateGroup]("m_parts_level_up_rate_group")
if err != nil {
return nil, fmt.Errorf("load parts level up rate table: %w", err)
}
- priceRows, err := utils.ReadJSON[partsLevelUpPriceRow]("EntityMPartsLevelUpPriceGroupTable.json")
+ priceRows, err := utils.ReadTable[EntityMPartsLevelUpPriceGroup]("m_parts_level_up_price_group")
if err != nil {
return nil, fmt.Errorf("load parts level up price table: %w", err)
}
- partsById := make(map[int32]PartsRow, len(partsRows))
+ partsById := make(map[int32]EntityMParts, len(partsRows))
for _, p := range partsRows {
partsById[p.PartsId] = p
}
@@ -84,7 +58,7 @@ func LoadPartsCatalog() (*PartsCatalog, error) {
return nil, fmt.Errorf("load function resolver: %w", err)
}
- rarityByRarityType := make(map[model.RarityType]PartsRarityRow, len(rarityRows))
+ rarityByRarityType := make(map[model.RarityType]EntityMPartsRarity, len(rarityRows))
sellPriceByRarity := make(map[model.RarityType]NumericalFunc, len(rarityRows))
for _, r := range rarityRows {
rarityByRarityType[r.RarityType] = r
diff --git a/server/internal/masterdata/quest.go b/server/internal/masterdata/quest.go
index 4a65d69..9e2c5d7 100644
--- a/server/internal/masterdata/quest.go
+++ b/server/internal/masterdata/quest.go
@@ -4,214 +4,34 @@ import (
"fmt"
"sort"
- "lunar-tear/server/internal/model"
"lunar-tear/server/internal/utils"
)
-type QuestSceneRow struct {
- QuestSceneId int32 `json:"QuestSceneId"`
- QuestId int32 `json:"QuestId"`
- SortOrder int32 `json:"SortOrder"`
- QuestSceneType model.QuestSceneType `json:"QuestSceneType"`
- AssetBackgroundId int32 `json:"AssetBackgroundId"`
- EventMapNumberUpper int32 `json:"EventMapNumberUpper"`
- EventMapNumberLower int32 `json:"EventMapNumberLower"`
- IsMainFlowQuestTarget bool `json:"IsMainFlowQuestTarget"`
- IsBattleOnlyTarget bool `json:"IsBattleOnlyTarget"`
- QuestResultType model.QuestResultType `json:"QuestResultType"`
- IsStorySkipTarget bool `json:"IsStorySkipTarget"`
-}
-
-type QuestRow struct {
- QuestId int32 `json:"QuestId"`
- NameQuestTextId int32 `json:"NameQuestTextId"`
- PictureBookNameQuestTextId int32 `json:"PictureBookNameQuestTextId"`
- QuestReleaseConditionListId int32 `json:"QuestReleaseConditionListId"`
- StoryQuestTextId int32 `json:"StoryQuestTextId"`
- QuestDisplayAttributeGroupId int32 `json:"QuestDisplayAttributeGroupId"`
- RecommendedDeckPower int32 `json:"RecommendedDeckPower"`
- QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
- QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"`
- QuestDeckRestrictionGroupId int32 `json:"QuestDeckRestrictionGroupId"`
- QuestMissionGroupId int32 `json:"QuestMissionGroupId"`
- Stamina int32 `json:"Stamina"`
- UserExp int32 `json:"UserExp"`
- CharacterExp int32 `json:"CharacterExp"`
- CostumeExp int32 `json:"CostumeExp"`
- Gold int32 `json:"Gold"`
- DailyClearableCount int32 `json:"DailyClearableCount"`
- IsRunInTheBackground bool `json:"IsRunInTheBackground"`
- IsCountedAsQuest bool `json:"IsCountedAsQuest"`
- QuestBonusId int32 `json:"QuestBonusId"`
- IsNotShowAfterClear bool `json:"IsNotShowAfterClear"`
- IsBigWinTarget bool `json:"IsBigWinTarget"`
- IsUsableSkipTicket bool `json:"IsUsableSkipTicket"`
- QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"`
- InvisibleQuestMissionGroupId int32 `json:"InvisibleQuestMissionGroupId"`
- FieldEffectGroupId int32 `json:"FieldEffectGroupId"`
-}
-
-type QuestMissionRow struct {
- QuestMissionId int32 `json:"QuestMissionId"`
- QuestMissionConditionType model.QuestMissionConditionType `json:"QuestMissionConditionType"`
- QuestMissionRewardId int32 `json:"QuestMissionRewardId"`
- QuestMissionConditionValueGroupId int32 `json:"QuestMissionConditionValueGroupId"`
-}
-
-type QuestMissionGroupRow struct {
- QuestMissionGroupId int32 `json:"QuestMissionGroupId"`
- SortOrder int32 `json:"SortOrder"`
- QuestMissionId int32 `json:"QuestMissionId"`
-}
-
-type QuestMissionRewardRow struct {
- QuestMissionRewardId int32 `json:"QuestMissionRewardId"`
- PossessionType model.PossessionType `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
-type MainQuestSequenceRow struct {
- MainQuestSequenceId int32 `json:"MainQuestSequenceId"`
- SortOrder int32 `json:"SortOrder"`
- QuestId int32 `json:"QuestId"`
-}
-
-type MainQuestRouteRow struct {
- MainQuestRouteId int32 `json:"MainQuestRouteId"`
- MainQuestSeasonId int32 `json:"MainQuestSeasonId"`
- SortOrder int32 `json:"SortOrder"`
- CharacterId int32 `json:"CharacterId"`
-}
-
-type MainQuestChapterRow struct {
- MainQuestChapterId int32 `json:"MainQuestChapterId"`
- MainQuestRouteId int32 `json:"MainQuestRouteId"`
- SortOrder int32 `json:"SortOrder"`
- MainQuestSequenceGroupId int32 `json:"MainQuestSequenceGroupId"`
- PortalCageCharacterGroupId int32 `json:"PortalCageCharacterGroupId"`
- StartDatetime int64 `json:"StartDatetime"`
- IsInvisibleInLibrary bool `json:"IsInvisibleInLibrary"`
- JoinLibraryChapterId int32 `json:"JoinLibraryChapterId"`
-}
-
-type QuestFirstClearRewardSwitchRow struct {
- QuestId int32 `json:"QuestId"`
- QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
- SwitchConditionClearQuestId int32 `json:"SwitchConditionClearQuestId"`
-}
-
-type QuestFirstClearRewardGroupRow struct {
- QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
- QuestFirstClearRewardType int32 `json:"QuestFirstClearRewardType"`
- SortOrder int32 `json:"SortOrder"`
- PossessionType model.PossessionType `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
- IsPickup bool `json:"IsPickup"`
-}
-
-type QuestReplayFlowRewardGroupRow struct {
- QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"`
- SortOrder int32 `json:"SortOrder"`
- PossessionType model.PossessionType `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
-type QuestSceneGrantRow struct {
- QuestSceneId int32 `json:"QuestSceneId"`
- PossessionType model.PossessionType `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
-type QuestPickupRewardGroupRow struct {
- QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"`
- SortOrder int32 `json:"SortOrder"`
- BattleDropRewardId int32 `json:"BattleDropRewardId"`
-}
-
-type BattleDropRewardRow struct {
- BattleDropRewardId int32 `json:"BattleDropRewardId"`
- PossessionType model.PossessionType `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
-type QuestSceneBattleRow struct {
- QuestSceneId int32 `json:"QuestSceneId"`
- BattleGroupId int32 `json:"BattleGroupId"`
-}
-
-type BattleGroupRow struct {
- BattleGroupId int32 `json:"BattleGroupId"`
- WaveNumber int32 `json:"WaveNumber"`
- BattleId int32 `json:"BattleId"`
-}
-
-type BattleRow struct {
- BattleId int32 `json:"BattleId"`
- BattleNpcId int32 `json:"BattleNpcId"`
- DeckType model.DeckType `json:"DeckType"`
- BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"`
-}
-
-type BattleNpcDeckRow struct {
- BattleNpcId int32 `json:"BattleNpcId"`
- DeckType model.DeckType `json:"DeckType"`
- BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"`
- BattleNpcDeckCharacterUuid01 string `json:"BattleNpcDeckCharacterUuid01"`
- BattleNpcDeckCharacterUuid02 string `json:"BattleNpcDeckCharacterUuid02"`
- BattleNpcDeckCharacterUuid03 string `json:"BattleNpcDeckCharacterUuid03"`
-}
-
-type BattleNpcDropCategoryRow struct {
- BattleNpcId int32 `json:"BattleNpcId"`
- BattleNpcDeckCharacterUuid string `json:"BattleNpcDeckCharacterUuid"`
- BattleDropCategoryId int32 `json:"BattleDropCategoryId"`
-}
-
type BattleDropInfo struct {
QuestSceneId int32
BattleDropCategoryId int32
}
-type TutorialUnlockConditionRow struct {
- TutorialType int32 `json:"TutorialType"`
- TutorialUnlockConditionType int32 `json:"TutorialUnlockConditionType"`
- ConditionValue int32 `json:"ConditionValue"`
-}
-
-type RentalDeckRow struct {
- BattleGroupId int32 `json:"BattleGroupId"`
-}
-
-type UserLevelRow struct {
- UserLevel int32 `json:"UserLevel"`
- MaxStamina int32 `json:"MaxStamina"`
-}
-
type QuestCatalog struct {
- SceneById map[int32]QuestSceneRow
- MissionById map[int32]QuestMissionRow
- QuestById map[int32]QuestRow
+ SceneById map[int32]EntityMQuestScene
+ MissionById map[int32]EntityMQuestMission
+ QuestById map[int32]EntityMQuest
MissionIdsByQuestId map[int32][]int32
RouteIdByQuestId map[int32]int32
SceneIdsByQuestId map[int32][]int32
OrderedQuestIds []int32
- FirstClearRewardsByGroupId map[int32][]QuestFirstClearRewardGroupRow
- FirstClearRewardSwitchesByQuestId map[int32][]QuestFirstClearRewardSwitchRow
- MissionRewardsByMissionId map[int32][]QuestMissionRewardRow
+ FirstClearRewardsByGroupId map[int32][]EntityMQuestFirstClearRewardGroup
+ FirstClearRewardSwitchesByQuestId map[int32][]EntityMQuestFirstClearRewardSwitch
+ MissionRewardsByMissionId map[int32][]EntityMQuestMissionReward
WeaponIdsByReleaseConditionGroupId map[int32][]int32
- ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow
- SceneGrantsBySceneId map[int32][]QuestSceneGrantRow
- BattleDropRewardById map[int32]BattleDropRewardRow
+ ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup
+ SceneGrantsBySceneId map[int32][]EntityMUserQuestSceneGrantPossession
+ BattleDropRewardById map[int32]EntityMBattleDropReward
PickupRewardIdsByGroupId map[int32][]int32
BattleDropsByQuestId map[int32][]BattleDropInfo
- ReplayFlowRewardsByGroupId map[int32][]QuestReplayFlowRewardGroupRow
+ ReplayFlowRewardsByGroupId map[int32][]EntityMQuestReplayFlowRewardGroup
RentalQuestIds map[int32]bool
- TutorialUnlockConditions []TutorialUnlockConditionRow
+ TutorialUnlockConditions []EntityMTutorialUnlockCondition
ChapterLastSceneByQuestId map[int32]int32
SeasonIdByRouteId map[int32]int32
@@ -221,8 +41,8 @@ type QuestCatalog struct {
CostumeMaxLevelByRarity map[int32]NumericalFunc
MaxStaminaByLevel map[int32]int32
- CostumeById map[int32]CostumeMasterRow
- WeaponById map[int32]WeaponMasterRow
+ CostumeById map[int32]EntityMCostume
+ WeaponById map[int32]EntityMWeapon
WeaponSkillSlots map[int32][]int32
WeaponAbilitySlots map[int32][]int32
@@ -231,7 +51,7 @@ type QuestCatalog struct {
}
func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
- scenes, err := utils.ReadJSON[QuestSceneRow]("EntityMQuestSceneTable.json")
+ scenes, err := utils.ReadTable[EntityMQuestScene]("m_quest_scene")
if err != nil {
return nil, fmt.Errorf("load quest scene table: %w", err)
}
@@ -245,17 +65,17 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
return scenes[i].QuestSceneId < scenes[j].QuestSceneId
})
- missions, err := utils.ReadJSON[QuestMissionRow]("EntityMQuestMissionTable.json")
+ missions, err := utils.ReadTable[EntityMQuestMission]("m_quest_mission")
if err != nil {
return nil, fmt.Errorf("load quest mission table: %w", err)
}
- quests, err := utils.ReadJSON[QuestRow]("EntityMQuestTable.json")
+ quests, err := utils.ReadTable[EntityMQuest]("m_quest")
if err != nil {
return nil, fmt.Errorf("load quest table: %w", err)
}
- missionGroups, err := utils.ReadJSON[QuestMissionGroupRow]("EntityMQuestMissionGroupTable.json")
+ missionGroups, err := utils.ReadTable[EntityMQuestMissionGroup]("m_quest_mission_group")
if err != nil {
return nil, fmt.Errorf("load quest mission group table: %w", err)
}
@@ -269,7 +89,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
return missionGroups[i].QuestMissionId < missionGroups[j].QuestMissionId
})
- sequences, err := utils.ReadJSON[MainQuestSequenceRow]("EntityMMainQuestSequenceTable.json")
+ sequences, err := utils.ReadTable[EntityMMainQuestSequence]("m_main_quest_sequence")
if err != nil {
return nil, fmt.Errorf("load main quest sequence table: %w", err)
}
@@ -283,12 +103,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
return sequences[i].QuestId < sequences[j].QuestId
})
- chapters, err := utils.ReadJSON[MainQuestChapterRow]("EntityMMainQuestChapterTable.json")
+ chapters, err := utils.ReadTable[EntityMMainQuestChapter]("m_main_quest_chapter")
if err != nil {
return nil, fmt.Errorf("load main quest chapter table: %w", err)
}
- routes, err := utils.ReadJSON[MainQuestRouteRow]("EntityMMainQuestRouteTable.json")
+ routes, err := utils.ReadTable[EntityMMainQuestRoute]("m_main_quest_route")
if err != nil {
return nil, fmt.Errorf("load main quest route table: %w", err)
}
@@ -297,12 +117,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
seasonIdByRouteId[r.MainQuestRouteId] = r.MainQuestSeasonId
}
- firstClearSwitches, err := utils.ReadJSON[QuestFirstClearRewardSwitchRow]("EntityMQuestFirstClearRewardSwitchTable.json")
+ firstClearSwitches, err := utils.ReadTable[EntityMQuestFirstClearRewardSwitch]("m_quest_first_clear_reward_switch")
if err != nil {
return nil, fmt.Errorf("load quest first clear reward switch table: %w", err)
}
- firstClearRewards, err := utils.ReadJSON[QuestFirstClearRewardGroupRow]("EntityMQuestFirstClearRewardGroupTable.json")
+ firstClearRewards, err := utils.ReadTable[EntityMQuestFirstClearRewardGroup]("m_quest_first_clear_reward_group")
if err != nil {
return nil, fmt.Errorf("load quest first clear reward group table: %w", err)
}
@@ -316,7 +136,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
return firstClearRewards[i].QuestFirstClearRewardType < firstClearRewards[j].QuestFirstClearRewardType
})
- replayFlowRewards, err := utils.ReadJSON[QuestReplayFlowRewardGroupRow]("EntityMQuestReplayFlowRewardGroupTable.json")
+ replayFlowRewards, err := utils.ReadTable[EntityMQuestReplayFlowRewardGroup]("m_quest_replay_flow_reward_group")
if err != nil {
return nil, fmt.Errorf("load quest replay flow reward group table: %w", err)
}
@@ -327,52 +147,52 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
return replayFlowRewards[i].SortOrder < replayFlowRewards[j].SortOrder
})
- missionRewards, err := utils.ReadJSON[QuestMissionRewardRow]("EntityMQuestMissionRewardTable.json")
+ missionRewards, err := utils.ReadTable[EntityMQuestMissionReward]("m_quest_mission_reward")
if err != nil {
return nil, fmt.Errorf("load quest mission reward table: %w", err)
}
- weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json")
+ weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon")
if err != nil {
return nil, fmt.Errorf("load weapon table: %w", err)
}
- weaponSkillGroups, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json")
+ weaponSkillGroups, err := utils.ReadTable[EntityMWeaponSkillGroup]("m_weapon_skill_group")
if err != nil {
return nil, fmt.Errorf("load weapon skill group table: %w", err)
}
- weaponAbilityGroups, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json")
+ weaponAbilityGroups, err := utils.ReadTable[EntityMWeaponAbilityGroup]("m_weapon_ability_group")
if err != nil {
return nil, fmt.Errorf("load weapon ability group table: %w", err)
}
- releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json")
+ releaseConditions, err := utils.ReadTable[EntityMWeaponStoryReleaseConditionGroup]("m_weapon_story_release_condition_group")
if err != nil {
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
}
- costumeMasters, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json")
+ costumeMasters, err := utils.ReadTable[EntityMCostume]("m_costume")
if err != nil {
return nil, fmt.Errorf("load costume table: %w", err)
}
- costumeRarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json")
+ costumeRarities, err := utils.ReadTable[EntityMCostumeRarity]("m_costume_rarity")
if err != nil {
return nil, fmt.Errorf("load costume rarity table: %w", err)
}
- sceneGrants, err := utils.ReadJSON[QuestSceneGrantRow]("EntityMUserQuestSceneGrantPossessionTable.json")
+ sceneGrants, err := utils.ReadTable[EntityMUserQuestSceneGrantPossession]("m_user_quest_scene_grant_possession")
if err != nil {
return nil, fmt.Errorf("load quest scene grant table: %w", err)
}
- battleDropRewards, err := utils.ReadJSON[BattleDropRewardRow]("EntityMBattleDropRewardTable.json")
+ battleDropRewards, err := utils.ReadTable[EntityMBattleDropReward]("m_battle_drop_reward")
if err != nil {
return nil, fmt.Errorf("load battle drop reward table: %w", err)
}
- pickupRewardGroups, err := utils.ReadJSON[QuestPickupRewardGroupRow]("EntityMQuestPickupRewardGroupTable.json")
+ pickupRewardGroups, err := utils.ReadTable[EntityMQuestPickupRewardGroup]("m_quest_pickup_reward_group")
if err != nil {
return nil, fmt.Errorf("load quest pickup reward group table: %w", err)
}
@@ -383,37 +203,37 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
return pickupRewardGroups[i].SortOrder < pickupRewardGroups[j].SortOrder
})
- sceneBattles, err := utils.ReadJSON[QuestSceneBattleRow]("EntityMQuestSceneBattleTable.json")
+ sceneBattles, err := utils.ReadTable[EntityMQuestSceneBattle]("m_quest_scene_battle")
if err != nil {
return nil, fmt.Errorf("load quest scene battle table: %w", err)
}
- battleGroups, err := utils.ReadJSON[BattleGroupRow]("EntityMBattleGroupTable.json")
+ battleGroups, err := utils.ReadTable[EntityMBattleGroup]("m_battle_group")
if err != nil {
return nil, fmt.Errorf("load battle group table: %w", err)
}
- battles, err := utils.ReadJSON[BattleRow]("EntityMBattleTable.json")
+ battles, err := utils.ReadTable[EntityMBattle]("m_battle")
if err != nil {
return nil, fmt.Errorf("load battle table: %w", err)
}
- npcDecks, err := utils.ReadJSON[BattleNpcDeckRow]("EntityMBattleNpcDeckTable.json")
+ npcDecks, err := utils.ReadTable[EntityMBattleNpcDeck]("m_battle_npc_deck")
if err != nil {
return nil, fmt.Errorf("load battle npc deck table: %w", err)
}
- npcDropCategories, err := utils.ReadJSON[BattleNpcDropCategoryRow]("EntityMBattleNpcDeckCharacterDropCategoryTable.json")
+ npcDropCategories, err := utils.ReadTable[EntityMBattleNpcDeckCharacterDropCategory]("m_battle_npc_deck_character_drop_category")
if err != nil {
return nil, fmt.Errorf("load battle npc drop category table: %w", err)
}
- rentalDecks, err := utils.ReadJSON[RentalDeckRow]("EntityMBattleRentalDeckTable.json")
+ rentalDecks, err := utils.ReadTable[EntityMBattleRentalDeck]("m_battle_rental_deck")
if err != nil {
return nil, fmt.Errorf("load battle rental deck table: %w", err)
}
- tutorialUnlockConds, err := utils.ReadJSON[TutorialUnlockConditionRow]("EntityMTutorialUnlockConditionTable.json")
+ tutorialUnlockConds, err := utils.ReadTable[EntityMTutorialUnlockCondition]("m_tutorial_unlock_condition")
if err != nil {
return nil, fmt.Errorf("load tutorial unlock condition table: %w", err)
}
@@ -423,7 +243,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
return nil, err
}
- userLevels, err := utils.ReadJSON[UserLevelRow]("EntityMUserLevelTable.json")
+ userLevels, err := utils.ReadTable[EntityMUserLevel]("m_user_level")
if err != nil {
return nil, fmt.Errorf("load user level table: %w", err)
}
@@ -450,12 +270,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
}
}
- costumeById := make(map[int32]CostumeMasterRow, len(costumeMasters))
+ costumeById := make(map[int32]EntityMCostume, len(costumeMasters))
for _, cm := range costumeMasters {
costumeById[cm.CostumeId] = cm
}
- weaponById := make(map[int32]WeaponMasterRow, len(weapons))
+ weaponById := make(map[int32]EntityMWeapon, len(weapons))
for _, w := range weapons {
weaponById[w.WeaponId] = w
}
@@ -469,19 +289,19 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
abilitySlots[row.WeaponAbilityGroupId] = append(abilitySlots[row.WeaponAbilityGroupId], row.SlotNumber)
}
- sceneById := make(map[int32]QuestSceneRow, len(scenes))
+ sceneById := make(map[int32]EntityMQuestScene, len(scenes))
sceneIdsByQuestId := make(map[int32][]int32)
for _, scene := range scenes {
sceneById[scene.QuestSceneId] = scene
sceneIdsByQuestId[scene.QuestId] = append(sceneIdsByQuestId[scene.QuestId], scene.QuestSceneId)
}
- missionById := make(map[int32]QuestMissionRow, len(missions))
+ missionById := make(map[int32]EntityMQuestMission, len(missions))
for _, mission := range missions {
missionById[mission.QuestMissionId] = mission
}
- questById := make(map[int32]QuestRow, len(quests))
+ questById := make(map[int32]EntityMQuest, len(quests))
for _, quest := range quests {
questById[quest.QuestId] = quest
}
@@ -500,7 +320,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
missionIdsByQuestId[questId] = append([]int32(nil), missionIds...)
}
- chapterBySequenceId := make(map[int32]MainQuestChapterRow, len(chapters))
+ chapterBySequenceId := make(map[int32]EntityMMainQuestChapter, len(chapters))
for _, chapter := range chapters {
chapterBySequenceId[chapter.MainQuestSequenceGroupId] = chapter
}
@@ -511,12 +331,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
}
}
- sortedChapters := make([]MainQuestChapterRow, len(chapters))
+ sortedChapters := make([]EntityMMainQuestChapter, len(chapters))
copy(sortedChapters, chapters)
sort.Slice(sortedChapters, func(i, j int) bool {
return sortedChapters[i].SortOrder < sortedChapters[j].SortOrder
})
- sequencesByGroupId := make(map[int32][]MainQuestSequenceRow)
+ sequencesByGroupId := make(map[int32][]EntityMMainQuestSequence)
for _, seq := range sequences {
sequencesByGroupId[seq.MainQuestSequenceId] = append(sequencesByGroupId[seq.MainQuestSequenceId], seq)
}
@@ -544,25 +364,25 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
}
}
- firstClearRewardsByGroupId := make(map[int32][]QuestFirstClearRewardGroupRow, len(firstClearRewards))
+ firstClearRewardsByGroupId := make(map[int32][]EntityMQuestFirstClearRewardGroup, len(firstClearRewards))
for _, reward := range firstClearRewards {
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId] = append(
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId], reward)
}
- replayFlowRewardsByGroupId := make(map[int32][]QuestReplayFlowRewardGroupRow, len(replayFlowRewards))
+ replayFlowRewardsByGroupId := make(map[int32][]EntityMQuestReplayFlowRewardGroup, len(replayFlowRewards))
for _, reward := range replayFlowRewards {
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId] = append(
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId], reward)
}
- firstClearRewardSwitchesByQuestId := make(map[int32][]QuestFirstClearRewardSwitchRow, len(firstClearSwitches))
+ firstClearRewardSwitchesByQuestId := make(map[int32][]EntityMQuestFirstClearRewardSwitch, len(firstClearSwitches))
for _, switchRow := range firstClearSwitches {
firstClearRewardSwitchesByQuestId[switchRow.QuestId] = append(
firstClearRewardSwitchesByQuestId[switchRow.QuestId], switchRow)
}
- missionRewardsByMissionId := make(map[int32][]QuestMissionRewardRow, len(missionRewards))
+ missionRewardsByMissionId := make(map[int32][]EntityMQuestMissionReward, len(missionRewards))
for _, reward := range missionRewards {
missionRewardsByMissionId[reward.QuestMissionRewardId] = append(
missionRewardsByMissionId[reward.QuestMissionRewardId], reward)
@@ -576,18 +396,18 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
}
}
- releaseConditionsByGroupId := make(map[int32][]WeaponStoryReleaseConditionRow)
+ releaseConditionsByGroupId := make(map[int32][]EntityMWeaponStoryReleaseConditionGroup)
for _, c := range releaseConditions {
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId] = append(
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c)
}
- sceneGrantsBySceneId := make(map[int32][]QuestSceneGrantRow)
+ sceneGrantsBySceneId := make(map[int32][]EntityMUserQuestSceneGrantPossession)
for _, sg := range sceneGrants {
sceneGrantsBySceneId[sg.QuestSceneId] = append(sceneGrantsBySceneId[sg.QuestSceneId], sg)
}
- battleDropRewardById := make(map[int32]BattleDropRewardRow, len(battleDropRewards))
+ battleDropRewardById := make(map[int32]EntityMBattleDropReward, len(battleDropRewards))
for _, bdr := range battleDropRewards {
battleDropRewardById[bdr.BattleDropRewardId] = bdr
}
@@ -609,22 +429,22 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
}
type npcDeckKey struct {
- BattleNpcId int32
- DeckType model.DeckType
+ BattleNpcId int64
+ DeckType int32
BattleNpcDeckNumber int32
}
- npcDeckByKey := make(map[npcDeckKey]BattleNpcDeckRow, len(npcDecks))
+ npcDeckByKey := make(map[npcDeckKey]EntityMBattleNpcDeck, len(npcDecks))
for _, d := range npcDecks {
npcDeckByKey[npcDeckKey{d.BattleNpcId, d.DeckType, d.BattleNpcDeckNumber}] = d
}
- battleByIdMap := make(map[int32]BattleRow, len(battles))
+ battleByIdMap := make(map[int32]EntityMBattle, len(battles))
for _, b := range battles {
battleByIdMap[b.BattleId] = b
}
type dropCatKey struct {
- BattleNpcId int32
+ BattleNpcId int64
Uuid string
}
dropCategoryByKey := make(map[dropCatKey]int32, len(npcDropCategories))
diff --git a/server/internal/masterdata/shop.go b/server/internal/masterdata/shop.go
index bb29946..65f30be 100644
--- a/server/internal/masterdata/shop.go
+++ b/server/internal/masterdata/shop.go
@@ -8,96 +8,47 @@ import (
"lunar-tear/server/internal/utils"
)
-type ShopItemRow struct {
- ShopItemId int32 `json:"ShopItemId"`
- PriceType int32 `json:"PriceType"`
- PriceId int32 `json:"PriceId"`
- Price int32 `json:"Price"`
- ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"`
-}
-
-type ShopContentRow struct {
- ShopItemId int32 `json:"ShopItemId"`
- PossessionType int32 `json:"PossessionType"`
- PossessionId int32 `json:"PossessionId"`
- Count int32 `json:"Count"`
-}
-
-type ShopContentEffectRow struct {
- ShopItemId int32 `json:"ShopItemId"`
- EffectTargetType int32 `json:"EffectTargetType"`
- EffectValueType int32 `json:"EffectValueType"`
- EffectValue int32 `json:"EffectValue"`
-}
-
-type shopItemLimitedStockRow struct {
- ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"`
- MaxCount int32 `json:"MaxCount"`
-}
-
-type shopRow struct {
- ShopId int32 `json:"ShopId"`
- ShopGroupType int32 `json:"ShopGroupType"`
- ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"`
-}
-
-type shopItemCellGroupRow struct {
- ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"`
- ShopItemCellId int32 `json:"ShopItemCellId"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type shopItemCellRow struct {
- ShopItemCellId int32 `json:"ShopItemCellId"`
- ShopItemId int32 `json:"ShopItemId"`
-}
-
type ExchangeShopCell struct {
SortOrder int32
ShopItemId int32
}
type ShopCatalog struct {
- Items map[int32]ShopItemRow
- Contents map[int32][]ShopContentRow
- Effects map[int32][]ShopContentEffectRow
+ Items map[int32]EntityMShopItem
+ Contents map[int32][]EntityMShopItemContentPossession
+ Effects map[int32][]EntityMShopItemContentEffect
MaxStaminaMillis map[int32]int32 // level -> max stamina in millis
LimitedStock map[int32]int32 // stock id -> max count
ItemShopPool []int32 // shop item IDs for the replaceable item shop, sorted by cell sort order
ExchangeShopCells map[int32][]ExchangeShopCell // shopId -> sorted cells for exchange shops
}
-type userLevelEntry struct {
- UserLevel int32 `json:"UserLevel"`
- MaxStamina int32 `json:"MaxStamina"`
-}
-
func LoadShopCatalog() (*ShopCatalog, error) {
- items, err := utils.ReadJSON[ShopItemRow]("EntityMShopItemTable.json")
+ items, err := utils.ReadTable[EntityMShopItem]("m_shop_item")
if err != nil {
return nil, fmt.Errorf("load shop item table: %w", err)
}
- contents, err := utils.ReadJSON[ShopContentRow]("EntityMShopItemContentPossessionTable.json")
+ contents, err := utils.ReadTable[EntityMShopItemContentPossession]("m_shop_item_content_possession")
if err != nil {
return nil, fmt.Errorf("load shop content possession table: %w", err)
}
- effects, err := utils.ReadJSON[ShopContentEffectRow]("EntityMShopItemContentEffectTable.json")
+ effects, err := utils.ReadTable[EntityMShopItemContentEffect]("m_shop_item_content_effect")
if err != nil {
return nil, fmt.Errorf("load shop content effect table: %w", err)
}
- userLevels, err := utils.ReadJSON[userLevelEntry]("EntityMUserLevelTable.json")
+ userLevels, err := utils.ReadTable[EntityMUserLevel]("m_user_level")
if err != nil {
return nil, fmt.Errorf("load user level table: %w", err)
}
- stockRows, err := utils.ReadJSON[shopItemLimitedStockRow]("EntityMShopItemLimitedStockTable.json")
+ stockRows, err := utils.ReadTable[EntityMShopItemLimitedStock]("m_shop_item_limited_stock")
if err != nil {
return nil, fmt.Errorf("load shop item limited stock table: %w", err)
}
catalog := &ShopCatalog{
- Items: make(map[int32]ShopItemRow, len(items)),
- Contents: make(map[int32][]ShopContentRow, len(contents)),
- Effects: make(map[int32][]ShopContentEffectRow, len(effects)),
+ Items: make(map[int32]EntityMShopItem, len(items)),
+ Contents: make(map[int32][]EntityMShopItemContentPossession, len(contents)),
+ Effects: make(map[int32][]EntityMShopItemContentEffect, len(effects)),
MaxStaminaMillis: make(map[int32]int32, len(userLevels)),
LimitedStock: make(map[int32]int32, len(stockRows)),
}
@@ -117,15 +68,15 @@ func LoadShopCatalog() (*ShopCatalog, error) {
catalog.LimitedStock[row.ShopItemLimitedStockId] = row.MaxCount
}
- shops, err := utils.ReadJSON[shopRow]("EntityMShopTable.json")
+ shops, err := utils.ReadTable[EntityMShop]("m_shop")
if err != nil {
return nil, fmt.Errorf("load shop table: %w", err)
}
- cellGroups, err := utils.ReadJSON[shopItemCellGroupRow]("EntityMShopItemCellGroupTable.json")
+ cellGroups, err := utils.ReadTable[EntityMShopItemCellGroup]("m_shop_item_cell_group")
if err != nil {
return nil, fmt.Errorf("load shop item cell group table: %w", err)
}
- cells, err := utils.ReadJSON[shopItemCellRow]("EntityMShopItemCellTable.json")
+ cells, err := utils.ReadTable[EntityMShopItemCell]("m_shop_item_cell")
if err != nil {
return nil, fmt.Errorf("load shop item cell table: %w", err)
}
@@ -135,7 +86,7 @@ func LoadShopCatalog() (*ShopCatalog, error) {
cellIdToItemId[c.ShopItemCellId] = c.ShopItemId
}
- cellGroupByCGId := make(map[int32][]shopItemCellGroupRow, len(cellGroups))
+ cellGroupByCGId := make(map[int32][]EntityMShopItemCellGroup, len(cellGroups))
for _, cg := range cellGroups {
cellGroupByCGId[cg.ShopItemCellGroupId] = append(cellGroupByCGId[cg.ShopItemCellGroupId], cg)
}
diff --git a/server/internal/masterdata/sidestory.go b/server/internal/masterdata/sidestory.go
index 57c9210..8485731 100644
--- a/server/internal/masterdata/sidestory.go
+++ b/server/internal/masterdata/sidestory.go
@@ -5,18 +5,12 @@ import (
"lunar-tear/server/internal/utils"
)
-type sideStorySceneRow struct {
- SideStoryQuestId int32 `json:"SideStoryQuestId"`
- SideStoryQuestSceneId int32 `json:"SideStoryQuestSceneId"`
- SortOrder int32 `json:"SortOrder"`
-}
-
type SideStoryCatalog struct {
FirstSceneByQuestId map[int32]int32
}
func LoadSideStoryCatalog() *SideStoryCatalog {
- scenes, err := utils.ReadJSON[sideStorySceneRow]("EntityMSideStoryQuestSceneTable.json")
+ scenes, err := utils.ReadTable[EntityMSideStoryQuestScene]("m_side_story_quest_scene")
if err != nil {
log.Fatalf("load side story quest scene table: %v", err)
}
diff --git a/server/internal/masterdata/weapon.go b/server/internal/masterdata/weapon.go
index 3971f24..ac68c20 100644
--- a/server/internal/masterdata/weapon.go
+++ b/server/internal/masterdata/weapon.go
@@ -9,172 +9,55 @@ import (
"lunar-tear/server/internal/utils"
)
-type WeaponMasterRow struct {
- WeaponId int32 `json:"WeaponId"`
- RarityType int32 `json:"RarityType"`
- WeaponType int32 `json:"WeaponType"`
- WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"`
- WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"`
- WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"`
- WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"`
- WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"`
-}
-
-type WeaponStoryReleaseConditionRow struct {
- WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"`
- StoryIndex int32 `json:"StoryIndex"`
- WeaponStoryReleaseConditionType model.WeaponStoryReleaseConditionType `json:"WeaponStoryReleaseConditionType"`
- ConditionValue int32 `json:"ConditionValue"`
- WeaponStoryReleaseConditionOperationGroupId int32 `json:"WeaponStoryReleaseConditionOperationGroupId"`
-}
-
-type WeaponSkillGroupRow struct {
- WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"`
- SlotNumber int32 `json:"SlotNumber"`
- SkillId int32 `json:"SkillId"`
- WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"`
-}
-
-type WeaponAbilityGroupRow struct {
- WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"`
- SlotNumber int32 `json:"SlotNumber"`
- AbilityId int32 `json:"AbilityId"`
- WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"`
-}
-
-type weaponSpecificEnhanceRow struct {
- WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"`
- BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"`
- SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
- RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
- EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"`
- EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
- MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
- EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"`
- LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"`
- LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"`
- MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"`
- SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"`
- MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"`
- AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"`
-}
-
-type weaponConsumeExchangeRow struct {
- WeaponId int32 `json:"WeaponId"`
- ConsumableItemId int32 `json:"ConsumableItemId"`
- Count int32 `json:"Count"`
-}
-
-type WeaponEvolutionGroupRow struct {
- WeaponEvolutionGroupId int32 `json:"WeaponEvolutionGroupId"`
- EvolutionOrder int32 `json:"EvolutionOrder"`
- WeaponId int32 `json:"WeaponId"`
-}
-
-type WeaponEvolutionMaterialRow struct {
- WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type WeaponSkillEnhanceMaterialRow struct {
- WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"`
- SkillLevel int32 `json:"SkillLevel"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type WeaponAbilityEnhanceMaterialRow struct {
- WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"`
- AbilityLevel int32 `json:"AbilityLevel"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type WeaponAwakenRow struct {
- WeaponId int32 `json:"WeaponId"`
- WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"`
- WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"`
- ConsumeGold int32 `json:"ConsumeGold"`
- LevelLimitUp int32 `json:"LevelLimitUp"`
-}
-
type WeaponAwakenEffectGroupRow struct {
WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"`
WeaponAwakenEffectType int32 `json:"WeaponAwakenEffectType"`
WeaponAwakenEffectId int32 `json:"WeaponAwakenEffectId"`
}
-type WeaponAwakenMaterialGroupRow struct {
- WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"`
- MaterialId int32 `json:"MaterialId"`
- Count int32 `json:"Count"`
- SortOrder int32 `json:"SortOrder"`
-}
-
-type weaponRarityEnhanceRow struct {
- RarityType int32 `json:"RarityType"`
- BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"`
- SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
- RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
- EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"`
- EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
- MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
- EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"`
- LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"`
- LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"`
- MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"`
- SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"`
- MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"`
- AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"`
-}
-
type WeaponCatalog struct {
- Weapons map[int32]WeaponMasterRow
- Materials map[int32]MaterialRow
+ Weapons map[int32]EntityMWeapon
+ Materials map[int32]EntityMMaterial
ExpByEnhanceId map[int32][]int32
GoldCostByEnhanceId map[int32]NumericalFunc
MaxLevelByEnhanceId map[int32]NumericalFunc
SellPriceByEnhanceId map[int32]NumericalFunc
MedalsByWeaponId map[int32]map[int32]int32 // WeaponId -> ConsumableItemId -> Count
EvolutionNextWeaponId map[int32]int32
- EvolutionOrder map[int32]int32 // WeaponId -> 0-based position in evolution chain
- EvolutionMaterials map[int32][]WeaponEvolutionMaterialRow // WeaponEvolutionMaterialGroupId -> materials
+ EvolutionOrder map[int32]int32 // WeaponId -> 0-based position in evolution chain
+ EvolutionMaterials map[int32][]EntityMWeaponEvolutionMaterialGroup // WeaponEvolutionMaterialGroupId -> materials
EvolutionCostByEnhanceId map[int32]NumericalFunc
AbilitySlots map[int32][]int32 // WeaponAbilityGroupId -> slot numbers
- SkillGroupsByGroupId map[int32][]WeaponSkillGroupRow
- SkillEnhanceMats map[[2]int32][]WeaponSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel]
+ SkillGroupsByGroupId map[int32][]EntityMWeaponSkillGroup
+ SkillEnhanceMats map[[2]int32][]EntityMWeaponSkillEnhancementMaterial // key: [enhancementMaterialId, skillLevel]
SkillMaxLevelByEnhanceId map[int32]NumericalFunc
SkillCostByEnhanceId map[int32]NumericalFunc
- AbilityGroupsByGroupId map[int32][]WeaponAbilityGroupRow
- AbilityEnhanceMats map[[2]int32][]WeaponAbilityEnhanceMaterialRow // key: [enhancementMaterialId, abilityLevel]
+ AbilityGroupsByGroupId map[int32][]EntityMWeaponAbilityGroup
+ AbilityEnhanceMats map[[2]int32][]EntityMWeaponAbilityEnhancementMaterial // key: [enhancementMaterialId, abilityLevel]
AbilityMaxLevelByEnhanceId map[int32]NumericalFunc
AbilityCostByEnhanceId map[int32]NumericalFunc
EnhanceCostByWeaponByEnhanceId map[int32]NumericalFunc
LimitBreakCostByWeaponByEnhanceId map[int32]NumericalFunc
LimitBreakCostByMaterialByEnhanceId map[int32]NumericalFunc
BaseExpByEnhanceId map[int32]int32
- ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow
+ ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup
- AwakenByWeaponId map[int32]WeaponAwakenRow
- AwakenMaterialsByGroupId map[int32][]WeaponAwakenMaterialGroupRow
+ AwakenByWeaponId map[int32]EntityMWeaponAwaken
+ AwakenMaterialsByGroupId map[int32][]EntityMWeaponAwakenMaterialGroup
}
func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
- weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json")
+ weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon")
if err != nil {
return nil, fmt.Errorf("load weapon table: %w", err)
}
- enhanceRows, err := utils.ReadJSON[weaponSpecificEnhanceRow]("EntityMWeaponSpecificEnhanceTable.json")
+ enhanceRows, err := utils.ReadTable[EntityMWeaponSpecificEnhance]("m_weapon_specific_enhance")
if err != nil {
return nil, fmt.Errorf("load weapon specific enhance table: %w", err)
}
- rarityEnhanceRows, err := utils.ReadJSON[weaponRarityEnhanceRow]("EntityMWeaponRarityTable.json")
+ rarityEnhanceRows, err := utils.ReadTable[EntityMWeaponRarity]("m_weapon_rarity")
if err != nil {
return nil, fmt.Errorf("load weapon rarity table: %w", err)
}
@@ -189,51 +72,51 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
return nil, fmt.Errorf("load function resolver: %w", err)
}
- exchangeRows, err := utils.ReadJSON[weaponConsumeExchangeRow]("EntityMWeaponConsumeExchangeConsumableItemGroupTable.json")
+ exchangeRows, err := utils.ReadTable[EntityMWeaponConsumeExchangeConsumableItemGroup]("m_weapon_consume_exchange_consumable_item_group")
if err != nil {
return nil, fmt.Errorf("load weapon consume exchange table: %w", err)
}
- evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json")
+ evoGroupRows, err := utils.ReadTable[EntityMWeaponEvolutionGroup]("m_weapon_evolution_group")
if err != nil {
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
}
- evoMatRows, err := utils.ReadJSON[WeaponEvolutionMaterialRow]("EntityMWeaponEvolutionMaterialGroupTable.json")
+ evoMatRows, err := utils.ReadTable[EntityMWeaponEvolutionMaterialGroup]("m_weapon_evolution_material_group")
if err != nil {
return nil, fmt.Errorf("load weapon evolution material group table: %w", err)
}
- abilityGroupRows, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json")
+ abilityGroupRows, err := utils.ReadTable[EntityMWeaponAbilityGroup]("m_weapon_ability_group")
if err != nil {
return nil, fmt.Errorf("load weapon ability group table: %w", err)
}
- skillGroupRows, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json")
+ skillGroupRows, err := utils.ReadTable[EntityMWeaponSkillGroup]("m_weapon_skill_group")
if err != nil {
return nil, fmt.Errorf("load weapon skill group table: %w", err)
}
- skillMatRows, err := utils.ReadJSON[WeaponSkillEnhanceMaterialRow]("EntityMWeaponSkillEnhancementMaterialTable.json")
+ skillMatRows, err := utils.ReadTable[EntityMWeaponSkillEnhancementMaterial]("m_weapon_skill_enhancement_material")
if err != nil {
return nil, fmt.Errorf("load weapon skill enhancement material table: %w", err)
}
- abilityMatRows, err := utils.ReadJSON[WeaponAbilityEnhanceMaterialRow]("EntityMWeaponAbilityEnhancementMaterialTable.json")
+ abilityMatRows, err := utils.ReadTable[EntityMWeaponAbilityEnhancementMaterial]("m_weapon_ability_enhancement_material")
if err != nil {
return nil, fmt.Errorf("load weapon ability enhancement material table: %w", err)
}
- releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json")
+ releaseConditions, err := utils.ReadTable[EntityMWeaponStoryReleaseConditionGroup]("m_weapon_story_release_condition_group")
if err != nil {
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
}
- awakenRows, err := utils.ReadJSON[WeaponAwakenRow]("EntityMWeaponAwakenTable.json")
+ awakenRows, err := utils.ReadTable[EntityMWeaponAwaken]("m_weapon_awaken")
if err != nil {
return nil, fmt.Errorf("load weapon awaken table: %w", err)
}
- awakenMatRows, err := utils.ReadJSON[WeaponAwakenMaterialGroupRow]("EntityMWeaponAwakenMaterialGroupTable.json")
+ awakenMatRows, err := utils.ReadTable[EntityMWeaponAwakenMaterialGroup]("m_weapon_awaken_material_group")
if err != nil {
return nil, fmt.Errorf("load weapon awaken material group table: %w", err)
}
catalog := &WeaponCatalog{
- Weapons: make(map[int32]WeaponMasterRow, len(weapons)),
+ Weapons: make(map[int32]EntityMWeapon, len(weapons)),
Materials: matCatalog.ByType[model.MaterialTypeWeaponEnhancement],
ExpByEnhanceId: make(map[int32][]int32, len(enhanceRows)),
GoldCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
@@ -242,25 +125,25 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
MedalsByWeaponId: make(map[int32]map[int32]int32),
EvolutionNextWeaponId: make(map[int32]int32),
EvolutionOrder: make(map[int32]int32),
- EvolutionMaterials: make(map[int32][]WeaponEvolutionMaterialRow),
+ EvolutionMaterials: make(map[int32][]EntityMWeaponEvolutionMaterialGroup),
EvolutionCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
AbilitySlots: make(map[int32][]int32),
- SkillGroupsByGroupId: make(map[int32][]WeaponSkillGroupRow),
- SkillEnhanceMats: make(map[[2]int32][]WeaponSkillEnhanceMaterialRow),
+ SkillGroupsByGroupId: make(map[int32][]EntityMWeaponSkillGroup),
+ SkillEnhanceMats: make(map[[2]int32][]EntityMWeaponSkillEnhancementMaterial),
SkillMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
SkillCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
- AbilityGroupsByGroupId: make(map[int32][]WeaponAbilityGroupRow),
- AbilityEnhanceMats: make(map[[2]int32][]WeaponAbilityEnhanceMaterialRow),
+ AbilityGroupsByGroupId: make(map[int32][]EntityMWeaponAbilityGroup),
+ AbilityEnhanceMats: make(map[[2]int32][]EntityMWeaponAbilityEnhancementMaterial),
AbilityMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
AbilityCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
EnhanceCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
LimitBreakCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
LimitBreakCostByMaterialByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)),
- ReleaseConditionsByGroupId: make(map[int32][]WeaponStoryReleaseConditionRow),
+ ReleaseConditionsByGroupId: make(map[int32][]EntityMWeaponStoryReleaseConditionGroup),
- AwakenByWeaponId: make(map[int32]WeaponAwakenRow, len(awakenRows)),
- AwakenMaterialsByGroupId: make(map[int32][]WeaponAwakenMaterialGroupRow),
+ AwakenByWeaponId: make(map[int32]EntityMWeaponAwaken, len(awakenRows)),
+ AwakenMaterialsByGroupId: make(map[int32][]EntityMWeaponAwakenMaterialGroup),
}
for _, w := range weapons {
@@ -338,7 +221,7 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
catalog.MedalsByWeaponId[ex.WeaponId][ex.ConsumableItemId] = ex.Count
}
- grouped := make(map[int32][]WeaponEvolutionGroupRow)
+ grouped := make(map[int32][]EntityMWeaponEvolutionGroup)
for _, row := range evoGroupRows {
grouped[row.WeaponEvolutionGroupId] = append(grouped[row.WeaponEvolutionGroupId], row)
}
@@ -399,7 +282,7 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
// Rarity-based enhancement fallback: for weapons with WeaponSpecificEnhanceId == 0,
// use EntityMWeaponRarityTable curves via synthetic enhance IDs (-RarityType).
- rarityByType := make(map[int32]weaponRarityEnhanceRow, len(rarityEnhanceRows))
+ rarityByType := make(map[int32]EntityMWeaponRarity, len(rarityEnhanceRows))
for _, r := range rarityEnhanceRows {
rarityByType[r.RarityType] = r
}
diff --git a/server/internal/model/platform.go b/server/internal/model/platform.go
new file mode 100644
index 0000000..e4df295
--- /dev/null
+++ b/server/internal/model/platform.go
@@ -0,0 +1,77 @@
+package model
+
+import (
+ "context"
+ "strconv"
+
+ "google.golang.org/grpc/metadata"
+)
+
+type ClientPlatform struct {
+ OsType int32 // 1=iOS, 2=Android
+ PlatformType int32 // 1=AppStore, 2=GooglePlay, 8=Amazon
+}
+
+const (
+ OsTypeIOS int32 = 1
+ OsTypeAndroid int32 = 2
+
+ PlatformTypeAppStore int32 = 1
+ PlatformTypeGooglePlayStore int32 = 2
+ PlatformTypeAmazonAppStore int32 = 8
+)
+
+var DefaultPlatform = ClientPlatform{OsType: OsTypeAndroid, PlatformType: PlatformTypeGooglePlayStore}
+
+type platformKey struct{}
+
+func (p ClientPlatform) String() string {
+ os := "unknown"
+ switch p.OsType {
+ case OsTypeIOS:
+ os = "iOS"
+ case OsTypeAndroid:
+ os = "Android"
+ }
+ plat := "unknown"
+ switch p.PlatformType {
+ case PlatformTypeAppStore:
+ plat = "AppStore"
+ case PlatformTypeGooglePlayStore:
+ plat = "GooglePlay"
+ case PlatformTypeAmazonAppStore:
+ plat = "Amazon"
+ }
+ return os + "/" + plat
+}
+
+func ClientPlatformFromHeaders(ctx context.Context) ClientPlatform {
+ md, ok := metadata.FromIncomingContext(ctx)
+ if !ok {
+ return DefaultPlatform
+ }
+
+ p := DefaultPlatform
+ if vals := md.Get("x-apb-os-type"); len(vals) > 0 {
+ if v, err := strconv.ParseInt(vals[0], 10, 32); err == nil {
+ p.OsType = int32(v)
+ }
+ }
+ if vals := md.Get("x-apb-platform-type"); len(vals) > 0 {
+ if v, err := strconv.ParseInt(vals[0], 10, 32); err == nil {
+ p.PlatformType = int32(v)
+ }
+ }
+ return p
+}
+
+func NewContextWithPlatform(ctx context.Context, p ClientPlatform) context.Context {
+ return context.WithValue(ctx, platformKey{}, p)
+}
+
+func ClientPlatformFromContext(ctx context.Context) ClientPlatform {
+ if p, ok := ctx.Value(platformKey{}).(ClientPlatform); ok {
+ return p
+ }
+ return DefaultPlatform
+}
diff --git a/server/internal/questflow/event_quest.go b/server/internal/questflow/event_quest.go
index d23aec0..b1a508f 100644
--- a/server/internal/questflow/event_quest.go
+++ b/server/internal/questflow/event_quest.go
@@ -83,7 +83,7 @@ func (h *QuestHandler) HandleEventQuestSceneProgress(user *store.UserState, ques
h.applySceneGrants(user, questSceneId, nowMillis)
- if scene.QuestResultType == model.QuestResultTypeHalfResult {
+ if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult {
h.clearQuestMissions(user, scene.QuestId, nowMillis)
}
}
diff --git a/server/internal/questflow/extra_quest.go b/server/internal/questflow/extra_quest.go
index 58c6cb6..60e4ac2 100644
--- a/server/internal/questflow/extra_quest.go
+++ b/server/internal/questflow/extra_quest.go
@@ -80,7 +80,7 @@ func (h *QuestHandler) HandleExtraQuestSceneProgress(user *store.UserState, ques
h.applySceneGrants(user, questSceneId, nowMillis)
- if scene.QuestResultType == model.QuestResultTypeHalfResult {
+ if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult {
h.clearQuestMissions(user, scene.QuestId, nowMillis)
}
}
diff --git a/server/internal/questflow/handler.go b/server/internal/questflow/handler.go
index 13c3eae..b9bcd93 100644
--- a/server/internal/questflow/handler.go
+++ b/server/internal/questflow/handler.go
@@ -53,7 +53,7 @@ func BuildGranter(catalog *masterdata.QuestCatalog) *store.PossessionGranter {
for i, r := range rows {
conds[i] = store.WeaponStoryReleaseCond{
StoryIndex: r.StoryIndex,
- WeaponStoryReleaseConditionType: r.WeaponStoryReleaseConditionType,
+ WeaponStoryReleaseConditionType: model.WeaponStoryReleaseConditionType(r.WeaponStoryReleaseConditionType),
ConditionValue: r.ConditionValue,
}
}
diff --git a/server/internal/questflow/quest.go b/server/internal/questflow/quest.go
index 500b115..b491276 100644
--- a/server/internal/questflow/quest.go
+++ b/server/internal/questflow/quest.go
@@ -23,7 +23,7 @@ func (h *QuestHandler) initQuestState(user *store.UserState, questId int32) {
}
}
-func isMainQuestPlayable(quest masterdata.QuestRow) bool {
+func isMainQuestPlayable(quest masterdata.EntityMQuest) bool {
return !quest.IsRunInTheBackground && quest.IsCountedAsQuest
}
diff --git a/server/internal/questflow/rewards.go b/server/internal/questflow/rewards.go
index d0608ed..32df3fc 100644
--- a/server/internal/questflow/rewards.go
+++ b/server/internal/questflow/rewards.go
@@ -20,10 +20,10 @@ func (h *QuestHandler) isQuestCleared(user *store.UserState, questId int32) bool
return quest.QuestStateType == model.UserQuestStateTypeCleared
}
-func appendMissionRewards(dst []RewardGrant, src []masterdata.QuestMissionRewardRow) []RewardGrant {
+func appendMissionRewards(dst []RewardGrant, src []masterdata.EntityMQuestMissionReward) []RewardGrant {
for _, r := range src {
dst = append(dst, RewardGrant{
- PossessionType: r.PossessionType,
+ PossessionType: model.PossessionType(r.PossessionType),
PossessionId: r.PossessionId,
Count: r.Count,
})
@@ -31,7 +31,7 @@ func appendMissionRewards(dst []RewardGrant, src []masterdata.QuestMissionReward
return dst
}
-func (h *QuestHandler) firstClearRewardGroupId(user *store.UserState, questDef masterdata.QuestRow) int32 {
+func (h *QuestHandler) firstClearRewardGroupId(user *store.UserState, questDef masterdata.EntityMQuest) int32 {
rewardGroupId := questDef.QuestFirstClearRewardGroupId
for _, switchRow := range h.FirstClearRewardSwitchesByQuestId[questDef.QuestId] {
if h.isQuestCleared(user, switchRow.SwitchConditionClearQuestId) {
@@ -57,7 +57,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
outcome.FirstClearRewards = append(outcome.FirstClearRewards, RewardGrant{
- PossessionType: reward.PossessionType,
+ PossessionType: model.PossessionType(reward.PossessionType),
PossessionId: reward.PossessionId,
Count: reward.Count,
})
@@ -67,7 +67,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
if user.MainQuest.CurrentQuestFlowType == int32(model.QuestFlowTypeReplayFlow) && questDef.QuestReplayFlowRewardGroupId > 0 {
for _, reward := range h.ReplayFlowRewardsByGroupId[questDef.QuestReplayFlowRewardGroupId] {
outcome.ReplayFlowFirstClearRewards = append(outcome.ReplayFlowFirstClearRewards, RewardGrant{
- PossessionType: reward.PossessionType,
+ PossessionType: model.PossessionType(reward.PossessionType),
PossessionId: reward.PossessionId,
Count: reward.Count,
})
@@ -78,7 +78,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
regularMissionCount := 0
for _, questMissionId := range h.MissionIdsByQuestId[questId] {
missionDef, ok := h.MissionById[questMissionId]
- if !ok || missionDef.QuestMissionConditionType == model.QuestMissionConditionTypeComplete {
+ if !ok || model.QuestMissionConditionType(missionDef.QuestMissionConditionType) == model.QuestMissionConditionTypeComplete {
continue
}
regularMissionCount++
@@ -103,7 +103,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
if allRegularWillClear {
for _, questMissionId := range h.MissionIdsByQuestId[questId] {
missionDef, ok := h.MissionById[questMissionId]
- if !ok || missionDef.QuestMissionConditionType != model.QuestMissionConditionTypeComplete {
+ if !ok || model.QuestMissionConditionType(missionDef.QuestMissionConditionType) != model.QuestMissionConditionTypeComplete {
continue
}
key := store.QuestMissionKey{QuestId: questId, QuestMissionId: questMissionId}
@@ -122,7 +122,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
return outcome
}
-func (h *QuestHandler) computeDropRewards(questDef masterdata.QuestRow) []RewardGrant {
+func (h *QuestHandler) computeDropRewards(questDef masterdata.EntityMQuest) []RewardGrant {
if questDef.QuestPickupRewardGroupId == 0 {
return nil
}
@@ -130,7 +130,7 @@ func (h *QuestHandler) computeDropRewards(questDef masterdata.QuestRow) []Reward
for _, dropId := range h.PickupRewardIdsByGroupId[questDef.QuestPickupRewardGroupId] {
if bdr, ok := h.BattleDropRewardById[dropId]; ok {
drops = append(drops, RewardGrant{
- PossessionType: bdr.PossessionType,
+ PossessionType: model.PossessionType(bdr.PossessionType),
PossessionId: bdr.PossessionId,
Count: bdr.Count,
})
@@ -257,7 +257,7 @@ func (h *QuestHandler) applyQuestRewards(user *store.UserState, questId int32, n
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
- h.applyRewardPossession(user, reward.PossessionType, reward.PossessionId, reward.Count, nowMillis)
+ h.applyRewardPossession(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis)
}
}
@@ -365,7 +365,7 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat
}
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
- if reward.PossessionType != model.PossessionTypeWeapon {
+ if model.PossessionType(reward.PossessionType) != model.PossessionTypeWeapon {
continue
}
weaponId := reward.PossessionId
@@ -375,7 +375,7 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat
}
groupId := weapon.WeaponStoryReleaseConditionGroupId
for _, cond := range h.ReleaseConditionsByGroupId[groupId] {
- if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 {
+ if model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 {
if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
changedIds = append(changedIds, weaponId)
}
@@ -387,7 +387,7 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat
if resultType == model.QuestResultTypeFullResult {
for groupId, conditions := range h.ReleaseConditionsByGroupId {
for _, cond := range conditions {
- if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId {
+ if model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId {
for _, weaponId := range h.WeaponIdsByReleaseConditionGroupId[groupId] {
if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
changedIds = append(changedIds, weaponId)
diff --git a/server/internal/questflow/scene.go b/server/internal/questflow/scene.go
index b2d25a5..ae6ccc1 100644
--- a/server/internal/questflow/scene.go
+++ b/server/internal/questflow/scene.go
@@ -15,7 +15,7 @@ func (h *QuestHandler) applySceneGrants(user *store.UserState, questSceneId int3
return
}
for _, g := range grants {
- h.applyRewardPossession(user, g.PossessionType, g.PossessionId, g.Count, nowMillis)
+ h.applyRewardPossession(user, model.PossessionType(g.PossessionType), g.PossessionId, g.Count, nowMillis)
}
}
@@ -123,7 +123,7 @@ func (h *QuestHandler) HandleMainQuestSceneProgress(user *store.UserState, quest
}
if isMainQuestPlayable(quest) {
- if scene.QuestResultType == model.QuestResultTypeHalfResult {
+ if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult {
nowMillis := gametime.NowMillis()
h.clearQuestMissions(user, quest.QuestId, nowMillis)
}
diff --git a/server/internal/service/asset_resolver.go b/server/internal/service/asset_resolver.go
index f9d56e7..fd5a5b5 100644
--- a/server/internal/service/asset_resolver.go
+++ b/server/internal/service/asset_resolver.go
@@ -20,7 +20,9 @@ type assetResolution struct {
Candidates []assetCandidate
}
-type assetResolver struct{}
+type assetResolver struct {
+ baseDir string
+}
func newRevisionTracker() *revisionTracker {
return &revisionTracker{
@@ -28,8 +30,8 @@ func newRevisionTracker() *revisionTracker {
}
}
-func newAssetResolver() *assetResolver {
- return &assetResolver{}
+func newAssetResolver(baseDir string) *assetResolver {
+ return &assetResolver{baseDir: baseDir}
}
func normalizeClientAddr(remoteAddr string) string {
@@ -73,7 +75,7 @@ func (r *assetResolver) Resolve(objectId, assetType, activeRevision string) (ass
resolution := assetResolution{ActiveRevision: activeRevision}
revision := activeRevision
- candidates, listSize, ok := objectIdToFilePathCandidates(revision, assetType, objectId)
+ candidates, listSize, ok := objectIdToFilePathCandidates(r.baseDir, revision, assetType, objectId)
if ok && len(candidates) > 0 {
resolution.ListRevision = revision
resolution.ListSize = listSize
@@ -94,6 +96,6 @@ func (r *assetResolver) Prewarm(activeRevision string) {
if activeRevision == "" {
return
}
- _, _ = loadListBinIndex(activeRevision)
- _ = loadInfoIndex(activeRevision)
+ _, _ = loadListBinIndex(r.baseDir, activeRevision)
+ _ = loadInfoIndex(r.baseDir, activeRevision)
}
diff --git a/server/internal/service/banner.go b/server/internal/service/banner.go
index 607d0e3..408d90e 100644
--- a/server/internal/service/banner.go
+++ b/server/internal/service/banner.go
@@ -6,7 +6,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type BannerServiceServer struct {
@@ -44,6 +43,5 @@ func (s *BannerServiceServer) GetMamaBanner(ctx context.Context, req *pb.GetMama
TermLimitedGacha: termLimited,
LatestChapterGacha: latestChapter,
IsExistUnreadPop: false,
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
diff --git a/server/internal/service/battle.go b/server/internal/service/battle.go
index 1fa3832..d54320f 100644
--- a/server/internal/service/battle.go
+++ b/server/internal/service/battle.go
@@ -7,7 +7,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type BattleServiceServer struct {
@@ -22,7 +21,7 @@ func NewBattleServiceServer(users store.UserRepository, sessions store.SessionRe
func (s *BattleServiceServer) StartWave(ctx context.Context, req *pb.StartWaveRequest) (*pb.StartWaveResponse, error) {
log.Printf("[BattleService] StartWave: userParty=%d npcParty=%d", len(req.UserPartyInitialInfoList), len(req.NpcPartyInitialInfoList))
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
s.users.UpdateUser(userId, func(user *store.UserState) {
user.Battle.IsActive = true
user.Battle.StartCount++
@@ -30,15 +29,13 @@ func (s *BattleServiceServer) StartWave(ctx context.Context, req *pb.StartWaveRe
user.Battle.LastUserPartyCount = int32(len(req.UserPartyInitialInfoList))
user.Battle.LastNpcPartyCount = int32(len(req.NpcPartyInitialInfoList))
})
- return &pb.StartWaveResponse{
- DiffUserData: userdata.EmptyDiff(),
- }, nil
+ return &pb.StartWaveResponse{}, nil
}
func (s *BattleServiceServer) FinishWave(ctx context.Context, req *pb.FinishWaveRequest) (*pb.FinishWaveResponse, error) {
log.Printf("[BattleService] FinishWave: battleBinary=%d userParty=%d npcParty=%d elapsedFrames=%d",
len(req.BattleBinary), len(req.UserPartyResultInfoList), len(req.NpcPartyResultInfoList), req.ElapsedFrameCount)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
s.users.UpdateUser(userId, func(user *store.UserState) {
user.Battle.IsActive = false
user.Battle.FinishCount++
@@ -48,7 +45,5 @@ func (s *BattleServiceServer) FinishWave(ctx context.Context, req *pb.FinishWave
user.Battle.LastBattleBinarySize = int32(len(req.BattleBinary))
user.Battle.LastElapsedFrameCount = req.ElapsedFrameCount
})
- return &pb.FinishWaveResponse{
- DiffUserData: userdata.EmptyDiff(),
- }, nil
+ return &pb.FinishWaveResponse{}, nil
}
diff --git a/server/internal/service/cageornament.go b/server/internal/service/cageornament.go
index f53960b..920857b 100644
--- a/server/internal/service/cageornament.go
+++ b/server/internal/service/cageornament.go
@@ -9,7 +9,6 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type CageOrnamentServiceServer struct {
@@ -32,9 +31,9 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
log.Fatalf("[CageOrnamentService] ReceiveReward: no reward for cageOrnamentId=%d", req.CageOrnamentId)
}
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{
CageOrnamentId: req.CageOrnamentId,
AcquisitionDatetime: nowMillis,
@@ -43,17 +42,6 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
s.granter.GrantFull(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis)
})
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
- []string{
- "IUserMaterial", "IUserConsumableItem", "IUserGem",
- "IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter",
- "IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility",
- "IUserWeaponNote",
- "IUserCageOrnamentReward",
- },
- ))
- userdata.AddWeaponStoryDiff(diff, user, s.granter.DrainChangedStoryWeaponIds())
-
return &pb.ReceiveRewardResponse{
CageOrnamentReward: []*pb.CageOrnamentReward{
{
@@ -62,16 +50,15 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
Count: reward.Count,
},
},
- DiffUserData: diff,
}, nil
}
func (s *CageOrnamentServiceServer) RecordAccess(ctx context.Context, req *pb.RecordAccessRequest) (*pb.RecordAccessResponse, error) {
log.Printf("[CageOrnamentService] RecordAccess: cageOrnamentId=%d", req.CageOrnamentId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
if _, exists := user.CageOrnamentRewards[req.CageOrnamentId]; !exists {
user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{
CageOrnamentId: req.CageOrnamentId,
@@ -81,11 +68,5 @@ func (s *CageOrnamentServiceServer) RecordAccess(ctx context.Context, req *pb.Re
}
})
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
- []string{"IUserCageOrnamentReward"},
- ))
-
- return &pb.RecordAccessResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.RecordAccessResponse{}, nil
}
diff --git a/server/internal/service/character.go b/server/internal/service/character.go
index ae12367..5fb6ad1 100644
--- a/server/internal/service/character.go
+++ b/server/internal/service/character.go
@@ -8,7 +8,6 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type CharacterServiceServer struct {
@@ -26,7 +25,7 @@ func NewCharacterServiceServer(users store.UserRepository, sessions store.Sessio
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
stepGroupId, ok := s.catalog.StepGroupByCharacterId[req.CharacterId]
@@ -35,11 +34,7 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
return &pb.RebirthResponse{}, nil
}
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
current := user.CharacterRebirths[req.CharacterId]
currentCount := current.RebirthCount
targetCount := currentCount + req.RebirthCount
@@ -77,9 +72,5 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
return nil, err
}
- rebirthTables := []string{"IUserCharacterRebirth", "IUserMaterial", "IUserConsumableItem"}
- tables := userdata.ProjectTables(snapshot, rebirthTables)
- diff := tracker.Apply(snapshot, tables)
-
- return &pb.RebirthResponse{DiffUserData: diff}, nil
+ return &pb.RebirthResponse{}, nil
}
diff --git a/server/internal/service/characterboard.go b/server/internal/service/characterboard.go
index e586d25..ebd7abc 100644
--- a/server/internal/service/characterboard.go
+++ b/server/internal/service/characterboard.go
@@ -8,7 +8,6 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type CharacterBoardServiceServer struct {
@@ -25,14 +24,9 @@ func NewCharacterBoardServiceServer(users store.UserRepository, sessions store.S
func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.ReleasePanelRequest) (*pb.ReleasePanelResponse, error) {
log.Printf("[CharacterBoardService] ReleasePanel: panelIds=%v", req.CharacterBoardPanelId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}).
- Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
-
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
for _, panelId := range req.CharacterBoardPanelId {
panel, ok := s.catalog.PanelById[panelId]
if !ok {
@@ -46,28 +40,17 @@ func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.
}
})
- boardTables := []string{
- "IUserCharacterBoard",
- "IUserCharacterBoardAbility",
- "IUserCharacterBoardStatusUp",
- "IUserMaterial",
- "IUserConsumableItem",
- "IUserGem",
- }
- tables := userdata.ProjectTables(user, boardTables)
- diff := tracker.Apply(user, tables)
-
- return &pb.ReleasePanelResponse{DiffUserData: diff}, nil
+ return &pb.ReleasePanelResponse{}, nil
}
-func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.CharacterBoardPanelRow) {
+func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
costs := s.catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId]
for _, cost := range costs {
store.DeductPossession(user, model.PossessionType(cost.PossessionType), cost.PossessionId, cost.Count)
}
}
-func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel masterdata.CharacterBoardPanelRow) {
+func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
boardId := panel.CharacterBoardId
board := user.CharacterBoards[boardId]
board.CharacterBoardId = boardId
@@ -90,7 +73,7 @@ func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel
user.CharacterBoards[boardId] = board
}
-func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.CharacterBoardPanelRow) {
+func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
effects := s.catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId]
for _, eff := range effects {
switch model.CharacterBoardEffectType(eff.CharacterBoardEffectType) {
@@ -102,7 +85,7 @@ func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel
}
}
-func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.CharacterBoardReleaseEffectRow) {
+func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
ability, ok := s.catalog.AbilityById[eff.CharacterBoardEffectId]
if !ok {
log.Printf("[CharacterBoardService] unknown abilityId=%d", eff.CharacterBoardEffectId)
@@ -127,7 +110,7 @@ func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState,
user.CharacterBoardAbilities[key] = state
}
-func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.CharacterBoardReleaseEffectRow) {
+func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
statusUp, ok := s.catalog.StatusUpById[eff.CharacterBoardEffectId]
if !ok {
log.Printf("[CharacterBoardService] unknown statusUpId=%d", eff.CharacterBoardEffectId)
diff --git a/server/internal/service/characterviewer.go b/server/internal/service/characterviewer.go
index d6ba310..5aff808 100644
--- a/server/internal/service/characterviewer.go
+++ b/server/internal/service/characterviewer.go
@@ -2,12 +2,10 @@ package service
import (
"context"
- "encoding/json"
"fmt"
"log"
pb "lunar-tear/server/gen/proto"
- "lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
@@ -28,7 +26,7 @@ func NewCharacterViewerServiceServer(users store.UserRepository, sessions store.
func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _ *emptypb.Empty) (*pb.CharacterViewerTopResponse, error) {
log.Printf("[CharacterViewerService] CharacterViewerTop")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
panic(fmt.Sprintf("CharacterViewerTop: no user for userId=%d: %v", userId, err))
@@ -37,32 +35,7 @@ func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _
released := s.catalog.ReleasedFieldIds(user)
log.Printf("[CharacterViewerService] released %d fields for user %d", len(released), userId)
- now := gametime.NowMillis()
- records := make([]map[string]any, 0, len(released))
- for _, fieldId := range released {
- records = append(records, map[string]any{
- "userId": userId,
- "characterViewerFieldId": fieldId,
- "releaseDatetime": now,
- "latestVersion": 0,
- })
- }
-
- payload := "[]"
- if len(records) > 0 {
- data, _ := json.Marshal(records)
- payload = string(data)
- }
-
- diff := map[string]*pb.DiffData{
- "IUserCharacterViewerField": {
- UpdateRecordsJson: payload,
- DeleteKeysJson: "[]",
- },
- }
-
return &pb.CharacterViewerTopResponse{
ReleaseCharacterViewerFieldId: released,
- DiffUserData: diff,
}, nil
}
diff --git a/server/internal/service/companion.go b/server/internal/service/companion.go
index 69a1287..75e25a9 100644
--- a/server/internal/service/companion.go
+++ b/server/internal/service/companion.go
@@ -9,17 +9,10 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
const companionMaxLevel = int32(50)
-var companionDiffTables = []string{
- "IUserCompanion",
- "IUserMaterial",
- "IUserConsumableItem",
-}
-
type CompanionServiceServer struct {
pb.UnimplementedCompanionServiceServer
users store.UserRepository
@@ -35,10 +28,10 @@ func NewCompanionServiceServer(users store.UserRepository, sessions store.Sessio
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
companion, ok := user.Companions[req.UserCompanionUuid]
if !ok {
log.Printf("[CompanionService] Enhance: companion uuid=%s not found", req.UserCompanionUuid)
@@ -77,9 +70,5 @@ func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionE
return nil, fmt.Errorf("companion enhance: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, companionDiffTables))
-
- return &pb.CompanionEnhanceResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.CompanionEnhanceResponse{}, nil
}
diff --git a/server/internal/service/config.go b/server/internal/service/config.go
index 12c725d..25ec14a 100644
--- a/server/internal/service/config.go
+++ b/server/internal/service/config.go
@@ -5,7 +5,6 @@ import (
"log"
pb "lunar-tear/server/gen/proto"
- "lunar-tear/server/internal/userdata"
"google.golang.org/protobuf/types/known/emptypb"
)
@@ -14,7 +13,7 @@ type ConfigServiceServer struct {
pb.UnimplementedConfigServiceServer
GrpcHost string
GrpcPort int32
- OctoURL string // HTTP base URL for Octo (list + assets); client uses this instead of default resources.app.nierreincarnation.com
+ OctoURL string
}
func NewConfigServiceServer(host string, port int32, octoURL string) *ConfigServiceServer {
@@ -42,6 +41,5 @@ func (s *ConfigServiceServer) GetReviewServerConfig(ctx context.Context, _ *empt
MasterData: &pb.MasterDataConfig{
UrlFormat: s.OctoURL + "/master-data/%s",
},
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
diff --git a/server/internal/service/consumableitem.go b/server/internal/service/consumableitem.go
index a9d061c..ee0c00a 100644
--- a/server/internal/service/consumableitem.go
+++ b/server/internal/service/consumableitem.go
@@ -8,7 +8,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type ConsumableItemServiceServer struct {
@@ -26,13 +25,9 @@ func NewConsumableItemServiceServer(users store.UserRepository, sessions store.S
func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) {
log.Printf("[ConsumableItemService] Sell: %d item(s)", len(req.ConsumableItemPossession))
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
totalGold := int32(0)
for _, item := range req.ConsumableItemPossession {
row, ok := s.catalog.All[item.ConsumableItemId]
@@ -66,10 +61,5 @@ func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.Consumab
return nil, fmt.Errorf("consumable item sell: %w", err)
}
- tables := userdata.ProjectTables(snapshot, []string{"IUserConsumableItem"})
- diff := tracker.Apply(snapshot, tables)
-
- return &pb.ConsumableItemSellResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.ConsumableItemSellResponse{}, nil
}
diff --git a/server/internal/service/contentsstory.go b/server/internal/service/contentsstory.go
index 87ae421..d780c43 100644
--- a/server/internal/service/contentsstory.go
+++ b/server/internal/service/contentsstory.go
@@ -8,7 +8,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type ContentsStoryServiceServer struct {
@@ -24,19 +23,15 @@ func NewContentsStoryServiceServer(users store.UserRepository, sessions store.Se
func (s *ContentsStoryServiceServer) RegisterPlayed(ctx context.Context, req *pb.ContentsStoryRegisterPlayedRequest) (*pb.ContentsStoryRegisterPlayedResponse, error) {
log.Printf("[ContentsStoryService] RegisterPlayed: contentsStoryId=%d", req.ContentsStoryId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
user.ContentsStories[req.ContentsStoryId] = nowMillis
})
if err != nil {
return nil, fmt.Errorf("update user: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserContentsStory"}))
-
- return &pb.ContentsStoryRegisterPlayedResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.ContentsStoryRegisterPlayedResponse{}, nil
}
diff --git a/server/internal/service/costume.go b/server/internal/service/costume.go
index 50ebfe3..600ed9d 100644
--- a/server/internal/service/costume.go
+++ b/server/internal/service/costume.go
@@ -14,15 +14,8 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
-var costumeDiffTables = []string{
- "IUserCostume",
- "IUserMaterial",
- "IUserConsumableItem",
-}
-
type CostumeServiceServer struct {
pb.UnimplementedCostumeServiceServer
users store.UserRepository
@@ -38,10 +31,10 @@ func NewCostumeServiceServer(users store.UserRepository, sessions store.SessionR
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
costume, ok := user.Costumes[req.UserCostumeUuid]
if !ok {
log.Printf("[CostumeService] Enhance: costume uuid=%s not found", req.UserCostumeUuid)
@@ -98,30 +91,19 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
return nil, fmt.Errorf("costume enhance: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables))
-
return &pb.EnhanceResponse{
IsGreatSuccess: false,
SurplusEnhanceMaterial: map[int32]int32{},
- DiffUserData: diff,
}, nil
}
-var awakenDiffTables = []string{
- "IUserCostume",
- "IUserMaterial",
- "IUserConsumableItem",
- "IUserCostumeAwakenStatusUp",
- "IUserThought",
-}
-
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
costume, ok := user.Costumes[req.UserCostumeUuid]
if !ok {
log.Printf("[CostumeService] Awaken: costume uuid=%s not found", req.UserCostumeUuid)
@@ -179,11 +161,7 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
return nil, fmt.Errorf("costume awaken: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, awakenDiffTables))
-
- return &pb.AwakenResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.AwakenResponse{}, nil
}
func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) {
@@ -245,19 +223,13 @@ func (s *CostumeServiceServer) applyAwakenItemAcquire(user *store.UserState, ite
log.Printf("[CostumeService] Awaken: granted thought id=%d", acq.PossessionId)
}
-var activeSkillDiffTables = []string{
- "IUserCostumeActiveSkill",
- "IUserMaterial",
- "IUserConsumableItem",
-}
-
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
costume, ok := user.Costumes[req.UserCostumeUuid]
if !ok {
log.Printf("[CostumeService] EnhanceActiveSkill: costume uuid=%s not found", req.UserCostumeUuid)
@@ -332,20 +304,16 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
return nil, fmt.Errorf("costume enhance active skill: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, activeSkillDiffTables))
-
- return &pb.EnhanceActiveSkillResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.EnhanceActiveSkillResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
costume, ok := user.Costumes[req.UserCostumeUuid]
if !ok {
log.Printf("[CostumeService] LimitBreak: costume uuid=%s not found", req.UserCostumeUuid)
@@ -389,30 +357,16 @@ func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBrea
return nil, fmt.Errorf("costume limit break: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables))
-
- return &pb.LimitBreakResponse{
- DiffUserData: diff,
- }, nil
-}
-
-var lotteryEffectDiffTables = []string{
- "IUserCostume",
- "IUserCostumeLotteryEffect",
- "IUserCostumeLotteryEffectAbility",
- "IUserCostumeLotteryEffectStatusUp",
- "IUserCostumeLotteryEffectPending",
- "IUserConsumableItem",
- "IUserMaterial",
+ return &pb.LimitBreakResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
costume, ok := user.Costumes[req.UserCostumeUuid]
if !ok {
log.Printf("[CostumeService] UnlockLotteryEffectSlot: costume uuid=%s not found", req.UserCostumeUuid)
@@ -458,26 +412,16 @@ func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req
return nil, fmt.Errorf("costume unlock lottery effect slot: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
-
- return &pb.UnlockLotteryEffectSlotResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.UnlockLotteryEffectSlotResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}).
- Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"}).
- Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
costume, ok := user.Costumes[req.UserCostumeUuid]
if !ok {
log.Printf("[CostumeService] DrawLotteryEffect: costume uuid=%s not found", req.UserCostumeUuid)
@@ -514,7 +458,7 @@ func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.Dr
totalWeight += row.Weight
}
roll := rand.Int31n(totalWeight)
- var picked masterdata.CostumeLotteryEffectOddsRow
+ var picked masterdata.EntityMCostumeLotteryEffectOddsGroup
for _, row := range oddsPool {
roll -= row.Weight
if roll < 0 {
@@ -550,24 +494,16 @@ func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.Dr
return nil, fmt.Errorf("costume draw lottery effect: %w", err)
}
- diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
-
- return &pb.DrawLotteryEffectResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.DrawLotteryEffectResponse{}, nil
}
func (s *CostumeServiceServer) ConfirmLotteryEffect(ctx context.Context, req *pb.ConfirmLotteryEffectRequest) (*pb.ConfirmLotteryEffectResponse, error) {
log.Printf("[CostumeService] ConfirmLotteryEffect: uuid=%s accept=%v", req.UserCostumeUuid, req.IsAccept)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
pending, ok := user.CostumeLotteryEffectPending[req.UserCostumeUuid]
if !ok {
log.Printf("[CostumeService] ConfirmLotteryEffect: no pending for uuid=%s", req.UserCostumeUuid)
@@ -596,9 +532,5 @@ func (s *CostumeServiceServer) ConfirmLotteryEffect(ctx context.Context, req *pb
return nil, fmt.Errorf("costume confirm lottery effect: %w", err)
}
- diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
-
- return &pb.ConfirmLotteryEffectResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.ConfirmLotteryEffectResponse{}, nil
}
diff --git a/server/internal/service/data.go b/server/internal/service/data.go
index 1ccbcaa..52c7e1f 100644
--- a/server/internal/service/data.go
+++ b/server/internal/service/data.go
@@ -41,7 +41,7 @@ func (s *DataServiceServer) GetUserDataNameV2(ctx context.Context, _ *emptypb.Em
func (s *DataServiceServer) GetUserData(ctx context.Context, req *pb.UserDataGetRequest) (*pb.UserDataGetResponse, error) {
log.Printf("[DataService] GetUserData: tables=%v", req.TableName)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
return nil, fmt.Errorf("snapshot user: %w", err)
diff --git a/server/internal/service/deck.go b/server/internal/service/deck.go
index f1ae15a..3ff3dc2 100644
--- a/server/internal/service/deck.go
+++ b/server/internal/service/deck.go
@@ -8,7 +8,6 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type DeckServiceServer struct {
@@ -23,26 +22,23 @@ func NewDeckServiceServer(users store.UserRepository, sessions store.SessionRepo
func (s *DeckServiceServer) UpdateName(ctx context.Context, req *pb.UpdateNameRequest) (*pb.UpdateNameResponse, error) {
log.Printf("[DeckService] UpdateName: deckType=%d deckNumber=%d name=%q", req.DeckType, req.UserDeckNumber, req.Name)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
deckKey := store.DeckKey{DeckType: model.DeckType(req.DeckType), UserDeckNumber: req.UserDeckNumber}
deck := user.Decks[deckKey]
deck.Name = req.Name
user.Decks[deckKey] = deck
})
- result := userdata.ProjectTables(user, []string{"IUserDeck"})
- return &pb.UpdateNameResponse{
- DiffUserData: userdata.BuildDiffFromTables(result),
- }, nil
+ return &pb.UpdateNameResponse{}, nil
}
func (s *DeckServiceServer) RefreshDeckPower(ctx context.Context, req *pb.RefreshDeckPowerRequest) (*pb.RefreshDeckPowerResponse, error) {
log.Printf("[DeckService] RefreshDeckPower: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
if req.DeckPower == nil {
log.Printf("[DeckService] RefreshDeckPower: deckPower is nil")
return
@@ -81,19 +77,14 @@ func (s *DeckServiceServer) RefreshDeckPower(ctx context.Context, req *pb.Refres
}
})
- result := userdata.ProjectTables(user, []string{
- "IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
- })
- return &pb.RefreshDeckPowerResponse{
- DiffUserData: userdata.BuildDiffFromTables(result),
- }, nil
+ return &pb.RefreshDeckPowerResponse{}, nil
}
func (s *DeckServiceServer) RefreshMultiDeckPower(ctx context.Context, req *pb.RefreshMultiDeckPowerRequest) (*pb.RefreshMultiDeckPowerResponse, error) {
log.Printf("[DeckService] RefreshMultiDeckPower: %d entries", len(req.DeckPowerInfo))
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
for _, info := range req.DeckPowerInfo {
if info.DeckPower == nil {
continue
@@ -133,12 +124,7 @@ func (s *DeckServiceServer) RefreshMultiDeckPower(ctx context.Context, req *pb.R
}
})
- result := userdata.ProjectTables(user, []string{
- "IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
- })
- return &pb.RefreshMultiDeckPowerResponse{
- DiffUserData: userdata.BuildDiffFromTables(result),
- }, nil
+ return &pb.RefreshMultiDeckPowerResponse{}, nil
}
func deckSlotsFromProto(deck *pb.Deck) []store.DeckCharacterInput {
@@ -171,47 +157,23 @@ func (s *DeckServiceServer) ReplaceDeck(ctx context.Context, req *pb.ReplaceDeck
i+1, ch.UserCostumeUuid, ch.MainUserWeaponUuid, ch.SubUserWeaponUuid, ch.UserCompanionUuid, ch.UserThoughtUuid)
}
}
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
- []string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
- Track("IUserDeckPartsGroup", oldUser, userdata.DeckPartsGroupRecords,
- []string{"userId", "userDeckCharacterUuid", "userPartsUuid"}).
- Track("IUserDeckCharacterDressupCostume", oldUser, userdata.DeckDressupCostumeRecords,
- []string{"userId", "userDeckCharacterUuid"})
-
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
if req.Deck == nil {
return
}
store.ApplyDeckReplacement(user, model.DeckType(req.DeckType), req.UserDeckNumber, deckSlotsFromProto(req.Deck), gametime.NowMillis())
})
- result := userdata.ProjectTables(user, []string{
- "IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
- "IUserDeckCharacterDressupCostume",
- })
- return &pb.ReplaceDeckResponse{
- DiffUserData: tracker.Apply(user, result),
- }, nil
+ return &pb.ReplaceDeckResponse{}, nil
}
func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.ReplaceTripleDeckRequest) (*pb.ReplaceTripleDeckResponse, error) {
log.Printf("[DeckService] ReplaceTripleDeck: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
- []string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
- Track("IUserDeckPartsGroup", oldUser, userdata.DeckPartsGroupRecords,
- []string{"userId", "userDeckCharacterUuid", "userPartsUuid"}).
- Track("IUserDeckCharacterDressupCostume", oldUser, userdata.DeckDressupCostumeRecords,
- []string{"userId", "userDeckCharacterUuid"})
-
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
nowMillis := gametime.NowMillis()
for idx, detail := range []*pb.DeckDetail{req.DeckDetail01, req.DeckDetail02, req.DeckDetail03} {
if detail == nil || detail.Deck == nil {
@@ -231,11 +193,23 @@ func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.Repla
}
})
- result := userdata.ProjectTables(user, []string{
- "IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
- "IUserDeckCharacterDressupCostume",
- })
- return &pb.ReplaceTripleDeckResponse{
- DiffUserData: tracker.Apply(user, result),
- }, nil
+ return &pb.ReplaceTripleDeckResponse{}, nil
+}
+
+func (s *DeckServiceServer) ReplaceMultiDeck(ctx context.Context, req *pb.ReplaceMultiDeckRequest) (*pb.ReplaceMultiDeckResponse, error) {
+ log.Printf("[DeckService] ReplaceMultiDeck: %d entries", len(req.DeckDetail))
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+
+ s.users.UpdateUser(userId, func(user *store.UserState) {
+ nowMillis := gametime.NowMillis()
+ for idx, detail := range req.DeckDetail {
+ if detail == nil || detail.Deck == nil {
+ continue
+ }
+ log.Printf("[DeckService] ReplaceMultiDeck detail %d: deckType=%d deckNumber=%d", idx+1, detail.DeckType, detail.UserDeckNumber)
+ store.ApplyDeckReplacement(user, model.DeckType(detail.DeckType), detail.UserDeckNumber, deckSlotsFromProto(detail.Deck), nowMillis)
+ }
+ })
+
+ return &pb.ReplaceMultiDeckResponse{}, nil
}
diff --git a/server/internal/service/dokan.go b/server/internal/service/dokan.go
index 4fe12bf..a7e427e 100644
--- a/server/internal/service/dokan.go
+++ b/server/internal/service/dokan.go
@@ -7,7 +7,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type DokanServiceServer struct {
@@ -23,8 +22,8 @@ func NewDokanServiceServer(users store.UserRepository, sessions store.SessionRep
func (s *DokanServiceServer) RegisterDokanConfirmed(ctx context.Context, req *pb.RegisterDokanConfirmedRequest) (*pb.RegisterDokanConfirmedResponse, error) {
log.Printf("[DokanService] RegisterDokanConfirmed: dokanIds=%v", req.DokanId)
- userId := currentUserId(ctx, s.users, s.sessions)
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
for _, id := range req.DokanId {
user.DokanConfirmed[id] = true
}
@@ -33,9 +32,5 @@ func (s *DokanServiceServer) RegisterDokanConfirmed(ctx context.Context, req *pb
return nil, fmt.Errorf("update user: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserDokan"}))
-
- return &pb.RegisterDokanConfirmedResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.RegisterDokanConfirmedResponse{}, nil
}
diff --git a/server/internal/service/explore.go b/server/internal/service/explore.go
index 619b976..6ffc673 100644
--- a/server/internal/service/explore.go
+++ b/server/internal/service/explore.go
@@ -10,7 +10,6 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
const (
@@ -19,18 +18,6 @@ const (
exploreRewardBaseCount = 1
)
-var exploreDiffTables = []string{
- "IUserExplore",
- "IUserExploreScore",
-}
-
-var exploreFinishDiffTables = []string{
- "IUserExplore",
- "IUserExploreScore",
- "IUserMaterial",
- "IUserStatus",
-}
-
type ExploreServiceServer struct {
pb.UnimplementedExploreServiceServer
users store.UserRepository
@@ -49,10 +36,10 @@ func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartEx
return nil, fmt.Errorf("explore id=%d not found", req.ExploreId)
}
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
explore := s.catalog.Explores[req.ExploreId]
if req.UseConsumableItemId > 0 && explore.ConsumeItemCount > 0 {
cur := user.ConsumableItems[req.UseConsumableItemId]
@@ -71,11 +58,7 @@ func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartEx
return nil, fmt.Errorf("start explore: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreDiffTables))
-
- return &pb.StartExploreResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.StartExploreResponse{}, nil
}
func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.FinishExploreRequest) (*pb.FinishExploreResponse, error) {
@@ -88,12 +71,12 @@ func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.Finish
assetGradeIconId := s.catalog.GradeForScore(req.ExploreId, req.Score)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
rewardCount := int32(exploreRewardBaseCount) * explore.RewardLotteryCount
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
existing, exists := user.ExploreScores[req.ExploreId]
if !exists || req.Score > existing.MaxScore {
user.ExploreScores[req.ExploreId] = store.ExploreScoreState{
@@ -123,8 +106,6 @@ func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.Finish
return nil, fmt.Errorf("finish explore: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreFinishDiffTables))
-
rewards := []*pb.ExploreReward{
{
PossessionType: int32(model.PossessionTypeMaterial),
@@ -137,17 +118,16 @@ func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.Finish
AcquireStaminaCount: exploreStaminaRecovery,
ExploreReward: rewards,
AssetGradeIconId: assetGradeIconId,
- DiffUserData: diff,
}, nil
}
func (s *ExploreServiceServer) RetireExplore(ctx context.Context, req *pb.RetireExploreRequest) (*pb.RetireExploreResponse, error) {
log.Printf("[ExploreService] RetireExplore: exploreId=%d", req.ExploreId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
user.Explore = store.ExploreState{
PlayingExploreId: 0,
IsUseExploreTicket: false,
@@ -159,9 +139,5 @@ func (s *ExploreServiceServer) RetireExplore(ctx context.Context, req *pb.Retire
return nil, fmt.Errorf("retire explore: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserExplore"}))
-
- return &pb.RetireExploreResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.RetireExploreResponse{}, nil
}
diff --git a/server/internal/service/friend.go b/server/internal/service/friend.go
index 15fbcba..ebb53da 100644
--- a/server/internal/service/friend.go
+++ b/server/internal/service/friend.go
@@ -6,7 +6,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -23,7 +22,7 @@ func NewFriendServiceServer(users store.UserRepository, sessions store.SessionRe
func (s *FriendServiceServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
log.Printf("[FriendService] GetUser: playerId=%d", req.PlayerId)
- return &pb.GetUserResponse{DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetUserResponse{}, nil
}
func (s *FriendServiceServer) GetFriendList(ctx context.Context, req *pb.GetFriendListRequest) (*pb.GetFriendListResponse, error) {
@@ -32,22 +31,19 @@ func (s *FriendServiceServer) GetFriendList(ctx context.Context, req *pb.GetFrie
FriendUser: []*pb.FriendUser{},
SendCheerCount: 0,
ReceivedCheerCount: 0,
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
func (s *FriendServiceServer) GetFriendRequestList(ctx context.Context, req *emptypb.Empty) (*pb.GetFriendRequestListResponse, error) {
log.Printf("[FriendService] GetFriendRequestList")
return &pb.GetFriendRequestListResponse{
- User: []*pb.User{},
- DiffUserData: userdata.EmptyDiff(),
+ User: []*pb.User{},
}, nil
}
func (s *FriendServiceServer) SearchRecommendedUsers(ctx context.Context, req *emptypb.Empty) (*pb.SearchRecommendedUsersResponse, error) {
log.Printf("[FriendService] SearchRecommendedUsers")
return &pb.SearchRecommendedUsersResponse{
- Users: []*pb.User{},
- DiffUserData: userdata.EmptyDiff(),
+ Users: []*pb.User{},
}, nil
}
diff --git a/server/internal/service/gacha.go b/server/internal/service/gacha.go
index 174922e..499b4d5 100644
--- a/server/internal/service/gacha.go
+++ b/server/internal/service/gacha.go
@@ -11,25 +11,11 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
-var gachaDiffTables = []string{
- "IUserGem",
- "IUserCostume",
- "IUserWeapon",
- "IUserConsumableItem",
- "IUserCostumeActiveSkill",
- "IUserWeaponNote",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserCharacter",
- "IUserMaterial",
-}
-
type GachaServiceServer struct {
pb.UnimplementedGachaServiceServer
users store.UserRepository
@@ -56,7 +42,7 @@ func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaL
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
catalog := s.catalog
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
user, err := s.users.UpdateUser(userId, func(user *store.UserState) {
@@ -82,7 +68,6 @@ func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaL
return &pb.GetGachaListResponse{
Gacha: gachaList,
ConvertedGachaMedal: toProtoConvertedGachaMedal(user.Gacha.ConvertedGachaMedal),
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
@@ -134,7 +119,7 @@ func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaReque
catalog := s.catalog
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
return nil, fmt.Errorf("snapshot user: %w", err)
@@ -152,8 +137,7 @@ func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaReque
}
return &pb.GetGachaResponse{
- Gacha: byId,
- DiffUserData: userdata.EmptyDiff(),
+ Gacha: byId,
}, nil
}
@@ -165,7 +149,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
}
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
execCount := req.ExecCount
if execCount <= 0 {
execCount = 1
@@ -290,17 +274,11 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
nextGacha := toProtoGacha(*entry, &bs)
- changedStoryIds := s.handler.Granter.DrainChangedStoryWeaponIds()
- diffOrder := append(gachaDiffTables, "IUserWeaponStory")
- diff := userdata.BuildDiffFromTablesOrdered(userdata.ProjectTables(updatedUser, diffOrder), diffOrder)
- userdata.AddWeaponStoryDiff(diff, updatedUser, changedStoryIds)
-
return &pb.DrawResponse{
NextGacha: nextGacha,
GachaResult: gachaResults,
GachaBonus: bonuses,
MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{},
- DiffUserData: diff,
}, nil
}
@@ -312,7 +290,7 @@ func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBox
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
}
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
if resetErr := s.handler.HandleResetBox(user, *entry); resetErr != nil {
log.Printf("[GachaService] ResetBoxGacha error: %v", resetErr)
@@ -325,14 +303,13 @@ func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBox
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
return &pb.ResetBoxGachaResponse{
- Gacha: toProtoGacha(*entry, &bs),
- DiffUserData: userdata.EmptyDiff(),
+ Gacha: toProtoGacha(*entry, &bs),
}, nil
}
func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Empty) (*pb.GetRewardGachaResponse, error) {
log.Printf("[GachaService] GetRewardGacha")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
return nil, fmt.Errorf("snapshot user: %w", err)
@@ -353,14 +330,13 @@ func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Em
Available: drawCount < maxCount,
TodaysCurrentDrawCount: drawCount,
DailyMaxCount: maxCount,
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawRequest) (*pb.RewardDrawResponse, error) {
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)
var items []gacha.DrawnItem
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
@@ -393,12 +369,8 @@ func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawR
})
}
- tables := userdata.FullClientTableMap(updatedUser)
- diff := userdata.BuildDiffFromTables(tables)
-
return &pb.RewardDrawResponse{
RewardGachaResult: results,
- DiffUserData: diff,
}, nil
}
diff --git a/server/internal/service/gameplay.go b/server/internal/service/gameplay.go
index a76c92e..ff4a089 100644
--- a/server/internal/service/gameplay.go
+++ b/server/internal/service/gameplay.go
@@ -5,7 +5,6 @@ import (
"log"
pb "lunar-tear/server/gen/proto"
- "lunar-tear/server/internal/userdata"
)
type GameplayServiceServer struct {
@@ -23,6 +22,5 @@ func (s *GameplayServiceServer) CheckBeforeGamePlay(ctx context.Context, req *pb
return &pb.CheckBeforeGamePlayResponse{
IsExistUnreadPop: false,
MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{},
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
diff --git a/server/internal/service/gift.go b/server/internal/service/gift.go
index b54ccd8..0b72ff5 100644
--- a/server/internal/service/gift.go
+++ b/server/internal/service/gift.go
@@ -11,7 +11,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -30,7 +29,7 @@ func NewGiftServiceServer(users store.UserRepository, sessions store.SessionRepo
func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGiftRequest) (*pb.ReceiveGiftResponse, error) {
log.Printf("[GiftService] ReceiveGift: giftUuids=%d", len(req.UserGiftUuid))
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
received := make([]string, 0, len(req.UserGiftUuid))
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
nowMillis := gametime.NowMillis()
@@ -54,7 +53,6 @@ func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGift
ReceivedGiftUuid: []string{},
ExpiredGiftUuid: []string{},
OverflowGiftUuid: []string{},
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
@@ -62,7 +60,6 @@ func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGift
ReceivedGiftUuid: received,
ExpiredGiftUuid: []string{},
OverflowGiftUuid: []string{},
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
@@ -70,7 +67,7 @@ func (s *GiftServiceServer) GetGiftList(ctx context.Context, req *pb.GetGiftList
log.Printf("[GiftService] GetGiftList: rewardKinds=%v expirationType=%d ascending=%v nextCursor=%d previousCursor=%d getCount=%d",
req.RewardKindType, req.ExpirationType, req.IsAscendingSort, req.NextCursor, req.PreviousCursor, req.GetCount)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
return nil, fmt.Errorf("snapshot user: %w", err)
@@ -101,13 +98,12 @@ func (s *GiftServiceServer) GetGiftList(ctx context.Context, req *pb.GetGiftList
TotalPageCount: pageCount(len(user.Gifts.NotReceived), int(req.GetCount)),
NextCursor: 0,
PreviousCursor: 0,
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
func (s *GiftServiceServer) GetGiftReceiveHistoryList(ctx context.Context, req *emptypb.Empty) (*pb.GetGiftReceiveHistoryListResponse, error) {
log.Printf("[GiftService] GetGiftReceiveHistoryList")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
return nil, fmt.Errorf("snapshot user: %w", err)
@@ -121,8 +117,7 @@ func (s *GiftServiceServer) GetGiftReceiveHistoryList(ctx context.Context, req *
})
}
return &pb.GetGiftReceiveHistoryListResponse{
- Gift: items,
- DiffUserData: userdata.EmptyDiff(),
+ Gift: items,
}, nil
}
diff --git a/server/internal/service/gimmick.go b/server/internal/service/gimmick.go
index 103f7bd..4d8c4d2 100644
--- a/server/internal/service/gimmick.go
+++ b/server/internal/service/gimmick.go
@@ -8,7 +8,6 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -27,8 +26,8 @@ func NewGimmickServiceServer(users store.UserRepository, sessions store.SessionR
func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.UpdateSequenceRequest) (*pb.UpdateSequenceResponse, error) {
log.Printf("[GimmickService] UpdateSequence: scheduleId=%d sequenceId=%d",
req.GimmickSequenceScheduleId, req.GimmickSequenceId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
key := store.GimmickSequenceKey{
GimmickSequenceScheduleId: req.GimmickSequenceScheduleId,
GimmickSequenceId: req.GimmickSequenceId,
@@ -37,16 +36,14 @@ func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.Updat
sequence.Key = key
user.Gimmick.Sequences[key] = sequence
})
- return &pb.UpdateSequenceResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickSequence"})),
- }, nil
+ return &pb.UpdateSequenceResponse{}, nil
}
func (s *GimmickServiceServer) UpdateGimmickProgress(ctx context.Context, req *pb.UpdateGimmickProgressRequest) (*pb.UpdateGimmickProgressResponse, error) {
log.Printf("[GimmickService] UpdateGimmickProgress: scheduleId=%d sequenceId=%d gimmickId=%d ornamentIndex=%d progressValueBit=%d flowType=%d",
req.GimmickSequenceScheduleId, req.GimmickSequenceId, req.GimmickId, req.GimmickOrnamentIndex, req.ProgressValueBit, req.FlowType)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
nowMillis := gametime.NowMillis()
progressKey := store.GimmickKey{
GimmickSequenceScheduleId: req.GimmickSequenceScheduleId,
@@ -74,18 +71,14 @@ func (s *GimmickServiceServer) UpdateGimmickProgress(ctx context.Context, req *p
GimmickOrnamentReward: []*pb.GimmickReward{},
IsSequenceCleared: false,
GimmickSequenceClearReward: []*pb.GimmickReward{},
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{
- "IUserGimmick",
- "IUserGimmickOrnamentProgress",
- })),
}, nil
}
func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *emptypb.Empty) (*pb.InitSequenceScheduleResponse, error) {
log.Printf("[GimmickService] InitSequenceSchedule")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
now := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
added := 0
for _, key := range s.gimmickCatalog.ActiveScheduleKeys(*user, now) {
if _, exists := user.Gimmick.Sequences[key]; !exists {
@@ -97,15 +90,13 @@ func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *empt
log.Printf("[GimmickService] InitSequenceSchedule: added %d sequences (total %d)", added, len(user.Gimmick.Sequences))
}
})
- return &pb.InitSequenceScheduleResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, gimmickDiffTables)),
- }, nil
+ return &pb.InitSequenceScheduleResponse{}, nil
}
func (s *GimmickServiceServer) Unlock(ctx context.Context, req *pb.UnlockRequest) (*pb.UnlockResponse, error) {
log.Printf("[GimmickService] Unlock: gimmickKeys=%d", len(req.GimmickKey))
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
for _, item := range req.GimmickKey {
key := store.GimmickKey{
GimmickSequenceScheduleId: item.GimmickSequenceScheduleId,
@@ -118,7 +109,5 @@ func (s *GimmickServiceServer) Unlock(ctx context.Context, req *pb.UnlockRequest
user.Gimmick.Unlocks[key] = unlock
}
})
- return &pb.UnlockResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickUnlock"})),
- }, nil
+ return &pb.UnlockResponse{}, nil
}
diff --git a/server/internal/service/listbin.go b/server/internal/service/listbin.go
index ff6c13a..5211c74 100644
--- a/server/internal/service/listbin.go
+++ b/server/internal/service/listbin.go
@@ -208,7 +208,7 @@ func parseListBin(data []byte) listBinIndex {
return idx
}
-func loadListBinIndex(revision string) (listBinIndex, bool) {
+func loadListBinIndex(baseDir, revision string) (listBinIndex, bool) {
listBinCacheMu.RLock()
idx, ok := listBinCache[revision]
listBinCacheMu.RUnlock()
@@ -230,7 +230,7 @@ func loadListBinIndex(revision string) (listBinIndex, bool) {
listBinInflight[revision] = load
listBinCacheMu.Unlock()
- filePath := filepath.Join("assets", "revisions", revision, "list.bin")
+ filePath := filepath.Join(baseDir, "assets", "revisions", revision, "list.bin")
data, err := os.ReadFile(filePath)
if err != nil {
listBinCacheMu.Lock()
@@ -250,7 +250,7 @@ func loadListBinIndex(revision string) (listBinIndex, bool) {
return idx, true
}
-func loadInfoIndex(revision string) map[string]infoAlias {
+func loadInfoIndex(baseDir, revision string) map[string]infoAlias {
infoCacheMu.RLock()
m, ok := infoCache[revision]
infoCacheMu.RUnlock()
@@ -272,7 +272,7 @@ func loadInfoIndex(revision string) map[string]infoAlias {
infoInflight[revision] = load
infoCacheMu.Unlock()
- filePath := filepath.Join("assets", "revisions", revision, "info.json")
+ filePath := filepath.Join(baseDir, "assets", "revisions", revision, "info.json")
data, err := os.ReadFile(filePath)
if err != nil {
infoCacheMu.Lock()
@@ -378,7 +378,7 @@ func hasNonASCII(s string) bool {
// an en locale fallback is appended (marked IsLocaleFallback so callers can skip MD5 validation).
// For paths with non-ASCII characters, mojibake (double-encoded) and fullwidth-to-ASCII
// variants are also tried.
-func pathStrToFullPaths(revision, assetType, pathStr string) []pathCandidate {
+func pathStrToFullPaths(baseDir, revision, assetType, pathStr string) []pathCandidate {
fsPath := strings.ReplaceAll(pathStr, ")", "/")
if strings.Contains(fsPath, "..") || filepath.IsAbs(fsPath) || strings.HasPrefix(fsPath, "/") {
return nil
@@ -402,7 +402,7 @@ func pathStrToFullPaths(revision, assetType, pathStr string) []pathCandidate {
if strings.Contains(pathStr, ")ko)") {
entries = append(entries, tagged{strings.ReplaceAll(pathStr, ")ko)", ")en)"), true})
}
- base := filepath.Join("assets", "revisions", revision)
+ base := filepath.Join(baseDir, "assets", "revisions", revision)
var out []pathCandidate
seen := make(map[string]bool)
for _, e := range entries {
@@ -434,13 +434,13 @@ func appendUniqueCandidate(candidates []assetCandidate, seen map[string]bool, ca
return append(candidates, candidate)
}
-func duplicateCandidatePath(candidate assetCandidate, assetType, targetRevision, targetBaseName string) string {
- root := filepath.Join("assets", "revisions", candidate.Revision, assetType)
+func duplicateCandidatePath(baseDir string, candidate assetCandidate, assetType, targetRevision, targetBaseName string) string {
+ root := filepath.Join(baseDir, "assets", "revisions", candidate.Revision, assetType)
rel, err := filepath.Rel(root, candidate.Path)
if err != nil || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
return ""
}
- return filepath.Join("assets", "revisions", targetRevision, assetType, filepath.Dir(rel), targetBaseName)
+ return filepath.Join(baseDir, "assets", "revisions", targetRevision, assetType, filepath.Dir(rel), targetBaseName)
}
// objectIdToFilePathCandidates returns candidate file paths for the object: list.bin path, locale fallbacks
@@ -448,8 +448,8 @@ func duplicateCandidatePath(candidate assetCandidate, assetType, targetRevision,
// The original locale path is tried first (with MD5 validation); locale fallbacks are tried after
// (without MD5 validation, since the hash in list.bin refers to the original locale's content).
// Callers should try each path until one exists on disk.
-func objectIdToFilePathCandidates(revision, assetType, objectId string) (candidates []assetCandidate, size int64, ok bool) {
- idx, ok := loadListBinIndex(revision)
+func objectIdToFilePathCandidates(baseDir, revision, assetType, objectId string) (candidates []assetCandidate, size int64, ok bool) {
+ idx, ok := loadListBinIndex(baseDir, revision)
if !ok || idx == nil {
return nil, 0, false
}
@@ -457,7 +457,7 @@ func objectIdToFilePathCandidates(revision, assetType, objectId string) (candida
if !ok || entry.Path == "" {
return nil, 0, false
}
- paths := pathStrToFullPaths(revision, assetType, entry.Path)
+ paths := pathStrToFullPaths(baseDir, revision, assetType, entry.Path)
if len(paths) == 0 {
return nil, 0, false
}
@@ -474,15 +474,14 @@ func objectIdToFilePathCandidates(revision, assetType, objectId string) (candida
ExpectedMD5: md5,
})
}
- // Add paths from info.json: when requested file is a "from-name" (duplicate not included), serve "to-name" instead.
- infoIndex := loadInfoIndex(revision)
+ infoIndex := loadInfoIndex(baseDir, revision)
if len(infoIndex) > 0 {
for _, c := range candidates {
alias, ok := infoIndex[filepath.Base(c.Path)]
if !ok || alias.ToName == "" {
continue
}
- alt := duplicateCandidatePath(c, assetType, alias.ToRevision, alias.ToName)
+ alt := duplicateCandidatePath(baseDir, c, assetType, alias.ToRevision, alias.ToName)
if alt == "" {
continue
}
diff --git a/server/internal/service/loginbonus.go b/server/internal/service/loginbonus.go
index cb90890..0fc6775 100644
--- a/server/internal/service/loginbonus.go
+++ b/server/internal/service/loginbonus.go
@@ -11,7 +11,6 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -29,9 +28,9 @@ func NewLoginBonusServiceServer(users store.UserRepository, sessions store.Sessi
func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb.Empty) (*pb.ReceiveStampResponse, error) {
log.Printf("[LoginBonusService] ReceiveStamp")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
now := gametime.NowMillis()
nextStamp := user.LoginBonus.CurrentStampNumber + 1
@@ -64,9 +63,5 @@ func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb
user.LoginBonus.LatestVersion = now
})
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
- []string{"IUserLoginBonus"},
- ))
- setCommonResponseTrailers(ctx, diff, false)
- return &pb.ReceiveStampResponse{DiffUserData: diff}, nil
+ return &pb.ReceiveStampResponse{}, nil
}
diff --git a/server/internal/service/material.go b/server/internal/service/material.go
index 6b448a7..d9b0138 100644
--- a/server/internal/service/material.go
+++ b/server/internal/service/material.go
@@ -8,14 +8,8 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
-var materialDiffTables = []string{
- "IUserMaterial",
- "IUserConsumableItem",
-}
-
type MaterialServiceServer struct {
pb.UnimplementedMaterialServiceServer
users store.UserRepository
@@ -31,13 +25,9 @@ func NewMaterialServiceServer(users store.UserRepository, sessions store.Session
func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRequest) (*pb.MaterialSellResponse, error) {
log.Printf("[MaterialService] Sell: %d item(s)", len(req.MaterialPossession))
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
totalGold := int32(0)
for _, item := range req.MaterialPossession {
mat, ok := s.catalog.All[item.MaterialId]
@@ -71,10 +61,5 @@ func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRe
return nil, fmt.Errorf("material sell: %w", err)
}
- tables := userdata.ProjectTables(snapshot, materialDiffTables)
- diff := tracker.Apply(snapshot, tables)
-
- return &pb.MaterialSellResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.MaterialSellResponse{}, nil
}
diff --git a/server/internal/service/mission.go b/server/internal/service/mission.go
index e7721e2..c0a9c2f 100644
--- a/server/internal/service/mission.go
+++ b/server/internal/service/mission.go
@@ -7,7 +7,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type MissionServiceServer struct {
@@ -23,15 +22,11 @@ func NewMissionServiceServer(users store.UserRepository, sessions store.SessionR
func (s *MissionServiceServer) UpdateMissionProgress(ctx context.Context, req *pb.UpdateMissionProgressRequest) (*pb.UpdateMissionProgressResponse, error) {
log.Printf("[MissionService] UpdateMissionProgress: cage=%v pictureBook=%v", req.CageMeasurableValues, req.PictureBookMeasurableValues)
- userId := currentUserId(ctx, s.users, s.sessions)
- snapshot, err := s.users.LoadUser(userId)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ _, err := s.users.LoadUser(userId)
if err != nil {
return nil, fmt.Errorf("snapshot user: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMission"}))
-
- return &pb.UpdateMissionProgressResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.UpdateMissionProgressResponse{}, nil
}
diff --git a/server/internal/service/movie.go b/server/internal/service/movie.go
index 2feebdf..fc7a863 100644
--- a/server/internal/service/movie.go
+++ b/server/internal/service/movie.go
@@ -8,7 +8,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type MovieServiceServer struct {
@@ -24,10 +23,10 @@ func NewMovieServiceServer(users store.UserRepository, sessions store.SessionRep
func (s *MovieServiceServer) SaveViewedMovie(ctx context.Context, req *pb.SaveViewedMovieRequest) (*pb.SaveViewedMovieResponse, error) {
log.Printf("[MovieService] SaveViewedMovie: movieIds=%v", req.MovieId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
now := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
for _, mid := range req.MovieId {
user.ViewedMovies[mid] = now
}
@@ -36,9 +35,5 @@ func (s *MovieServiceServer) SaveViewedMovie(ctx context.Context, req *pb.SaveVi
return nil, fmt.Errorf("update user: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMovie"}))
-
- return &pb.SaveViewedMovieResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.SaveViewedMovieResponse{}, nil
}
diff --git a/server/internal/service/navicutin.go b/server/internal/service/navicutin.go
index 84d058b..87004e0 100644
--- a/server/internal/service/navicutin.go
+++ b/server/internal/service/navicutin.go
@@ -7,7 +7,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type NaviCutInServiceServer struct {
@@ -23,17 +22,13 @@ func NewNaviCutInServiceServer(users store.UserRepository, sessions store.Sessio
func (s *NaviCutInServiceServer) RegisterPlayed(ctx context.Context, req *pb.RegisterPlayedRequest) (*pb.RegisterPlayedResponse, error) {
log.Printf("[NaviCutInService] RegisterPlayed: naviCutId=%d", req.NaviCutId)
- userId := currentUserId(ctx, s.users, s.sessions)
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
user.NaviCutInPlayed[req.NaviCutId] = true
})
if err != nil {
return nil, fmt.Errorf("update user: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserNaviCutIn"}))
-
- return &pb.RegisterPlayedResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.RegisterPlayedResponse{}, nil
}
diff --git a/server/internal/service/notification.go b/server/internal/service/notification.go
index 9459c74..b4c78b6 100644
--- a/server/internal/service/notification.go
+++ b/server/internal/service/notification.go
@@ -6,7 +6,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -23,20 +22,18 @@ func NewNotificationServiceServer(users store.UserRepository, sessions store.Ses
func (s *NotificationServiceServer) GetHeaderNotification(ctx context.Context, req *emptypb.Empty) (*pb.GetHeaderNotificationResponse, error) {
log.Printf("[NotificationService] GetHeaderNotification")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
return &pb.GetHeaderNotificationResponse{
GiftNotReceiveCount: 0,
FriendRequestReceiveCount: 0,
IsExistUnreadInformation: false,
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
return &pb.GetHeaderNotificationResponse{
GiftNotReceiveCount: int32(len(user.Gifts.NotReceived)),
FriendRequestReceiveCount: user.Notifications.FriendRequestReceiveCount,
IsExistUnreadInformation: user.Notifications.IsExistUnreadInformation,
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
diff --git a/server/internal/service/octo.go b/server/internal/service/octo.go
index 9b10b06..8e50797 100644
--- a/server/internal/service/octo.go
+++ b/server/internal/service/octo.go
@@ -50,6 +50,7 @@ const resourcesURLOriginal = "https://resources.app.nierreincarnation.com"
type OctoHTTPServer struct {
mux *http.ServeMux
ResourcesBaseURL string // if non-empty and exactly 43 chars, list.bin is rewritten to use this base for asset URLs
+ BaseDir string // root directory containing the assets/ tree; empty means current directory
revisions *revisionTracker
resolver *assetResolver
}
@@ -124,12 +125,13 @@ func fileMD5Hex(path string, info os.FileInfo) (string, error) {
return sum, nil
}
-func NewOctoHTTPServer(resourcesBaseURL string) *OctoHTTPServer {
+func NewOctoHTTPServer(resourcesBaseURL, baseDir string) *OctoHTTPServer {
s := &OctoHTTPServer{
mux: http.NewServeMux(),
ResourcesBaseURL: resourcesBaseURL,
+ BaseDir: baseDir,
revisions: newRevisionTracker(),
- resolver: newAssetResolver(),
+ resolver: newAssetResolver(baseDir),
}
s.resolver.Prewarm("0")
s.mux.HandleFunc("/", s.handleAll)
@@ -226,7 +228,7 @@ func (s *OctoHTTPServer) handleOctoV2(w http.ResponseWriter, r *http.Request, pa
requestedRevision := parts[len(parts)-1]
if requestedRevision != "" {
revision := "0"
- filePath := "assets/revisions/0/list.bin"
+ filePath := filepath.Join(s.BaseDir, "assets", "revisions", "0", "list.bin")
if requestedRevision != revision {
log.Printf("[OctoV2] Resource list request revision=%s canonicalized to revision=%s", requestedRevision, revision)
}
@@ -266,7 +268,7 @@ func (s *OctoHTTPServer) serveOctoV1List(w http.ResponseWriter, r *http.Request,
requestedRevision = parts[len(parts)-1]
}
revision := "0"
- filePath := "assets/revisions/0/list.bin"
+ filePath := filepath.Join(s.BaseDir, "assets", "revisions", "0", "list.bin")
if requestedRevision != revision {
log.Printf("[OctoV1] list request revision=%s canonicalized to revision=%s", requestedRevision, revision)
}
@@ -316,11 +318,11 @@ func (s *OctoHTTPServer) serveUnsoAsset(w http.ResponseWriter, r *http.Request,
w.WriteHeader(http.StatusNotFound)
return
}
- baseDir := filepath.Join("assets", "revisions")
+ revDir := filepath.Join(s.BaseDir, "assets", "revisions")
var triedPaths []string
var md5Mismatches []string
for _, candidate := range resolution.Candidates {
- rel, err := filepath.Rel(baseDir, candidate.Path)
+ rel, err := filepath.Rel(revDir, candidate.Path)
if err != nil || strings.Contains(rel, "..") || filepath.IsAbs(rel) {
continue
}
@@ -409,9 +411,9 @@ func (s *OctoHTTPServer) serveDatabaseBinE(w http.ResponseWriter, r *http.Reques
break
}
}
- filePath := "assets/release/database.bin.e"
+ filePath := filepath.Join(s.BaseDir, "assets", "release", "database.bin.e")
if version != "" {
- vPath := "assets/release/" + version + ".bin.e"
+ vPath := filepath.Join(s.BaseDir, "assets", "release", version+".bin.e")
if _, err := os.Stat(vPath); err == nil {
filePath = vPath
}
diff --git a/server/internal/service/omikuji.go b/server/internal/service/omikuji.go
index 1b43699..816349d 100644
--- a/server/internal/service/omikuji.go
+++ b/server/internal/service/omikuji.go
@@ -9,7 +9,6 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type OmikujiServiceServer struct {
@@ -26,21 +25,18 @@ func NewOmikujiServiceServer(users store.UserRepository, sessions store.SessionR
func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiDrawRequest) (*pb.OmikujiDrawResponse, error) {
log.Printf("[OmikujiService] OmikujiDraw: omikujiId=%d", req.OmikujiId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
now := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
user.DrawnOmikuji[req.OmikujiId] = now
})
if err != nil {
return nil, fmt.Errorf("update user: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserOmikuji"}))
-
return &pb.OmikujiDrawResponse{
OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId),
OmikujiItem: []*pb.OmikujiItem{},
- DiffUserData: diff,
}, nil
}
diff --git a/server/internal/service/parts.go b/server/internal/service/parts.go
index b9a3754..b83d02c 100644
--- a/server/internal/service/parts.go
+++ b/server/internal/service/parts.go
@@ -10,16 +10,10 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
const partsMaxLevel = int32(15)
-var partsDiffTables = []string{
- "IUserParts",
- "IUserConsumableItem",
-}
-
type PartsServiceServer struct {
pb.UnimplementedPartsServiceServer
users store.UserRepository
@@ -35,13 +29,9 @@ func NewPartsServiceServer(users store.UserRepository, sessions store.SessionRep
func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) {
log.Printf("[PartsService] Sell: %d part(s)", len(req.UserPartsUuid))
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserParts", oldUser, userdata.SortedPartsRecords, []string{"userId", "userPartsUuid"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
totalGold := int32(0)
for _, uuid := range req.UserPartsUuid {
part, ok := user.Parts[uuid]
@@ -81,23 +71,18 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
return nil, fmt.Errorf("parts sell: %w", err)
}
- tables := userdata.ProjectTables(snapshot, partsDiffTables)
- diff := tracker.Apply(snapshot, tables)
-
- return &pb.PartsSellResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.PartsSellResponse{}, nil
}
func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRequest) (*pb.PartsEnhanceResponse, error) {
log.Printf("[PartsService] Enhance: uuid=%s", req.UserPartsUuid)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
isSuccess := false
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
part, ok := user.Parts[req.UserPartsUuid]
if !ok {
log.Printf("[PartsService] Enhance: part uuid=%s not found", req.UserPartsUuid)
@@ -158,11 +143,8 @@ func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRe
return nil, fmt.Errorf("parts enhance: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, partsDiffTables))
-
return &pb.PartsEnhanceResponse{
- IsSuccess: isSuccess,
- DiffUserData: diff,
+ IsSuccess: isSuccess,
}, nil
}
@@ -170,10 +152,10 @@ func (s *PartsServiceServer) ReplacePreset(ctx context.Context, req *pb.PartsRep
log.Printf("[PartsService] ReplacePreset: preset=%d uuids=[%s, %s, %s]",
req.UserPartsPresetNumber, req.UserPartsUuid01, req.UserPartsUuid02, req.UserPartsUuid03)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
preset := user.PartsPresets[req.UserPartsPresetNumber]
preset.UserPartsPresetNumber = req.UserPartsPresetNumber
preset.UserPartsUuid01 = req.UserPartsUuid01
@@ -186,9 +168,5 @@ func (s *PartsServiceServer) ReplacePreset(ctx context.Context, req *pb.PartsRep
return nil, fmt.Errorf("parts replace preset: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserPartsPreset"}))
-
- return &pb.PartsReplacePresetResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.PartsReplacePresetResponse{}, nil
}
diff --git a/server/internal/service/portalcage.go b/server/internal/service/portalcage.go
index b293c1c..18b17d7 100644
--- a/server/internal/service/portalcage.go
+++ b/server/internal/service/portalcage.go
@@ -7,7 +7,6 @@ import (
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type PortalCageServiceServer struct {
@@ -23,17 +22,11 @@ func NewPortalCageServiceServer(users store.UserRepository, sessions store.Sessi
func (s *PortalCageServiceServer) UpdatePortalCageSceneProgress(ctx context.Context, req *pb.UpdatePortalCageSceneProgressRequest) (*pb.UpdatePortalCageSceneProgressResponse, error) {
log.Printf("[PortalCageService] UpdatePortalCageSceneProgress: portalCageSceneId=%d", req.PortalCageSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
now := gametime.NowMillis()
user.PortalCageStatus.IsCurrentProgress = true
user.PortalCageStatus.LatestVersion = now
})
-
- tables := userdata.ProjectTables(user,
- []string{"IUserPortalCageStatus"},
- )
- return &pb.UpdatePortalCageSceneProgressResponse{
- DiffUserData: userdata.BuildDiffFromTablesOrdered(tables, []string{"IUserPortalCageStatus"}),
- }, nil
+ return &pb.UpdatePortalCageSceneProgressResponse{}, nil
}
diff --git a/server/internal/service/quest_bighunt.go b/server/internal/service/quest_bighunt.go
index c6e8a47..0397e17 100644
--- a/server/internal/service/quest_bighunt.go
+++ b/server/internal/service/quest_bighunt.go
@@ -10,7 +10,6 @@ import (
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -32,25 +31,11 @@ func NewBigHuntServiceServer(
return &BigHuntServiceServer{users: users, sessions: sessions, catalog: catalog, engine: engine}
}
-var bigHuntDiffTables = []string{
- "IUserBigHuntProgressStatus",
- "IUserBigHuntMaxScore",
- "IUserBigHuntStatus",
- "IUserBigHuntScheduleMaxScore",
- "IUserBigHuntWeeklyMaxScore",
- "IUserBigHuntWeeklyStatus",
-}
-
-func buildBigHuntDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
- tables := userdata.ProjectTables(user, tableNames)
- return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
-}
-
func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.StartBigHuntQuestRequest) (*pb.StartBigHuntQuestResponse, error) {
log.Printf("[BigHuntService] StartBigHuntQuest: bossQuestId=%d questId=%d deckNumber=%d isDryRun=%v",
req.BigHuntBossQuestId, req.BigHuntQuestId, req.UserDeckNumber, req.IsDryRun)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
bhQuest, ok := s.catalog.QuestById[req.BigHuntQuestId]
@@ -58,7 +43,7 @@ func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.St
log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId)
}
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
if ok {
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis)
}
@@ -80,31 +65,27 @@ func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.St
user.BigHuntStatuses[req.BigHuntBossQuestId] = st
})
- return &pb.StartBigHuntQuestResponse{
- DiffUserData: buildBigHuntDiff(user, append([]string{"IUserQuest"}, bigHuntDiffTables...)),
- }, nil
+ return &pb.StartBigHuntQuestResponse{}, nil
}
func (s *BigHuntServiceServer) UpdateBigHuntQuestSceneProgress(ctx context.Context, req *pb.UpdateBigHuntQuestSceneProgressRequest) (*pb.UpdateBigHuntQuestSceneProgressResponse, error) {
log.Printf("[BigHuntService] UpdateBigHuntQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.BigHuntProgress.CurrentQuestSceneId = req.QuestSceneId
user.BigHuntProgress.LatestVersion = nowMillis
})
- return &pb.UpdateBigHuntQuestSceneProgressResponse{
- DiffUserData: buildBigHuntDiff(user, []string{"IUserBigHuntProgressStatus"}),
- }, nil
+ return &pb.UpdateBigHuntQuestSceneProgressResponse{}, nil
}
func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.FinishBigHuntQuestRequest) (*pb.FinishBigHuntQuestResponse, error) {
log.Printf("[BigHuntService] FinishBigHuntQuest: bossQuestId=%d questId=%d isRetired=%v",
req.BigHuntBossQuestId, req.BigHuntQuestId, req.IsRetired)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
@@ -114,7 +95,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
var scoreInfo *pb.BigHuntScoreInfo
var scoreRewards []*pb.BigHuntReward
- user, _ := 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)
if req.IsRetired || user.BigHuntProgress.IsDryRun {
@@ -229,18 +210,13 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
BattleReport: &pb.BigHuntBattleReport{
BattleReportWave: []*pb.BigHuntBattleReportWave{},
},
- DiffUserData: buildBigHuntDiff(user, append([]string{
- "IUserQuest",
- "IUserConsumableItem",
- "IUserMaterial",
- }, bigHuntDiffTables...)),
}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
@@ -248,7 +224,7 @@ func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.
var battleBinary []byte
var deckNumber int32
- user, _ := 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)
user.BigHuntProgress.CurrentQuestSceneId = 0
@@ -267,17 +243,16 @@ func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.
return &pb.RestartBigHuntQuestResponse{
BattleBinary: battleBinary,
DeckNumber: deckNumber,
- DiffUserData: buildBigHuntDiff(user, append([]string{"IUserQuest"}, bigHuntDiffTables...)),
}, nil
}
func (s *BigHuntServiceServer) SkipBigHuntQuest(ctx context.Context, req *pb.SkipBigHuntQuestRequest) (*pb.SkipBigHuntQuestResponse, error) {
log.Printf("[BigHuntService] SkipBigHuntQuest: bossQuestId=%d skipCount=%d", req.BigHuntBossQuestId, req.SkipCount)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
st := user.BigHuntStatuses[req.BigHuntBossQuestId]
st.DailyChallengeCount += req.SkipCount
st.LatestChallengeDatetime = nowMillis
@@ -286,15 +261,14 @@ func (s *BigHuntServiceServer) SkipBigHuntQuest(ctx context.Context, req *pb.Ski
})
return &pb.SkipBigHuntQuestResponse{
- ScoreReward: []*pb.BigHuntReward{},
- DiffUserData: buildBigHuntDiff(user, bigHuntDiffTables),
+ ScoreReward: []*pb.BigHuntReward{},
}, nil
}
func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *pb.SaveBigHuntBattleInfoRequest) (*pb.SaveBigHuntBattleInfoResponse, error) {
log.Printf("[BigHuntService] SaveBigHuntBattleInfo: elapsedFrames=%d", req.ElapsedFrameCount)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
var totalDamage int64
@@ -306,7 +280,7 @@ func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *p
}
}
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.BigHuntBattleBinary = req.BattleBinary
if req.BigHuntBattleDetail != nil {
@@ -322,15 +296,13 @@ func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *p
user.BigHuntProgress.LatestVersion = nowMillis
})
- return &pb.SaveBigHuntBattleInfoResponse{
- DiffUserData: buildBigHuntDiff(user, []string{"IUserBigHuntProgressStatus"}),
- }, nil
+ return &pb.SaveBigHuntBattleInfoResponse{}, nil
}
func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb.Empty) (*pb.GetBigHuntTopDataResponse, error) {
log.Printf("[BigHuntService] GetBigHuntTopData")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, _ := s.users.LoadUser(userId)
nowMillis := gametime.NowMillis()
@@ -368,7 +340,6 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
WeeklyScoreReward: weeklyRewards,
IsReceivedWeeklyScoreReward: ws.IsReceivedWeeklyReward,
LastWeekWeeklyScoreReward: lastWeekRewards,
- DiffUserData: buildBigHuntDiff(user, bigHuntDiffTables),
}, nil
}
diff --git a/server/internal/service/quest_event.go b/server/internal/service/quest_event.go
index 5fb8323..113e5ed 100644
--- a/server/internal/service/quest_event.go
+++ b/server/internal/service/quest_event.go
@@ -8,7 +8,6 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -16,9 +15,9 @@ import (
func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) {
log.Printf("[QuestService] StartEventQuest: chapterId=%d questId=%d isBattleOnly=%v", req.EventQuestChapterId, req.QuestId, req.IsBattleOnly)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := 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)
})
@@ -34,12 +33,6 @@ func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartE
return &pb.StartEventQuestResponse{
BattleDropReward: pbDrops,
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserStatus",
- "IUserQuest",
- "IUserQuestMission",
- "IUserEventQuestProgressStatus",
- }),
}, nil
}
@@ -47,34 +40,12 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v", req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated)
nowMillis := gametime.NowMillis()
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
var outcome questflow.FinishOutcome
- user, _ := 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)
})
- diff := buildSelectedQuestDiff(user, []string{
- "IUserQuest",
- "IUserQuestMission",
- "IUserEventQuestProgressStatus",
- "IUserStatus",
- "IUserGem",
- "IUserCharacter",
- "IUserCostume",
- "IUserCostumeActiveSkill",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserWeaponNote",
- "IUserCompanion",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
- "IUserParts",
- "IUserPartsGroupNote",
- })
- userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
-
return &pb.FinishEventQuestResponse{
DropReward: toProtoRewards(outcome.DropRewards),
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
@@ -84,55 +55,31 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
IsBigWin: outcome.IsBigWin,
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
UserStatusCampaignReward: []*pb.QuestReward{},
- DiffUserData: diff,
}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleEventQuestRestart(user, req.EventQuestChapterId, req.QuestId, gametime.NowMillis())
})
return &pb.RestartEventQuestResponse{
BattleDropReward: []*pb.BattleDropReward{},
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserQuest",
- "IUserQuestMission",
- "IUserEventQuestProgressStatus",
- }),
}, nil
}
func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context, req *pb.UpdateEventQuestSceneProgressRequest) (*pb.UpdateEventQuestSceneProgressResponse, error) {
log.Printf("[QuestService] UpdateEventQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
})
- diff := buildSelectedQuestDiff(user, []string{
- "IUserEventQuestProgressStatus",
- "IUserCharacter",
- "IUserCostume",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserCompanion",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
- "IUserParts",
- "IUserPartsGroupNote",
- })
- userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
-
- return &pb.UpdateEventQuestSceneProgressResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.UpdateEventQuestSceneProgressResponse{}, nil
}
const defaultGuerrillaFreeOpenMinutes = int32(60)
@@ -140,16 +87,14 @@ const defaultGuerrillaFreeOpenMinutes = int32(60)
func (s *QuestServiceServer) StartGuerrillaFreeOpen(ctx context.Context, req *emptypb.Empty) (*pb.StartGuerrillaFreeOpenResponse, error) {
log.Printf("[QuestService] StartGuerrillaFreeOpen")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.GuerrillaFreeOpen.StartDatetime = nowMillis
user.GuerrillaFreeOpen.OpenMinutes = defaultGuerrillaFreeOpenMinutes
user.GuerrillaFreeOpen.DailyOpenedCount++
user.GuerrillaFreeOpen.LatestVersion = nowMillis
})
- return &pb.StartGuerrillaFreeOpenResponse{
- DiffUserData: buildSelectedQuestDiff(user, []string{"IUserEventQuestGuerrillaFreeOpen"}),
- }, nil
+ return &pb.StartGuerrillaFreeOpenResponse{}, nil
}
diff --git a/server/internal/service/quest_extra.go b/server/internal/service/quest_extra.go
index e89ab69..8a4b0be 100644
--- a/server/internal/service/quest_extra.go
+++ b/server/internal/service/quest_extra.go
@@ -8,15 +8,14 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := 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)
})
@@ -32,12 +31,6 @@ func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartE
return &pb.StartExtraQuestResponse{
BattleDropReward: pbDrops,
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserStatus",
- "IUserQuest",
- "IUserQuestMission",
- "IUserExtraQuestProgressStatus",
- }),
}, nil
}
@@ -45,34 +38,12 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
log.Printf("[QuestService] FinishExtraQuest: questId=%d isRetired=%v isAnnihilated=%v", req.QuestId, req.IsRetired, req.IsAnnihilated)
nowMillis := gametime.NowMillis()
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
var outcome questflow.FinishOutcome
- user, _ := 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)
})
- diff := buildSelectedQuestDiff(user, []string{
- "IUserQuest",
- "IUserQuestMission",
- "IUserExtraQuestProgressStatus",
- "IUserStatus",
- "IUserGem",
- "IUserCharacter",
- "IUserCostume",
- "IUserCostumeActiveSkill",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserWeaponNote",
- "IUserCompanion",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
- "IUserParts",
- "IUserPartsGroupNote",
- })
- userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
-
return &pb.FinishExtraQuestResponse{
DropReward: toProtoRewards(outcome.DropRewards),
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
@@ -81,16 +52,17 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
IsBigWin: outcome.IsBigWin,
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
UserStatusCampaignReward: []*pb.QuestReward{},
- DiffUserData: diff,
}, nil
}
func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.RestartExtraQuestRequest) (*pb.RestartExtraQuestResponse, error) {
log.Printf("[QuestService] RestartExtraQuest: questId=%d", req.QuestId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ var deckNumber int32
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleExtraQuestRestart(user, req.QuestId, gametime.NowMillis())
+ deckNumber = user.Quests[req.QuestId].UserDeckNumber
})
drops := s.engine.BattleDropRewards(req.QuestId)
@@ -105,40 +77,17 @@ func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.Rest
return &pb.RestartExtraQuestResponse{
BattleDropReward: pbDrops,
- DeckNumber: user.Quests[req.QuestId].UserDeckNumber,
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserQuest",
- "IUserQuestMission",
- "IUserExtraQuestProgressStatus",
- }),
+ DeckNumber: deckNumber,
}, nil
}
func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context, req *pb.UpdateExtraQuestSceneProgressRequest) (*pb.UpdateExtraQuestSceneProgressResponse, error) {
log.Printf("[QuestService] UpdateExtraQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
})
- diff := buildSelectedQuestDiff(user, []string{
- "IUserExtraQuestProgressStatus",
- "IUserCharacter",
- "IUserCostume",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserCompanion",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
- "IUserParts",
- "IUserPartsGroupNote",
- })
- userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
-
- return &pb.UpdateExtraQuestSceneProgressResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.UpdateExtraQuestSceneProgressResponse{}, nil
}
diff --git a/server/internal/service/quest_main.go b/server/internal/service/quest_main.go
index a16918c..0d5584d 100644
--- a/server/internal/service/quest_main.go
+++ b/server/internal/service/quest_main.go
@@ -9,7 +9,6 @@ import (
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -28,91 +27,45 @@ func NewQuestServiceServer(users store.UserRepository, sessions store.SessionRep
return &QuestServiceServer{users: users, sessions: sessions, engine: engine}
}
-func buildSelectedQuestDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
- tables := userdata.ProjectTables(user, tableNames)
- return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
-}
-
func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, req *pb.UpdateMainFlowSceneProgressRequest) (*pb.UpdateMainFlowSceneProgressResponse, error) {
log.Printf("[QuestService] UpdateMainFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
})
- diff := buildSelectedQuestDiff(user, []string{
- "IUserMainQuestFlowStatus",
- "IUserMainQuestMainFlowStatus",
- "IUserMainQuestProgressStatus",
- "IUserMainQuestSeasonRoute",
- "IUserPortalCageStatus",
- "IUserSideStoryQuestSceneProgressStatus",
- "IUserQuest",
- "IUserCharacter",
- "IUserCostume",
- "IUserCostumeActiveSkill",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserWeaponNote",
- "IUserCompanion",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
- "IUserParts",
- "IUserPartsGroupNote",
- })
- userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
-
- return &pb.UpdateMainFlowSceneProgressResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.UpdateMainFlowSceneProgressResponse{}, nil
}
func (s *QuestServiceServer) UpdateReplayFlowSceneProgress(ctx context.Context, req *pb.UpdateReplayFlowSceneProgressRequest) (*pb.UpdateReplayFlowSceneProgressResponse, error) {
log.Printf("[QuestService] UpdateReplayFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleReplayFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
})
- return &pb.UpdateReplayFlowSceneProgressResponse{
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserMainQuestFlowStatus",
- "IUserMainQuestReplayFlowStatus",
- }),
- }, nil
+ return &pb.UpdateReplayFlowSceneProgressResponse{}, nil
}
func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, req *pb.UpdateMainQuestSceneProgressRequest) (*pb.UpdateMainQuestSceneProgressResponse, error) {
log.Printf("[QuestService] UpdateMainQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleMainQuestSceneProgress(user, req.QuestSceneId)
})
- return &pb.UpdateMainQuestSceneProgressResponse{
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserStatus",
- "IUserCharacter",
- "IUserQuest",
- "IUserQuestMission",
- "IUserMainQuestFlowStatus",
- "IUserMainQuestMainFlowStatus",
- "IUserMainQuestProgressStatus",
- }),
- }, nil
+ return &pb.UpdateMainQuestSceneProgressResponse{}, nil
}
func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) {
log.Printf("[QuestService] StartMainQuest: %+v", req)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
if req.IsReplayFlow {
s.engine.HandleQuestStartReplay(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
} else {
@@ -132,16 +85,6 @@ func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMa
return &pb.StartMainQuestResponse{
BattleDropReward: pbDrops,
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserStatus",
- "IUserQuest",
- "IUserQuestMission",
- "IUserMainQuestFlowStatus",
- "IUserMainQuestMainFlowStatus",
- "IUserMainQuestProgressStatus",
- "IUserMainQuestSeasonRoute",
- "IUserMainQuestReplayFlowStatus",
- }),
}, nil
}
@@ -165,38 +108,12 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.StorySkipType)
nowMillis := gametime.NowMillis()
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
var outcome questflow.FinishOutcome
- user, _ := 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)
})
- diff := buildSelectedQuestDiff(user, []string{
- "IUserQuest",
- "IUserQuestMission",
- "IUserMainQuestFlowStatus",
- "IUserMainQuestMainFlowStatus",
- "IUserMainQuestProgressStatus",
- "IUserMainQuestSeasonRoute",
- "IUserMainQuestReplayFlowStatus",
- "IUserStatus",
- "IUserGem",
- "IUserCharacter",
- "IUserCostume",
- "IUserCostumeActiveSkill",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserWeaponNote",
- "IUserCompanion",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
- "IUserParts",
- "IUserPartsGroupNote",
- })
- userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
-
return &pb.FinishMainQuestResponse{
DropReward: toProtoRewards(outcome.DropRewards),
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
@@ -207,16 +124,17 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards),
UserStatusCampaignReward: []*pb.QuestReward{},
- DiffUserData: diff,
}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ var deckNumber int32
+ s.users.UpdateUser(userId, func(user *store.UserState) {
s.engine.HandleQuestRestart(user, req.QuestId, gametime.NowMillis())
+ deckNumber = user.Quests[req.QuestId].UserDeckNumber
})
drops := s.engine.BattleDropRewards(req.QuestId)
@@ -231,33 +149,22 @@ func (s *QuestServiceServer) RestartMainQuest(ctx context.Context, req *pb.Resta
return &pb.RestartMainQuestResponse{
BattleDropReward: pbDrops,
- DeckNumber: user.Quests[req.QuestId].UserDeckNumber,
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserStatus",
- "IUserQuest",
- "IUserQuestMission",
- "IUserMainQuestFlowStatus",
- "IUserMainQuestMainFlowStatus",
- "IUserMainQuestProgressStatus",
- "IUserMainQuestSeasonRoute",
- }),
+ DeckNumber: deckNumber,
}, nil
}
func (s *QuestServiceServer) FinishAutoOrbit(ctx context.Context, req *emptypb.Empty) (*pb.FinishAutoOrbitResponse, error) {
log.Printf("[QuestService] FinishAutoOrbit")
- return &pb.FinishAutoOrbitResponse{
- DiffUserData: userdata.EmptyDiff(),
- }, nil
+ return &pb.FinishAutoOrbitResponse{}, nil
}
func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestRequest) (*pb.SkipQuestResponse, error) {
log.Printf("[QuestService] SkipQuest: questId=%d skipCount=%d useEffectItems=%d", req.QuestId, req.SkipCount, len(req.UseEffectItem))
nowMillis := gametime.NowMillis()
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
var outcome questflow.FinishOutcome
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
for _, item := range req.UseEffectItem {
log.Printf("[QuestService] SkipQuest UseEffectItem: consumableItemId=%d count=%d", item.ConsumableItemId, item.Count)
user.ConsumableItems[item.ConsumableItemId] -= item.Count
@@ -271,24 +178,14 @@ func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestReq
return &pb.SkipQuestResponse{
DropReward: toProtoRewards(outcome.DropRewards),
UserStatusCampaignReward: []*pb.QuestReward{},
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserQuest",
- "IUserStatus",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserParts",
- "IUserPartsGroupNote",
- "IUserCharacter",
- "IUserCostume",
- }),
}, nil
}
func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteRequest) (*pb.SetRouteResponse, error) {
log.Printf("[QuestService] SetRoute: mainQuestRouteId=%d", req.MainQuestRouteId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.MainQuest.CurrentMainQuestRouteId = req.MainQuestRouteId
if seasonId, ok := s.engine.SeasonIdByRouteId[req.MainQuestRouteId]; ok {
user.MainQuest.MainQuestSeasonId = seasonId
@@ -298,30 +195,22 @@ func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteReque
user.PortalCageStatus.LatestVersion = now
})
- return &pb.SetRouteResponse{
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserMainQuestSeasonRoute",
- "IUserMainQuestMainFlowStatus",
- "IUserPortalCageStatus",
- }),
- }, nil
+ return &pb.SetRouteResponse{}, nil
}
func (s *QuestServiceServer) SetQuestSceneChoice(ctx context.Context, req *pb.SetQuestSceneChoiceRequest) (*pb.SetQuestSceneChoiceResponse, error) {
log.Printf("[QuestService] SetQuestSceneChoice: questSceneId=%d choiceNumber=%d",
req.QuestSceneId, req.ChoiceNumber)
- return &pb.SetQuestSceneChoiceResponse{
- DiffUserData: userdata.EmptyDiff(),
- }, nil
+ return &pb.SetQuestSceneChoiceResponse{}, nil
}
func (s *QuestServiceServer) ResetLimitContentQuestProgress(ctx context.Context, req *pb.ResetLimitContentQuestProgressRequest) (*pb.ResetLimitContentQuestProgressResponse, error) {
log.Printf("[QuestService] ResetLimitContentQuestProgress: eventQuestChapterId=%d questId=%d",
req.EventQuestChapterId, req.QuestId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
if _, exists := user.SideStoryQuests[req.QuestId]; exists {
user.SideStoryQuests[req.QuestId] = store.SideStoryQuestProgress{
HeadSideStoryQuestSceneId: 0,
@@ -339,20 +228,14 @@ func (s *QuestServiceServer) ResetLimitContentQuestProgress(ctx context.Context,
}
})
- return &pb.ResetLimitContentQuestProgressResponse{
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserSideStoryQuest",
- "IUserSideStoryQuestSceneProgressStatus",
- "IUserQuestLimitContentStatus",
- }),
- }, nil
+ return &pb.ResetLimitContentQuestProgressResponse{}, nil
}
func (s *QuestServiceServer) SetAutoSaleSetting(ctx context.Context, req *pb.SetAutoSaleSettingRequest) (*pb.SetAutoSaleSettingResponse, error) {
log.Printf("[QuestService] SetAutoSaleSetting: items=%d", len(req.AutoSaleSettingItem))
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.AutoSaleSettings = make(map[int32]store.AutoSaleSettingState, len(req.AutoSaleSettingItem))
for itemType, itemValue := range req.AutoSaleSettingItem {
user.AutoSaleSettings[itemType] = store.AutoSaleSettingState{
@@ -362,9 +245,5 @@ func (s *QuestServiceServer) SetAutoSaleSetting(ctx context.Context, req *pb.Set
}
})
- return &pb.SetAutoSaleSettingResponse{
- DiffUserData: buildSelectedQuestDiff(user, []string{
- "IUserAutoSaleSettingDetail",
- }),
- }, nil
+ return &pb.SetAutoSaleSettingResponse{}, nil
}
diff --git a/server/internal/service/quest_sidestory.go b/server/internal/service/quest_sidestory.go
index 366a7a3..f5c711e 100644
--- a/server/internal/service/quest_sidestory.go
+++ b/server/internal/service/quest_sidestory.go
@@ -9,7 +9,6 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type SideStoryQuestServiceServer struct {
@@ -23,19 +22,14 @@ func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.S
return &SideStoryQuestServiceServer{users: users, sessions: sessions, catalog: catalog}
}
-func buildSideStoryDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
- tables := userdata.ProjectTables(user, tableNames)
- return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
-}
-
func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) {
log.Printf("[SideStoryQuestService] MoveSideStoryQuestProgress: sideStoryQuestId=%d", req.SideStoryQuestId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
firstSceneId := s.catalog.FirstSceneByQuestId[req.SideStoryQuestId]
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
var sceneId int32
@@ -58,21 +52,16 @@ func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Con
}
})
- return &pb.MoveSideStoryQuestResponse{
- DiffUserData: buildSideStoryDiff(user, []string{
- "IUserSideStoryQuest",
- "IUserSideStoryQuestSceneProgressStatus",
- }),
- }, nil
+ return &pb.MoveSideStoryQuestResponse{}, nil
}
func (s *SideStoryQuestServiceServer) UpdateSideStoryQuestSceneProgress(ctx context.Context, req *pb.UpdateSideStoryQuestSceneProgressRequest) (*pb.UpdateSideStoryQuestSceneProgressResponse, error) {
log.Printf("[SideStoryQuestService] UpdateSideStoryQuestSceneProgress: sideStoryQuestId=%d sceneId=%d",
req.SideStoryQuestId, req.SideStoryQuestSceneId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId = req.SideStoryQuestSceneId
user.SideStoryActiveProgress.LatestVersion = nowMillis
@@ -84,10 +73,5 @@ func (s *SideStoryQuestServiceServer) UpdateSideStoryQuestSceneProgress(ctx cont
user.SideStoryQuests[req.SideStoryQuestId] = progress
})
- return &pb.UpdateSideStoryQuestSceneProgressResponse{
- DiffUserData: buildSideStoryDiff(user, []string{
- "IUserSideStoryQuest",
- "IUserSideStoryQuestSceneProgressStatus",
- }),
- }, nil
+ return &pb.UpdateSideStoryQuestSceneProgressResponse{}, nil
}
diff --git a/server/internal/service/reward.go b/server/internal/service/reward.go
index 133dbb1..9adc07e 100644
--- a/server/internal/service/reward.go
+++ b/server/internal/service/reward.go
@@ -9,7 +9,6 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
@@ -34,7 +33,7 @@ func NewRewardServiceServer(
func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *emptypb.Empty) (*pb.ReceiveBigHuntRewardResponse, error) {
log.Printf("[RewardService] ReceiveBigHuntReward")
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
weeklyVersion := gametime.WeeklyVersion(nowMillis)
@@ -42,7 +41,7 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
var weeklyRewards []*pb.BigHuntReward
isReceived := false
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
isReceived = ws.IsReceivedWeeklyReward
@@ -106,19 +105,11 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
weeklyScoreResults = []*pb.WeeklyScoreResult{}
}
- tables := userdata.ProjectTables(user, []string{
- "IUserBigHuntWeeklyStatus",
- "IUserBigHuntWeeklyMaxScore",
- "IUserConsumableItem",
- "IUserMaterial",
- })
-
return &pb.ReceiveBigHuntRewardResponse{
WeeklyScoreResult: weeklyScoreResults,
WeeklyScoreReward: weeklyRewards,
IsReceivedWeeklyScoreReward: isReceived,
LastWeekWeeklyScoreReward: []*pb.BigHuntReward{},
- DiffUserData: userdata.BuildDiffFromTables(tables),
}, nil
}
diff --git a/server/internal/service/shop.go b/server/internal/service/shop.go
index ddf2dcd..2ebfed3 100644
--- a/server/internal/service/shop.go
+++ b/server/internal/service/shop.go
@@ -10,30 +10,10 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
"google.golang.org/protobuf/types/known/emptypb"
)
-var shopDiffTables = []string{
- "IUserShopItem",
- "IUserShopReplaceable",
- "IUserShopReplaceableLineup",
- "IUserGem",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
- "IUserPremiumItem",
- "IUserStatus",
- "IUserCostume",
- "IUserCostumeActiveSkill",
- "IUserCharacter",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserWeaponNote",
-}
-
type ShopServiceServer struct {
pb.UnimplementedShopServiceServer
users store.UserRepository
@@ -49,10 +29,10 @@ func NewShopServiceServer(users store.UserRepository, sessions store.SessionRepo
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
for shopItemId, qty := range req.ShopItems {
item, ok := s.catalog.Items[shopItemId]
if !ok {
@@ -88,23 +68,18 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
if err != nil {
return nil, fmt.Errorf("shop buy: %w", err)
}
-
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
- userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds())
-
return &pb.BuyResponse{
OverflowPossession: []*pb.Possession{},
- DiffUserData: diff,
}, nil
}
func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.RefreshRequest) (*pb.RefreshResponse, error) {
log.Printf("[ShopService] RefreshUserData: isGemUsed=%v", req.IsGemUsed)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, 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 {
for i, itemId := range s.catalog.ItemShopPool {
slot := int32(i + 1)
@@ -131,18 +106,13 @@ func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.Refresh
return nil, fmt.Errorf("shop refresh: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
-
- return &pb.RefreshResponse{
- DiffUserData: diff,
- }, nil
+ return &pb.RefreshResponse{}, nil
}
func (s *ShopServiceServer) GetCesaLimit(_ context.Context, _ *emptypb.Empty) (*pb.GetCesaLimitResponse, error) {
log.Printf("[ShopService] GetCesaLimit")
return &pb.GetCesaLimitResponse{
- CesaLimit: []*pb.CesaLimit{},
- DiffUserData: userdata.EmptyDiff(),
+ CesaLimit: []*pb.CesaLimit{},
}, nil
}
@@ -150,10 +120,10 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
log.Printf("[ShopService] CreatePurchaseTransaction: shopId=%d shopItemId=%d productId=%s",
req.ShopId, req.ShopItemId, req.ProductId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, 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]
if !ok {
log.Printf("[ShopService] CreatePurchaseTransaction: unknown shopItemId=%d", req.ShopItemId)
@@ -193,28 +163,22 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
txId := fmt.Sprintf("tx_%d_%d_%d", userId, req.ShopItemId, nowMillis)
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
-
return &pb.CreatePurchaseTransactionResponse{
PurchaseTransactionId: txId,
- DiffUserData: diff,
}, nil
}
func (s *ShopServiceServer) PurchaseGooglePlayStoreProduct(ctx context.Context, req *pb.PurchaseGooglePlayStoreProductRequest) (*pb.PurchaseGooglePlayStoreProductResponse, error) {
log.Printf("[ShopService] PurchaseGooglePlayStoreProduct: txId=%s", req.PurchaseTransactionId)
- userId := currentUserId(ctx, s.users, s.sessions)
- snapshot, err := s.users.LoadUser(userId)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ _, err := s.users.LoadUser(userId)
if err != nil {
return nil, fmt.Errorf("purchase google play: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
-
return &pb.PurchaseGooglePlayStoreProductResponse{
OverflowPossession: []*pb.Possession{},
- DiffUserData: diff,
}, nil
}
diff --git a/server/internal/service/state.go b/server/internal/service/state.go
index 47b1c07..0bb2f01 100644
--- a/server/internal/service/state.go
+++ b/server/internal/service/state.go
@@ -8,43 +8,7 @@ import (
"google.golang.org/grpc/metadata"
)
-var startedGameStartTables = []string{
- "IUserProfile",
- "IUserCharacter",
- "IUserCostume",
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserCompanion",
- "IUserDeckCharacter",
- "IUserDeck",
- "IUserGem",
- "IUserMission",
- "IUserMainQuestFlowStatus",
- "IUserMainQuestMainFlowStatus",
- "IUserMainQuestProgressStatus",
- "IUserMainQuestSeasonRoute",
- "IUserQuest",
- "IUserQuestMission",
- "IUserTutorialProgress",
- "IUserWeaponNote",
- "IUserCostumeActiveSkill",
- "IUserDeckTypeNote",
- "IUserDeckSubWeaponGroup",
- "IUserDeckPartsGroup",
- "IUserConsumableItem",
- "IUserMaterial",
- "IUserImportantItem",
-}
-
-var gimmickDiffTables = []string{
- "IUserGimmick",
- "IUserGimmickOrnamentProgress",
- "IUserGimmickSequence",
- "IUserGimmickUnlock",
-}
-
-func currentUserId(ctx context.Context, users store.UserRepository, sessions store.SessionRepository) int64 {
+func CurrentUserId(ctx context.Context, users store.UserRepository, sessions store.SessionRepository) int64 {
if md, ok := metadata.FromIncomingContext(ctx); ok {
if vals := md.Get("x-apb-session-key"); len(vals) > 0 {
if userId, err := sessions.ResolveUserId(vals[0]); err == nil {
diff --git a/server/internal/service/tutorial.go b/server/internal/service/tutorial.go
index 4f72592..e0a0a61 100644
--- a/server/internal/service/tutorial.go
+++ b/server/internal/service/tutorial.go
@@ -9,7 +9,6 @@ import (
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
type TutorialServiceServer struct {
@@ -25,10 +24,10 @@ func NewTutorialServiceServer(users store.UserRepository, sessions store.Session
func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb.SetTutorialProgressRequest) (*pb.SetTutorialProgressResponse, error) {
log.Printf("[TutorialService] SetTutorialProgress: type=%d phase=%d choice=%d", req.TutorialType, req.ProgressPhase, req.ChoiceId)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
var grants []questflow.RewardGrant
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
existing, exists := user.Tutorials[req.TutorialType]
if !exists || req.ProgressPhase >= existing.ProgressPhase {
user.Tutorials[req.TutorialType] = store.TutorialProgressState{
@@ -42,22 +41,7 @@ func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb
store.EnsureDefaultDeck(user, nowMillis)
}
})
- tables := []string{"IUserTutorialProgress"}
- if req.TutorialType == int32(model.TutorialTypeMenuFirst) ||
- req.TutorialType == int32(model.TutorialTypeMenuSecond) {
- tables = append(tables,
- "IUserCharacter", "IUserCostume", "IUserWeapon",
- "IUserWeaponSkill", "IUserWeaponAbility",
- "IUserCompanion", "IUserDeckCharacter", "IUserDeck",
- )
- }
- if len(grants) > 0 {
- tables = append(tables, "IUserCompanion")
- }
- result := userdata.ProjectTables(user, tables)
- for _, t := range tables {
- log.Printf("[TutorialService] DiffTable %s -> %s", t, result[t])
- }
+
rewards := make([]*pb.TutorialChoiceReward, len(grants))
for i, g := range grants {
rewards[i] = &pb.TutorialChoiceReward{
@@ -68,14 +52,13 @@ func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb
}
return &pb.SetTutorialProgressResponse{
TutorialChoiceReward: rewards,
- DiffUserData: userdata.BuildDiffFromTables(result),
}, nil
}
func (s *TutorialServiceServer) SetTutorialProgressAndReplaceDeck(ctx context.Context, req *pb.SetTutorialProgressAndReplaceDeckRequest) (*pb.SetTutorialProgressAndReplaceDeckResponse, error) {
log.Printf("[TutorialService] SetTutorialProgressAndReplaceDeck: type=%d phase=%d deckType=%d deckNumber=%d", req.TutorialType, req.ProgressPhase, req.DeckType, req.UserDeckNumber)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
existing, exists := user.Tutorials[req.TutorialType]
if !exists || req.ProgressPhase >= existing.ProgressPhase {
user.Tutorials[req.TutorialType] = store.TutorialProgressState{
@@ -87,12 +70,5 @@ func (s *TutorialServiceServer) SetTutorialProgressAndReplaceDeck(ctx context.Co
store.ApplyDeckReplacement(user, model.DeckType(req.DeckType), req.UserDeckNumber, deckSlotsFromProto(req.Deck), gametime.NowMillis())
}
})
- return &pb.SetTutorialProgressAndReplaceDeckResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{
- "IUserTutorialProgress",
- "IUserDeck",
- "IUserDeckCharacter",
- "IUserDeckSubWeaponGroup",
- })),
- }, nil
+ return &pb.SetTutorialProgressAndReplaceDeckResponse{}, nil
}
diff --git a/server/internal/service/user.go b/server/internal/service/user.go
index d322211..d41a3b5 100644
--- a/server/internal/service/user.go
+++ b/server/internal/service/user.go
@@ -2,73 +2,56 @@ package service
import (
"context"
+ "encoding/json"
"fmt"
"log"
- "sort"
+ "net/http"
+ "strconv"
+ "strings"
"time"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/status"
+ "google.golang.org/protobuf/types/known/emptypb"
+ "google.golang.org/protobuf/types/known/timestamppb"
pb "lunar-tear/server/gen/proto"
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
-
- "google.golang.org/grpc"
- "google.golang.org/grpc/metadata"
- "google.golang.org/protobuf/types/known/emptypb"
- "google.golang.org/protobuf/types/known/timestamppb"
)
type UserServiceServer struct {
pb.UnimplementedUserServiceServer
users store.UserRepository
sessions store.SessionRepository
+ authURL string
}
-func NewUserServiceServer(users store.UserRepository, sessions store.SessionRepository) *UserServiceServer {
- return &UserServiceServer{users: users, sessions: sessions}
-}
-
-func setCommonResponseTrailers(ctx context.Context, diff map[string]*pb.DiffData, includeUpdateNames bool) {
- keys := make([]string, 0, len(diff))
- for key := range diff {
- keys = append(keys, key)
- }
- sort.Strings(keys)
-
- var pairs []string
- if includeUpdateNames && len(keys) > 0 {
- pairs = append(pairs, "x-apb-update-user-data-names", keys[0])
- for _, key := range keys[1:] {
- pairs[len(pairs)-1] += "," + key
- }
- }
-
- if err := grpc.SetTrailer(ctx, metadata.Pairs(pairs...)); err != nil {
- log.Printf("[UserService] failed to set trailers: %v", err)
+func NewUserServiceServer(users store.UserRepository, sessions store.SessionRepository, authURL string) *UserServiceServer {
+ if authURL != "" && !strings.Contains(authURL, "://") {
+ authURL = "http://" + authURL
}
+ return &UserServiceServer{users: users, sessions: sessions, authURL: authURL}
}
func (s *UserServiceServer) RegisterUser(ctx context.Context, req *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) {
- userId, err := s.users.CreateUser(req.Uuid)
+ platform := model.ClientPlatformFromContext(ctx)
+ userId, err := s.users.CreateUser(req.Uuid, platform)
if err != nil {
return nil, fmt.Errorf("create user: %w", err)
}
- user, err := s.users.LoadUser(userId)
- if err != nil {
- return nil, fmt.Errorf("load user: %w", err)
- }
- log.Printf("[UserService] RegisterUser: uuid=%s terminalId=%s -> userId=%d", req.Uuid, req.TerminalId, user.UserId)
+ log.Printf("[UserService] RegisterUser: uuid=%s terminalId=%s platform=%s -> userId=%d", req.Uuid, req.TerminalId, platform, userId)
return &pb.RegisterUserResponse{
- UserId: user.UserId,
- Signature: fmt.Sprintf("sig_%d_%d", user.UserId, gametime.Now().Unix()),
- DiffUserData: userdata.BuildDiffFromTables(userdata.FirstEntranceClientTableMap(user)),
+ UserId: userId,
+ Signature: fmt.Sprintf("sig_%d_%d", userId, gametime.Now().Unix()),
}, nil
}
func (s *UserServiceServer) Auth(ctx context.Context, req *pb.AuthUserRequest) (*pb.AuthUserResponse, error) {
- log.Printf("[UserService] Auth: uuid=%s", req.Uuid)
+ platform := model.ClientPlatformFromContext(ctx)
+ log.Printf("[UserService] Auth: uuid=%s platform=%s", req.Uuid, platform)
session, err := s.sessions.CreateSession(req.Uuid, 24*time.Hour)
if err != nil {
@@ -84,7 +67,6 @@ func (s *UserServiceServer) Auth(ctx context.Context, req *pb.AuthUserRequest) (
ExpireDatetime: timestamppb.New(session.ExpireAt),
Signature: req.Signature,
UserId: user.UserId,
- DiffUserData: userdata.BuildDiffFromTables(userdata.FirstEntranceClientTableMap(user)),
}, nil
}
@@ -97,81 +79,69 @@ func (s *UserServiceServer) GameStart(ctx context.Context, _ *emptypb.Empty) (*p
}
}
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.GameStartDatetime = gametime.NowMillis()
})
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user, startedGameStartTables))
- setCommonResponseTrailers(ctx, diff, true)
- return &pb.GameStartResponse{
- // Apply only the starter outgame rows we need after title completion.
- // Keep IUser and other risky core-account rows out of GameStart diff.
- DiffUserData: diff,
- }, nil
+ return &pb.GameStartResponse{}, nil
}
func (s *UserServiceServer) TransferUser(ctx context.Context, req *pb.TransferUserRequest) (*pb.TransferUserResponse, error) {
- log.Printf("[UserService] TransferUser")
- userId, err := s.users.CreateUser(req.Uuid)
+ platform := model.ClientPlatformFromContext(ctx)
+ log.Printf("[UserService] TransferUser: platform=%s", platform)
+ userId, err := s.users.CreateUser(req.Uuid, platform)
if err != nil {
return nil, fmt.Errorf("create user: %w", err)
}
return &pb.TransferUserResponse{
- UserId: userId,
- Signature: "transferred-sig",
- DiffUserData: userdata.EmptyDiff(),
+ UserId: userId,
+ Signature: "transferred-sig",
}, nil
}
func (s *UserServiceServer) SetUserName(ctx context.Context, req *pb.SetUserNameRequest) (*pb.SetUserNameResponse, error) {
log.Printf("[UserService] SetUserName: %s", req.Name)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
nowMillis := gametime.NowMillis()
user.Profile.Name = req.Name
user.Profile.NameUpdateDatetime = nowMillis
})
- return &pb.SetUserNameResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})),
- }, nil
+ return &pb.SetUserNameResponse{}, nil
}
func (s *UserServiceServer) SetUserMessage(ctx context.Context, req *pb.SetUserMessageRequest) (*pb.SetUserMessageResponse, error) {
log.Printf("[UserService] SetUserMessage: %s", req.Message)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
nowMillis := gametime.NowMillis()
user.Profile.Message = req.Message
user.Profile.MessageUpdateDatetime = nowMillis
})
- return &pb.SetUserMessageResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})),
- }, nil
+ return &pb.SetUserMessageResponse{}, nil
}
func (s *UserServiceServer) SetUserFavoriteCostumeId(ctx context.Context, req *pb.SetUserFavoriteCostumeIdRequest) (*pb.SetUserFavoriteCostumeIdResponse, error) {
log.Printf("[UserService] SetUserFavoriteCostumeId: %d", req.FavoriteCostumeId)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
nowMillis := gametime.NowMillis()
user.Profile.FavoriteCostumeId = req.FavoriteCostumeId
user.Profile.FavoriteCostumeIdUpdateDatetime = nowMillis
})
- return &pb.SetUserFavoriteCostumeIdResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})),
- }, nil
+ return &pb.SetUserFavoriteCostumeIdResponse{}, nil
}
func (s *UserServiceServer) GetUserProfile(ctx context.Context, req *pb.GetUserProfileRequest) (*pb.GetUserProfileResponse, error) {
log.Printf("[UserService] GetUserProfile: playerId=%d", req.PlayerId)
userId := req.PlayerId
if userId == 0 {
- userId = currentUserId(ctx, s.users, s.sessions)
+ userId = CurrentUserId(ctx, s.users, s.sessions)
}
user, err := s.users.LoadUser(userId)
if err != nil {
- return &pb.GetUserProfileResponse{DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetUserProfileResponse{}, nil
}
deckCharacters := []*pb.ProfileDeckCharacter{}
@@ -210,66 +180,148 @@ func (s *UserServiceServer) GetUserProfile(ctx context.Context, req *pb.GetUserP
HistoryItem: []*pb.PlayHistoryItem{},
HistoryCategoryGraphItem: []*pb.PlayHistoryCategoryGraphItem{},
},
- DiffUserData: userdata.EmptyDiff(),
}, nil
}
func (s *UserServiceServer) SetBirthYearMonth(ctx context.Context, req *pb.SetBirthYearMonthRequest) (*pb.SetBirthYearMonthResponse, error) {
log.Printf("[UserService] SetBirthYearMonth: %d/%d", req.BirthYear, req.BirthMonth)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
_, _ = s.users.UpdateUser(userId, func(user *store.UserState) {
user.BirthYear = req.BirthYear
user.BirthMonth = req.BirthMonth
})
- return &pb.SetBirthYearMonthResponse{DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.SetBirthYearMonthResponse{}, nil
}
func (s *UserServiceServer) GetBirthYearMonth(ctx context.Context, _ *emptypb.Empty) (*pb.GetBirthYearMonthResponse, error) {
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
- return &pb.GetBirthYearMonthResponse{BirthYear: 2000, BirthMonth: 1, DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetBirthYearMonthResponse{BirthYear: 2000, BirthMonth: 1}, nil
}
- return &pb.GetBirthYearMonthResponse{BirthYear: user.BirthYear, BirthMonth: user.BirthMonth, DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetBirthYearMonthResponse{BirthYear: user.BirthYear, BirthMonth: user.BirthMonth}, nil
}
func (s *UserServiceServer) GetChargeMoney(ctx context.Context, _ *emptypb.Empty) (*pb.GetChargeMoneyResponse, error) {
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
- return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: 0, DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: 0}, nil
}
- return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: user.ChargeMoneyThisMonth, DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: user.ChargeMoneyThisMonth}, nil
}
func (s *UserServiceServer) SetUserSetting(ctx context.Context, req *pb.SetUserSettingRequest) (*pb.SetUserSettingResponse, error) {
log.Printf("[UserService] SetUserSetting: isNotifyPurchaseAlert=%v", req.IsNotifyPurchaseAlert)
- userId := currentUserId(ctx, s.users, s.sessions)
- user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ s.users.UpdateUser(userId, func(user *store.UserState) {
user.Setting.IsNotifyPurchaseAlert = req.IsNotifyPurchaseAlert
})
- return &pb.SetUserSettingResponse{
- DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserSetting"})),
- }, nil
+ return &pb.SetUserSettingResponse{}, nil
}
func (s *UserServiceServer) GetAndroidArgs(ctx context.Context, req *pb.GetAndroidArgsRequest) (*pb.GetAndroidArgsResponse, error) {
- return &pb.GetAndroidArgsResponse{Nonce: "Mama", ApiKey: "1234567890", DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetAndroidArgsResponse{Nonce: "Mama", ApiKey: "1234567890"}, nil
}
func (s *UserServiceServer) GetBackupToken(ctx context.Context, req *pb.GetBackupTokenRequest) (*pb.GetBackupTokenResponse, error) {
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
user, err := s.users.LoadUser(userId)
if err != nil {
- return &pb.GetBackupTokenResponse{BackupToken: "mock-backup-token", DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetBackupTokenResponse{BackupToken: "mock-backup-token"}, nil
}
- return &pb.GetBackupTokenResponse{BackupToken: user.BackupToken, DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetBackupTokenResponse{BackupToken: user.BackupToken}, nil
}
func (s *UserServiceServer) CheckTransferSetting(ctx context.Context, _ *emptypb.Empty) (*pb.CheckTransferSettingResponse, error) {
- return &pb.CheckTransferSettingResponse{DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.CheckTransferSettingResponse{}, nil
}
func (s *UserServiceServer) GetUserGamePlayNote(ctx context.Context, req *pb.GetUserGamePlayNoteRequest) (*pb.GetUserGamePlayNoteResponse, error) {
- return &pb.GetUserGamePlayNoteResponse{DiffUserData: userdata.EmptyDiff()}, nil
+ return &pb.GetUserGamePlayNoteResponse{}, nil
+}
+
+func (s *UserServiceServer) resolveAuthToken(token string) (facebookId int64, err error) {
+ if s.authURL == "" {
+ return 0, status.Error(codes.FailedPrecondition, "auth server not configured (--auth-url)")
+ }
+
+ resp, err := http.Get(s.authURL + "/me?access_token=" + token)
+ if err != nil {
+ return 0, status.Errorf(codes.Internal, "auth server unreachable: %v", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return 0, status.Error(codes.Unauthenticated, "invalid or expired token")
+ }
+
+ var body struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
+ return 0, status.Errorf(codes.Internal, "decode auth response: %v", err)
+ }
+ if body.ID == "" {
+ return 0, status.Error(codes.Unauthenticated, "auth server returned empty id")
+ }
+
+ id, err := strconv.ParseInt(body.ID, 10, 64)
+ if err != nil {
+ return 0, status.Errorf(codes.Internal, "invalid auth id %q: %v", body.ID, err)
+ }
+ return id, nil
+}
+
+func (s *UserServiceServer) SetFacebookAccount(ctx context.Context, req *pb.SetFacebookAccountRequest) (*pb.SetFacebookAccountResponse, error) {
+ log.Printf("[UserService] SetFacebookAccount")
+
+ fbId, err := s.resolveAuthToken(req.Token)
+ if err != nil {
+ return nil, err
+ }
+
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ if err := s.users.SetFacebookId(userId, fbId); err != nil {
+ return nil, fmt.Errorf("set facebook id: %w", err)
+ }
+ log.Printf("[UserService] linked facebook_id=%d to user_id=%d", fbId, userId)
+ return &pb.SetFacebookAccountResponse{}, nil
+}
+
+func (s *UserServiceServer) UnsetFacebookAccount(ctx context.Context, _ *emptypb.Empty) (*pb.UnsetFacebookAccountResponse, error) {
+ log.Printf("[UserService] UnsetFacebookAccount")
+
+ userId := CurrentUserId(ctx, s.users, s.sessions)
+ if err := s.users.ClearFacebookId(userId); err != nil {
+ return nil, fmt.Errorf("clear facebook id: %w", err)
+ }
+ log.Printf("[UserService] unlinked facebook from user_id=%d", userId)
+ return &pb.UnsetFacebookAccountResponse{}, nil
+}
+
+func (s *UserServiceServer) TransferUserByFacebook(ctx context.Context, req *pb.TransferUserByFacebookRequest) (*pb.TransferUserByFacebookResponse, error) {
+ log.Printf("[UserService] TransferUserByFacebook: uuid=%s", req.Uuid)
+
+ fbId, err := s.resolveAuthToken(req.Token)
+ if err != nil {
+ return nil, err
+ }
+
+ userId, err := s.users.GetUserByFacebookId(fbId)
+ if err != nil {
+ return nil, status.Error(codes.NotFound, "no account linked to this login")
+ }
+
+ if err := s.users.UpdateUUID(userId, req.Uuid); err != nil {
+ return nil, fmt.Errorf("update uuid: %w", err)
+ }
+
+ log.Printf("[UserService] transferred facebook_id=%d -> user_id=%d with new uuid=%s", fbId, userId, req.Uuid)
+
+ return &pb.TransferUserByFacebookResponse{
+ UserId: userId,
+ Signature: fmt.Sprintf("fb_transfer_%d_%d", userId, gametime.Now().Unix()),
+ }, nil
}
diff --git a/server/internal/service/weapon.go b/server/internal/service/weapon.go
index 14351e2..48427bc 100644
--- a/server/internal/service/weapon.go
+++ b/server/internal/service/weapon.go
@@ -11,35 +11,8 @@ import (
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
- "lunar-tear/server/internal/userdata"
)
-var weaponDiffTables = []string{
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserWeaponAwaken",
- "IUserMaterial",
- "IUserConsumableItem",
-}
-
-var limitBreakDiffTables = []string{
- "IUserWeapon",
- "IUserWeaponSkill",
- "IUserWeaponAbility",
- "IUserWeaponAwaken",
- "IUserMaterial",
- "IUserConsumableItem",
- "IUserWeaponNote",
-}
-
-var weaponAwakenDiffTables = []string{
- "IUserWeapon",
- "IUserWeaponAwaken",
- "IUserMaterial",
- "IUserConsumableItem",
-}
-
type WeaponServiceServer struct {
pb.UnimplementedWeaponServiceServer
users store.UserRepository
@@ -55,10 +28,10 @@ func NewWeaponServiceServer(users store.UserRepository, sessions store.SessionRe
func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectRequest) (*pb.ProtectResponse, error) {
log.Printf("[WeaponService] Protect: uuids=%v", req.UserWeaponUuid)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
for _, uuid := range req.UserWeaponUuid {
weapon, ok := user.Weapons[uuid]
if !ok {
@@ -71,17 +44,16 @@ func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectReques
}
})
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"}))
- return &pb.ProtectResponse{DiffUserData: diff}, nil
+ return &pb.ProtectResponse{}, nil
}
func (s *WeaponServiceServer) Unprotect(ctx context.Context, req *pb.UnprotectRequest) (*pb.UnprotectResponse, error) {
log.Printf("[WeaponService] Unprotect: uuids=%v", req.UserWeaponUuid)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
+ s.users.UpdateUser(userId, func(user *store.UserState) {
for _, uuid := range req.UserWeaponUuid {
weapon, ok := user.Weapons[uuid]
if !ok {
@@ -94,18 +66,16 @@ func (s *WeaponServiceServer) Unprotect(ctx context.Context, req *pb.UnprotectRe
}
})
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"}))
- return &pb.UnprotectResponse{DiffUserData: diff}, nil
+ return &pb.UnprotectResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- var changedStoryIds []int32
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceByMaterial: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -157,35 +127,24 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
user.Weapons[req.UserWeaponUuid] = weapon
log.Printf("[WeaponService] EnhanceByMaterial: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level)
- changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
+ s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
})
if err != nil {
return nil, fmt.Errorf("weapon enhance by material: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
- userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
-
return &pb.EnhanceByMaterialResponse{
IsGreatSuccess: false,
SurplusEnhanceMaterial: map[int32]int32{},
- DiffUserData: diff,
}, nil
}
func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*pb.SellResponse, error) {
log.Printf("[WeaponService] Sell: uuids=%v", req.UserWeaponUuid)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
- Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
- Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
- Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
totalGold := int32(0)
for _, uuid := range req.UserWeaponUuid {
weapon, ok := user.Weapons[uuid]
@@ -225,21 +184,16 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
return nil, fmt.Errorf("weapon sell: %w", err)
}
- sellDiffTables := []string{"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeaponAwaken", "IUserConsumableItem"}
- tables := userdata.ProjectTables(snapshot, sellDiffTables)
- diff := tracker.Apply(snapshot, tables)
-
- return &pb.SellResponse{DiffUserData: diff}, nil
+ return &pb.SellResponse{}, nil
}
func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest) (*pb.EvolveResponse, error) {
log.Printf("[WeaponService] Evolve: uuid=%s", req.UserWeaponUuid)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- var changedStoryIds []int32
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] Evolve: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -298,25 +252,22 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
log.Printf("[WeaponService] Evolve: weaponId %d -> %d", wm.WeaponId, evolvedId)
- changedStoryIds = s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis)
+ s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis)
})
if err != nil {
return nil, fmt.Errorf("weapon evolve: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
- userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
-
- return &pb.EvolveResponse{DiffUserData: diff}, nil
+ return &pb.EvolveResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceSkill: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -330,7 +281,7 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
}
groupRows := s.catalog.SkillGroupsByGroupId[wm.WeaponSkillGroupId]
- var skillGroup *masterdata.WeaponSkillGroupRow
+ var skillGroup *masterdata.EntityMWeaponSkillGroup
for i := range groupRows {
if groupRows[i].SkillId == req.SkillId {
skillGroup = &groupRows[i]
@@ -403,18 +354,16 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
return nil, fmt.Errorf("weapon enhance skill: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
-
- return &pb.EnhanceSkillResponse{DiffUserData: diff}, nil
+ return &pb.EnhanceSkillResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceAbility: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -428,7 +377,7 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
}
groupRows := s.catalog.AbilityGroupsByGroupId[wm.WeaponAbilityGroupId]
- var abilityGroup *masterdata.WeaponAbilityGroupRow
+ var abilityGroup *masterdata.EntityMWeaponAbilityGroup
for i := range groupRows {
if groupRows[i].AbilityId == req.AbilityId {
abilityGroup = &groupRows[i]
@@ -501,18 +450,16 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
return nil, fmt.Errorf("weapon enhance ability: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
-
- return &pb.EnhanceAbilityResponse{DiffUserData: diff}, nil
+ return &pb.EnhanceAbilityResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] LimitBreakByMaterial: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -572,25 +519,16 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
return nil, fmt.Errorf("weapon limit break by material: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, limitBreakDiffTables))
-
- return &pb.LimitBreakByMaterialResponse{DiffUserData: diff}, nil
+ return &pb.LimitBreakByMaterialResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
- Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
- Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
- Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"})
-
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] LimitBreakByWeapon: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -658,27 +596,16 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
return nil, fmt.Errorf("weapon limit break by weapon: %w", err)
}
- tables := userdata.ProjectTables(snapshot, limitBreakDiffTables)
- diff := tracker.Apply(snapshot, tables)
-
- return &pb.LimitBreakByWeaponResponse{DiffUserData: diff}, nil
+ return &pb.LimitBreakByWeaponResponse{}, nil
}
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)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- oldUser, _ := s.users.LoadUser(userId)
- tracker := userdata.NewDeleteTracker().
- Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
- Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
- Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
- Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"})
-
- var changedStoryIds []int32
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] EnhanceByWeapon: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -740,77 +667,63 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
user.Weapons[req.UserWeaponUuid] = weapon
log.Printf("[WeaponService] EnhanceByWeapon: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level)
- changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
+ s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis)
})
if err != nil {
return nil, fmt.Errorf("weapon enhance by weapon: %w", err)
}
- tables := userdata.ProjectTables(snapshot, weaponDiffTables)
- diff := tracker.Apply(snapshot, tables)
- userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
-
return &pb.EnhanceByWeaponResponse{
IsGreatSuccess: false,
SurplusEnhanceWeapon: []string{},
- DiffUserData: diff,
}, nil
}
-func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) []int32 {
+func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) {
wm, ok := s.catalog.Weapons[weaponId]
if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 {
- return nil
+ return
}
evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId]
conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId]
- changed := false
for _, cond := range conditions {
- granted := false
- switch cond.WeaponStoryReleaseConditionType {
+ switch model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) {
case model.WeaponStoryReleaseConditionTypeAcquisition:
- granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
+ store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
case model.WeaponStoryReleaseConditionTypeReachSpecifiedLevel:
if level >= cond.ConditionValue {
- granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
+ store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
case model.WeaponStoryReleaseConditionTypeReachInitialMaxLevel:
if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
if level >= maxFunc.Evaluate(0) {
- granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
+ store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
}
case model.WeaponStoryReleaseConditionTypeReachOnceEvolvedMaxLevel:
if hasEvo && evoOrder >= 1 {
if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok {
if level >= maxFunc.Evaluate(0) {
- granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
+ store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
}
}
case model.WeaponStoryReleaseConditionTypeReachSpecifiedEvolutionCount:
if hasEvo && evoOrder >= cond.ConditionValue {
- granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
+ store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis)
}
}
- if granted {
- changed = true
- }
}
- if changed {
- return []int32{weaponId}
- }
- return nil
}
func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRequest) (*pb.WeaponAwakenResponse, error) {
log.Printf("[WeaponService] Awaken: uuid=%s", req.UserWeaponUuid)
- userId := currentUserId(ctx, s.users, s.sessions)
+ userId := CurrentUserId(ctx, s.users, s.sessions)
nowMillis := gametime.NowMillis()
- snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
+ _, err := s.users.UpdateUser(userId, func(user *store.UserState) {
weapon, ok := user.Weapons[req.UserWeaponUuid]
if !ok {
log.Printf("[WeaponService] Awaken: weapon uuid=%s not found", req.UserWeaponUuid)
@@ -857,7 +770,5 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
return nil, fmt.Errorf("weapon awaken: %w", err)
}
- diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponAwakenDiffTables))
-
- return &pb.WeaponAwakenResponse{DiffUserData: diff}, nil
+ return &pb.WeaponAwakenResponse{}, nil
}
diff --git a/server/internal/store/seed.go b/server/internal/store/seed.go
index 4d996e4..4e61ca4 100644
--- a/server/internal/store/seed.go
+++ b/server/internal/store/seed.go
@@ -16,13 +16,13 @@ const (
defaultChargeMoneyThisMonth = int64(0)
)
-func SeedUserState(userId int64, uuid string, nowMillis int64) *UserState {
+func SeedUserState(userId int64, uuid string, nowMillis int64, platform model.ClientPlatform) *UserState {
user := &UserState{
UserId: userId,
Uuid: uuid,
PlayerId: userId,
- OsType: 2,
- PlatformType: 2,
+ OsType: platform.OsType,
+ PlatformType: platform.PlatformType,
UserRestrictionType: 0,
RegisterDatetime: nowMillis,
GameStartDatetime: nowMillis,
diff --git a/server/internal/store/sqlite/load.go b/server/internal/store/sqlite/load.go
index 380022b..839d8bb 100644
--- a/server/internal/store/sqlite/load.go
+++ b/server/internal/store/sqlite/load.go
@@ -10,19 +10,21 @@ import (
func (s *SQLiteStore) LoadUser(userId int64) (store.UserState, error) {
var u store.UserState
+ var fbId sql.NullInt64
err := s.db.QueryRow(`SELECT user_id, uuid, player_id, os_type, platform_type, user_restriction_type,
register_datetime, game_start_datetime, latest_version, birth_year, birth_month,
- backup_token, charge_money_this_month FROM users WHERE user_id = ?`, userId).Scan(
+ backup_token, charge_money_this_month, facebook_id FROM users WHERE user_id = ?`, userId).Scan(
&u.UserId, &u.Uuid, &u.PlayerId, &u.OsType, &u.PlatformType, &u.UserRestrictionType,
&u.RegisterDatetime, &u.GameStartDatetime, &u.LatestVersion, &u.BirthYear, &u.BirthMonth,
- &u.BackupToken, &u.ChargeMoneyThisMonth)
+ &u.BackupToken, &u.ChargeMoneyThisMonth, &fbId)
if err == sql.ErrNoRows {
return u, store.ErrNotFound
}
if err != nil {
return u, fmt.Errorf("load users: %w", err)
}
+ u.FacebookId = fbId.Int64
initMaps(&u)
diff --git a/server/internal/store/sqlite/user.go b/server/internal/store/sqlite/user.go
index e45d4b2..5bf8217 100644
--- a/server/internal/store/sqlite/user.go
+++ b/server/internal/store/sqlite/user.go
@@ -4,10 +4,11 @@ import (
"database/sql"
"fmt"
+ "lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
)
-func (s *SQLiteStore) CreateUser(uuid string) (int64, error) {
+func (s *SQLiteStore) CreateUser(uuid string, platform model.ClientPlatform) (int64, error) {
tx, err := s.db.Begin()
if err != nil {
return 0, fmt.Errorf("begin tx: %w", err)
@@ -24,8 +25,8 @@ func (s *SQLiteStore) CreateUser(uuid string) (int64, error) {
res, err := tx.Exec(`INSERT INTO users (uuid, player_id, os_type, platform_type, user_restriction_type,
register_datetime, game_start_datetime, latest_version, birth_year, birth_month,
- backup_token, charge_money_this_month) VALUES (?, 0, 2, 2, 0, ?, ?, 0, 2000, 1, 'mock-backup-token', 0)`,
- uuid, nowMillis, nowMillis)
+ backup_token, charge_money_this_month) VALUES (?, 0, ?, ?, 0, ?, ?, 0, 2000, 1, 'mock-backup-token', 0)`,
+ uuid, platform.OsType, platform.PlatformType, nowMillis, nowMillis)
if err != nil {
return 0, fmt.Errorf("insert user: %w", err)
}
@@ -39,7 +40,7 @@ func (s *SQLiteStore) CreateUser(uuid string) (int64, error) {
return 0, fmt.Errorf("update player_id: %w", err)
}
- user := store.SeedUserState(userId, uuid, nowMillis)
+ user := store.SeedUserState(userId, uuid, nowMillis, platform)
if err := writeUserState(tx, userId, user); err != nil {
return 0, fmt.Errorf("write seed state: %w", err)
}
@@ -188,6 +189,52 @@ func (s *SQLiteStore) ImportUser(u *store.UserState) error {
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit: %w", err)
}
+
+ return nil
+}
+
+func (s *SQLiteStore) SetFacebookId(userId int64, facebookId int64) error {
+ _, err := s.db.Exec(`UPDATE users SET facebook_id = ? WHERE user_id = ?`, facebookId, userId)
+ if err != nil {
+ return fmt.Errorf("set facebook_id: %w", err)
+ }
+ return nil
+}
+
+func (s *SQLiteStore) GetUserByFacebookId(facebookId int64) (int64, error) {
+ var userId int64
+ err := s.db.QueryRow(`SELECT user_id FROM users WHERE facebook_id = ?`, facebookId).Scan(&userId)
+ if err == sql.ErrNoRows {
+ return 0, store.ErrNotFound
+ }
+ if err != nil {
+ return 0, fmt.Errorf("query user by facebook_id: %w", err)
+ }
+ return userId, nil
+}
+
+func (s *SQLiteStore) GetFacebookId(userId int64) (int64, error) {
+ var fbId sql.NullInt64
+ err := s.db.QueryRow(`SELECT facebook_id FROM users WHERE user_id = ?`, userId).Scan(&fbId)
+ if err != nil {
+ return 0, store.ErrNotFound
+ }
+ return fbId.Int64, nil
+}
+
+func (s *SQLiteStore) ClearFacebookId(userId int64) error {
+ _, err := s.db.Exec(`UPDATE users SET facebook_id = NULL WHERE user_id = ?`, userId)
+ if err != nil {
+ return fmt.Errorf("clear facebook_id: %w", err)
+ }
+ return nil
+}
+
+func (s *SQLiteStore) UpdateUUID(userId int64, newUuid string) error {
+ _, err := s.db.Exec(`UPDATE users SET uuid = ? WHERE user_id = ?`, newUuid, userId)
+ if err != nil {
+ return fmt.Errorf("update uuid: %w", err)
+ }
return nil
}
diff --git a/server/internal/store/store.go b/server/internal/store/store.go
index f75db72..32ea1ac 100644
--- a/server/internal/store/store.go
+++ b/server/internal/store/store.go
@@ -3,6 +3,8 @@ package store
import (
"errors"
"time"
+
+ "lunar-tear/server/internal/model"
)
var ErrNotFound = errors.New("store: not found")
@@ -10,11 +12,16 @@ var ErrNotFound = errors.New("store: not found")
type Clock func() time.Time
type UserRepository interface {
- CreateUser(uuid string) (int64, error)
+ CreateUser(uuid string, platform model.ClientPlatform) (int64, error)
GetUserByUUID(uuid string) (int64, error)
LoadUser(userId int64) (UserState, error)
UpdateUser(userId int64, mutate func(*UserState)) (UserState, error)
DefaultUserId() (int64, error)
+ SetFacebookId(userId int64, facebookId int64) error
+ GetUserByFacebookId(facebookId int64) (int64, error)
+ GetFacebookId(userId int64) (int64, error)
+ ClearFacebookId(userId int64) error
+ UpdateUUID(userId int64, newUuid string) error
}
type SessionRepository interface {
diff --git a/server/internal/store/types.go b/server/internal/store/types.go
index c1f3ab1..2b0f503 100644
--- a/server/internal/store/types.go
+++ b/server/internal/store/types.go
@@ -31,6 +31,7 @@ type UserState struct {
BirthMonth int32
BackupToken string
ChargeMoneyThisMonth int64
+ FacebookId int64
Setting UserSettingState
Status UserStatusState
diff --git a/server/internal/userdata/changed_tables.go b/server/internal/userdata/changed_tables.go
new file mode 100644
index 0000000..f219d78
--- /dev/null
+++ b/server/internal/userdata/changed_tables.go
@@ -0,0 +1,450 @@
+package userdata
+
+import (
+ "encoding/json"
+ "maps"
+ "slices"
+ "sort"
+ "strings"
+
+ pb "lunar-tear/server/gen/proto"
+ "lunar-tear/server/internal/store"
+)
+
+func mapsEqualSimple[K comparable, V comparable](a, b map[K]V) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for k, va := range a {
+ if vb, ok := b[k]; !ok || va != vb {
+ return false
+ }
+ }
+ return true
+}
+
+func mapsEqualStruct[K comparable, V comparable](a, b map[K]V) bool {
+ return mapsEqualSimple(a, b)
+}
+
+func mapsEqualSliceValues[K comparable, V comparable](a, b map[K][]V) bool {
+ if len(a) != len(b) {
+ return false
+ }
+ for k, va := range a {
+ vb, ok := b[k]
+ if !ok || !slices.Equal(va, vb) {
+ return false
+ }
+ }
+ return true
+}
+
+func gimmickStateEqual(a, b store.GimmickState) bool {
+ return mapsEqualStruct(a.Progress, b.Progress) &&
+ mapsEqualStruct(a.OrnamentProgress, b.OrnamentProgress) &&
+ mapsEqualStruct(a.Sequences, b.Sequences) &&
+ mapsEqualStruct(a.Unlocks, b.Unlocks)
+}
+
+func ChangedTables(before, after *store.UserState) []string {
+ var changed []string
+ add := func(name string) { changed = append(changed, name) }
+
+ if before.UserId != after.UserId || before.PlayerId != after.PlayerId ||
+ before.OsType != after.OsType || before.PlatformType != after.PlatformType ||
+ before.UserRestrictionType != after.UserRestrictionType ||
+ before.RegisterDatetime != after.RegisterDatetime ||
+ before.GameStartDatetime != after.GameStartDatetime ||
+ before.LatestVersion != after.LatestVersion {
+ add("IUser")
+ }
+ if before.Setting != after.Setting {
+ add("IUserSetting")
+ }
+ if before.Status != after.Status {
+ add("IUserStatus")
+ }
+ if before.Gem != after.Gem {
+ add("IUserGem")
+ }
+ if before.Profile != after.Profile {
+ add("IUserProfile")
+ }
+ if before.Login != after.Login {
+ add("IUserLogin")
+ }
+ if before.LoginBonus != after.LoginBonus {
+ add("IUserLoginBonus")
+ }
+ if before.PortalCageStatus != after.PortalCageStatus {
+ add("IUserPortalCageStatus")
+ }
+ if before.GuerrillaFreeOpen != after.GuerrillaFreeOpen {
+ add("IUserEventQuestGuerrillaFreeOpen")
+ }
+ if before.ShopReplaceable != after.ShopReplaceable {
+ add("IUserShopReplaceable")
+ }
+ if before.Explore != after.Explore {
+ add("IUserExplore")
+ }
+ if before.BigHuntProgress != after.BigHuntProgress {
+ add("IUserBigHuntProgressStatus")
+ }
+ if before.FacebookId != after.FacebookId {
+ add("IUserFacebook")
+ }
+
+ if before.MainQuest != after.MainQuest {
+ add("IUserMainQuestFlowStatus")
+ add("IUserMainQuestMainFlowStatus")
+ add("IUserMainQuestProgressStatus")
+ add("IUserMainQuestSeasonRoute")
+ add("IUserMainQuestReplayFlowStatus")
+ }
+ if before.EventQuest != after.EventQuest {
+ add("IUserEventQuestProgressStatus")
+ }
+ if before.ExtraQuest != after.ExtraQuest {
+ add("IUserExtraQuestProgressStatus")
+ }
+ if before.SideStoryActiveProgress != after.SideStoryActiveProgress {
+ add("IUserSideStoryQuestSceneProgressStatus")
+ }
+
+ if !mapsEqualStruct(before.Tutorials, after.Tutorials) {
+ add("IUserTutorialProgress")
+ }
+ if !mapsEqualStruct(before.Missions, after.Missions) {
+ add("IUserMission")
+ }
+ if !mapsEqualStruct(before.Characters, after.Characters) {
+ add("IUserCharacter")
+ }
+ if !mapsEqualStruct(before.Costumes, after.Costumes) {
+ add("IUserCostume")
+ }
+ if !mapsEqualStruct(before.Weapons, after.Weapons) {
+ add("IUserWeapon")
+ }
+ if !mapsEqualStruct(before.WeaponStories, after.WeaponStories) {
+ add("IUserWeaponStory")
+ }
+ if !mapsEqualStruct(before.WeaponNotes, after.WeaponNotes) {
+ add("IUserWeaponNote")
+ }
+ if !mapsEqualStruct(before.Companions, after.Companions) {
+ add("IUserCompanion")
+ }
+ if !mapsEqualStruct(before.Thoughts, after.Thoughts) {
+ add("IUserThought")
+ }
+ if !mapsEqualSimple(before.ConsumableItems, after.ConsumableItems) {
+ add("IUserConsumableItem")
+ }
+ if !mapsEqualSimple(before.Materials, after.Materials) {
+ add("IUserMaterial")
+ }
+ if !mapsEqualSimple(before.ImportantItems, after.ImportantItems) {
+ add("IUserImportantItem")
+ }
+ if !mapsEqualSimple(before.PremiumItems, after.PremiumItems) {
+ add("IUserPremiumItem")
+ }
+ if !mapsEqualStruct(before.Parts, after.Parts) {
+ add("IUserParts")
+ }
+ if !mapsEqualStruct(before.PartsGroupNotes, after.PartsGroupNotes) {
+ add("IUserPartsGroupNote")
+ }
+ if !mapsEqualStruct(before.PartsPresets, after.PartsPresets) {
+ add("IUserPartsPreset")
+ }
+ if !mapsEqualStruct(before.CostumeActiveSkills, after.CostumeActiveSkills) {
+ add("IUserCostumeActiveSkill")
+ }
+ if !mapsEqualSliceValues(before.WeaponSkills, after.WeaponSkills) {
+ add("IUserWeaponSkill")
+ }
+ if !mapsEqualSliceValues(before.WeaponAbilities, after.WeaponAbilities) {
+ add("IUserWeaponAbility")
+ }
+ if !mapsEqualStruct(before.WeaponAwakens, after.WeaponAwakens) {
+ add("IUserWeaponAwaken")
+ }
+ if !mapsEqualStruct(before.DeckTypeNotes, after.DeckTypeNotes) {
+ add("IUserDeckTypeNote")
+ }
+ if !mapsEqualStruct(before.DeckCharacters, after.DeckCharacters) {
+ add("IUserDeckCharacter")
+ add("IUserDeckCharacterDressupCostume")
+ }
+ if !mapsEqualStruct(before.Decks, after.Decks) {
+ add("IUserDeck")
+ }
+ if !mapsEqualSliceValues(before.DeckSubWeapons, after.DeckSubWeapons) {
+ add("IUserDeckSubWeaponGroup")
+ }
+ if !mapsEqualSliceValues(before.DeckParts, after.DeckParts) {
+ add("IUserDeckPartsGroup")
+ }
+ if !mapsEqualStruct(before.Quests, after.Quests) {
+ add("IUserQuest")
+ }
+ if !mapsEqualStruct(before.QuestMissions, after.QuestMissions) {
+ add("IUserQuestMission")
+ }
+ if !mapsEqualStruct(before.SideStoryQuests, after.SideStoryQuests) {
+ add("IUserSideStoryQuest")
+ }
+ if !mapsEqualStruct(before.QuestLimitContentStatus, after.QuestLimitContentStatus) {
+ add("IUserQuestLimitContentStatus")
+ }
+ if !mapsEqualSimple(before.NaviCutInPlayed, after.NaviCutInPlayed) {
+ add("IUserNaviCutIn")
+ }
+ if !mapsEqualSimple(before.ViewedMovies, after.ViewedMovies) {
+ add("IUserMovie")
+ }
+ if !mapsEqualSimple(before.ContentsStories, after.ContentsStories) {
+ add("IUserContentsStory")
+ }
+ if !mapsEqualSimple(before.DrawnOmikuji, after.DrawnOmikuji) {
+ add("IUserOmikuji")
+ }
+ if !mapsEqualSimple(before.DokanConfirmed, after.DokanConfirmed) {
+ add("IUserDokan")
+ }
+ if !mapsEqualStruct(before.ShopItems, after.ShopItems) {
+ add("IUserShopItem")
+ }
+ if !mapsEqualStruct(before.ShopReplaceableLineup, after.ShopReplaceableLineup) {
+ add("IUserShopReplaceableLineup")
+ }
+ if !mapsEqualStruct(before.ExploreScores, after.ExploreScores) {
+ add("IUserExploreScore")
+ }
+ if !mapsEqualStruct(before.CharacterBoards, after.CharacterBoards) {
+ add("IUserCharacterBoard")
+ }
+ if !mapsEqualStruct(before.CharacterBoardAbilities, after.CharacterBoardAbilities) {
+ add("IUserCharacterBoardAbility")
+ }
+ if !mapsEqualStruct(before.CharacterBoardStatusUps, after.CharacterBoardStatusUps) {
+ add("IUserCharacterBoardStatusUp")
+ }
+ if !mapsEqualStruct(before.CostumeAwakenStatusUps, after.CostumeAwakenStatusUps) {
+ add("IUserCostumeAwakenStatusUp")
+ }
+ if !mapsEqualStruct(before.CostumeLotteryEffects, after.CostumeLotteryEffects) {
+ add("IUserCostumeLotteryEffect")
+ }
+ if !mapsEqualStruct(before.CostumeLotteryEffectPending, after.CostumeLotteryEffectPending) {
+ add("IUserCostumeLotteryEffectPending")
+ }
+ if !mapsEqualStruct(before.AutoSaleSettings, after.AutoSaleSettings) {
+ add("IUserAutoSaleSettingDetail")
+ }
+ if !mapsEqualStruct(before.CharacterRebirths, after.CharacterRebirths) {
+ add("IUserCharacterRebirth")
+ }
+ if !mapsEqualStruct(before.CageOrnamentRewards, after.CageOrnamentRewards) {
+ add("IUserCageOrnamentReward")
+ }
+
+ if !mapsEqualStruct(before.BigHuntMaxScores, after.BigHuntMaxScores) {
+ add("IUserBigHuntMaxScore")
+ }
+ if !mapsEqualStruct(before.BigHuntStatuses, after.BigHuntStatuses) {
+ add("IUserBigHuntStatus")
+ }
+ if !mapsEqualStruct(before.BigHuntScheduleMaxScores, after.BigHuntScheduleMaxScores) {
+ add("IUserBigHuntScheduleMaxScore")
+ }
+ if !mapsEqualStruct(before.BigHuntWeeklyMaxScores, after.BigHuntWeeklyMaxScores) {
+ add("IUserBigHuntWeeklyMaxScore")
+ }
+ if !mapsEqualStruct(before.BigHuntWeeklyStatuses, after.BigHuntWeeklyStatuses) {
+ add("IUserBigHuntWeeklyStatus")
+ }
+
+ if !gimmickStateEqual(before.Gimmick, after.Gimmick) {
+ if !mapsEqualStruct(before.Gimmick.Progress, after.Gimmick.Progress) {
+ add("IUserGimmick")
+ }
+ if !mapsEqualStruct(before.Gimmick.OrnamentProgress, after.Gimmick.OrnamentProgress) {
+ add("IUserGimmickOrnamentProgress")
+ }
+ if !mapsEqualStruct(before.Gimmick.Sequences, after.Gimmick.Sequences) {
+ add("IUserGimmickSequence")
+ }
+ if !mapsEqualStruct(before.Gimmick.Unlocks, after.Gimmick.Unlocks) {
+ add("IUserGimmickUnlock")
+ }
+ }
+
+ return changed
+}
+
+func ComputeDelta(before, after *store.UserState, changedTables []string) map[string]*pb.DiffData {
+ diff := make(map[string]*pb.DiffData, len(changedTables))
+ for _, table := range changedTables {
+ afterJSON := projectTable(table, *after)
+ deleteKeys := "[]"
+ if kf := keyFieldsForTable(table); len(kf) > 0 {
+ beforeJSON := projectTable(table, *before)
+ deleteKeys = ComputeDeleteKeys(
+ parseJSONRecords(beforeJSON),
+ parseJSONRecords(afterJSON),
+ kf,
+ )
+ }
+ diff[table] = &pb.DiffData{
+ UpdateRecordsJson: afterJSON,
+ DeleteKeysJson: deleteKeys,
+ }
+ }
+ return diff
+}
+
+func AllTableNames() []string {
+ return slices.Sorted(maps.Keys(projectors))
+}
+
+func SortedChangedNames(tables []string) string {
+ sorted := make([]string, len(tables))
+ copy(sorted, tables)
+ sort.Strings(sorted)
+ return strings.Join(sorted, ",")
+}
+
+func parseJSONRecords(jsonStr string) []map[string]any {
+ if jsonStr == "" || jsonStr == "[]" {
+ return nil
+ }
+ var records []map[string]any
+ if err := json.Unmarshal([]byte(jsonStr), &records); err != nil {
+ return nil
+ }
+ return records
+}
+
+func keyFieldsForTable(table string) []string {
+ switch table {
+ case "IUserWeapon":
+ return []string{"userId", "userWeaponUuid"}
+ case "IUserWeaponSkill":
+ return []string{"userId", "userWeaponUuid", "slotNumber"}
+ case "IUserWeaponAbility":
+ return []string{"userId", "userWeaponUuid", "slotNumber"}
+ case "IUserWeaponAwaken":
+ return []string{"userId", "userWeaponUuid"}
+ case "IUserCostume":
+ return []string{"userId", "userCostumeUuid"}
+ case "IUserCompanion":
+ return []string{"userId", "userCompanionUuid"}
+ case "IUserThought":
+ return []string{"userId", "userThoughtUuid"}
+ case "IUserParts":
+ return []string{"userId", "userPartsUuid"}
+ case "IUserDeckCharacter":
+ return []string{"userId", "userDeckCharacterUuid"}
+ case "IUserDeck":
+ return []string{"userId", "deckType", "userDeckNumber"}
+ case "IUserDeckSubWeaponGroup":
+ return []string{"userId", "userDeckCharacterUuid", "sortOrder"}
+ case "IUserDeckPartsGroup":
+ return []string{"userId", "userDeckCharacterUuid", "sortOrder"}
+ case "IUserDeckCharacterDressupCostume":
+ return []string{"userId", "userDeckCharacterUuid"}
+ case "IUserCharacter":
+ return []string{"userId", "characterId"}
+ case "IUserConsumableItem":
+ return []string{"userId", "consumableItemId"}
+ case "IUserMaterial":
+ return []string{"userId", "materialId"}
+ case "IUserImportantItem":
+ return []string{"userId", "importantItemId"}
+ case "IUserPremiumItem":
+ return []string{"userId", "premiumItemId"}
+ case "IUserQuest":
+ return []string{"userId", "questId"}
+ case "IUserQuestMission":
+ return []string{"userId", "questId", "questMissionId"}
+ case "IUserMission":
+ return []string{"userId", "missionId"}
+ case "IUserWeaponStory":
+ return []string{"userId", "weaponId"}
+ case "IUserWeaponNote":
+ return []string{"userId", "weaponId"}
+ case "IUserTutorialProgress":
+ return []string{"userId", "tutorialType"}
+ case "IUserGimmick":
+ return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId", "gimmickId"}
+ case "IUserGimmickOrnamentProgress":
+ return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId", "gimmickId", "gimmickOrnamentIndex"}
+ case "IUserGimmickSequence":
+ return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId"}
+ case "IUserGimmickUnlock":
+ return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId", "gimmickId"}
+ case "IUserCostumeActiveSkill":
+ return []string{"userId", "userCostumeUuid"}
+ case "IUserCostumeAwakenStatusUp":
+ return []string{"userId", "userCostumeUuid", "statusCalculationType"}
+ case "IUserCostumeLotteryEffect":
+ return []string{"userId", "userCostumeUuid", "slotNumber"}
+ case "IUserCostumeLotteryEffectPending":
+ return []string{"userId", "userCostumeUuid"}
+ case "IUserCharacterBoard":
+ return []string{"userId", "characterBoardId"}
+ case "IUserCharacterBoardAbility":
+ return []string{"userId", "characterId", "abilityId"}
+ case "IUserCharacterBoardStatusUp":
+ return []string{"userId", "characterId", "statusCalculationType"}
+ case "IUserExploreScore":
+ return []string{"userId", "exploreId"}
+ case "IUserPartsGroupNote":
+ return []string{"userId", "partsGroupId"}
+ case "IUserPartsPreset":
+ return []string{"userId", "userPartsPresetNumber"}
+ case "IUserCageOrnamentReward":
+ return []string{"userId", "cageOrnamentId"}
+ case "IUserAutoSaleSettingDetail":
+ return []string{"userId", "possessionAutoSaleItemType"}
+ case "IUserCharacterRebirth":
+ return []string{"userId", "characterId"}
+ case "IUserShopItem":
+ return []string{"userId", "shopItemId"}
+ case "IUserShopReplaceableLineup":
+ return []string{"userId", "slotNumber"}
+ case "IUserNaviCutIn":
+ return []string{"userId", "naviCutInId"}
+ case "IUserMovie":
+ return []string{"userId", "movieId"}
+ case "IUserContentsStory":
+ return []string{"userId", "contentsStoryId"}
+ case "IUserOmikuji":
+ return []string{"userId", "omikujiId"}
+ case "IUserDokan":
+ return []string{"userId", "dokanId"}
+ case "IUserSideStoryQuest":
+ return []string{"userId", "sideStoryQuestId"}
+ case "IUserQuestLimitContentStatus":
+ return []string{"userId", "questId"}
+ case "IUserBigHuntMaxScore":
+ return []string{"userId", "bigHuntBossId"}
+ case "IUserBigHuntStatus":
+ return []string{"userId", "bigHuntBossQuestId"}
+ case "IUserBigHuntScheduleMaxScore":
+ return []string{"userId", "bigHuntScheduleId", "bigHuntBossId"}
+ case "IUserBigHuntWeeklyMaxScore":
+ return []string{"userId", "bigHuntWeeklyVersion", "attributeType"}
+ case "IUserBigHuntWeeklyStatus":
+ return []string{"userId", "bigHuntWeeklyVersion"}
+ case "IUserDeckTypeNote":
+ return []string{"userId", "deckType"}
+ default:
+ return nil
+ }
+}
diff --git a/server/internal/userdata/proj_bighunt.go b/server/internal/userdata/proj_bighunt.go
index 77c9c5e..235d1c7 100644
--- a/server/internal/userdata/proj_bighunt.go
+++ b/server/internal/userdata/proj_bighunt.go
@@ -4,11 +4,12 @@ import (
"sort"
"lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/utils"
)
func init() {
register("IUserBigHuntProgressStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentBigHuntBossQuestId": user.BigHuntProgress.CurrentBigHuntBossQuestId,
"currentBigHuntQuestId": user.BigHuntProgress.CurrentBigHuntQuestId,
@@ -39,7 +40,7 @@ func init() {
"latestVersion": ms.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
})
@@ -63,7 +64,7 @@ func init() {
"latestVersion": st.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
})
@@ -97,7 +98,7 @@ func init() {
"latestVersion": ms.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
})
@@ -130,7 +131,7 @@ func init() {
"latestVersion": ms.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
})
@@ -153,7 +154,7 @@ func init() {
"latestVersion": ws.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
})
}
diff --git a/server/internal/userdata/proj_characterboard.go b/server/internal/userdata/proj_characterboard.go
index 34fcca6..258b98f 100644
--- a/server/internal/userdata/proj_characterboard.go
+++ b/server/internal/userdata/proj_characterboard.go
@@ -4,19 +4,20 @@ import (
"sort"
"lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/utils"
)
func init() {
register("IUserCharacterBoard", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCharacterBoardRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCharacterBoardRecords(user)...)
return s
})
register("IUserCharacterBoardAbility", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCharacterBoardAbilityRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCharacterBoardAbilityRecords(user)...)
return s
})
register("IUserCharacterBoardStatusUp", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCharacterBoardStatusUpRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCharacterBoardStatusUpRecords(user)...)
return s
})
registerStatic("IUserCharacterBoardCompleteReward")
diff --git a/server/internal/userdata/proj_deck.go b/server/internal/userdata/proj_deck.go
index 28f9f73..1b9f535 100644
--- a/server/internal/userdata/proj_deck.go
+++ b/server/internal/userdata/proj_deck.go
@@ -5,31 +5,32 @@ import (
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/utils"
)
func init() {
register("IUserDeck", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedDeckRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedDeckRecords(user)...)
return s
})
register("IUserDeckCharacter", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedDeckCharacterRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedDeckCharacterRecords(user)...)
return s
})
register("IUserDeckSubWeaponGroup", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedDeckSubWeaponGroupRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedDeckSubWeaponGroupRecords(user)...)
return s
})
register("IUserDeckTypeNote", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedDeckTypeNoteRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedDeckTypeNoteRecords(user)...)
return s
})
register("IUserDeckPartsGroup", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedDeckPartsGroupRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedDeckPartsGroupRecords(user)...)
return s
})
register("IUserDeckCharacterDressupCostume", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedDeckDressupCostumeRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedDeckDressupCostumeRecords(user)...)
return s
})
registerStatic(
diff --git a/server/internal/userdata/proj_gimmick.go b/server/internal/userdata/proj_gimmick.go
index 7cdfbd4..5e76c21 100644
--- a/server/internal/userdata/proj_gimmick.go
+++ b/server/internal/userdata/proj_gimmick.go
@@ -4,23 +4,24 @@ import (
"sort"
"lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/utils"
)
func init() {
register("IUserGimmick", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedGimmickRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedGimmickRecords(user)...)
return s
})
register("IUserGimmickOrnamentProgress", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedGimmickOrnamentProgressRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedGimmickOrnamentProgressRecords(user)...)
return s
})
register("IUserGimmickSequence", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedGimmickSequenceRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedGimmickSequenceRecords(user)...)
return s
})
register("IUserGimmickUnlock", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedGimmickUnlockRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedGimmickUnlockRecords(user)...)
return s
})
}
diff --git a/server/internal/userdata/proj_inventory.go b/server/internal/userdata/proj_inventory.go
index 3b27ef9..e4af5ef 100644
--- a/server/internal/userdata/proj_inventory.go
+++ b/server/internal/userdata/proj_inventory.go
@@ -6,111 +6,112 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/utils"
)
func init() {
register("IUserCharacter", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCharacterRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCharacterRecords(user)...)
return s
})
register("IUserCostume", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCostumeRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCostumeRecords(user)...)
return s
})
register("IUserWeapon", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedWeaponRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedWeaponRecords(user)...)
return s
})
register("IUserWeaponStory", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedWeaponStoryRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedWeaponStoryRecords(user)...)
return s
})
register("IUserWeaponNote", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedWeaponNoteRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedWeaponNoteRecords(user)...)
return s
})
register("IUserCompanion", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCompanionRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCompanionRecords(user)...)
return s
})
register("IUserThought", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedThoughtRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedThoughtRecords(user)...)
return s
})
register("IUserConsumableItem", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedConsumableItemRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedConsumableItemRecords(user)...)
return s
})
register("IUserMaterial", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedMaterialRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedMaterialRecords(user)...)
return s
})
register("IUserImportantItem", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedImportantItemRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedImportantItemRecords(user)...)
return s
})
register("IUserPremiumItem", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedPremiumItemRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedPremiumItemRecords(user)...)
return s
})
register("IUserParts", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedPartsRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedPartsRecords(user)...)
return s
})
register("IUserCostumeActiveSkill", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCostumeActiveSkillRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCostumeActiveSkillRecords(user)...)
return s
})
register("IUserWeaponSkill", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedWeaponSkillRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedWeaponSkillRecords(user)...)
return s
})
register("IUserWeaponAbility", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedWeaponAbilityRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedWeaponAbilityRecords(user)...)
return s
})
register("IUserExplore", func(user store.UserState) string {
- s, _ := encodeJSONMaps(exploreRecord(user))
+ s, _ := utils.EncodeJSONMaps(exploreRecord(user))
return s
})
register("IUserExploreScore", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedExploreScoreRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedExploreScoreRecords(user)...)
return s
})
register("IUserPartsGroupNote", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedPartsGroupNoteRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedPartsGroupNoteRecords(user)...)
return s
})
register("IUserPartsPreset", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedPartsPresetRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedPartsPresetRecords(user)...)
return s
})
register("IUserCostumeAwakenStatusUp", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCostumeAwakenStatusUpRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCostumeAwakenStatusUpRecords(user)...)
return s
})
register("IUserAutoSaleSettingDetail", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedAutoSaleSettingRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedAutoSaleSettingRecords(user)...)
return s
})
register("IUserCharacterRebirth", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCharacterRebirthRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCharacterRebirthRecords(user)...)
return s
})
register("IUserCageOrnamentReward", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCageOrnamentRewardRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCageOrnamentRewardRecords(user)...)
return s
})
register("IUserWeaponAwaken", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedWeaponAwakenRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedWeaponAwakenRecords(user)...)
return s
})
register("IUserCostumeLotteryEffect", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedCostumeLotteryEffectRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedCostumeLotteryEffectRecords(user)...)
return s
})
register("IUserCostumeLotteryEffectPending", func(user store.UserState) string {
- s, _ := encodeJSONMaps(SortedCostumeLotteryEffectPendingRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(SortedCostumeLotteryEffectPendingRecords(user)...)
return s
})
registerStatic(
@@ -285,7 +286,7 @@ func WeaponStoryRecordsForIds(user store.UserState, weaponIds []int32) string {
"latestVersion": row.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
}
diff --git a/server/internal/userdata/proj_quest.go b/server/internal/userdata/proj_quest.go
index 4060757..981d43f 100644
--- a/server/internal/userdata/proj_quest.go
+++ b/server/internal/userdata/proj_quest.go
@@ -4,6 +4,7 @@ import (
"sort"
"lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/utils"
)
func sortedQuestRecords(user store.UserState) []map[string]any {
@@ -60,15 +61,15 @@ func sortedQuestMissionRecords(user store.UserState) []map[string]any {
func init() {
register("IUserQuest", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedQuestRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedQuestRecords(user)...)
return s
})
register("IUserQuestMission", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedQuestMissionRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedQuestMissionRecords(user)...)
return s
})
register("IUserMainQuestFlowStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentQuestFlowType": user.MainQuest.CurrentQuestFlowType,
"latestVersion": user.MainQuest.LatestVersion,
@@ -76,7 +77,7 @@ func init() {
return s
})
register("IUserMainQuestMainFlowStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentMainQuestRouteId": user.MainQuest.CurrentMainQuestRouteId,
"currentQuestSceneId": user.MainQuest.CurrentQuestSceneId,
@@ -87,7 +88,7 @@ func init() {
return s
})
register("IUserMainQuestProgressStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentQuestSceneId": user.MainQuest.ProgressQuestSceneId,
"headQuestSceneId": user.MainQuest.ProgressHeadQuestSceneId,
@@ -97,7 +98,7 @@ func init() {
return s
})
register("IUserMainQuestSeasonRoute", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"mainQuestSeasonId": user.MainQuest.MainQuestSeasonId,
"mainQuestRouteId": user.MainQuest.CurrentMainQuestRouteId,
@@ -106,7 +107,7 @@ func init() {
return s
})
register("IUserEventQuestProgressStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentEventQuestChapterId": user.EventQuest.CurrentEventQuestChapterId,
"currentQuestId": user.EventQuest.CurrentQuestId,
@@ -117,7 +118,7 @@ func init() {
return s
})
register("IUserExtraQuestProgressStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentQuestId": user.ExtraQuest.CurrentQuestId,
"currentQuestSceneId": user.ExtraQuest.CurrentQuestSceneId,
@@ -127,7 +128,7 @@ func init() {
return s
})
register("IUserMainQuestReplayFlowStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentHeadQuestSceneId": user.MainQuest.ReplayFlowHeadQuestSceneId,
"currentQuestSceneId": user.MainQuest.ReplayFlowCurrentQuestSceneId,
@@ -136,7 +137,7 @@ func init() {
return s
})
register("IUserSideStoryQuestSceneProgressStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"currentSideStoryQuestId": user.SideStoryActiveProgress.CurrentSideStoryQuestId,
"currentSideStoryQuestSceneId": user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId,
@@ -164,7 +165,7 @@ func init() {
"latestVersion": progress.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
})
register("IUserQuestLimitContentStatus", func(user store.UserState) string {
@@ -187,7 +188,7 @@ func init() {
"latestVersion": st.LatestVersion,
})
}
- s, _ := encodeJSONMaps(records...)
+ s, _ := utils.EncodeJSONMaps(records...)
return s
})
registerStatic(
diff --git a/server/internal/userdata/proj_user.go b/server/internal/userdata/proj_user.go
index 54095f1..b841220 100644
--- a/server/internal/userdata/proj_user.go
+++ b/server/internal/userdata/proj_user.go
@@ -5,11 +5,12 @@ import (
"lunar-tear/server/internal/gametime"
"lunar-tear/server/internal/store"
+ "lunar-tear/server/internal/utils"
)
func init() {
register("IUser", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"playerId": user.PlayerId,
"osType": user.OsType,
@@ -22,15 +23,15 @@ func init() {
return s
})
register("IUserSetting", func(user store.UserState) string {
- s, _ := encodeJSONRecords(&EntityIUserSetting{
- UserId: user.UserId,
- IsNotifyPurchaseAlert: user.Setting.IsNotifyPurchaseAlert,
- LatestVersion: user.Setting.LatestVersion,
+ s, _ := utils.EncodeJSONMaps(map[string]any{
+ "userId": user.UserId,
+ "isNotifyPurchaseAlert": user.Setting.IsNotifyPurchaseAlert,
+ "latestVersion": user.Setting.LatestVersion,
})
return s
})
register("IUserStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"level": user.Status.Level,
"exp": user.Status.Exp,
@@ -41,16 +42,16 @@ func init() {
return s
})
register("IUserGem", func(user store.UserState) string {
- s, _ := encodeJSONRecords(&EntityIUserGem{
- UserId: user.UserId,
- PaidGem: user.Gem.PaidGem,
- FreeGem: user.Gem.FreeGem,
- LatestVersion: gametime.NowMillis(),
+ s, _ := utils.EncodeJSONMaps(map[string]any{
+ "userId": user.UserId,
+ "paidGem": user.Gem.PaidGem,
+ "freeGem": user.Gem.FreeGem,
+ "latestVersion": gametime.NowMillis(),
})
return s
})
register("IUserProfile", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"name": user.Profile.Name,
"nameUpdateDatetime": user.Profile.NameUpdateDatetime,
@@ -63,58 +64,58 @@ func init() {
return s
})
register("IUserLogin", func(user store.UserState) string {
- s, _ := encodeJSONRecords(&EntityIUserLogin{
- UserId: user.UserId,
- TotalLoginCount: user.Login.TotalLoginCount,
- ContinualLoginCount: user.Login.ContinualLoginCount,
- MaxContinualLoginCount: user.Login.MaxContinualLoginCount,
- LastLoginDatetime: user.Login.LastLoginDatetime,
- LastComebackLoginDatetime: user.Login.LastComebackLoginDatetime,
- LatestVersion: user.Login.LatestVersion,
+ s, _ := utils.EncodeJSONMaps(map[string]any{
+ "userId": user.UserId,
+ "totalLoginCount": user.Login.TotalLoginCount,
+ "continualLoginCount": user.Login.ContinualLoginCount,
+ "maxContinualLoginCount": user.Login.MaxContinualLoginCount,
+ "lastLoginDatetime": user.Login.LastLoginDatetime,
+ "lastComebackLoginDatetime": user.Login.LastComebackLoginDatetime,
+ "latestVersion": user.Login.LatestVersion,
})
return s
})
register("IUserLoginBonus", func(user store.UserState) string {
- s, _ := encodeJSONRecords(&EntityIUserLoginBonus{
- UserId: user.UserId,
- LoginBonusId: user.LoginBonus.LoginBonusId,
- CurrentPageNumber: user.LoginBonus.CurrentPageNumber,
- CurrentStampNumber: user.LoginBonus.CurrentStampNumber,
- LatestRewardReceiveDatetime: user.LoginBonus.LatestRewardReceiveDatetime,
- LatestVersion: user.LoginBonus.LatestVersion,
+ s, _ := utils.EncodeJSONMaps(map[string]any{
+ "userId": user.UserId,
+ "loginBonusId": user.LoginBonus.LoginBonusId,
+ "currentPageNumber": user.LoginBonus.CurrentPageNumber,
+ "currentStampNumber": user.LoginBonus.CurrentStampNumber,
+ "latestRewardReceiveDatetime": user.LoginBonus.LatestRewardReceiveDatetime,
+ "latestVersion": user.LoginBonus.LatestVersion,
})
return s
})
register("IUserTutorialProgress", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedTutorialRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedTutorialRecords(user)...)
return s
})
register("IUserMission", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedMissionRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedMissionRecords(user)...)
return s
})
register("IUserNaviCutIn", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedNaviCutInRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedNaviCutInRecords(user)...)
return s
})
register("IUserMovie", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedMovieRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedMovieRecords(user)...)
return s
})
register("IUserContentsStory", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedContentsStoryRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedContentsStoryRecords(user)...)
return s
})
register("IUserOmikuji", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedOmikujiRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedOmikujiRecords(user)...)
return s
})
register("IUserDokan", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedDokanRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedDokanRecords(user)...)
return s
})
register("IUserPortalCageStatus", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"isCurrentProgress": user.PortalCageStatus.IsCurrentProgress,
"dropItemStartDatetime": user.PortalCageStatus.DropItemStartDatetime,
@@ -124,7 +125,7 @@ func init() {
return s
})
register("IUserEventQuestGuerrillaFreeOpen", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"startDatetime": user.GuerrillaFreeOpen.StartDatetime,
"openMinutes": user.GuerrillaFreeOpen.OpenMinutes,
@@ -135,11 +136,11 @@ func init() {
})
register("IUserShopItem", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedShopItemRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedShopItemRecords(user)...)
return s
})
register("IUserShopReplaceable", func(user store.UserState) string {
- s, _ := encodeJSONMaps(map[string]any{
+ s, _ := utils.EncodeJSONMaps(map[string]any{
"userId": user.UserId,
"lineupUpdateCount": user.ShopReplaceable.LineupUpdateCount,
"latestLineupUpdateDatetime": user.ShopReplaceable.LatestLineupUpdateDatetime,
@@ -148,11 +149,26 @@ func init() {
return s
})
register("IUserShopReplaceableLineup", func(user store.UserState) string {
- s, _ := encodeJSONMaps(sortedShopReplaceableLineupRecords(user)...)
+ s, _ := utils.EncodeJSONMaps(sortedShopReplaceableLineupRecords(user)...)
return s
})
- registerStatic()
+ register("IUserFacebook", func(user store.UserState) string {
+ return ProjectFacebook(user.UserId, user.FacebookId)
+ })
+ registerStatic("IUserApple")
+}
+
+func ProjectFacebook(userId int64, facebookId int64) string {
+ if facebookId == 0 {
+ return "[]"
+ }
+ s, _ := utils.EncodeJSONMaps(map[string]any{
+ "userId": userId,
+ "facebookId": facebookId,
+ "latestVersion": gametime.NowMillis(),
+ })
+ return s
}
func sortedTutorialRecords(user store.UserState) []map[string]any {
diff --git a/server/internal/userdata/state_projection.go b/server/internal/userdata/state_projection.go
index a852001..980b3b7 100644
--- a/server/internal/userdata/state_projection.go
+++ b/server/internal/userdata/state_projection.go
@@ -99,6 +99,8 @@ func FullClientTableMap(user store.UserState) map[string]string {
"IUserBigHuntScheduleMaxScore": projectTable("IUserBigHuntScheduleMaxScore", user),
"IUserBigHuntWeeklyMaxScore": projectTable("IUserBigHuntWeeklyMaxScore", user),
"IUserBigHuntWeeklyStatus": projectTable("IUserBigHuntWeeklyStatus", user),
+ "IUserFacebook": projectTable("IUserFacebook", user),
+ "IUserApple": projectTable("IUserApple", user),
}
}
diff --git a/server/internal/userdata/userdata.go b/server/internal/userdata/userdata.go
deleted file mode 100644
index f71b179..0000000
--- a/server/internal/userdata/userdata.go
+++ /dev/null
@@ -1,302 +0,0 @@
-package userdata
-
-import (
- "encoding/base64"
- "encoding/json"
- "fmt"
-
- "lunar-tear/server/internal/gametime"
-
- "github.com/vmihailenco/msgpack/v5"
-)
-
-// EntityIUser mirrors the game's EntityIUser [MessagePackObject] with [Key(0..7)].
-// Serialized as a MessagePack array of 8 elements.
-type EntityIUser struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- PlayerId int64 // Key(1)
- OsType int32 // Key(2) — 2 = Android
- PlatformType int32 // Key(3) — 2 = GooglePlay
- UserRestrictionType int32 // Key(4) — 0 = None
- RegisterDatetime int64 // Key(5) — unix millis
- GameStartDatetime int64 // Key(6) — unix millis
- LatestVersion int64 // Key(7)
-}
-
-// EntityIUserSetting mirrors EntityIUserSetting [Key(0..2)].
-type EntityIUserSetting struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 `json:"userId"` // Key(0)
- IsNotifyPurchaseAlert bool `json:"isNotifyPurchaseAlert"` // Key(1)
- LatestVersion int64 `json:"latestVersion"` // Key(2)
-}
-
-// EntityIUserTutorialProgress mirrors EntityIUserTutorialProgress [Key(0..4)].
-type EntityIUserTutorialProgress struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- TutorialType int32 // Key(1)
- ProgressPhase int32 // Key(2)
- ChoiceId int32 // Key(3)
- LatestVersion int64 // Key(4)
-}
-
-// EntityIUserQuest mirrors EntityIUserQuest [Key(0..9)].
-type EntityIUserQuest struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- QuestId int32 // Key(1)
- QuestStateType int32 // Key(2) — 2 = Cleared
- IsBattleOnly bool // Key(3)
- LatestStartDatetime int64 // Key(4) — unix millis
- ClearCount int32 // Key(5)
- DailyClearCount int32 // Key(6)
- LastClearDatetime int64 // Key(7) — unix millis
- ShortestClearFrames int32 // Key(8)
- LatestVersion int64 // Key(9)
-}
-
-// EntityIUserMainQuestFlowStatus mirrors EntityIUserMainQuestFlowStatus [Key(0..2)].
-type EntityIUserMainQuestFlowStatus struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- CurrentQuestFlowType int32 // Key(1) // QuestFlowType: 0=UNKNOWN, 1=MAIN_FLOW, 2=SUB_FLOW, 3=REPLAY_FLOW, 4=ANOTHER_ROUTE_REPLAY_FLOW
- LatestVersion int64 // Key(2)
-}
-
-// EntityIUserMainQuestMainFlowStatus mirrors EntityIUserMainQuestMainFlowStatus [Key(0..5)].
-type EntityIUserMainQuestMainFlowStatus struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- CurrentMainQuestRouteId int32 // Key(1)
- CurrentQuestSceneId int32 // Key(2)
- HeadQuestSceneId int32 // Key(3)
- IsReachedLastQuestScene bool // Key(4)
- LatestVersion int64 // Key(5)
-}
-
-// EntityIUserMainQuestProgressStatus mirrors EntityIUserMainQuestProgressStatus [Key(0..4)].
-// This table is used by ActivePlayerToEntityPlayingMainQuestStatus (0x2AB4A48).
-type EntityIUserMainQuestProgressStatus struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- CurrentQuestSceneId int32 // Key(1)
- HeadQuestSceneId int32 // Key(2)
- CurrentQuestFlowType int32 // Key(3) // QuestFlowType: 0=UNKNOWN, 1=MAIN_FLOW, 2=SUB_FLOW, 3=REPLAY_FLOW, 4=ANOTHER_ROUTE_REPLAY_FLOW
- LatestVersion int64 // Key(4)
-}
-
-// EntityIUserMainQuestSeasonRoute mirrors EntityIUserMainQuestSeasonRoute [Key(0..3)].
-type EntityIUserMainQuestSeasonRoute struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- MainQuestSeasonId int32 // Key(1)
- MainQuestRouteId int32 // Key(2)
- LatestVersion int64 // Key(3)
-}
-
-// EntityIUserStatus mirrors EntityIUserStatus [Key(0..5)].
-type EntityIUserStatus struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- Level int32 // Key(1)
- Exp int32 // Key(2)
- StaminaMilliValue int32 // Key(3)
- StaminaUpdateDatetime int64 // Key(4)
- LatestVersion int64 // Key(5)
-}
-
-// EntityIUserGem mirrors EntityIUserGem [Key(0..3)].
-type EntityIUserGem struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 `json:"userId"` // Key(0)
- PaidGem int32 `json:"paidGem"` // Key(1)
- FreeGem int32 `json:"freeGem"` // Key(2)
- LatestVersion int64 `json:"latestVersion"` // Key(3)
-}
-
-// EntityIUserProfile mirrors EntityIUserProfile [Key(0..7)].
-type EntityIUserProfile struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- Name string // Key(1)
- NameUpdateDatetime int64 // Key(2)
- Message string // Key(3)
- MessageUpdateDatetime int64 // Key(4)
- FavoriteCostumeId int32 // Key(5)
- FavoriteCostumeIdUpdateDatetime int64 // Key(6)
- LatestVersion int64 // Key(7)
-}
-
-// EntityIUserCharacter mirrors EntityIUserCharacter [Key(0..4)].
-type EntityIUserCharacter struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- CharacterId int32 // Key(1)
- Level int32 // Key(2)
- Exp int32 // Key(3)
- LatestVersion int64 // Key(4)
-}
-
-// EntityIUserCostume mirrors EntityIUserCostume [Key(0..9)].
-type EntityIUserCostume struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- UserCostumeUuid string // Key(1)
- CostumeId int32 // Key(2)
- LimitBreakCount int32 // Key(3)
- Level int32 // Key(4)
- Exp int32 // Key(5)
- HeadupDisplayViewId int32 // Key(6)
- AcquisitionDatetime int64 // Key(7)
- AwakenCount int32 // Key(8)
- LatestVersion int64 // Key(9)
-}
-
-// EntityIUserWeapon mirrors EntityIUserWeapon [Key(0..8)].
-type EntityIUserWeapon struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- UserWeaponUuid string // Key(1)
- WeaponId int32 // Key(2)
- Level int32 // Key(3)
- Exp int32 // Key(4)
- LimitBreakCount int32 // Key(5)
- IsProtected bool // Key(6)
- AcquisitionDatetime int64 // Key(7)
- LatestVersion int64 // Key(8)
-}
-
-// EntityIUserCompanion mirrors EntityIUserCompanion [Key(0..6)].
-type EntityIUserCompanion struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- UserCompanionUuid string // Key(1)
- CompanionId int32 // Key(2)
- HeadupDisplayViewId int32 // Key(3)
- Level int32 // Key(4)
- AcquisitionDatetime int64 // Key(5)
- LatestVersion int64 // Key(6)
-}
-
-// EntityIUserDeckCharacter mirrors EntityIUserDeckCharacter [Key(0..7)].
-type EntityIUserDeckCharacter struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- UserDeckCharacterUuid string // Key(1)
- UserCostumeUuid string // Key(2)
- MainUserWeaponUuid string // Key(3)
- UserCompanionUuid string // Key(4)
- Power int32 // Key(5)
- UserThoughtUuid string // Key(6)
- LatestVersion int64 // Key(7)
-}
-
-// EntityIUserDeck mirrors EntityIUserDeck [Key(0..8)].
-type EntityIUserDeck struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- DeckType int32 // Key(1)
- UserDeckNumber int32 // Key(2)
- UserDeckCharacterUuid01 string // Key(3)
- UserDeckCharacterUuid02 string // Key(4)
- UserDeckCharacterUuid03 string // Key(5)
- Name string // Key(6)
- Power int32 // Key(7)
- LatestVersion int64 // Key(8)
-}
-
-// EntityIUserLogin mirrors EntityIUserLogin [Key(0..6)].
-type EntityIUserLogin struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 `json:"userId"` // Key(0)
- TotalLoginCount int32 `json:"totalLoginCount"` // Key(1)
- ContinualLoginCount int32 `json:"continualLoginCount"` // Key(2)
- MaxContinualLoginCount int32 `json:"maxContinualLoginCount"` // Key(3)
- LastLoginDatetime int64 `json:"lastLoginDatetime"` // Key(4)
- LastComebackLoginDatetime int64 `json:"lastComebackLoginDatetime"` // Key(5)
- LatestVersion int64 `json:"latestVersion"` // Key(6)
-}
-
-// EntityIUserLoginBonus mirrors EntityIUserLoginBonus [Key(0..5)].
-type EntityIUserLoginBonus struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 `json:"userId"` // Key(0)
- LoginBonusId int32 `json:"loginBonusId"` // Key(1)
- CurrentPageNumber int32 `json:"currentPageNumber"` // Key(2)
- CurrentStampNumber int32 `json:"currentStampNumber"` // Key(3)
- LatestRewardReceiveDatetime int64 `json:"latestRewardReceiveDatetime"` // Key(4)
- LatestVersion int64 `json:"latestVersion"` // Key(5)
-}
-
-// EntityIUserMission mirrors EntityIUserMission [Key(0..6)].
-type EntityIUserMission struct {
- _msgpack struct{} `msgpack:",asArray"`
- UserId int64 // Key(0)
- MissionId int32 // Key(1)
- StartDatetime int64 // Key(2)
- ProgressValue int32 // Key(3)
- MissionProgressStatusType int32 // Key(4)
- ClearDatetime int64 // Key(5)
- LatestVersion int64 // Key(6)
-}
-
-// EncodeRecords serializes a slice of entities to the client-expected format:
-// a JSON array of base64-encoded MessagePack byte strings.
-func EncodeRecords(entities ...any) (string, error) {
- b64List := make([]string, 0, len(entities))
- for _, e := range entities {
- data, err := msgpack.Marshal(e)
- if err != nil {
- return "", fmt.Errorf("msgpack marshal: %w", err)
- }
- b64List = append(b64List, base64.StdEncoding.EncodeToString(data))
- }
- jsonBytes, err := json.Marshal(b64List)
- if err != nil {
- return "", fmt.Errorf("json marshal: %w", err)
- }
- return string(jsonBytes), nil
-}
-
-func encodeJSONRecords(entities ...any) (string, error) {
- jsonBytes, err := json.Marshal(entities)
- if err != nil {
- return "", fmt.Errorf("json marshal records: %w", err)
- }
- return string(jsonBytes), nil
-}
-
-func encodeJSONMaps(records ...map[string]any) (string, error) {
- jsonBytes, err := json.Marshal(records)
- if err != nil {
- return "", fmt.Errorf("json marshal maps: %w", err)
- }
- return string(jsonBytes), nil
-}
-
-// DefaultUserData returns pre-built user data tables for a fresh user.
-// We provide BOTH msgpack-encoded (base64) and plain JSON variants.
-// The server tries msgpack first; if the client doesn't accept it, switch to JSON.
-func DefaultUserData(userId int64) map[string]string {
- now := gametime.Now().Unix()
-
- userRecord, _ := EncodeRecords(&EntityIUser{
- UserId: userId,
- PlayerId: userId,
- OsType: 2,
- PlatformType: 2,
- RegisterDatetime: now,
- })
-
- settingRecord, _ := EncodeRecords(&EntityIUserSetting{
- UserId: userId,
- })
-
- data := map[string]string{
- "user": userRecord,
- "user_setting": settingRecord,
- }
- return data
-}
diff --git a/server/internal/utils/utils.go b/server/internal/utils/utils.go
index 889f04d..74b4db2 100644
--- a/server/internal/utils/utils.go
+++ b/server/internal/utils/utils.go
@@ -3,19 +3,20 @@ package utils
import (
"encoding/json"
"fmt"
- "os"
- "path/filepath"
+ "lunar-tear/server/internal/masterdata/memorydb"
)
-func ReadJSON[T any](filename string) ([]T, error) {
- path := filepath.Join("assets", "master_data", filename)
- data, err := os.ReadFile(path)
- if err != nil {
- return nil, fmt.Errorf("read %s: %w", path, err)
- }
- var out []T
- if err := json.Unmarshal(data, &out); err != nil {
- return nil, fmt.Errorf("unmarshal %s: %w", path, err)
- }
- return out, nil
+// ReadTable deserializes a master data table from the in-memory binary store.
+// The key is the snake_case table name as it appears in the binary header
+// (e.g. "m_weapon", "m_costume").
+func ReadTable[T any](key string) ([]T, error) {
+ return memorydb.ReadTable[T](key)
+}
+
+func EncodeJSONMaps(records ...map[string]any) (string, error) {
+ jsonBytes, err := json.Marshal(records)
+ if err != nil {
+ return "", fmt.Errorf("json marshal maps: %w", err)
+ }
+ return string(jsonBytes), nil
}
diff --git a/server/migrations/20260417165746_add_facebook_id.sql b/server/migrations/20260417165746_add_facebook_id.sql
new file mode 100644
index 0000000..8d91811
--- /dev/null
+++ b/server/migrations/20260417165746_add_facebook_id.sql
@@ -0,0 +1,8 @@
+-- +goose Up
+ALTER TABLE users ADD COLUMN facebook_id INTEGER;
+CREATE UNIQUE INDEX idx_users_facebook_id ON users(facebook_id) WHERE facebook_id IS NOT NULL;
+
+-- +goose Down
+DROP INDEX IF EXISTS idx_users_facebook_id;
+ALTER TABLE users DROP COLUMN facebook_id;
+