mirror of Walter-Sparrow / lunar-tear
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add authentication server, dev CLI, Docker multi-service setup, and cross-platform improvements

+4520 -2885
+18 -2
.github/workflows/docker-image.yml
··· 1 - name: Build and Push lunar-tear to Docker Hub 1 + name: Build and Push Docker images to Docker Hub 2 2 3 3 on: 4 4 push: ··· 17 17 username: ${{ secrets.DOCKERHUB_USERNAME }} 18 18 password: ${{ secrets.DOCKERHUB_TOKEN }} 19 19 20 - - name: Build and push Docker image 20 + - name: Build and push game server image 21 21 uses: docker/build-push-action@v5 22 22 with: 23 23 context: ./server 24 24 file: ./server/Dockerfile 25 25 push: true 26 26 tags: kretts/lunar-tear:latest 27 + 28 + - name: Build and push CDN image 29 + uses: docker/build-push-action@v5 30 + with: 31 + context: ./server 32 + file: ./server/Dockerfile.cdn 33 + push: true 34 + tags: kretts/octo-cdn:latest 35 + 36 + - name: Build and push auth server image 37 + uses: docker/build-push-action@v5 38 + with: 39 + context: ./server 40 + file: ./server/Dockerfile.auth 41 + push: true 42 + tags: kretts/auth-server:latest
+4
.gitignore
··· 6 6 server/tmp/ 7 7 server/lunar-tear 8 8 server/import-snapshot 9 + server/auth-server 10 + server/claim-account 11 + server/octo-cdn 12 + server/dev 9 13 10 14 __pycache__/ 11 15
+139 -20
README.md
··· 36 36 ```bash 37 37 cd server 38 38 mkdir -p db 39 - goose -dir migrations sqlite3 db/game.db up 39 + goose -dir migrations -allow-missing sqlite3 db/game.db up 40 40 ``` 41 41 42 42 ### Importing a Snapshot ··· 65 65 66 66 ### Run 67 67 68 + 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. 69 + 70 + **Start the CDN** (serves asset bundles, list.bin, master data, web pages): 71 + 72 + ```bash 73 + cd server 74 + go run ./cmd/octo-cdn \ 75 + --listen 0.0.0.0:8080 \ 76 + --public-addr 10.0.2.2:8080 77 + ``` 78 + 79 + **Start the game server** (gRPC, points the client at the CDN): 80 + 68 81 ```bash 69 82 cd server 70 83 go run ./cmd/lunar-tear \ 71 - --host 10.0.2.2 \ 72 - --http-port 8080 \ 73 - --grpc-port 8003 84 + --listen 0.0.0.0:8003 \ 85 + --public-addr 10.0.2.2:8003 \ 86 + --octo-url http://10.0.2.2:8080 74 87 ``` 75 88 76 - 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: 89 + 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: 77 90 78 91 ```bash 79 92 go build -o lunar-tear ./cmd/lunar-tear 80 93 sudo setcap cap_net_bind_service=+ep ./lunar-tear 81 - ./lunar-tear --host 10.0.2.2 --http-port 8080 94 + ./lunar-tear --public-addr 10.0.2.2:443 --octo-url http://10.0.2.2:8080 82 95 ``` 83 96 97 + 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. 98 + 99 + ### Run All Services At Once 100 + 101 + 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. 102 + 103 + ```bash 104 + cd server 105 + make dev 106 + ``` 107 + 108 + Or directly: 109 + 110 + ```bash 111 + cd server 112 + go run ./cmd/dev 113 + ``` 114 + 115 + Each service's output is prefixed with a colored label (`[auth]`, `[cdn]`, `[grpc]`). Press Ctrl+C to shut everything down. 116 + 117 + Override defaults with namespaced flags: 118 + 119 + ```bash 120 + 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 121 + ``` 122 + 123 + Or via `make`: 124 + 125 + ```bash 126 + make dev ARGS="--grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000" 127 + ``` 128 + 129 + | Flag | Default | Description | 130 + | --------------------- | ------------------ | ---------------------------------------- | 131 + | `--auth.listen` | `0.0.0.0:3000` | auth-server listen address | 132 + | `--auth.db` | `db/auth.db` | auth-server SQLite database path | 133 + | `--cdn.listen` | `0.0.0.0:8080` | octo-cdn local bind address | 134 + | `--cdn.public-addr` | `10.0.2.2:8080` | octo-cdn externally-reachable addr | 135 + | `--grpc.listen` | `0.0.0.0:8003` | lunar-tear gRPC listen address | 136 + | `--grpc.public-addr` | `10.0.2.2:8003` | lunar-tear externally-reachable addr | 137 + | `--grpc.octo-url` | `http://10.0.2.2:8080` | Octo CDN base URL passed to lunar-tear | 138 + | `--grpc.auth-url` | `http://localhost:3000` | auth server base URL passed to lunar-tear | 139 + | `--no-color` | `false` | disable colored output | 140 + 84 141 ### Ports 85 142 86 - | Protocol | Port | Notes | 87 - | -------- | ---- | ----------------------------------------------------------- | 88 - | gRPC | 443 | default; configurable with `--grpc-port` (requires patched client) | 89 - | HTTP | 8080 | Octo asset API + game web pages (`--http-port` flag) | 143 + | Protocol | Port | Binary | Notes | 144 + | -------- | ---- | ------------- | ----------------------------------------------------------- | 145 + | gRPC | 443 | `lunar-tear` | default; configurable with `--listen` (requires patched client) | 146 + | HTTP | 8080 | `octo-cdn` | Octo asset API + game web pages | 147 + 148 + ### Game Server Flags (`lunar-tear`) 149 + 150 + | Flag | Default | Description | 151 + | --------------- | ----------------- | ---------------------------------------------------- | 152 + | `--listen` | `0.0.0.0:443` | gRPC listen address (host:port) | 153 + | `--public-addr` | `127.0.0.1:443` | externally-reachable host:port advertised to clients | 154 + | `--octo-url` | *(required)* | CDN base URL the client uses for assets (e.g. `http://10.0.2.2:8080`) | 155 + | `--db` | `db/game.db` | SQLite database path | 156 + | `--auth-url` | *(empty)* | Auth server base URL (e.g. `http://localhost:3000`) | 90 157 91 - ### Flags 158 + ### CDN Flags (`octo-cdn`) 92 159 93 - | Flag | Default | Description | 94 - | ------------- | ------------ | ---------------------------------------------------- | 95 - | `--host` | `127.0.0.1` | hostname/IP given to the client | 96 - | `--http-port` | `8080` | HTTP/Octo server port | 97 - | `--grpc-port` | `443` | gRPC server port (client must be patched to match) | 98 - | `--db` | `db/game.db` | SQLite database path | 160 + | Flag | Default | Description | 161 + | --------------- | ----------------- | -------------------------------------------------------- | 162 + | `--listen` | `0.0.0.0:8080` | local bind address | 163 + | `--public-addr` | `127.0.0.1:8080` | externally-reachable address (used in list.bin rewriting) | 164 + | `--assets-dir` | `.` | root directory containing the `assets/` tree | 99 165 100 166 ### Docker 101 167 102 - Migrations run automatically on container start. 168 + 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. 103 169 104 170 ```bash 105 171 cd server 106 172 docker compose up -d 107 173 ``` 108 174 109 - The `db/` directory is mounted as a volume so the database persists across restarts. Make sure `assets/` is populated before starting. 175 + 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. 176 + 177 + Each service has its own image and can be deployed independently: 178 + 179 + | Service | Image | Default Port | Notes | 180 + | -------- | --------------------------- | ------------ | ------------------------------ | 181 + | `server` | `kretts/lunar-tear:latest` | 8003 | gRPC game server | 182 + | `cdn` | `kretts/octo-cdn:latest` | 8080 | HTTP asset CDN | 183 + | `auth` | `kretts/auth-server:latest` | 3000 | Account registration and login | 184 + 185 + 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. 110 186 111 187 ### Makefile Targets 112 188 ··· 115 191 | Target | Description | 116 192 | -------------- | ------------------------------------------------------- | 117 193 | `make proto` | Regenerate protobuf stubs | 118 - | `make build` | Build the server binary | 194 + | `make build` | Build the game server binary | 195 + | `make build-cdn` | Build the CDN binary | 196 + | `make build-auth` | Build the auth server binary | 119 197 | `make build-import` | Build the import-snapshot tool | 198 + | `make build-claim-account` | Build the claim-account tool | 199 + | `make dev` | Run all three services with one command | 120 200 | `make migrate` | Run goose migrations on `db/game.db` | 121 201 | `make import` | Import a snapshot (`SNAPSHOT=... UUID=...` required) | 202 + 203 + ## Claim Account 204 + 205 + 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. 206 + 207 + Useful when a new client connects and creates a throwaway account, but you want it to load an existing account instead. 208 + 209 + ```bash 210 + cd server 211 + go run ./cmd/claim-account --name "PlayerName" --db db/game.db 212 + ``` 213 + 214 + | Flag | Default | Description | 215 + | -------- | ------------ | ---------------------------------------------------- | 216 + | `--name` | *(required)* | In-game player name to claim | 217 + | `--db` | `db/game.db` | SQLite database path | 218 + 219 + ## Auth Server 220 + 221 + 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. 222 + 223 + ### Run 224 + 225 + ```bash 226 + cd server 227 + go run ./cmd/auth-server \ 228 + --listen 0.0.0.0:3000 \ 229 + --db db/auth.db 230 + ``` 231 + 232 + 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. 233 + 234 + ### Flags 235 + 236 + | Flag | Default | Description | 237 + | ---------- | --------------- | -------------------------------------------- | 238 + | `--listen` | `0.0.0.0:3000` | HTTP listen address (host:port) | 239 + | `--db` | `db/auth.db` | SQLite database path for auth users | 240 + | `--secret` | *(generated)* | Hex-encoded HMAC secret for token signing | 122 241 123 242 ## ⚠️ Legal Disclaimer 124 243
+20
server/Dockerfile.auth
··· 1 + FROM alpine:latest AS builder 2 + 3 + WORKDIR /usr/local/src 4 + COPY . . 5 + 6 + RUN apk add --no-cache go 7 + 8 + RUN go build -o auth-server ./cmd/auth-server 9 + 10 + FROM alpine:latest 11 + 12 + WORKDIR /opt/auth-server 13 + 14 + RUN chown 1000:1000 /opt/auth-server 15 + 16 + USER 1000 17 + 18 + COPY --from=builder /usr/local/src/auth-server . 19 + 20 + ENTRYPOINT ["./auth-server"]
+30
server/Dockerfile.cdn
··· 1 + FROM alpine:latest AS builder 2 + 3 + WORKDIR /usr/local/src 4 + COPY . . 5 + 6 + RUN apk add --no-cache \ 7 + protobuf \ 8 + protobuf-dev \ 9 + protoc \ 10 + make \ 11 + go \ 12 + libcap 13 + 14 + RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest &&\ 15 + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest &&\ 16 + PATH="$PATH:$(go env GOPATH)/bin" make proto &&\ 17 + go build -o octo-cdn ./cmd/octo-cdn &&\ 18 + setcap cap_net_bind_service=+ep ./octo-cdn 19 + 20 + FROM alpine:latest 21 + 22 + WORKDIR /opt/octo-cdn 23 + 24 + RUN chown 1000:1000 /opt/octo-cdn 25 + 26 + USER 1000 27 + 28 + COPY --from=builder /usr/local/src/octo-cdn . 29 + 30 + ENTRYPOINT ["./octo-cdn"]
+25 -4
server/Makefile
··· 3 3 # (generating all would put them in one package and cause name clashes). 4 4 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 5 5 6 + EXE = 7 + ifeq ($(OS),Windows_NT) 8 + EXE = .exe 9 + endif 10 + 6 11 proto: 7 12 protoc -I . $(PROTO_USED) --go_out=. --go_opt=module=lunar-tear/server --go-grpc_out=. --go-grpc_opt=module=lunar-tear/server 8 13 @echo "Generated in gen/proto/" 9 14 10 15 build: 11 - go build -o lunar-tear ./cmd/lunar-tear 16 + go build -o lunar-tear$(EXE) ./cmd/lunar-tear 17 + 18 + build-cdn: 19 + go build -o octo-cdn$(EXE) ./cmd/octo-cdn 12 20 13 21 build-import: 14 - go build -o import-snapshot ./cmd/import-snapshot 22 + go build -o import-snapshot$(EXE) ./cmd/import-snapshot 23 + 24 + build-auth: 25 + go build -o auth-server$(EXE) ./cmd/auth-server 26 + 27 + build-claim-account: 28 + go build -o claim-account$(EXE) ./cmd/claim-account 29 + 30 + dev: 31 + go run ./cmd/dev $(ARGS) 15 32 16 33 migrate: 34 + ifeq ($(OS),Windows_NT) 35 + if not exist db mkdir db 36 + else 17 37 mkdir -p db 18 - goose -dir migrations sqlite3 db/game.db up 38 + endif 39 + goose -dir migrations -allow-missing sqlite3 db/game.db up 19 40 20 41 import: 21 42 ifndef SNAPSHOT ··· 26 47 endif 27 48 go run ./cmd/import-snapshot --snapshot $(SNAPSHOT) --uuid $(UUID) 28 49 29 - .PHONY: proto build build-import migrate import 50 + .PHONY: proto build build-cdn build-auth build-import build-claim-account dev migrate import
+212
server/cmd/auth-server/handlers.go
··· 1 + package main 2 + 3 + import ( 4 + "embed" 5 + "encoding/base64" 6 + "encoding/json" 7 + "fmt" 8 + "html/template" 9 + "log" 10 + "net/http" 11 + "net/url" 12 + "strconv" 13 + "strings" 14 + ) 15 + 16 + //go:embed login.html 17 + var loginFS embed.FS 18 + 19 + var loginTmpl = template.Must(template.ParseFS(loginFS, "login.html")) 20 + 21 + type Handlers struct { 22 + store *AuthStore 23 + tok *TokenService 24 + } 25 + 26 + func NewHandlers(store *AuthStore, tok *TokenService) *Handlers { 27 + return &Handlers{store: store, tok: tok} 28 + } 29 + 30 + type loginPageData struct { 31 + RedirectURI string 32 + State string 33 + Error string 34 + Username string 35 + } 36 + 37 + func isOAuthPath(path string) bool { 38 + // Match /v{N}/dialog/oauth or /v{N}.{M}/dialog/oauth 39 + parts := strings.Split(strings.TrimPrefix(path, "/"), "/") 40 + if len(parts) != 3 { 41 + return false 42 + } 43 + return strings.HasPrefix(parts[0], "v") && parts[1] == "dialog" && parts[2] == "oauth" 44 + } 45 + 46 + func isMePath(path string) bool { 47 + p := strings.TrimPrefix(path, "/") 48 + if p == "me" { 49 + return true 50 + } 51 + parts := strings.Split(p, "/") 52 + return len(parts) == 2 && strings.HasPrefix(parts[0], "v") && parts[1] == "me" 53 + } 54 + 55 + func (h *Handlers) HandleOAuth(w http.ResponseWriter, r *http.Request) { 56 + if isMePath(r.URL.Path) { 57 + h.HandleMe(w, r) 58 + return 59 + } 60 + 61 + if !isOAuthPath(r.URL.Path) { 62 + http.NotFound(w, r) 63 + return 64 + } 65 + 66 + switch r.Method { 67 + case http.MethodGet: 68 + h.oauthGet(w, r) 69 + case http.MethodPost: 70 + h.oauthPost(w, r) 71 + default: 72 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 73 + } 74 + } 75 + 76 + func (h *Handlers) oauthGet(w http.ResponseWriter, r *http.Request) { 77 + data := loginPageData{ 78 + RedirectURI: r.URL.Query().Get("redirect_uri"), 79 + State: r.URL.Query().Get("state"), 80 + } 81 + w.Header().Set("Content-Type", "text/html; charset=utf-8") 82 + if err := loginTmpl.Execute(w, data); err != nil { 83 + log.Printf("render login page: %v", err) 84 + } 85 + } 86 + 87 + func (h *Handlers) oauthPost(w http.ResponseWriter, r *http.Request) { 88 + if err := r.ParseForm(); err != nil { 89 + http.Error(w, "bad form", http.StatusBadRequest) 90 + return 91 + } 92 + 93 + username := strings.TrimSpace(r.FormValue("username")) 94 + password := r.FormValue("password") 95 + action := r.FormValue("action") 96 + redirectURI := r.FormValue("redirect_uri") 97 + state := r.FormValue("state") 98 + 99 + renderErr := func(msg string) { 100 + data := loginPageData{ 101 + RedirectURI: redirectURI, 102 + State: state, 103 + Error: msg, 104 + Username: username, 105 + } 106 + w.Header().Set("Content-Type", "text/html; charset=utf-8") 107 + w.WriteHeader(http.StatusOK) 108 + if err := loginTmpl.Execute(w, data); err != nil { 109 + log.Printf("render login page: %v", err) 110 + } 111 + } 112 + 113 + if username == "" || password == "" { 114 + renderErr("Username and password are required.") 115 + return 116 + } 117 + 118 + var user AuthUser 119 + var err error 120 + 121 + switch action { 122 + case "register": 123 + user, err = h.store.CreateUser(username, password) 124 + if err == ErrUserExists { 125 + renderErr("Username is already taken.") 126 + return 127 + } 128 + if err != nil { 129 + log.Printf("create user: %v", err) 130 + renderErr("Server error. Try again.") 131 + return 132 + } 133 + log.Printf("registered user %q (id=%d)", user.Username, user.ID) 134 + 135 + case "login": 136 + user, err = h.store.VerifyUser(username, password) 137 + if err == ErrInvalidCreds { 138 + renderErr("Invalid username or password.") 139 + return 140 + } 141 + if err != nil { 142 + log.Printf("verify user: %v", err) 143 + renderErr("Server error. Try again.") 144 + return 145 + } 146 + log.Printf("authenticated user %q (id=%d)", user.Username, user.ID) 147 + 148 + default: 149 + renderErr("Invalid action.") 150 + return 151 + } 152 + 153 + token, err := h.tok.Generate(user) 154 + if err != nil { 155 + log.Printf("generate token: %v", err) 156 + renderErr("Server error. Try again.") 157 + return 158 + } 159 + 160 + payload := fmt.Sprintf(`{"user_id":"%d"}`, user.ID) 161 + b64 := base64.StdEncoding.EncodeToString([]byte(payload)) 162 + 163 + fragment := url.Values{} 164 + fragment.Set("access_token", token) 165 + fragment.Set("token_type", "bearer") 166 + fragment.Set("expires_in", strconv.FormatInt(int64(tokenTTL.Seconds()), 10)) 167 + fragment.Set("signed_request", "0."+b64) 168 + if state != "" { 169 + fragment.Set("state", state) 170 + } 171 + 172 + target := redirectURI + "?" + fragment.Encode() 173 + log.Printf("redirecting to %s", target) 174 + http.Redirect(w, r, target, http.StatusFound) 175 + } 176 + 177 + func (h *Handlers) HandleCheckUsername(w http.ResponseWriter, r *http.Request) { 178 + username := strings.TrimSpace(r.URL.Query().Get("username")) 179 + w.Header().Set("Content-Type", "application/json") 180 + if username == "" { 181 + json.NewEncoder(w).Encode(map[string]bool{"exists": false}) 182 + return 183 + } 184 + json.NewEncoder(w).Encode(map[string]bool{"exists": h.store.UserExists(username)}) 185 + } 186 + 187 + func (h *Handlers) HandleMe(w http.ResponseWriter, r *http.Request) { 188 + token := r.URL.Query().Get("access_token") 189 + if token == "" { 190 + auth := r.Header.Get("Authorization") 191 + if strings.HasPrefix(auth, "Bearer ") { 192 + token = auth[7:] 193 + } 194 + } 195 + 196 + if token == "" { 197 + http.Error(w, `{"error":{"message":"missing access_token","type":"OAuthException","code":190}}`, http.StatusUnauthorized) 198 + return 199 + } 200 + 201 + claims, err := h.tok.Validate(token) 202 + if err != nil { 203 + http.Error(w, fmt.Sprintf(`{"error":{"message":"%s","type":"OAuthException","code":190}}`, err), http.StatusUnauthorized) 204 + return 205 + } 206 + 207 + w.Header().Set("Content-Type", "application/json") 208 + json.NewEncoder(w).Encode(map[string]string{ 209 + "id": strconv.FormatInt(claims.Sub, 10), 210 + "name": claims.Name, 211 + }) 212 + }
+199
server/cmd/auth-server/login.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1"> 6 + <title>Lunar Tear – Login</title> 7 + <style> 8 + * { margin: 0; padding: 0; box-sizing: border-box; } 9 + body { 10 + font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; 11 + background: #0a0a0a; 12 + color: #e0e0e0; 13 + min-height: 100vh; 14 + min-height: 100dvh; 15 + display: flex; 16 + align-items: center; 17 + justify-content: center; 18 + } 19 + .card { 20 + background: #161616; 21 + border: 1px solid #2a2a2a; 22 + border-radius: 8px; 23 + padding: 40px 32px 32px; 24 + width: 100%; 25 + max-width: 360px; 26 + } 27 + h1 { 28 + text-align: center; 29 + font-size: 28px; 30 + font-weight: 300; 31 + letter-spacing: 6px; 32 + text-transform: uppercase; 33 + color: #c8c8c8; 34 + margin-bottom: 8px; 35 + } 36 + .subtitle { 37 + text-align: center; 38 + font-size: 11px; 39 + letter-spacing: 3px; 40 + text-transform: uppercase; 41 + color: #555; 42 + margin-bottom: 32px; 43 + transition: color 0.3s; 44 + } 45 + .error { 46 + background: #2a1515; 47 + border: 1px solid #5a2020; 48 + color: #e08080; 49 + border-radius: 4px; 50 + padding: 10px 14px; 51 + font-size: 13px; 52 + margin-bottom: 20px; 53 + } 54 + label { 55 + display: block; 56 + font-size: 11px; 57 + letter-spacing: 1px; 58 + text-transform: uppercase; 59 + color: #888; 60 + margin-bottom: 6px; 61 + } 62 + input[type="text"], 63 + input[type="password"] { 64 + width: 100%; 65 + padding: 10px 12px; 66 + background: #0e0e0e; 67 + border: 1px solid #333; 68 + border-radius: 4px; 69 + color: #e0e0e0; 70 + font-size: 15px; 71 + margin-bottom: 18px; 72 + outline: none; 73 + transition: border-color 0.2s; 74 + } 75 + input:focus { border-color: #666; } 76 + .buttons { 77 + display: flex; 78 + gap: 10px; 79 + margin-top: 8px; 80 + } 81 + button { 82 + flex: 1; 83 + padding: 11px 0; 84 + border: 1px solid #333; 85 + border-radius: 4px; 86 + font-size: 13px; 87 + letter-spacing: 1px; 88 + cursor: pointer; 89 + transition: background 0.2s, border-color 0.2s, opacity 0.3s; 90 + } 91 + .btn-login { 92 + background: #e0e0e0; 93 + color: #111; 94 + border-color: #e0e0e0; 95 + } 96 + .btn-login:hover { background: #fff; border-color: #fff; } 97 + .btn-register { 98 + background: transparent; 99 + color: #aaa; 100 + } 101 + .btn-register:hover { border-color: #666; color: #e0e0e0; } 102 + .hidden { display: none; } 103 + @media (max-height: 480px) { 104 + body { align-items: stretch; padding: 0; } 105 + .card { 106 + max-width: none; border-radius: 0; border: none; 107 + min-height: 100vh; min-height: 100dvh; 108 + padding: 20px 24px; 109 + display: flex; flex-direction: column; justify-content: center; 110 + } 111 + h1 { font-size: 22px; margin-bottom: 4px; } 112 + .subtitle { margin-bottom: 16px; } 113 + input[type="text"], 114 + input[type="password"] { padding: 8px 10px; margin-bottom: 12px; } 115 + .buttons { margin-top: 4px; } 116 + button { padding: 9px 0; } 117 + } 118 + </style> 119 + </head> 120 + <body> 121 + <form class="card" method="POST"> 122 + <h1>Lunar Tear</h1> 123 + <div class="subtitle" id="subtitle">Authentication</div> 124 + 125 + {{if .Error}} 126 + <div class="error">{{.Error}}</div> 127 + {{end}} 128 + 129 + <input type="hidden" name="redirect_uri" value="{{.RedirectURI}}"> 130 + <input type="hidden" name="state" value="{{.State}}"> 131 + 132 + <label for="username">Username</label> 133 + <input type="text" id="username" name="username" value="{{.Username}}" autocomplete="username" autofocus required> 134 + 135 + <label for="password">Password</label> 136 + <input type="password" id="password" name="password" autocomplete="current-password" required> 137 + 138 + <div class="buttons"> 139 + <button type="submit" name="action" value="login" class="btn-login hidden" id="btn-login">Login</button> 140 + <button type="submit" name="action" value="register" class="btn-register hidden" id="btn-register">Create Account</button> 141 + </div> 142 + </form> 143 + <script> 144 + (function() { 145 + var input = document.getElementById('username'); 146 + var btnLogin = document.getElementById('btn-login'); 147 + var btnRegister = document.getElementById('btn-register'); 148 + var subtitle = document.getElementById('subtitle'); 149 + var timer = null; 150 + var lastChecked = ''; 151 + 152 + function check() { 153 + var name = input.value.trim(); 154 + if (name === '') { 155 + btnLogin.classList.add('hidden'); 156 + btnRegister.classList.add('hidden'); 157 + subtitle.textContent = 'Authentication'; 158 + lastChecked = ''; 159 + return; 160 + } 161 + if (name === lastChecked) return; 162 + lastChecked = name; 163 + 164 + fetch('/check-username?username=' + encodeURIComponent(name)) 165 + .then(function(r) { return r.json(); }) 166 + .then(function(data) { 167 + if (input.value.trim() !== name) return; 168 + if (data.exists) { 169 + btnLogin.classList.remove('hidden'); 170 + btnRegister.classList.add('hidden'); 171 + subtitle.textContent = 'Welcome back'; 172 + } else { 173 + btnLogin.classList.add('hidden'); 174 + btnRegister.classList.remove('hidden'); 175 + subtitle.textContent = 'Create your account'; 176 + } 177 + }) 178 + .catch(function() { 179 + btnLogin.classList.remove('hidden'); 180 + btnRegister.classList.remove('hidden'); 181 + subtitle.textContent = 'Authentication'; 182 + }); 183 + } 184 + 185 + input.addEventListener('input', function() { 186 + clearTimeout(timer); 187 + timer = setTimeout(check, 300); 188 + }); 189 + 190 + input.addEventListener('blur', function() { 191 + clearTimeout(timer); 192 + check(); 193 + }); 194 + 195 + if (input.value.trim() !== '') check(); 196 + })(); 197 + </script> 198 + </body> 199 + </html>
+53
server/cmd/auth-server/main.go
··· 1 + package main 2 + 3 + import ( 4 + "crypto/rand" 5 + "database/sql" 6 + "encoding/hex" 7 + "flag" 8 + "log" 9 + "net/http" 10 + 11 + _ "modernc.org/sqlite" 12 + ) 13 + 14 + func main() { 15 + listen := flag.String("listen", "0.0.0.0:3000", "HTTP listen address (host:port)") 16 + dbPath := flag.String("db", "db/auth.db", "SQLite database path for auth users") 17 + secret := flag.String("secret", "", "HMAC secret for tokens (auto-generated if empty)") 18 + flag.Parse() 19 + 20 + hmacSecret := []byte(*secret) 21 + if len(hmacSecret) == 0 { 22 + hmacSecret = make([]byte, 32) 23 + if _, err := rand.Read(hmacSecret); err != nil { 24 + log.Fatalf("generate secret: %v", err) 25 + } 26 + log.Printf("generated HMAC secret: %s", hex.EncodeToString(hmacSecret)) 27 + log.Printf("pass --secret=%s to reuse across restarts", hex.EncodeToString(hmacSecret)) 28 + } 29 + 30 + db, err := sql.Open("sqlite", *dbPath) 31 + if err != nil { 32 + log.Fatalf("open database: %v", err) 33 + } 34 + defer db.Close() 35 + 36 + store, err := NewAuthStore(db) 37 + if err != nil { 38 + log.Fatalf("init auth store: %v", err) 39 + } 40 + 41 + tok := NewTokenService(hmacSecret) 42 + h := NewHandlers(store, tok) 43 + 44 + mux := http.NewServeMux() 45 + mux.HandleFunc("/", h.HandleOAuth) 46 + mux.HandleFunc("/me", h.HandleMe) 47 + mux.HandleFunc("/check-username", h.HandleCheckUsername) 48 + 49 + log.Printf("auth server listening on %s", *listen) 50 + if err := http.ListenAndServe(*listen, mux); err != nil { 51 + log.Fatalf("listen: %v", err) 52 + } 53 + }
+103
server/cmd/auth-server/store.go
··· 1 + package main 2 + 3 + import ( 4 + "database/sql" 5 + "errors" 6 + "fmt" 7 + "time" 8 + 9 + "golang.org/x/crypto/bcrypt" 10 + ) 11 + 12 + var ( 13 + ErrUserExists = errors.New("username already taken") 14 + ErrInvalidCreds = errors.New("invalid username or password") 15 + ) 16 + 17 + type AuthUser struct { 18 + ID int64 19 + Username string 20 + } 21 + 22 + type AuthStore struct { 23 + db *sql.DB 24 + } 25 + 26 + func NewAuthStore(db *sql.DB) (*AuthStore, error) { 27 + _, err := db.Exec(` 28 + CREATE TABLE IF NOT EXISTS auth_users ( 29 + id INTEGER PRIMARY KEY AUTOINCREMENT, 30 + username TEXT NOT NULL UNIQUE, 31 + password BLOB NOT NULL, 32 + created_at TEXT NOT NULL 33 + ) 34 + `) 35 + if err != nil { 36 + return nil, fmt.Errorf("create auth_users table: %w", err) 37 + } 38 + return &AuthStore{db: db}, nil 39 + } 40 + 41 + func (s *AuthStore) CreateUser(username, password string) (AuthUser, error) { 42 + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 43 + if err != nil { 44 + return AuthUser{}, fmt.Errorf("hash password: %w", err) 45 + } 46 + 47 + res, err := s.db.Exec( 48 + `INSERT INTO auth_users (username, password, created_at) VALUES (?, ?, ?)`, 49 + username, hash, time.Now().UTC().Format(time.RFC3339), 50 + ) 51 + if err != nil { 52 + if isUniqueViolation(err) { 53 + return AuthUser{}, ErrUserExists 54 + } 55 + return AuthUser{}, fmt.Errorf("insert user: %w", err) 56 + } 57 + 58 + id, _ := res.LastInsertId() 59 + return AuthUser{ID: id, Username: username}, nil 60 + } 61 + 62 + func (s *AuthStore) VerifyUser(username, password string) (AuthUser, error) { 63 + var id int64 64 + var hash []byte 65 + err := s.db.QueryRow( 66 + `SELECT id, password FROM auth_users WHERE username = ?`, username, 67 + ).Scan(&id, &hash) 68 + if err != nil { 69 + return AuthUser{}, ErrInvalidCreds 70 + } 71 + 72 + if err := bcrypt.CompareHashAndPassword(hash, []byte(password)); err != nil { 73 + return AuthUser{}, ErrInvalidCreds 74 + } 75 + 76 + return AuthUser{ID: id, Username: username}, nil 77 + } 78 + 79 + func (s *AuthStore) UserExists(username string) bool { 80 + var n int 81 + err := s.db.QueryRow(`SELECT 1 FROM auth_users WHERE username = ?`, username).Scan(&n) 82 + return err == nil 83 + } 84 + 85 + func isUniqueViolation(err error) bool { 86 + return err != nil && (errors.Is(err, sql.ErrNoRows) || 87 + // modernc.org/sqlite returns error strings containing "UNIQUE constraint failed" 88 + fmt.Sprintf("%v", err) == fmt.Sprintf("%v", err) && 89 + contains(err.Error(), "UNIQUE constraint failed")) 90 + } 91 + 92 + func contains(s, substr string) bool { 93 + return len(s) >= len(substr) && searchStr(s, substr) 94 + } 95 + 96 + func searchStr(s, sub string) bool { 97 + for i := 0; i <= len(s)-len(sub); i++ { 98 + if s[i:i+len(sub)] == sub { 99 + return true 100 + } 101 + } 102 + return false 103 + }
+102
server/cmd/auth-server/token.go
··· 1 + package main 2 + 3 + import ( 4 + "crypto/hmac" 5 + "crypto/sha256" 6 + "encoding/base64" 7 + "encoding/json" 8 + "errors" 9 + "fmt" 10 + "time" 11 + ) 12 + 13 + const tokenTTL = 24 * time.Hour 14 + 15 + var ( 16 + ErrTokenInvalid = errors.New("invalid token") 17 + ErrTokenExpired = errors.New("token expired") 18 + ) 19 + 20 + type TokenClaims struct { 21 + Sub int64 `json:"sub"` 22 + Name string `json:"name"` 23 + Iat int64 `json:"iat"` 24 + Exp int64 `json:"exp"` 25 + } 26 + 27 + type TokenService struct { 28 + secret []byte 29 + } 30 + 31 + func NewTokenService(secret []byte) *TokenService { 32 + return &TokenService{secret: secret} 33 + } 34 + 35 + func (t *TokenService) Generate(user AuthUser) (string, error) { 36 + now := time.Now().Unix() 37 + claims := TokenClaims{ 38 + Sub: user.ID, 39 + Name: user.Username, 40 + Iat: now, 41 + Exp: now + int64(tokenTTL.Seconds()), 42 + } 43 + 44 + payload, err := json.Marshal(claims) 45 + if err != nil { 46 + return "", fmt.Errorf("marshal claims: %w", err) 47 + } 48 + 49 + enc := base64.RawURLEncoding 50 + payloadB64 := enc.EncodeToString(payload) 51 + 52 + mac := hmac.New(sha256.New, t.secret) 53 + mac.Write(payload) 54 + sig := enc.EncodeToString(mac.Sum(nil)) 55 + 56 + return payloadB64 + "." + sig, nil 57 + } 58 + 59 + func (t *TokenService) Validate(token string) (TokenClaims, error) { 60 + dot := -1 61 + for i := range token { 62 + if token[i] == '.' { 63 + dot = i 64 + break 65 + } 66 + } 67 + if dot < 0 { 68 + return TokenClaims{}, ErrTokenInvalid 69 + } 70 + 71 + payloadB64 := token[:dot] 72 + sigB64 := token[dot+1:] 73 + 74 + enc := base64.RawURLEncoding 75 + 76 + payload, err := enc.DecodeString(payloadB64) 77 + if err != nil { 78 + return TokenClaims{}, ErrTokenInvalid 79 + } 80 + 81 + sig, err := enc.DecodeString(sigB64) 82 + if err != nil { 83 + return TokenClaims{}, ErrTokenInvalid 84 + } 85 + 86 + mac := hmac.New(sha256.New, t.secret) 87 + mac.Write(payload) 88 + if !hmac.Equal(mac.Sum(nil), sig) { 89 + return TokenClaims{}, ErrTokenInvalid 90 + } 91 + 92 + var claims TokenClaims 93 + if err := json.Unmarshal(payload, &claims); err != nil { 94 + return TokenClaims{}, ErrTokenInvalid 95 + } 96 + 97 + if time.Now().Unix() > claims.Exp { 98 + return TokenClaims{}, ErrTokenExpired 99 + } 100 + 101 + return claims, nil 102 + }
+166
server/cmd/claim-account/main.go
··· 1 + package main 2 + 3 + import ( 4 + "database/sql" 5 + "flag" 6 + "fmt" 7 + "log" 8 + 9 + "lunar-tear/server/internal/database" 10 + ) 11 + 12 + var childTables = []string{ 13 + "user_cage_ornament_rewards", 14 + "user_shop_replaceable_lineup", 15 + "user_shop_items", 16 + "user_gacha_banner_box_drew_counts", 17 + "user_gacha_banners", 18 + "user_gacha_converted_medals", 19 + "user_gifts", 20 + "user_dokan_confirmed", 21 + "user_drawn_omikuji", 22 + "user_contents_stories", 23 + "user_viewed_movies", 24 + "user_navi_cutin_played", 25 + "user_auto_sale_settings", 26 + "user_explore_scores", 27 + "user_tutorials", 28 + "user_premium_items", 29 + "user_important_items", 30 + "user_materials", 31 + "user_consumable_items", 32 + "user_gimmick_unlocks", 33 + "user_gimmick_sequences", 34 + "user_gimmick_ornament_progress", 35 + "user_gimmick_progress", 36 + "user_big_hunt_weekly_statuses", 37 + "user_big_hunt_weekly_max_scores", 38 + "user_big_hunt_schedule_max_scores", 39 + "user_big_hunt_statuses", 40 + "user_big_hunt_max_scores", 41 + "user_quest_limit_content_status", 42 + "user_side_story_quests", 43 + "user_missions", 44 + "user_quest_missions", 45 + "user_quests", 46 + "user_deck_type_notes", 47 + "user_deck_parts", 48 + "user_deck_sub_weapons", 49 + "user_decks", 50 + "user_deck_characters", 51 + "user_parts_presets", 52 + "user_parts_group_notes", 53 + "user_parts", 54 + "user_thoughts", 55 + "user_companions", 56 + "user_weapon_notes", 57 + "user_weapon_stories", 58 + "user_weapon_awakens", 59 + "user_weapon_abilities", 60 + "user_weapon_skills", 61 + "user_weapons", 62 + "user_costume_awaken_status_ups", 63 + "user_costume_active_skills", 64 + "user_costumes", 65 + "user_character_rebirths", 66 + "user_character_board_status_ups", 67 + "user_character_board_abilities", 68 + "user_character_boards", 69 + "user_characters", 70 + "user_gacha", 71 + "user_shop_replaceable", 72 + "user_explore", 73 + "user_guerrilla_free_open", 74 + "user_portal_cage", 75 + "user_notification", 76 + "user_battle", 77 + "user_big_hunt_state", 78 + "user_side_story_active", 79 + "user_extra_quest", 80 + "user_event_quest", 81 + "user_main_quest", 82 + "user_login_bonus", 83 + "user_login", 84 + "user_profile", 85 + "user_gem", 86 + "user_status", 87 + "user_setting", 88 + "sessions", 89 + } 90 + 91 + func main() { 92 + dbPath := flag.String("db", "db/game.db", "SQLite database path") 93 + name := flag.String("name", "", "In-game player name to look up in user_profile (required)") 94 + flag.Parse() 95 + 96 + if *name == "" { 97 + log.Fatal("--name flag is required") 98 + } 99 + 100 + db, err := database.Open(*dbPath) 101 + if err != nil { 102 + log.Fatalf("open database: %v", err) 103 + } 104 + defer db.Close() 105 + 106 + var targetId int64 107 + err = db.QueryRow(`SELECT user_id FROM user_profile WHERE name = ?`, *name).Scan(&targetId) 108 + if err == sql.ErrNoRows { 109 + log.Fatalf("no user found with name %q", *name) 110 + } 111 + if err != nil { 112 + log.Fatalf("query user_profile: %v", err) 113 + } 114 + 115 + var targetUuid string 116 + err = db.QueryRow(`SELECT uuid FROM users WHERE user_id = ?`, targetId).Scan(&targetUuid) 117 + if err != nil { 118 + log.Fatalf("query target uuid: %v", err) 119 + } 120 + 121 + var latestId int64 122 + var latestUuid string 123 + err = db.QueryRow(`SELECT user_id, uuid FROM users ORDER BY user_id DESC LIMIT 1`).Scan(&latestId, &latestUuid) 124 + if err != nil { 125 + log.Fatalf("query latest user: %v", err) 126 + } 127 + 128 + if targetId == latestId { 129 + log.Printf("user %q (id=%d) is already the most recent user, nothing to do", *name, targetId) 130 + return 131 + } 132 + 133 + log.Printf("target: id=%d uuid=%s (name=%q)", targetId, targetUuid, *name) 134 + log.Printf("latest: id=%d uuid=%s (will be deleted)", latestId, latestUuid) 135 + 136 + tx, err := db.Begin() 137 + if err != nil { 138 + log.Fatalf("begin transaction: %v", err) 139 + } 140 + defer tx.Rollback() 141 + 142 + for _, t := range childTables { 143 + if _, err := tx.Exec(fmt.Sprintf(`DELETE FROM %s WHERE user_id = ?`, t), latestId); err != nil { 144 + log.Fatalf("delete from %s: %v", t, err) 145 + } 146 + } 147 + if _, err := tx.Exec(`DELETE FROM users WHERE user_id = ?`, latestId); err != nil { 148 + log.Fatalf("delete latest user: %v", err) 149 + } 150 + 151 + if _, err := tx.Exec(`UPDATE users SET uuid = ? WHERE user_id = ?`, latestUuid, targetId); err != nil { 152 + log.Fatalf("update target uuid: %v", err) 153 + } 154 + 155 + if _, err := tx.Exec(`DELETE FROM sessions WHERE user_id = ?`, targetId); err != nil { 156 + log.Fatalf("delete stale sessions: %v", err) 157 + } 158 + 159 + if err := tx.Commit(); err != nil { 160 + log.Fatalf("commit: %v", err) 161 + } 162 + 163 + fmt.Printf("claimed account:\n") 164 + fmt.Printf(" user %d (%s): uuid changed %s -> %s\n", targetId, *name, targetUuid, latestUuid) 165 + fmt.Printf(" user %d: deleted\n", latestId) 166 + }
+13
server/cmd/dev/color_unix.go
··· 1 + //go:build !windows 2 + 3 + package main 4 + 5 + import ( 6 + "os" 7 + 8 + "golang.org/x/term" 9 + ) 10 + 11 + func colorSupported() bool { 12 + return term.IsTerminal(int(os.Stdout.Fd())) 13 + }
+22
server/cmd/dev/color_windows.go
··· 1 + //go:build windows 2 + 3 + package main 4 + 5 + import ( 6 + "os" 7 + 8 + "golang.org/x/sys/windows" 9 + "golang.org/x/term" 10 + ) 11 + 12 + func colorSupported() bool { 13 + fd := os.Stdout.Fd() 14 + if !term.IsTerminal(int(fd)) { 15 + return false 16 + } 17 + var mode uint32 18 + if windows.GetConsoleMode(windows.Handle(fd), &mode) != nil { 19 + return false 20 + } 21 + return windows.SetConsoleMode(windows.Handle(fd), mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil 22 + }
+147
server/cmd/dev/main.go
··· 1 + package main 2 + 3 + import ( 4 + "bufio" 5 + "context" 6 + "flag" 7 + "fmt" 8 + "io" 9 + "log" 10 + "os" 11 + "os/exec" 12 + "os/signal" 13 + "sync" 14 + "syscall" 15 + ) 16 + 17 + var ( 18 + colorReset = "\033[0m" 19 + colorRed = "\033[31m" 20 + colorGreen = "\033[32m" 21 + colorYellow = "\033[33m" 22 + colorCyan = "\033[36m" 23 + ) 24 + 25 + type service struct { 26 + label string 27 + color string 28 + cmd *exec.Cmd 29 + } 30 + 31 + func main() { 32 + // auth-server flags 33 + authListen := flag.String("auth.listen", "0.0.0.0:3000", "auth-server listen address (host:port)") 34 + authDB := flag.String("auth.db", "db/auth.db", "auth-server SQLite database path") 35 + 36 + // octo-cdn flags 37 + cdnListen := flag.String("cdn.listen", "0.0.0.0:8080", "octo-cdn local bind address") 38 + cdnPublicAddr := flag.String("cdn.public-addr", "10.0.2.2:8080", "octo-cdn externally-reachable address") 39 + 40 + // lunar-tear (grpc) flags 41 + grpcListen := flag.String("grpc.listen", "0.0.0.0:8003", "lunar-tear gRPC listen address (host:port)") 42 + grpcPublicAddr := flag.String("grpc.public-addr", "10.0.2.2:8003", "lunar-tear externally-reachable address") 43 + grpcOctoURL := flag.String("grpc.octo-url", "", "Octo CDN base URL passed to lunar-tear (default: derived from cdn.public-addr)") 44 + grpcAuthURL := flag.String("grpc.auth-url", "", "auth server base URL passed to lunar-tear (default: derived from auth.listen)") 45 + 46 + noColor := flag.Bool("no-color", false, "disable colored output") 47 + flag.Parse() 48 + 49 + if *grpcOctoURL == "" { 50 + *grpcOctoURL = fmt.Sprintf("http://%s", *cdnPublicAddr) 51 + } 52 + if *grpcAuthURL == "" { 53 + *grpcAuthURL = fmt.Sprintf("http://%s", *authListen) 54 + } 55 + 56 + if *noColor || !colorSupported() { 57 + colorReset = "" 58 + colorRed = "" 59 + colorGreen = "" 60 + colorYellow = "" 61 + colorCyan = "" 62 + } 63 + 64 + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 65 + defer stop() 66 + 67 + services := []service{ 68 + { 69 + label: "auth", 70 + color: colorGreen, 71 + cmd: exec.CommandContext(ctx, "go", "run", "./cmd/auth-server", 72 + "--listen", *authListen, 73 + "--db", *authDB, 74 + ), 75 + }, 76 + { 77 + label: "cdn", 78 + color: colorCyan, 79 + cmd: exec.CommandContext(ctx, "go", "run", "./cmd/octo-cdn", 80 + "--listen", *cdnListen, 81 + "--public-addr", *cdnPublicAddr, 82 + ), 83 + }, 84 + { 85 + label: "grpc", 86 + color: colorYellow, 87 + cmd: exec.CommandContext(ctx, "go", "run", "./cmd/lunar-tear", 88 + "--listen", *grpcListen, 89 + "--public-addr", *grpcPublicAddr, 90 + "--octo-url", *grpcOctoURL, 91 + "--auth-url", *grpcAuthURL, 92 + ), 93 + }, 94 + } 95 + 96 + var wg sync.WaitGroup 97 + errCh := make(chan error, len(services)) 98 + 99 + for i := range services { 100 + svc := &services[i] 101 + stdout, err := svc.cmd.StdoutPipe() 102 + if err != nil { 103 + log.Fatalf("[%s] stdout pipe: %v", svc.label, err) 104 + } 105 + stderr, err := svc.cmd.StderrPipe() 106 + if err != nil { 107 + log.Fatalf("[%s] stderr pipe: %v", svc.label, err) 108 + } 109 + 110 + if err := svc.cmd.Start(); err != nil { 111 + log.Fatalf("[%s] start: %v", svc.label, err) 112 + } 113 + 114 + prefix := fmt.Sprintf("%s[%s]%s ", svc.color, svc.label, colorReset) 115 + wg.Add(2) 116 + go prefixLines(&wg, prefix, stdout) 117 + go prefixLines(&wg, prefix, stderr) 118 + 119 + wg.Add(1) 120 + go func(s *service) { 121 + defer wg.Done() 122 + if err := s.cmd.Wait(); err != nil { 123 + errCh <- fmt.Errorf("[%s] %w", s.label, err) 124 + } 125 + }(svc) 126 + 127 + log.Printf("%s%s started (pid %d)%s", svc.color, svc.label, svc.cmd.Process.Pid, colorReset) 128 + } 129 + 130 + select { 131 + case <-ctx.Done(): 132 + log.Println("shutting down all services...") 133 + case err := <-errCh: 134 + log.Printf("%s%s%s", colorRed, err, colorReset) 135 + stop() 136 + } 137 + 138 + wg.Wait() 139 + } 140 + 141 + func prefixLines(wg *sync.WaitGroup, prefix string, r io.Reader) { 142 + defer wg.Done() 143 + scanner := bufio.NewScanner(r) 144 + for scanner.Scan() { 145 + fmt.Printf("%s%s\n", prefix, scanner.Text()) 146 + } 147 + }
+28 -61
server/cmd/lunar-tear/grpc.go
··· 1 1 package main 2 2 3 3 import ( 4 - "context" 5 - "fmt" 6 4 "log" 7 5 "net" 6 + "strconv" 8 7 9 8 pb "lunar-tear/server/gen/proto" 10 9 "lunar-tear/server/internal/gacha" 11 - "lunar-tear/server/internal/gametime" 10 + "lunar-tear/server/internal/interceptor" 12 11 "lunar-tear/server/internal/masterdata" 13 12 "lunar-tear/server/internal/questflow" 14 13 "lunar-tear/server/internal/service" 15 14 "lunar-tear/server/internal/store" 16 15 17 16 "google.golang.org/grpc" 18 - "google.golang.org/grpc/codes" 19 - "google.golang.org/grpc/metadata" 20 17 "google.golang.org/grpc/reflection" 21 - "google.golang.org/grpc/status" 22 18 ) 23 19 24 20 type loggingListener struct { ··· 36 32 } 37 33 38 34 func startGRPC( 39 - host string, 40 - grpcPort int, 35 + listenAddr string, 36 + publicAddr string, 41 37 octoURL string, 38 + authURL string, 42 39 userStore interface { 43 40 store.UserRepository 44 41 store.SessionRepository ··· 64 61 gameConfig *masterdata.GameConfig, 65 62 sideStoryCatalog *masterdata.SideStoryCatalog, 66 63 bigHuntCatalog *masterdata.BigHuntCatalog, 67 - ) { 68 - addr := fmt.Sprintf(":%d", grpcPort) 69 - lis, err := net.Listen("tcp", addr) 64 + ) *grpc.Server { 65 + lis, err := net.Listen("tcp", listenAddr) 70 66 if err != nil { 71 - log.Fatalf("failed to listen on %s: %v", addr, err) 67 + log.Fatalf("failed to listen on %s: %v", listenAddr, err) 72 68 } 73 69 lis = loggingListener{Listener: lis} 74 70 71 + diffInterceptor := interceptor.NewDiffInterceptor(userStore, userStore) 75 72 grpcServer := grpc.NewServer( 76 - grpc.ChainUnaryInterceptor(loggingInterceptor, timeSyncInterceptor), 77 - grpc.UnknownServiceHandler(loggingUnknownService), 73 + grpc.ChainUnaryInterceptor(interceptor.Platform, interceptor.Logging, diffInterceptor, interceptor.TimeSync), 74 + grpc.UnknownServiceHandler(interceptor.UnknownService), 78 75 ) 79 76 80 77 registerServices(grpcServer, 81 - host, 82 - grpcPort, 78 + publicAddr, 83 79 octoURL, 80 + authURL, 84 81 userStore, 85 82 questEngine, 86 83 gachaHandler, ··· 107 104 108 105 reflection.Register(grpcServer) 109 106 110 - log.Printf("gRPC server listening on %s", addr) 111 - log.Printf("client host address: %s:%d", host, grpcPort) 107 + log.Printf("gRPC server listening on %s", lis.Addr()) 108 + log.Printf("public address: %s", publicAddr) 112 109 113 - if err := grpcServer.Serve(lis); err != nil { 114 - log.Fatalf("failed to serve: %v", err) 115 - } 110 + go func() { 111 + if err := grpcServer.Serve(lis); err != nil { 112 + log.Printf("gRPC server stopped: %v", err) 113 + } 114 + }() 115 + return grpcServer 116 116 } 117 117 118 118 func registerServices( 119 119 srv *grpc.Server, 120 - host string, 121 - grpcPort int, 120 + publicAddr string, 122 121 octoURL string, 122 + authURL string, 123 123 userStore interface { 124 124 store.UserRepository 125 125 store.SessionRepository ··· 146 146 sideStoryCatalog *masterdata.SideStoryCatalog, 147 147 bigHuntCatalog *masterdata.BigHuntCatalog, 148 148 ) { 149 + pubHost, pubPortStr, _ := net.SplitHostPort(publicAddr) 150 + pubPort, _ := strconv.Atoi(pubPortStr) 151 + 149 152 pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(gachaEntries)) 150 - pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore)) 153 + pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL)) 151 154 pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore)) 152 - pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(host, int32(grpcPort), octoURL)) 155 + pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(pubHost, int32(pubPort), octoURL)) 153 156 pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore)) 154 157 pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, questEngine)) 155 158 pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, gachaEntries, gachaHandler)) ··· 184 187 pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, bigHuntCatalog, questEngine)) 185 188 pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, bigHuntCatalog, questEngine.Granter)) 186 189 } 187 - 188 - func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 189 - log.Printf(">>> %s", info.FullMethod) 190 - resp, err := handler(ctx, req) 191 - if err != nil { 192 - log.Printf("<<< %s ERROR: %v", info.FullMethod, err) 193 - } else { 194 - log.Printf("<<< %s OK", info.FullMethod) 195 - } 196 - return resp, err 197 - } 198 - 199 - func timeSyncInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 200 - resp, err := handler(ctx, req) 201 - switch info.FullMethod { 202 - case "/apb.api.user.UserService/Auth", 203 - "/apb.api.user.UserService/RegisterUser", 204 - "/apb.api.user.UserService/TransferUser": 205 - default: 206 - grpc.SetTrailer(ctx, metadata.Pairs( 207 - "x-apb-response-datetime", fmt.Sprintf("%d", gametime.NowMillis()), 208 - )) 209 - } 210 - return resp, err 211 - } 212 - 213 - func loggingUnknownService(_ any, stream grpc.ServerStream) error { 214 - fullMethod, ok := grpc.MethodFromServerStream(stream) 215 - if !ok { 216 - fullMethod = "<unknown>" 217 - } 218 - log.Printf(">>> %s", fullMethod) 219 - err := status.Errorf(codes.Unimplemented, "unknown service or method %s", fullMethod) 220 - log.Printf("<<< %s ERROR: %v", fullMethod, err) 221 - return err 222 - }
-24
server/cmd/lunar-tear/http.go
··· 1 - package main 2 - 3 - import ( 4 - "fmt" 5 - "log" 6 - "net/http" 7 - 8 - "lunar-tear/server/internal/service" 9 - 10 - "golang.org/x/net/http2" 11 - "golang.org/x/net/http2/h2c" 12 - ) 13 - 14 - func startHTTP(port int, resourcesBaseURL string) { 15 - octoServer := service.NewOctoHTTPServer(resourcesBaseURL) 16 - h2s := &http2.Server{} 17 - octoHandler := h2c.NewHandler(octoServer.Handler(), h2s) 18 - log.Printf("Octo HTTP server listening on :%d (HTTP/1.1 + h2c)", port) 19 - srv := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: octoHandler} 20 - http2.ConfigureServer(srv, h2s) 21 - if err := srv.ListenAndServe(); err != nil { 22 - log.Fatalf("HTTP server on %d failed: %v", port, err) 23 - } 24 - }
+31 -18
server/cmd/lunar-tear/main.go
··· 1 1 package main 2 2 3 3 import ( 4 + "context" 4 5 "flag" 5 6 "log" 6 - "strconv" 7 - "strings" 7 + "os" 8 + "os/signal" 9 + "syscall" 8 10 9 11 "lunar-tear/server/internal/database" 10 12 "lunar-tear/server/internal/gacha" 11 13 "lunar-tear/server/internal/gametime" 12 14 "lunar-tear/server/internal/masterdata" 15 + "lunar-tear/server/internal/masterdata/memorydb" 13 16 "lunar-tear/server/internal/questflow" 14 17 "lunar-tear/server/internal/store/sqlite" 15 18 ) 16 19 17 20 func main() { 18 - httpPort := flag.Int("http-port", 8080, "HTTP server port (Octo API)") 19 - grpcPort := flag.Int("grpc-port", 443, "gRPC server port") 20 - host := flag.String("host", "127.0.0.1", "hostname the client will connect to") 21 + listen := flag.String("listen", "0.0.0.0:443", "gRPC listen address (host:port)") 22 + publicAddr := flag.String("public-addr", "127.0.0.1:443", "externally-reachable host:port advertised to clients") 21 23 dbPath := flag.String("db", "db/game.db", "SQLite database path") 24 + octoURL := flag.String("octo-url", "", "Octo CDN base URL the client will use for assets (e.g. http://10.0.2.2:8080)") 25 + authURL := flag.String("auth-url", "", "Auth server base URL for Facebook token validation (e.g. http://localhost:3000)") 22 26 flag.Parse() 23 27 24 - octoURL := "http://" + *host + ":" + strconv.Itoa(*httpPort) 25 - prefix := octoURL + "/" 26 - padLen := 43 - len(prefix) 27 - resourcesBaseURL := "" 28 - if padLen < 1 { 29 - log.Printf("[config] host:port too long for 43-char resource URL; list.bin will be served unchanged") 30 - } else { 31 - resourcesBaseURL = prefix + strings.Repeat("r", padLen) 28 + if *octoURL == "" { 29 + log.Fatalf("--octo-url is required (e.g. http://10.0.2.2:8080)") 32 30 } 33 31 34 - go startHTTP(*httpPort, resourcesBaseURL) 32 + if err := memorydb.Init("assets/release/20240404193219.bin.e"); err != nil { 33 + log.Fatalf("load master data: %v", err) 34 + } 35 + log.Printf("master data loaded (%d tables)", memorydb.TableCount()) 36 + 37 + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 38 + defer stop() 35 39 36 40 db, err := database.Open(*dbPath) 37 41 if err != nil { ··· 164 168 sideStoryCatalog := masterdata.LoadSideStoryCatalog() 165 169 bigHuntCatalog := masterdata.LoadBigHuntCatalog() 166 170 167 - startGRPC( 168 - *host, 169 - *grpcPort, 170 - octoURL, 171 + grpcServer := startGRPC( 172 + *listen, 173 + *publicAddr, 174 + *octoURL, 175 + *authURL, 171 176 userStore, 172 177 questHandler, 173 178 gachaHandler, ··· 191 196 sideStoryCatalog, 192 197 bigHuntCatalog, 193 198 ) 199 + 200 + <-ctx.Done() 201 + log.Println("shutting down...") 202 + 203 + grpcServer.GracefulStop() 204 + database.Checkpoint(db) 205 + 206 + log.Println("shutdown complete") 194 207 }
+72
server/cmd/octo-cdn/main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "flag" 6 + "fmt" 7 + "log" 8 + "net" 9 + "net/http" 10 + "os" 11 + "os/signal" 12 + "strings" 13 + "syscall" 14 + 15 + "lunar-tear/server/internal/service" 16 + 17 + "golang.org/x/net/http2" 18 + "golang.org/x/net/http2/h2c" 19 + ) 20 + 21 + func main() { 22 + listen := flag.String("listen", "0.0.0.0:8080", "local bind address (host:port)") 23 + publicAddr := flag.String("public-addr", "127.0.0.1:8080", "externally-reachable host:port used for list.bin URL rewriting") 24 + assetsDir := flag.String("assets-dir", ".", "root directory containing the assets/ tree") 25 + flag.Parse() 26 + 27 + // Build resourcesBaseURL from public-addr (must be exactly 43 chars to fit in list.bin protobuf). 28 + prefix := "http://" + *publicAddr + "/" 29 + padLen := 43 - len(prefix) 30 + resourcesBaseURL := "" 31 + if padLen < 1 { 32 + log.Printf("[config] public-addr too long for 43-char resource URL; list.bin will be served unchanged") 33 + } else { 34 + resourcesBaseURL = prefix + strings.Repeat("r", padLen) 35 + } 36 + 37 + octoServer := service.NewOctoHTTPServer(resourcesBaseURL, *assetsDir) 38 + h2s := &http2.Server{} 39 + handler := h2c.NewHandler(octoServer.Handler(), h2s) 40 + 41 + srv := &http.Server{ 42 + Addr: *listen, 43 + Handler: handler, 44 + } 45 + http2.ConfigureServer(srv, h2s) 46 + 47 + // Resolve actual listen address for logging (useful when port is 0). 48 + lis, err := net.Listen("tcp", *listen) 49 + if err != nil { 50 + log.Fatalf("failed to listen on %s: %v", *listen, err) 51 + } 52 + log.Printf("Octo CDN listening on %s (HTTP/1.1 + h2c)", lis.Addr()) 53 + log.Printf("public address: %s", *publicAddr) 54 + if *assetsDir != "." { 55 + log.Printf("assets directory: %s", *assetsDir) 56 + } 57 + 58 + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) 59 + defer stop() 60 + 61 + go func() { 62 + if err := srv.Serve(lis); err != nil && err != http.ErrServerClosed { 63 + fmt.Fprintf(os.Stderr, "HTTP server error: %v\n", err) 64 + os.Exit(1) 65 + } 66 + }() 67 + 68 + <-ctx.Done() 69 + log.Println("shutting down...") 70 + srv.Shutdown(context.Background()) 71 + log.Println("shutdown complete") 72 + }
+30 -4
server/docker-compose.yaml
··· 1 + name: lunar-tear 2 + 1 3 services: 2 4 server: 3 5 build: . 4 6 image: kretts/lunar-tear:latest 5 7 environment: 6 - LUNAR_HOST: 127.0.0.1 7 - LUNAR_HTTP_PORT: 8080 8 - LUNAR_GRPC_PORT: 8003 8 + LUNAR_LISTEN: 0.0.0.0:8003 9 + LUNAR_PUBLIC_ADDR: 127.0.0.1:8003 10 + LUNAR_OCTO_URL: http://cdn:8080 11 + LUNAR_AUTH_URL: http://auth:3000 9 12 volumes: 10 - - ./assets:/opt/lunar-tear/assets 11 13 - ./db:/opt/lunar-tear/db 14 + - ./assets:/opt/lunar-tear/assets 12 15 ports: 13 16 - 8003:8003 17 + depends_on: 18 + - cdn 19 + - auth 20 + 21 + cdn: 22 + build: 23 + context: . 24 + dockerfile: Dockerfile.cdn 25 + image: kretts/octo-cdn:latest 26 + command: ["--listen", "0.0.0.0:8080", "--public-addr", "10.0.2.2:8080"] 27 + volumes: 28 + - ./assets:/opt/octo-cdn/assets 29 + ports: 14 30 - 8080:8080 15 31 32 + auth: 33 + build: 34 + context: . 35 + dockerfile: Dockerfile.auth 36 + image: kretts/auth-server:latest 37 + command: ["--listen", "0.0.0.0:3000", "--db", "db/auth.db"] 38 + volumes: 39 + - ./db:/opt/auth-server/db 40 + ports: 41 + - 3000:3000
+10 -1
server/entrypoint.sh
··· 4 4 mkdir -p db 5 5 goose -dir migrations sqlite3 db/game.db up 6 6 7 - exec ./lunar-tear --host "${LUNAR_HOST}" --http-port "${LUNAR_HTTP_PORT}" --grpc-port "${LUNAR_GRPC_PORT:-443}" 7 + AUTH_FLAG="" 8 + if [ -n "${LUNAR_AUTH_URL}" ]; then 9 + AUTH_FLAG="--auth-url ${LUNAR_AUTH_URL}" 10 + fi 11 + 12 + exec ./lunar-tear \ 13 + --listen "${LUNAR_LISTEN:-0.0.0.0:443}" \ 14 + --public-addr "${LUNAR_PUBLIC_ADDR}" \ 15 + --octo-url "${LUNAR_OCTO_URL}" \ 16 + ${AUTH_FLAG}
+7 -9
server/go.mod
··· 4 4 5 5 require ( 6 6 github.com/google/uuid v1.6.0 7 + github.com/pierrec/lz4/v4 v4.1.26 7 8 github.com/vmihailenco/msgpack/v5 v5.4.1 8 - golang.org/x/net v0.50.0 9 + golang.org/x/crypto v0.50.0 10 + golang.org/x/net v0.52.0 11 + golang.org/x/sys v0.43.0 12 + golang.org/x/term v0.42.0 9 13 google.golang.org/grpc v1.79.1 10 14 google.golang.org/protobuf v1.36.11 15 + modernc.org/sqlite v1.48.2 11 16 ) 12 17 13 18 require ( 14 19 github.com/dustin/go-humanize v1.0.1 // indirect 15 20 github.com/mattn/go-isatty v0.0.20 // indirect 16 - github.com/mfridman/interpolate v0.0.2 // indirect 17 21 github.com/ncruces/go-strftime v1.0.0 // indirect 18 - github.com/pressly/goose/v3 v3.27.0 // indirect 19 22 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 20 - github.com/sethvargo/go-retry v0.3.0 // indirect 21 23 github.com/stretchr/testify v1.11.1 // indirect 22 24 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 23 25 go.opentelemetry.io/otel v1.40.0 // indirect 24 - go.uber.org/multierr v1.11.0 // indirect 25 - golang.org/x/sync v0.19.0 // indirect 26 - golang.org/x/sys v0.42.0 // indirect 27 - golang.org/x/text v0.34.0 // indirect 26 + golang.org/x/text v0.36.0 // indirect 28 27 google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect 29 28 modernc.org/libc v1.70.0 // indirect 30 29 modernc.org/mathutil v1.7.1 // indirect 31 30 modernc.org/memory v1.11.0 // indirect 32 - modernc.org/sqlite v1.48.2 // indirect 33 31 )
+42 -16
server/go.sum
··· 12 12 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 13 13 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 14 14 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 15 + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= 16 + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= 15 17 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 16 18 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 19 + github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 20 + github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 17 21 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 18 22 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 19 - github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= 20 - github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= 21 23 github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= 22 24 github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 25 + github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= 26 + github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= 23 27 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 28 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 - github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= 26 - github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= 27 29 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 28 30 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 29 - github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= 30 - github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= 31 31 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 32 32 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 33 33 github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= ··· 46 46 go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= 47 47 go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= 48 48 go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= 49 - go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 50 - go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 51 - golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= 52 - golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= 53 - golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= 54 - golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 49 + golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= 50 + golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= 51 + golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= 52 + golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= 53 + golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= 54 + golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= 55 + golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= 56 + golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 55 57 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 - golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= 57 - golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 58 - golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= 59 - golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= 58 + golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= 59 + golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 60 + golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= 61 + golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= 62 + golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= 63 + golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= 64 + golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= 65 + golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= 60 66 gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= 61 67 gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= 62 68 google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ= ··· 67 73 google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 68 74 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 69 75 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 76 + modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= 77 + modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= 78 + modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw= 79 + modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0= 80 + modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= 81 + modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= 82 + modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= 83 + modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= 84 + modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= 85 + modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= 86 + modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= 87 + modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= 70 88 modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw= 71 89 modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo= 72 90 modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= 73 91 modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= 74 92 modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= 75 93 modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= 94 + modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= 95 + modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= 96 + modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= 97 + modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= 76 98 modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c= 77 99 modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig= 100 + modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= 101 + modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= 102 + modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 103 + modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+7
server/internal/database/database.go
··· 3 3 import ( 4 4 "database/sql" 5 5 "fmt" 6 + "log" 6 7 "os" 7 8 "path/filepath" 8 9 ··· 40 41 41 42 return db, nil 42 43 } 44 + 45 + func Checkpoint(db *sql.DB) { 46 + if _, err := db.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil { 47 + log.Printf("WAL checkpoint: %v", err) 48 + } 49 + }
+130
server/internal/interceptor/diff.go
··· 1 + package interceptor 2 + 3 + import ( 4 + "context" 5 + "log" 6 + "reflect" 7 + "sort" 8 + "strings" 9 + "sync" 10 + 11 + pb "lunar-tear/server/gen/proto" 12 + "lunar-tear/server/internal/service" 13 + "lunar-tear/server/internal/store" 14 + "lunar-tear/server/internal/userdata" 15 + 16 + "google.golang.org/grpc" 17 + "google.golang.org/grpc/metadata" 18 + ) 19 + 20 + type diffUserDataGetter interface { 21 + GetDiffUserData() map[string]*pb.DiffData 22 + } 23 + 24 + var ( 25 + diffFieldCache sync.Map // map[reflect.Type]bool 26 + diffDataMapTyp = reflect.TypeFor[map[string]*pb.DiffData]() 27 + ) 28 + 29 + func hasDiffField(resp any) bool { 30 + t := reflect.TypeOf(resp) 31 + if cached, ok := diffFieldCache.Load(t); ok { 32 + return cached.(bool) 33 + } 34 + elem := t 35 + if elem.Kind() == reflect.Pointer { 36 + elem = elem.Elem() 37 + } 38 + f, ok := elem.FieldByName("DiffUserData") 39 + result := ok && f.Type == diffDataMapTyp 40 + diffFieldCache.Store(t, result) 41 + return result 42 + } 43 + 44 + func NewDiffInterceptor( 45 + users store.UserRepository, 46 + sessions store.SessionRepository, 47 + ) grpc.UnaryServerInterceptor { 48 + return func( 49 + ctx context.Context, 50 + req any, 51 + info *grpc.UnaryServerInfo, 52 + handler grpc.UnaryHandler, 53 + ) (any, error) { 54 + if skipDiffForMethod(info.FullMethod) { 55 + return handler(ctx, req) 56 + } 57 + 58 + userId := service.CurrentUserId(ctx, users, sessions) 59 + if userId == 0 { 60 + return handler(ctx, req) 61 + } 62 + 63 + before, err := users.LoadUser(userId) 64 + if err != nil { 65 + return handler(ctx, req) 66 + } 67 + 68 + resp, handlerErr := handler(ctx, req) 69 + if handlerErr != nil || resp == nil { 70 + return resp, handlerErr 71 + } 72 + 73 + if !hasDiffField(resp) { 74 + return resp, nil 75 + } 76 + 77 + if getter, ok := resp.(diffUserDataGetter); ok { 78 + if existing := getter.GetDiffUserData(); len(existing) > 0 { 79 + setUpdateNamesTrailer(ctx, existing) 80 + return resp, nil 81 + } 82 + } 83 + 84 + after, err := users.LoadUser(userId) 85 + if err != nil { 86 + return resp, nil 87 + } 88 + 89 + changed := userdata.ChangedTables(&before, &after) 90 + if len(changed) == 0 { 91 + return resp, nil 92 + } 93 + 94 + diff := userdata.ComputeDelta(&before, &after, changed) 95 + reflect.ValueOf(resp).Elem().FieldByName("DiffUserData").Set(reflect.ValueOf(diff)) 96 + setUpdateNamesTrailer(ctx, diff) 97 + 98 + return resp, nil 99 + } 100 + } 101 + 102 + func skipDiffForMethod(method string) bool { 103 + switch method { 104 + case "/apb.api.user.UserService/Auth", 105 + "/apb.api.user.UserService/RegisterUser", 106 + "/apb.api.user.UserService/TransferUser", 107 + "/apb.api.user.UserService/TransferUserByFacebook", 108 + "/apb.api.config.ConfigService/GetConfig", 109 + "/apb.api.data.DataService/GetLatestMasterDataVersion", 110 + "/apb.api.data.DataService/GetUserDataNameV2", 111 + "/apb.api.data.DataService/GetUserData": 112 + return true 113 + } 114 + return false 115 + } 116 + 117 + func setUpdateNamesTrailer(ctx context.Context, diff map[string]*pb.DiffData) { 118 + if len(diff) == 0 { 119 + return 120 + } 121 + keys := make([]string, 0, len(diff)) 122 + for key := range diff { 123 + keys = append(keys, key) 124 + } 125 + sort.Strings(keys) 126 + value := strings.Join(keys, ",") 127 + if err := grpc.SetTrailer(ctx, metadata.Pairs("x-apb-update-user-data-names", value)); err != nil { 128 + log.Printf("[DiffInterceptor] failed to set trailer: %v", err) 129 + } 130 + }
+32
server/internal/interceptor/logging.go
··· 1 + package interceptor 2 + 3 + import ( 4 + "context" 5 + "log" 6 + 7 + "google.golang.org/grpc" 8 + "google.golang.org/grpc/codes" 9 + "google.golang.org/grpc/status" 10 + ) 11 + 12 + func Logging(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 13 + log.Printf(">>> %s", info.FullMethod) 14 + resp, err := handler(ctx, req) 15 + if err != nil { 16 + log.Printf("<<< %s ERROR: %v", info.FullMethod, err) 17 + } else { 18 + log.Printf("<<< %s OK", info.FullMethod) 19 + } 20 + return resp, err 21 + } 22 + 23 + func UnknownService(_ any, stream grpc.ServerStream) error { 24 + fullMethod, ok := grpc.MethodFromServerStream(stream) 25 + if !ok { 26 + fullMethod = "<unknown>" 27 + } 28 + log.Printf(">>> %s", fullMethod) 29 + err := status.Errorf(codes.Unimplemented, "unknown service or method %s", fullMethod) 30 + log.Printf("<<< %s ERROR: %v", fullMethod, err) 31 + return err 32 + }
+15
server/internal/interceptor/platform.go
··· 1 + package interceptor 2 + 3 + import ( 4 + "context" 5 + 6 + "lunar-tear/server/internal/model" 7 + 8 + "google.golang.org/grpc" 9 + ) 10 + 11 + func Platform(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 12 + p := model.ClientPlatformFromHeaders(ctx) 13 + ctx = model.NewContextWithPlatform(ctx, p) 14 + return handler(ctx, req) 15 + }
+25
server/internal/interceptor/timesync.go
··· 1 + package interceptor 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + 7 + "lunar-tear/server/internal/gametime" 8 + 9 + "google.golang.org/grpc" 10 + "google.golang.org/grpc/metadata" 11 + ) 12 + 13 + func TimeSync(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 14 + resp, err := handler(ctx, req) 15 + switch info.FullMethod { 16 + case "/apb.api.user.UserService/Auth", 17 + "/apb.api.user.UserService/RegisterUser", 18 + "/apb.api.user.UserService/TransferUser": 19 + default: 20 + grpc.SetTrailer(ctx, metadata.Pairs( 21 + "x-apb-response-datetime", fmt.Sprintf("%d", gametime.NowMillis()), 22 + )) 23 + } 24 + return resp, err 25 + }
+10 -77
server/internal/masterdata/bighunt.go
··· 8 8 "lunar-tear/server/internal/utils" 9 9 ) 10 10 11 - type bigHuntBossQuestRow struct { 12 - BigHuntBossQuestId int32 `json:"BigHuntBossQuestId"` 13 - BigHuntBossId int32 `json:"BigHuntBossId"` 14 - BigHuntQuestGroupId int32 `json:"BigHuntQuestGroupId"` 15 - BigHuntBossQuestScoreCoefficientId int32 `json:"BigHuntBossQuestScoreCoefficientId"` 16 - BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"` 17 - DailyChallengeCount int32 `json:"DailyChallengeCount"` 18 - } 19 - 20 11 type BigHuntBossQuestRow struct { 21 12 BigHuntBossQuestId int32 22 13 BigHuntBossId int32 ··· 25 16 DailyChallengeCount int32 26 17 } 27 18 28 - type bigHuntQuestRow struct { 29 - BigHuntQuestId int32 `json:"BigHuntQuestId"` 30 - QuestId int32 `json:"QuestId"` 31 - BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"` 32 - } 33 - 34 19 type BigHuntQuestRow struct { 35 20 BigHuntQuestId int32 36 21 QuestId int32 37 22 BigHuntQuestScoreCoefficientId int32 38 23 } 39 24 40 - type bigHuntQuestScoreCoefficientRow struct { 41 - BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"` 42 - ScoreDifficultBonusPermil int32 `json:"ScoreDifficultBonusPermil"` 43 - } 44 - 45 - type bigHuntBossRow struct { 46 - BigHuntBossId int32 `json:"BigHuntBossId"` 47 - BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"` 48 - AttributeType int32 `json:"AttributeType"` 49 - } 50 - 51 25 type BigHuntBossRow struct { 52 26 BigHuntBossId int32 53 27 BigHuntBossGradeGroupId int32 54 28 AttributeType int32 55 29 } 56 30 57 - type bigHuntBossGradeGroupRow struct { 58 - BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"` 59 - NecessaryScore int64 `json:"NecessaryScore"` 60 - AssetGradeIconId int32 `json:"AssetGradeIconId"` 61 - } 62 - 63 31 type GradeThreshold struct { 64 32 NecessaryScore int64 65 33 AssetGradeIconId int32 66 34 } 67 35 68 - type bigHuntScheduleRow struct { 69 - BigHuntScheduleId int32 `json:"BigHuntScheduleId"` 70 - ChallengeStartDatetime int64 `json:"ChallengeStartDatetime"` 71 - ChallengeEndDatetime int64 `json:"ChallengeEndDatetime"` 72 - } 73 - 74 - type scoreRewardScheduleRow struct { 75 - BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"` 76 - GroupIndex int32 `json:"GroupIndex"` 77 - BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"` 78 - StartDatetime int64 `json:"StartDatetime"` 79 - } 80 - 81 36 type ScoreRewardScheduleEntry struct { 82 37 BigHuntScoreRewardGroupId int32 83 38 StartDatetime int64 84 39 } 85 40 86 - type scoreRewardGroupRow struct { 87 - BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"` 88 - NecessaryScore int64 `json:"NecessaryScore"` 89 - BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"` 90 - } 91 - 92 41 type ScoreRewardThreshold struct { 93 42 NecessaryScore int64 94 43 BigHuntRewardGroupId int32 95 44 } 96 45 97 - type rewardGroupRow struct { 98 - BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"` 99 - SortOrder int32 `json:"SortOrder"` 100 - PossessionType int32 `json:"PossessionType"` 101 - PossessionId int32 `json:"PossessionId"` 102 - Count int32 `json:"Count"` 103 - } 104 - 105 46 type RewardItem struct { 106 47 PossessionType int32 107 48 PossessionId int32 108 49 Count int32 109 50 } 110 51 111 - type weeklyRewardScheduleRow struct { 112 - BigHuntWeeklyAttributeScoreRewardGroupScheduleId int32 `json:"BigHuntWeeklyAttributeScoreRewardGroupScheduleId"` 113 - AttributeType int32 `json:"AttributeType"` 114 - GroupIndex int32 `json:"GroupIndex"` 115 - BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"` 116 - StartDatetime int64 `json:"StartDatetime"` 117 - } 118 - 119 52 type BigHuntWeeklyRewardKey struct { 120 53 ScheduleId int32 121 54 AttributeType int32 ··· 189 122 } 190 123 191 124 func LoadBigHuntCatalog() *BigHuntCatalog { 192 - bossQuestRows, err := utils.ReadJSON[bigHuntBossQuestRow]("EntityMBigHuntBossQuestTable.json") 125 + bossQuestRows, err := utils.ReadTable[EntityMBigHuntBossQuest]("m_big_hunt_boss_quest") 193 126 if err != nil { 194 127 log.Fatalf("load big hunt boss quest table: %v", err) 195 128 } ··· 204 137 } 205 138 } 206 139 207 - questRows, err := utils.ReadJSON[bigHuntQuestRow]("EntityMBigHuntQuestTable.json") 140 + questRows, err := utils.ReadTable[EntityMBigHuntQuest]("m_big_hunt_quest") 208 141 if err != nil { 209 142 log.Fatalf("load big hunt quest table: %v", err) 210 143 } ··· 217 150 } 218 151 } 219 152 220 - coeffRows, err := utils.ReadJSON[bigHuntQuestScoreCoefficientRow]("EntityMBigHuntQuestScoreCoefficientTable.json") 153 + coeffRows, err := utils.ReadTable[EntityMBigHuntQuestScoreCoefficient]("m_big_hunt_quest_score_coefficient") 221 154 if err != nil { 222 155 log.Fatalf("load big hunt quest score coefficient table: %v", err) 223 156 } ··· 226 159 scoreCoefficients[r.BigHuntQuestScoreCoefficientId] = r.ScoreDifficultBonusPermil 227 160 } 228 161 229 - bossRows, err := utils.ReadJSON[bigHuntBossRow]("EntityMBigHuntBossTable.json") 162 + bossRows, err := utils.ReadTable[EntityMBigHuntBoss]("m_big_hunt_boss") 230 163 if err != nil { 231 164 log.Fatalf("load big hunt boss table: %v", err) 232 165 } ··· 239 172 } 240 173 } 241 174 242 - gradeRows, err := utils.ReadJSON[bigHuntBossGradeGroupRow]("EntityMBigHuntBossGradeGroupTable.json") 175 + gradeRows, err := utils.ReadTable[EntityMBigHuntBossGradeGroup]("m_big_hunt_boss_grade_group") 243 176 if err != nil { 244 177 log.Fatalf("load big hunt boss grade group table: %v", err) 245 178 } ··· 256 189 }) 257 190 } 258 191 259 - scheduleRows, err := utils.ReadJSON[bigHuntScheduleRow]("EntityMBigHuntScheduleTable.json") 192 + scheduleRows, err := utils.ReadTable[EntityMBigHuntSchedule]("m_big_hunt_schedule") 260 193 if err != nil { 261 194 log.Fatalf("load big hunt schedule table: %v", err) 262 195 } ··· 274 207 } 275 208 } 276 209 277 - rewardSchedRows, err := utils.ReadJSON[scoreRewardScheduleRow]("EntityMBigHuntScoreRewardGroupScheduleTable.json") 210 + rewardSchedRows, err := utils.ReadTable[EntityMBigHuntScoreRewardGroupSchedule]("m_big_hunt_score_reward_group_schedule") 278 211 if err != nil { 279 212 log.Fatalf("load big hunt score reward group schedule table: %v", err) 280 213 } ··· 294 227 }) 295 228 } 296 229 297 - rewardGroupRows, err := utils.ReadJSON[scoreRewardGroupRow]("EntityMBigHuntScoreRewardGroupTable.json") 230 + rewardGroupRows, err := utils.ReadTable[EntityMBigHuntScoreRewardGroup]("m_big_hunt_score_reward_group") 298 231 if err != nil { 299 232 log.Fatalf("load big hunt score reward group table: %v", err) 300 233 } ··· 314 247 }) 315 248 } 316 249 317 - rewardItemRows, err := utils.ReadJSON[rewardGroupRow]("EntityMBigHuntRewardGroupTable.json") 250 + rewardItemRows, err := utils.ReadTable[EntityMBigHuntRewardGroup]("m_big_hunt_reward_group") 318 251 if err != nil { 319 252 log.Fatalf("load big hunt reward group table: %v", err) 320 253 } ··· 327 260 }) 328 261 } 329 262 330 - weeklySchedRows, err := utils.ReadJSON[weeklyRewardScheduleRow]("EntityMBigHuntWeeklyAttributeScoreRewardGroupScheduleTable.json") 263 + weeklySchedRows, err := utils.ReadTable[EntityMBigHuntWeeklyAttributeScoreRewardGroupSchedule]("m_big_hunt_weekly_attribute_score_reward_group_schedule") 331 264 if err != nil { 332 265 log.Fatalf("load big hunt weekly attribute score reward group schedule table: %v", err) 333 266 }
+2 -14
server/internal/masterdata/cageornament.go
··· 5 5 "lunar-tear/server/internal/utils" 6 6 ) 7 7 8 - type cageOrnament struct { 9 - CageOrnamentId int32 `json:"CageOrnamentId"` 10 - CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"` 11 - } 12 - 13 - type cageOrnamentRewardRow struct { 14 - CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"` 15 - PossessionType int32 `json:"PossessionType"` 16 - PossessionId int32 `json:"PossessionId"` 17 - Count int32 `json:"Count"` 18 - } 19 - 20 8 type CageOrnamentReward struct { 21 9 PossessionType int32 22 10 PossessionId int32 ··· 38 26 } 39 27 40 28 func LoadCageOrnamentCatalog() *CageOrnamentCatalog { 41 - ornaments, err := utils.ReadJSON[cageOrnament]("EntityMCageOrnamentTable.json") 29 + ornaments, err := utils.ReadTable[EntityMCageOrnament]("m_cage_ornament") 42 30 if err != nil { 43 31 log.Fatalf("load cage ornament table: %v", err) 44 32 } 45 - rewards, err := utils.ReadJSON[cageOrnamentRewardRow]("EntityMCageOrnamentRewardTable.json") 33 + rewards, err := utils.ReadTable[EntityMCageOrnamentReward]("m_cage_ornament_reward") 46 34 if err != nil { 47 35 log.Fatalf("load cage ornament reward table: %v", err) 48 36 }
+7 -25
server/internal/masterdata/character_rebirth.go
··· 6 6 "lunar-tear/server/internal/utils" 7 7 ) 8 8 9 - type CharacterRebirthRow struct { 10 - CharacterId int32 `json:"CharacterId"` 11 - CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"` 12 - } 13 - 14 - type CharacterRebirthStepRow struct { 15 - CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"` 16 - BeforeRebirthCount int32 `json:"BeforeRebirthCount"` 17 - CostumeLevelLimitUp int32 `json:"CostumeLevelLimitUp"` 18 - CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"` 19 - } 20 - 21 - type CharacterRebirthMaterialRow struct { 22 - CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"` 23 - MaterialId int32 `json:"MaterialId"` 24 - Count int32 `json:"Count"` 25 - } 26 - 27 9 type StepKey struct { 28 10 GroupId int32 29 11 BeforeRebirthCount int32 ··· 31 13 32 14 type CharacterRebirthCatalog struct { 33 15 StepGroupByCharacterId map[int32]int32 34 - StepByGroupAndCount map[StepKey]CharacterRebirthStepRow 35 - MaterialsByGroupId map[int32][]CharacterRebirthMaterialRow 16 + StepByGroupAndCount map[StepKey]EntityMCharacterRebirthStepGroup 17 + MaterialsByGroupId map[int32][]EntityMCharacterRebirthMaterialGroup 36 18 } 37 19 38 20 func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) { 39 - rebirthRows, err := utils.ReadJSON[CharacterRebirthRow]("EntityMCharacterRebirthTable.json") 21 + rebirthRows, err := utils.ReadTable[EntityMCharacterRebirth]("m_character_rebirth") 40 22 if err != nil { 41 23 return nil, fmt.Errorf("load character rebirth table: %w", err) 42 24 } 43 25 44 - stepRows, err := utils.ReadJSON[CharacterRebirthStepRow]("EntityMCharacterRebirthStepGroupTable.json") 26 + stepRows, err := utils.ReadTable[EntityMCharacterRebirthStepGroup]("m_character_rebirth_step_group") 45 27 if err != nil { 46 28 return nil, fmt.Errorf("load character rebirth step group table: %w", err) 47 29 } 48 30 49 - materialRows, err := utils.ReadJSON[CharacterRebirthMaterialRow]("EntityMCharacterRebirthMaterialGroupTable.json") 31 + materialRows, err := utils.ReadTable[EntityMCharacterRebirthMaterialGroup]("m_character_rebirth_material_group") 50 32 if err != nil { 51 33 return nil, fmt.Errorf("load character rebirth material group table: %w", err) 52 34 } ··· 56 38 stepGroupByCharacterId[r.CharacterId] = r.CharacterRebirthStepGroupId 57 39 } 58 40 59 - stepByGroupAndCount := make(map[StepKey]CharacterRebirthStepRow, len(stepRows)) 41 + stepByGroupAndCount := make(map[StepKey]EntityMCharacterRebirthStepGroup, len(stepRows)) 60 42 for _, s := range stepRows { 61 43 stepByGroupAndCount[StepKey{GroupId: s.CharacterRebirthStepGroupId, BeforeRebirthCount: s.BeforeRebirthCount}] = s 62 44 } 63 45 64 - materialsByGroupId := make(map[int32][]CharacterRebirthMaterialRow) 46 + materialsByGroupId := make(map[int32][]EntityMCharacterRebirthMaterialGroup) 65 47 for _, m := range materialRows { 66 48 materialsByGroupId[m.CharacterRebirthMaterialGroupId] = append(materialsByGroupId[m.CharacterRebirthMaterialGroupId], m) 67 49 }
+24 -84
server/internal/masterdata/characterboard.go
··· 7 7 "lunar-tear/server/internal/utils" 8 8 ) 9 9 10 - type CharacterBoardPanelRow struct { 11 - CharacterBoardPanelId int32 `json:"CharacterBoardPanelId"` 12 - CharacterBoardId int32 `json:"CharacterBoardId"` 13 - CharacterBoardPanelUnlockConditionGroupId int32 `json:"CharacterBoardPanelUnlockConditionGroupId"` 14 - CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"` 15 - CharacterBoardPanelReleaseRewardGroupId int32 `json:"CharacterBoardPanelReleaseRewardGroupId"` 16 - CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"` 17 - SortOrder int32 `json:"SortOrder"` 18 - ParentCharacterBoardPanelId int32 `json:"ParentCharacterBoardPanelId"` 19 - PlaceIndex int32 `json:"PlaceIndex"` 20 - } 21 - 22 - type CharacterBoardReleasePossessionRow struct { 23 - CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"` 24 - PossessionType int32 `json:"PossessionType"` 25 - PossessionId int32 `json:"PossessionId"` 26 - Count int32 `json:"Count"` 27 - SortOrder int32 `json:"SortOrder"` 28 - } 29 - 30 - type CharacterBoardReleaseEffectRow struct { 31 - CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"` 32 - SortOrder int32 `json:"SortOrder"` 33 - CharacterBoardEffectType int32 `json:"CharacterBoardEffectType"` 34 - CharacterBoardEffectId int32 `json:"CharacterBoardEffectId"` 35 - EffectValue int32 `json:"EffectValue"` 36 - } 37 - 38 - type CharacterBoardRow struct { 39 - CharacterBoardId int32 `json:"CharacterBoardId"` 40 - CharacterBoardGroupId int32 `json:"CharacterBoardGroupId"` 41 - CharacterBoardUnlockConditionGroupId int32 `json:"CharacterBoardUnlockConditionGroupId"` 42 - ReleaseRank int32 `json:"ReleaseRank"` 43 - } 44 - 45 - type CharacterBoardStatusUpRow struct { 46 - CharacterBoardStatusUpId int32 `json:"CharacterBoardStatusUpId"` 47 - CharacterBoardStatusUpType int32 `json:"CharacterBoardStatusUpType"` 48 - CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"` 49 - } 50 - 51 - type CharacterBoardAbilityRow struct { 52 - CharacterBoardAbilityId int32 `json:"CharacterBoardAbilityId"` 53 - CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"` 54 - AbilityId int32 `json:"AbilityId"` 55 - } 56 - 57 - type CharacterBoardAbilityMaxLevelRow struct { 58 - CharacterId int32 `json:"CharacterId"` 59 - AbilityId int32 `json:"AbilityId"` 60 - MaxLevel int32 `json:"MaxLevel"` 61 - } 62 - 63 - type CharacterBoardEffectTargetRow struct { 64 - CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"` 65 - GroupIndex int32 `json:"GroupIndex"` 66 - CharacterBoardEffectTargetType int32 `json:"CharacterBoardEffectTargetType"` 67 - TargetValue int32 `json:"TargetValue"` 68 - } 69 - 70 10 type CharacterBoardAssignmentRow struct { 71 11 CharacterId int32 `json:"CharacterId"` 72 12 CharacterBoardCategoryId int32 `json:"CharacterBoardCategoryId"` ··· 83 23 } 84 24 85 25 type CharacterBoardCatalog struct { 86 - PanelById map[int32]CharacterBoardPanelRow 87 - PanelsByBoardId map[int32][]CharacterBoardPanelRow 88 - ReleaseCostsByGroupId map[int32][]CharacterBoardReleasePossessionRow 89 - ReleaseEffectsByGroupId map[int32][]CharacterBoardReleaseEffectRow 90 - StatusUpById map[int32]CharacterBoardStatusUpRow 91 - AbilityById map[int32]CharacterBoardAbilityRow 26 + PanelById map[int32]EntityMCharacterBoardPanel 27 + PanelsByBoardId map[int32][]EntityMCharacterBoardPanel 28 + ReleaseCostsByGroupId map[int32][]EntityMCharacterBoardPanelReleasePossessionGroup 29 + ReleaseEffectsByGroupId map[int32][]EntityMCharacterBoardPanelReleaseEffectGroup 30 + StatusUpById map[int32]EntityMCharacterBoardStatusUp 31 + AbilityById map[int32]EntityMCharacterBoardAbility 92 32 AbilityMaxLevel map[store.CharacterBoardAbilityKey]int32 93 - EffectTargetsByGroupId map[int32][]CharacterBoardEffectTargetRow 94 - BoardById map[int32]CharacterBoardRow 33 + EffectTargetsByGroupId map[int32][]EntityMCharacterBoardEffectTargetGroup 34 + BoardById map[int32]EntityMCharacterBoard 95 35 } 96 36 97 37 func LoadCharacterBoardCatalog() (*CharacterBoardCatalog, error) { 98 - panels, err := utils.ReadJSON[CharacterBoardPanelRow]("EntityMCharacterBoardPanelTable.json") 38 + panels, err := utils.ReadTable[EntityMCharacterBoardPanel]("m_character_board_panel") 99 39 if err != nil { 100 40 return nil, fmt.Errorf("load character board panel table: %w", err) 101 41 } 102 42 103 - costs, err := utils.ReadJSON[CharacterBoardReleasePossessionRow]("EntityMCharacterBoardPanelReleasePossessionGroupTable.json") 43 + costs, err := utils.ReadTable[EntityMCharacterBoardPanelReleasePossessionGroup]("m_character_board_panel_release_possession_group") 104 44 if err != nil { 105 45 return nil, fmt.Errorf("load character board release possession table: %w", err) 106 46 } 107 47 108 - effects, err := utils.ReadJSON[CharacterBoardReleaseEffectRow]("EntityMCharacterBoardPanelReleaseEffectGroupTable.json") 48 + effects, err := utils.ReadTable[EntityMCharacterBoardPanelReleaseEffectGroup]("m_character_board_panel_release_effect_group") 109 49 if err != nil { 110 50 return nil, fmt.Errorf("load character board release effect table: %w", err) 111 51 } 112 52 113 - boards, err := utils.ReadJSON[CharacterBoardRow]("EntityMCharacterBoardTable.json") 53 + boards, err := utils.ReadTable[EntityMCharacterBoard]("m_character_board") 114 54 if err != nil { 115 55 return nil, fmt.Errorf("load character board table: %w", err) 116 56 } 117 57 118 - statusUps, err := utils.ReadJSON[CharacterBoardStatusUpRow]("EntityMCharacterBoardStatusUpTable.json") 58 + statusUps, err := utils.ReadTable[EntityMCharacterBoardStatusUp]("m_character_board_status_up") 119 59 if err != nil { 120 60 return nil, fmt.Errorf("load character board status up table: %w", err) 121 61 } 122 62 123 - abilities, err := utils.ReadJSON[CharacterBoardAbilityRow]("EntityMCharacterBoardAbilityTable.json") 63 + abilities, err := utils.ReadTable[EntityMCharacterBoardAbility]("m_character_board_ability") 124 64 if err != nil { 125 65 return nil, fmt.Errorf("load character board ability table: %w", err) 126 66 } 127 67 128 - abilityMaxLevels, err := utils.ReadJSON[CharacterBoardAbilityMaxLevelRow]("EntityMCharacterBoardAbilityMaxLevelTable.json") 68 + abilityMaxLevels, err := utils.ReadTable[EntityMCharacterBoardAbilityMaxLevel]("m_character_board_ability_max_level") 129 69 if err != nil { 130 70 return nil, fmt.Errorf("load character board ability max level table: %w", err) 131 71 } 132 72 133 - targets, err := utils.ReadJSON[CharacterBoardEffectTargetRow]("EntityMCharacterBoardEffectTargetGroupTable.json") 73 + targets, err := utils.ReadTable[EntityMCharacterBoardEffectTargetGroup]("m_character_board_effect_target_group") 134 74 if err != nil { 135 75 return nil, fmt.Errorf("load character board effect target table: %w", err) 136 76 } 137 77 138 78 catalog := &CharacterBoardCatalog{ 139 - PanelById: make(map[int32]CharacterBoardPanelRow, len(panels)), 140 - PanelsByBoardId: make(map[int32][]CharacterBoardPanelRow), 141 - ReleaseCostsByGroupId: make(map[int32][]CharacterBoardReleasePossessionRow), 142 - ReleaseEffectsByGroupId: make(map[int32][]CharacterBoardReleaseEffectRow), 143 - StatusUpById: make(map[int32]CharacterBoardStatusUpRow, len(statusUps)), 144 - AbilityById: make(map[int32]CharacterBoardAbilityRow, len(abilities)), 79 + PanelById: make(map[int32]EntityMCharacterBoardPanel, len(panels)), 80 + PanelsByBoardId: make(map[int32][]EntityMCharacterBoardPanel), 81 + ReleaseCostsByGroupId: make(map[int32][]EntityMCharacterBoardPanelReleasePossessionGroup), 82 + ReleaseEffectsByGroupId: make(map[int32][]EntityMCharacterBoardPanelReleaseEffectGroup), 83 + StatusUpById: make(map[int32]EntityMCharacterBoardStatusUp, len(statusUps)), 84 + AbilityById: make(map[int32]EntityMCharacterBoardAbility, len(abilities)), 145 85 AbilityMaxLevel: make(map[store.CharacterBoardAbilityKey]int32, len(abilityMaxLevels)), 146 - EffectTargetsByGroupId: make(map[int32][]CharacterBoardEffectTargetRow), 147 - BoardById: make(map[int32]CharacterBoardRow, len(boards)), 86 + EffectTargetsByGroupId: make(map[int32][]EntityMCharacterBoardEffectTargetGroup), 87 + BoardById: make(map[int32]EntityMCharacterBoard, len(boards)), 148 88 } 149 89 150 90 for _, p := range panels {
+1 -6
server/internal/masterdata/characterviewer.go
··· 9 9 "lunar-tear/server/internal/utils" 10 10 ) 11 11 12 - type characterViewerField struct { 13 - CharacterViewerFieldId int32 `json:"CharacterViewerFieldId"` 14 - ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"` 15 - } 16 - 17 12 type characterViewerFieldEntry struct { 18 13 FieldId int32 19 14 RequiredQuestId int32 ··· 39 34 } 40 35 41 36 func LoadCharacterViewerCatalog(resolver *ConditionResolver) *CharacterViewerCatalog { 42 - fields, err := utils.ReadJSON[characterViewerField]("EntityMCharacterViewerFieldTable.json") 37 + fields, err := utils.ReadTable[EntityMCharacterViewerField]("m_character_viewer_field") 43 38 if err != nil { 44 39 log.Fatalf("load character viewer field table: %v", err) 45 40 }
+5 -22
server/internal/masterdata/companion.go
··· 6 6 "lunar-tear/server/internal/utils" 7 7 ) 8 8 9 - type companionRow struct { 10 - CompanionId int32 `json:"CompanionId"` 11 - CompanionCategoryType int32 `json:"CompanionCategoryType"` 12 - } 13 - 14 - type companionCategoryRow struct { 15 - CompanionCategoryType int32 `json:"CompanionCategoryType"` 16 - EnhancementCostNumericalFunctionId int32 `json:"EnhancementCostNumericalFunctionId"` 17 - } 18 - 19 - type companionEnhancementMaterialRow struct { 20 - CompanionCategoryType int32 `json:"CompanionCategoryType"` 21 - Level int32 `json:"Level"` 22 - MaterialId int32 `json:"MaterialId"` 23 - Count int32 `json:"Count"` 24 - } 25 - 26 9 type CompanionLevelKey struct { 27 10 CategoryType int32 28 11 Level int32 ··· 34 17 } 35 18 36 19 type CompanionCatalog struct { 37 - CompanionById map[int32]companionRow 20 + CompanionById map[int32]EntityMCompanion 38 21 GoldCostByCategory map[int32]NumericalFunc 39 22 MaterialsByKey map[CompanionLevelKey]CompanionMaterialCost 40 23 } 41 24 42 25 func LoadCompanionCatalog() (*CompanionCatalog, error) { 43 - companions, err := utils.ReadJSON[companionRow]("EntityMCompanionTable.json") 26 + companions, err := utils.ReadTable[EntityMCompanion]("m_companion") 44 27 if err != nil { 45 28 return nil, fmt.Errorf("load companion table: %w", err) 46 29 } 47 30 48 - categories, err := utils.ReadJSON[companionCategoryRow]("EntityMCompanionCategoryTable.json") 31 + categories, err := utils.ReadTable[EntityMCompanionCategory]("m_companion_category") 49 32 if err != nil { 50 33 return nil, fmt.Errorf("load companion category table: %w", err) 51 34 } 52 35 53 - materials, err := utils.ReadJSON[companionEnhancementMaterialRow]("EntityMCompanionEnhancementMaterialTable.json") 36 + materials, err := utils.ReadTable[EntityMCompanionEnhancementMaterial]("m_companion_enhancement_material") 54 37 if err != nil { 55 38 return nil, fmt.Errorf("load companion enhancement material table: %w", err) 56 39 } ··· 60 43 return nil, fmt.Errorf("load function resolver: %w", err) 61 44 } 62 45 63 - companionById := make(map[int32]companionRow, len(companions)) 46 + companionById := make(map[int32]EntityMCompanion, len(companions)) 64 47 for _, c := range companions { 65 48 companionById[c.CompanionId] = c 66 49 }
+5 -18
server/internal/masterdata/conditions.go
··· 7 7 "lunar-tear/server/internal/utils" 8 8 ) 9 9 10 - type evaluateCondition struct { 11 - EvaluateConditionId int32 `json:"EvaluateConditionId"` 12 - EvaluateConditionFunctionType model.EvaluateConditionFunctionType `json:"EvaluateConditionFunctionType"` 13 - EvaluateConditionEvaluateType model.EvaluateConditionEvaluateType `json:"EvaluateConditionEvaluateType"` 14 - EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"` 15 - } 16 - 17 - type evaluateConditionValueGroup struct { 18 - EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"` 19 - GroupIndex int32 `json:"GroupIndex"` 20 - Value int64 `json:"Value"` 21 - } 22 - 23 10 const defaultGroupIndex = 1 24 11 25 12 type ConditionResolver struct { ··· 27 14 } 28 15 29 16 func LoadConditionResolver() (*ConditionResolver, error) { 30 - conditions, err := utils.ReadJSON[evaluateCondition]("EntityMEvaluateConditionTable.json") 17 + conditions, err := utils.ReadTable[EntityMEvaluateCondition]("m_evaluate_condition") 31 18 if err != nil { 32 19 return nil, fmt.Errorf("load evaluate condition table: %w", err) 33 20 } 34 - valueGroups, err := utils.ReadJSON[evaluateConditionValueGroup]("EntityMEvaluateConditionValueGroupTable.json") 21 + valueGroups, err := utils.ReadTable[EntityMEvaluateConditionValueGroup]("m_evaluate_condition_value_group") 35 22 if err != nil { 36 23 return nil, fmt.Errorf("load evaluate condition value group table: %w", err) 37 24 } 38 25 39 - condById := make(map[int32]evaluateCondition, len(conditions)) 26 + condById := make(map[int32]EntityMEvaluateCondition, len(conditions)) 40 27 for _, c := range conditions { 41 28 condById[c.EvaluateConditionId] = c 42 29 } ··· 52 39 53 40 resolved := make(map[int32]int32) 54 41 for _, c := range conditions { 55 - if c.EvaluateConditionFunctionType == model.EvaluateConditionFunctionTypeQuestClear && 56 - c.EvaluateConditionEvaluateType == model.EvaluateConditionEvaluateTypeIdContain { 42 + if model.EvaluateConditionFunctionType(c.EvaluateConditionFunctionType) == model.EvaluateConditionFunctionTypeQuestClear && 43 + model.EvaluateConditionEvaluateType(c.EvaluateConditionEvaluateType) == model.EvaluateConditionEvaluateTypeIdContain { 57 44 if questId, ok := vgByKey[vgKey{c.EvaluateConditionValueGroupId, defaultGroupIndex}]; ok { 58 45 resolved[c.EvaluateConditionId] = int32(questId) 59 46 }
+1 -6
server/internal/masterdata/config.go
··· 7 7 "lunar-tear/server/internal/utils" 8 8 ) 9 9 10 - type configRow struct { 11 - ConfigKey string `json:"ConfigKey"` 12 - Value string `json:"Value"` 13 - } 14 - 15 10 type GameConfig struct { 16 11 ConsumableItemIdForGold int32 17 12 ConsumableItemIdForMedal int32 ··· 41 36 } 42 37 43 38 func LoadGameConfig() (*GameConfig, error) { 44 - rows, err := utils.ReadJSON[configRow]("EntityMConfigTable.json") 39 + rows, err := utils.ReadTable[EntityMConfig]("m_config") 45 40 if err != nil { 46 41 return nil, fmt.Errorf("load config table: %w", err) 47 42 }
+3 -8
server/internal/masterdata/consumableitem.go
··· 6 6 "lunar-tear/server/internal/utils" 7 7 ) 8 8 9 - type ConsumableItemRow struct { 10 - ConsumableItemId int32 `json:"ConsumableItemId"` 11 - SellPrice int32 `json:"SellPrice"` 12 - } 13 - 14 9 type ConsumableItemCatalog struct { 15 - All map[int32]ConsumableItemRow 10 + All map[int32]EntityMConsumableItem 16 11 } 17 12 18 13 func LoadConsumableItemCatalog() (*ConsumableItemCatalog, error) { 19 - rows, err := utils.ReadJSON[ConsumableItemRow]("EntityMConsumableItemTable.json") 14 + rows, err := utils.ReadTable[EntityMConsumableItem]("m_consumable_item") 20 15 if err != nil { 21 16 return nil, fmt.Errorf("load consumable item table: %w", err) 22 17 } 23 18 24 19 catalog := &ConsumableItemCatalog{ 25 - All: make(map[int32]ConsumableItemRow, len(rows)), 20 + All: make(map[int32]EntityMConsumableItem, len(rows)), 26 21 } 27 22 for _, row := range rows { 28 23 catalog.All[row.ConsumableItemId] = row
+34 -129
server/internal/masterdata/costume.go
··· 8 8 "lunar-tear/server/internal/utils" 9 9 ) 10 10 11 - type CostumeMasterRow struct { 12 - CostumeId int32 `json:"CostumeId"` 13 - CharacterId int32 `json:"CharacterId"` 14 - SkillfulWeaponType int32 `json:"SkillfulWeaponType"` 15 - RarityType int32 `json:"RarityType"` 16 - CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"` 17 - CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"` 18 - } 19 - 20 - type costumeRarityRow struct { 21 - RarityType int32 `json:"RarityType"` 22 - CostumeLimitBreakMaterialRarityGroupId int32 `json:"CostumeLimitBreakMaterialRarityGroupId"` 23 - RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"` 24 - EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"` 25 - LimitBreakCostNumericalFunctionId int32 `json:"LimitBreakCostNumericalFunctionId"` 26 - MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"` 27 - ActiveSkillMaxLevelNumericalFunctionId int32 `json:"ActiveSkillMaxLevelNumericalFunctionId"` 28 - ActiveSkillEnhancementCostNumericalFunctionId int32 `json:"ActiveSkillEnhancementCostNumericalFunctionId"` 29 - } 30 - 31 - type CostumeAwakenRow struct { 32 - CostumeId int32 `json:"CostumeId"` 33 - CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"` 34 - CostumeAwakenStepMaterialGroupId int32 `json:"CostumeAwakenStepMaterialGroupId"` 35 - CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"` 36 - } 37 - 38 - type costumeAwakenPriceRow struct { 39 - CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"` 40 - AwakenStepLowerLimit int32 `json:"AwakenStepLowerLimit"` 41 - Gold int32 `json:"Gold"` 42 - } 43 - 44 - type CostumeAwakenEffectRow struct { 45 - CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"` 46 - AwakenStep int32 `json:"AwakenStep"` 47 - CostumeAwakenEffectType int32 `json:"CostumeAwakenEffectType"` 48 - CostumeAwakenEffectId int32 `json:"CostumeAwakenEffectId"` 49 - } 50 - 51 - type CostumeAwakenStatusUpRow struct { 52 - CostumeAwakenStatusUpGroupId int32 `json:"CostumeAwakenStatusUpGroupId"` 53 - SortOrder int32 `json:"SortOrder"` 54 - StatusKindType int32 `json:"StatusKindType"` 55 - StatusCalculationType int32 `json:"StatusCalculationType"` 56 - EffectValue int32 `json:"EffectValue"` 57 - } 58 - 59 - type CostumeAwakenItemAcquireRow struct { 60 - CostumeAwakenItemAcquireId int32 `json:"CostumeAwakenItemAcquireId"` 61 - PossessionType int32 `json:"PossessionType"` 62 - PossessionId int32 `json:"PossessionId"` 63 - Count int32 `json:"Count"` 64 - } 65 - 66 - type CostumeActiveSkillGroupRow struct { 67 - CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"` 68 - CostumeLimitBreakCountLowerLimit int32 `json:"CostumeLimitBreakCountLowerLimit"` 69 - CostumeActiveSkillId int32 `json:"CostumeActiveSkillId"` 70 - CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"` 71 - } 72 - 73 - type CostumeActiveSkillEnhanceMaterialRow struct { 74 - CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"` 75 - SkillLevel int32 `json:"SkillLevel"` 76 - MaterialId int32 `json:"MaterialId"` 77 - Count int32 `json:"Count"` 78 - SortOrder int32 `json:"SortOrder"` 79 - } 80 - 81 - type CostumeLotteryEffectRow struct { 82 - CostumeId int32 `json:"CostumeId"` 83 - SlotNumber int32 `json:"SlotNumber"` 84 - CostumeLotteryEffectOddsGroupId int32 `json:"CostumeLotteryEffectOddsGroupId"` 85 - CostumeLotteryEffectUnlockMaterialGroupId int32 `json:"CostumeLotteryEffectUnlockMaterialGroupId"` 86 - CostumeLotteryEffectDrawMaterialGroupId int32 `json:"CostumeLotteryEffectDrawMaterialGroupId"` 87 - CostumeLotteryEffectReleaseScheduleId int32 `json:"CostumeLotteryEffectReleaseScheduleId"` 88 - } 89 - 90 - type CostumeLotteryEffectMaterialGroupRow struct { 91 - CostumeLotteryEffectMaterialGroupId int32 `json:"CostumeLotteryEffectMaterialGroupId"` 92 - MaterialId int32 `json:"MaterialId"` 93 - Count int32 `json:"Count"` 94 - SortOrder int32 `json:"SortOrder"` 95 - } 96 - 97 - type CostumeLotteryEffectOddsRow struct { 98 - CostumeLotteryEffectOddsGroupId int32 `json:"CostumeLotteryEffectOddsGroupId"` 99 - OddsNumber int32 `json:"OddsNumber"` 100 - Weight int32 `json:"Weight"` 101 - CostumeLotteryEffectType int32 `json:"CostumeLotteryEffectType"` 102 - CostumeLotteryEffectTargetId int32 `json:"CostumeLotteryEffectTargetId"` 103 - RarityType int32 `json:"RarityType"` 104 - } 105 - 106 11 type CostumeCatalog struct { 107 - Costumes map[int32]CostumeMasterRow 108 - Materials map[int32]MaterialRow 12 + Costumes map[int32]EntityMCostume 13 + Materials map[int32]EntityMMaterial 109 14 ExpByRarity map[int32][]int32 110 15 EnhanceCostByRarity map[int32]NumericalFunc 111 16 MaxLevelByRarity map[int32]NumericalFunc 112 17 LimitBreakCostByRarity map[int32]NumericalFunc 113 18 114 - AwakenByCostumeId map[int32]CostumeAwakenRow 19 + AwakenByCostumeId map[int32]EntityMCostumeAwaken 115 20 AwakenPriceByGroup map[int32]int32 116 - AwakenEffectsByGroupAndStep map[int32]map[int32]CostumeAwakenEffectRow 117 - AwakenStatusUpByGroup map[int32][]CostumeAwakenStatusUpRow 118 - AwakenItemAcquireById map[int32]CostumeAwakenItemAcquireRow 21 + AwakenEffectsByGroupAndStep map[int32]map[int32]EntityMCostumeAwakenEffectGroup 22 + AwakenStatusUpByGroup map[int32][]EntityMCostumeAwakenStatusUpGroup 23 + AwakenItemAcquireById map[int32]EntityMCostumeAwakenItemAcquire 119 24 120 - ActiveSkillGroupsByGroupId map[int32][]CostumeActiveSkillGroupRow // sorted by CostumeLimitBreakCountLowerLimit desc 121 - ActiveSkillEnhanceMats map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel] 25 + ActiveSkillGroupsByGroupId map[int32][]EntityMCostumeActiveSkillGroup // sorted by CostumeLimitBreakCountLowerLimit desc 26 + ActiveSkillEnhanceMats map[[2]int32][]EntityMCostumeActiveSkillEnhancementMaterial // key: [enhancementMaterialId, skillLevel] 122 27 ActiveSkillMaxLevelByRarity map[int32]NumericalFunc 123 28 ActiveSkillCostByRarity map[int32]NumericalFunc 124 29 125 - LotteryEffects map[[2]int32]CostumeLotteryEffectRow // key: [costumeId, slotNumber] 126 - LotteryEffectMats map[int32][]CostumeLotteryEffectMaterialGroupRow // key: materialGroupId (both unlock and draw) 127 - LotteryEffectOdds map[int32][]CostumeLotteryEffectOddsRow // key: oddsGroupId 30 + LotteryEffects map[[2]int32]EntityMCostumeLotteryEffect // key: [costumeId, slotNumber] 31 + LotteryEffectMats map[int32][]EntityMCostumeLotteryEffectMaterialGroup // key: materialGroupId (both unlock and draw) 32 + LotteryEffectOdds map[int32][]EntityMCostumeLotteryEffectOddsGroup // key: oddsGroupId 128 33 } 129 34 130 35 func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) { 131 - costumes, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json") 36 + costumes, err := utils.ReadTable[EntityMCostume]("m_costume") 132 37 if err != nil { 133 38 return nil, fmt.Errorf("load costume table: %w", err) 134 39 } 135 40 136 - rarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json") 41 + rarities, err := utils.ReadTable[EntityMCostumeRarity]("m_costume_rarity") 137 42 if err != nil { 138 43 return nil, fmt.Errorf("load costume rarity table: %w", err) 139 44 } ··· 148 53 return nil, fmt.Errorf("load function resolver: %w", err) 149 54 } 150 55 151 - awakenRows, err := utils.ReadJSON[CostumeAwakenRow]("EntityMCostumeAwakenTable.json") 56 + awakenRows, err := utils.ReadTable[EntityMCostumeAwaken]("m_costume_awaken") 152 57 if err != nil { 153 58 return nil, fmt.Errorf("load costume awaken table: %w", err) 154 59 } 155 - awakenPriceRows, err := utils.ReadJSON[costumeAwakenPriceRow]("EntityMCostumeAwakenPriceGroupTable.json") 60 + awakenPriceRows, err := utils.ReadTable[EntityMCostumeAwakenPriceGroup]("m_costume_awaken_price_group") 156 61 if err != nil { 157 62 return nil, fmt.Errorf("load costume awaken price table: %w", err) 158 63 } 159 - awakenEffectRows, err := utils.ReadJSON[CostumeAwakenEffectRow]("EntityMCostumeAwakenEffectGroupTable.json") 64 + awakenEffectRows, err := utils.ReadTable[EntityMCostumeAwakenEffectGroup]("m_costume_awaken_effect_group") 160 65 if err != nil { 161 66 return nil, fmt.Errorf("load costume awaken effect table: %w", err) 162 67 } 163 - awakenStatusUpRows, err := utils.ReadJSON[CostumeAwakenStatusUpRow]("EntityMCostumeAwakenStatusUpGroupTable.json") 68 + awakenStatusUpRows, err := utils.ReadTable[EntityMCostumeAwakenStatusUpGroup]("m_costume_awaken_status_up_group") 164 69 if err != nil { 165 70 return nil, fmt.Errorf("load costume awaken status up table: %w", err) 166 71 } 167 - awakenItemAcquireRows, err := utils.ReadJSON[CostumeAwakenItemAcquireRow]("EntityMCostumeAwakenItemAcquireTable.json") 72 + awakenItemAcquireRows, err := utils.ReadTable[EntityMCostumeAwakenItemAcquire]("m_costume_awaken_item_acquire") 168 73 if err != nil { 169 74 return nil, fmt.Errorf("load costume awaken item acquire table: %w", err) 170 75 } 171 76 172 - activeSkillGroupRows, err := utils.ReadJSON[CostumeActiveSkillGroupRow]("EntityMCostumeActiveSkillGroupTable.json") 77 + activeSkillGroupRows, err := utils.ReadTable[EntityMCostumeActiveSkillGroup]("m_costume_active_skill_group") 173 78 if err != nil { 174 79 return nil, fmt.Errorf("load costume active skill group table: %w", err) 175 80 } 176 - activeSkillMatRows, err := utils.ReadJSON[CostumeActiveSkillEnhanceMaterialRow]("EntityMCostumeActiveSkillEnhancementMaterialTable.json") 81 + activeSkillMatRows, err := utils.ReadTable[EntityMCostumeActiveSkillEnhancementMaterial]("m_costume_active_skill_enhancement_material") 177 82 if err != nil { 178 83 return nil, fmt.Errorf("load costume active skill enhancement material table: %w", err) 179 84 } 180 85 181 - lotteryEffectRows, err := utils.ReadJSON[CostumeLotteryEffectRow]("EntityMCostumeLotteryEffectTable.json") 86 + lotteryEffectRows, err := utils.ReadTable[EntityMCostumeLotteryEffect]("m_costume_lottery_effect") 182 87 if err != nil { 183 88 return nil, fmt.Errorf("load costume lottery effect table: %w", err) 184 89 } 185 - lotteryEffectMatRows, err := utils.ReadJSON[CostumeLotteryEffectMaterialGroupRow]("EntityMCostumeLotteryEffectMaterialGroupTable.json") 90 + lotteryEffectMatRows, err := utils.ReadTable[EntityMCostumeLotteryEffectMaterialGroup]("m_costume_lottery_effect_material_group") 186 91 if err != nil { 187 92 return nil, fmt.Errorf("load costume lottery effect material group table: %w", err) 188 93 } 189 - lotteryEffectOddsRows, err := utils.ReadJSON[CostumeLotteryEffectOddsRow]("EntityMCostumeLotteryEffectOddsGroupTable.json") 94 + lotteryEffectOddsRows, err := utils.ReadTable[EntityMCostumeLotteryEffectOddsGroup]("m_costume_lottery_effect_odds_group") 190 95 if err != nil { 191 96 return nil, fmt.Errorf("load costume lottery effect odds group table: %w", err) 192 97 } 193 98 194 99 catalog := &CostumeCatalog{ 195 - Costumes: make(map[int32]CostumeMasterRow, len(costumes)), 100 + Costumes: make(map[int32]EntityMCostume, len(costumes)), 196 101 Materials: matCatalog.ByType[model.MaterialTypeCostumeEnhancement], 197 102 ExpByRarity: make(map[int32][]int32, len(rarities)), 198 103 EnhanceCostByRarity: make(map[int32]NumericalFunc, len(rarities)), 199 104 MaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)), 200 105 LimitBreakCostByRarity: make(map[int32]NumericalFunc, len(rarities)), 201 106 202 - AwakenByCostumeId: make(map[int32]CostumeAwakenRow, len(awakenRows)), 107 + AwakenByCostumeId: make(map[int32]EntityMCostumeAwaken, len(awakenRows)), 203 108 AwakenPriceByGroup: make(map[int32]int32, len(awakenPriceRows)), 204 - AwakenEffectsByGroupAndStep: make(map[int32]map[int32]CostumeAwakenEffectRow), 205 - AwakenStatusUpByGroup: make(map[int32][]CostumeAwakenStatusUpRow), 206 - AwakenItemAcquireById: make(map[int32]CostumeAwakenItemAcquireRow, len(awakenItemAcquireRows)), 109 + AwakenEffectsByGroupAndStep: make(map[int32]map[int32]EntityMCostumeAwakenEffectGroup), 110 + AwakenStatusUpByGroup: make(map[int32][]EntityMCostumeAwakenStatusUpGroup), 111 + AwakenItemAcquireById: make(map[int32]EntityMCostumeAwakenItemAcquire, len(awakenItemAcquireRows)), 207 112 208 - ActiveSkillGroupsByGroupId: make(map[int32][]CostumeActiveSkillGroupRow), 209 - ActiveSkillEnhanceMats: make(map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow), 113 + ActiveSkillGroupsByGroupId: make(map[int32][]EntityMCostumeActiveSkillGroup), 114 + ActiveSkillEnhanceMats: make(map[[2]int32][]EntityMCostumeActiveSkillEnhancementMaterial), 210 115 ActiveSkillMaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)), 211 116 ActiveSkillCostByRarity: make(map[int32]NumericalFunc, len(rarities)), 212 117 213 - LotteryEffects: make(map[[2]int32]CostumeLotteryEffectRow, len(lotteryEffectRows)), 214 - LotteryEffectMats: make(map[int32][]CostumeLotteryEffectMaterialGroupRow), 215 - LotteryEffectOdds: make(map[int32][]CostumeLotteryEffectOddsRow), 118 + LotteryEffects: make(map[[2]int32]EntityMCostumeLotteryEffect, len(lotteryEffectRows)), 119 + LotteryEffectMats: make(map[int32][]EntityMCostumeLotteryEffectMaterialGroup), 120 + LotteryEffectOdds: make(map[int32][]EntityMCostumeLotteryEffectOddsGroup), 216 121 } 217 122 218 123 for _, row := range costumes { ··· 259 164 for _, row := range awakenEffectRows { 260 165 m, ok := catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId] 261 166 if !ok { 262 - m = make(map[int32]CostumeAwakenEffectRow) 167 + m = make(map[int32]EntityMCostumeAwakenEffectGroup) 263 168 catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId] = m 264 169 } 265 170 m[row.AwakenStep] = row
+3 -20
server/internal/masterdata/dup_exchange.go
··· 5 5 "lunar-tear/server/internal/utils" 6 6 ) 7 7 8 - type costumeDupRow struct { 9 - CostumeId int32 `json:"CostumeId"` 10 - PossessionType int32 `json:"PossessionType"` 11 - PossessionId int32 `json:"PossessionId"` 12 - Count int32 `json:"Count"` 13 - } 14 - 15 8 func LoadDupExchange() (map[int32][]model.DupExchangeEntry, error) { 16 9 result := make(map[int32][]model.DupExchangeEntry) 17 10 18 - costumeRows, err := utils.ReadJSON[costumeDupRow]("EntityMCostumeDuplicationExchangePossessionGroupTable.json") 11 + costumeRows, err := utils.ReadTable[EntityMCostumeDuplicationExchangePossessionGroup]("m_costume_duplication_exchange_possession_group") 19 12 if err != nil { 20 13 return nil, err 21 14 } ··· 30 23 return result, nil 31 24 } 32 25 33 - type lbMaterialRow struct { 34 - CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"` 35 - MaterialId int32 `json:"MaterialId"` 36 - } 37 - 38 - type costumeLBRef struct { 39 - CostumeId int32 `json:"CostumeId"` 40 - CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"` 41 - } 42 - 43 26 const dupExchangeFallbackCount int32 = 10 44 27 45 28 func EnrichDupExchange(dupMap map[int32][]model.DupExchangeEntry, pool *GachaCatalog) (int, error) { 46 - lbRows, err := utils.ReadJSON[lbMaterialRow]("EntityMCostumeLimitBreakMaterialGroupTable.json") 29 + lbRows, err := utils.ReadTable[EntityMCostumeLimitBreakMaterialGroup]("m_costume_limit_break_material_group") 47 30 if err != nil { 48 31 return 0, err 49 32 } ··· 52 35 groupToMaterial[r.CostumeLimitBreakMaterialGroupId] = r.MaterialId 53 36 } 54 37 55 - costumeRows, err := utils.ReadJSON[costumeLBRef]("EntityMCostumeTable.json") 38 + costumeRows, err := utils.ReadTable[EntityMCostume]("m_costume") 56 39 if err != nil { 57 40 return 0, err 58 41 }
+1106
server/internal/masterdata/entities.go
··· 1 + // Code generated by scripts/gen_entities.py from schemas.json. DO NOT EDIT. 2 + 3 + package masterdata 4 + 5 + // EntityMBattle is table key "m_battle". 6 + type EntityMBattle struct { 7 + _ struct{} `msgpack:",array"` 8 + BattleId int32 9 + BattleNpcId int64 10 + DeckType int32 11 + BattleNpcDeckNumber int32 12 + } 13 + 14 + // EntityMBattleDropReward is table key "m_battle_drop_reward". 15 + type EntityMBattleDropReward struct { 16 + _ struct{} `msgpack:",array"` 17 + BattleDropRewardId int32 18 + PossessionType int32 19 + PossessionId int32 20 + Count int32 21 + } 22 + 23 + // EntityMBattleGroup is table key "m_battle_group". 24 + type EntityMBattleGroup struct { 25 + _ struct{} `msgpack:",array"` 26 + BattleGroupId int32 27 + WaveNumber int32 28 + BattleId int32 29 + WaveStartActAssetId int32 30 + WaveEndActAssetId int32 31 + BattleCameraControllerAssetId int32 32 + BattlePointIndex int32 33 + BattleStartCameraType int32 34 + } 35 + 36 + // EntityMBattleNpcDeck is table key "m_battle_npc_deck". 37 + type EntityMBattleNpcDeck struct { 38 + _ struct{} `msgpack:",array"` 39 + BattleNpcId int64 40 + DeckType int32 41 + BattleNpcDeckNumber int32 42 + BattleNpcDeckCharacterUuid01 string 43 + BattleNpcDeckCharacterUuid02 string 44 + BattleNpcDeckCharacterUuid03 string 45 + Name string 46 + Power int32 47 + } 48 + 49 + // EntityMBattleNpcDeckCharacterDropCategory is table key "m_battle_npc_deck_character_drop_category". 50 + type EntityMBattleNpcDeckCharacterDropCategory struct { 51 + _ struct{} `msgpack:",array"` 52 + BattleNpcId int64 53 + BattleNpcDeckCharacterUuid string 54 + BattleDropCategoryId int32 55 + } 56 + 57 + // EntityMBattleRentalDeck is table key "m_battle_rental_deck". 58 + type EntityMBattleRentalDeck struct { 59 + _ struct{} `msgpack:",array"` 60 + BattleGroupId int32 61 + BattleNpcId int64 62 + DeckType int32 63 + BattleNpcDeckNumber int32 64 + } 65 + 66 + // EntityMBigHuntBoss is table key "m_big_hunt_boss". 67 + type EntityMBigHuntBoss struct { 68 + _ struct{} `msgpack:",array"` 69 + BigHuntBossId int32 70 + BigHuntBossGradeGroupId int32 71 + NameBigHuntBossTextId int32 72 + BigHuntBossAssetId int32 73 + AttributeType int32 74 + } 75 + 76 + // EntityMBigHuntBossGradeGroup is table key "m_big_hunt_boss_grade_group". 77 + type EntityMBigHuntBossGradeGroup struct { 78 + _ struct{} `msgpack:",array"` 79 + BigHuntBossGradeGroupId int32 80 + NecessaryScore int64 81 + AssetGradeIconId int32 82 + } 83 + 84 + // EntityMBigHuntBossQuest is table key "m_big_hunt_boss_quest". 85 + type EntityMBigHuntBossQuest struct { 86 + _ struct{} `msgpack:",array"` 87 + BigHuntBossQuestId int32 88 + BigHuntBossId int32 89 + BigHuntQuestGroupId int32 90 + BigHuntBossQuestScoreCoefficientId int32 91 + BigHuntScoreRewardGroupScheduleId int32 92 + BigHuntLinkId int32 93 + DailyChallengeCount int32 94 + } 95 + 96 + // EntityMBigHuntQuest is table key "m_big_hunt_quest". 97 + type EntityMBigHuntQuest struct { 98 + _ struct{} `msgpack:",array"` 99 + BigHuntQuestId int32 100 + QuestId int32 101 + BigHuntQuestScoreCoefficientId int32 102 + } 103 + 104 + // EntityMBigHuntQuestScoreCoefficient is table key "m_big_hunt_quest_score_coefficient". 105 + type EntityMBigHuntQuestScoreCoefficient struct { 106 + _ struct{} `msgpack:",array"` 107 + BigHuntQuestScoreCoefficientId int32 108 + ScoreDifficultBonusPermil int32 109 + } 110 + 111 + // EntityMBigHuntRewardGroup is table key "m_big_hunt_reward_group". 112 + type EntityMBigHuntRewardGroup struct { 113 + _ struct{} `msgpack:",array"` 114 + BigHuntRewardGroupId int32 115 + SortOrder int32 116 + PossessionType int32 117 + PossessionId int32 118 + Count int32 119 + } 120 + 121 + // EntityMBigHuntSchedule is table key "m_big_hunt_schedule". 122 + type EntityMBigHuntSchedule struct { 123 + _ struct{} `msgpack:",array"` 124 + BigHuntScheduleId int32 125 + NoticeStartDatetime int64 126 + ChallengeStartDatetime int64 127 + ChallengeEndDatetime int64 128 + SeasonAssetId int32 129 + } 130 + 131 + // EntityMBigHuntScoreRewardGroup is table key "m_big_hunt_score_reward_group". 132 + type EntityMBigHuntScoreRewardGroup struct { 133 + _ struct{} `msgpack:",array"` 134 + BigHuntScoreRewardGroupId int32 135 + NecessaryScore int64 136 + BigHuntRewardGroupId int32 137 + } 138 + 139 + // EntityMBigHuntScoreRewardGroupSchedule is table key "m_big_hunt_score_reward_group_schedule". 140 + type EntityMBigHuntScoreRewardGroupSchedule struct { 141 + _ struct{} `msgpack:",array"` 142 + BigHuntScoreRewardGroupScheduleId int32 143 + GroupIndex int32 144 + BigHuntScoreRewardGroupId int32 145 + StartDatetime int64 146 + } 147 + 148 + // EntityMBigHuntWeeklyAttributeScoreRewardGroupSchedule is table key "m_big_hunt_weekly_attribute_score_reward_group_schedule". 149 + type EntityMBigHuntWeeklyAttributeScoreRewardGroupSchedule struct { 150 + _ struct{} `msgpack:",array"` 151 + BigHuntWeeklyAttributeScoreRewardGroupScheduleId int32 152 + AttributeType int32 153 + GroupIndex int32 154 + BigHuntScoreRewardGroupId int32 155 + StartDatetime int64 156 + } 157 + 158 + // EntityMCageOrnament is table key "m_cage_ornament". 159 + type EntityMCageOrnament struct { 160 + _ struct{} `msgpack:",array"` 161 + CageOrnamentId int32 162 + StartDatetime int64 163 + EndDatetime int64 164 + CageOrnamentRewardId int32 165 + } 166 + 167 + // EntityMCageOrnamentReward is table key "m_cage_ornament_reward". 168 + type EntityMCageOrnamentReward struct { 169 + _ struct{} `msgpack:",array"` 170 + CageOrnamentRewardId int32 171 + PossessionType int32 172 + PossessionId int32 173 + Count int32 174 + } 175 + 176 + // EntityMCatalogCostume is table key "m_catalog_costume". 177 + type EntityMCatalogCostume struct { 178 + _ struct{} `msgpack:",array"` 179 + CostumeId int32 180 + SortOrder int32 181 + CatalogTermId int32 182 + } 183 + 184 + // EntityMCatalogWeapon is table key "m_catalog_weapon". 185 + type EntityMCatalogWeapon struct { 186 + _ struct{} `msgpack:",array"` 187 + WeaponId int32 188 + SortOrder int32 189 + CatalogTermId int32 190 + } 191 + 192 + // EntityMCharacterBoard is table key "m_character_board". 193 + type EntityMCharacterBoard struct { 194 + _ struct{} `msgpack:",array"` 195 + CharacterBoardId int32 196 + CharacterBoardGroupId int32 197 + CharacterBoardUnlockConditionGroupId int32 198 + ReleaseRank int32 199 + } 200 + 201 + // EntityMCharacterBoardAbility is table key "m_character_board_ability". 202 + type EntityMCharacterBoardAbility struct { 203 + _ struct{} `msgpack:",array"` 204 + CharacterBoardAbilityId int32 205 + CharacterBoardEffectTargetGroupId int32 206 + AbilityId int32 207 + } 208 + 209 + // EntityMCharacterBoardAbilityMaxLevel is table key "m_character_board_ability_max_level". 210 + type EntityMCharacterBoardAbilityMaxLevel struct { 211 + _ struct{} `msgpack:",array"` 212 + CharacterId int32 213 + AbilityId int32 214 + MaxLevel int32 215 + } 216 + 217 + // EntityMCharacterBoardEffectTargetGroup is table key "m_character_board_effect_target_group". 218 + type EntityMCharacterBoardEffectTargetGroup struct { 219 + _ struct{} `msgpack:",array"` 220 + CharacterBoardEffectTargetGroupId int32 221 + GroupIndex int32 222 + CharacterBoardEffectTargetType int32 223 + TargetValue int32 224 + } 225 + 226 + // EntityMCharacterBoardPanel is table key "m_character_board_panel". 227 + type EntityMCharacterBoardPanel struct { 228 + _ struct{} `msgpack:",array"` 229 + CharacterBoardPanelId int32 230 + CharacterBoardId int32 231 + CharacterBoardPanelUnlockConditionGroupId int32 232 + CharacterBoardPanelReleasePossessionGroupId int32 233 + CharacterBoardPanelReleaseRewardGroupId int32 234 + CharacterBoardPanelReleaseEffectGroupId int32 235 + SortOrder int32 236 + ParentCharacterBoardPanelId int32 237 + PlaceIndex int32 238 + } 239 + 240 + // EntityMCharacterBoardPanelReleaseEffectGroup is table key "m_character_board_panel_release_effect_group". 241 + type EntityMCharacterBoardPanelReleaseEffectGroup struct { 242 + _ struct{} `msgpack:",array"` 243 + CharacterBoardPanelReleaseEffectGroupId int32 244 + SortOrder int32 245 + CharacterBoardEffectType int32 246 + CharacterBoardEffectId int32 247 + EffectValue int32 248 + } 249 + 250 + // EntityMCharacterBoardPanelReleasePossessionGroup is table key "m_character_board_panel_release_possession_group". 251 + type EntityMCharacterBoardPanelReleasePossessionGroup struct { 252 + _ struct{} `msgpack:",array"` 253 + CharacterBoardPanelReleasePossessionGroupId int32 254 + PossessionType int32 255 + PossessionId int32 256 + Count int32 257 + SortOrder int32 258 + } 259 + 260 + // EntityMCharacterBoardStatusUp is table key "m_character_board_status_up". 261 + type EntityMCharacterBoardStatusUp struct { 262 + _ struct{} `msgpack:",array"` 263 + CharacterBoardStatusUpId int32 264 + CharacterBoardStatusUpType int32 265 + CharacterBoardEffectTargetGroupId int32 266 + } 267 + 268 + // EntityMCharacterRebirth is table key "m_character_rebirth". 269 + type EntityMCharacterRebirth struct { 270 + _ struct{} `msgpack:",array"` 271 + CharacterId int32 272 + CharacterRebirthStepGroupId int32 273 + SortOrder int32 274 + CharacterAssignmentType int32 275 + } 276 + 277 + // EntityMCharacterRebirthMaterialGroup is table key "m_character_rebirth_material_group". 278 + type EntityMCharacterRebirthMaterialGroup struct { 279 + _ struct{} `msgpack:",array"` 280 + CharacterRebirthMaterialGroupId int32 281 + MaterialId int32 282 + Count int32 283 + SortOrder int32 284 + } 285 + 286 + // EntityMCharacterRebirthStepGroup is table key "m_character_rebirth_step_group". 287 + type EntityMCharacterRebirthStepGroup struct { 288 + _ struct{} `msgpack:",array"` 289 + CharacterRebirthStepGroupId int32 290 + BeforeRebirthCount int32 291 + CostumeLevelLimitUp int32 292 + CharacterRebirthMaterialGroupId int32 293 + } 294 + 295 + // EntityMCharacterViewerField is table key "m_character_viewer_field". 296 + type EntityMCharacterViewerField struct { 297 + _ struct{} `msgpack:",array"` 298 + CharacterViewerFieldId int32 299 + ReleaseEvaluateConditionId int32 300 + PublishDatetime int64 301 + CharacterViewerFieldAssetId int32 302 + AssetBackgroundId int32 303 + SortOrder int32 304 + } 305 + 306 + // EntityMCompanion is table key "m_companion". 307 + type EntityMCompanion struct { 308 + _ struct{} `msgpack:",array"` 309 + CompanionId int32 310 + AttributeType int32 311 + CompanionCategoryType int32 312 + CompanionBaseStatusId int32 313 + CompanionStatusCalculationId int32 314 + SkillId int32 315 + CompanionAbilityGroupId int32 316 + ActorId int32 317 + ActorSkeletonId int32 318 + AssetVariationId int32 319 + CharacterMoverBattleActorAiId int32 320 + } 321 + 322 + // EntityMCompanionCategory is table key "m_companion_category". 323 + type EntityMCompanionCategory struct { 324 + _ struct{} `msgpack:",array"` 325 + CompanionCategoryType int32 326 + EnhancementCostNumericalFunctionId int32 327 + } 328 + 329 + // EntityMCompanionEnhancementMaterial is table key "m_companion_enhancement_material". 330 + type EntityMCompanionEnhancementMaterial struct { 331 + _ struct{} `msgpack:",array"` 332 + CompanionCategoryType int32 333 + Level int32 334 + MaterialId int32 335 + Count int32 336 + SortOrder int32 337 + } 338 + 339 + // EntityMConfig is table key "m_config". 340 + type EntityMConfig struct { 341 + _ struct{} `msgpack:",array"` 342 + ConfigKey string 343 + Value string 344 + } 345 + 346 + // EntityMConsumableItem is table key "m_consumable_item". 347 + type EntityMConsumableItem struct { 348 + _ struct{} `msgpack:",array"` 349 + ConsumableItemId int32 350 + ConsumableItemType int32 351 + SortOrder int32 352 + SellPrice int32 353 + ConsumableItemTermId int32 354 + AssetName string 355 + AssetCategoryId int32 356 + AssetVariationId int32 357 + } 358 + 359 + // EntityMCostume is table key "m_costume". 360 + type EntityMCostume struct { 361 + _ struct{} `msgpack:",array"` 362 + CostumeId int32 363 + CharacterId int32 364 + ActorId int32 365 + CostumeAssetCategoryType int32 366 + ActorSkeletonId int32 367 + AssetVariationId int32 368 + SkillfulWeaponType int32 369 + RarityType int32 370 + CostumeBaseStatusId int32 371 + CostumeStatusCalculationId int32 372 + CostumeLimitBreakMaterialGroupId int32 373 + CostumeAbilityGroupId int32 374 + CostumeActiveSkillGroupId int32 375 + CounterSkillDetailId int32 376 + CharacterMoverBattleActorAiId int32 377 + CostumeDefaultSkillGroupId int32 378 + CostumeLevelBonusId int32 379 + DefaultActorSkillAiId int32 380 + CostumeEmblemAssetId int32 381 + BattleActorSkillAiGroupId int32 382 + } 383 + 384 + // EntityMCostumeActiveSkillEnhancementMaterial is table key "m_costume_active_skill_enhancement_material". 385 + type EntityMCostumeActiveSkillEnhancementMaterial struct { 386 + _ struct{} `msgpack:",array"` 387 + CostumeActiveSkillEnhancementMaterialId int32 388 + SkillLevel int32 389 + MaterialId int32 390 + Count int32 391 + SortOrder int32 392 + } 393 + 394 + // EntityMCostumeActiveSkillGroup is table key "m_costume_active_skill_group". 395 + type EntityMCostumeActiveSkillGroup struct { 396 + _ struct{} `msgpack:",array"` 397 + CostumeActiveSkillGroupId int32 398 + CostumeLimitBreakCountLowerLimit int32 399 + CostumeActiveSkillId int32 400 + CostumeActiveSkillEnhancementMaterialId int32 401 + } 402 + 403 + // EntityMCostumeAwaken is table key "m_costume_awaken". 404 + type EntityMCostumeAwaken struct { 405 + _ struct{} `msgpack:",array"` 406 + CostumeId int32 407 + CostumeAwakenEffectGroupId int32 408 + CostumeAwakenStepMaterialGroupId int32 409 + CostumeAwakenPriceGroupId int32 410 + } 411 + 412 + // EntityMCostumeAwakenEffectGroup is table key "m_costume_awaken_effect_group". 413 + type EntityMCostumeAwakenEffectGroup struct { 414 + _ struct{} `msgpack:",array"` 415 + CostumeAwakenEffectGroupId int32 416 + AwakenStep int32 417 + CostumeAwakenEffectType int32 418 + CostumeAwakenEffectId int32 419 + } 420 + 421 + // EntityMCostumeAwakenItemAcquire is table key "m_costume_awaken_item_acquire". 422 + type EntityMCostumeAwakenItemAcquire struct { 423 + _ struct{} `msgpack:",array"` 424 + CostumeAwakenItemAcquireId int32 425 + PossessionType int32 426 + PossessionId int32 427 + Count int32 428 + } 429 + 430 + // EntityMCostumeAwakenPriceGroup is table key "m_costume_awaken_price_group". 431 + type EntityMCostumeAwakenPriceGroup struct { 432 + _ struct{} `msgpack:",array"` 433 + CostumeAwakenPriceGroupId int32 434 + AwakenStepLowerLimit int32 435 + Gold int32 436 + } 437 + 438 + // EntityMCostumeAwakenStatusUpGroup is table key "m_costume_awaken_status_up_group". 439 + type EntityMCostumeAwakenStatusUpGroup struct { 440 + _ struct{} `msgpack:",array"` 441 + CostumeAwakenStatusUpGroupId int32 442 + SortOrder int32 443 + StatusKindType int32 444 + StatusCalculationType int32 445 + EffectValue int32 446 + } 447 + 448 + // EntityMCostumeDuplicationExchangePossessionGroup is table key "m_costume_duplication_exchange_possession_group". 449 + type EntityMCostumeDuplicationExchangePossessionGroup struct { 450 + _ struct{} `msgpack:",array"` 451 + CostumeId int32 452 + PossessionType int32 453 + PossessionId int32 454 + Count int32 455 + } 456 + 457 + // EntityMCostumeLimitBreakMaterialGroup is table key "m_costume_limit_break_material_group". 458 + type EntityMCostumeLimitBreakMaterialGroup struct { 459 + _ struct{} `msgpack:",array"` 460 + CostumeLimitBreakMaterialGroupId int32 461 + MaterialId int32 462 + Count int32 463 + SortOrder int32 464 + CostumeOverflowExchangePossessionGroupId int32 465 + } 466 + 467 + // EntityMCostumeLotteryEffect is table key "m_costume_lottery_effect". 468 + type EntityMCostumeLotteryEffect struct { 469 + _ struct{} `msgpack:",array"` 470 + CostumeId int32 471 + SlotNumber int32 472 + CostumeLotteryEffectOddsGroupId int32 473 + CostumeLotteryEffectUnlockMaterialGroupId int32 474 + CostumeLotteryEffectDrawMaterialGroupId int32 475 + CostumeLotteryEffectReleaseScheduleId int32 476 + } 477 + 478 + // EntityMCostumeLotteryEffectMaterialGroup is table key "m_costume_lottery_effect_material_group". 479 + type EntityMCostumeLotteryEffectMaterialGroup struct { 480 + _ struct{} `msgpack:",array"` 481 + CostumeLotteryEffectMaterialGroupId int32 482 + MaterialId int32 483 + Count int32 484 + SortOrder int32 485 + } 486 + 487 + // EntityMCostumeLotteryEffectOddsGroup is table key "m_costume_lottery_effect_odds_group". 488 + type EntityMCostumeLotteryEffectOddsGroup struct { 489 + _ struct{} `msgpack:",array"` 490 + CostumeLotteryEffectOddsGroupId int32 491 + OddsNumber int32 492 + Weight int32 493 + CostumeLotteryEffectType int32 494 + CostumeLotteryEffectTargetId int32 495 + RarityType int32 496 + } 497 + 498 + // EntityMCostumeRarity is table key "m_costume_rarity". 499 + type EntityMCostumeRarity struct { 500 + _ struct{} `msgpack:",array"` 501 + RarityType int32 502 + CostumeLimitBreakMaterialRarityGroupId int32 503 + EnhancementCostByMaterialNumericalFunctionId int32 504 + LimitBreakCostNumericalFunctionId int32 505 + MaxLevelNumericalFunctionId int32 506 + RequiredExpForLevelUpNumericalParameterMapId int32 507 + ActiveSkillMaxLevelNumericalFunctionId int32 508 + ActiveSkillEnhancementCostNumericalFunctionId int32 509 + } 510 + 511 + // EntityMEvaluateCondition is table key "m_evaluate_condition". 512 + type EntityMEvaluateCondition struct { 513 + _ struct{} `msgpack:",array"` 514 + EvaluateConditionId int32 515 + EvaluateConditionFunctionType int32 516 + EvaluateConditionEvaluateType int32 517 + EvaluateConditionValueGroupId int32 518 + NameEvaluateConditionTextId int32 519 + } 520 + 521 + // EntityMEvaluateConditionValueGroup is table key "m_evaluate_condition_value_group". 522 + type EntityMEvaluateConditionValueGroup struct { 523 + _ struct{} `msgpack:",array"` 524 + EvaluateConditionValueGroupId int32 525 + GroupIndex int32 526 + Value int64 527 + } 528 + 529 + // EntityMExplore is table key "m_explore". 530 + type EntityMExplore struct { 531 + _ struct{} `msgpack:",array"` 532 + ExploreId int32 533 + ExploreUnlockConditionId int32 534 + StartDatetime int64 535 + ConsumeItemCount int32 536 + RewardLotteryCount int32 537 + } 538 + 539 + // EntityMExploreGradeAsset is table key "m_explore_grade_asset". 540 + type EntityMExploreGradeAsset struct { 541 + _ struct{} `msgpack:",array"` 542 + ExploreGradeId int32 543 + AssetGradeIconId int32 544 + } 545 + 546 + // EntityMExploreGradeScore is table key "m_explore_grade_score". 547 + type EntityMExploreGradeScore struct { 548 + _ struct{} `msgpack:",array"` 549 + ExploreId int32 550 + NecessaryScore int32 551 + ExploreGradeId int32 552 + } 553 + 554 + // EntityMGachaMedal is table key "m_gacha_medal". 555 + type EntityMGachaMedal struct { 556 + _ struct{} `msgpack:",array"` 557 + GachaMedalId int32 558 + CeilingCount int32 559 + ConsumableItemId int32 560 + ShopTransitionGachaId int32 561 + AutoConvertDatetime int64 562 + ConversionRate int32 563 + } 564 + 565 + // EntityMGimmickSequenceSchedule is table key "m_gimmick_sequence_schedule". 566 + type EntityMGimmickSequenceSchedule struct { 567 + _ struct{} `msgpack:",array"` 568 + GimmickSequenceScheduleId int32 569 + StartDatetime int64 570 + EndDatetime int64 571 + FirstGimmickSequenceId int32 572 + ReleaseEvaluateConditionId int32 573 + } 574 + 575 + // EntityMLoginBonusStamp is table key "m_login_bonus_stamp". 576 + type EntityMLoginBonusStamp struct { 577 + _ struct{} `msgpack:",array"` 578 + LoginBonusId int32 579 + LowerPageNumber int32 580 + StampNumber int32 581 + RewardPossessionType int32 582 + RewardPossessionId int32 583 + RewardCount int32 584 + } 585 + 586 + // EntityMMainQuestChapter is table key "m_main_quest_chapter". 587 + type EntityMMainQuestChapter struct { 588 + _ struct{} `msgpack:",array"` 589 + MainQuestChapterId int32 590 + MainQuestRouteId int32 591 + SortOrder int32 592 + MainQuestSequenceGroupId int32 593 + PortalCageCharacterGroupId int32 594 + StartDatetime int64 595 + IsInvisibleInLibrary bool 596 + JoinLibraryChapterId int32 597 + } 598 + 599 + // EntityMMainQuestRoute is table key "m_main_quest_route". 600 + type EntityMMainQuestRoute struct { 601 + _ struct{} `msgpack:",array"` 602 + MainQuestRouteId int32 603 + MainQuestSeasonId int32 604 + SortOrder int32 605 + CharacterId int32 606 + } 607 + 608 + // EntityMMainQuestSequence is table key "m_main_quest_sequence". 609 + type EntityMMainQuestSequence struct { 610 + _ struct{} `msgpack:",array"` 611 + MainQuestSequenceId int32 612 + SortOrder int32 613 + QuestId int32 614 + } 615 + 616 + // EntityMMaterial is table key "m_material". 617 + type EntityMMaterial struct { 618 + _ struct{} `msgpack:",array"` 619 + MaterialId int32 620 + MaterialType int32 621 + RarityType int32 622 + WeaponType int32 623 + AttributeType int32 624 + EffectValue int32 625 + SellPrice int32 626 + AssetName string 627 + AssetCategoryId int32 628 + AssetVariationId int32 629 + MaterialSaleObtainPossessionId int32 630 + } 631 + 632 + // EntityMMomBanner is table key "m_mom_banner". 633 + type EntityMMomBanner struct { 634 + _ struct{} `msgpack:",array"` 635 + MomBannerId int32 636 + SortOrderDesc int32 637 + DestinationDomainType int32 638 + DestinationDomainId int32 639 + BannerAssetName string 640 + IsEmphasis bool 641 + StartDatetime int64 642 + EndDatetime int64 643 + TargetUserStatusType int32 644 + } 645 + 646 + // EntityMNumericalFunction is table key "m_numerical_function". 647 + type EntityMNumericalFunction struct { 648 + _ struct{} `msgpack:",array"` 649 + NumericalFunctionId int32 650 + NumericalFunctionType int32 651 + NumericalFunctionParameterGroupId int32 652 + } 653 + 654 + // EntityMNumericalFunctionParameterGroup is table key "m_numerical_function_parameter_group". 655 + type EntityMNumericalFunctionParameterGroup struct { 656 + _ struct{} `msgpack:",array"` 657 + NumericalFunctionParameterGroupId int32 658 + ParameterIndex int32 659 + ParameterValue int32 660 + } 661 + 662 + // EntityMNumericalParameterMap is table key "m_numerical_parameter_map". 663 + type EntityMNumericalParameterMap struct { 664 + _ struct{} `msgpack:",array"` 665 + NumericalParameterMapId int32 666 + ParameterKey int32 667 + ParameterValue int32 668 + } 669 + 670 + // EntityMOmikuji is table key "m_omikuji". 671 + type EntityMOmikuji struct { 672 + _ struct{} `msgpack:",array"` 673 + OmikujiId int32 674 + StartDatetime int64 675 + EndDatetime int64 676 + OmikujiAssetId int32 677 + } 678 + 679 + // EntityMParts is table key "m_parts". 680 + type EntityMParts struct { 681 + _ struct{} `msgpack:",array"` 682 + PartsId int32 683 + RarityType int32 684 + PartsGroupId int32 685 + PartsStatusMainLotteryGroupId int32 686 + PartsStatusSubLotteryGroupId int32 687 + PartsInitialLotteryId int32 688 + } 689 + 690 + // EntityMPartsLevelUpPriceGroup is table key "m_parts_level_up_price_group". 691 + type EntityMPartsLevelUpPriceGroup struct { 692 + _ struct{} `msgpack:",array"` 693 + PartsLevelUpPriceGroupId int32 694 + LevelLowerLimit int32 695 + Gold int32 696 + } 697 + 698 + // EntityMPartsLevelUpRateGroup is table key "m_parts_level_up_rate_group". 699 + type EntityMPartsLevelUpRateGroup struct { 700 + _ struct{} `msgpack:",array"` 701 + PartsLevelUpRateGroupId int32 702 + LevelLowerLimit int32 703 + SuccessRatePermil int32 704 + } 705 + 706 + // EntityMPartsRarity is table key "m_parts_rarity". 707 + type EntityMPartsRarity struct { 708 + _ struct{} `msgpack:",array"` 709 + RarityType int32 710 + PartsLevelUpRateGroupId int32 711 + PartsLevelUpPriceGroupId int32 712 + SellPriceNumericalFunctionId int32 713 + } 714 + 715 + // EntityMQuest is table key "m_quest". 716 + type EntityMQuest struct { 717 + _ struct{} `msgpack:",array"` 718 + QuestId int32 719 + NameQuestTextId int32 720 + PictureBookNameQuestTextId int32 721 + QuestReleaseConditionListId int32 722 + StoryQuestTextId int32 723 + QuestDisplayAttributeGroupId int32 724 + RecommendedDeckPower int32 725 + QuestFirstClearRewardGroupId int32 726 + QuestPickupRewardGroupId int32 727 + QuestDeckRestrictionGroupId int32 728 + QuestMissionGroupId int32 729 + Stamina int32 730 + UserExp int32 731 + CharacterExp int32 732 + CostumeExp int32 733 + Gold int32 734 + DailyClearableCount int32 735 + IsRunInTheBackground bool 736 + IsCountedAsQuest bool 737 + QuestBonusId int32 738 + IsNotShowAfterClear bool 739 + IsBigWinTarget bool 740 + IsUsableSkipTicket bool 741 + QuestReplayFlowRewardGroupId int32 742 + InvisibleQuestMissionGroupId int32 743 + FieldEffectGroupId int32 744 + } 745 + 746 + // EntityMQuestFirstClearRewardGroup is table key "m_quest_first_clear_reward_group". 747 + type EntityMQuestFirstClearRewardGroup struct { 748 + _ struct{} `msgpack:",array"` 749 + QuestFirstClearRewardGroupId int32 750 + QuestFirstClearRewardType int32 751 + SortOrder int32 752 + PossessionType int32 753 + PossessionId int32 754 + Count int32 755 + IsPickup bool 756 + } 757 + 758 + // EntityMQuestFirstClearRewardSwitch is table key "m_quest_first_clear_reward_switch". 759 + type EntityMQuestFirstClearRewardSwitch struct { 760 + _ struct{} `msgpack:",array"` 761 + QuestId int32 762 + QuestFirstClearRewardGroupId int32 763 + SwitchConditionClearQuestId int32 764 + } 765 + 766 + // EntityMQuestMission is table key "m_quest_mission". 767 + type EntityMQuestMission struct { 768 + _ struct{} `msgpack:",array"` 769 + QuestMissionId int32 770 + QuestMissionConditionType int32 771 + ConditionValue int32 772 + QuestMissionRewardId int32 773 + QuestMissionConditionValueGroupId int32 774 + } 775 + 776 + // EntityMQuestMissionGroup is table key "m_quest_mission_group". 777 + type EntityMQuestMissionGroup struct { 778 + _ struct{} `msgpack:",array"` 779 + QuestMissionGroupId int32 780 + SortOrder int32 781 + QuestMissionId int32 782 + } 783 + 784 + // EntityMQuestMissionReward is table key "m_quest_mission_reward". 785 + type EntityMQuestMissionReward struct { 786 + _ struct{} `msgpack:",array"` 787 + QuestMissionRewardId int32 788 + PossessionType int32 789 + PossessionId int32 790 + Count int32 791 + } 792 + 793 + // EntityMQuestPickupRewardGroup is table key "m_quest_pickup_reward_group". 794 + type EntityMQuestPickupRewardGroup struct { 795 + _ struct{} `msgpack:",array"` 796 + QuestPickupRewardGroupId int32 797 + SortOrder int32 798 + BattleDropRewardId int32 799 + } 800 + 801 + // EntityMQuestReplayFlowRewardGroup is table key "m_quest_replay_flow_reward_group". 802 + type EntityMQuestReplayFlowRewardGroup struct { 803 + _ struct{} `msgpack:",array"` 804 + QuestReplayFlowRewardGroupId int32 805 + SortOrder int32 806 + PossessionType int32 807 + PossessionId int32 808 + Count int32 809 + } 810 + 811 + // EntityMQuestScene is table key "m_quest_scene". 812 + type EntityMQuestScene struct { 813 + _ struct{} `msgpack:",array"` 814 + QuestSceneId int32 815 + QuestId int32 816 + SortOrder int32 817 + QuestSceneType int32 818 + AssetBackgroundId int32 819 + EventMapNumberUpper int32 820 + EventMapNumberLower int32 821 + IsMainFlowQuestTarget bool 822 + IsBattleOnlyTarget bool 823 + QuestResultType int32 824 + IsStorySkipTarget bool 825 + } 826 + 827 + // EntityMQuestSceneBattle is table key "m_quest_scene_battle". 828 + type EntityMQuestSceneBattle struct { 829 + _ struct{} `msgpack:",array"` 830 + QuestSceneId int32 831 + BattleGroupId int32 832 + BattleDropBoxGroupId int32 833 + BattleFieldLocaleSettingIndex int32 834 + BattleEventGroupId int32 835 + PostProcessConfigurationIndex int32 836 + } 837 + 838 + // EntityMShop is table key "m_shop". 839 + type EntityMShop struct { 840 + _ struct{} `msgpack:",array"` 841 + ShopId int32 842 + ShopGroupType int32 843 + SortOrderInShopGroup int32 844 + ShopType int32 845 + NameShopTextId int32 846 + ShopUpdatableLabelType int32 847 + ShopExchangeType int32 848 + ShopItemCellGroupId int32 849 + RelatedMainFunctionType int32 850 + StartDatetime int64 851 + EndDatetime int64 852 + LimitedOpenId int32 853 + } 854 + 855 + // EntityMShopItem is table key "m_shop_item". 856 + type EntityMShopItem struct { 857 + _ struct{} `msgpack:",array"` 858 + ShopItemId int32 859 + NameShopTextId int32 860 + DescriptionShopTextId int32 861 + ShopItemContentType int32 862 + PriceType int32 863 + PriceId int32 864 + Price int32 865 + RegularPrice int32 866 + ShopPromotionType int32 867 + ShopItemLimitedStockId int32 868 + AssetCategoryId int32 869 + AssetVariationId int32 870 + ShopItemDecorationType int32 871 + } 872 + 873 + // EntityMShopItemCell is table key "m_shop_item_cell". 874 + type EntityMShopItemCell struct { 875 + _ struct{} `msgpack:",array"` 876 + ShopItemCellId int32 877 + StepNumber int32 878 + ShopItemId int32 879 + } 880 + 881 + // EntityMShopItemCellGroup is table key "m_shop_item_cell_group". 882 + type EntityMShopItemCellGroup struct { 883 + _ struct{} `msgpack:",array"` 884 + ShopItemCellGroupId int32 885 + ShopItemCellId int32 886 + SortOrder int32 887 + ShopItemCellTermId int32 888 + } 889 + 890 + // EntityMShopItemContentEffect is table key "m_shop_item_content_effect". 891 + type EntityMShopItemContentEffect struct { 892 + _ struct{} `msgpack:",array"` 893 + ShopItemId int32 894 + EffectTargetType int32 895 + EffectValueType int32 896 + EffectValue int32 897 + } 898 + 899 + // EntityMShopItemContentPossession is table key "m_shop_item_content_possession". 900 + type EntityMShopItemContentPossession struct { 901 + _ struct{} `msgpack:",array"` 902 + ShopItemId int32 903 + PossessionType int32 904 + PossessionId int32 905 + SortOrder int32 906 + Count int32 907 + } 908 + 909 + // EntityMShopItemLimitedStock is table key "m_shop_item_limited_stock". 910 + type EntityMShopItemLimitedStock struct { 911 + _ struct{} `msgpack:",array"` 912 + ShopItemLimitedStockId int32 913 + MaxCount int32 914 + ShopItemAutoResetType int32 915 + ShopItemAutoResetPeriod int32 916 + } 917 + 918 + // EntityMSideStoryQuestScene is table key "m_side_story_quest_scene". 919 + type EntityMSideStoryQuestScene struct { 920 + _ struct{} `msgpack:",array"` 921 + SideStoryQuestId int32 922 + SideStoryQuestSceneId int32 923 + SortOrder int32 924 + AssetBackgroundId int32 925 + EventMapNumberUpper int32 926 + EventMapNumberLower int32 927 + } 928 + 929 + // EntityMTutorialUnlockCondition is table key "m_tutorial_unlock_condition". 930 + type EntityMTutorialUnlockCondition struct { 931 + _ struct{} `msgpack:",array"` 932 + TutorialType int32 933 + TutorialUnlockConditionType int32 934 + ConditionValue int32 935 + } 936 + 937 + // EntityMUserLevel is table key "m_user_level". 938 + type EntityMUserLevel struct { 939 + _ struct{} `msgpack:",array"` 940 + UserLevel int32 941 + MaxStamina int32 942 + } 943 + 944 + // EntityMUserQuestSceneGrantPossession is table key "m_user_quest_scene_grant_possession". 945 + type EntityMUserQuestSceneGrantPossession struct { 946 + _ struct{} `msgpack:",array"` 947 + QuestSceneId int32 948 + PossessionType int32 949 + PossessionId int32 950 + Count int32 951 + IsGift bool 952 + IsDebug bool 953 + } 954 + 955 + // EntityMWeapon is table key "m_weapon". 956 + type EntityMWeapon struct { 957 + _ struct{} `msgpack:",array"` 958 + WeaponId int32 959 + WeaponCategoryType int32 960 + WeaponType int32 961 + AssetVariationId int32 962 + RarityType int32 963 + AttributeType int32 964 + IsRestrictDiscard bool 965 + WeaponBaseStatusId int32 966 + WeaponStatusCalculationId int32 967 + WeaponSkillGroupId int32 968 + WeaponAbilityGroupId int32 969 + WeaponEvolutionMaterialGroupId int32 970 + WeaponEvolutionGrantPossessionGroupId int32 971 + WeaponStoryReleaseConditionGroupId int32 972 + WeaponSpecificEnhanceId int32 973 + WeaponSpecificLimitBreakMaterialGroupId int32 974 + CharacterWalkaroundRangeType int32 975 + IsRecyclable bool 976 + } 977 + 978 + // EntityMWeaponAbilityEnhancementMaterial is table key "m_weapon_ability_enhancement_material". 979 + type EntityMWeaponAbilityEnhancementMaterial struct { 980 + _ struct{} `msgpack:",array"` 981 + WeaponAbilityEnhancementMaterialId int32 982 + AbilityLevel int32 983 + MaterialId int32 984 + Count int32 985 + SortOrder int32 986 + } 987 + 988 + // EntityMWeaponAbilityGroup is table key "m_weapon_ability_group". 989 + type EntityMWeaponAbilityGroup struct { 990 + _ struct{} `msgpack:",array"` 991 + WeaponAbilityGroupId int32 992 + SlotNumber int32 993 + AbilityId int32 994 + WeaponAbilityEnhancementMaterialId int32 995 + } 996 + 997 + // EntityMWeaponAwaken is table key "m_weapon_awaken". 998 + type EntityMWeaponAwaken struct { 999 + _ struct{} `msgpack:",array"` 1000 + WeaponId int32 1001 + WeaponAwakenEffectGroupId int32 1002 + WeaponAwakenMaterialGroupId int32 1003 + ConsumeGold int32 1004 + LevelLimitUp int32 1005 + } 1006 + 1007 + // EntityMWeaponAwakenMaterialGroup is table key "m_weapon_awaken_material_group". 1008 + type EntityMWeaponAwakenMaterialGroup struct { 1009 + _ struct{} `msgpack:",array"` 1010 + WeaponAwakenMaterialGroupId int32 1011 + MaterialId int32 1012 + Count int32 1013 + SortOrder int32 1014 + } 1015 + 1016 + // EntityMWeaponConsumeExchangeConsumableItemGroup is table key "m_weapon_consume_exchange_consumable_item_group". 1017 + type EntityMWeaponConsumeExchangeConsumableItemGroup struct { 1018 + _ struct{} `msgpack:",array"` 1019 + WeaponId int32 1020 + ConsumableItemId int32 1021 + Count int32 1022 + } 1023 + 1024 + // EntityMWeaponEvolutionGroup is table key "m_weapon_evolution_group". 1025 + type EntityMWeaponEvolutionGroup struct { 1026 + _ struct{} `msgpack:",array"` 1027 + WeaponEvolutionGroupId int32 1028 + EvolutionOrder int32 1029 + WeaponId int32 1030 + } 1031 + 1032 + // EntityMWeaponEvolutionMaterialGroup is table key "m_weapon_evolution_material_group". 1033 + type EntityMWeaponEvolutionMaterialGroup struct { 1034 + _ struct{} `msgpack:",array"` 1035 + WeaponEvolutionMaterialGroupId int32 1036 + MaterialId int32 1037 + Count int32 1038 + SortOrder int32 1039 + } 1040 + 1041 + // EntityMWeaponRarity is table key "m_weapon_rarity". 1042 + type EntityMWeaponRarity struct { 1043 + _ struct{} `msgpack:",array"` 1044 + RarityType int32 1045 + BaseEnhancementObtainedExp int32 1046 + SellPriceNumericalFunctionId int32 1047 + MaxLevelNumericalFunctionId int32 1048 + MaxSkillLevelNumericalFunctionId int32 1049 + MaxAbilityLevelNumericalFunctionId int32 1050 + RequiredExpForLevelUpNumericalParameterMapId int32 1051 + EnhancementCostByWeaponNumericalFunctionId int32 1052 + EnhancementCostByMaterialNumericalFunctionId int32 1053 + SkillEnhancementCostNumericalFunctionId int32 1054 + AbilityEnhancementCostNumericalFunctionId int32 1055 + LimitBreakCostByWeaponNumericalFunctionId int32 1056 + LimitBreakCostByMaterialNumericalFunctionId int32 1057 + EvolutionCostNumericalFunctionId int32 1058 + } 1059 + 1060 + // EntityMWeaponSkillEnhancementMaterial is table key "m_weapon_skill_enhancement_material". 1061 + type EntityMWeaponSkillEnhancementMaterial struct { 1062 + _ struct{} `msgpack:",array"` 1063 + WeaponSkillEnhancementMaterialId int32 1064 + SkillLevel int32 1065 + MaterialId int32 1066 + Count int32 1067 + SortOrder int32 1068 + } 1069 + 1070 + // EntityMWeaponSkillGroup is table key "m_weapon_skill_group". 1071 + type EntityMWeaponSkillGroup struct { 1072 + _ struct{} `msgpack:",array"` 1073 + WeaponSkillGroupId int32 1074 + SlotNumber int32 1075 + SkillId int32 1076 + WeaponSkillEnhancementMaterialId int32 1077 + } 1078 + 1079 + // EntityMWeaponSpecificEnhance is table key "m_weapon_specific_enhance". 1080 + type EntityMWeaponSpecificEnhance struct { 1081 + _ struct{} `msgpack:",array"` 1082 + WeaponSpecificEnhanceId int32 1083 + BaseEnhancementObtainedExp int32 1084 + SellPriceNumericalFunctionId int32 1085 + MaxLevelNumericalFunctionId int32 1086 + MaxSkillLevelNumericalFunctionId int32 1087 + MaxAbilityLevelNumericalFunctionId int32 1088 + RequiredExpForLevelUpNumericalParameterMapId int32 1089 + EnhancementCostByWeaponNumericalFunctionId int32 1090 + EnhancementCostByMaterialNumericalFunctionId int32 1091 + SkillEnhancementCostNumericalFunctionId int32 1092 + AbilityEnhancementCostNumericalFunctionId int32 1093 + LimitBreakCostByWeaponNumericalFunctionId int32 1094 + LimitBreakCostByMaterialNumericalFunctionId int32 1095 + EvolutionCostNumericalFunctionId int32 1096 + } 1097 + 1098 + // EntityMWeaponStoryReleaseConditionGroup is table key "m_weapon_story_release_condition_group". 1099 + type EntityMWeaponStoryReleaseConditionGroup struct { 1100 + _ struct{} `msgpack:",array"` 1101 + WeaponStoryReleaseConditionGroupId int32 1102 + StoryIndex int32 1103 + WeaponStoryReleaseConditionType int32 1104 + ConditionValue int32 1105 + WeaponStoryReleaseConditionOperationGroupId int32 1106 + }
+8 -25
server/internal/masterdata/explore.go
··· 7 7 "lunar-tear/server/internal/utils" 8 8 ) 9 9 10 - type ExploreRow struct { 11 - ExploreId int32 `json:"ExploreId"` 12 - ConsumeItemCount int32 `json:"ConsumeItemCount"` 13 - RewardLotteryCount int32 `json:"RewardLotteryCount"` 14 - } 15 - 16 - type ExploreGradeScoreRow struct { 17 - ExploreId int32 `json:"ExploreId"` 18 - NecessaryScore int32 `json:"NecessaryScore"` 19 - ExploreGradeId int32 `json:"ExploreGradeId"` 20 - } 21 - 22 - type ExploreGradeAssetRow struct { 23 - ExploreGradeId int32 `json:"ExploreGradeId"` 24 - AssetGradeIconId int32 `json:"AssetGradeIconId"` 25 - } 26 - 27 10 type ExploreCatalog struct { 28 - Explores map[int32]ExploreRow 29 - GradeScores map[int32][]ExploreGradeScoreRow // keyed by ExploreId, sorted desc by NecessaryScore 30 - GradeAssets map[int32]int32 // gradeId -> assetGradeIconId 11 + Explores map[int32]EntityMExplore 12 + GradeScores map[int32][]EntityMExploreGradeScore // keyed by ExploreId, sorted desc by NecessaryScore 13 + GradeAssets map[int32]int32 // gradeId -> assetGradeIconId 31 14 } 32 15 33 16 func LoadExploreCatalog() (*ExploreCatalog, error) { 34 - explores, err := utils.ReadJSON[ExploreRow]("EntityMExploreTable.json") 17 + explores, err := utils.ReadTable[EntityMExplore]("m_explore") 35 18 if err != nil { 36 19 return nil, fmt.Errorf("load explore table: %w", err) 37 20 } 38 21 39 - gradeScores, err := utils.ReadJSON[ExploreGradeScoreRow]("EntityMExploreGradeScoreTable.json") 22 + gradeScores, err := utils.ReadTable[EntityMExploreGradeScore]("m_explore_grade_score") 40 23 if err != nil { 41 24 return nil, fmt.Errorf("load explore grade score table: %w", err) 42 25 } 43 26 44 - gradeAssets, err := utils.ReadJSON[ExploreGradeAssetRow]("EntityMExploreGradeAssetTable.json") 27 + gradeAssets, err := utils.ReadTable[EntityMExploreGradeAsset]("m_explore_grade_asset") 45 28 if err != nil { 46 29 return nil, fmt.Errorf("load explore grade asset table: %w", err) 47 30 } 48 31 49 32 catalog := &ExploreCatalog{ 50 - Explores: make(map[int32]ExploreRow, len(explores)), 51 - GradeScores: make(map[int32][]ExploreGradeScoreRow), 33 + Explores: make(map[int32]EntityMExplore, len(explores)), 34 + GradeScores: make(map[int32][]EntityMExploreGradeScore), 52 35 GradeAssets: make(map[int32]int32, len(gradeAssets)), 53 36 } 54 37
+4 -22
server/internal/masterdata/gacha.go
··· 10 10 "lunar-tear/server/internal/utils" 11 11 ) 12 12 13 - type gachaMedalRow struct { 14 - GachaMedalId int32 `json:"GachaMedalId"` 15 - ShopTransitionGachaId int32 `json:"ShopTransitionGachaId"` 16 - ConsumableItemId int32 `json:"ConsumableItemId"` 17 - AutoConvertDatetime int64 `json:"AutoConvertDatetime"` 18 - ConversionRate int32 `json:"ConversionRate"` 19 - } 20 - 21 - type momBannerRow struct { 22 - MomBannerId int32 `json:"MomBannerId"` 23 - SortOrderDesc int32 `json:"SortOrderDesc"` 24 - DestinationDomainType int32 `json:"DestinationDomainType"` 25 - DestinationDomainId int32 `json:"DestinationDomainId"` 26 - BannerAssetName string `json:"BannerAssetName"` 27 - StartDatetime int64 `json:"StartDatetime"` 28 - EndDatetime int64 `json:"EndDatetime"` 29 - } 30 - 31 13 type GachaMedalInfo struct { 32 14 GachaMedalId int32 33 15 ConsumableItemId int32 ··· 38 20 const chapterGachaIdBase int32 = 200000 39 21 40 22 func LoadGachaCatalog() ([]store.GachaCatalogEntry, map[int32]GachaMedalInfo, error) { 41 - medals, err := utils.ReadJSON[gachaMedalRow]("EntityMGachaMedalTable.json") 23 + medals, err := utils.ReadTable[EntityMGachaMedal]("m_gacha_medal") 42 24 if err != nil { 43 25 return nil, nil, fmt.Errorf("load gacha medal table: %w", err) 44 26 } 45 - banners, err := utils.ReadJSON[momBannerRow]("EntityMMomBannerTable.json") 27 + banners, err := utils.ReadTable[EntityMMomBanner]("m_mom_banner") 46 28 if err != nil { 47 29 return nil, nil, fmt.Errorf("load mom banner table: %w", err) 48 30 } 49 31 50 - gachaToMedal := make(map[int32]gachaMedalRow) 32 + gachaToMedal := make(map[int32]EntityMGachaMedal) 51 33 medalInfoByGacha := make(map[int32]GachaMedalInfo) 52 34 for _, m := range medals { 53 35 gachaToMedal[m.ShopTransitionGachaId] = m ··· 59 41 } 60 42 } 61 43 62 - stepupSteps := make(map[int32][]momBannerRow) 44 + stepupSteps := make(map[int32][]EntityMMomBanner) 63 45 var entries []store.GachaCatalogEntry 64 46 65 47 for _, b := range banners {
+8 -38
server/internal/masterdata/gacha_pool.go
··· 46 46 ShopFeaturedByMedal map[int32][]ShopFeaturedEntry // consumableId -> paired entries 47 47 } 48 48 49 - type costumePoolRow struct { 50 - CostumeId int32 `json:"CostumeId"` 51 - CharacterId int32 `json:"CharacterId"` 52 - SkillfulWeaponType int32 `json:"SkillfulWeaponType"` 53 - RarityType int32 `json:"RarityType"` 54 - } 55 - 56 - type weaponPoolRow struct { 57 - WeaponId int32 `json:"WeaponId"` 58 - WeaponType int32 `json:"WeaponType"` 59 - RarityType int32 `json:"RarityType"` 60 - IsRestrictDiscard bool `json:"IsRestrictDiscard"` 61 - } 62 - 63 - type catalogCostumeRow struct { 64 - CostumeId int32 `json:"CostumeId"` 65 - CatalogTermId int32 `json:"CatalogTermId"` 66 - } 67 - 68 - type catalogWeaponRow struct { 69 - WeaponId int32 `json:"WeaponId"` 70 - CatalogTermId int32 `json:"CatalogTermId"` 71 - } 72 - 73 - type materialPoolRow struct { 74 - MaterialId int32 `json:"MaterialId"` 75 - MaterialType int32 `json:"MaterialType"` 76 - RarityType int32 `json:"RarityType"` 77 - } 78 - 79 49 func LoadGachaPool() (*GachaCatalog, error) { 80 - costumes, err := utils.ReadJSON[costumePoolRow]("EntityMCostumeTable.json") 50 + costumes, err := utils.ReadTable[EntityMCostume]("m_costume") 81 51 if err != nil { 82 52 return nil, fmt.Errorf("load costume table: %w", err) 83 53 } 84 - weapons, err := utils.ReadJSON[weaponPoolRow]("EntityMWeaponTable.json") 54 + weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon") 85 55 if err != nil { 86 56 return nil, fmt.Errorf("load weapon table: %w", err) 87 57 } 88 - catalogCostumes, err := utils.ReadJSON[catalogCostumeRow]("EntityMCatalogCostumeTable.json") 58 + catalogCostumes, err := utils.ReadTable[EntityMCatalogCostume]("m_catalog_costume") 89 59 if err != nil { 90 60 return nil, fmt.Errorf("load catalog costume table: %w", err) 91 61 } 92 - catalogWeapons, err := utils.ReadJSON[catalogWeaponRow]("EntityMCatalogWeaponTable.json") 62 + catalogWeapons, err := utils.ReadTable[EntityMCatalogWeapon]("m_catalog_weapon") 93 63 if err != nil { 94 64 return nil, fmt.Errorf("load catalog weapon table: %w", err) 95 65 } 96 - materials, err := utils.ReadJSON[materialPoolRow]("EntityMMaterialTable.json") 66 + materials, err := utils.ReadTable[EntityMMaterial]("m_material") 97 67 if err != nil { 98 68 return nil, fmt.Errorf("load material table: %w", err) 99 69 } 100 - evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json") 70 + evoGroupRows, err := utils.ReadTable[EntityMWeaponEvolutionGroup]("m_weapon_evolution_group") 101 71 if err != nil { 102 72 return nil, fmt.Errorf("load weapon evolution group table: %w", err) 103 73 } ··· 414 384 len(pool.BannerPools), len(allFeaturedCostumes), len(allFeaturedWeapons)) 415 385 } 416 386 417 - func buildEvolvedWeaponSet(rows []WeaponEvolutionGroupRow) map[int32]bool { 418 - grouped := make(map[int32][]WeaponEvolutionGroupRow) 387 + func buildEvolvedWeaponSet(rows []EntityMWeaponEvolutionGroup) map[int32]bool { 388 + grouped := make(map[int32][]EntityMWeaponEvolutionGroup) 419 389 for _, r := range rows { 420 390 grouped[r.WeaponEvolutionGroupId] = append(grouped[r.WeaponEvolutionGroupId], r) 421 391 }
+1 -9
server/internal/masterdata/gimmick.go
··· 9 9 "lunar-tear/server/internal/utils" 10 10 ) 11 11 12 - type gimmickScheduleRow struct { 13 - GimmickSequenceScheduleId int32 `json:"GimmickSequenceScheduleId"` 14 - StartDatetime int64 `json:"StartDatetime"` 15 - EndDatetime int64 `json:"EndDatetime"` 16 - FirstGimmickSequenceId int32 `json:"FirstGimmickSequenceId"` 17 - ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"` 18 - } 19 - 20 12 type gimmickScheduleEntry struct { 21 13 ScheduleId int32 22 14 StartDatetime int64 ··· 30 22 } 31 23 32 24 func LoadGimmickCatalog(resolver *ConditionResolver) (*GimmickCatalog, error) { 33 - rows, err := utils.ReadJSON[gimmickScheduleRow]("EntityMGimmickSequenceScheduleTable.json") 25 + rows, err := utils.ReadTable[EntityMGimmickSequenceSchedule]("m_gimmick_sequence_schedule") 34 26 if err != nil { 35 27 return nil, fmt.Errorf("load gimmick sequence schedule table: %w", err) 36 28 }
+1 -10
server/internal/masterdata/loginbonus.go
··· 5 5 "lunar-tear/server/internal/utils" 6 6 ) 7 7 8 - type loginBonusStamp struct { 9 - LoginBonusId int32 `json:"LoginBonusId"` 10 - LowerPageNumber int32 `json:"LowerPageNumber"` 11 - StampNumber int32 `json:"StampNumber"` 12 - RewardPossessionType int32 `json:"RewardPossessionType"` 13 - RewardPossessionId int32 `json:"RewardPossessionId"` 14 - RewardCount int32 `json:"RewardCount"` 15 - } 16 - 17 8 type loginBonusStampKey struct { 18 9 LoginBonusId int32 19 10 LowerPageNumber int32 ··· 36 27 } 37 28 38 29 func LoadLoginBonusCatalog() *LoginBonusCatalog { 39 - stamps, err := utils.ReadJSON[loginBonusStamp]("EntityMLoginBonusStampTable.json") 30 + stamps, err := utils.ReadTable[EntityMLoginBonusStamp]("m_login_bonus_stamp") 40 31 if err != nil { 41 32 log.Fatalf("load login bonus stamp table: %v", err) 42 33 }
+12 -25
server/internal/masterdata/material.go
··· 7 7 "lunar-tear/server/internal/utils" 8 8 ) 9 9 10 - type MaterialRow struct { 11 - MaterialId int32 `json:"MaterialId"` 12 - MaterialType model.MaterialType `json:"MaterialType"` 13 - WeaponType int32 `json:"WeaponType"` 14 - EffectValue int32 `json:"EffectValue"` 15 - SellPrice int32 `json:"SellPrice"` 16 - } 17 - 18 - type numericalParameterMapRow struct { 19 - NumericalParameterMapId int32 `json:"NumericalParameterMapId"` 20 - ParameterKey int32 `json:"ParameterKey"` 21 - ParameterValue int32 `json:"ParameterValue"` 22 - } 23 - 24 - func LoadParameterMap() ([]numericalParameterMapRow, error) { 25 - rows, err := utils.ReadJSON[numericalParameterMapRow]("EntityMNumericalParameterMapTable.json") 10 + func LoadParameterMap() ([]EntityMNumericalParameterMap, error) { 11 + rows, err := utils.ReadTable[EntityMNumericalParameterMap]("m_numerical_parameter_map") 26 12 if err != nil { 27 13 return nil, fmt.Errorf("load numerical parameter map table: %w", err) 28 14 } 29 15 return rows, nil 30 16 } 31 17 32 - func BuildExpThresholds(paramMapRows []numericalParameterMapRow, mapId int32) []int32 { 18 + func BuildExpThresholds(paramMapRows []EntityMNumericalParameterMap, mapId int32) []int32 { 33 19 maxKey := int32(0) 34 20 for _, r := range paramMapRows { 35 21 if r.NumericalParameterMapId == mapId && r.ParameterKey > maxKey { ··· 46 32 } 47 33 48 34 type MaterialCatalog struct { 49 - All map[int32]MaterialRow 50 - ByType map[model.MaterialType]map[int32]MaterialRow 35 + All map[int32]EntityMMaterial 36 + ByType map[model.MaterialType]map[int32]EntityMMaterial 51 37 } 52 38 53 39 func LoadMaterialCatalog() (*MaterialCatalog, error) { 54 - rows, err := utils.ReadJSON[MaterialRow]("EntityMMaterialTable.json") 40 + rows, err := utils.ReadTable[EntityMMaterial]("m_material") 55 41 if err != nil { 56 42 return nil, fmt.Errorf("load material table: %w", err) 57 43 } 58 44 59 45 catalog := &MaterialCatalog{ 60 - All: make(map[int32]MaterialRow, len(rows)), 61 - ByType: make(map[model.MaterialType]map[int32]MaterialRow), 46 + All: make(map[int32]EntityMMaterial, len(rows)), 47 + ByType: make(map[model.MaterialType]map[int32]EntityMMaterial), 62 48 } 63 49 for _, row := range rows { 64 50 catalog.All[row.MaterialId] = row 65 - if catalog.ByType[row.MaterialType] == nil { 66 - catalog.ByType[row.MaterialType] = make(map[int32]MaterialRow) 51 + mt := model.MaterialType(row.MaterialType) 52 + if catalog.ByType[mt] == nil { 53 + catalog.ByType[mt] = make(map[int32]EntityMMaterial) 67 54 } 68 - catalog.ByType[row.MaterialType][row.MaterialId] = row 55 + catalog.ByType[mt][row.MaterialId] = row 69 56 } 70 57 return catalog, nil 71 58 }
+257
server/internal/masterdata/memorydb/memorydb.go
··· 1 + package memorydb 2 + 3 + import ( 4 + "bytes" 5 + "crypto/aes" 6 + "crypto/cipher" 7 + "encoding/binary" 8 + "encoding/hex" 9 + "fmt" 10 + "os" 11 + 12 + "github.com/pierrec/lz4/v4" 13 + "github.com/vmihailenco/msgpack/v5" 14 + ) 15 + 16 + var tables map[string][]byte 17 + 18 + const ( 19 + aesKeyHex = "36436230313332314545356536624265" 20 + aesIVHex = "45666341656634434165356536446141" 21 + lz4ExtCode = int8(99) 22 + ) 23 + 24 + func Init(binPath string) error { 25 + encrypted, err := os.ReadFile(binPath) 26 + if err != nil { 27 + return fmt.Errorf("read bin.e: %w", err) 28 + } 29 + 30 + decrypted, err := decrypt(encrypted) 31 + if err != nil { 32 + return fmt.Errorf("decrypt: %w", err) 33 + } 34 + 35 + toc, dataBlob, err := parseHeader(decrypted) 36 + if err != nil { 37 + return fmt.Errorf("parse header: %w", err) 38 + } 39 + 40 + tables = make(map[string][]byte, len(toc)) 41 + for name, offLen := range toc { 42 + off := offLen[0] 43 + length := offLen[1] 44 + if off+length > len(dataBlob) { 45 + return fmt.Errorf("table %q: offset %d + length %d exceeds data blob size %d", name, off, length, len(dataBlob)) 46 + } 47 + tables[name] = dataBlob[off : off+length] 48 + } 49 + 50 + return nil 51 + } 52 + 53 + func TableCount() int { 54 + return len(tables) 55 + } 56 + 57 + func TableBytes(key string) ([]byte, bool) { 58 + b, ok := tables[key] 59 + return b, ok 60 + } 61 + 62 + func ReadTable[T any](key string) ([]T, error) { 63 + raw, ok := TableBytes(key) 64 + if !ok { 65 + return nil, fmt.Errorf("table %q not found in master data", key) 66 + } 67 + return decompressAndUnmarshal[T](raw) 68 + } 69 + 70 + func decrypt(data []byte) ([]byte, error) { 71 + key, err := hex.DecodeString(aesKeyHex) 72 + if err != nil { 73 + return nil, fmt.Errorf("decode key: %w", err) 74 + } 75 + iv, err := hex.DecodeString(aesIVHex) 76 + if err != nil { 77 + return nil, fmt.Errorf("decode iv: %w", err) 78 + } 79 + 80 + block, err := aes.NewCipher(key) 81 + if err != nil { 82 + return nil, fmt.Errorf("new cipher: %w", err) 83 + } 84 + 85 + if len(data)%aes.BlockSize != 0 { 86 + return nil, fmt.Errorf("ciphertext length %d is not a multiple of block size %d", len(data), aes.BlockSize) 87 + } 88 + 89 + decrypted := make([]byte, len(data)) 90 + cbc := cipher.NewCBCDecrypter(block, iv) 91 + cbc.CryptBlocks(decrypted, data) 92 + 93 + decrypted, err = pkcs7Unpad(decrypted, aes.BlockSize) 94 + if err != nil { 95 + return nil, fmt.Errorf("unpad: %w", err) 96 + } 97 + return decrypted, nil 98 + } 99 + 100 + func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { 101 + if len(data) == 0 { 102 + return nil, fmt.Errorf("empty data") 103 + } 104 + padLen := int(data[len(data)-1]) 105 + if padLen == 0 || padLen > blockSize || padLen > len(data) { 106 + return nil, fmt.Errorf("invalid padding length %d", padLen) 107 + } 108 + for _, b := range data[len(data)-padLen:] { 109 + if int(b) != padLen { 110 + return nil, fmt.Errorf("invalid padding byte") 111 + } 112 + } 113 + return data[:len(data)-padLen], nil 114 + } 115 + 116 + func parseHeader(data []byte) (map[string][2]int, []byte, error) { 117 + // Decode the header (first msgpack object) using Decode into interface{}, 118 + // then compute how many bytes it consumed to find the data blob start. 119 + r := bytes.NewReader(data) 120 + dec := msgpack.NewDecoder(r) 121 + dec.UseLooseInterfaceDecoding(true) 122 + 123 + var headerRaw interface{} 124 + if err := dec.Decode(&headerRaw); err != nil { 125 + return nil, nil, fmt.Errorf("decode header: %w", err) 126 + } 127 + 128 + headerMap, ok := headerRaw.(map[string]interface{}) 129 + if !ok { 130 + return nil, nil, fmt.Errorf("header is not a map, got %T", headerRaw) 131 + } 132 + 133 + toc := make(map[string][2]int, len(headerMap)) 134 + for name, val := range headerMap { 135 + arr, ok := val.([]interface{}) 136 + if !ok || len(arr) != 2 { 137 + return nil, nil, fmt.Errorf("table %q: expected [offset, length] array, got %T", name, val) 138 + } 139 + offset, err := toInt(arr[0]) 140 + if err != nil { 141 + return nil, nil, fmt.Errorf("table %q offset: %w", name, err) 142 + } 143 + length, err := toInt(arr[1]) 144 + if err != nil { 145 + return nil, nil, fmt.Errorf("table %q length: %w", name, err) 146 + } 147 + toc[name] = [2]int{offset, length} 148 + } 149 + 150 + consumed := int(int64(len(data)) - int64(r.Len())) 151 + return toc, data[consumed:], nil 152 + } 153 + 154 + func toInt(v interface{}) (int, error) { 155 + switch n := v.(type) { 156 + case int8: 157 + return int(n), nil 158 + case int16: 159 + return int(n), nil 160 + case int32: 161 + return int(n), nil 162 + case int64: 163 + return int(n), nil 164 + case uint8: 165 + return int(n), nil 166 + case uint16: 167 + return int(n), nil 168 + case uint32: 169 + return int(n), nil 170 + case uint64: 171 + return int(n), nil 172 + default: 173 + return 0, fmt.Errorf("cannot convert %T to int", v) 174 + } 175 + } 176 + 177 + func decompressAndUnmarshal[T any](raw []byte) ([]T, error) { 178 + // Peek at the raw msgpack to check if it's an ext type (LZ4 compressed) 179 + // or a plain array. 180 + if len(raw) == 0 { 181 + return nil, nil 182 + } 183 + 184 + // Try to decode as ext type first 185 + dec := msgpack.NewDecoder(bytes.NewReader(raw)) 186 + code, extData, err := decodeExt(dec) 187 + if err == nil && code == lz4ExtCode { 188 + uncompressedSize, lz4Data, err := readLZ4ExtHeader(extData) 189 + if err != nil { 190 + return nil, fmt.Errorf("read lz4 ext header: %w", err) 191 + } 192 + 193 + decompressed := make([]byte, uncompressedSize) 194 + n, err := lz4.UncompressBlock(lz4Data, decompressed) 195 + if err != nil { 196 + return nil, fmt.Errorf("lz4 decompress: %w", err) 197 + } 198 + decompressed = decompressed[:n] 199 + 200 + var result []T 201 + if err := msgpack.Unmarshal(decompressed, &result); err != nil { 202 + return nil, fmt.Errorf("unmarshal decompressed table: %w", err) 203 + } 204 + return result, nil 205 + } 206 + 207 + // Not LZ4 compressed, try as plain array 208 + var result []T 209 + if err := msgpack.Unmarshal(raw, &result); err != nil { 210 + return nil, fmt.Errorf("unmarshal plain table: %w", err) 211 + } 212 + return result, nil 213 + } 214 + 215 + func decodeExt(dec *msgpack.Decoder) (int8, []byte, error) { 216 + var ext msgpack.RawMessage 217 + if err := dec.Decode(&ext); err != nil { 218 + return 0, nil, err 219 + } 220 + // ext is the full msgpack ext bytes including the header. 221 + // Re-decode just the header to get the type code, then the body is the rest. 222 + innerDec := msgpack.NewDecoder(bytes.NewReader(ext)) 223 + extID, extLen, err := innerDec.DecodeExtHeader() 224 + if err != nil { 225 + return 0, nil, err 226 + } 227 + extData := make([]byte, extLen) 228 + if _, err := innerDec.Buffered().Read(extData); err != nil { 229 + return 0, nil, fmt.Errorf("read ext data: %w", err) 230 + } 231 + return extID, extData, nil 232 + } 233 + 234 + func readLZ4ExtHeader(data []byte) (int, []byte, error) { 235 + if len(data) == 0 { 236 + return 0, nil, fmt.Errorf("empty ext data") 237 + } 238 + tag := data[0] 239 + switch { 240 + case tag == 0xd2: // big-endian int32 241 + if len(data) < 5 { 242 + return 0, nil, fmt.Errorf("not enough data for int32 size") 243 + } 244 + size := int(int32(binary.BigEndian.Uint32(data[1:5]))) 245 + return size, data[5:], nil 246 + case tag == 0xce: // big-endian uint32 247 + if len(data) < 5 { 248 + return 0, nil, fmt.Errorf("not enough data for uint32 size") 249 + } 250 + size := int(binary.BigEndian.Uint32(data[1:5])) 251 + return size, data[5:], nil 252 + case tag <= 0x7f: // positive fixint 253 + return int(tag), data[1:], nil 254 + default: 255 + return 0, nil, fmt.Errorf("unexpected tag 0x%02x in LZ4 ext header", tag) 256 + } 257 + }
+3 -15
server/internal/masterdata/numericalfunc.go
··· 8 8 "lunar-tear/server/internal/utils" 9 9 ) 10 10 11 - type numericalFunctionRow struct { 12 - NumericalFunctionId int32 `json:"NumericalFunctionId"` 13 - NumericalFunctionType int32 `json:"NumericalFunctionType"` 14 - NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"` 15 - } 16 - 17 - type numericalFunctionParameterRow struct { 18 - NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"` 19 - ParameterIndex int32 `json:"ParameterIndex"` 20 - ParameterValue int32 `json:"ParameterValue"` 21 - } 22 - 23 11 type NumericalFunc struct { 24 12 Type model.NumericalFunctionType 25 13 Params []int32 ··· 61 49 } 62 50 63 51 func LoadFunctionResolver() (*FunctionResolver, error) { 64 - funcRows, err := utils.ReadJSON[numericalFunctionRow]("EntityMNumericalFunctionTable.json") 52 + funcRows, err := utils.ReadTable[EntityMNumericalFunction]("m_numerical_function") 65 53 if err != nil { 66 54 return nil, fmt.Errorf("load numerical function table: %w", err) 67 55 } 68 56 69 - paramRows, err := utils.ReadJSON[numericalFunctionParameterRow]("EntityMNumericalFunctionParameterGroupTable.json") 57 + paramRows, err := utils.ReadTable[EntityMNumericalFunctionParameterGroup]("m_numerical_function_parameter_group") 70 58 if err != nil { 71 59 return nil, fmt.Errorf("load numerical function parameter group table: %w", err) 72 60 } 73 61 74 - paramsByGroup := make(map[int32][]numericalFunctionParameterRow, len(paramRows)) 62 + paramsByGroup := make(map[int32][]EntityMNumericalFunctionParameterGroup, len(paramRows)) 75 63 for _, r := range paramRows { 76 64 paramsByGroup[r.NumericalFunctionParameterGroupId] = append( 77 65 paramsByGroup[r.NumericalFunctionParameterGroupId], r)
+1 -6
server/internal/masterdata/omikuji.go
··· 5 5 "lunar-tear/server/internal/utils" 6 6 ) 7 7 8 - type omikujiEntry struct { 9 - OmikujiId int32 `json:"OmikujiId"` 10 - OmikujiAssetId int32 `json:"OmikujiAssetId"` 11 - } 12 - 13 8 type OmikujiCatalog struct { 14 9 assetIds map[int32]int32 15 10 } ··· 22 17 } 23 18 24 19 func LoadOmikujiCatalog() *OmikujiCatalog { 25 - entries, err := utils.ReadJSON[omikujiEntry]("EntityMOmikujiTable.json") 20 + entries, err := utils.ReadTable[EntityMOmikuji]("m_omikuji") 26 21 if err != nil { 27 22 log.Fatalf("load omikuji table: %v", err) 28 23 }
+8 -34
server/internal/masterdata/parts.go
··· 7 7 "lunar-tear/server/internal/utils" 8 8 ) 9 9 10 - type PartsRow struct { 11 - PartsId int32 `json:"PartsId"` 12 - RarityType model.RarityType `json:"RarityType"` 13 - PartsGroupId int32 `json:"PartsGroupId"` 14 - PartsStatusMainLotteryGroupId int32 `json:"PartsStatusMainLotteryGroupId"` 15 - } 16 - 17 - type PartsRarityRow struct { 18 - RarityType model.RarityType `json:"RarityType"` 19 - PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"` 20 - PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"` 21 - SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"` 22 - } 23 - 24 - type partsLevelUpRateRow struct { 25 - PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"` 26 - LevelLowerLimit int32 `json:"LevelLowerLimit"` 27 - SuccessRatePermil int32 `json:"SuccessRatePermil"` 28 - } 29 - 30 - type partsLevelUpPriceRow struct { 31 - PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"` 32 - LevelLowerLimit int32 `json:"LevelLowerLimit"` 33 - Gold int32 `json:"Gold"` 34 - } 35 - 36 10 type PartsCatalog struct { 37 - PartsById map[int32]PartsRow 11 + PartsById map[int32]EntityMParts 38 12 DefaultPartsStatusMainByLotteryGroup map[int32]int32 39 - RarityByRarityType map[model.RarityType]PartsRarityRow 13 + RarityByRarityType map[model.RarityType]EntityMPartsRarity 40 14 RateByGroupAndLevel map[int32]map[int32]int32 41 15 PriceByGroupAndLevel map[int32]map[int32]int32 42 16 SellPriceByRarity map[model.RarityType]NumericalFunc 43 17 } 44 18 45 19 func LoadPartsCatalog() (*PartsCatalog, error) { 46 - partsRows, err := utils.ReadJSON[PartsRow]("EntityMPartsTable.json") 20 + partsRows, err := utils.ReadTable[EntityMParts]("m_parts") 47 21 if err != nil { 48 22 return nil, fmt.Errorf("load parts table: %w", err) 49 23 } 50 24 51 - rarityRows, err := utils.ReadJSON[PartsRarityRow]("EntityMPartsRarityTable.json") 25 + rarityRows, err := utils.ReadTable[EntityMPartsRarity]("m_parts_rarity") 52 26 if err != nil { 53 27 return nil, fmt.Errorf("load parts rarity table: %w", err) 54 28 } 55 29 56 - rateRows, err := utils.ReadJSON[partsLevelUpRateRow]("EntityMPartsLevelUpRateGroupTable.json") 30 + rateRows, err := utils.ReadTable[EntityMPartsLevelUpRateGroup]("m_parts_level_up_rate_group") 57 31 if err != nil { 58 32 return nil, fmt.Errorf("load parts level up rate table: %w", err) 59 33 } 60 34 61 - priceRows, err := utils.ReadJSON[partsLevelUpPriceRow]("EntityMPartsLevelUpPriceGroupTable.json") 35 + priceRows, err := utils.ReadTable[EntityMPartsLevelUpPriceGroup]("m_parts_level_up_price_group") 62 36 if err != nil { 63 37 return nil, fmt.Errorf("load parts level up price table: %w", err) 64 38 } 65 39 66 - partsById := make(map[int32]PartsRow, len(partsRows)) 40 + partsById := make(map[int32]EntityMParts, len(partsRows)) 67 41 for _, p := range partsRows { 68 42 partsById[p.PartsId] = p 69 43 } ··· 84 58 return nil, fmt.Errorf("load function resolver: %w", err) 85 59 } 86 60 87 - rarityByRarityType := make(map[model.RarityType]PartsRarityRow, len(rarityRows)) 61 + rarityByRarityType := make(map[model.RarityType]EntityMPartsRarity, len(rarityRows)) 88 62 sellPriceByRarity := make(map[model.RarityType]NumericalFunc, len(rarityRows)) 89 63 for _, r := range rarityRows { 90 64 rarityByRarityType[r.RarityType] = r
+61 -241
server/internal/masterdata/quest.go
··· 4 4 "fmt" 5 5 "sort" 6 6 7 - "lunar-tear/server/internal/model" 8 7 "lunar-tear/server/internal/utils" 9 8 ) 10 9 11 - type QuestSceneRow struct { 12 - QuestSceneId int32 `json:"QuestSceneId"` 13 - QuestId int32 `json:"QuestId"` 14 - SortOrder int32 `json:"SortOrder"` 15 - QuestSceneType model.QuestSceneType `json:"QuestSceneType"` 16 - AssetBackgroundId int32 `json:"AssetBackgroundId"` 17 - EventMapNumberUpper int32 `json:"EventMapNumberUpper"` 18 - EventMapNumberLower int32 `json:"EventMapNumberLower"` 19 - IsMainFlowQuestTarget bool `json:"IsMainFlowQuestTarget"` 20 - IsBattleOnlyTarget bool `json:"IsBattleOnlyTarget"` 21 - QuestResultType model.QuestResultType `json:"QuestResultType"` 22 - IsStorySkipTarget bool `json:"IsStorySkipTarget"` 23 - } 24 - 25 - type QuestRow struct { 26 - QuestId int32 `json:"QuestId"` 27 - NameQuestTextId int32 `json:"NameQuestTextId"` 28 - PictureBookNameQuestTextId int32 `json:"PictureBookNameQuestTextId"` 29 - QuestReleaseConditionListId int32 `json:"QuestReleaseConditionListId"` 30 - StoryQuestTextId int32 `json:"StoryQuestTextId"` 31 - QuestDisplayAttributeGroupId int32 `json:"QuestDisplayAttributeGroupId"` 32 - RecommendedDeckPower int32 `json:"RecommendedDeckPower"` 33 - QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"` 34 - QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"` 35 - QuestDeckRestrictionGroupId int32 `json:"QuestDeckRestrictionGroupId"` 36 - QuestMissionGroupId int32 `json:"QuestMissionGroupId"` 37 - Stamina int32 `json:"Stamina"` 38 - UserExp int32 `json:"UserExp"` 39 - CharacterExp int32 `json:"CharacterExp"` 40 - CostumeExp int32 `json:"CostumeExp"` 41 - Gold int32 `json:"Gold"` 42 - DailyClearableCount int32 `json:"DailyClearableCount"` 43 - IsRunInTheBackground bool `json:"IsRunInTheBackground"` 44 - IsCountedAsQuest bool `json:"IsCountedAsQuest"` 45 - QuestBonusId int32 `json:"QuestBonusId"` 46 - IsNotShowAfterClear bool `json:"IsNotShowAfterClear"` 47 - IsBigWinTarget bool `json:"IsBigWinTarget"` 48 - IsUsableSkipTicket bool `json:"IsUsableSkipTicket"` 49 - QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"` 50 - InvisibleQuestMissionGroupId int32 `json:"InvisibleQuestMissionGroupId"` 51 - FieldEffectGroupId int32 `json:"FieldEffectGroupId"` 52 - } 53 - 54 - type QuestMissionRow struct { 55 - QuestMissionId int32 `json:"QuestMissionId"` 56 - QuestMissionConditionType model.QuestMissionConditionType `json:"QuestMissionConditionType"` 57 - QuestMissionRewardId int32 `json:"QuestMissionRewardId"` 58 - QuestMissionConditionValueGroupId int32 `json:"QuestMissionConditionValueGroupId"` 59 - } 60 - 61 - type QuestMissionGroupRow struct { 62 - QuestMissionGroupId int32 `json:"QuestMissionGroupId"` 63 - SortOrder int32 `json:"SortOrder"` 64 - QuestMissionId int32 `json:"QuestMissionId"` 65 - } 66 - 67 - type QuestMissionRewardRow struct { 68 - QuestMissionRewardId int32 `json:"QuestMissionRewardId"` 69 - PossessionType model.PossessionType `json:"PossessionType"` 70 - PossessionId int32 `json:"PossessionId"` 71 - Count int32 `json:"Count"` 72 - } 73 - 74 - type MainQuestSequenceRow struct { 75 - MainQuestSequenceId int32 `json:"MainQuestSequenceId"` 76 - SortOrder int32 `json:"SortOrder"` 77 - QuestId int32 `json:"QuestId"` 78 - } 79 - 80 - type MainQuestRouteRow struct { 81 - MainQuestRouteId int32 `json:"MainQuestRouteId"` 82 - MainQuestSeasonId int32 `json:"MainQuestSeasonId"` 83 - SortOrder int32 `json:"SortOrder"` 84 - CharacterId int32 `json:"CharacterId"` 85 - } 86 - 87 - type MainQuestChapterRow struct { 88 - MainQuestChapterId int32 `json:"MainQuestChapterId"` 89 - MainQuestRouteId int32 `json:"MainQuestRouteId"` 90 - SortOrder int32 `json:"SortOrder"` 91 - MainQuestSequenceGroupId int32 `json:"MainQuestSequenceGroupId"` 92 - PortalCageCharacterGroupId int32 `json:"PortalCageCharacterGroupId"` 93 - StartDatetime int64 `json:"StartDatetime"` 94 - IsInvisibleInLibrary bool `json:"IsInvisibleInLibrary"` 95 - JoinLibraryChapterId int32 `json:"JoinLibraryChapterId"` 96 - } 97 - 98 - type QuestFirstClearRewardSwitchRow struct { 99 - QuestId int32 `json:"QuestId"` 100 - QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"` 101 - SwitchConditionClearQuestId int32 `json:"SwitchConditionClearQuestId"` 102 - } 103 - 104 - type QuestFirstClearRewardGroupRow struct { 105 - QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"` 106 - QuestFirstClearRewardType int32 `json:"QuestFirstClearRewardType"` 107 - SortOrder int32 `json:"SortOrder"` 108 - PossessionType model.PossessionType `json:"PossessionType"` 109 - PossessionId int32 `json:"PossessionId"` 110 - Count int32 `json:"Count"` 111 - IsPickup bool `json:"IsPickup"` 112 - } 113 - 114 - type QuestReplayFlowRewardGroupRow struct { 115 - QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"` 116 - SortOrder int32 `json:"SortOrder"` 117 - PossessionType model.PossessionType `json:"PossessionType"` 118 - PossessionId int32 `json:"PossessionId"` 119 - Count int32 `json:"Count"` 120 - } 121 - 122 - type QuestSceneGrantRow struct { 123 - QuestSceneId int32 `json:"QuestSceneId"` 124 - PossessionType model.PossessionType `json:"PossessionType"` 125 - PossessionId int32 `json:"PossessionId"` 126 - Count int32 `json:"Count"` 127 - } 128 - 129 - type QuestPickupRewardGroupRow struct { 130 - QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"` 131 - SortOrder int32 `json:"SortOrder"` 132 - BattleDropRewardId int32 `json:"BattleDropRewardId"` 133 - } 134 - 135 - type BattleDropRewardRow struct { 136 - BattleDropRewardId int32 `json:"BattleDropRewardId"` 137 - PossessionType model.PossessionType `json:"PossessionType"` 138 - PossessionId int32 `json:"PossessionId"` 139 - Count int32 `json:"Count"` 140 - } 141 - 142 - type QuestSceneBattleRow struct { 143 - QuestSceneId int32 `json:"QuestSceneId"` 144 - BattleGroupId int32 `json:"BattleGroupId"` 145 - } 146 - 147 - type BattleGroupRow struct { 148 - BattleGroupId int32 `json:"BattleGroupId"` 149 - WaveNumber int32 `json:"WaveNumber"` 150 - BattleId int32 `json:"BattleId"` 151 - } 152 - 153 - type BattleRow struct { 154 - BattleId int32 `json:"BattleId"` 155 - BattleNpcId int32 `json:"BattleNpcId"` 156 - DeckType model.DeckType `json:"DeckType"` 157 - BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"` 158 - } 159 - 160 - type BattleNpcDeckRow struct { 161 - BattleNpcId int32 `json:"BattleNpcId"` 162 - DeckType model.DeckType `json:"DeckType"` 163 - BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"` 164 - BattleNpcDeckCharacterUuid01 string `json:"BattleNpcDeckCharacterUuid01"` 165 - BattleNpcDeckCharacterUuid02 string `json:"BattleNpcDeckCharacterUuid02"` 166 - BattleNpcDeckCharacterUuid03 string `json:"BattleNpcDeckCharacterUuid03"` 167 - } 168 - 169 - type BattleNpcDropCategoryRow struct { 170 - BattleNpcId int32 `json:"BattleNpcId"` 171 - BattleNpcDeckCharacterUuid string `json:"BattleNpcDeckCharacterUuid"` 172 - BattleDropCategoryId int32 `json:"BattleDropCategoryId"` 173 - } 174 - 175 10 type BattleDropInfo struct { 176 11 QuestSceneId int32 177 12 BattleDropCategoryId int32 178 13 } 179 14 180 - type TutorialUnlockConditionRow struct { 181 - TutorialType int32 `json:"TutorialType"` 182 - TutorialUnlockConditionType int32 `json:"TutorialUnlockConditionType"` 183 - ConditionValue int32 `json:"ConditionValue"` 184 - } 185 - 186 - type RentalDeckRow struct { 187 - BattleGroupId int32 `json:"BattleGroupId"` 188 - } 189 - 190 - type UserLevelRow struct { 191 - UserLevel int32 `json:"UserLevel"` 192 - MaxStamina int32 `json:"MaxStamina"` 193 - } 194 - 195 15 type QuestCatalog struct { 196 - SceneById map[int32]QuestSceneRow 197 - MissionById map[int32]QuestMissionRow 198 - QuestById map[int32]QuestRow 16 + SceneById map[int32]EntityMQuestScene 17 + MissionById map[int32]EntityMQuestMission 18 + QuestById map[int32]EntityMQuest 199 19 MissionIdsByQuestId map[int32][]int32 200 20 RouteIdByQuestId map[int32]int32 201 21 SceneIdsByQuestId map[int32][]int32 202 22 OrderedQuestIds []int32 203 - FirstClearRewardsByGroupId map[int32][]QuestFirstClearRewardGroupRow 204 - FirstClearRewardSwitchesByQuestId map[int32][]QuestFirstClearRewardSwitchRow 205 - MissionRewardsByMissionId map[int32][]QuestMissionRewardRow 23 + FirstClearRewardsByGroupId map[int32][]EntityMQuestFirstClearRewardGroup 24 + FirstClearRewardSwitchesByQuestId map[int32][]EntityMQuestFirstClearRewardSwitch 25 + MissionRewardsByMissionId map[int32][]EntityMQuestMissionReward 206 26 WeaponIdsByReleaseConditionGroupId map[int32][]int32 207 - ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow 208 - SceneGrantsBySceneId map[int32][]QuestSceneGrantRow 209 - BattleDropRewardById map[int32]BattleDropRewardRow 27 + ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup 28 + SceneGrantsBySceneId map[int32][]EntityMUserQuestSceneGrantPossession 29 + BattleDropRewardById map[int32]EntityMBattleDropReward 210 30 PickupRewardIdsByGroupId map[int32][]int32 211 31 BattleDropsByQuestId map[int32][]BattleDropInfo 212 - ReplayFlowRewardsByGroupId map[int32][]QuestReplayFlowRewardGroupRow 32 + ReplayFlowRewardsByGroupId map[int32][]EntityMQuestReplayFlowRewardGroup 213 33 RentalQuestIds map[int32]bool 214 - TutorialUnlockConditions []TutorialUnlockConditionRow 34 + TutorialUnlockConditions []EntityMTutorialUnlockCondition 215 35 ChapterLastSceneByQuestId map[int32]int32 216 36 SeasonIdByRouteId map[int32]int32 217 37 ··· 221 41 CostumeMaxLevelByRarity map[int32]NumericalFunc 222 42 MaxStaminaByLevel map[int32]int32 223 43 224 - CostumeById map[int32]CostumeMasterRow 225 - WeaponById map[int32]WeaponMasterRow 44 + CostumeById map[int32]EntityMCostume 45 + WeaponById map[int32]EntityMWeapon 226 46 227 47 WeaponSkillSlots map[int32][]int32 228 48 WeaponAbilitySlots map[int32][]int32 ··· 231 51 } 232 52 233 53 func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) { 234 - scenes, err := utils.ReadJSON[QuestSceneRow]("EntityMQuestSceneTable.json") 54 + scenes, err := utils.ReadTable[EntityMQuestScene]("m_quest_scene") 235 55 if err != nil { 236 56 return nil, fmt.Errorf("load quest scene table: %w", err) 237 57 } ··· 245 65 return scenes[i].QuestSceneId < scenes[j].QuestSceneId 246 66 }) 247 67 248 - missions, err := utils.ReadJSON[QuestMissionRow]("EntityMQuestMissionTable.json") 68 + missions, err := utils.ReadTable[EntityMQuestMission]("m_quest_mission") 249 69 if err != nil { 250 70 return nil, fmt.Errorf("load quest mission table: %w", err) 251 71 } 252 72 253 - quests, err := utils.ReadJSON[QuestRow]("EntityMQuestTable.json") 73 + quests, err := utils.ReadTable[EntityMQuest]("m_quest") 254 74 if err != nil { 255 75 return nil, fmt.Errorf("load quest table: %w", err) 256 76 } 257 77 258 - missionGroups, err := utils.ReadJSON[QuestMissionGroupRow]("EntityMQuestMissionGroupTable.json") 78 + missionGroups, err := utils.ReadTable[EntityMQuestMissionGroup]("m_quest_mission_group") 259 79 if err != nil { 260 80 return nil, fmt.Errorf("load quest mission group table: %w", err) 261 81 } ··· 269 89 return missionGroups[i].QuestMissionId < missionGroups[j].QuestMissionId 270 90 }) 271 91 272 - sequences, err := utils.ReadJSON[MainQuestSequenceRow]("EntityMMainQuestSequenceTable.json") 92 + sequences, err := utils.ReadTable[EntityMMainQuestSequence]("m_main_quest_sequence") 273 93 if err != nil { 274 94 return nil, fmt.Errorf("load main quest sequence table: %w", err) 275 95 } ··· 283 103 return sequences[i].QuestId < sequences[j].QuestId 284 104 }) 285 105 286 - chapters, err := utils.ReadJSON[MainQuestChapterRow]("EntityMMainQuestChapterTable.json") 106 + chapters, err := utils.ReadTable[EntityMMainQuestChapter]("m_main_quest_chapter") 287 107 if err != nil { 288 108 return nil, fmt.Errorf("load main quest chapter table: %w", err) 289 109 } 290 110 291 - routes, err := utils.ReadJSON[MainQuestRouteRow]("EntityMMainQuestRouteTable.json") 111 + routes, err := utils.ReadTable[EntityMMainQuestRoute]("m_main_quest_route") 292 112 if err != nil { 293 113 return nil, fmt.Errorf("load main quest route table: %w", err) 294 114 } ··· 297 117 seasonIdByRouteId[r.MainQuestRouteId] = r.MainQuestSeasonId 298 118 } 299 119 300 - firstClearSwitches, err := utils.ReadJSON[QuestFirstClearRewardSwitchRow]("EntityMQuestFirstClearRewardSwitchTable.json") 120 + firstClearSwitches, err := utils.ReadTable[EntityMQuestFirstClearRewardSwitch]("m_quest_first_clear_reward_switch") 301 121 if err != nil { 302 122 return nil, fmt.Errorf("load quest first clear reward switch table: %w", err) 303 123 } 304 124 305 - firstClearRewards, err := utils.ReadJSON[QuestFirstClearRewardGroupRow]("EntityMQuestFirstClearRewardGroupTable.json") 125 + firstClearRewards, err := utils.ReadTable[EntityMQuestFirstClearRewardGroup]("m_quest_first_clear_reward_group") 306 126 if err != nil { 307 127 return nil, fmt.Errorf("load quest first clear reward group table: %w", err) 308 128 } ··· 316 136 return firstClearRewards[i].QuestFirstClearRewardType < firstClearRewards[j].QuestFirstClearRewardType 317 137 }) 318 138 319 - replayFlowRewards, err := utils.ReadJSON[QuestReplayFlowRewardGroupRow]("EntityMQuestReplayFlowRewardGroupTable.json") 139 + replayFlowRewards, err := utils.ReadTable[EntityMQuestReplayFlowRewardGroup]("m_quest_replay_flow_reward_group") 320 140 if err != nil { 321 141 return nil, fmt.Errorf("load quest replay flow reward group table: %w", err) 322 142 } ··· 327 147 return replayFlowRewards[i].SortOrder < replayFlowRewards[j].SortOrder 328 148 }) 329 149 330 - missionRewards, err := utils.ReadJSON[QuestMissionRewardRow]("EntityMQuestMissionRewardTable.json") 150 + missionRewards, err := utils.ReadTable[EntityMQuestMissionReward]("m_quest_mission_reward") 331 151 if err != nil { 332 152 return nil, fmt.Errorf("load quest mission reward table: %w", err) 333 153 } 334 154 335 - weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json") 155 + weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon") 336 156 if err != nil { 337 157 return nil, fmt.Errorf("load weapon table: %w", err) 338 158 } 339 159 340 - weaponSkillGroups, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json") 160 + weaponSkillGroups, err := utils.ReadTable[EntityMWeaponSkillGroup]("m_weapon_skill_group") 341 161 if err != nil { 342 162 return nil, fmt.Errorf("load weapon skill group table: %w", err) 343 163 } 344 164 345 - weaponAbilityGroups, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json") 165 + weaponAbilityGroups, err := utils.ReadTable[EntityMWeaponAbilityGroup]("m_weapon_ability_group") 346 166 if err != nil { 347 167 return nil, fmt.Errorf("load weapon ability group table: %w", err) 348 168 } 349 169 350 - releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json") 170 + releaseConditions, err := utils.ReadTable[EntityMWeaponStoryReleaseConditionGroup]("m_weapon_story_release_condition_group") 351 171 if err != nil { 352 172 return nil, fmt.Errorf("load weapon story release condition table: %w", err) 353 173 } 354 174 355 - costumeMasters, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json") 175 + costumeMasters, err := utils.ReadTable[EntityMCostume]("m_costume") 356 176 if err != nil { 357 177 return nil, fmt.Errorf("load costume table: %w", err) 358 178 } 359 179 360 - costumeRarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json") 180 + costumeRarities, err := utils.ReadTable[EntityMCostumeRarity]("m_costume_rarity") 361 181 if err != nil { 362 182 return nil, fmt.Errorf("load costume rarity table: %w", err) 363 183 } 364 184 365 - sceneGrants, err := utils.ReadJSON[QuestSceneGrantRow]("EntityMUserQuestSceneGrantPossessionTable.json") 185 + sceneGrants, err := utils.ReadTable[EntityMUserQuestSceneGrantPossession]("m_user_quest_scene_grant_possession") 366 186 if err != nil { 367 187 return nil, fmt.Errorf("load quest scene grant table: %w", err) 368 188 } 369 189 370 - battleDropRewards, err := utils.ReadJSON[BattleDropRewardRow]("EntityMBattleDropRewardTable.json") 190 + battleDropRewards, err := utils.ReadTable[EntityMBattleDropReward]("m_battle_drop_reward") 371 191 if err != nil { 372 192 return nil, fmt.Errorf("load battle drop reward table: %w", err) 373 193 } 374 194 375 - pickupRewardGroups, err := utils.ReadJSON[QuestPickupRewardGroupRow]("EntityMQuestPickupRewardGroupTable.json") 195 + pickupRewardGroups, err := utils.ReadTable[EntityMQuestPickupRewardGroup]("m_quest_pickup_reward_group") 376 196 if err != nil { 377 197 return nil, fmt.Errorf("load quest pickup reward group table: %w", err) 378 198 } ··· 383 203 return pickupRewardGroups[i].SortOrder < pickupRewardGroups[j].SortOrder 384 204 }) 385 205 386 - sceneBattles, err := utils.ReadJSON[QuestSceneBattleRow]("EntityMQuestSceneBattleTable.json") 206 + sceneBattles, err := utils.ReadTable[EntityMQuestSceneBattle]("m_quest_scene_battle") 387 207 if err != nil { 388 208 return nil, fmt.Errorf("load quest scene battle table: %w", err) 389 209 } 390 210 391 - battleGroups, err := utils.ReadJSON[BattleGroupRow]("EntityMBattleGroupTable.json") 211 + battleGroups, err := utils.ReadTable[EntityMBattleGroup]("m_battle_group") 392 212 if err != nil { 393 213 return nil, fmt.Errorf("load battle group table: %w", err) 394 214 } 395 215 396 - battles, err := utils.ReadJSON[BattleRow]("EntityMBattleTable.json") 216 + battles, err := utils.ReadTable[EntityMBattle]("m_battle") 397 217 if err != nil { 398 218 return nil, fmt.Errorf("load battle table: %w", err) 399 219 } 400 220 401 - npcDecks, err := utils.ReadJSON[BattleNpcDeckRow]("EntityMBattleNpcDeckTable.json") 221 + npcDecks, err := utils.ReadTable[EntityMBattleNpcDeck]("m_battle_npc_deck") 402 222 if err != nil { 403 223 return nil, fmt.Errorf("load battle npc deck table: %w", err) 404 224 } 405 225 406 - npcDropCategories, err := utils.ReadJSON[BattleNpcDropCategoryRow]("EntityMBattleNpcDeckCharacterDropCategoryTable.json") 226 + npcDropCategories, err := utils.ReadTable[EntityMBattleNpcDeckCharacterDropCategory]("m_battle_npc_deck_character_drop_category") 407 227 if err != nil { 408 228 return nil, fmt.Errorf("load battle npc drop category table: %w", err) 409 229 } 410 230 411 - rentalDecks, err := utils.ReadJSON[RentalDeckRow]("EntityMBattleRentalDeckTable.json") 231 + rentalDecks, err := utils.ReadTable[EntityMBattleRentalDeck]("m_battle_rental_deck") 412 232 if err != nil { 413 233 return nil, fmt.Errorf("load battle rental deck table: %w", err) 414 234 } 415 235 416 - tutorialUnlockConds, err := utils.ReadJSON[TutorialUnlockConditionRow]("EntityMTutorialUnlockConditionTable.json") 236 + tutorialUnlockConds, err := utils.ReadTable[EntityMTutorialUnlockCondition]("m_tutorial_unlock_condition") 417 237 if err != nil { 418 238 return nil, fmt.Errorf("load tutorial unlock condition table: %w", err) 419 239 } ··· 423 243 return nil, err 424 244 } 425 245 426 - userLevels, err := utils.ReadJSON[UserLevelRow]("EntityMUserLevelTable.json") 246 + userLevels, err := utils.ReadTable[EntityMUserLevel]("m_user_level") 427 247 if err != nil { 428 248 return nil, fmt.Errorf("load user level table: %w", err) 429 249 } ··· 450 270 } 451 271 } 452 272 453 - costumeById := make(map[int32]CostumeMasterRow, len(costumeMasters)) 273 + costumeById := make(map[int32]EntityMCostume, len(costumeMasters)) 454 274 for _, cm := range costumeMasters { 455 275 costumeById[cm.CostumeId] = cm 456 276 } 457 277 458 - weaponById := make(map[int32]WeaponMasterRow, len(weapons)) 278 + weaponById := make(map[int32]EntityMWeapon, len(weapons)) 459 279 for _, w := range weapons { 460 280 weaponById[w.WeaponId] = w 461 281 } ··· 469 289 abilitySlots[row.WeaponAbilityGroupId] = append(abilitySlots[row.WeaponAbilityGroupId], row.SlotNumber) 470 290 } 471 291 472 - sceneById := make(map[int32]QuestSceneRow, len(scenes)) 292 + sceneById := make(map[int32]EntityMQuestScene, len(scenes)) 473 293 sceneIdsByQuestId := make(map[int32][]int32) 474 294 for _, scene := range scenes { 475 295 sceneById[scene.QuestSceneId] = scene 476 296 sceneIdsByQuestId[scene.QuestId] = append(sceneIdsByQuestId[scene.QuestId], scene.QuestSceneId) 477 297 } 478 298 479 - missionById := make(map[int32]QuestMissionRow, len(missions)) 299 + missionById := make(map[int32]EntityMQuestMission, len(missions)) 480 300 for _, mission := range missions { 481 301 missionById[mission.QuestMissionId] = mission 482 302 } 483 303 484 - questById := make(map[int32]QuestRow, len(quests)) 304 + questById := make(map[int32]EntityMQuest, len(quests)) 485 305 for _, quest := range quests { 486 306 questById[quest.QuestId] = quest 487 307 } ··· 500 320 missionIdsByQuestId[questId] = append([]int32(nil), missionIds...) 501 321 } 502 322 503 - chapterBySequenceId := make(map[int32]MainQuestChapterRow, len(chapters)) 323 + chapterBySequenceId := make(map[int32]EntityMMainQuestChapter, len(chapters)) 504 324 for _, chapter := range chapters { 505 325 chapterBySequenceId[chapter.MainQuestSequenceGroupId] = chapter 506 326 } ··· 511 331 } 512 332 } 513 333 514 - sortedChapters := make([]MainQuestChapterRow, len(chapters)) 334 + sortedChapters := make([]EntityMMainQuestChapter, len(chapters)) 515 335 copy(sortedChapters, chapters) 516 336 sort.Slice(sortedChapters, func(i, j int) bool { 517 337 return sortedChapters[i].SortOrder < sortedChapters[j].SortOrder 518 338 }) 519 - sequencesByGroupId := make(map[int32][]MainQuestSequenceRow) 339 + sequencesByGroupId := make(map[int32][]EntityMMainQuestSequence) 520 340 for _, seq := range sequences { 521 341 sequencesByGroupId[seq.MainQuestSequenceId] = append(sequencesByGroupId[seq.MainQuestSequenceId], seq) 522 342 } ··· 544 364 } 545 365 } 546 366 547 - firstClearRewardsByGroupId := make(map[int32][]QuestFirstClearRewardGroupRow, len(firstClearRewards)) 367 + firstClearRewardsByGroupId := make(map[int32][]EntityMQuestFirstClearRewardGroup, len(firstClearRewards)) 548 368 for _, reward := range firstClearRewards { 549 369 firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId] = append( 550 370 firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId], reward) 551 371 } 552 372 553 - replayFlowRewardsByGroupId := make(map[int32][]QuestReplayFlowRewardGroupRow, len(replayFlowRewards)) 373 + replayFlowRewardsByGroupId := make(map[int32][]EntityMQuestReplayFlowRewardGroup, len(replayFlowRewards)) 554 374 for _, reward := range replayFlowRewards { 555 375 replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId] = append( 556 376 replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId], reward) 557 377 } 558 378 559 - firstClearRewardSwitchesByQuestId := make(map[int32][]QuestFirstClearRewardSwitchRow, len(firstClearSwitches)) 379 + firstClearRewardSwitchesByQuestId := make(map[int32][]EntityMQuestFirstClearRewardSwitch, len(firstClearSwitches)) 560 380 for _, switchRow := range firstClearSwitches { 561 381 firstClearRewardSwitchesByQuestId[switchRow.QuestId] = append( 562 382 firstClearRewardSwitchesByQuestId[switchRow.QuestId], switchRow) 563 383 } 564 384 565 - missionRewardsByMissionId := make(map[int32][]QuestMissionRewardRow, len(missionRewards)) 385 + missionRewardsByMissionId := make(map[int32][]EntityMQuestMissionReward, len(missionRewards)) 566 386 for _, reward := range missionRewards { 567 387 missionRewardsByMissionId[reward.QuestMissionRewardId] = append( 568 388 missionRewardsByMissionId[reward.QuestMissionRewardId], reward) ··· 576 396 } 577 397 } 578 398 579 - releaseConditionsByGroupId := make(map[int32][]WeaponStoryReleaseConditionRow) 399 + releaseConditionsByGroupId := make(map[int32][]EntityMWeaponStoryReleaseConditionGroup) 580 400 for _, c := range releaseConditions { 581 401 releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId] = append( 582 402 releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c) 583 403 } 584 404 585 - sceneGrantsBySceneId := make(map[int32][]QuestSceneGrantRow) 405 + sceneGrantsBySceneId := make(map[int32][]EntityMUserQuestSceneGrantPossession) 586 406 for _, sg := range sceneGrants { 587 407 sceneGrantsBySceneId[sg.QuestSceneId] = append(sceneGrantsBySceneId[sg.QuestSceneId], sg) 588 408 } 589 409 590 - battleDropRewardById := make(map[int32]BattleDropRewardRow, len(battleDropRewards)) 410 + battleDropRewardById := make(map[int32]EntityMBattleDropReward, len(battleDropRewards)) 591 411 for _, bdr := range battleDropRewards { 592 412 battleDropRewardById[bdr.BattleDropRewardId] = bdr 593 413 } ··· 609 429 } 610 430 611 431 type npcDeckKey struct { 612 - BattleNpcId int32 613 - DeckType model.DeckType 432 + BattleNpcId int64 433 + DeckType int32 614 434 BattleNpcDeckNumber int32 615 435 } 616 - npcDeckByKey := make(map[npcDeckKey]BattleNpcDeckRow, len(npcDecks)) 436 + npcDeckByKey := make(map[npcDeckKey]EntityMBattleNpcDeck, len(npcDecks)) 617 437 for _, d := range npcDecks { 618 438 npcDeckByKey[npcDeckKey{d.BattleNpcId, d.DeckType, d.BattleNpcDeckNumber}] = d 619 439 } 620 440 621 - battleByIdMap := make(map[int32]BattleRow, len(battles)) 441 + battleByIdMap := make(map[int32]EntityMBattle, len(battles)) 622 442 for _, b := range battles { 623 443 battleByIdMap[b.BattleId] = b 624 444 } 625 445 626 446 type dropCatKey struct { 627 - BattleNpcId int32 447 + BattleNpcId int64 628 448 Uuid string 629 449 } 630 450 dropCategoryByKey := make(map[dropCatKey]int32, len(npcDropCategories))
+15 -64
server/internal/masterdata/shop.go
··· 8 8 "lunar-tear/server/internal/utils" 9 9 ) 10 10 11 - type ShopItemRow struct { 12 - ShopItemId int32 `json:"ShopItemId"` 13 - PriceType int32 `json:"PriceType"` 14 - PriceId int32 `json:"PriceId"` 15 - Price int32 `json:"Price"` 16 - ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"` 17 - } 18 - 19 - type ShopContentRow struct { 20 - ShopItemId int32 `json:"ShopItemId"` 21 - PossessionType int32 `json:"PossessionType"` 22 - PossessionId int32 `json:"PossessionId"` 23 - Count int32 `json:"Count"` 24 - } 25 - 26 - type ShopContentEffectRow struct { 27 - ShopItemId int32 `json:"ShopItemId"` 28 - EffectTargetType int32 `json:"EffectTargetType"` 29 - EffectValueType int32 `json:"EffectValueType"` 30 - EffectValue int32 `json:"EffectValue"` 31 - } 32 - 33 - type shopItemLimitedStockRow struct { 34 - ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"` 35 - MaxCount int32 `json:"MaxCount"` 36 - } 37 - 38 - type shopRow struct { 39 - ShopId int32 `json:"ShopId"` 40 - ShopGroupType int32 `json:"ShopGroupType"` 41 - ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"` 42 - } 43 - 44 - type shopItemCellGroupRow struct { 45 - ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"` 46 - ShopItemCellId int32 `json:"ShopItemCellId"` 47 - SortOrder int32 `json:"SortOrder"` 48 - } 49 - 50 - type shopItemCellRow struct { 51 - ShopItemCellId int32 `json:"ShopItemCellId"` 52 - ShopItemId int32 `json:"ShopItemId"` 53 - } 54 - 55 11 type ExchangeShopCell struct { 56 12 SortOrder int32 57 13 ShopItemId int32 58 14 } 59 15 60 16 type ShopCatalog struct { 61 - Items map[int32]ShopItemRow 62 - Contents map[int32][]ShopContentRow 63 - Effects map[int32][]ShopContentEffectRow 17 + Items map[int32]EntityMShopItem 18 + Contents map[int32][]EntityMShopItemContentPossession 19 + Effects map[int32][]EntityMShopItemContentEffect 64 20 MaxStaminaMillis map[int32]int32 // level -> max stamina in millis 65 21 LimitedStock map[int32]int32 // stock id -> max count 66 22 ItemShopPool []int32 // shop item IDs for the replaceable item shop, sorted by cell sort order 67 23 ExchangeShopCells map[int32][]ExchangeShopCell // shopId -> sorted cells for exchange shops 68 24 } 69 25 70 - type userLevelEntry struct { 71 - UserLevel int32 `json:"UserLevel"` 72 - MaxStamina int32 `json:"MaxStamina"` 73 - } 74 - 75 26 func LoadShopCatalog() (*ShopCatalog, error) { 76 - items, err := utils.ReadJSON[ShopItemRow]("EntityMShopItemTable.json") 27 + items, err := utils.ReadTable[EntityMShopItem]("m_shop_item") 77 28 if err != nil { 78 29 return nil, fmt.Errorf("load shop item table: %w", err) 79 30 } 80 - contents, err := utils.ReadJSON[ShopContentRow]("EntityMShopItemContentPossessionTable.json") 31 + contents, err := utils.ReadTable[EntityMShopItemContentPossession]("m_shop_item_content_possession") 81 32 if err != nil { 82 33 return nil, fmt.Errorf("load shop content possession table: %w", err) 83 34 } 84 - effects, err := utils.ReadJSON[ShopContentEffectRow]("EntityMShopItemContentEffectTable.json") 35 + effects, err := utils.ReadTable[EntityMShopItemContentEffect]("m_shop_item_content_effect") 85 36 if err != nil { 86 37 return nil, fmt.Errorf("load shop content effect table: %w", err) 87 38 } 88 - userLevels, err := utils.ReadJSON[userLevelEntry]("EntityMUserLevelTable.json") 39 + userLevels, err := utils.ReadTable[EntityMUserLevel]("m_user_level") 89 40 if err != nil { 90 41 return nil, fmt.Errorf("load user level table: %w", err) 91 42 } 92 - stockRows, err := utils.ReadJSON[shopItemLimitedStockRow]("EntityMShopItemLimitedStockTable.json") 43 + stockRows, err := utils.ReadTable[EntityMShopItemLimitedStock]("m_shop_item_limited_stock") 93 44 if err != nil { 94 45 return nil, fmt.Errorf("load shop item limited stock table: %w", err) 95 46 } 96 47 97 48 catalog := &ShopCatalog{ 98 - Items: make(map[int32]ShopItemRow, len(items)), 99 - Contents: make(map[int32][]ShopContentRow, len(contents)), 100 - Effects: make(map[int32][]ShopContentEffectRow, len(effects)), 49 + Items: make(map[int32]EntityMShopItem, len(items)), 50 + Contents: make(map[int32][]EntityMShopItemContentPossession, len(contents)), 51 + Effects: make(map[int32][]EntityMShopItemContentEffect, len(effects)), 101 52 MaxStaminaMillis: make(map[int32]int32, len(userLevels)), 102 53 LimitedStock: make(map[int32]int32, len(stockRows)), 103 54 } ··· 117 68 catalog.LimitedStock[row.ShopItemLimitedStockId] = row.MaxCount 118 69 } 119 70 120 - shops, err := utils.ReadJSON[shopRow]("EntityMShopTable.json") 71 + shops, err := utils.ReadTable[EntityMShop]("m_shop") 121 72 if err != nil { 122 73 return nil, fmt.Errorf("load shop table: %w", err) 123 74 } 124 - cellGroups, err := utils.ReadJSON[shopItemCellGroupRow]("EntityMShopItemCellGroupTable.json") 75 + cellGroups, err := utils.ReadTable[EntityMShopItemCellGroup]("m_shop_item_cell_group") 125 76 if err != nil { 126 77 return nil, fmt.Errorf("load shop item cell group table: %w", err) 127 78 } 128 - cells, err := utils.ReadJSON[shopItemCellRow]("EntityMShopItemCellTable.json") 79 + cells, err := utils.ReadTable[EntityMShopItemCell]("m_shop_item_cell") 129 80 if err != nil { 130 81 return nil, fmt.Errorf("load shop item cell table: %w", err) 131 82 } ··· 135 86 cellIdToItemId[c.ShopItemCellId] = c.ShopItemId 136 87 } 137 88 138 - cellGroupByCGId := make(map[int32][]shopItemCellGroupRow, len(cellGroups)) 89 + cellGroupByCGId := make(map[int32][]EntityMShopItemCellGroup, len(cellGroups)) 139 90 for _, cg := range cellGroups { 140 91 cellGroupByCGId[cg.ShopItemCellGroupId] = append(cellGroupByCGId[cg.ShopItemCellGroupId], cg) 141 92 }
+1 -7
server/internal/masterdata/sidestory.go
··· 5 5 "lunar-tear/server/internal/utils" 6 6 ) 7 7 8 - type sideStorySceneRow struct { 9 - SideStoryQuestId int32 `json:"SideStoryQuestId"` 10 - SideStoryQuestSceneId int32 `json:"SideStoryQuestSceneId"` 11 - SortOrder int32 `json:"SortOrder"` 12 - } 13 - 14 8 type SideStoryCatalog struct { 15 9 FirstSceneByQuestId map[int32]int32 16 10 } 17 11 18 12 func LoadSideStoryCatalog() *SideStoryCatalog { 19 - scenes, err := utils.ReadJSON[sideStorySceneRow]("EntityMSideStoryQuestSceneTable.json") 13 + scenes, err := utils.ReadTable[EntityMSideStoryQuestScene]("m_side_story_quest_scene") 20 14 if err != nil { 21 15 log.Fatalf("load side story quest scene table: %v", err) 22 16 }
+35 -152
server/internal/masterdata/weapon.go
··· 9 9 "lunar-tear/server/internal/utils" 10 10 ) 11 11 12 - type WeaponMasterRow struct { 13 - WeaponId int32 `json:"WeaponId"` 14 - RarityType int32 `json:"RarityType"` 15 - WeaponType int32 `json:"WeaponType"` 16 - WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"` 17 - WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"` 18 - WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"` 19 - WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"` 20 - WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"` 21 - } 22 - 23 - type WeaponStoryReleaseConditionRow struct { 24 - WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"` 25 - StoryIndex int32 `json:"StoryIndex"` 26 - WeaponStoryReleaseConditionType model.WeaponStoryReleaseConditionType `json:"WeaponStoryReleaseConditionType"` 27 - ConditionValue int32 `json:"ConditionValue"` 28 - WeaponStoryReleaseConditionOperationGroupId int32 `json:"WeaponStoryReleaseConditionOperationGroupId"` 29 - } 30 - 31 - type WeaponSkillGroupRow struct { 32 - WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"` 33 - SlotNumber int32 `json:"SlotNumber"` 34 - SkillId int32 `json:"SkillId"` 35 - WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"` 36 - } 37 - 38 - type WeaponAbilityGroupRow struct { 39 - WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"` 40 - SlotNumber int32 `json:"SlotNumber"` 41 - AbilityId int32 `json:"AbilityId"` 42 - WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"` 43 - } 44 - 45 - type weaponSpecificEnhanceRow struct { 46 - WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"` 47 - BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"` 48 - SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"` 49 - RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"` 50 - EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"` 51 - EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"` 52 - MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"` 53 - EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"` 54 - LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"` 55 - LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"` 56 - MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"` 57 - SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"` 58 - MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"` 59 - AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"` 60 - } 61 - 62 - type weaponConsumeExchangeRow struct { 63 - WeaponId int32 `json:"WeaponId"` 64 - ConsumableItemId int32 `json:"ConsumableItemId"` 65 - Count int32 `json:"Count"` 66 - } 67 - 68 - type WeaponEvolutionGroupRow struct { 69 - WeaponEvolutionGroupId int32 `json:"WeaponEvolutionGroupId"` 70 - EvolutionOrder int32 `json:"EvolutionOrder"` 71 - WeaponId int32 `json:"WeaponId"` 72 - } 73 - 74 - type WeaponEvolutionMaterialRow struct { 75 - WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"` 76 - MaterialId int32 `json:"MaterialId"` 77 - Count int32 `json:"Count"` 78 - SortOrder int32 `json:"SortOrder"` 79 - } 80 - 81 - type WeaponSkillEnhanceMaterialRow struct { 82 - WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"` 83 - SkillLevel int32 `json:"SkillLevel"` 84 - MaterialId int32 `json:"MaterialId"` 85 - Count int32 `json:"Count"` 86 - SortOrder int32 `json:"SortOrder"` 87 - } 88 - 89 - type WeaponAbilityEnhanceMaterialRow struct { 90 - WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"` 91 - AbilityLevel int32 `json:"AbilityLevel"` 92 - MaterialId int32 `json:"MaterialId"` 93 - Count int32 `json:"Count"` 94 - SortOrder int32 `json:"SortOrder"` 95 - } 96 - 97 - type WeaponAwakenRow struct { 98 - WeaponId int32 `json:"WeaponId"` 99 - WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"` 100 - WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"` 101 - ConsumeGold int32 `json:"ConsumeGold"` 102 - LevelLimitUp int32 `json:"LevelLimitUp"` 103 - } 104 - 105 12 type WeaponAwakenEffectGroupRow struct { 106 13 WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"` 107 14 WeaponAwakenEffectType int32 `json:"WeaponAwakenEffectType"` 108 15 WeaponAwakenEffectId int32 `json:"WeaponAwakenEffectId"` 109 16 } 110 17 111 - type WeaponAwakenMaterialGroupRow struct { 112 - WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"` 113 - MaterialId int32 `json:"MaterialId"` 114 - Count int32 `json:"Count"` 115 - SortOrder int32 `json:"SortOrder"` 116 - } 117 - 118 - type weaponRarityEnhanceRow struct { 119 - RarityType int32 `json:"RarityType"` 120 - BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"` 121 - SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"` 122 - RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"` 123 - EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"` 124 - EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"` 125 - MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"` 126 - EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"` 127 - LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"` 128 - LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"` 129 - MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"` 130 - SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"` 131 - MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"` 132 - AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"` 133 - } 134 - 135 18 type WeaponCatalog struct { 136 - Weapons map[int32]WeaponMasterRow 137 - Materials map[int32]MaterialRow 19 + Weapons map[int32]EntityMWeapon 20 + Materials map[int32]EntityMMaterial 138 21 ExpByEnhanceId map[int32][]int32 139 22 GoldCostByEnhanceId map[int32]NumericalFunc 140 23 MaxLevelByEnhanceId map[int32]NumericalFunc 141 24 SellPriceByEnhanceId map[int32]NumericalFunc 142 25 MedalsByWeaponId map[int32]map[int32]int32 // WeaponId -> ConsumableItemId -> Count 143 26 EvolutionNextWeaponId map[int32]int32 144 - EvolutionOrder map[int32]int32 // WeaponId -> 0-based position in evolution chain 145 - EvolutionMaterials map[int32][]WeaponEvolutionMaterialRow // WeaponEvolutionMaterialGroupId -> materials 27 + EvolutionOrder map[int32]int32 // WeaponId -> 0-based position in evolution chain 28 + EvolutionMaterials map[int32][]EntityMWeaponEvolutionMaterialGroup // WeaponEvolutionMaterialGroupId -> materials 146 29 EvolutionCostByEnhanceId map[int32]NumericalFunc 147 30 AbilitySlots map[int32][]int32 // WeaponAbilityGroupId -> slot numbers 148 - SkillGroupsByGroupId map[int32][]WeaponSkillGroupRow 149 - SkillEnhanceMats map[[2]int32][]WeaponSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel] 31 + SkillGroupsByGroupId map[int32][]EntityMWeaponSkillGroup 32 + SkillEnhanceMats map[[2]int32][]EntityMWeaponSkillEnhancementMaterial // key: [enhancementMaterialId, skillLevel] 150 33 SkillMaxLevelByEnhanceId map[int32]NumericalFunc 151 34 SkillCostByEnhanceId map[int32]NumericalFunc 152 - AbilityGroupsByGroupId map[int32][]WeaponAbilityGroupRow 153 - AbilityEnhanceMats map[[2]int32][]WeaponAbilityEnhanceMaterialRow // key: [enhancementMaterialId, abilityLevel] 35 + AbilityGroupsByGroupId map[int32][]EntityMWeaponAbilityGroup 36 + AbilityEnhanceMats map[[2]int32][]EntityMWeaponAbilityEnhancementMaterial // key: [enhancementMaterialId, abilityLevel] 154 37 AbilityMaxLevelByEnhanceId map[int32]NumericalFunc 155 38 AbilityCostByEnhanceId map[int32]NumericalFunc 156 39 EnhanceCostByWeaponByEnhanceId map[int32]NumericalFunc 157 40 LimitBreakCostByWeaponByEnhanceId map[int32]NumericalFunc 158 41 LimitBreakCostByMaterialByEnhanceId map[int32]NumericalFunc 159 42 BaseExpByEnhanceId map[int32]int32 160 - ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow 43 + ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup 161 44 162 - AwakenByWeaponId map[int32]WeaponAwakenRow 163 - AwakenMaterialsByGroupId map[int32][]WeaponAwakenMaterialGroupRow 45 + AwakenByWeaponId map[int32]EntityMWeaponAwaken 46 + AwakenMaterialsByGroupId map[int32][]EntityMWeaponAwakenMaterialGroup 164 47 } 165 48 166 49 func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) { 167 - weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json") 50 + weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon") 168 51 if err != nil { 169 52 return nil, fmt.Errorf("load weapon table: %w", err) 170 53 } 171 54 172 - enhanceRows, err := utils.ReadJSON[weaponSpecificEnhanceRow]("EntityMWeaponSpecificEnhanceTable.json") 55 + enhanceRows, err := utils.ReadTable[EntityMWeaponSpecificEnhance]("m_weapon_specific_enhance") 173 56 if err != nil { 174 57 return nil, fmt.Errorf("load weapon specific enhance table: %w", err) 175 58 } 176 59 177 - rarityEnhanceRows, err := utils.ReadJSON[weaponRarityEnhanceRow]("EntityMWeaponRarityTable.json") 60 + rarityEnhanceRows, err := utils.ReadTable[EntityMWeaponRarity]("m_weapon_rarity") 178 61 if err != nil { 179 62 return nil, fmt.Errorf("load weapon rarity table: %w", err) 180 63 } ··· 189 72 return nil, fmt.Errorf("load function resolver: %w", err) 190 73 } 191 74 192 - exchangeRows, err := utils.ReadJSON[weaponConsumeExchangeRow]("EntityMWeaponConsumeExchangeConsumableItemGroupTable.json") 75 + exchangeRows, err := utils.ReadTable[EntityMWeaponConsumeExchangeConsumableItemGroup]("m_weapon_consume_exchange_consumable_item_group") 193 76 if err != nil { 194 77 return nil, fmt.Errorf("load weapon consume exchange table: %w", err) 195 78 } 196 79 197 - evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json") 80 + evoGroupRows, err := utils.ReadTable[EntityMWeaponEvolutionGroup]("m_weapon_evolution_group") 198 81 if err != nil { 199 82 return nil, fmt.Errorf("load weapon evolution group table: %w", err) 200 83 } 201 - evoMatRows, err := utils.ReadJSON[WeaponEvolutionMaterialRow]("EntityMWeaponEvolutionMaterialGroupTable.json") 84 + evoMatRows, err := utils.ReadTable[EntityMWeaponEvolutionMaterialGroup]("m_weapon_evolution_material_group") 202 85 if err != nil { 203 86 return nil, fmt.Errorf("load weapon evolution material group table: %w", err) 204 87 } 205 - abilityGroupRows, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json") 88 + abilityGroupRows, err := utils.ReadTable[EntityMWeaponAbilityGroup]("m_weapon_ability_group") 206 89 if err != nil { 207 90 return nil, fmt.Errorf("load weapon ability group table: %w", err) 208 91 } 209 - skillGroupRows, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json") 92 + skillGroupRows, err := utils.ReadTable[EntityMWeaponSkillGroup]("m_weapon_skill_group") 210 93 if err != nil { 211 94 return nil, fmt.Errorf("load weapon skill group table: %w", err) 212 95 } 213 - skillMatRows, err := utils.ReadJSON[WeaponSkillEnhanceMaterialRow]("EntityMWeaponSkillEnhancementMaterialTable.json") 96 + skillMatRows, err := utils.ReadTable[EntityMWeaponSkillEnhancementMaterial]("m_weapon_skill_enhancement_material") 214 97 if err != nil { 215 98 return nil, fmt.Errorf("load weapon skill enhancement material table: %w", err) 216 99 } 217 - abilityMatRows, err := utils.ReadJSON[WeaponAbilityEnhanceMaterialRow]("EntityMWeaponAbilityEnhancementMaterialTable.json") 100 + abilityMatRows, err := utils.ReadTable[EntityMWeaponAbilityEnhancementMaterial]("m_weapon_ability_enhancement_material") 218 101 if err != nil { 219 102 return nil, fmt.Errorf("load weapon ability enhancement material table: %w", err) 220 103 } 221 - releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json") 104 + releaseConditions, err := utils.ReadTable[EntityMWeaponStoryReleaseConditionGroup]("m_weapon_story_release_condition_group") 222 105 if err != nil { 223 106 return nil, fmt.Errorf("load weapon story release condition table: %w", err) 224 107 } 225 108 226 - awakenRows, err := utils.ReadJSON[WeaponAwakenRow]("EntityMWeaponAwakenTable.json") 109 + awakenRows, err := utils.ReadTable[EntityMWeaponAwaken]("m_weapon_awaken") 227 110 if err != nil { 228 111 return nil, fmt.Errorf("load weapon awaken table: %w", err) 229 112 } 230 - awakenMatRows, err := utils.ReadJSON[WeaponAwakenMaterialGroupRow]("EntityMWeaponAwakenMaterialGroupTable.json") 113 + awakenMatRows, err := utils.ReadTable[EntityMWeaponAwakenMaterialGroup]("m_weapon_awaken_material_group") 231 114 if err != nil { 232 115 return nil, fmt.Errorf("load weapon awaken material group table: %w", err) 233 116 } 234 117 235 118 catalog := &WeaponCatalog{ 236 - Weapons: make(map[int32]WeaponMasterRow, len(weapons)), 119 + Weapons: make(map[int32]EntityMWeapon, len(weapons)), 237 120 Materials: matCatalog.ByType[model.MaterialTypeWeaponEnhancement], 238 121 ExpByEnhanceId: make(map[int32][]int32, len(enhanceRows)), 239 122 GoldCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), ··· 242 125 MedalsByWeaponId: make(map[int32]map[int32]int32), 243 126 EvolutionNextWeaponId: make(map[int32]int32), 244 127 EvolutionOrder: make(map[int32]int32), 245 - EvolutionMaterials: make(map[int32][]WeaponEvolutionMaterialRow), 128 + EvolutionMaterials: make(map[int32][]EntityMWeaponEvolutionMaterialGroup), 246 129 EvolutionCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 247 130 AbilitySlots: make(map[int32][]int32), 248 - SkillGroupsByGroupId: make(map[int32][]WeaponSkillGroupRow), 249 - SkillEnhanceMats: make(map[[2]int32][]WeaponSkillEnhanceMaterialRow), 131 + SkillGroupsByGroupId: make(map[int32][]EntityMWeaponSkillGroup), 132 + SkillEnhanceMats: make(map[[2]int32][]EntityMWeaponSkillEnhancementMaterial), 250 133 SkillMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 251 134 SkillCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 252 - AbilityGroupsByGroupId: make(map[int32][]WeaponAbilityGroupRow), 253 - AbilityEnhanceMats: make(map[[2]int32][]WeaponAbilityEnhanceMaterialRow), 135 + AbilityGroupsByGroupId: make(map[int32][]EntityMWeaponAbilityGroup), 136 + AbilityEnhanceMats: make(map[[2]int32][]EntityMWeaponAbilityEnhancementMaterial), 254 137 AbilityMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 255 138 AbilityCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 256 139 EnhanceCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 257 140 LimitBreakCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 258 141 LimitBreakCostByMaterialByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)), 259 142 BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)), 260 - ReleaseConditionsByGroupId: make(map[int32][]WeaponStoryReleaseConditionRow), 143 + ReleaseConditionsByGroupId: make(map[int32][]EntityMWeaponStoryReleaseConditionGroup), 261 144 262 - AwakenByWeaponId: make(map[int32]WeaponAwakenRow, len(awakenRows)), 263 - AwakenMaterialsByGroupId: make(map[int32][]WeaponAwakenMaterialGroupRow), 145 + AwakenByWeaponId: make(map[int32]EntityMWeaponAwaken, len(awakenRows)), 146 + AwakenMaterialsByGroupId: make(map[int32][]EntityMWeaponAwakenMaterialGroup), 264 147 } 265 148 266 149 for _, w := range weapons { ··· 338 221 catalog.MedalsByWeaponId[ex.WeaponId][ex.ConsumableItemId] = ex.Count 339 222 } 340 223 341 - grouped := make(map[int32][]WeaponEvolutionGroupRow) 224 + grouped := make(map[int32][]EntityMWeaponEvolutionGroup) 342 225 for _, row := range evoGroupRows { 343 226 grouped[row.WeaponEvolutionGroupId] = append(grouped[row.WeaponEvolutionGroupId], row) 344 227 } ··· 399 282 400 283 // Rarity-based enhancement fallback: for weapons with WeaponSpecificEnhanceId == 0, 401 284 // use EntityMWeaponRarityTable curves via synthetic enhance IDs (-RarityType). 402 - rarityByType := make(map[int32]weaponRarityEnhanceRow, len(rarityEnhanceRows)) 285 + rarityByType := make(map[int32]EntityMWeaponRarity, len(rarityEnhanceRows)) 403 286 for _, r := range rarityEnhanceRows { 404 287 rarityByType[r.RarityType] = r 405 288 }
+77
server/internal/model/platform.go
··· 1 + package model 2 + 3 + import ( 4 + "context" 5 + "strconv" 6 + 7 + "google.golang.org/grpc/metadata" 8 + ) 9 + 10 + type ClientPlatform struct { 11 + OsType int32 // 1=iOS, 2=Android 12 + PlatformType int32 // 1=AppStore, 2=GooglePlay, 8=Amazon 13 + } 14 + 15 + const ( 16 + OsTypeIOS int32 = 1 17 + OsTypeAndroid int32 = 2 18 + 19 + PlatformTypeAppStore int32 = 1 20 + PlatformTypeGooglePlayStore int32 = 2 21 + PlatformTypeAmazonAppStore int32 = 8 22 + ) 23 + 24 + var DefaultPlatform = ClientPlatform{OsType: OsTypeAndroid, PlatformType: PlatformTypeGooglePlayStore} 25 + 26 + type platformKey struct{} 27 + 28 + func (p ClientPlatform) String() string { 29 + os := "unknown" 30 + switch p.OsType { 31 + case OsTypeIOS: 32 + os = "iOS" 33 + case OsTypeAndroid: 34 + os = "Android" 35 + } 36 + plat := "unknown" 37 + switch p.PlatformType { 38 + case PlatformTypeAppStore: 39 + plat = "AppStore" 40 + case PlatformTypeGooglePlayStore: 41 + plat = "GooglePlay" 42 + case PlatformTypeAmazonAppStore: 43 + plat = "Amazon" 44 + } 45 + return os + "/" + plat 46 + } 47 + 48 + func ClientPlatformFromHeaders(ctx context.Context) ClientPlatform { 49 + md, ok := metadata.FromIncomingContext(ctx) 50 + if !ok { 51 + return DefaultPlatform 52 + } 53 + 54 + p := DefaultPlatform 55 + if vals := md.Get("x-apb-os-type"); len(vals) > 0 { 56 + if v, err := strconv.ParseInt(vals[0], 10, 32); err == nil { 57 + p.OsType = int32(v) 58 + } 59 + } 60 + if vals := md.Get("x-apb-platform-type"); len(vals) > 0 { 61 + if v, err := strconv.ParseInt(vals[0], 10, 32); err == nil { 62 + p.PlatformType = int32(v) 63 + } 64 + } 65 + return p 66 + } 67 + 68 + func NewContextWithPlatform(ctx context.Context, p ClientPlatform) context.Context { 69 + return context.WithValue(ctx, platformKey{}, p) 70 + } 71 + 72 + func ClientPlatformFromContext(ctx context.Context) ClientPlatform { 73 + if p, ok := ctx.Value(platformKey{}).(ClientPlatform); ok { 74 + return p 75 + } 76 + return DefaultPlatform 77 + }
+1 -1
server/internal/questflow/event_quest.go
··· 83 83 84 84 h.applySceneGrants(user, questSceneId, nowMillis) 85 85 86 - if scene.QuestResultType == model.QuestResultTypeHalfResult { 86 + if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult { 87 87 h.clearQuestMissions(user, scene.QuestId, nowMillis) 88 88 } 89 89 }
+1 -1
server/internal/questflow/extra_quest.go
··· 80 80 81 81 h.applySceneGrants(user, questSceneId, nowMillis) 82 82 83 - if scene.QuestResultType == model.QuestResultTypeHalfResult { 83 + if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult { 84 84 h.clearQuestMissions(user, scene.QuestId, nowMillis) 85 85 } 86 86 }
+1 -1
server/internal/questflow/handler.go
··· 53 53 for i, r := range rows { 54 54 conds[i] = store.WeaponStoryReleaseCond{ 55 55 StoryIndex: r.StoryIndex, 56 - WeaponStoryReleaseConditionType: r.WeaponStoryReleaseConditionType, 56 + WeaponStoryReleaseConditionType: model.WeaponStoryReleaseConditionType(r.WeaponStoryReleaseConditionType), 57 57 ConditionValue: r.ConditionValue, 58 58 } 59 59 }
+1 -1
server/internal/questflow/quest.go
··· 23 23 } 24 24 } 25 25 26 - func isMainQuestPlayable(quest masterdata.QuestRow) bool { 26 + func isMainQuestPlayable(quest masterdata.EntityMQuest) bool { 27 27 return !quest.IsRunInTheBackground && quest.IsCountedAsQuest 28 28 } 29 29
+13 -13
server/internal/questflow/rewards.go
··· 20 20 return quest.QuestStateType == model.UserQuestStateTypeCleared 21 21 } 22 22 23 - func appendMissionRewards(dst []RewardGrant, src []masterdata.QuestMissionRewardRow) []RewardGrant { 23 + func appendMissionRewards(dst []RewardGrant, src []masterdata.EntityMQuestMissionReward) []RewardGrant { 24 24 for _, r := range src { 25 25 dst = append(dst, RewardGrant{ 26 - PossessionType: r.PossessionType, 26 + PossessionType: model.PossessionType(r.PossessionType), 27 27 PossessionId: r.PossessionId, 28 28 Count: r.Count, 29 29 }) ··· 31 31 return dst 32 32 } 33 33 34 - func (h *QuestHandler) firstClearRewardGroupId(user *store.UserState, questDef masterdata.QuestRow) int32 { 34 + func (h *QuestHandler) firstClearRewardGroupId(user *store.UserState, questDef masterdata.EntityMQuest) int32 { 35 35 rewardGroupId := questDef.QuestFirstClearRewardGroupId 36 36 for _, switchRow := range h.FirstClearRewardSwitchesByQuestId[questDef.QuestId] { 37 37 if h.isQuestCleared(user, switchRow.SwitchConditionClearQuestId) { ··· 57 57 rewardGroupId := h.firstClearRewardGroupId(user, questDef) 58 58 for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] { 59 59 outcome.FirstClearRewards = append(outcome.FirstClearRewards, RewardGrant{ 60 - PossessionType: reward.PossessionType, 60 + PossessionType: model.PossessionType(reward.PossessionType), 61 61 PossessionId: reward.PossessionId, 62 62 Count: reward.Count, 63 63 }) ··· 67 67 if user.MainQuest.CurrentQuestFlowType == int32(model.QuestFlowTypeReplayFlow) && questDef.QuestReplayFlowRewardGroupId > 0 { 68 68 for _, reward := range h.ReplayFlowRewardsByGroupId[questDef.QuestReplayFlowRewardGroupId] { 69 69 outcome.ReplayFlowFirstClearRewards = append(outcome.ReplayFlowFirstClearRewards, RewardGrant{ 70 - PossessionType: reward.PossessionType, 70 + PossessionType: model.PossessionType(reward.PossessionType), 71 71 PossessionId: reward.PossessionId, 72 72 Count: reward.Count, 73 73 }) ··· 78 78 regularMissionCount := 0 79 79 for _, questMissionId := range h.MissionIdsByQuestId[questId] { 80 80 missionDef, ok := h.MissionById[questMissionId] 81 - if !ok || missionDef.QuestMissionConditionType == model.QuestMissionConditionTypeComplete { 81 + if !ok || model.QuestMissionConditionType(missionDef.QuestMissionConditionType) == model.QuestMissionConditionTypeComplete { 82 82 continue 83 83 } 84 84 regularMissionCount++ ··· 103 103 if allRegularWillClear { 104 104 for _, questMissionId := range h.MissionIdsByQuestId[questId] { 105 105 missionDef, ok := h.MissionById[questMissionId] 106 - if !ok || missionDef.QuestMissionConditionType != model.QuestMissionConditionTypeComplete { 106 + if !ok || model.QuestMissionConditionType(missionDef.QuestMissionConditionType) != model.QuestMissionConditionTypeComplete { 107 107 continue 108 108 } 109 109 key := store.QuestMissionKey{QuestId: questId, QuestMissionId: questMissionId} ··· 122 122 return outcome 123 123 } 124 124 125 - func (h *QuestHandler) computeDropRewards(questDef masterdata.QuestRow) []RewardGrant { 125 + func (h *QuestHandler) computeDropRewards(questDef masterdata.EntityMQuest) []RewardGrant { 126 126 if questDef.QuestPickupRewardGroupId == 0 { 127 127 return nil 128 128 } ··· 130 130 for _, dropId := range h.PickupRewardIdsByGroupId[questDef.QuestPickupRewardGroupId] { 131 131 if bdr, ok := h.BattleDropRewardById[dropId]; ok { 132 132 drops = append(drops, RewardGrant{ 133 - PossessionType: bdr.PossessionType, 133 + PossessionType: model.PossessionType(bdr.PossessionType), 134 134 PossessionId: bdr.PossessionId, 135 135 Count: bdr.Count, 136 136 }) ··· 257 257 258 258 rewardGroupId := h.firstClearRewardGroupId(user, questDef) 259 259 for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] { 260 - h.applyRewardPossession(user, reward.PossessionType, reward.PossessionId, reward.Count, nowMillis) 260 + h.applyRewardPossession(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis) 261 261 } 262 262 } 263 263 ··· 365 365 } 366 366 rewardGroupId := h.firstClearRewardGroupId(user, questDef) 367 367 for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] { 368 - if reward.PossessionType != model.PossessionTypeWeapon { 368 + if model.PossessionType(reward.PossessionType) != model.PossessionTypeWeapon { 369 369 continue 370 370 } 371 371 weaponId := reward.PossessionId ··· 375 375 } 376 376 groupId := weapon.WeaponStoryReleaseConditionGroupId 377 377 for _, cond := range h.ReleaseConditionsByGroupId[groupId] { 378 - if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 { 378 + if model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 { 379 379 if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) { 380 380 changedIds = append(changedIds, weaponId) 381 381 } ··· 387 387 if resultType == model.QuestResultTypeFullResult { 388 388 for groupId, conditions := range h.ReleaseConditionsByGroupId { 389 389 for _, cond := range conditions { 390 - if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId { 390 + if model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId { 391 391 for _, weaponId := range h.WeaponIdsByReleaseConditionGroupId[groupId] { 392 392 if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) { 393 393 changedIds = append(changedIds, weaponId)
+2 -2
server/internal/questflow/scene.go
··· 15 15 return 16 16 } 17 17 for _, g := range grants { 18 - h.applyRewardPossession(user, g.PossessionType, g.PossessionId, g.Count, nowMillis) 18 + h.applyRewardPossession(user, model.PossessionType(g.PossessionType), g.PossessionId, g.Count, nowMillis) 19 19 } 20 20 } 21 21 ··· 123 123 } 124 124 125 125 if isMainQuestPlayable(quest) { 126 - if scene.QuestResultType == model.QuestResultTypeHalfResult { 126 + if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult { 127 127 nowMillis := gametime.NowMillis() 128 128 h.clearQuestMissions(user, quest.QuestId, nowMillis) 129 129 }
+8 -6
server/internal/service/asset_resolver.go
··· 20 20 Candidates []assetCandidate 21 21 } 22 22 23 - type assetResolver struct{} 23 + type assetResolver struct { 24 + baseDir string 25 + } 24 26 25 27 func newRevisionTracker() *revisionTracker { 26 28 return &revisionTracker{ ··· 28 30 } 29 31 } 30 32 31 - func newAssetResolver() *assetResolver { 32 - return &assetResolver{} 33 + func newAssetResolver(baseDir string) *assetResolver { 34 + return &assetResolver{baseDir: baseDir} 33 35 } 34 36 35 37 func normalizeClientAddr(remoteAddr string) string { ··· 73 75 resolution := assetResolution{ActiveRevision: activeRevision} 74 76 revision := activeRevision 75 77 76 - candidates, listSize, ok := objectIdToFilePathCandidates(revision, assetType, objectId) 78 + candidates, listSize, ok := objectIdToFilePathCandidates(r.baseDir, revision, assetType, objectId) 77 79 if ok && len(candidates) > 0 { 78 80 resolution.ListRevision = revision 79 81 resolution.ListSize = listSize ··· 94 96 if activeRevision == "" { 95 97 return 96 98 } 97 - _, _ = loadListBinIndex(activeRevision) 98 - _ = loadInfoIndex(activeRevision) 99 + _, _ = loadListBinIndex(r.baseDir, activeRevision) 100 + _ = loadInfoIndex(r.baseDir, activeRevision) 99 101 }
-2
server/internal/service/banner.go
··· 6 6 pb "lunar-tear/server/gen/proto" 7 7 "lunar-tear/server/internal/model" 8 8 "lunar-tear/server/internal/store" 9 - "lunar-tear/server/internal/userdata" 10 9 ) 11 10 12 11 type BannerServiceServer struct { ··· 44 43 TermLimitedGacha: termLimited, 45 44 LatestChapterGacha: latestChapter, 46 45 IsExistUnreadPop: false, 47 - DiffUserData: userdata.EmptyDiff(), 48 46 }, nil 49 47 }
+4 -9
server/internal/service/battle.go
··· 7 7 pb "lunar-tear/server/gen/proto" 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/store" 10 - "lunar-tear/server/internal/userdata" 11 10 ) 12 11 13 12 type BattleServiceServer struct { ··· 22 21 23 22 func (s *BattleServiceServer) StartWave(ctx context.Context, req *pb.StartWaveRequest) (*pb.StartWaveResponse, error) { 24 23 log.Printf("[BattleService] StartWave: userParty=%d npcParty=%d", len(req.UserPartyInitialInfoList), len(req.NpcPartyInitialInfoList)) 25 - userId := currentUserId(ctx, s.users, s.sessions) 24 + userId := CurrentUserId(ctx, s.users, s.sessions) 26 25 s.users.UpdateUser(userId, func(user *store.UserState) { 27 26 user.Battle.IsActive = true 28 27 user.Battle.StartCount++ ··· 30 29 user.Battle.LastUserPartyCount = int32(len(req.UserPartyInitialInfoList)) 31 30 user.Battle.LastNpcPartyCount = int32(len(req.NpcPartyInitialInfoList)) 32 31 }) 33 - return &pb.StartWaveResponse{ 34 - DiffUserData: userdata.EmptyDiff(), 35 - }, nil 32 + return &pb.StartWaveResponse{}, nil 36 33 } 37 34 38 35 func (s *BattleServiceServer) FinishWave(ctx context.Context, req *pb.FinishWaveRequest) (*pb.FinishWaveResponse, error) { 39 36 log.Printf("[BattleService] FinishWave: battleBinary=%d userParty=%d npcParty=%d elapsedFrames=%d", 40 37 len(req.BattleBinary), len(req.UserPartyResultInfoList), len(req.NpcPartyResultInfoList), req.ElapsedFrameCount) 41 - userId := currentUserId(ctx, s.users, s.sessions) 38 + userId := CurrentUserId(ctx, s.users, s.sessions) 42 39 s.users.UpdateUser(userId, func(user *store.UserState) { 43 40 user.Battle.IsActive = false 44 41 user.Battle.FinishCount++ ··· 48 45 user.Battle.LastBattleBinarySize = int32(len(req.BattleBinary)) 49 46 user.Battle.LastElapsedFrameCount = req.ElapsedFrameCount 50 47 }) 51 - return &pb.FinishWaveResponse{ 52 - DiffUserData: userdata.EmptyDiff(), 53 - }, nil 48 + return &pb.FinishWaveResponse{}, nil 54 49 }
+5 -24
server/internal/service/cageornament.go
··· 9 9 "lunar-tear/server/internal/masterdata" 10 10 "lunar-tear/server/internal/model" 11 11 "lunar-tear/server/internal/store" 12 - "lunar-tear/server/internal/userdata" 13 12 ) 14 13 15 14 type CageOrnamentServiceServer struct { ··· 32 31 log.Fatalf("[CageOrnamentService] ReceiveReward: no reward for cageOrnamentId=%d", req.CageOrnamentId) 33 32 } 34 33 35 - userId := currentUserId(ctx, s.users, s.sessions) 34 + userId := CurrentUserId(ctx, s.users, s.sessions) 36 35 nowMillis := gametime.NowMillis() 37 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 36 + s.users.UpdateUser(userId, func(user *store.UserState) { 38 37 user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{ 39 38 CageOrnamentId: req.CageOrnamentId, 40 39 AcquisitionDatetime: nowMillis, ··· 43 42 s.granter.GrantFull(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis) 44 43 }) 45 44 46 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user, 47 - []string{ 48 - "IUserMaterial", "IUserConsumableItem", "IUserGem", 49 - "IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter", 50 - "IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", 51 - "IUserWeaponNote", 52 - "IUserCageOrnamentReward", 53 - }, 54 - )) 55 - userdata.AddWeaponStoryDiff(diff, user, s.granter.DrainChangedStoryWeaponIds()) 56 - 57 45 return &pb.ReceiveRewardResponse{ 58 46 CageOrnamentReward: []*pb.CageOrnamentReward{ 59 47 { ··· 62 50 Count: reward.Count, 63 51 }, 64 52 }, 65 - DiffUserData: diff, 66 53 }, nil 67 54 } 68 55 69 56 func (s *CageOrnamentServiceServer) RecordAccess(ctx context.Context, req *pb.RecordAccessRequest) (*pb.RecordAccessResponse, error) { 70 57 log.Printf("[CageOrnamentService] RecordAccess: cageOrnamentId=%d", req.CageOrnamentId) 71 58 72 - userId := currentUserId(ctx, s.users, s.sessions) 59 + userId := CurrentUserId(ctx, s.users, s.sessions) 73 60 nowMillis := gametime.NowMillis() 74 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 61 + s.users.UpdateUser(userId, func(user *store.UserState) { 75 62 if _, exists := user.CageOrnamentRewards[req.CageOrnamentId]; !exists { 76 63 user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{ 77 64 CageOrnamentId: req.CageOrnamentId, ··· 81 68 } 82 69 }) 83 70 84 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user, 85 - []string{"IUserCageOrnamentReward"}, 86 - )) 87 - 88 - return &pb.RecordAccessResponse{ 89 - DiffUserData: diff, 90 - }, nil 71 + return &pb.RecordAccessResponse{}, nil 91 72 }
+3 -12
server/internal/service/character.go
··· 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/masterdata" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 12 14 13 type CharacterServiceServer struct { ··· 26 25 func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthRequest) (*pb.RebirthResponse, error) { 27 26 log.Printf("[CharacterService] Rebirth: characterId=%d rebirthCount=%d", req.CharacterId, req.RebirthCount) 28 27 29 - userId := currentUserId(ctx, s.users, s.sessions) 28 + userId := CurrentUserId(ctx, s.users, s.sessions) 30 29 nowMillis := gametime.NowMillis() 31 30 32 31 stepGroupId, ok := s.catalog.StepGroupByCharacterId[req.CharacterId] ··· 35 34 return &pb.RebirthResponse{}, nil 36 35 } 37 36 38 - oldUser, _ := s.users.LoadUser(userId) 39 - tracker := userdata.NewDeleteTracker(). 40 - Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}) 41 - 42 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 37 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 43 38 current := user.CharacterRebirths[req.CharacterId] 44 39 currentCount := current.RebirthCount 45 40 targetCount := currentCount + req.RebirthCount ··· 77 72 return nil, err 78 73 } 79 74 80 - rebirthTables := []string{"IUserCharacterRebirth", "IUserMaterial", "IUserConsumableItem"} 81 - tables := userdata.ProjectTables(snapshot, rebirthTables) 82 - diff := tracker.Apply(snapshot, tables) 83 - 84 - return &pb.RebirthResponse{DiffUserData: diff}, nil 75 + return &pb.RebirthResponse{}, nil 85 76 }
+8 -25
server/internal/service/characterboard.go
··· 8 8 "lunar-tear/server/internal/masterdata" 9 9 "lunar-tear/server/internal/model" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 12 14 13 type CharacterBoardServiceServer struct { ··· 25 24 func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.ReleasePanelRequest) (*pb.ReleasePanelResponse, error) { 26 25 log.Printf("[CharacterBoardService] ReleasePanel: panelIds=%v", req.CharacterBoardPanelId) 27 26 28 - userId := currentUserId(ctx, s.users, s.sessions) 29 - 30 - oldUser, _ := s.users.LoadUser(userId) 31 - tracker := userdata.NewDeleteTracker(). 32 - Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}). 33 - Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"}) 27 + userId := CurrentUserId(ctx, s.users, s.sessions) 34 28 35 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 29 + s.users.UpdateUser(userId, func(user *store.UserState) { 36 30 for _, panelId := range req.CharacterBoardPanelId { 37 31 panel, ok := s.catalog.PanelById[panelId] 38 32 if !ok { ··· 46 40 } 47 41 }) 48 42 49 - boardTables := []string{ 50 - "IUserCharacterBoard", 51 - "IUserCharacterBoardAbility", 52 - "IUserCharacterBoardStatusUp", 53 - "IUserMaterial", 54 - "IUserConsumableItem", 55 - "IUserGem", 56 - } 57 - tables := userdata.ProjectTables(user, boardTables) 58 - diff := tracker.Apply(user, tables) 59 - 60 - return &pb.ReleasePanelResponse{DiffUserData: diff}, nil 43 + return &pb.ReleasePanelResponse{}, nil 61 44 } 62 45 63 - func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.CharacterBoardPanelRow) { 46 + func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) { 64 47 costs := s.catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId] 65 48 for _, cost := range costs { 66 49 store.DeductPossession(user, model.PossessionType(cost.PossessionType), cost.PossessionId, cost.Count) 67 50 } 68 51 } 69 52 70 - func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel masterdata.CharacterBoardPanelRow) { 53 + func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) { 71 54 boardId := panel.CharacterBoardId 72 55 board := user.CharacterBoards[boardId] 73 56 board.CharacterBoardId = boardId ··· 90 73 user.CharacterBoards[boardId] = board 91 74 } 92 75 93 - func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.CharacterBoardPanelRow) { 76 + func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) { 94 77 effects := s.catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId] 95 78 for _, eff := range effects { 96 79 switch model.CharacterBoardEffectType(eff.CharacterBoardEffectType) { ··· 102 85 } 103 86 } 104 87 105 - func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.CharacterBoardReleaseEffectRow) { 88 + func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) { 106 89 ability, ok := s.catalog.AbilityById[eff.CharacterBoardEffectId] 107 90 if !ok { 108 91 log.Printf("[CharacterBoardService] unknown abilityId=%d", eff.CharacterBoardEffectId) ··· 127 110 user.CharacterBoardAbilities[key] = state 128 111 } 129 112 130 - func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.CharacterBoardReleaseEffectRow) { 113 + func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) { 131 114 statusUp, ok := s.catalog.StatusUpById[eff.CharacterBoardEffectId] 132 115 if !ok { 133 116 log.Printf("[CharacterBoardService] unknown statusUpId=%d", eff.CharacterBoardEffectId)
+1 -28
server/internal/service/characterviewer.go
··· 2 2 3 3 import ( 4 4 "context" 5 - "encoding/json" 6 5 "fmt" 7 6 "log" 8 7 9 8 pb "lunar-tear/server/gen/proto" 10 - "lunar-tear/server/internal/gametime" 11 9 "lunar-tear/server/internal/masterdata" 12 10 "lunar-tear/server/internal/store" 13 11 ··· 28 26 func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _ *emptypb.Empty) (*pb.CharacterViewerTopResponse, error) { 29 27 log.Printf("[CharacterViewerService] CharacterViewerTop") 30 28 31 - userId := currentUserId(ctx, s.users, s.sessions) 29 + userId := CurrentUserId(ctx, s.users, s.sessions) 32 30 user, err := s.users.LoadUser(userId) 33 31 if err != nil { 34 32 panic(fmt.Sprintf("CharacterViewerTop: no user for userId=%d: %v", userId, err)) ··· 37 35 released := s.catalog.ReleasedFieldIds(user) 38 36 log.Printf("[CharacterViewerService] released %d fields for user %d", len(released), userId) 39 37 40 - now := gametime.NowMillis() 41 - records := make([]map[string]any, 0, len(released)) 42 - for _, fieldId := range released { 43 - records = append(records, map[string]any{ 44 - "userId": userId, 45 - "characterViewerFieldId": fieldId, 46 - "releaseDatetime": now, 47 - "latestVersion": 0, 48 - }) 49 - } 50 - 51 - payload := "[]" 52 - if len(records) > 0 { 53 - data, _ := json.Marshal(records) 54 - payload = string(data) 55 - } 56 - 57 - diff := map[string]*pb.DiffData{ 58 - "IUserCharacterViewerField": { 59 - UpdateRecordsJson: payload, 60 - DeleteKeysJson: "[]", 61 - }, 62 - } 63 - 64 38 return &pb.CharacterViewerTopResponse{ 65 39 ReleaseCharacterViewerFieldId: released, 66 - DiffUserData: diff, 67 40 }, nil 68 41 }
+3 -14
server/internal/service/companion.go
··· 9 9 "lunar-tear/server/internal/gametime" 10 10 "lunar-tear/server/internal/masterdata" 11 11 "lunar-tear/server/internal/store" 12 - "lunar-tear/server/internal/userdata" 13 12 ) 14 13 15 14 const companionMaxLevel = int32(50) 16 - 17 - var companionDiffTables = []string{ 18 - "IUserCompanion", 19 - "IUserMaterial", 20 - "IUserConsumableItem", 21 - } 22 15 23 16 type CompanionServiceServer struct { 24 17 pb.UnimplementedCompanionServiceServer ··· 35 28 func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionEnhanceRequest) (*pb.CompanionEnhanceResponse, error) { 36 29 log.Printf("[CompanionService] Enhance: uuid=%s addLevel=%d", req.UserCompanionUuid, req.AddLevelCount) 37 30 38 - userId := currentUserId(ctx, s.users, s.sessions) 31 + userId := CurrentUserId(ctx, s.users, s.sessions) 39 32 nowMillis := gametime.NowMillis() 40 33 41 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 34 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 42 35 companion, ok := user.Companions[req.UserCompanionUuid] 43 36 if !ok { 44 37 log.Printf("[CompanionService] Enhance: companion uuid=%s not found", req.UserCompanionUuid) ··· 77 70 return nil, fmt.Errorf("companion enhance: %w", err) 78 71 } 79 72 80 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, companionDiffTables)) 81 - 82 - return &pb.CompanionEnhanceResponse{ 83 - DiffUserData: diff, 84 - }, nil 73 + return &pb.CompanionEnhanceResponse{}, nil 85 74 }
+1 -3
server/internal/service/config.go
··· 5 5 "log" 6 6 7 7 pb "lunar-tear/server/gen/proto" 8 - "lunar-tear/server/internal/userdata" 9 8 10 9 "google.golang.org/protobuf/types/known/emptypb" 11 10 ) ··· 14 13 pb.UnimplementedConfigServiceServer 15 14 GrpcHost string 16 15 GrpcPort int32 17 - OctoURL string // HTTP base URL for Octo (list + assets); client uses this instead of default resources.app.nierreincarnation.com 16 + OctoURL string 18 17 } 19 18 20 19 func NewConfigServiceServer(host string, port int32, octoURL string) *ConfigServiceServer { ··· 42 41 MasterData: &pb.MasterDataConfig{ 43 42 UrlFormat: s.OctoURL + "/master-data/%s", 44 43 }, 45 - DiffUserData: userdata.EmptyDiff(), 46 44 }, nil 47 45 }
+3 -13
server/internal/service/consumableitem.go
··· 8 8 pb "lunar-tear/server/gen/proto" 9 9 "lunar-tear/server/internal/masterdata" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 12 14 13 type ConsumableItemServiceServer struct { ··· 26 25 func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) { 27 26 log.Printf("[ConsumableItemService] Sell: %d item(s)", len(req.ConsumableItemPossession)) 28 27 29 - userId := currentUserId(ctx, s.users, s.sessions) 30 - 31 - oldUser, _ := s.users.LoadUser(userId) 32 - tracker := userdata.NewDeleteTracker(). 33 - Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"}) 28 + userId := CurrentUserId(ctx, s.users, s.sessions) 34 29 35 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 30 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 36 31 totalGold := int32(0) 37 32 for _, item := range req.ConsumableItemPossession { 38 33 row, ok := s.catalog.All[item.ConsumableItemId] ··· 66 61 return nil, fmt.Errorf("consumable item sell: %w", err) 67 62 } 68 63 69 - tables := userdata.ProjectTables(snapshot, []string{"IUserConsumableItem"}) 70 - diff := tracker.Apply(snapshot, tables) 71 - 72 - return &pb.ConsumableItemSellResponse{ 73 - DiffUserData: diff, 74 - }, nil 64 + return &pb.ConsumableItemSellResponse{}, nil 75 65 }
+3 -8
server/internal/service/contentsstory.go
··· 8 8 pb "lunar-tear/server/gen/proto" 9 9 "lunar-tear/server/internal/gametime" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 12 14 13 type ContentsStoryServiceServer struct { ··· 24 23 func (s *ContentsStoryServiceServer) RegisterPlayed(ctx context.Context, req *pb.ContentsStoryRegisterPlayedRequest) (*pb.ContentsStoryRegisterPlayedResponse, error) { 25 24 log.Printf("[ContentsStoryService] RegisterPlayed: contentsStoryId=%d", req.ContentsStoryId) 26 25 27 - userId := currentUserId(ctx, s.users, s.sessions) 26 + userId := CurrentUserId(ctx, s.users, s.sessions) 28 27 nowMillis := gametime.NowMillis() 29 28 30 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 29 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 31 30 user.ContentsStories[req.ContentsStoryId] = nowMillis 32 31 }) 33 32 if err != nil { 34 33 return nil, fmt.Errorf("update user: %w", err) 35 34 } 36 35 37 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserContentsStory"})) 38 - 39 - return &pb.ContentsStoryRegisterPlayedResponse{ 40 - DiffUserData: diff, 41 - }, nil 36 + return &pb.ContentsStoryRegisterPlayedResponse{}, nil 42 37 }
+21 -89
server/internal/service/costume.go
··· 14 14 "lunar-tear/server/internal/masterdata" 15 15 "lunar-tear/server/internal/model" 16 16 "lunar-tear/server/internal/store" 17 - "lunar-tear/server/internal/userdata" 18 17 ) 19 - 20 - var costumeDiffTables = []string{ 21 - "IUserCostume", 22 - "IUserMaterial", 23 - "IUserConsumableItem", 24 - } 25 18 26 19 type CostumeServiceServer struct { 27 20 pb.UnimplementedCostumeServiceServer ··· 38 31 func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceRequest) (*pb.EnhanceResponse, error) { 39 32 log.Printf("[CostumeService] Enhance: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials) 40 33 41 - userId := currentUserId(ctx, s.users, s.sessions) 34 + userId := CurrentUserId(ctx, s.users, s.sessions) 42 35 nowMillis := gametime.NowMillis() 43 36 44 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 37 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 45 38 costume, ok := user.Costumes[req.UserCostumeUuid] 46 39 if !ok { 47 40 log.Printf("[CostumeService] Enhance: costume uuid=%s not found", req.UserCostumeUuid) ··· 98 91 return nil, fmt.Errorf("costume enhance: %w", err) 99 92 } 100 93 101 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables)) 102 - 103 94 return &pb.EnhanceResponse{ 104 95 IsGreatSuccess: false, 105 96 SurplusEnhanceMaterial: map[int32]int32{}, 106 - DiffUserData: diff, 107 97 }, nil 108 98 } 109 99 110 - var awakenDiffTables = []string{ 111 - "IUserCostume", 112 - "IUserMaterial", 113 - "IUserConsumableItem", 114 - "IUserCostumeAwakenStatusUp", 115 - "IUserThought", 116 - } 117 - 118 100 func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest) (*pb.AwakenResponse, error) { 119 101 log.Printf("[CostumeService] Awaken: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials) 120 102 121 - userId := currentUserId(ctx, s.users, s.sessions) 103 + userId := CurrentUserId(ctx, s.users, s.sessions) 122 104 nowMillis := gametime.NowMillis() 123 105 124 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 106 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 125 107 costume, ok := user.Costumes[req.UserCostumeUuid] 126 108 if !ok { 127 109 log.Printf("[CostumeService] Awaken: costume uuid=%s not found", req.UserCostumeUuid) ··· 179 161 return nil, fmt.Errorf("costume awaken: %w", err) 180 162 } 181 163 182 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, awakenDiffTables)) 183 - 184 - return &pb.AwakenResponse{ 185 - DiffUserData: diff, 186 - }, nil 164 + return &pb.AwakenResponse{}, nil 187 165 } 188 166 189 167 func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) { ··· 245 223 log.Printf("[CostumeService] Awaken: granted thought id=%d", acq.PossessionId) 246 224 } 247 225 248 - var activeSkillDiffTables = []string{ 249 - "IUserCostumeActiveSkill", 250 - "IUserMaterial", 251 - "IUserConsumableItem", 252 - } 253 - 254 226 func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.EnhanceActiveSkillRequest) (*pb.EnhanceActiveSkillResponse, error) { 255 227 log.Printf("[CostumeService] EnhanceActiveSkill: uuid=%s addLevel=%d", req.UserCostumeUuid, req.AddLevelCount) 256 228 257 - userId := currentUserId(ctx, s.users, s.sessions) 229 + userId := CurrentUserId(ctx, s.users, s.sessions) 258 230 nowMillis := gametime.NowMillis() 259 231 260 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 232 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 261 233 costume, ok := user.Costumes[req.UserCostumeUuid] 262 234 if !ok { 263 235 log.Printf("[CostumeService] EnhanceActiveSkill: costume uuid=%s not found", req.UserCostumeUuid) ··· 332 304 return nil, fmt.Errorf("costume enhance active skill: %w", err) 333 305 } 334 306 335 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, activeSkillDiffTables)) 336 - 337 - return &pb.EnhanceActiveSkillResponse{ 338 - DiffUserData: diff, 339 - }, nil 307 + return &pb.EnhanceActiveSkillResponse{}, nil 340 308 } 341 309 342 310 func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBreakRequest) (*pb.LimitBreakResponse, error) { 343 311 log.Printf("[CostumeService] LimitBreak: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials) 344 312 345 - userId := currentUserId(ctx, s.users, s.sessions) 313 + userId := CurrentUserId(ctx, s.users, s.sessions) 346 314 nowMillis := gametime.NowMillis() 347 315 348 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 316 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 349 317 costume, ok := user.Costumes[req.UserCostumeUuid] 350 318 if !ok { 351 319 log.Printf("[CostumeService] LimitBreak: costume uuid=%s not found", req.UserCostumeUuid) ··· 389 357 return nil, fmt.Errorf("costume limit break: %w", err) 390 358 } 391 359 392 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables)) 393 - 394 - return &pb.LimitBreakResponse{ 395 - DiffUserData: diff, 396 - }, nil 397 - } 398 - 399 - var lotteryEffectDiffTables = []string{ 400 - "IUserCostume", 401 - "IUserCostumeLotteryEffect", 402 - "IUserCostumeLotteryEffectAbility", 403 - "IUserCostumeLotteryEffectStatusUp", 404 - "IUserCostumeLotteryEffectPending", 405 - "IUserConsumableItem", 406 - "IUserMaterial", 360 + return &pb.LimitBreakResponse{}, nil 407 361 } 408 362 409 363 func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req *pb.UnlockLotteryEffectSlotRequest) (*pb.UnlockLotteryEffectSlotResponse, error) { 410 364 log.Printf("[CostumeService] UnlockLotteryEffectSlot: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber) 411 365 412 - userId := currentUserId(ctx, s.users, s.sessions) 366 + userId := CurrentUserId(ctx, s.users, s.sessions) 413 367 nowMillis := gametime.NowMillis() 414 368 415 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 369 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 416 370 costume, ok := user.Costumes[req.UserCostumeUuid] 417 371 if !ok { 418 372 log.Printf("[CostumeService] UnlockLotteryEffectSlot: costume uuid=%s not found", req.UserCostumeUuid) ··· 458 412 return nil, fmt.Errorf("costume unlock lottery effect slot: %w", err) 459 413 } 460 414 461 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, lotteryEffectDiffTables)) 462 - 463 - return &pb.UnlockLotteryEffectSlotResponse{ 464 - DiffUserData: diff, 465 - }, nil 415 + return &pb.UnlockLotteryEffectSlotResponse{}, nil 466 416 } 467 417 468 418 func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.DrawLotteryEffectRequest) (*pb.DrawLotteryEffectResponse, error) { 469 419 log.Printf("[CostumeService] DrawLotteryEffect: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber) 470 420 471 - userId := currentUserId(ctx, s.users, s.sessions) 421 + userId := CurrentUserId(ctx, s.users, s.sessions) 472 422 nowMillis := gametime.NowMillis() 473 423 474 - oldUser, _ := s.users.LoadUser(userId) 475 - tracker := userdata.NewDeleteTracker(). 476 - Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}). 477 - Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"}). 478 - Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"}) 479 - 480 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 424 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 481 425 costume, ok := user.Costumes[req.UserCostumeUuid] 482 426 if !ok { 483 427 log.Printf("[CostumeService] DrawLotteryEffect: costume uuid=%s not found", req.UserCostumeUuid) ··· 514 458 totalWeight += row.Weight 515 459 } 516 460 roll := rand.Int31n(totalWeight) 517 - var picked masterdata.CostumeLotteryEffectOddsRow 461 + var picked masterdata.EntityMCostumeLotteryEffectOddsGroup 518 462 for _, row := range oddsPool { 519 463 roll -= row.Weight 520 464 if roll < 0 { ··· 550 494 return nil, fmt.Errorf("costume draw lottery effect: %w", err) 551 495 } 552 496 553 - diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables)) 554 - 555 - return &pb.DrawLotteryEffectResponse{ 556 - DiffUserData: diff, 557 - }, nil 497 + return &pb.DrawLotteryEffectResponse{}, nil 558 498 } 559 499 560 500 func (s *CostumeServiceServer) ConfirmLotteryEffect(ctx context.Context, req *pb.ConfirmLotteryEffectRequest) (*pb.ConfirmLotteryEffectResponse, error) { 561 501 log.Printf("[CostumeService] ConfirmLotteryEffect: uuid=%s accept=%v", req.UserCostumeUuid, req.IsAccept) 562 502 563 - userId := currentUserId(ctx, s.users, s.sessions) 503 + userId := CurrentUserId(ctx, s.users, s.sessions) 564 504 nowMillis := gametime.NowMillis() 565 505 566 - oldUser, _ := s.users.LoadUser(userId) 567 - tracker := userdata.NewDeleteTracker(). 568 - Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"}) 569 - 570 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 506 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 571 507 pending, ok := user.CostumeLotteryEffectPending[req.UserCostumeUuid] 572 508 if !ok { 573 509 log.Printf("[CostumeService] ConfirmLotteryEffect: no pending for uuid=%s", req.UserCostumeUuid) ··· 596 532 return nil, fmt.Errorf("costume confirm lottery effect: %w", err) 597 533 } 598 534 599 - diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables)) 600 - 601 - return &pb.ConfirmLotteryEffectResponse{ 602 - DiffUserData: diff, 603 - }, nil 535 + return &pb.ConfirmLotteryEffectResponse{}, nil 604 536 }
+1 -1
server/internal/service/data.go
··· 41 41 func (s *DataServiceServer) GetUserData(ctx context.Context, req *pb.UserDataGetRequest) (*pb.UserDataGetResponse, error) { 42 42 log.Printf("[DataService] GetUserData: tables=%v", req.TableName) 43 43 44 - userId := currentUserId(ctx, s.users, s.sessions) 44 + userId := CurrentUserId(ctx, s.users, s.sessions) 45 45 user, err := s.users.LoadUser(userId) 46 46 if err != nil { 47 47 return nil, fmt.Errorf("snapshot user: %w", err)
+32 -58
server/internal/service/deck.go
··· 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/model" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 12 14 13 type DeckServiceServer struct { ··· 23 22 24 23 func (s *DeckServiceServer) UpdateName(ctx context.Context, req *pb.UpdateNameRequest) (*pb.UpdateNameResponse, error) { 25 24 log.Printf("[DeckService] UpdateName: deckType=%d deckNumber=%d name=%q", req.DeckType, req.UserDeckNumber, req.Name) 26 - userId := currentUserId(ctx, s.users, s.sessions) 25 + userId := CurrentUserId(ctx, s.users, s.sessions) 27 26 28 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 27 + s.users.UpdateUser(userId, func(user *store.UserState) { 29 28 deckKey := store.DeckKey{DeckType: model.DeckType(req.DeckType), UserDeckNumber: req.UserDeckNumber} 30 29 deck := user.Decks[deckKey] 31 30 deck.Name = req.Name 32 31 user.Decks[deckKey] = deck 33 32 }) 34 33 35 - result := userdata.ProjectTables(user, []string{"IUserDeck"}) 36 - return &pb.UpdateNameResponse{ 37 - DiffUserData: userdata.BuildDiffFromTables(result), 38 - }, nil 34 + return &pb.UpdateNameResponse{}, nil 39 35 } 40 36 41 37 func (s *DeckServiceServer) RefreshDeckPower(ctx context.Context, req *pb.RefreshDeckPowerRequest) (*pb.RefreshDeckPowerResponse, error) { 42 38 log.Printf("[DeckService] RefreshDeckPower: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber) 43 - userId := currentUserId(ctx, s.users, s.sessions) 39 + userId := CurrentUserId(ctx, s.users, s.sessions) 44 40 45 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 41 + s.users.UpdateUser(userId, func(user *store.UserState) { 46 42 if req.DeckPower == nil { 47 43 log.Printf("[DeckService] RefreshDeckPower: deckPower is nil") 48 44 return ··· 81 77 } 82 78 }) 83 79 84 - result := userdata.ProjectTables(user, []string{ 85 - "IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote", 86 - }) 87 - return &pb.RefreshDeckPowerResponse{ 88 - DiffUserData: userdata.BuildDiffFromTables(result), 89 - }, nil 80 + return &pb.RefreshDeckPowerResponse{}, nil 90 81 } 91 82 92 83 func (s *DeckServiceServer) RefreshMultiDeckPower(ctx context.Context, req *pb.RefreshMultiDeckPowerRequest) (*pb.RefreshMultiDeckPowerResponse, error) { 93 84 log.Printf("[DeckService] RefreshMultiDeckPower: %d entries", len(req.DeckPowerInfo)) 94 - userId := currentUserId(ctx, s.users, s.sessions) 85 + userId := CurrentUserId(ctx, s.users, s.sessions) 95 86 96 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 87 + s.users.UpdateUser(userId, func(user *store.UserState) { 97 88 for _, info := range req.DeckPowerInfo { 98 89 if info.DeckPower == nil { 99 90 continue ··· 133 124 } 134 125 }) 135 126 136 - result := userdata.ProjectTables(user, []string{ 137 - "IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote", 138 - }) 139 - return &pb.RefreshMultiDeckPowerResponse{ 140 - DiffUserData: userdata.BuildDiffFromTables(result), 141 - }, nil 127 + return &pb.RefreshMultiDeckPowerResponse{}, nil 142 128 } 143 129 144 130 func deckSlotsFromProto(deck *pb.Deck) []store.DeckCharacterInput { ··· 171 157 i+1, ch.UserCostumeUuid, ch.MainUserWeaponUuid, ch.SubUserWeaponUuid, ch.UserCompanionUuid, ch.UserThoughtUuid) 172 158 } 173 159 } 174 - userId := currentUserId(ctx, s.users, s.sessions) 175 - 176 - oldUser, _ := s.users.LoadUser(userId) 177 - tracker := userdata.NewDeleteTracker(). 178 - Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords, 179 - []string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}). 180 - Track("IUserDeckPartsGroup", oldUser, userdata.DeckPartsGroupRecords, 181 - []string{"userId", "userDeckCharacterUuid", "userPartsUuid"}). 182 - Track("IUserDeckCharacterDressupCostume", oldUser, userdata.DeckDressupCostumeRecords, 183 - []string{"userId", "userDeckCharacterUuid"}) 160 + userId := CurrentUserId(ctx, s.users, s.sessions) 184 161 185 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 162 + s.users.UpdateUser(userId, func(user *store.UserState) { 186 163 if req.Deck == nil { 187 164 return 188 165 } 189 166 store.ApplyDeckReplacement(user, model.DeckType(req.DeckType), req.UserDeckNumber, deckSlotsFromProto(req.Deck), gametime.NowMillis()) 190 167 }) 191 168 192 - result := userdata.ProjectTables(user, []string{ 193 - "IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup", 194 - "IUserDeckCharacterDressupCostume", 195 - }) 196 - return &pb.ReplaceDeckResponse{ 197 - DiffUserData: tracker.Apply(user, result), 198 - }, nil 169 + return &pb.ReplaceDeckResponse{}, nil 199 170 } 200 171 201 172 func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.ReplaceTripleDeckRequest) (*pb.ReplaceTripleDeckResponse, error) { 202 173 log.Printf("[DeckService] ReplaceTripleDeck: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber) 203 - userId := currentUserId(ctx, s.users, s.sessions) 174 + userId := CurrentUserId(ctx, s.users, s.sessions) 204 175 205 - oldUser, _ := s.users.LoadUser(userId) 206 - tracker := userdata.NewDeleteTracker(). 207 - Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords, 208 - []string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}). 209 - Track("IUserDeckPartsGroup", oldUser, userdata.DeckPartsGroupRecords, 210 - []string{"userId", "userDeckCharacterUuid", "userPartsUuid"}). 211 - Track("IUserDeckCharacterDressupCostume", oldUser, userdata.DeckDressupCostumeRecords, 212 - []string{"userId", "userDeckCharacterUuid"}) 213 - 214 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 176 + s.users.UpdateUser(userId, func(user *store.UserState) { 215 177 nowMillis := gametime.NowMillis() 216 178 for idx, detail := range []*pb.DeckDetail{req.DeckDetail01, req.DeckDetail02, req.DeckDetail03} { 217 179 if detail == nil || detail.Deck == nil { ··· 231 193 } 232 194 }) 233 195 234 - result := userdata.ProjectTables(user, []string{ 235 - "IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup", 236 - "IUserDeckCharacterDressupCostume", 196 + return &pb.ReplaceTripleDeckResponse{}, nil 197 + } 198 + 199 + func (s *DeckServiceServer) ReplaceMultiDeck(ctx context.Context, req *pb.ReplaceMultiDeckRequest) (*pb.ReplaceMultiDeckResponse, error) { 200 + log.Printf("[DeckService] ReplaceMultiDeck: %d entries", len(req.DeckDetail)) 201 + userId := CurrentUserId(ctx, s.users, s.sessions) 202 + 203 + s.users.UpdateUser(userId, func(user *store.UserState) { 204 + nowMillis := gametime.NowMillis() 205 + for idx, detail := range req.DeckDetail { 206 + if detail == nil || detail.Deck == nil { 207 + continue 208 + } 209 + log.Printf("[DeckService] ReplaceMultiDeck detail %d: deckType=%d deckNumber=%d", idx+1, detail.DeckType, detail.UserDeckNumber) 210 + store.ApplyDeckReplacement(user, model.DeckType(detail.DeckType), detail.UserDeckNumber, deckSlotsFromProto(detail.Deck), nowMillis) 211 + } 237 212 }) 238 - return &pb.ReplaceTripleDeckResponse{ 239 - DiffUserData: tracker.Apply(user, result), 240 - }, nil 213 + 214 + return &pb.ReplaceMultiDeckResponse{}, nil 241 215 }
+3 -8
server/internal/service/dokan.go
··· 7 7 8 8 pb "lunar-tear/server/gen/proto" 9 9 "lunar-tear/server/internal/store" 10 - "lunar-tear/server/internal/userdata" 11 10 ) 12 11 13 12 type DokanServiceServer struct { ··· 23 22 func (s *DokanServiceServer) RegisterDokanConfirmed(ctx context.Context, req *pb.RegisterDokanConfirmedRequest) (*pb.RegisterDokanConfirmedResponse, error) { 24 23 log.Printf("[DokanService] RegisterDokanConfirmed: dokanIds=%v", req.DokanId) 25 24 26 - userId := currentUserId(ctx, s.users, s.sessions) 27 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 25 + userId := CurrentUserId(ctx, s.users, s.sessions) 26 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 28 27 for _, id := range req.DokanId { 29 28 user.DokanConfirmed[id] = true 30 29 } ··· 33 32 return nil, fmt.Errorf("update user: %w", err) 34 33 } 35 34 36 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserDokan"})) 37 - 38 - return &pb.RegisterDokanConfirmedResponse{ 39 - DiffUserData: diff, 40 - }, nil 35 + return &pb.RegisterDokanConfirmedResponse{}, nil 41 36 }
+8 -32
server/internal/service/explore.go
··· 10 10 "lunar-tear/server/internal/masterdata" 11 11 "lunar-tear/server/internal/model" 12 12 "lunar-tear/server/internal/store" 13 - "lunar-tear/server/internal/userdata" 14 13 ) 15 14 16 15 const ( ··· 19 18 exploreRewardBaseCount = 1 20 19 ) 21 20 22 - var exploreDiffTables = []string{ 23 - "IUserExplore", 24 - "IUserExploreScore", 25 - } 26 - 27 - var exploreFinishDiffTables = []string{ 28 - "IUserExplore", 29 - "IUserExploreScore", 30 - "IUserMaterial", 31 - "IUserStatus", 32 - } 33 - 34 21 type ExploreServiceServer struct { 35 22 pb.UnimplementedExploreServiceServer 36 23 users store.UserRepository ··· 49 36 return nil, fmt.Errorf("explore id=%d not found", req.ExploreId) 50 37 } 51 38 52 - userId := currentUserId(ctx, s.users, s.sessions) 39 + userId := CurrentUserId(ctx, s.users, s.sessions) 53 40 nowMillis := gametime.NowMillis() 54 41 55 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 42 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 56 43 explore := s.catalog.Explores[req.ExploreId] 57 44 if req.UseConsumableItemId > 0 && explore.ConsumeItemCount > 0 { 58 45 cur := user.ConsumableItems[req.UseConsumableItemId] ··· 71 58 return nil, fmt.Errorf("start explore: %w", err) 72 59 } 73 60 74 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreDiffTables)) 75 - 76 - return &pb.StartExploreResponse{ 77 - DiffUserData: diff, 78 - }, nil 61 + return &pb.StartExploreResponse{}, nil 79 62 } 80 63 81 64 func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.FinishExploreRequest) (*pb.FinishExploreResponse, error) { ··· 88 71 89 72 assetGradeIconId := s.catalog.GradeForScore(req.ExploreId, req.Score) 90 73 91 - userId := currentUserId(ctx, s.users, s.sessions) 74 + userId := CurrentUserId(ctx, s.users, s.sessions) 92 75 nowMillis := gametime.NowMillis() 93 76 94 77 rewardCount := int32(exploreRewardBaseCount) * explore.RewardLotteryCount 95 78 96 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 79 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 97 80 existing, exists := user.ExploreScores[req.ExploreId] 98 81 if !exists || req.Score > existing.MaxScore { 99 82 user.ExploreScores[req.ExploreId] = store.ExploreScoreState{ ··· 123 106 return nil, fmt.Errorf("finish explore: %w", err) 124 107 } 125 108 126 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreFinishDiffTables)) 127 - 128 109 rewards := []*pb.ExploreReward{ 129 110 { 130 111 PossessionType: int32(model.PossessionTypeMaterial), ··· 137 118 AcquireStaminaCount: exploreStaminaRecovery, 138 119 ExploreReward: rewards, 139 120 AssetGradeIconId: assetGradeIconId, 140 - DiffUserData: diff, 141 121 }, nil 142 122 } 143 123 144 124 func (s *ExploreServiceServer) RetireExplore(ctx context.Context, req *pb.RetireExploreRequest) (*pb.RetireExploreResponse, error) { 145 125 log.Printf("[ExploreService] RetireExplore: exploreId=%d", req.ExploreId) 146 126 147 - userId := currentUserId(ctx, s.users, s.sessions) 127 + userId := CurrentUserId(ctx, s.users, s.sessions) 148 128 nowMillis := gametime.NowMillis() 149 129 150 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 130 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 151 131 user.Explore = store.ExploreState{ 152 132 PlayingExploreId: 0, 153 133 IsUseExploreTicket: false, ··· 159 139 return nil, fmt.Errorf("retire explore: %w", err) 160 140 } 161 141 162 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserExplore"})) 163 - 164 - return &pb.RetireExploreResponse{ 165 - DiffUserData: diff, 166 - }, nil 142 + return &pb.RetireExploreResponse{}, nil 167 143 }
+3 -7
server/internal/service/friend.go
··· 6 6 7 7 pb "lunar-tear/server/gen/proto" 8 8 "lunar-tear/server/internal/store" 9 - "lunar-tear/server/internal/userdata" 10 9 11 10 emptypb "google.golang.org/protobuf/types/known/emptypb" 12 11 ) ··· 23 22 24 23 func (s *FriendServiceServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) { 25 24 log.Printf("[FriendService] GetUser: playerId=%d", req.PlayerId) 26 - return &pb.GetUserResponse{DiffUserData: userdata.EmptyDiff()}, nil 25 + return &pb.GetUserResponse{}, nil 27 26 } 28 27 29 28 func (s *FriendServiceServer) GetFriendList(ctx context.Context, req *pb.GetFriendListRequest) (*pb.GetFriendListResponse, error) { ··· 32 31 FriendUser: []*pb.FriendUser{}, 33 32 SendCheerCount: 0, 34 33 ReceivedCheerCount: 0, 35 - DiffUserData: userdata.EmptyDiff(), 36 34 }, nil 37 35 } 38 36 39 37 func (s *FriendServiceServer) GetFriendRequestList(ctx context.Context, req *emptypb.Empty) (*pb.GetFriendRequestListResponse, error) { 40 38 log.Printf("[FriendService] GetFriendRequestList") 41 39 return &pb.GetFriendRequestListResponse{ 42 - User: []*pb.User{}, 43 - DiffUserData: userdata.EmptyDiff(), 40 + User: []*pb.User{}, 44 41 }, nil 45 42 } 46 43 47 44 func (s *FriendServiceServer) SearchRecommendedUsers(ctx context.Context, req *emptypb.Empty) (*pb.SearchRecommendedUsersResponse, error) { 48 45 log.Printf("[FriendService] SearchRecommendedUsers") 49 46 return &pb.SearchRecommendedUsersResponse{ 50 - Users: []*pb.User{}, 51 - DiffUserData: userdata.EmptyDiff(), 47 + Users: []*pb.User{}, 52 48 }, nil 53 49 }
+8 -36
server/internal/service/gacha.go
··· 11 11 "lunar-tear/server/internal/gametime" 12 12 "lunar-tear/server/internal/model" 13 13 "lunar-tear/server/internal/store" 14 - "lunar-tear/server/internal/userdata" 15 14 16 15 emptypb "google.golang.org/protobuf/types/known/emptypb" 17 16 "google.golang.org/protobuf/types/known/timestamppb" 18 17 ) 19 18 20 - var gachaDiffTables = []string{ 21 - "IUserGem", 22 - "IUserCostume", 23 - "IUserWeapon", 24 - "IUserConsumableItem", 25 - "IUserCostumeActiveSkill", 26 - "IUserWeaponNote", 27 - "IUserWeaponSkill", 28 - "IUserWeaponAbility", 29 - "IUserCharacter", 30 - "IUserMaterial", 31 - } 32 - 33 19 type GachaServiceServer struct { 34 20 pb.UnimplementedGachaServiceServer 35 21 users store.UserRepository ··· 56 42 log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType) 57 43 58 44 catalog := s.catalog 59 - userId := currentUserId(ctx, s.users, s.sessions) 45 + userId := CurrentUserId(ctx, s.users, s.sessions) 60 46 nowMillis := gametime.NowMillis() 61 47 62 48 user, err := s.users.UpdateUser(userId, func(user *store.UserState) { ··· 82 68 return &pb.GetGachaListResponse{ 83 69 Gacha: gachaList, 84 70 ConvertedGachaMedal: toProtoConvertedGachaMedal(user.Gacha.ConvertedGachaMedal), 85 - DiffUserData: userdata.EmptyDiff(), 86 71 }, nil 87 72 } 88 73 ··· 134 119 135 120 catalog := s.catalog 136 121 137 - userId := currentUserId(ctx, s.users, s.sessions) 122 + userId := CurrentUserId(ctx, s.users, s.sessions) 138 123 user, err := s.users.LoadUser(userId) 139 124 if err != nil { 140 125 return nil, fmt.Errorf("snapshot user: %w", err) ··· 152 137 } 153 138 154 139 return &pb.GetGachaResponse{ 155 - Gacha: byId, 156 - DiffUserData: userdata.EmptyDiff(), 140 + Gacha: byId, 157 141 }, nil 158 142 } 159 143 ··· 165 149 return nil, fmt.Errorf("gacha %d not found", req.GachaId) 166 150 } 167 151 168 - userId := currentUserId(ctx, s.users, s.sessions) 152 + userId := CurrentUserId(ctx, s.users, s.sessions) 169 153 execCount := req.ExecCount 170 154 if execCount <= 0 { 171 155 execCount = 1 ··· 290 274 bs := updatedUser.Gacha.BannerStates[entry.GachaId] 291 275 nextGacha := toProtoGacha(*entry, &bs) 292 276 293 - changedStoryIds := s.handler.Granter.DrainChangedStoryWeaponIds() 294 - diffOrder := append(gachaDiffTables, "IUserWeaponStory") 295 - diff := userdata.BuildDiffFromTablesOrdered(userdata.ProjectTables(updatedUser, diffOrder), diffOrder) 296 - userdata.AddWeaponStoryDiff(diff, updatedUser, changedStoryIds) 297 - 298 277 return &pb.DrawResponse{ 299 278 NextGacha: nextGacha, 300 279 GachaResult: gachaResults, 301 280 GachaBonus: bonuses, 302 281 MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{}, 303 - DiffUserData: diff, 304 282 }, nil 305 283 } 306 284 ··· 312 290 return nil, fmt.Errorf("gacha %d not found", req.GachaId) 313 291 } 314 292 315 - userId := currentUserId(ctx, s.users, s.sessions) 293 + userId := CurrentUserId(ctx, s.users, s.sessions) 316 294 updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) { 317 295 if resetErr := s.handler.HandleResetBox(user, *entry); resetErr != nil { 318 296 log.Printf("[GachaService] ResetBoxGacha error: %v", resetErr) ··· 325 303 bs := updatedUser.Gacha.BannerStates[entry.GachaId] 326 304 327 305 return &pb.ResetBoxGachaResponse{ 328 - Gacha: toProtoGacha(*entry, &bs), 329 - DiffUserData: userdata.EmptyDiff(), 306 + Gacha: toProtoGacha(*entry, &bs), 330 307 }, nil 331 308 } 332 309 333 310 func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Empty) (*pb.GetRewardGachaResponse, error) { 334 311 log.Printf("[GachaService] GetRewardGacha") 335 - userId := currentUserId(ctx, s.users, s.sessions) 312 + userId := CurrentUserId(ctx, s.users, s.sessions) 336 313 user, err := s.users.LoadUser(userId) 337 314 if err != nil { 338 315 return nil, fmt.Errorf("snapshot user: %w", err) ··· 353 330 Available: drawCount < maxCount, 354 331 TodaysCurrentDrawCount: drawCount, 355 332 DailyMaxCount: maxCount, 356 - DiffUserData: userdata.EmptyDiff(), 357 333 }, nil 358 334 } 359 335 360 336 func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawRequest) (*pb.RewardDrawResponse, error) { 361 337 log.Printf("[GachaService] RewardDraw: placement=%q reward=%q amount=%q", req.PlacementName, req.RewardName, req.RewardAmount) 362 338 363 - userId := currentUserId(ctx, s.users, s.sessions) 339 + userId := CurrentUserId(ctx, s.users, s.sessions) 364 340 365 341 var items []gacha.DrawnItem 366 342 updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) { ··· 393 369 }) 394 370 } 395 371 396 - tables := userdata.FullClientTableMap(updatedUser) 397 - diff := userdata.BuildDiffFromTables(tables) 398 - 399 372 return &pb.RewardDrawResponse{ 400 373 RewardGachaResult: results, 401 - DiffUserData: diff, 402 374 }, nil 403 375 } 404 376
-2
server/internal/service/gameplay.go
··· 5 5 "log" 6 6 7 7 pb "lunar-tear/server/gen/proto" 8 - "lunar-tear/server/internal/userdata" 9 8 ) 10 9 11 10 type GameplayServiceServer struct { ··· 23 22 return &pb.CheckBeforeGamePlayResponse{ 24 23 IsExistUnreadPop: false, 25 24 MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{}, 26 - DiffUserData: userdata.EmptyDiff(), 27 25 }, nil 28 26 }
+4 -9
server/internal/service/gift.go
··· 11 11 pb "lunar-tear/server/gen/proto" 12 12 "lunar-tear/server/internal/gametime" 13 13 "lunar-tear/server/internal/store" 14 - "lunar-tear/server/internal/userdata" 15 14 16 15 emptypb "google.golang.org/protobuf/types/known/emptypb" 17 16 "google.golang.org/protobuf/types/known/timestamppb" ··· 30 29 func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGiftRequest) (*pb.ReceiveGiftResponse, error) { 31 30 log.Printf("[GiftService] ReceiveGift: giftUuids=%d", len(req.UserGiftUuid)) 32 31 33 - userId := currentUserId(ctx, s.users, s.sessions) 32 + userId := CurrentUserId(ctx, s.users, s.sessions) 34 33 received := make([]string, 0, len(req.UserGiftUuid)) 35 34 _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 36 35 nowMillis := gametime.NowMillis() ··· 54 53 ReceivedGiftUuid: []string{}, 55 54 ExpiredGiftUuid: []string{}, 56 55 OverflowGiftUuid: []string{}, 57 - DiffUserData: userdata.EmptyDiff(), 58 56 }, nil 59 57 } 60 58 ··· 62 60 ReceivedGiftUuid: received, 63 61 ExpiredGiftUuid: []string{}, 64 62 OverflowGiftUuid: []string{}, 65 - DiffUserData: userdata.EmptyDiff(), 66 63 }, nil 67 64 } 68 65 ··· 70 67 log.Printf("[GiftService] GetGiftList: rewardKinds=%v expirationType=%d ascending=%v nextCursor=%d previousCursor=%d getCount=%d", 71 68 req.RewardKindType, req.ExpirationType, req.IsAscendingSort, req.NextCursor, req.PreviousCursor, req.GetCount) 72 69 73 - userId := currentUserId(ctx, s.users, s.sessions) 70 + userId := CurrentUserId(ctx, s.users, s.sessions) 74 71 user, err := s.users.LoadUser(userId) 75 72 if err != nil { 76 73 return nil, fmt.Errorf("snapshot user: %w", err) ··· 101 98 TotalPageCount: pageCount(len(user.Gifts.NotReceived), int(req.GetCount)), 102 99 NextCursor: 0, 103 100 PreviousCursor: 0, 104 - DiffUserData: userdata.EmptyDiff(), 105 101 }, nil 106 102 } 107 103 108 104 func (s *GiftServiceServer) GetGiftReceiveHistoryList(ctx context.Context, req *emptypb.Empty) (*pb.GetGiftReceiveHistoryListResponse, error) { 109 105 log.Printf("[GiftService] GetGiftReceiveHistoryList") 110 - userId := currentUserId(ctx, s.users, s.sessions) 106 + userId := CurrentUserId(ctx, s.users, s.sessions) 111 107 user, err := s.users.LoadUser(userId) 112 108 if err != nil { 113 109 return nil, fmt.Errorf("snapshot user: %w", err) ··· 121 117 }) 122 118 } 123 119 return &pb.GetGiftReceiveHistoryListResponse{ 124 - Gift: items, 125 - DiffUserData: userdata.EmptyDiff(), 120 + Gift: items, 126 121 }, nil 127 122 } 128 123
+11 -22
server/internal/service/gimmick.go
··· 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/masterdata" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 13 12 emptypb "google.golang.org/protobuf/types/known/emptypb" 14 13 ) ··· 27 26 func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.UpdateSequenceRequest) (*pb.UpdateSequenceResponse, error) { 28 27 log.Printf("[GimmickService] UpdateSequence: scheduleId=%d sequenceId=%d", 29 28 req.GimmickSequenceScheduleId, req.GimmickSequenceId) 30 - userId := currentUserId(ctx, s.users, s.sessions) 31 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 29 + userId := CurrentUserId(ctx, s.users, s.sessions) 30 + s.users.UpdateUser(userId, func(user *store.UserState) { 32 31 key := store.GimmickSequenceKey{ 33 32 GimmickSequenceScheduleId: req.GimmickSequenceScheduleId, 34 33 GimmickSequenceId: req.GimmickSequenceId, ··· 37 36 sequence.Key = key 38 37 user.Gimmick.Sequences[key] = sequence 39 38 }) 40 - return &pb.UpdateSequenceResponse{ 41 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickSequence"})), 42 - }, nil 39 + return &pb.UpdateSequenceResponse{}, nil 43 40 } 44 41 45 42 func (s *GimmickServiceServer) UpdateGimmickProgress(ctx context.Context, req *pb.UpdateGimmickProgressRequest) (*pb.UpdateGimmickProgressResponse, error) { 46 43 log.Printf("[GimmickService] UpdateGimmickProgress: scheduleId=%d sequenceId=%d gimmickId=%d ornamentIndex=%d progressValueBit=%d flowType=%d", 47 44 req.GimmickSequenceScheduleId, req.GimmickSequenceId, req.GimmickId, req.GimmickOrnamentIndex, req.ProgressValueBit, req.FlowType) 48 - userId := currentUserId(ctx, s.users, s.sessions) 49 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 45 + userId := CurrentUserId(ctx, s.users, s.sessions) 46 + s.users.UpdateUser(userId, func(user *store.UserState) { 50 47 nowMillis := gametime.NowMillis() 51 48 progressKey := store.GimmickKey{ 52 49 GimmickSequenceScheduleId: req.GimmickSequenceScheduleId, ··· 74 71 GimmickOrnamentReward: []*pb.GimmickReward{}, 75 72 IsSequenceCleared: false, 76 73 GimmickSequenceClearReward: []*pb.GimmickReward{}, 77 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{ 78 - "IUserGimmick", 79 - "IUserGimmickOrnamentProgress", 80 - })), 81 74 }, nil 82 75 } 83 76 84 77 func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *emptypb.Empty) (*pb.InitSequenceScheduleResponse, error) { 85 78 log.Printf("[GimmickService] InitSequenceSchedule") 86 - userId := currentUserId(ctx, s.users, s.sessions) 79 + userId := CurrentUserId(ctx, s.users, s.sessions) 87 80 now := gametime.NowMillis() 88 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 81 + s.users.UpdateUser(userId, func(user *store.UserState) { 89 82 added := 0 90 83 for _, key := range s.gimmickCatalog.ActiveScheduleKeys(*user, now) { 91 84 if _, exists := user.Gimmick.Sequences[key]; !exists { ··· 97 90 log.Printf("[GimmickService] InitSequenceSchedule: added %d sequences (total %d)", added, len(user.Gimmick.Sequences)) 98 91 } 99 92 }) 100 - return &pb.InitSequenceScheduleResponse{ 101 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, gimmickDiffTables)), 102 - }, nil 93 + return &pb.InitSequenceScheduleResponse{}, nil 103 94 } 104 95 105 96 func (s *GimmickServiceServer) Unlock(ctx context.Context, req *pb.UnlockRequest) (*pb.UnlockResponse, error) { 106 97 log.Printf("[GimmickService] Unlock: gimmickKeys=%d", len(req.GimmickKey)) 107 - userId := currentUserId(ctx, s.users, s.sessions) 108 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 98 + userId := CurrentUserId(ctx, s.users, s.sessions) 99 + s.users.UpdateUser(userId, func(user *store.UserState) { 109 100 for _, item := range req.GimmickKey { 110 101 key := store.GimmickKey{ 111 102 GimmickSequenceScheduleId: item.GimmickSequenceScheduleId, ··· 118 109 user.Gimmick.Unlocks[key] = unlock 119 110 } 120 111 }) 121 - return &pb.UnlockResponse{ 122 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickUnlock"})), 123 - }, nil 112 + return &pb.UnlockResponse{}, nil 124 113 }
+14 -15
server/internal/service/listbin.go
··· 208 208 return idx 209 209 } 210 210 211 - func loadListBinIndex(revision string) (listBinIndex, bool) { 211 + func loadListBinIndex(baseDir, revision string) (listBinIndex, bool) { 212 212 listBinCacheMu.RLock() 213 213 idx, ok := listBinCache[revision] 214 214 listBinCacheMu.RUnlock() ··· 230 230 listBinInflight[revision] = load 231 231 listBinCacheMu.Unlock() 232 232 233 - filePath := filepath.Join("assets", "revisions", revision, "list.bin") 233 + filePath := filepath.Join(baseDir, "assets", "revisions", revision, "list.bin") 234 234 data, err := os.ReadFile(filePath) 235 235 if err != nil { 236 236 listBinCacheMu.Lock() ··· 250 250 return idx, true 251 251 } 252 252 253 - func loadInfoIndex(revision string) map[string]infoAlias { 253 + func loadInfoIndex(baseDir, revision string) map[string]infoAlias { 254 254 infoCacheMu.RLock() 255 255 m, ok := infoCache[revision] 256 256 infoCacheMu.RUnlock() ··· 272 272 infoInflight[revision] = load 273 273 infoCacheMu.Unlock() 274 274 275 - filePath := filepath.Join("assets", "revisions", revision, "info.json") 275 + filePath := filepath.Join(baseDir, "assets", "revisions", revision, "info.json") 276 276 data, err := os.ReadFile(filePath) 277 277 if err != nil { 278 278 infoCacheMu.Lock() ··· 378 378 // an en locale fallback is appended (marked IsLocaleFallback so callers can skip MD5 validation). 379 379 // For paths with non-ASCII characters, mojibake (double-encoded) and fullwidth-to-ASCII 380 380 // variants are also tried. 381 - func pathStrToFullPaths(revision, assetType, pathStr string) []pathCandidate { 381 + func pathStrToFullPaths(baseDir, revision, assetType, pathStr string) []pathCandidate { 382 382 fsPath := strings.ReplaceAll(pathStr, ")", "/") 383 383 if strings.Contains(fsPath, "..") || filepath.IsAbs(fsPath) || strings.HasPrefix(fsPath, "/") { 384 384 return nil ··· 402 402 if strings.Contains(pathStr, ")ko)") { 403 403 entries = append(entries, tagged{strings.ReplaceAll(pathStr, ")ko)", ")en)"), true}) 404 404 } 405 - base := filepath.Join("assets", "revisions", revision) 405 + base := filepath.Join(baseDir, "assets", "revisions", revision) 406 406 var out []pathCandidate 407 407 seen := make(map[string]bool) 408 408 for _, e := range entries { ··· 434 434 return append(candidates, candidate) 435 435 } 436 436 437 - func duplicateCandidatePath(candidate assetCandidate, assetType, targetRevision, targetBaseName string) string { 438 - root := filepath.Join("assets", "revisions", candidate.Revision, assetType) 437 + func duplicateCandidatePath(baseDir string, candidate assetCandidate, assetType, targetRevision, targetBaseName string) string { 438 + root := filepath.Join(baseDir, "assets", "revisions", candidate.Revision, assetType) 439 439 rel, err := filepath.Rel(root, candidate.Path) 440 440 if err != nil || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) { 441 441 return "" 442 442 } 443 - return filepath.Join("assets", "revisions", targetRevision, assetType, filepath.Dir(rel), targetBaseName) 443 + return filepath.Join(baseDir, "assets", "revisions", targetRevision, assetType, filepath.Dir(rel), targetBaseName) 444 444 } 445 445 446 446 // objectIdToFilePathCandidates returns candidate file paths for the object: list.bin path, locale fallbacks ··· 448 448 // The original locale path is tried first (with MD5 validation); locale fallbacks are tried after 449 449 // (without MD5 validation, since the hash in list.bin refers to the original locale's content). 450 450 // Callers should try each path until one exists on disk. 451 - func objectIdToFilePathCandidates(revision, assetType, objectId string) (candidates []assetCandidate, size int64, ok bool) { 452 - idx, ok := loadListBinIndex(revision) 451 + func objectIdToFilePathCandidates(baseDir, revision, assetType, objectId string) (candidates []assetCandidate, size int64, ok bool) { 452 + idx, ok := loadListBinIndex(baseDir, revision) 453 453 if !ok || idx == nil { 454 454 return nil, 0, false 455 455 } ··· 457 457 if !ok || entry.Path == "" { 458 458 return nil, 0, false 459 459 } 460 - paths := pathStrToFullPaths(revision, assetType, entry.Path) 460 + paths := pathStrToFullPaths(baseDir, revision, assetType, entry.Path) 461 461 if len(paths) == 0 { 462 462 return nil, 0, false 463 463 } ··· 474 474 ExpectedMD5: md5, 475 475 }) 476 476 } 477 - // Add paths from info.json: when requested file is a "from-name" (duplicate not included), serve "to-name" instead. 478 - infoIndex := loadInfoIndex(revision) 477 + infoIndex := loadInfoIndex(baseDir, revision) 479 478 if len(infoIndex) > 0 { 480 479 for _, c := range candidates { 481 480 alias, ok := infoIndex[filepath.Base(c.Path)] 482 481 if !ok || alias.ToName == "" { 483 482 continue 484 483 } 485 - alt := duplicateCandidatePath(c, assetType, alias.ToRevision, alias.ToName) 484 + alt := duplicateCandidatePath(baseDir, c, assetType, alias.ToRevision, alias.ToName) 486 485 if alt == "" { 487 486 continue 488 487 }
+3 -8
server/internal/service/loginbonus.go
··· 11 11 "lunar-tear/server/internal/gametime" 12 12 "lunar-tear/server/internal/masterdata" 13 13 "lunar-tear/server/internal/store" 14 - "lunar-tear/server/internal/userdata" 15 14 16 15 emptypb "google.golang.org/protobuf/types/known/emptypb" 17 16 ) ··· 29 28 30 29 func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb.Empty) (*pb.ReceiveStampResponse, error) { 31 30 log.Printf("[LoginBonusService] ReceiveStamp") 32 - userId := currentUserId(ctx, s.users, s.sessions) 31 + userId := CurrentUserId(ctx, s.users, s.sessions) 33 32 34 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 33 + s.users.UpdateUser(userId, func(user *store.UserState) { 35 34 now := gametime.NowMillis() 36 35 nextStamp := user.LoginBonus.CurrentStampNumber + 1 37 36 ··· 64 63 user.LoginBonus.LatestVersion = now 65 64 }) 66 65 67 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user, 68 - []string{"IUserLoginBonus"}, 69 - )) 70 - setCommonResponseTrailers(ctx, diff, false) 71 - return &pb.ReceiveStampResponse{DiffUserData: diff}, nil 66 + return &pb.ReceiveStampResponse{}, nil 72 67 }
+3 -18
server/internal/service/material.go
··· 8 8 pb "lunar-tear/server/gen/proto" 9 9 "lunar-tear/server/internal/masterdata" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 - 14 - var materialDiffTables = []string{ 15 - "IUserMaterial", 16 - "IUserConsumableItem", 17 - } 18 12 19 13 type MaterialServiceServer struct { 20 14 pb.UnimplementedMaterialServiceServer ··· 31 25 func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRequest) (*pb.MaterialSellResponse, error) { 32 26 log.Printf("[MaterialService] Sell: %d item(s)", len(req.MaterialPossession)) 33 27 34 - userId := currentUserId(ctx, s.users, s.sessions) 28 + userId := CurrentUserId(ctx, s.users, s.sessions) 35 29 36 - oldUser, _ := s.users.LoadUser(userId) 37 - tracker := userdata.NewDeleteTracker(). 38 - Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}) 39 - 40 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 30 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 41 31 totalGold := int32(0) 42 32 for _, item := range req.MaterialPossession { 43 33 mat, ok := s.catalog.All[item.MaterialId] ··· 71 61 return nil, fmt.Errorf("material sell: %w", err) 72 62 } 73 63 74 - tables := userdata.ProjectTables(snapshot, materialDiffTables) 75 - diff := tracker.Apply(snapshot, tables) 76 - 77 - return &pb.MaterialSellResponse{ 78 - DiffUserData: diff, 79 - }, nil 64 + return &pb.MaterialSellResponse{}, nil 80 65 }
+3 -8
server/internal/service/mission.go
··· 7 7 8 8 pb "lunar-tear/server/gen/proto" 9 9 "lunar-tear/server/internal/store" 10 - "lunar-tear/server/internal/userdata" 11 10 ) 12 11 13 12 type MissionServiceServer struct { ··· 23 22 func (s *MissionServiceServer) UpdateMissionProgress(ctx context.Context, req *pb.UpdateMissionProgressRequest) (*pb.UpdateMissionProgressResponse, error) { 24 23 log.Printf("[MissionService] UpdateMissionProgress: cage=%v pictureBook=%v", req.CageMeasurableValues, req.PictureBookMeasurableValues) 25 24 26 - userId := currentUserId(ctx, s.users, s.sessions) 27 - snapshot, err := s.users.LoadUser(userId) 25 + userId := CurrentUserId(ctx, s.users, s.sessions) 26 + _, err := s.users.LoadUser(userId) 28 27 if err != nil { 29 28 return nil, fmt.Errorf("snapshot user: %w", err) 30 29 } 31 30 32 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMission"})) 33 - 34 - return &pb.UpdateMissionProgressResponse{ 35 - DiffUserData: diff, 36 - }, nil 31 + return &pb.UpdateMissionProgressResponse{}, nil 37 32 }
+3 -8
server/internal/service/movie.go
··· 8 8 pb "lunar-tear/server/gen/proto" 9 9 "lunar-tear/server/internal/gametime" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 12 14 13 type MovieServiceServer struct { ··· 24 23 func (s *MovieServiceServer) SaveViewedMovie(ctx context.Context, req *pb.SaveViewedMovieRequest) (*pb.SaveViewedMovieResponse, error) { 25 24 log.Printf("[MovieService] SaveViewedMovie: movieIds=%v", req.MovieId) 26 25 27 - userId := currentUserId(ctx, s.users, s.sessions) 26 + userId := CurrentUserId(ctx, s.users, s.sessions) 28 27 now := gametime.NowMillis() 29 28 30 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 29 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 31 30 for _, mid := range req.MovieId { 32 31 user.ViewedMovies[mid] = now 33 32 } ··· 36 35 return nil, fmt.Errorf("update user: %w", err) 37 36 } 38 37 39 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMovie"})) 40 - 41 - return &pb.SaveViewedMovieResponse{ 42 - DiffUserData: diff, 43 - }, nil 38 + return &pb.SaveViewedMovieResponse{}, nil 44 39 }
+3 -8
server/internal/service/navicutin.go
··· 7 7 8 8 pb "lunar-tear/server/gen/proto" 9 9 "lunar-tear/server/internal/store" 10 - "lunar-tear/server/internal/userdata" 11 10 ) 12 11 13 12 type NaviCutInServiceServer struct { ··· 23 22 func (s *NaviCutInServiceServer) RegisterPlayed(ctx context.Context, req *pb.RegisterPlayedRequest) (*pb.RegisterPlayedResponse, error) { 24 23 log.Printf("[NaviCutInService] RegisterPlayed: naviCutId=%d", req.NaviCutId) 25 24 26 - userId := currentUserId(ctx, s.users, s.sessions) 27 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 25 + userId := CurrentUserId(ctx, s.users, s.sessions) 26 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 28 27 user.NaviCutInPlayed[req.NaviCutId] = true 29 28 }) 30 29 if err != nil { 31 30 return nil, fmt.Errorf("update user: %w", err) 32 31 } 33 32 34 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserNaviCutIn"})) 35 - 36 - return &pb.RegisterPlayedResponse{ 37 - DiffUserData: diff, 38 - }, nil 33 + return &pb.RegisterPlayedResponse{}, nil 39 34 }
+1 -4
server/internal/service/notification.go
··· 6 6 7 7 pb "lunar-tear/server/gen/proto" 8 8 "lunar-tear/server/internal/store" 9 - "lunar-tear/server/internal/userdata" 10 9 11 10 emptypb "google.golang.org/protobuf/types/known/emptypb" 12 11 ) ··· 23 22 24 23 func (s *NotificationServiceServer) GetHeaderNotification(ctx context.Context, req *emptypb.Empty) (*pb.GetHeaderNotificationResponse, error) { 25 24 log.Printf("[NotificationService] GetHeaderNotification") 26 - userId := currentUserId(ctx, s.users, s.sessions) 25 + userId := CurrentUserId(ctx, s.users, s.sessions) 27 26 user, err := s.users.LoadUser(userId) 28 27 if err != nil { 29 28 return &pb.GetHeaderNotificationResponse{ 30 29 GiftNotReceiveCount: 0, 31 30 FriendRequestReceiveCount: 0, 32 31 IsExistUnreadInformation: false, 33 - DiffUserData: userdata.EmptyDiff(), 34 32 }, nil 35 33 } 36 34 return &pb.GetHeaderNotificationResponse{ 37 35 GiftNotReceiveCount: int32(len(user.Gifts.NotReceived)), 38 36 FriendRequestReceiveCount: user.Notifications.FriendRequestReceiveCount, 39 37 IsExistUnreadInformation: user.Notifications.IsExistUnreadInformation, 40 - DiffUserData: userdata.EmptyDiff(), 41 38 }, nil 42 39 }
+10 -8
server/internal/service/octo.go
··· 50 50 type OctoHTTPServer struct { 51 51 mux *http.ServeMux 52 52 ResourcesBaseURL string // if non-empty and exactly 43 chars, list.bin is rewritten to use this base for asset URLs 53 + BaseDir string // root directory containing the assets/ tree; empty means current directory 53 54 revisions *revisionTracker 54 55 resolver *assetResolver 55 56 } ··· 124 125 return sum, nil 125 126 } 126 127 127 - func NewOctoHTTPServer(resourcesBaseURL string) *OctoHTTPServer { 128 + func NewOctoHTTPServer(resourcesBaseURL, baseDir string) *OctoHTTPServer { 128 129 s := &OctoHTTPServer{ 129 130 mux: http.NewServeMux(), 130 131 ResourcesBaseURL: resourcesBaseURL, 132 + BaseDir: baseDir, 131 133 revisions: newRevisionTracker(), 132 - resolver: newAssetResolver(), 134 + resolver: newAssetResolver(baseDir), 133 135 } 134 136 s.resolver.Prewarm("0") 135 137 s.mux.HandleFunc("/", s.handleAll) ··· 226 228 requestedRevision := parts[len(parts)-1] 227 229 if requestedRevision != "" { 228 230 revision := "0" 229 - filePath := "assets/revisions/0/list.bin" 231 + filePath := filepath.Join(s.BaseDir, "assets", "revisions", "0", "list.bin") 230 232 if requestedRevision != revision { 231 233 log.Printf("[OctoV2] Resource list request revision=%s canonicalized to revision=%s", requestedRevision, revision) 232 234 } ··· 266 268 requestedRevision = parts[len(parts)-1] 267 269 } 268 270 revision := "0" 269 - filePath := "assets/revisions/0/list.bin" 271 + filePath := filepath.Join(s.BaseDir, "assets", "revisions", "0", "list.bin") 270 272 if requestedRevision != revision { 271 273 log.Printf("[OctoV1] list request revision=%s canonicalized to revision=%s", requestedRevision, revision) 272 274 } ··· 316 318 w.WriteHeader(http.StatusNotFound) 317 319 return 318 320 } 319 - baseDir := filepath.Join("assets", "revisions") 321 + revDir := filepath.Join(s.BaseDir, "assets", "revisions") 320 322 var triedPaths []string 321 323 var md5Mismatches []string 322 324 for _, candidate := range resolution.Candidates { 323 - rel, err := filepath.Rel(baseDir, candidate.Path) 325 + rel, err := filepath.Rel(revDir, candidate.Path) 324 326 if err != nil || strings.Contains(rel, "..") || filepath.IsAbs(rel) { 325 327 continue 326 328 } ··· 409 411 break 410 412 } 411 413 } 412 - filePath := "assets/release/database.bin.e" 414 + filePath := filepath.Join(s.BaseDir, "assets", "release", "database.bin.e") 413 415 if version != "" { 414 - vPath := "assets/release/" + version + ".bin.e" 416 + vPath := filepath.Join(s.BaseDir, "assets", "release", version+".bin.e") 415 417 if _, err := os.Stat(vPath); err == nil { 416 418 filePath = vPath 417 419 }
+2 -6
server/internal/service/omikuji.go
··· 9 9 "lunar-tear/server/internal/gametime" 10 10 "lunar-tear/server/internal/masterdata" 11 11 "lunar-tear/server/internal/store" 12 - "lunar-tear/server/internal/userdata" 13 12 ) 14 13 15 14 type OmikujiServiceServer struct { ··· 26 25 func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiDrawRequest) (*pb.OmikujiDrawResponse, error) { 27 26 log.Printf("[OmikujiService] OmikujiDraw: omikujiId=%d", req.OmikujiId) 28 27 29 - userId := currentUserId(ctx, s.users, s.sessions) 28 + userId := CurrentUserId(ctx, s.users, s.sessions) 30 29 now := gametime.NowMillis() 31 30 32 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 31 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 33 32 user.DrawnOmikuji[req.OmikujiId] = now 34 33 }) 35 34 if err != nil { 36 35 return nil, fmt.Errorf("update user: %w", err) 37 36 } 38 37 39 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserOmikuji"})) 40 - 41 38 return &pb.OmikujiDrawResponse{ 42 39 OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId), 43 40 OmikujiItem: []*pb.OmikujiItem{}, 44 - DiffUserData: diff, 45 41 }, nil 46 42 }
+9 -31
server/internal/service/parts.go
··· 10 10 "lunar-tear/server/internal/gametime" 11 11 "lunar-tear/server/internal/masterdata" 12 12 "lunar-tear/server/internal/store" 13 - "lunar-tear/server/internal/userdata" 14 13 ) 15 14 16 15 const partsMaxLevel = int32(15) 17 - 18 - var partsDiffTables = []string{ 19 - "IUserParts", 20 - "IUserConsumableItem", 21 - } 22 16 23 17 type PartsServiceServer struct { 24 18 pb.UnimplementedPartsServiceServer ··· 35 29 func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) { 36 30 log.Printf("[PartsService] Sell: %d part(s)", len(req.UserPartsUuid)) 37 31 38 - userId := currentUserId(ctx, s.users, s.sessions) 39 - 40 - oldUser, _ := s.users.LoadUser(userId) 41 - tracker := userdata.NewDeleteTracker(). 42 - Track("IUserParts", oldUser, userdata.SortedPartsRecords, []string{"userId", "userPartsUuid"}) 32 + userId := CurrentUserId(ctx, s.users, s.sessions) 43 33 44 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 34 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 45 35 totalGold := int32(0) 46 36 for _, uuid := range req.UserPartsUuid { 47 37 part, ok := user.Parts[uuid] ··· 81 71 return nil, fmt.Errorf("parts sell: %w", err) 82 72 } 83 73 84 - tables := userdata.ProjectTables(snapshot, partsDiffTables) 85 - diff := tracker.Apply(snapshot, tables) 86 - 87 - return &pb.PartsSellResponse{ 88 - DiffUserData: diff, 89 - }, nil 74 + return &pb.PartsSellResponse{}, nil 90 75 } 91 76 92 77 func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRequest) (*pb.PartsEnhanceResponse, error) { 93 78 log.Printf("[PartsService] Enhance: uuid=%s", req.UserPartsUuid) 94 79 95 - userId := currentUserId(ctx, s.users, s.sessions) 80 + userId := CurrentUserId(ctx, s.users, s.sessions) 96 81 nowMillis := gametime.NowMillis() 97 82 98 83 isSuccess := false 99 84 100 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 85 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 101 86 part, ok := user.Parts[req.UserPartsUuid] 102 87 if !ok { 103 88 log.Printf("[PartsService] Enhance: part uuid=%s not found", req.UserPartsUuid) ··· 158 143 return nil, fmt.Errorf("parts enhance: %w", err) 159 144 } 160 145 161 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, partsDiffTables)) 162 - 163 146 return &pb.PartsEnhanceResponse{ 164 - IsSuccess: isSuccess, 165 - DiffUserData: diff, 147 + IsSuccess: isSuccess, 166 148 }, nil 167 149 } 168 150 ··· 170 152 log.Printf("[PartsService] ReplacePreset: preset=%d uuids=[%s, %s, %s]", 171 153 req.UserPartsPresetNumber, req.UserPartsUuid01, req.UserPartsUuid02, req.UserPartsUuid03) 172 154 173 - userId := currentUserId(ctx, s.users, s.sessions) 155 + userId := CurrentUserId(ctx, s.users, s.sessions) 174 156 nowMillis := gametime.NowMillis() 175 157 176 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 158 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 177 159 preset := user.PartsPresets[req.UserPartsPresetNumber] 178 160 preset.UserPartsPresetNumber = req.UserPartsPresetNumber 179 161 preset.UserPartsUuid01 = req.UserPartsUuid01 ··· 186 168 return nil, fmt.Errorf("parts replace preset: %w", err) 187 169 } 188 170 189 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserPartsPreset"})) 190 - 191 - return &pb.PartsReplacePresetResponse{ 192 - DiffUserData: diff, 193 - }, nil 171 + return &pb.PartsReplacePresetResponse{}, nil 194 172 }
+3 -10
server/internal/service/portalcage.go
··· 7 7 pb "lunar-tear/server/gen/proto" 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/store" 10 - "lunar-tear/server/internal/userdata" 11 10 ) 12 11 13 12 type PortalCageServiceServer struct { ··· 23 22 func (s *PortalCageServiceServer) UpdatePortalCageSceneProgress(ctx context.Context, req *pb.UpdatePortalCageSceneProgressRequest) (*pb.UpdatePortalCageSceneProgressResponse, error) { 24 23 log.Printf("[PortalCageService] UpdatePortalCageSceneProgress: portalCageSceneId=%d", req.PortalCageSceneId) 25 24 26 - userId := currentUserId(ctx, s.users, s.sessions) 27 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 25 + userId := CurrentUserId(ctx, s.users, s.sessions) 26 + s.users.UpdateUser(userId, func(user *store.UserState) { 28 27 now := gametime.NowMillis() 29 28 user.PortalCageStatus.IsCurrentProgress = true 30 29 user.PortalCageStatus.LatestVersion = now 31 30 }) 32 - 33 - tables := userdata.ProjectTables(user, 34 - []string{"IUserPortalCageStatus"}, 35 - ) 36 - return &pb.UpdatePortalCageSceneProgressResponse{ 37 - DiffUserData: userdata.BuildDiffFromTablesOrdered(tables, []string{"IUserPortalCageStatus"}), 38 - }, nil 31 + return &pb.UpdatePortalCageSceneProgressResponse{}, nil 39 32 }
+17 -46
server/internal/service/quest_bighunt.go
··· 10 10 "lunar-tear/server/internal/model" 11 11 "lunar-tear/server/internal/questflow" 12 12 "lunar-tear/server/internal/store" 13 - "lunar-tear/server/internal/userdata" 14 13 15 14 emptypb "google.golang.org/protobuf/types/known/emptypb" 16 15 ) ··· 32 31 return &BigHuntServiceServer{users: users, sessions: sessions, catalog: catalog, engine: engine} 33 32 } 34 33 35 - var bigHuntDiffTables = []string{ 36 - "IUserBigHuntProgressStatus", 37 - "IUserBigHuntMaxScore", 38 - "IUserBigHuntStatus", 39 - "IUserBigHuntScheduleMaxScore", 40 - "IUserBigHuntWeeklyMaxScore", 41 - "IUserBigHuntWeeklyStatus", 42 - } 43 - 44 - func buildBigHuntDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData { 45 - tables := userdata.ProjectTables(user, tableNames) 46 - return userdata.BuildDiffFromTablesOrdered(tables, tableNames) 47 - } 48 - 49 34 func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.StartBigHuntQuestRequest) (*pb.StartBigHuntQuestResponse, error) { 50 35 log.Printf("[BigHuntService] StartBigHuntQuest: bossQuestId=%d questId=%d deckNumber=%d isDryRun=%v", 51 36 req.BigHuntBossQuestId, req.BigHuntQuestId, req.UserDeckNumber, req.IsDryRun) 52 37 53 - userId := currentUserId(ctx, s.users, s.sessions) 38 + userId := CurrentUserId(ctx, s.users, s.sessions) 54 39 nowMillis := gametime.NowMillis() 55 40 56 41 bhQuest, ok := s.catalog.QuestById[req.BigHuntQuestId] ··· 58 43 log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId) 59 44 } 60 45 61 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 46 + s.users.UpdateUser(userId, func(user *store.UserState) { 62 47 if ok { 63 48 s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis) 64 49 } ··· 80 65 user.BigHuntStatuses[req.BigHuntBossQuestId] = st 81 66 }) 82 67 83 - return &pb.StartBigHuntQuestResponse{ 84 - DiffUserData: buildBigHuntDiff(user, append([]string{"IUserQuest"}, bigHuntDiffTables...)), 85 - }, nil 68 + return &pb.StartBigHuntQuestResponse{}, nil 86 69 } 87 70 88 71 func (s *BigHuntServiceServer) UpdateBigHuntQuestSceneProgress(ctx context.Context, req *pb.UpdateBigHuntQuestSceneProgressRequest) (*pb.UpdateBigHuntQuestSceneProgressResponse, error) { 89 72 log.Printf("[BigHuntService] UpdateBigHuntQuestSceneProgress: questSceneId=%d", req.QuestSceneId) 90 73 91 - userId := currentUserId(ctx, s.users, s.sessions) 74 + userId := CurrentUserId(ctx, s.users, s.sessions) 92 75 nowMillis := gametime.NowMillis() 93 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 76 + s.users.UpdateUser(userId, func(user *store.UserState) { 94 77 user.BigHuntProgress.CurrentQuestSceneId = req.QuestSceneId 95 78 user.BigHuntProgress.LatestVersion = nowMillis 96 79 }) 97 80 98 - return &pb.UpdateBigHuntQuestSceneProgressResponse{ 99 - DiffUserData: buildBigHuntDiff(user, []string{"IUserBigHuntProgressStatus"}), 100 - }, nil 81 + return &pb.UpdateBigHuntQuestSceneProgressResponse{}, nil 101 82 } 102 83 103 84 func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.FinishBigHuntQuestRequest) (*pb.FinishBigHuntQuestResponse, error) { 104 85 log.Printf("[BigHuntService] FinishBigHuntQuest: bossQuestId=%d questId=%d isRetired=%v", 105 86 req.BigHuntBossQuestId, req.BigHuntQuestId, req.IsRetired) 106 87 107 - userId := currentUserId(ctx, s.users, s.sessions) 88 + userId := CurrentUserId(ctx, s.users, s.sessions) 108 89 nowMillis := gametime.NowMillis() 109 90 110 91 bhQuest := s.catalog.QuestById[req.BigHuntQuestId] ··· 114 95 var scoreInfo *pb.BigHuntScoreInfo 115 96 var scoreRewards []*pb.BigHuntReward 116 97 117 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 98 + s.users.UpdateUser(userId, func(user *store.UserState) { 118 99 s.engine.HandleBigHuntQuestFinish(user, bhQuest.QuestId, req.IsRetired, false, nowMillis) 119 100 120 101 if req.IsRetired || user.BigHuntProgress.IsDryRun { ··· 229 210 BattleReport: &pb.BigHuntBattleReport{ 230 211 BattleReportWave: []*pb.BigHuntBattleReportWave{}, 231 212 }, 232 - DiffUserData: buildBigHuntDiff(user, append([]string{ 233 - "IUserQuest", 234 - "IUserConsumableItem", 235 - "IUserMaterial", 236 - }, bigHuntDiffTables...)), 237 213 }, nil 238 214 } 239 215 240 216 func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.RestartBigHuntQuestRequest) (*pb.RestartBigHuntQuestResponse, error) { 241 217 log.Printf("[BigHuntService] RestartBigHuntQuest: bossQuestId=%d questId=%d", req.BigHuntBossQuestId, req.BigHuntQuestId) 242 218 243 - userId := currentUserId(ctx, s.users, s.sessions) 219 + userId := CurrentUserId(ctx, s.users, s.sessions) 244 220 nowMillis := gametime.NowMillis() 245 221 246 222 bhQuest := s.catalog.QuestById[req.BigHuntQuestId] ··· 248 224 var battleBinary []byte 249 225 var deckNumber int32 250 226 251 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 227 + s.users.UpdateUser(userId, func(user *store.UserState) { 252 228 s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, user.BigHuntDeckNumber, nowMillis) 253 229 254 230 user.BigHuntProgress.CurrentQuestSceneId = 0 ··· 267 243 return &pb.RestartBigHuntQuestResponse{ 268 244 BattleBinary: battleBinary, 269 245 DeckNumber: deckNumber, 270 - DiffUserData: buildBigHuntDiff(user, append([]string{"IUserQuest"}, bigHuntDiffTables...)), 271 246 }, nil 272 247 } 273 248 274 249 func (s *BigHuntServiceServer) SkipBigHuntQuest(ctx context.Context, req *pb.SkipBigHuntQuestRequest) (*pb.SkipBigHuntQuestResponse, error) { 275 250 log.Printf("[BigHuntService] SkipBigHuntQuest: bossQuestId=%d skipCount=%d", req.BigHuntBossQuestId, req.SkipCount) 276 251 277 - userId := currentUserId(ctx, s.users, s.sessions) 252 + userId := CurrentUserId(ctx, s.users, s.sessions) 278 253 nowMillis := gametime.NowMillis() 279 254 280 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 255 + s.users.UpdateUser(userId, func(user *store.UserState) { 281 256 st := user.BigHuntStatuses[req.BigHuntBossQuestId] 282 257 st.DailyChallengeCount += req.SkipCount 283 258 st.LatestChallengeDatetime = nowMillis ··· 286 261 }) 287 262 288 263 return &pb.SkipBigHuntQuestResponse{ 289 - ScoreReward: []*pb.BigHuntReward{}, 290 - DiffUserData: buildBigHuntDiff(user, bigHuntDiffTables), 264 + ScoreReward: []*pb.BigHuntReward{}, 291 265 }, nil 292 266 } 293 267 294 268 func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *pb.SaveBigHuntBattleInfoRequest) (*pb.SaveBigHuntBattleInfoResponse, error) { 295 269 log.Printf("[BigHuntService] SaveBigHuntBattleInfo: elapsedFrames=%d", req.ElapsedFrameCount) 296 270 297 - userId := currentUserId(ctx, s.users, s.sessions) 271 + userId := CurrentUserId(ctx, s.users, s.sessions) 298 272 nowMillis := gametime.NowMillis() 299 273 300 274 var totalDamage int64 ··· 306 280 } 307 281 } 308 282 309 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 283 + s.users.UpdateUser(userId, func(user *store.UserState) { 310 284 user.BigHuntBattleBinary = req.BattleBinary 311 285 312 286 if req.BigHuntBattleDetail != nil { ··· 322 296 user.BigHuntProgress.LatestVersion = nowMillis 323 297 }) 324 298 325 - return &pb.SaveBigHuntBattleInfoResponse{ 326 - DiffUserData: buildBigHuntDiff(user, []string{"IUserBigHuntProgressStatus"}), 327 - }, nil 299 + return &pb.SaveBigHuntBattleInfoResponse{}, nil 328 300 } 329 301 330 302 func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb.Empty) (*pb.GetBigHuntTopDataResponse, error) { 331 303 log.Printf("[BigHuntService] GetBigHuntTopData") 332 304 333 - userId := currentUserId(ctx, s.users, s.sessions) 305 + userId := CurrentUserId(ctx, s.users, s.sessions) 334 306 user, _ := s.users.LoadUser(userId) 335 307 336 308 nowMillis := gametime.NowMillis() ··· 368 340 WeeklyScoreReward: weeklyRewards, 369 341 IsReceivedWeeklyScoreReward: ws.IsReceivedWeeklyReward, 370 342 LastWeekWeeklyScoreReward: lastWeekRewards, 371 - DiffUserData: buildBigHuntDiff(user, bigHuntDiffTables), 372 343 }, nil 373 344 } 374 345
+12 -67
server/internal/service/quest_event.go
··· 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/questflow" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 13 12 emptypb "google.golang.org/protobuf/types/known/emptypb" 14 13 ) ··· 16 15 func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) { 17 16 log.Printf("[QuestService] StartEventQuest: chapterId=%d questId=%d isBattleOnly=%v", req.EventQuestChapterId, req.QuestId, req.IsBattleOnly) 18 17 19 - userId := currentUserId(ctx, s.users, s.sessions) 18 + userId := CurrentUserId(ctx, s.users, s.sessions) 20 19 nowMillis := gametime.NowMillis() 21 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 20 + s.users.UpdateUser(userId, func(user *store.UserState) { 22 21 s.engine.HandleEventQuestStart(user, req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis) 23 22 }) 24 23 ··· 34 33 35 34 return &pb.StartEventQuestResponse{ 36 35 BattleDropReward: pbDrops, 37 - DiffUserData: buildSelectedQuestDiff(user, []string{ 38 - "IUserStatus", 39 - "IUserQuest", 40 - "IUserQuestMission", 41 - "IUserEventQuestProgressStatus", 42 - }), 43 36 }, nil 44 37 } 45 38 ··· 47 40 log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v", req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated) 48 41 49 42 nowMillis := gametime.NowMillis() 50 - userId := currentUserId(ctx, s.users, s.sessions) 43 + userId := CurrentUserId(ctx, s.users, s.sessions) 51 44 var outcome questflow.FinishOutcome 52 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 45 + s.users.UpdateUser(userId, func(user *store.UserState) { 53 46 outcome = s.engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) 54 47 }) 55 48 56 - diff := buildSelectedQuestDiff(user, []string{ 57 - "IUserQuest", 58 - "IUserQuestMission", 59 - "IUserEventQuestProgressStatus", 60 - "IUserStatus", 61 - "IUserGem", 62 - "IUserCharacter", 63 - "IUserCostume", 64 - "IUserCostumeActiveSkill", 65 - "IUserWeapon", 66 - "IUserWeaponSkill", 67 - "IUserWeaponAbility", 68 - "IUserWeaponNote", 69 - "IUserCompanion", 70 - "IUserConsumableItem", 71 - "IUserMaterial", 72 - "IUserImportantItem", 73 - "IUserParts", 74 - "IUserPartsGroupNote", 75 - }) 76 - userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds) 77 - 78 49 return &pb.FinishEventQuestResponse{ 79 50 DropReward: toProtoRewards(outcome.DropRewards), 80 51 FirstClearReward: toProtoRewards(outcome.FirstClearRewards), ··· 84 55 IsBigWin: outcome.IsBigWin, 85 56 BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, 86 57 UserStatusCampaignReward: []*pb.QuestReward{}, 87 - DiffUserData: diff, 88 58 }, nil 89 59 } 90 60 91 61 func (s *QuestServiceServer) RestartEventQuest(ctx context.Context, req *pb.RestartEventQuestRequest) (*pb.RestartEventQuestResponse, error) { 92 62 log.Printf("[QuestService] RestartEventQuest: chapterId=%d questId=%d", req.EventQuestChapterId, req.QuestId) 93 63 94 - userId := currentUserId(ctx, s.users, s.sessions) 95 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 64 + userId := CurrentUserId(ctx, s.users, s.sessions) 65 + s.users.UpdateUser(userId, func(user *store.UserState) { 96 66 s.engine.HandleEventQuestRestart(user, req.EventQuestChapterId, req.QuestId, gametime.NowMillis()) 97 67 }) 98 68 99 69 return &pb.RestartEventQuestResponse{ 100 70 BattleDropReward: []*pb.BattleDropReward{}, 101 - DiffUserData: buildSelectedQuestDiff(user, []string{ 102 - "IUserQuest", 103 - "IUserQuestMission", 104 - "IUserEventQuestProgressStatus", 105 - }), 106 71 }, nil 107 72 } 108 73 109 74 func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context, req *pb.UpdateEventQuestSceneProgressRequest) (*pb.UpdateEventQuestSceneProgressResponse, error) { 110 75 log.Printf("[QuestService] UpdateEventQuestSceneProgress: questSceneId=%d", req.QuestSceneId) 111 76 112 - userId := currentUserId(ctx, s.users, s.sessions) 113 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 77 + userId := CurrentUserId(ctx, s.users, s.sessions) 78 + s.users.UpdateUser(userId, func(user *store.UserState) { 114 79 s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) 115 80 }) 116 81 117 - diff := buildSelectedQuestDiff(user, []string{ 118 - "IUserEventQuestProgressStatus", 119 - "IUserCharacter", 120 - "IUserCostume", 121 - "IUserWeapon", 122 - "IUserWeaponSkill", 123 - "IUserWeaponAbility", 124 - "IUserCompanion", 125 - "IUserConsumableItem", 126 - "IUserMaterial", 127 - "IUserImportantItem", 128 - "IUserParts", 129 - "IUserPartsGroupNote", 130 - }) 131 - userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds()) 132 - 133 - return &pb.UpdateEventQuestSceneProgressResponse{ 134 - DiffUserData: diff, 135 - }, nil 82 + return &pb.UpdateEventQuestSceneProgressResponse{}, nil 136 83 } 137 84 138 85 const defaultGuerrillaFreeOpenMinutes = int32(60) ··· 140 87 func (s *QuestServiceServer) StartGuerrillaFreeOpen(ctx context.Context, req *emptypb.Empty) (*pb.StartGuerrillaFreeOpenResponse, error) { 141 88 log.Printf("[QuestService] StartGuerrillaFreeOpen") 142 89 143 - userId := currentUserId(ctx, s.users, s.sessions) 90 + userId := CurrentUserId(ctx, s.users, s.sessions) 144 91 nowMillis := gametime.NowMillis() 145 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 92 + s.users.UpdateUser(userId, func(user *store.UserState) { 146 93 user.GuerrillaFreeOpen.StartDatetime = nowMillis 147 94 user.GuerrillaFreeOpen.OpenMinutes = defaultGuerrillaFreeOpenMinutes 148 95 user.GuerrillaFreeOpen.DailyOpenedCount++ 149 96 user.GuerrillaFreeOpen.LatestVersion = nowMillis 150 97 }) 151 98 152 - return &pb.StartGuerrillaFreeOpenResponse{ 153 - DiffUserData: buildSelectedQuestDiff(user, []string{"IUserEventQuestGuerrillaFreeOpen"}), 154 - }, nil 99 + return &pb.StartGuerrillaFreeOpenResponse{}, nil 155 100 }
+12 -63
server/internal/service/quest_extra.go
··· 8 8 "lunar-tear/server/internal/gametime" 9 9 "lunar-tear/server/internal/questflow" 10 10 "lunar-tear/server/internal/store" 11 - "lunar-tear/server/internal/userdata" 12 11 ) 13 12 14 13 func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) { 15 14 log.Printf("[QuestService] StartExtraQuest: questId=%d deckNumber=%d", req.QuestId, req.UserDeckNumber) 16 15 17 - userId := currentUserId(ctx, s.users, s.sessions) 16 + userId := CurrentUserId(ctx, s.users, s.sessions) 18 17 nowMillis := gametime.NowMillis() 19 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 18 + s.users.UpdateUser(userId, func(user *store.UserState) { 20 19 s.engine.HandleExtraQuestStart(user, req.QuestId, req.UserDeckNumber, nowMillis) 21 20 }) 22 21 ··· 32 31 33 32 return &pb.StartExtraQuestResponse{ 34 33 BattleDropReward: pbDrops, 35 - DiffUserData: buildSelectedQuestDiff(user, []string{ 36 - "IUserStatus", 37 - "IUserQuest", 38 - "IUserQuestMission", 39 - "IUserExtraQuestProgressStatus", 40 - }), 41 34 }, nil 42 35 } 43 36 ··· 45 38 log.Printf("[QuestService] FinishExtraQuest: questId=%d isRetired=%v isAnnihilated=%v", req.QuestId, req.IsRetired, req.IsAnnihilated) 46 39 47 40 nowMillis := gametime.NowMillis() 48 - userId := currentUserId(ctx, s.users, s.sessions) 41 + userId := CurrentUserId(ctx, s.users, s.sessions) 49 42 var outcome questflow.FinishOutcome 50 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 43 + s.users.UpdateUser(userId, func(user *store.UserState) { 51 44 outcome = s.engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) 52 45 }) 53 46 54 - diff := buildSelectedQuestDiff(user, []string{ 55 - "IUserQuest", 56 - "IUserQuestMission", 57 - "IUserExtraQuestProgressStatus", 58 - "IUserStatus", 59 - "IUserGem", 60 - "IUserCharacter", 61 - "IUserCostume", 62 - "IUserCostumeActiveSkill", 63 - "IUserWeapon", 64 - "IUserWeaponSkill", 65 - "IUserWeaponAbility", 66 - "IUserWeaponNote", 67 - "IUserCompanion", 68 - "IUserConsumableItem", 69 - "IUserMaterial", 70 - "IUserImportantItem", 71 - "IUserParts", 72 - "IUserPartsGroupNote", 73 - }) 74 - userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds) 75 - 76 47 return &pb.FinishExtraQuestResponse{ 77 48 DropReward: toProtoRewards(outcome.DropRewards), 78 49 FirstClearReward: toProtoRewards(outcome.FirstClearRewards), ··· 81 52 IsBigWin: outcome.IsBigWin, 82 53 BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, 83 54 UserStatusCampaignReward: []*pb.QuestReward{}, 84 - DiffUserData: diff, 85 55 }, nil 86 56 } 87 57 88 58 func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.RestartExtraQuestRequest) (*pb.RestartExtraQuestResponse, error) { 89 59 log.Printf("[QuestService] RestartExtraQuest: questId=%d", req.QuestId) 90 60 91 - userId := currentUserId(ctx, s.users, s.sessions) 92 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 61 + userId := CurrentUserId(ctx, s.users, s.sessions) 62 + var deckNumber int32 63 + s.users.UpdateUser(userId, func(user *store.UserState) { 93 64 s.engine.HandleExtraQuestRestart(user, req.QuestId, gametime.NowMillis()) 65 + deckNumber = user.Quests[req.QuestId].UserDeckNumber 94 66 }) 95 67 96 68 drops := s.engine.BattleDropRewards(req.QuestId) ··· 105 77 106 78 return &pb.RestartExtraQuestResponse{ 107 79 BattleDropReward: pbDrops, 108 - DeckNumber: user.Quests[req.QuestId].UserDeckNumber, 109 - DiffUserData: buildSelectedQuestDiff(user, []string{ 110 - "IUserQuest", 111 - "IUserQuestMission", 112 - "IUserExtraQuestProgressStatus", 113 - }), 80 + DeckNumber: deckNumber, 114 81 }, nil 115 82 } 116 83 117 84 func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context, req *pb.UpdateExtraQuestSceneProgressRequest) (*pb.UpdateExtraQuestSceneProgressResponse, error) { 118 85 log.Printf("[QuestService] UpdateExtraQuestSceneProgress: questSceneId=%d", req.QuestSceneId) 119 86 120 - userId := currentUserId(ctx, s.users, s.sessions) 121 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 87 + userId := CurrentUserId(ctx, s.users, s.sessions) 88 + s.users.UpdateUser(userId, func(user *store.UserState) { 122 89 s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) 123 90 }) 124 91 125 - diff := buildSelectedQuestDiff(user, []string{ 126 - "IUserExtraQuestProgressStatus", 127 - "IUserCharacter", 128 - "IUserCostume", 129 - "IUserWeapon", 130 - "IUserWeaponSkill", 131 - "IUserWeaponAbility", 132 - "IUserCompanion", 133 - "IUserConsumableItem", 134 - "IUserMaterial", 135 - "IUserImportantItem", 136 - "IUserParts", 137 - "IUserPartsGroupNote", 138 - }) 139 - userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds()) 140 - 141 - return &pb.UpdateExtraQuestSceneProgressResponse{ 142 - DiffUserData: diff, 143 - }, nil 92 + return &pb.UpdateExtraQuestSceneProgressResponse{}, nil 144 93 }
+31 -152
server/internal/service/quest_main.go
··· 9 9 "lunar-tear/server/internal/model" 10 10 "lunar-tear/server/internal/questflow" 11 11 "lunar-tear/server/internal/store" 12 - "lunar-tear/server/internal/userdata" 13 12 14 13 emptypb "google.golang.org/protobuf/types/known/emptypb" 15 14 ) ··· 28 27 return &QuestServiceServer{users: users, sessions: sessions, engine: engine} 29 28 } 30 29 31 - func buildSelectedQuestDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData { 32 - tables := userdata.ProjectTables(user, tableNames) 33 - return userdata.BuildDiffFromTablesOrdered(tables, tableNames) 34 - } 35 - 36 30 func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, req *pb.UpdateMainFlowSceneProgressRequest) (*pb.UpdateMainFlowSceneProgressResponse, error) { 37 31 log.Printf("[QuestService] UpdateMainFlowSceneProgress: questSceneId=%d", req.QuestSceneId) 38 32 39 - userId := currentUserId(ctx, s.users, s.sessions) 40 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 33 + userId := CurrentUserId(ctx, s.users, s.sessions) 34 + s.users.UpdateUser(userId, func(user *store.UserState) { 41 35 s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) 42 36 }) 43 37 44 - diff := buildSelectedQuestDiff(user, []string{ 45 - "IUserMainQuestFlowStatus", 46 - "IUserMainQuestMainFlowStatus", 47 - "IUserMainQuestProgressStatus", 48 - "IUserMainQuestSeasonRoute", 49 - "IUserPortalCageStatus", 50 - "IUserSideStoryQuestSceneProgressStatus", 51 - "IUserQuest", 52 - "IUserCharacter", 53 - "IUserCostume", 54 - "IUserCostumeActiveSkill", 55 - "IUserWeapon", 56 - "IUserWeaponSkill", 57 - "IUserWeaponAbility", 58 - "IUserWeaponNote", 59 - "IUserCompanion", 60 - "IUserConsumableItem", 61 - "IUserMaterial", 62 - "IUserImportantItem", 63 - "IUserParts", 64 - "IUserPartsGroupNote", 65 - }) 66 - userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds()) 67 - 68 - return &pb.UpdateMainFlowSceneProgressResponse{ 69 - DiffUserData: diff, 70 - }, nil 38 + return &pb.UpdateMainFlowSceneProgressResponse{}, nil 71 39 } 72 40 73 41 func (s *QuestServiceServer) UpdateReplayFlowSceneProgress(ctx context.Context, req *pb.UpdateReplayFlowSceneProgressRequest) (*pb.UpdateReplayFlowSceneProgressResponse, error) { 74 42 log.Printf("[QuestService] UpdateReplayFlowSceneProgress: questSceneId=%d", req.QuestSceneId) 75 43 76 - userId := currentUserId(ctx, s.users, s.sessions) 77 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 44 + userId := CurrentUserId(ctx, s.users, s.sessions) 45 + s.users.UpdateUser(userId, func(user *store.UserState) { 78 46 s.engine.HandleReplayFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis()) 79 47 }) 80 48 81 - return &pb.UpdateReplayFlowSceneProgressResponse{ 82 - DiffUserData: buildSelectedQuestDiff(user, []string{ 83 - "IUserMainQuestFlowStatus", 84 - "IUserMainQuestReplayFlowStatus", 85 - }), 86 - }, nil 49 + return &pb.UpdateReplayFlowSceneProgressResponse{}, nil 87 50 } 88 51 89 52 func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, req *pb.UpdateMainQuestSceneProgressRequest) (*pb.UpdateMainQuestSceneProgressResponse, error) { 90 53 log.Printf("[QuestService] UpdateMainQuestSceneProgress: questSceneId=%d", req.QuestSceneId) 91 54 92 - userId := currentUserId(ctx, s.users, s.sessions) 93 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 55 + userId := CurrentUserId(ctx, s.users, s.sessions) 56 + s.users.UpdateUser(userId, func(user *store.UserState) { 94 57 s.engine.HandleMainQuestSceneProgress(user, req.QuestSceneId) 95 58 }) 96 59 97 - return &pb.UpdateMainQuestSceneProgressResponse{ 98 - DiffUserData: buildSelectedQuestDiff(user, []string{ 99 - "IUserStatus", 100 - "IUserCharacter", 101 - "IUserQuest", 102 - "IUserQuestMission", 103 - "IUserMainQuestFlowStatus", 104 - "IUserMainQuestMainFlowStatus", 105 - "IUserMainQuestProgressStatus", 106 - }), 107 - }, nil 60 + return &pb.UpdateMainQuestSceneProgressResponse{}, nil 108 61 } 109 62 110 63 func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) { 111 64 log.Printf("[QuestService] StartMainQuest: %+v", req) 112 65 113 - userId := currentUserId(ctx, s.users, s.sessions) 66 + userId := CurrentUserId(ctx, s.users, s.sessions) 114 67 nowMillis := gametime.NowMillis() 115 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 68 + s.users.UpdateUser(userId, func(user *store.UserState) { 116 69 if req.IsReplayFlow { 117 70 s.engine.HandleQuestStartReplay(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis) 118 71 } else { ··· 132 85 133 86 return &pb.StartMainQuestResponse{ 134 87 BattleDropReward: pbDrops, 135 - DiffUserData: buildSelectedQuestDiff(user, []string{ 136 - "IUserStatus", 137 - "IUserQuest", 138 - "IUserQuestMission", 139 - "IUserMainQuestFlowStatus", 140 - "IUserMainQuestMainFlowStatus", 141 - "IUserMainQuestProgressStatus", 142 - "IUserMainQuestSeasonRoute", 143 - "IUserMainQuestReplayFlowStatus", 144 - }), 145 88 }, nil 146 89 } 147 90 ··· 165 108 req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.StorySkipType) 166 109 167 110 nowMillis := gametime.NowMillis() 168 - userId := currentUserId(ctx, s.users, s.sessions) 111 + userId := CurrentUserId(ctx, s.users, s.sessions) 169 112 var outcome questflow.FinishOutcome 170 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 113 + s.users.UpdateUser(userId, func(user *store.UserState) { 171 114 outcome = s.engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis) 172 115 }) 173 116 174 - diff := buildSelectedQuestDiff(user, []string{ 175 - "IUserQuest", 176 - "IUserQuestMission", 177 - "IUserMainQuestFlowStatus", 178 - "IUserMainQuestMainFlowStatus", 179 - "IUserMainQuestProgressStatus", 180 - "IUserMainQuestSeasonRoute", 181 - "IUserMainQuestReplayFlowStatus", 182 - "IUserStatus", 183 - "IUserGem", 184 - "IUserCharacter", 185 - "IUserCostume", 186 - "IUserCostumeActiveSkill", 187 - "IUserWeapon", 188 - "IUserWeaponSkill", 189 - "IUserWeaponAbility", 190 - "IUserWeaponNote", 191 - "IUserCompanion", 192 - "IUserConsumableItem", 193 - "IUserMaterial", 194 - "IUserImportantItem", 195 - "IUserParts", 196 - "IUserPartsGroupNote", 197 - }) 198 - userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds) 199 - 200 117 return &pb.FinishMainQuestResponse{ 201 118 DropReward: toProtoRewards(outcome.DropRewards), 202 119 FirstClearReward: toProtoRewards(outcome.FirstClearRewards), ··· 207 124 BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds, 208 125 ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards), 209 126 UserStatusCampaignReward: []*pb.QuestReward{}, 210 - DiffUserData: diff, 211 127 }, nil 212 128 } 213 129 214 130 func (s *QuestServiceServer) RestartMainQuest(ctx context.Context, req *pb.RestartMainQuestRequest) (*pb.RestartMainQuestResponse, error) { 215 131 log.Printf("[QuestService] RestartMainQuest: questId=%d isMainFlow=%v", req.QuestId, req.IsMainFlow) 216 132 217 - userId := currentUserId(ctx, s.users, s.sessions) 218 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 133 + userId := CurrentUserId(ctx, s.users, s.sessions) 134 + var deckNumber int32 135 + s.users.UpdateUser(userId, func(user *store.UserState) { 219 136 s.engine.HandleQuestRestart(user, req.QuestId, gametime.NowMillis()) 137 + deckNumber = user.Quests[req.QuestId].UserDeckNumber 220 138 }) 221 139 222 140 drops := s.engine.BattleDropRewards(req.QuestId) ··· 231 149 232 150 return &pb.RestartMainQuestResponse{ 233 151 BattleDropReward: pbDrops, 234 - DeckNumber: user.Quests[req.QuestId].UserDeckNumber, 235 - DiffUserData: buildSelectedQuestDiff(user, []string{ 236 - "IUserStatus", 237 - "IUserQuest", 238 - "IUserQuestMission", 239 - "IUserMainQuestFlowStatus", 240 - "IUserMainQuestMainFlowStatus", 241 - "IUserMainQuestProgressStatus", 242 - "IUserMainQuestSeasonRoute", 243 - }), 152 + DeckNumber: deckNumber, 244 153 }, nil 245 154 } 246 155 247 156 func (s *QuestServiceServer) FinishAutoOrbit(ctx context.Context, req *emptypb.Empty) (*pb.FinishAutoOrbitResponse, error) { 248 157 log.Printf("[QuestService] FinishAutoOrbit") 249 - return &pb.FinishAutoOrbitResponse{ 250 - DiffUserData: userdata.EmptyDiff(), 251 - }, nil 158 + return &pb.FinishAutoOrbitResponse{}, nil 252 159 } 253 160 254 161 func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestRequest) (*pb.SkipQuestResponse, error) { 255 162 log.Printf("[QuestService] SkipQuest: questId=%d skipCount=%d useEffectItems=%d", req.QuestId, req.SkipCount, len(req.UseEffectItem)) 256 163 257 164 nowMillis := gametime.NowMillis() 258 - userId := currentUserId(ctx, s.users, s.sessions) 165 + userId := CurrentUserId(ctx, s.users, s.sessions) 259 166 var outcome questflow.FinishOutcome 260 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 167 + s.users.UpdateUser(userId, func(user *store.UserState) { 261 168 for _, item := range req.UseEffectItem { 262 169 log.Printf("[QuestService] SkipQuest UseEffectItem: consumableItemId=%d count=%d", item.ConsumableItemId, item.Count) 263 170 user.ConsumableItems[item.ConsumableItemId] -= item.Count ··· 271 178 return &pb.SkipQuestResponse{ 272 179 DropReward: toProtoRewards(outcome.DropRewards), 273 180 UserStatusCampaignReward: []*pb.QuestReward{}, 274 - DiffUserData: buildSelectedQuestDiff(user, []string{ 275 - "IUserQuest", 276 - "IUserStatus", 277 - "IUserConsumableItem", 278 - "IUserMaterial", 279 - "IUserParts", 280 - "IUserPartsGroupNote", 281 - "IUserCharacter", 282 - "IUserCostume", 283 - }), 284 181 }, nil 285 182 } 286 183 287 184 func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteRequest) (*pb.SetRouteResponse, error) { 288 185 log.Printf("[QuestService] SetRoute: mainQuestRouteId=%d", req.MainQuestRouteId) 289 186 290 - userId := currentUserId(ctx, s.users, s.sessions) 291 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 187 + userId := CurrentUserId(ctx, s.users, s.sessions) 188 + s.users.UpdateUser(userId, func(user *store.UserState) { 292 189 user.MainQuest.CurrentMainQuestRouteId = req.MainQuestRouteId 293 190 if seasonId, ok := s.engine.SeasonIdByRouteId[req.MainQuestRouteId]; ok { 294 191 user.MainQuest.MainQuestSeasonId = seasonId ··· 298 195 user.PortalCageStatus.LatestVersion = now 299 196 }) 300 197 301 - return &pb.SetRouteResponse{ 302 - DiffUserData: buildSelectedQuestDiff(user, []string{ 303 - "IUserMainQuestSeasonRoute", 304 - "IUserMainQuestMainFlowStatus", 305 - "IUserPortalCageStatus", 306 - }), 307 - }, nil 198 + return &pb.SetRouteResponse{}, nil 308 199 } 309 200 310 201 func (s *QuestServiceServer) SetQuestSceneChoice(ctx context.Context, req *pb.SetQuestSceneChoiceRequest) (*pb.SetQuestSceneChoiceResponse, error) { 311 202 log.Printf("[QuestService] SetQuestSceneChoice: questSceneId=%d choiceNumber=%d", 312 203 req.QuestSceneId, req.ChoiceNumber) 313 - return &pb.SetQuestSceneChoiceResponse{ 314 - DiffUserData: userdata.EmptyDiff(), 315 - }, nil 204 + return &pb.SetQuestSceneChoiceResponse{}, nil 316 205 } 317 206 318 207 func (s *QuestServiceServer) ResetLimitContentQuestProgress(ctx context.Context, req *pb.ResetLimitContentQuestProgressRequest) (*pb.ResetLimitContentQuestProgressResponse, error) { 319 208 log.Printf("[QuestService] ResetLimitContentQuestProgress: eventQuestChapterId=%d questId=%d", 320 209 req.EventQuestChapterId, req.QuestId) 321 210 322 - userId := currentUserId(ctx, s.users, s.sessions) 211 + userId := CurrentUserId(ctx, s.users, s.sessions) 323 212 nowMillis := gametime.NowMillis() 324 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 213 + s.users.UpdateUser(userId, func(user *store.UserState) { 325 214 if _, exists := user.SideStoryQuests[req.QuestId]; exists { 326 215 user.SideStoryQuests[req.QuestId] = store.SideStoryQuestProgress{ 327 216 HeadSideStoryQuestSceneId: 0, ··· 339 228 } 340 229 }) 341 230 342 - return &pb.ResetLimitContentQuestProgressResponse{ 343 - DiffUserData: buildSelectedQuestDiff(user, []string{ 344 - "IUserSideStoryQuest", 345 - "IUserSideStoryQuestSceneProgressStatus", 346 - "IUserQuestLimitContentStatus", 347 - }), 348 - }, nil 231 + return &pb.ResetLimitContentQuestProgressResponse{}, nil 349 232 } 350 233 351 234 func (s *QuestServiceServer) SetAutoSaleSetting(ctx context.Context, req *pb.SetAutoSaleSettingRequest) (*pb.SetAutoSaleSettingResponse, error) { 352 235 log.Printf("[QuestService] SetAutoSaleSetting: items=%d", len(req.AutoSaleSettingItem)) 353 236 354 - userId := currentUserId(ctx, s.users, s.sessions) 355 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 237 + userId := CurrentUserId(ctx, s.users, s.sessions) 238 + s.users.UpdateUser(userId, func(user *store.UserState) { 356 239 user.AutoSaleSettings = make(map[int32]store.AutoSaleSettingState, len(req.AutoSaleSettingItem)) 357 240 for itemType, itemValue := range req.AutoSaleSettingItem { 358 241 user.AutoSaleSettings[itemType] = store.AutoSaleSettingState{ ··· 362 245 } 363 246 }) 364 247 365 - return &pb.SetAutoSaleSettingResponse{ 366 - DiffUserData: buildSelectedQuestDiff(user, []string{ 367 - "IUserAutoSaleSettingDetail", 368 - }), 369 - }, nil 248 + return &pb.SetAutoSaleSettingResponse{}, nil 370 249 }
+6 -22
server/internal/service/quest_sidestory.go
··· 9 9 "lunar-tear/server/internal/masterdata" 10 10 "lunar-tear/server/internal/model" 11 11 "lunar-tear/server/internal/store" 12 - "lunar-tear/server/internal/userdata" 13 12 ) 14 13 15 14 type SideStoryQuestServiceServer struct { ··· 23 22 return &SideStoryQuestServiceServer{users: users, sessions: sessions, catalog: catalog} 24 23 } 25 24 26 - func buildSideStoryDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData { 27 - tables := userdata.ProjectTables(user, tableNames) 28 - return userdata.BuildDiffFromTablesOrdered(tables, tableNames) 29 - } 30 - 31 25 func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) { 32 26 log.Printf("[SideStoryQuestService] MoveSideStoryQuestProgress: sideStoryQuestId=%d", req.SideStoryQuestId) 33 27 34 - userId := currentUserId(ctx, s.users, s.sessions) 28 + userId := CurrentUserId(ctx, s.users, s.sessions) 35 29 nowMillis := gametime.NowMillis() 36 30 firstSceneId := s.catalog.FirstSceneByQuestId[req.SideStoryQuestId] 37 31 38 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 32 + s.users.UpdateUser(userId, func(user *store.UserState) { 39 33 existing, exists := user.SideStoryQuests[req.SideStoryQuestId] 40 34 41 35 var sceneId int32 ··· 58 52 } 59 53 }) 60 54 61 - return &pb.MoveSideStoryQuestResponse{ 62 - DiffUserData: buildSideStoryDiff(user, []string{ 63 - "IUserSideStoryQuest", 64 - "IUserSideStoryQuestSceneProgressStatus", 65 - }), 66 - }, nil 55 + return &pb.MoveSideStoryQuestResponse{}, nil 67 56 } 68 57 69 58 func (s *SideStoryQuestServiceServer) UpdateSideStoryQuestSceneProgress(ctx context.Context, req *pb.UpdateSideStoryQuestSceneProgressRequest) (*pb.UpdateSideStoryQuestSceneProgressResponse, error) { 70 59 log.Printf("[SideStoryQuestService] UpdateSideStoryQuestSceneProgress: sideStoryQuestId=%d sceneId=%d", 71 60 req.SideStoryQuestId, req.SideStoryQuestSceneId) 72 61 73 - userId := currentUserId(ctx, s.users, s.sessions) 62 + userId := CurrentUserId(ctx, s.users, s.sessions) 74 63 nowMillis := gametime.NowMillis() 75 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 64 + s.users.UpdateUser(userId, func(user *store.UserState) { 76 65 user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId = req.SideStoryQuestSceneId 77 66 user.SideStoryActiveProgress.LatestVersion = nowMillis 78 67 ··· 84 73 user.SideStoryQuests[req.SideStoryQuestId] = progress 85 74 }) 86 75 87 - return &pb.UpdateSideStoryQuestSceneProgressResponse{ 88 - DiffUserData: buildSideStoryDiff(user, []string{ 89 - "IUserSideStoryQuest", 90 - "IUserSideStoryQuestSceneProgressStatus", 91 - }), 92 - }, nil 76 + return &pb.UpdateSideStoryQuestSceneProgressResponse{}, nil 93 77 }
+2 -11
server/internal/service/reward.go
··· 9 9 "lunar-tear/server/internal/masterdata" 10 10 "lunar-tear/server/internal/model" 11 11 "lunar-tear/server/internal/store" 12 - "lunar-tear/server/internal/userdata" 13 12 14 13 emptypb "google.golang.org/protobuf/types/known/emptypb" 15 14 ) ··· 34 33 func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *emptypb.Empty) (*pb.ReceiveBigHuntRewardResponse, error) { 35 34 log.Printf("[RewardService] ReceiveBigHuntReward") 36 35 37 - userId := currentUserId(ctx, s.users, s.sessions) 36 + userId := CurrentUserId(ctx, s.users, s.sessions) 38 37 nowMillis := gametime.NowMillis() 39 38 weeklyVersion := gametime.WeeklyVersion(nowMillis) 40 39 ··· 42 41 var weeklyRewards []*pb.BigHuntReward 43 42 isReceived := false 44 43 45 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 44 + s.users.UpdateUser(userId, func(user *store.UserState) { 46 45 ws := user.BigHuntWeeklyStatuses[weeklyVersion] 47 46 isReceived = ws.IsReceivedWeeklyReward 48 47 ··· 106 105 weeklyScoreResults = []*pb.WeeklyScoreResult{} 107 106 } 108 107 109 - tables := userdata.ProjectTables(user, []string{ 110 - "IUserBigHuntWeeklyStatus", 111 - "IUserBigHuntWeeklyMaxScore", 112 - "IUserConsumableItem", 113 - "IUserMaterial", 114 - }) 115 - 116 108 return &pb.ReceiveBigHuntRewardResponse{ 117 109 WeeklyScoreResult: weeklyScoreResults, 118 110 WeeklyScoreReward: weeklyRewards, 119 111 IsReceivedWeeklyScoreReward: isReceived, 120 112 LastWeekWeeklyScoreReward: []*pb.BigHuntReward{}, 121 - DiffUserData: userdata.BuildDiffFromTables(tables), 122 113 }, nil 123 114 } 124 115
+10 -46
server/internal/service/shop.go
··· 10 10 "lunar-tear/server/internal/masterdata" 11 11 "lunar-tear/server/internal/model" 12 12 "lunar-tear/server/internal/store" 13 - "lunar-tear/server/internal/userdata" 14 13 15 14 "google.golang.org/protobuf/types/known/emptypb" 16 15 ) 17 16 18 - var shopDiffTables = []string{ 19 - "IUserShopItem", 20 - "IUserShopReplaceable", 21 - "IUserShopReplaceableLineup", 22 - "IUserGem", 23 - "IUserConsumableItem", 24 - "IUserMaterial", 25 - "IUserImportantItem", 26 - "IUserPremiumItem", 27 - "IUserStatus", 28 - "IUserCostume", 29 - "IUserCostumeActiveSkill", 30 - "IUserCharacter", 31 - "IUserWeapon", 32 - "IUserWeaponSkill", 33 - "IUserWeaponAbility", 34 - "IUserWeaponNote", 35 - } 36 - 37 17 type ShopServiceServer struct { 38 18 pb.UnimplementedShopServiceServer 39 19 users store.UserRepository ··· 49 29 func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.BuyResponse, error) { 50 30 log.Printf("[ShopService] Buy: shopId=%d items=%v", req.ShopId, req.ShopItems) 51 31 52 - userId := currentUserId(ctx, s.users, s.sessions) 32 + userId := CurrentUserId(ctx, s.users, s.sessions) 53 33 nowMillis := gametime.NowMillis() 54 34 55 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 35 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 56 36 for shopItemId, qty := range req.ShopItems { 57 37 item, ok := s.catalog.Items[shopItemId] 58 38 if !ok { ··· 88 68 if err != nil { 89 69 return nil, fmt.Errorf("shop buy: %w", err) 90 70 } 91 - 92 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables)) 93 - userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds()) 94 - 95 71 return &pb.BuyResponse{ 96 72 OverflowPossession: []*pb.Possession{}, 97 - DiffUserData: diff, 98 73 }, nil 99 74 } 100 75 101 76 func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.RefreshRequest) (*pb.RefreshResponse, error) { 102 77 log.Printf("[ShopService] RefreshUserData: isGemUsed=%v", req.IsGemUsed) 103 78 104 - userId := currentUserId(ctx, s.users, s.sessions) 79 + userId := CurrentUserId(ctx, s.users, s.sessions) 105 80 nowMillis := gametime.NowMillis() 106 81 107 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 82 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 108 83 if len(user.ShopReplaceableLineup) == 0 && len(s.catalog.ItemShopPool) > 0 { 109 84 for i, itemId := range s.catalog.ItemShopPool { 110 85 slot := int32(i + 1) ··· 131 106 return nil, fmt.Errorf("shop refresh: %w", err) 132 107 } 133 108 134 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables)) 135 - 136 - return &pb.RefreshResponse{ 137 - DiffUserData: diff, 138 - }, nil 109 + return &pb.RefreshResponse{}, nil 139 110 } 140 111 141 112 func (s *ShopServiceServer) GetCesaLimit(_ context.Context, _ *emptypb.Empty) (*pb.GetCesaLimitResponse, error) { 142 113 log.Printf("[ShopService] GetCesaLimit") 143 114 return &pb.GetCesaLimitResponse{ 144 - CesaLimit: []*pb.CesaLimit{}, 145 - DiffUserData: userdata.EmptyDiff(), 115 + CesaLimit: []*pb.CesaLimit{}, 146 116 }, nil 147 117 } 148 118 ··· 150 120 log.Printf("[ShopService] CreatePurchaseTransaction: shopId=%d shopItemId=%d productId=%s", 151 121 req.ShopId, req.ShopItemId, req.ProductId) 152 122 153 - userId := currentUserId(ctx, s.users, s.sessions) 123 + userId := CurrentUserId(ctx, s.users, s.sessions) 154 124 nowMillis := gametime.NowMillis() 155 125 156 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 126 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 157 127 item, ok := s.catalog.Items[req.ShopItemId] 158 128 if !ok { 159 129 log.Printf("[ShopService] CreatePurchaseTransaction: unknown shopItemId=%d", req.ShopItemId) ··· 193 163 194 164 txId := fmt.Sprintf("tx_%d_%d_%d", userId, req.ShopItemId, nowMillis) 195 165 196 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables)) 197 - 198 166 return &pb.CreatePurchaseTransactionResponse{ 199 167 PurchaseTransactionId: txId, 200 - DiffUserData: diff, 201 168 }, nil 202 169 } 203 170 204 171 func (s *ShopServiceServer) PurchaseGooglePlayStoreProduct(ctx context.Context, req *pb.PurchaseGooglePlayStoreProductRequest) (*pb.PurchaseGooglePlayStoreProductResponse, error) { 205 172 log.Printf("[ShopService] PurchaseGooglePlayStoreProduct: txId=%s", req.PurchaseTransactionId) 206 173 207 - userId := currentUserId(ctx, s.users, s.sessions) 208 - snapshot, err := s.users.LoadUser(userId) 174 + userId := CurrentUserId(ctx, s.users, s.sessions) 175 + _, err := s.users.LoadUser(userId) 209 176 if err != nil { 210 177 return nil, fmt.Errorf("purchase google play: %w", err) 211 178 } 212 179 213 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables)) 214 - 215 180 return &pb.PurchaseGooglePlayStoreProductResponse{ 216 181 OverflowPossession: []*pb.Possession{}, 217 - DiffUserData: diff, 218 182 }, nil 219 183 } 220 184
+1 -37
server/internal/service/state.go
··· 8 8 "google.golang.org/grpc/metadata" 9 9 ) 10 10 11 - var startedGameStartTables = []string{ 12 - "IUserProfile", 13 - "IUserCharacter", 14 - "IUserCostume", 15 - "IUserWeapon", 16 - "IUserWeaponSkill", 17 - "IUserWeaponAbility", 18 - "IUserCompanion", 19 - "IUserDeckCharacter", 20 - "IUserDeck", 21 - "IUserGem", 22 - "IUserMission", 23 - "IUserMainQuestFlowStatus", 24 - "IUserMainQuestMainFlowStatus", 25 - "IUserMainQuestProgressStatus", 26 - "IUserMainQuestSeasonRoute", 27 - "IUserQuest", 28 - "IUserQuestMission", 29 - "IUserTutorialProgress", 30 - "IUserWeaponNote", 31 - "IUserCostumeActiveSkill", 32 - "IUserDeckTypeNote", 33 - "IUserDeckSubWeaponGroup", 34 - "IUserDeckPartsGroup", 35 - "IUserConsumableItem", 36 - "IUserMaterial", 37 - "IUserImportantItem", 38 - } 39 - 40 - var gimmickDiffTables = []string{ 41 - "IUserGimmick", 42 - "IUserGimmickOrnamentProgress", 43 - "IUserGimmickSequence", 44 - "IUserGimmickUnlock", 45 - } 46 - 47 - func currentUserId(ctx context.Context, users store.UserRepository, sessions store.SessionRepository) int64 { 11 + func CurrentUserId(ctx context.Context, users store.UserRepository, sessions store.SessionRepository) int64 { 48 12 if md, ok := metadata.FromIncomingContext(ctx); ok { 49 13 if vals := md.Get("x-apb-session-key"); len(vals) > 0 { 50 14 if userId, err := sessions.ResolveUserId(vals[0]); err == nil {
+6 -30
server/internal/service/tutorial.go
··· 9 9 "lunar-tear/server/internal/model" 10 10 "lunar-tear/server/internal/questflow" 11 11 "lunar-tear/server/internal/store" 12 - "lunar-tear/server/internal/userdata" 13 12 ) 14 13 15 14 type TutorialServiceServer struct { ··· 25 24 26 25 func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb.SetTutorialProgressRequest) (*pb.SetTutorialProgressResponse, error) { 27 26 log.Printf("[TutorialService] SetTutorialProgress: type=%d phase=%d choice=%d", req.TutorialType, req.ProgressPhase, req.ChoiceId) 28 - userId := currentUserId(ctx, s.users, s.sessions) 27 + userId := CurrentUserId(ctx, s.users, s.sessions) 29 28 nowMillis := gametime.NowMillis() 30 29 var grants []questflow.RewardGrant 31 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 30 + s.users.UpdateUser(userId, func(user *store.UserState) { 32 31 existing, exists := user.Tutorials[req.TutorialType] 33 32 if !exists || req.ProgressPhase >= existing.ProgressPhase { 34 33 user.Tutorials[req.TutorialType] = store.TutorialProgressState{ ··· 42 41 store.EnsureDefaultDeck(user, nowMillis) 43 42 } 44 43 }) 45 - tables := []string{"IUserTutorialProgress"} 46 - if req.TutorialType == int32(model.TutorialTypeMenuFirst) || 47 - req.TutorialType == int32(model.TutorialTypeMenuSecond) { 48 - tables = append(tables, 49 - "IUserCharacter", "IUserCostume", "IUserWeapon", 50 - "IUserWeaponSkill", "IUserWeaponAbility", 51 - "IUserCompanion", "IUserDeckCharacter", "IUserDeck", 52 - ) 53 - } 54 - if len(grants) > 0 { 55 - tables = append(tables, "IUserCompanion") 56 - } 57 - result := userdata.ProjectTables(user, tables) 58 - for _, t := range tables { 59 - log.Printf("[TutorialService] DiffTable %s -> %s", t, result[t]) 60 - } 44 + 61 45 rewards := make([]*pb.TutorialChoiceReward, len(grants)) 62 46 for i, g := range grants { 63 47 rewards[i] = &pb.TutorialChoiceReward{ ··· 68 52 } 69 53 return &pb.SetTutorialProgressResponse{ 70 54 TutorialChoiceReward: rewards, 71 - DiffUserData: userdata.BuildDiffFromTables(result), 72 55 }, nil 73 56 } 74 57 75 58 func (s *TutorialServiceServer) SetTutorialProgressAndReplaceDeck(ctx context.Context, req *pb.SetTutorialProgressAndReplaceDeckRequest) (*pb.SetTutorialProgressAndReplaceDeckResponse, error) { 76 59 log.Printf("[TutorialService] SetTutorialProgressAndReplaceDeck: type=%d phase=%d deckType=%d deckNumber=%d", req.TutorialType, req.ProgressPhase, req.DeckType, req.UserDeckNumber) 77 - userId := currentUserId(ctx, s.users, s.sessions) 78 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 60 + userId := CurrentUserId(ctx, s.users, s.sessions) 61 + s.users.UpdateUser(userId, func(user *store.UserState) { 79 62 existing, exists := user.Tutorials[req.TutorialType] 80 63 if !exists || req.ProgressPhase >= existing.ProgressPhase { 81 64 user.Tutorials[req.TutorialType] = store.TutorialProgressState{ ··· 87 70 store.ApplyDeckReplacement(user, model.DeckType(req.DeckType), req.UserDeckNumber, deckSlotsFromProto(req.Deck), gametime.NowMillis()) 88 71 } 89 72 }) 90 - return &pb.SetTutorialProgressAndReplaceDeckResponse{ 91 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{ 92 - "IUserTutorialProgress", 93 - "IUserDeck", 94 - "IUserDeckCharacter", 95 - "IUserDeckSubWeaponGroup", 96 - })), 97 - }, nil 73 + return &pb.SetTutorialProgressAndReplaceDeckResponse{}, nil 98 74 }
+142 -90
server/internal/service/user.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "encoding/json" 5 6 "fmt" 6 7 "log" 7 - "sort" 8 + "net/http" 9 + "strconv" 10 + "strings" 8 11 "time" 9 12 13 + "google.golang.org/grpc/codes" 14 + "google.golang.org/grpc/metadata" 15 + "google.golang.org/grpc/status" 16 + "google.golang.org/protobuf/types/known/emptypb" 17 + "google.golang.org/protobuf/types/known/timestamppb" 10 18 pb "lunar-tear/server/gen/proto" 11 19 "lunar-tear/server/internal/gametime" 12 20 "lunar-tear/server/internal/model" 13 21 "lunar-tear/server/internal/store" 14 - "lunar-tear/server/internal/userdata" 15 - 16 - "google.golang.org/grpc" 17 - "google.golang.org/grpc/metadata" 18 - "google.golang.org/protobuf/types/known/emptypb" 19 - "google.golang.org/protobuf/types/known/timestamppb" 20 22 ) 21 23 22 24 type UserServiceServer struct { 23 25 pb.UnimplementedUserServiceServer 24 26 users store.UserRepository 25 27 sessions store.SessionRepository 28 + authURL string 26 29 } 27 30 28 - func NewUserServiceServer(users store.UserRepository, sessions store.SessionRepository) *UserServiceServer { 29 - return &UserServiceServer{users: users, sessions: sessions} 30 - } 31 - 32 - func setCommonResponseTrailers(ctx context.Context, diff map[string]*pb.DiffData, includeUpdateNames bool) { 33 - keys := make([]string, 0, len(diff)) 34 - for key := range diff { 35 - keys = append(keys, key) 36 - } 37 - sort.Strings(keys) 38 - 39 - var pairs []string 40 - if includeUpdateNames && len(keys) > 0 { 41 - pairs = append(pairs, "x-apb-update-user-data-names", keys[0]) 42 - for _, key := range keys[1:] { 43 - pairs[len(pairs)-1] += "," + key 44 - } 31 + func NewUserServiceServer(users store.UserRepository, sessions store.SessionRepository, authURL string) *UserServiceServer { 32 + if authURL != "" && !strings.Contains(authURL, "://") { 33 + authURL = "http://" + authURL 45 34 } 46 - 47 - if err := grpc.SetTrailer(ctx, metadata.Pairs(pairs...)); err != nil { 48 - log.Printf("[UserService] failed to set trailers: %v", err) 49 - } 35 + return &UserServiceServer{users: users, sessions: sessions, authURL: authURL} 50 36 } 51 37 52 38 func (s *UserServiceServer) RegisterUser(ctx context.Context, req *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) { 53 - userId, err := s.users.CreateUser(req.Uuid) 39 + platform := model.ClientPlatformFromContext(ctx) 40 + userId, err := s.users.CreateUser(req.Uuid, platform) 54 41 if err != nil { 55 42 return nil, fmt.Errorf("create user: %w", err) 56 43 } 57 - user, err := s.users.LoadUser(userId) 58 - if err != nil { 59 - return nil, fmt.Errorf("load user: %w", err) 60 - } 61 - log.Printf("[UserService] RegisterUser: uuid=%s terminalId=%s -> userId=%d", req.Uuid, req.TerminalId, user.UserId) 44 + log.Printf("[UserService] RegisterUser: uuid=%s terminalId=%s platform=%s -> userId=%d", req.Uuid, req.TerminalId, platform, userId) 62 45 63 46 return &pb.RegisterUserResponse{ 64 - UserId: user.UserId, 65 - Signature: fmt.Sprintf("sig_%d_%d", user.UserId, gametime.Now().Unix()), 66 - DiffUserData: userdata.BuildDiffFromTables(userdata.FirstEntranceClientTableMap(user)), 47 + UserId: userId, 48 + Signature: fmt.Sprintf("sig_%d_%d", userId, gametime.Now().Unix()), 67 49 }, nil 68 50 } 69 51 70 52 func (s *UserServiceServer) Auth(ctx context.Context, req *pb.AuthUserRequest) (*pb.AuthUserResponse, error) { 71 - log.Printf("[UserService] Auth: uuid=%s", req.Uuid) 53 + platform := model.ClientPlatformFromContext(ctx) 54 + log.Printf("[UserService] Auth: uuid=%s platform=%s", req.Uuid, platform) 72 55 73 56 session, err := s.sessions.CreateSession(req.Uuid, 24*time.Hour) 74 57 if err != nil { ··· 84 67 ExpireDatetime: timestamppb.New(session.ExpireAt), 85 68 Signature: req.Signature, 86 69 UserId: user.UserId, 87 - DiffUserData: userdata.BuildDiffFromTables(userdata.FirstEntranceClientTableMap(user)), 88 70 }, nil 89 71 } 90 72 ··· 97 79 } 98 80 } 99 81 100 - userId := currentUserId(ctx, s.users, s.sessions) 101 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 82 + userId := CurrentUserId(ctx, s.users, s.sessions) 83 + s.users.UpdateUser(userId, func(user *store.UserState) { 102 84 user.GameStartDatetime = gametime.NowMillis() 103 85 }) 104 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user, startedGameStartTables)) 105 - setCommonResponseTrailers(ctx, diff, true) 106 86 107 - return &pb.GameStartResponse{ 108 - // Apply only the starter outgame rows we need after title completion. 109 - // Keep IUser and other risky core-account rows out of GameStart diff. 110 - DiffUserData: diff, 111 - }, nil 87 + return &pb.GameStartResponse{}, nil 112 88 } 113 89 114 90 func (s *UserServiceServer) TransferUser(ctx context.Context, req *pb.TransferUserRequest) (*pb.TransferUserResponse, error) { 115 - log.Printf("[UserService] TransferUser") 116 - userId, err := s.users.CreateUser(req.Uuid) 91 + platform := model.ClientPlatformFromContext(ctx) 92 + log.Printf("[UserService] TransferUser: platform=%s", platform) 93 + userId, err := s.users.CreateUser(req.Uuid, platform) 117 94 if err != nil { 118 95 return nil, fmt.Errorf("create user: %w", err) 119 96 } 120 97 return &pb.TransferUserResponse{ 121 - UserId: userId, 122 - Signature: "transferred-sig", 123 - DiffUserData: userdata.EmptyDiff(), 98 + UserId: userId, 99 + Signature: "transferred-sig", 124 100 }, nil 125 101 } 126 102 127 103 func (s *UserServiceServer) SetUserName(ctx context.Context, req *pb.SetUserNameRequest) (*pb.SetUserNameResponse, error) { 128 104 log.Printf("[UserService] SetUserName: %s", req.Name) 129 - userId := currentUserId(ctx, s.users, s.sessions) 130 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 105 + userId := CurrentUserId(ctx, s.users, s.sessions) 106 + s.users.UpdateUser(userId, func(user *store.UserState) { 131 107 nowMillis := gametime.NowMillis() 132 108 user.Profile.Name = req.Name 133 109 user.Profile.NameUpdateDatetime = nowMillis 134 110 }) 135 - return &pb.SetUserNameResponse{ 136 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})), 137 - }, nil 111 + return &pb.SetUserNameResponse{}, nil 138 112 } 139 113 140 114 func (s *UserServiceServer) SetUserMessage(ctx context.Context, req *pb.SetUserMessageRequest) (*pb.SetUserMessageResponse, error) { 141 115 log.Printf("[UserService] SetUserMessage: %s", req.Message) 142 - userId := currentUserId(ctx, s.users, s.sessions) 143 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 116 + userId := CurrentUserId(ctx, s.users, s.sessions) 117 + s.users.UpdateUser(userId, func(user *store.UserState) { 144 118 nowMillis := gametime.NowMillis() 145 119 user.Profile.Message = req.Message 146 120 user.Profile.MessageUpdateDatetime = nowMillis 147 121 }) 148 - return &pb.SetUserMessageResponse{ 149 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})), 150 - }, nil 122 + return &pb.SetUserMessageResponse{}, nil 151 123 } 152 124 153 125 func (s *UserServiceServer) SetUserFavoriteCostumeId(ctx context.Context, req *pb.SetUserFavoriteCostumeIdRequest) (*pb.SetUserFavoriteCostumeIdResponse, error) { 154 126 log.Printf("[UserService] SetUserFavoriteCostumeId: %d", req.FavoriteCostumeId) 155 - userId := currentUserId(ctx, s.users, s.sessions) 156 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 127 + userId := CurrentUserId(ctx, s.users, s.sessions) 128 + s.users.UpdateUser(userId, func(user *store.UserState) { 157 129 nowMillis := gametime.NowMillis() 158 130 user.Profile.FavoriteCostumeId = req.FavoriteCostumeId 159 131 user.Profile.FavoriteCostumeIdUpdateDatetime = nowMillis 160 132 }) 161 - return &pb.SetUserFavoriteCostumeIdResponse{ 162 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})), 163 - }, nil 133 + return &pb.SetUserFavoriteCostumeIdResponse{}, nil 164 134 } 165 135 166 136 func (s *UserServiceServer) GetUserProfile(ctx context.Context, req *pb.GetUserProfileRequest) (*pb.GetUserProfileResponse, error) { 167 137 log.Printf("[UserService] GetUserProfile: playerId=%d", req.PlayerId) 168 138 userId := req.PlayerId 169 139 if userId == 0 { 170 - userId = currentUserId(ctx, s.users, s.sessions) 140 + userId = CurrentUserId(ctx, s.users, s.sessions) 171 141 } 172 142 user, err := s.users.LoadUser(userId) 173 143 if err != nil { 174 - return &pb.GetUserProfileResponse{DiffUserData: userdata.EmptyDiff()}, nil 144 + return &pb.GetUserProfileResponse{}, nil 175 145 } 176 146 177 147 deckCharacters := []*pb.ProfileDeckCharacter{} ··· 210 180 HistoryItem: []*pb.PlayHistoryItem{}, 211 181 HistoryCategoryGraphItem: []*pb.PlayHistoryCategoryGraphItem{}, 212 182 }, 213 - DiffUserData: userdata.EmptyDiff(), 214 183 }, nil 215 184 } 216 185 217 186 func (s *UserServiceServer) SetBirthYearMonth(ctx context.Context, req *pb.SetBirthYearMonthRequest) (*pb.SetBirthYearMonthResponse, error) { 218 187 log.Printf("[UserService] SetBirthYearMonth: %d/%d", req.BirthYear, req.BirthMonth) 219 - userId := currentUserId(ctx, s.users, s.sessions) 188 + userId := CurrentUserId(ctx, s.users, s.sessions) 220 189 _, _ = s.users.UpdateUser(userId, func(user *store.UserState) { 221 190 user.BirthYear = req.BirthYear 222 191 user.BirthMonth = req.BirthMonth 223 192 }) 224 - return &pb.SetBirthYearMonthResponse{DiffUserData: userdata.EmptyDiff()}, nil 193 + return &pb.SetBirthYearMonthResponse{}, nil 225 194 } 226 195 227 196 func (s *UserServiceServer) GetBirthYearMonth(ctx context.Context, _ *emptypb.Empty) (*pb.GetBirthYearMonthResponse, error) { 228 - userId := currentUserId(ctx, s.users, s.sessions) 197 + userId := CurrentUserId(ctx, s.users, s.sessions) 229 198 user, err := s.users.LoadUser(userId) 230 199 if err != nil { 231 - return &pb.GetBirthYearMonthResponse{BirthYear: 2000, BirthMonth: 1, DiffUserData: userdata.EmptyDiff()}, nil 200 + return &pb.GetBirthYearMonthResponse{BirthYear: 2000, BirthMonth: 1}, nil 232 201 } 233 - return &pb.GetBirthYearMonthResponse{BirthYear: user.BirthYear, BirthMonth: user.BirthMonth, DiffUserData: userdata.EmptyDiff()}, nil 202 + return &pb.GetBirthYearMonthResponse{BirthYear: user.BirthYear, BirthMonth: user.BirthMonth}, nil 234 203 } 235 204 236 205 func (s *UserServiceServer) GetChargeMoney(ctx context.Context, _ *emptypb.Empty) (*pb.GetChargeMoneyResponse, error) { 237 - userId := currentUserId(ctx, s.users, s.sessions) 206 + userId := CurrentUserId(ctx, s.users, s.sessions) 238 207 user, err := s.users.LoadUser(userId) 239 208 if err != nil { 240 - return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: 0, DiffUserData: userdata.EmptyDiff()}, nil 209 + return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: 0}, nil 241 210 } 242 - return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: user.ChargeMoneyThisMonth, DiffUserData: userdata.EmptyDiff()}, nil 211 + return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: user.ChargeMoneyThisMonth}, nil 243 212 } 244 213 245 214 func (s *UserServiceServer) SetUserSetting(ctx context.Context, req *pb.SetUserSettingRequest) (*pb.SetUserSettingResponse, error) { 246 215 log.Printf("[UserService] SetUserSetting: isNotifyPurchaseAlert=%v", req.IsNotifyPurchaseAlert) 247 - userId := currentUserId(ctx, s.users, s.sessions) 248 - user, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 216 + userId := CurrentUserId(ctx, s.users, s.sessions) 217 + s.users.UpdateUser(userId, func(user *store.UserState) { 249 218 user.Setting.IsNotifyPurchaseAlert = req.IsNotifyPurchaseAlert 250 219 }) 251 - return &pb.SetUserSettingResponse{ 252 - DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserSetting"})), 253 - }, nil 220 + return &pb.SetUserSettingResponse{}, nil 254 221 } 255 222 256 223 func (s *UserServiceServer) GetAndroidArgs(ctx context.Context, req *pb.GetAndroidArgsRequest) (*pb.GetAndroidArgsResponse, error) { 257 - return &pb.GetAndroidArgsResponse{Nonce: "Mama", ApiKey: "1234567890", DiffUserData: userdata.EmptyDiff()}, nil 224 + return &pb.GetAndroidArgsResponse{Nonce: "Mama", ApiKey: "1234567890"}, nil 258 225 } 259 226 260 227 func (s *UserServiceServer) GetBackupToken(ctx context.Context, req *pb.GetBackupTokenRequest) (*pb.GetBackupTokenResponse, error) { 261 - userId := currentUserId(ctx, s.users, s.sessions) 228 + userId := CurrentUserId(ctx, s.users, s.sessions) 262 229 user, err := s.users.LoadUser(userId) 263 230 if err != nil { 264 - return &pb.GetBackupTokenResponse{BackupToken: "mock-backup-token", DiffUserData: userdata.EmptyDiff()}, nil 231 + return &pb.GetBackupTokenResponse{BackupToken: "mock-backup-token"}, nil 265 232 } 266 - return &pb.GetBackupTokenResponse{BackupToken: user.BackupToken, DiffUserData: userdata.EmptyDiff()}, nil 233 + return &pb.GetBackupTokenResponse{BackupToken: user.BackupToken}, nil 267 234 } 268 235 269 236 func (s *UserServiceServer) CheckTransferSetting(ctx context.Context, _ *emptypb.Empty) (*pb.CheckTransferSettingResponse, error) { 270 - return &pb.CheckTransferSettingResponse{DiffUserData: userdata.EmptyDiff()}, nil 237 + return &pb.CheckTransferSettingResponse{}, nil 271 238 } 272 239 273 240 func (s *UserServiceServer) GetUserGamePlayNote(ctx context.Context, req *pb.GetUserGamePlayNoteRequest) (*pb.GetUserGamePlayNoteResponse, error) { 274 - return &pb.GetUserGamePlayNoteResponse{DiffUserData: userdata.EmptyDiff()}, nil 241 + return &pb.GetUserGamePlayNoteResponse{}, nil 242 + } 243 + 244 + func (s *UserServiceServer) resolveAuthToken(token string) (facebookId int64, err error) { 245 + if s.authURL == "" { 246 + return 0, status.Error(codes.FailedPrecondition, "auth server not configured (--auth-url)") 247 + } 248 + 249 + resp, err := http.Get(s.authURL + "/me?access_token=" + token) 250 + if err != nil { 251 + return 0, status.Errorf(codes.Internal, "auth server unreachable: %v", err) 252 + } 253 + defer resp.Body.Close() 254 + 255 + if resp.StatusCode != http.StatusOK { 256 + return 0, status.Error(codes.Unauthenticated, "invalid or expired token") 257 + } 258 + 259 + var body struct { 260 + ID string `json:"id"` 261 + Name string `json:"name"` 262 + } 263 + if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { 264 + return 0, status.Errorf(codes.Internal, "decode auth response: %v", err) 265 + } 266 + if body.ID == "" { 267 + return 0, status.Error(codes.Unauthenticated, "auth server returned empty id") 268 + } 269 + 270 + id, err := strconv.ParseInt(body.ID, 10, 64) 271 + if err != nil { 272 + return 0, status.Errorf(codes.Internal, "invalid auth id %q: %v", body.ID, err) 273 + } 274 + return id, nil 275 + } 276 + 277 + func (s *UserServiceServer) SetFacebookAccount(ctx context.Context, req *pb.SetFacebookAccountRequest) (*pb.SetFacebookAccountResponse, error) { 278 + log.Printf("[UserService] SetFacebookAccount") 279 + 280 + fbId, err := s.resolveAuthToken(req.Token) 281 + if err != nil { 282 + return nil, err 283 + } 284 + 285 + userId := CurrentUserId(ctx, s.users, s.sessions) 286 + if err := s.users.SetFacebookId(userId, fbId); err != nil { 287 + return nil, fmt.Errorf("set facebook id: %w", err) 288 + } 289 + log.Printf("[UserService] linked facebook_id=%d to user_id=%d", fbId, userId) 290 + return &pb.SetFacebookAccountResponse{}, nil 291 + } 292 + 293 + func (s *UserServiceServer) UnsetFacebookAccount(ctx context.Context, _ *emptypb.Empty) (*pb.UnsetFacebookAccountResponse, error) { 294 + log.Printf("[UserService] UnsetFacebookAccount") 295 + 296 + userId := CurrentUserId(ctx, s.users, s.sessions) 297 + if err := s.users.ClearFacebookId(userId); err != nil { 298 + return nil, fmt.Errorf("clear facebook id: %w", err) 299 + } 300 + log.Printf("[UserService] unlinked facebook from user_id=%d", userId) 301 + return &pb.UnsetFacebookAccountResponse{}, nil 302 + } 303 + 304 + func (s *UserServiceServer) TransferUserByFacebook(ctx context.Context, req *pb.TransferUserByFacebookRequest) (*pb.TransferUserByFacebookResponse, error) { 305 + log.Printf("[UserService] TransferUserByFacebook: uuid=%s", req.Uuid) 306 + 307 + fbId, err := s.resolveAuthToken(req.Token) 308 + if err != nil { 309 + return nil, err 310 + } 311 + 312 + userId, err := s.users.GetUserByFacebookId(fbId) 313 + if err != nil { 314 + return nil, status.Error(codes.NotFound, "no account linked to this login") 315 + } 316 + 317 + if err := s.users.UpdateUUID(userId, req.Uuid); err != nil { 318 + return nil, fmt.Errorf("update uuid: %w", err) 319 + } 320 + 321 + log.Printf("[UserService] transferred facebook_id=%d -> user_id=%d with new uuid=%s", fbId, userId, req.Uuid) 322 + 323 + return &pb.TransferUserByFacebookResponse{ 324 + UserId: userId, 325 + Signature: fmt.Sprintf("fb_transfer_%d_%d", userId, gametime.Now().Unix()), 326 + }, nil 275 327 }
+44 -133
server/internal/service/weapon.go
··· 11 11 "lunar-tear/server/internal/masterdata" 12 12 "lunar-tear/server/internal/model" 13 13 "lunar-tear/server/internal/store" 14 - "lunar-tear/server/internal/userdata" 15 14 ) 16 15 17 - var weaponDiffTables = []string{ 18 - "IUserWeapon", 19 - "IUserWeaponSkill", 20 - "IUserWeaponAbility", 21 - "IUserWeaponAwaken", 22 - "IUserMaterial", 23 - "IUserConsumableItem", 24 - } 25 - 26 - var limitBreakDiffTables = []string{ 27 - "IUserWeapon", 28 - "IUserWeaponSkill", 29 - "IUserWeaponAbility", 30 - "IUserWeaponAwaken", 31 - "IUserMaterial", 32 - "IUserConsumableItem", 33 - "IUserWeaponNote", 34 - } 35 - 36 - var weaponAwakenDiffTables = []string{ 37 - "IUserWeapon", 38 - "IUserWeaponAwaken", 39 - "IUserMaterial", 40 - "IUserConsumableItem", 41 - } 42 - 43 16 type WeaponServiceServer struct { 44 17 pb.UnimplementedWeaponServiceServer 45 18 users store.UserRepository ··· 55 28 func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectRequest) (*pb.ProtectResponse, error) { 56 29 log.Printf("[WeaponService] Protect: uuids=%v", req.UserWeaponUuid) 57 30 58 - userId := currentUserId(ctx, s.users, s.sessions) 31 + userId := CurrentUserId(ctx, s.users, s.sessions) 59 32 nowMillis := gametime.NowMillis() 60 33 61 - snapshot, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 34 + s.users.UpdateUser(userId, func(user *store.UserState) { 62 35 for _, uuid := range req.UserWeaponUuid { 63 36 weapon, ok := user.Weapons[uuid] 64 37 if !ok { ··· 71 44 } 72 45 }) 73 46 74 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"})) 75 - return &pb.ProtectResponse{DiffUserData: diff}, nil 47 + return &pb.ProtectResponse{}, nil 76 48 } 77 49 78 50 func (s *WeaponServiceServer) Unprotect(ctx context.Context, req *pb.UnprotectRequest) (*pb.UnprotectResponse, error) { 79 51 log.Printf("[WeaponService] Unprotect: uuids=%v", req.UserWeaponUuid) 80 52 81 - userId := currentUserId(ctx, s.users, s.sessions) 53 + userId := CurrentUserId(ctx, s.users, s.sessions) 82 54 nowMillis := gametime.NowMillis() 83 55 84 - snapshot, _ := s.users.UpdateUser(userId, func(user *store.UserState) { 56 + s.users.UpdateUser(userId, func(user *store.UserState) { 85 57 for _, uuid := range req.UserWeaponUuid { 86 58 weapon, ok := user.Weapons[uuid] 87 59 if !ok { ··· 94 66 } 95 67 }) 96 68 97 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"})) 98 - return &pb.UnprotectResponse{DiffUserData: diff}, nil 69 + return &pb.UnprotectResponse{}, nil 99 70 } 100 71 101 72 func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.EnhanceByMaterialRequest) (*pb.EnhanceByMaterialResponse, error) { 102 73 log.Printf("[WeaponService] EnhanceByMaterial: uuid=%s materials=%v", req.UserWeaponUuid, req.Materials) 103 74 104 - userId := currentUserId(ctx, s.users, s.sessions) 75 + userId := CurrentUserId(ctx, s.users, s.sessions) 105 76 nowMillis := gametime.NowMillis() 106 77 107 - var changedStoryIds []int32 108 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 78 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 109 79 weapon, ok := user.Weapons[req.UserWeaponUuid] 110 80 if !ok { 111 81 log.Printf("[WeaponService] EnhanceByMaterial: weapon uuid=%s not found", req.UserWeaponUuid) ··· 157 127 user.Weapons[req.UserWeaponUuid] = weapon 158 128 log.Printf("[WeaponService] EnhanceByMaterial: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level) 159 129 160 - changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis) 130 + s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis) 161 131 }) 162 132 if err != nil { 163 133 return nil, fmt.Errorf("weapon enhance by material: %w", err) 164 134 } 165 - 166 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables)) 167 - userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) 168 135 169 136 return &pb.EnhanceByMaterialResponse{ 170 137 IsGreatSuccess: false, 171 138 SurplusEnhanceMaterial: map[int32]int32{}, 172 - DiffUserData: diff, 173 139 }, nil 174 140 } 175 141 176 142 func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*pb.SellResponse, error) { 177 143 log.Printf("[WeaponService] Sell: uuids=%v", req.UserWeaponUuid) 178 144 179 - userId := currentUserId(ctx, s.users, s.sessions) 180 - 181 - oldUser, _ := s.users.LoadUser(userId) 182 - tracker := userdata.NewDeleteTracker(). 183 - Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}). 184 - Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). 185 - Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). 186 - Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"}) 145 + userId := CurrentUserId(ctx, s.users, s.sessions) 187 146 188 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 147 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 189 148 totalGold := int32(0) 190 149 for _, uuid := range req.UserWeaponUuid { 191 150 weapon, ok := user.Weapons[uuid] ··· 225 184 return nil, fmt.Errorf("weapon sell: %w", err) 226 185 } 227 186 228 - sellDiffTables := []string{"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeaponAwaken", "IUserConsumableItem"} 229 - tables := userdata.ProjectTables(snapshot, sellDiffTables) 230 - diff := tracker.Apply(snapshot, tables) 231 - 232 - return &pb.SellResponse{DiffUserData: diff}, nil 187 + return &pb.SellResponse{}, nil 233 188 } 234 189 235 190 func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest) (*pb.EvolveResponse, error) { 236 191 log.Printf("[WeaponService] Evolve: uuid=%s", req.UserWeaponUuid) 237 192 238 - userId := currentUserId(ctx, s.users, s.sessions) 193 + userId := CurrentUserId(ctx, s.users, s.sessions) 239 194 nowMillis := gametime.NowMillis() 240 195 241 - var changedStoryIds []int32 242 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 196 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 243 197 weapon, ok := user.Weapons[req.UserWeaponUuid] 244 198 if !ok { 245 199 log.Printf("[WeaponService] Evolve: weapon uuid=%s not found", req.UserWeaponUuid) ··· 298 252 299 253 log.Printf("[WeaponService] Evolve: weaponId %d -> %d", wm.WeaponId, evolvedId) 300 254 301 - changedStoryIds = s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis) 255 + s.checkWeaponStoryUnlocks(user, evolvedId, weapon.Level, nowMillis) 302 256 }) 303 257 if err != nil { 304 258 return nil, fmt.Errorf("weapon evolve: %w", err) 305 259 } 306 260 307 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables)) 308 - userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) 309 - 310 - return &pb.EvolveResponse{DiffUserData: diff}, nil 261 + return &pb.EvolveResponse{}, nil 311 262 } 312 263 313 264 func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceSkillRequest) (*pb.EnhanceSkillResponse, error) { 314 265 log.Printf("[WeaponService] EnhanceSkill: uuid=%s skillId=%d addLevel=%d", req.UserWeaponUuid, req.SkillId, req.AddLevelCount) 315 266 316 - userId := currentUserId(ctx, s.users, s.sessions) 267 + userId := CurrentUserId(ctx, s.users, s.sessions) 317 268 nowMillis := gametime.NowMillis() 318 269 319 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 270 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 320 271 weapon, ok := user.Weapons[req.UserWeaponUuid] 321 272 if !ok { 322 273 log.Printf("[WeaponService] EnhanceSkill: weapon uuid=%s not found", req.UserWeaponUuid) ··· 330 281 } 331 282 332 283 groupRows := s.catalog.SkillGroupsByGroupId[wm.WeaponSkillGroupId] 333 - var skillGroup *masterdata.WeaponSkillGroupRow 284 + var skillGroup *masterdata.EntityMWeaponSkillGroup 334 285 for i := range groupRows { 335 286 if groupRows[i].SkillId == req.SkillId { 336 287 skillGroup = &groupRows[i] ··· 403 354 return nil, fmt.Errorf("weapon enhance skill: %w", err) 404 355 } 405 356 406 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables)) 407 - 408 - return &pb.EnhanceSkillResponse{DiffUserData: diff}, nil 357 + return &pb.EnhanceSkillResponse{}, nil 409 358 } 410 359 411 360 func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.EnhanceAbilityRequest) (*pb.EnhanceAbilityResponse, error) { 412 361 log.Printf("[WeaponService] EnhanceAbility: uuid=%s abilityId=%d addLevel=%d", req.UserWeaponUuid, req.AbilityId, req.AddLevelCount) 413 362 414 - userId := currentUserId(ctx, s.users, s.sessions) 363 + userId := CurrentUserId(ctx, s.users, s.sessions) 415 364 nowMillis := gametime.NowMillis() 416 365 417 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 366 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 418 367 weapon, ok := user.Weapons[req.UserWeaponUuid] 419 368 if !ok { 420 369 log.Printf("[WeaponService] EnhanceAbility: weapon uuid=%s not found", req.UserWeaponUuid) ··· 428 377 } 429 378 430 379 groupRows := s.catalog.AbilityGroupsByGroupId[wm.WeaponAbilityGroupId] 431 - var abilityGroup *masterdata.WeaponAbilityGroupRow 380 + var abilityGroup *masterdata.EntityMWeaponAbilityGroup 432 381 for i := range groupRows { 433 382 if groupRows[i].AbilityId == req.AbilityId { 434 383 abilityGroup = &groupRows[i] ··· 501 450 return nil, fmt.Errorf("weapon enhance ability: %w", err) 502 451 } 503 452 504 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables)) 505 - 506 - return &pb.EnhanceAbilityResponse{DiffUserData: diff}, nil 453 + return &pb.EnhanceAbilityResponse{}, nil 507 454 } 508 455 509 456 func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.LimitBreakByMaterialRequest) (*pb.LimitBreakByMaterialResponse, error) { 510 457 log.Printf("[WeaponService] LimitBreakByMaterial: uuid=%s materials=%v", req.UserWeaponUuid, req.Materials) 511 458 512 - userId := currentUserId(ctx, s.users, s.sessions) 459 + userId := CurrentUserId(ctx, s.users, s.sessions) 513 460 nowMillis := gametime.NowMillis() 514 461 515 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 462 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 516 463 weapon, ok := user.Weapons[req.UserWeaponUuid] 517 464 if !ok { 518 465 log.Printf("[WeaponService] LimitBreakByMaterial: weapon uuid=%s not found", req.UserWeaponUuid) ··· 572 519 return nil, fmt.Errorf("weapon limit break by material: %w", err) 573 520 } 574 521 575 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, limitBreakDiffTables)) 576 - 577 - return &pb.LimitBreakByMaterialResponse{DiffUserData: diff}, nil 522 + return &pb.LimitBreakByMaterialResponse{}, nil 578 523 } 579 524 580 525 func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.LimitBreakByWeaponRequest) (*pb.LimitBreakByWeaponResponse, error) { 581 526 log.Printf("[WeaponService] LimitBreakByWeapon: uuid=%s materialUuids=%v", req.UserWeaponUuid, req.MaterialUserWeaponUuids) 582 527 583 - userId := currentUserId(ctx, s.users, s.sessions) 528 + userId := CurrentUserId(ctx, s.users, s.sessions) 584 529 nowMillis := gametime.NowMillis() 585 530 586 - oldUser, _ := s.users.LoadUser(userId) 587 - tracker := userdata.NewDeleteTracker(). 588 - Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}). 589 - Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). 590 - Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). 591 - Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"}) 592 - 593 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 531 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 594 532 weapon, ok := user.Weapons[req.UserWeaponUuid] 595 533 if !ok { 596 534 log.Printf("[WeaponService] LimitBreakByWeapon: weapon uuid=%s not found", req.UserWeaponUuid) ··· 658 596 return nil, fmt.Errorf("weapon limit break by weapon: %w", err) 659 597 } 660 598 661 - tables := userdata.ProjectTables(snapshot, limitBreakDiffTables) 662 - diff := tracker.Apply(snapshot, tables) 663 - 664 - return &pb.LimitBreakByWeaponResponse{DiffUserData: diff}, nil 599 + return &pb.LimitBreakByWeaponResponse{}, nil 665 600 } 666 601 667 602 func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.EnhanceByWeaponRequest) (*pb.EnhanceByWeaponResponse, error) { 668 603 log.Printf("[WeaponService] EnhanceByWeapon: uuid=%s materialUuids=%v", req.UserWeaponUuid, req.MaterialUserWeaponUuids) 669 604 670 - userId := currentUserId(ctx, s.users, s.sessions) 605 + userId := CurrentUserId(ctx, s.users, s.sessions) 671 606 nowMillis := gametime.NowMillis() 672 607 673 - oldUser, _ := s.users.LoadUser(userId) 674 - tracker := userdata.NewDeleteTracker(). 675 - Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}). 676 - Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). 677 - Track("IUserWeaponAbility", oldUser, userdata.SortedWeaponAbilityRecords, []string{"userId", "userWeaponUuid", "slotNumber"}). 678 - Track("IUserWeaponAwaken", oldUser, userdata.SortedWeaponAwakenRecords, []string{"userId", "userWeaponUuid"}) 679 - 680 - var changedStoryIds []int32 681 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 608 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 682 609 weapon, ok := user.Weapons[req.UserWeaponUuid] 683 610 if !ok { 684 611 log.Printf("[WeaponService] EnhanceByWeapon: weapon uuid=%s not found", req.UserWeaponUuid) ··· 740 667 user.Weapons[req.UserWeaponUuid] = weapon 741 668 log.Printf("[WeaponService] EnhanceByWeapon: weaponId=%d +%d exp -> total=%d level=%d", weapon.WeaponId, totalExp, weapon.Exp, weapon.Level) 742 669 743 - changedStoryIds = s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis) 670 + s.checkWeaponStoryUnlocks(user, weapon.WeaponId, weapon.Level, nowMillis) 744 671 }) 745 672 if err != nil { 746 673 return nil, fmt.Errorf("weapon enhance by weapon: %w", err) 747 674 } 748 675 749 - tables := userdata.ProjectTables(snapshot, weaponDiffTables) 750 - diff := tracker.Apply(snapshot, tables) 751 - userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds) 752 - 753 676 return &pb.EnhanceByWeaponResponse{ 754 677 IsGreatSuccess: false, 755 678 SurplusEnhanceWeapon: []string{}, 756 - DiffUserData: diff, 757 679 }, nil 758 680 } 759 681 760 - func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) []int32 { 682 + func (s *WeaponServiceServer) checkWeaponStoryUnlocks(user *store.UserState, weaponId, level int32, nowMillis int64) { 761 683 wm, ok := s.catalog.Weapons[weaponId] 762 684 if !ok || wm.WeaponStoryReleaseConditionGroupId == 0 { 763 - return nil 685 + return 764 686 } 765 687 evoOrder, hasEvo := s.catalog.EvolutionOrder[weaponId] 766 688 conditions := s.catalog.ReleaseConditionsByGroupId[wm.WeaponStoryReleaseConditionGroupId] 767 689 768 - changed := false 769 690 for _, cond := range conditions { 770 - granted := false 771 - switch cond.WeaponStoryReleaseConditionType { 691 + switch model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) { 772 692 case model.WeaponStoryReleaseConditionTypeAcquisition: 773 - granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 693 + store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 774 694 case model.WeaponStoryReleaseConditionTypeReachSpecifiedLevel: 775 695 if level >= cond.ConditionValue { 776 - granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 696 + store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 777 697 } 778 698 case model.WeaponStoryReleaseConditionTypeReachInitialMaxLevel: 779 699 if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok { 780 700 if level >= maxFunc.Evaluate(0) { 781 - granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 701 + store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 782 702 } 783 703 } 784 704 case model.WeaponStoryReleaseConditionTypeReachOnceEvolvedMaxLevel: 785 705 if hasEvo && evoOrder >= 1 { 786 706 if maxFunc, ok := s.catalog.MaxLevelByEnhanceId[wm.WeaponSpecificEnhanceId]; ok { 787 707 if level >= maxFunc.Evaluate(0) { 788 - granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 708 + store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 789 709 } 790 710 } 791 711 } 792 712 case model.WeaponStoryReleaseConditionTypeReachSpecifiedEvolutionCount: 793 713 if hasEvo && evoOrder >= cond.ConditionValue { 794 - granted = store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 714 + store.GrantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) 795 715 } 796 716 } 797 - if granted { 798 - changed = true 799 - } 800 717 } 801 - if changed { 802 - return []int32{weaponId} 803 - } 804 - return nil 805 718 } 806 719 807 720 func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRequest) (*pb.WeaponAwakenResponse, error) { 808 721 log.Printf("[WeaponService] Awaken: uuid=%s", req.UserWeaponUuid) 809 722 810 - userId := currentUserId(ctx, s.users, s.sessions) 723 + userId := CurrentUserId(ctx, s.users, s.sessions) 811 724 nowMillis := gametime.NowMillis() 812 725 813 - snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) { 726 + _, err := s.users.UpdateUser(userId, func(user *store.UserState) { 814 727 weapon, ok := user.Weapons[req.UserWeaponUuid] 815 728 if !ok { 816 729 log.Printf("[WeaponService] Awaken: weapon uuid=%s not found", req.UserWeaponUuid) ··· 857 770 return nil, fmt.Errorf("weapon awaken: %w", err) 858 771 } 859 772 860 - diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponAwakenDiffTables)) 861 - 862 - return &pb.WeaponAwakenResponse{DiffUserData: diff}, nil 773 + return &pb.WeaponAwakenResponse{}, nil 863 774 }
+3 -3
server/internal/store/seed.go
··· 16 16 defaultChargeMoneyThisMonth = int64(0) 17 17 ) 18 18 19 - func SeedUserState(userId int64, uuid string, nowMillis int64) *UserState { 19 + func SeedUserState(userId int64, uuid string, nowMillis int64, platform model.ClientPlatform) *UserState { 20 20 user := &UserState{ 21 21 UserId: userId, 22 22 Uuid: uuid, 23 23 PlayerId: userId, 24 - OsType: 2, 25 - PlatformType: 2, 24 + OsType: platform.OsType, 25 + PlatformType: platform.PlatformType, 26 26 UserRestrictionType: 0, 27 27 RegisterDatetime: nowMillis, 28 28 GameStartDatetime: nowMillis,
+4 -2
server/internal/store/sqlite/load.go
··· 10 10 11 11 func (s *SQLiteStore) LoadUser(userId int64) (store.UserState, error) { 12 12 var u store.UserState 13 + var fbId sql.NullInt64 13 14 14 15 err := s.db.QueryRow(`SELECT user_id, uuid, player_id, os_type, platform_type, user_restriction_type, 15 16 register_datetime, game_start_datetime, latest_version, birth_year, birth_month, 16 - backup_token, charge_money_this_month FROM users WHERE user_id = ?`, userId).Scan( 17 + backup_token, charge_money_this_month, facebook_id FROM users WHERE user_id = ?`, userId).Scan( 17 18 &u.UserId, &u.Uuid, &u.PlayerId, &u.OsType, &u.PlatformType, &u.UserRestrictionType, 18 19 &u.RegisterDatetime, &u.GameStartDatetime, &u.LatestVersion, &u.BirthYear, &u.BirthMonth, 19 - &u.BackupToken, &u.ChargeMoneyThisMonth) 20 + &u.BackupToken, &u.ChargeMoneyThisMonth, &fbId) 20 21 if err == sql.ErrNoRows { 21 22 return u, store.ErrNotFound 22 23 } 23 24 if err != nil { 24 25 return u, fmt.Errorf("load users: %w", err) 25 26 } 27 + u.FacebookId = fbId.Int64 26 28 27 29 initMaps(&u) 28 30
+51 -4
server/internal/store/sqlite/user.go
··· 4 4 "database/sql" 5 5 "fmt" 6 6 7 + "lunar-tear/server/internal/model" 7 8 "lunar-tear/server/internal/store" 8 9 ) 9 10 10 - func (s *SQLiteStore) CreateUser(uuid string) (int64, error) { 11 + func (s *SQLiteStore) CreateUser(uuid string, platform model.ClientPlatform) (int64, error) { 11 12 tx, err := s.db.Begin() 12 13 if err != nil { 13 14 return 0, fmt.Errorf("begin tx: %w", err) ··· 24 25 25 26 res, err := tx.Exec(`INSERT INTO users (uuid, player_id, os_type, platform_type, user_restriction_type, 26 27 register_datetime, game_start_datetime, latest_version, birth_year, birth_month, 27 - backup_token, charge_money_this_month) VALUES (?, 0, 2, 2, 0, ?, ?, 0, 2000, 1, 'mock-backup-token', 0)`, 28 - uuid, nowMillis, nowMillis) 28 + backup_token, charge_money_this_month) VALUES (?, 0, ?, ?, 0, ?, ?, 0, 2000, 1, 'mock-backup-token', 0)`, 29 + uuid, platform.OsType, platform.PlatformType, nowMillis, nowMillis) 29 30 if err != nil { 30 31 return 0, fmt.Errorf("insert user: %w", err) 31 32 } ··· 39 40 return 0, fmt.Errorf("update player_id: %w", err) 40 41 } 41 42 42 - user := store.SeedUserState(userId, uuid, nowMillis) 43 + user := store.SeedUserState(userId, uuid, nowMillis, platform) 43 44 if err := writeUserState(tx, userId, user); err != nil { 44 45 return 0, fmt.Errorf("write seed state: %w", err) 45 46 } ··· 187 188 188 189 if err := tx.Commit(); err != nil { 189 190 return fmt.Errorf("commit: %w", err) 191 + } 192 + 193 + return nil 194 + } 195 + 196 + func (s *SQLiteStore) SetFacebookId(userId int64, facebookId int64) error { 197 + _, err := s.db.Exec(`UPDATE users SET facebook_id = ? WHERE user_id = ?`, facebookId, userId) 198 + if err != nil { 199 + return fmt.Errorf("set facebook_id: %w", err) 200 + } 201 + return nil 202 + } 203 + 204 + func (s *SQLiteStore) GetUserByFacebookId(facebookId int64) (int64, error) { 205 + var userId int64 206 + err := s.db.QueryRow(`SELECT user_id FROM users WHERE facebook_id = ?`, facebookId).Scan(&userId) 207 + if err == sql.ErrNoRows { 208 + return 0, store.ErrNotFound 209 + } 210 + if err != nil { 211 + return 0, fmt.Errorf("query user by facebook_id: %w", err) 212 + } 213 + return userId, nil 214 + } 215 + 216 + func (s *SQLiteStore) GetFacebookId(userId int64) (int64, error) { 217 + var fbId sql.NullInt64 218 + err := s.db.QueryRow(`SELECT facebook_id FROM users WHERE user_id = ?`, userId).Scan(&fbId) 219 + if err != nil { 220 + return 0, store.ErrNotFound 221 + } 222 + return fbId.Int64, nil 223 + } 224 + 225 + func (s *SQLiteStore) ClearFacebookId(userId int64) error { 226 + _, err := s.db.Exec(`UPDATE users SET facebook_id = NULL WHERE user_id = ?`, userId) 227 + if err != nil { 228 + return fmt.Errorf("clear facebook_id: %w", err) 229 + } 230 + return nil 231 + } 232 + 233 + func (s *SQLiteStore) UpdateUUID(userId int64, newUuid string) error { 234 + _, err := s.db.Exec(`UPDATE users SET uuid = ? WHERE user_id = ?`, newUuid, userId) 235 + if err != nil { 236 + return fmt.Errorf("update uuid: %w", err) 190 237 } 191 238 return nil 192 239 }
+8 -1
server/internal/store/store.go
··· 3 3 import ( 4 4 "errors" 5 5 "time" 6 + 7 + "lunar-tear/server/internal/model" 6 8 ) 7 9 8 10 var ErrNotFound = errors.New("store: not found") ··· 10 12 type Clock func() time.Time 11 13 12 14 type UserRepository interface { 13 - CreateUser(uuid string) (int64, error) 15 + CreateUser(uuid string, platform model.ClientPlatform) (int64, error) 14 16 GetUserByUUID(uuid string) (int64, error) 15 17 LoadUser(userId int64) (UserState, error) 16 18 UpdateUser(userId int64, mutate func(*UserState)) (UserState, error) 17 19 DefaultUserId() (int64, error) 20 + SetFacebookId(userId int64, facebookId int64) error 21 + GetUserByFacebookId(facebookId int64) (int64, error) 22 + GetFacebookId(userId int64) (int64, error) 23 + ClearFacebookId(userId int64) error 24 + UpdateUUID(userId int64, newUuid string) error 18 25 } 19 26 20 27 type SessionRepository interface {
+1
server/internal/store/types.go
··· 31 31 BirthMonth int32 32 32 BackupToken string 33 33 ChargeMoneyThisMonth int64 34 + FacebookId int64 34 35 35 36 Setting UserSettingState 36 37 Status UserStatusState
+450
server/internal/userdata/changed_tables.go
··· 1 + package userdata 2 + 3 + import ( 4 + "encoding/json" 5 + "maps" 6 + "slices" 7 + "sort" 8 + "strings" 9 + 10 + pb "lunar-tear/server/gen/proto" 11 + "lunar-tear/server/internal/store" 12 + ) 13 + 14 + func mapsEqualSimple[K comparable, V comparable](a, b map[K]V) bool { 15 + if len(a) != len(b) { 16 + return false 17 + } 18 + for k, va := range a { 19 + if vb, ok := b[k]; !ok || va != vb { 20 + return false 21 + } 22 + } 23 + return true 24 + } 25 + 26 + func mapsEqualStruct[K comparable, V comparable](a, b map[K]V) bool { 27 + return mapsEqualSimple(a, b) 28 + } 29 + 30 + func mapsEqualSliceValues[K comparable, V comparable](a, b map[K][]V) bool { 31 + if len(a) != len(b) { 32 + return false 33 + } 34 + for k, va := range a { 35 + vb, ok := b[k] 36 + if !ok || !slices.Equal(va, vb) { 37 + return false 38 + } 39 + } 40 + return true 41 + } 42 + 43 + func gimmickStateEqual(a, b store.GimmickState) bool { 44 + return mapsEqualStruct(a.Progress, b.Progress) && 45 + mapsEqualStruct(a.OrnamentProgress, b.OrnamentProgress) && 46 + mapsEqualStruct(a.Sequences, b.Sequences) && 47 + mapsEqualStruct(a.Unlocks, b.Unlocks) 48 + } 49 + 50 + func ChangedTables(before, after *store.UserState) []string { 51 + var changed []string 52 + add := func(name string) { changed = append(changed, name) } 53 + 54 + if before.UserId != after.UserId || before.PlayerId != after.PlayerId || 55 + before.OsType != after.OsType || before.PlatformType != after.PlatformType || 56 + before.UserRestrictionType != after.UserRestrictionType || 57 + before.RegisterDatetime != after.RegisterDatetime || 58 + before.GameStartDatetime != after.GameStartDatetime || 59 + before.LatestVersion != after.LatestVersion { 60 + add("IUser") 61 + } 62 + if before.Setting != after.Setting { 63 + add("IUserSetting") 64 + } 65 + if before.Status != after.Status { 66 + add("IUserStatus") 67 + } 68 + if before.Gem != after.Gem { 69 + add("IUserGem") 70 + } 71 + if before.Profile != after.Profile { 72 + add("IUserProfile") 73 + } 74 + if before.Login != after.Login { 75 + add("IUserLogin") 76 + } 77 + if before.LoginBonus != after.LoginBonus { 78 + add("IUserLoginBonus") 79 + } 80 + if before.PortalCageStatus != after.PortalCageStatus { 81 + add("IUserPortalCageStatus") 82 + } 83 + if before.GuerrillaFreeOpen != after.GuerrillaFreeOpen { 84 + add("IUserEventQuestGuerrillaFreeOpen") 85 + } 86 + if before.ShopReplaceable != after.ShopReplaceable { 87 + add("IUserShopReplaceable") 88 + } 89 + if before.Explore != after.Explore { 90 + add("IUserExplore") 91 + } 92 + if before.BigHuntProgress != after.BigHuntProgress { 93 + add("IUserBigHuntProgressStatus") 94 + } 95 + if before.FacebookId != after.FacebookId { 96 + add("IUserFacebook") 97 + } 98 + 99 + if before.MainQuest != after.MainQuest { 100 + add("IUserMainQuestFlowStatus") 101 + add("IUserMainQuestMainFlowStatus") 102 + add("IUserMainQuestProgressStatus") 103 + add("IUserMainQuestSeasonRoute") 104 + add("IUserMainQuestReplayFlowStatus") 105 + } 106 + if before.EventQuest != after.EventQuest { 107 + add("IUserEventQuestProgressStatus") 108 + } 109 + if before.ExtraQuest != after.ExtraQuest { 110 + add("IUserExtraQuestProgressStatus") 111 + } 112 + if before.SideStoryActiveProgress != after.SideStoryActiveProgress { 113 + add("IUserSideStoryQuestSceneProgressStatus") 114 + } 115 + 116 + if !mapsEqualStruct(before.Tutorials, after.Tutorials) { 117 + add("IUserTutorialProgress") 118 + } 119 + if !mapsEqualStruct(before.Missions, after.Missions) { 120 + add("IUserMission") 121 + } 122 + if !mapsEqualStruct(before.Characters, after.Characters) { 123 + add("IUserCharacter") 124 + } 125 + if !mapsEqualStruct(before.Costumes, after.Costumes) { 126 + add("IUserCostume") 127 + } 128 + if !mapsEqualStruct(before.Weapons, after.Weapons) { 129 + add("IUserWeapon") 130 + } 131 + if !mapsEqualStruct(before.WeaponStories, after.WeaponStories) { 132 + add("IUserWeaponStory") 133 + } 134 + if !mapsEqualStruct(before.WeaponNotes, after.WeaponNotes) { 135 + add("IUserWeaponNote") 136 + } 137 + if !mapsEqualStruct(before.Companions, after.Companions) { 138 + add("IUserCompanion") 139 + } 140 + if !mapsEqualStruct(before.Thoughts, after.Thoughts) { 141 + add("IUserThought") 142 + } 143 + if !mapsEqualSimple(before.ConsumableItems, after.ConsumableItems) { 144 + add("IUserConsumableItem") 145 + } 146 + if !mapsEqualSimple(before.Materials, after.Materials) { 147 + add("IUserMaterial") 148 + } 149 + if !mapsEqualSimple(before.ImportantItems, after.ImportantItems) { 150 + add("IUserImportantItem") 151 + } 152 + if !mapsEqualSimple(before.PremiumItems, after.PremiumItems) { 153 + add("IUserPremiumItem") 154 + } 155 + if !mapsEqualStruct(before.Parts, after.Parts) { 156 + add("IUserParts") 157 + } 158 + if !mapsEqualStruct(before.PartsGroupNotes, after.PartsGroupNotes) { 159 + add("IUserPartsGroupNote") 160 + } 161 + if !mapsEqualStruct(before.PartsPresets, after.PartsPresets) { 162 + add("IUserPartsPreset") 163 + } 164 + if !mapsEqualStruct(before.CostumeActiveSkills, after.CostumeActiveSkills) { 165 + add("IUserCostumeActiveSkill") 166 + } 167 + if !mapsEqualSliceValues(before.WeaponSkills, after.WeaponSkills) { 168 + add("IUserWeaponSkill") 169 + } 170 + if !mapsEqualSliceValues(before.WeaponAbilities, after.WeaponAbilities) { 171 + add("IUserWeaponAbility") 172 + } 173 + if !mapsEqualStruct(before.WeaponAwakens, after.WeaponAwakens) { 174 + add("IUserWeaponAwaken") 175 + } 176 + if !mapsEqualStruct(before.DeckTypeNotes, after.DeckTypeNotes) { 177 + add("IUserDeckTypeNote") 178 + } 179 + if !mapsEqualStruct(before.DeckCharacters, after.DeckCharacters) { 180 + add("IUserDeckCharacter") 181 + add("IUserDeckCharacterDressupCostume") 182 + } 183 + if !mapsEqualStruct(before.Decks, after.Decks) { 184 + add("IUserDeck") 185 + } 186 + if !mapsEqualSliceValues(before.DeckSubWeapons, after.DeckSubWeapons) { 187 + add("IUserDeckSubWeaponGroup") 188 + } 189 + if !mapsEqualSliceValues(before.DeckParts, after.DeckParts) { 190 + add("IUserDeckPartsGroup") 191 + } 192 + if !mapsEqualStruct(before.Quests, after.Quests) { 193 + add("IUserQuest") 194 + } 195 + if !mapsEqualStruct(before.QuestMissions, after.QuestMissions) { 196 + add("IUserQuestMission") 197 + } 198 + if !mapsEqualStruct(before.SideStoryQuests, after.SideStoryQuests) { 199 + add("IUserSideStoryQuest") 200 + } 201 + if !mapsEqualStruct(before.QuestLimitContentStatus, after.QuestLimitContentStatus) { 202 + add("IUserQuestLimitContentStatus") 203 + } 204 + if !mapsEqualSimple(before.NaviCutInPlayed, after.NaviCutInPlayed) { 205 + add("IUserNaviCutIn") 206 + } 207 + if !mapsEqualSimple(before.ViewedMovies, after.ViewedMovies) { 208 + add("IUserMovie") 209 + } 210 + if !mapsEqualSimple(before.ContentsStories, after.ContentsStories) { 211 + add("IUserContentsStory") 212 + } 213 + if !mapsEqualSimple(before.DrawnOmikuji, after.DrawnOmikuji) { 214 + add("IUserOmikuji") 215 + } 216 + if !mapsEqualSimple(before.DokanConfirmed, after.DokanConfirmed) { 217 + add("IUserDokan") 218 + } 219 + if !mapsEqualStruct(before.ShopItems, after.ShopItems) { 220 + add("IUserShopItem") 221 + } 222 + if !mapsEqualStruct(before.ShopReplaceableLineup, after.ShopReplaceableLineup) { 223 + add("IUserShopReplaceableLineup") 224 + } 225 + if !mapsEqualStruct(before.ExploreScores, after.ExploreScores) { 226 + add("IUserExploreScore") 227 + } 228 + if !mapsEqualStruct(before.CharacterBoards, after.CharacterBoards) { 229 + add("IUserCharacterBoard") 230 + } 231 + if !mapsEqualStruct(before.CharacterBoardAbilities, after.CharacterBoardAbilities) { 232 + add("IUserCharacterBoardAbility") 233 + } 234 + if !mapsEqualStruct(before.CharacterBoardStatusUps, after.CharacterBoardStatusUps) { 235 + add("IUserCharacterBoardStatusUp") 236 + } 237 + if !mapsEqualStruct(before.CostumeAwakenStatusUps, after.CostumeAwakenStatusUps) { 238 + add("IUserCostumeAwakenStatusUp") 239 + } 240 + if !mapsEqualStruct(before.CostumeLotteryEffects, after.CostumeLotteryEffects) { 241 + add("IUserCostumeLotteryEffect") 242 + } 243 + if !mapsEqualStruct(before.CostumeLotteryEffectPending, after.CostumeLotteryEffectPending) { 244 + add("IUserCostumeLotteryEffectPending") 245 + } 246 + if !mapsEqualStruct(before.AutoSaleSettings, after.AutoSaleSettings) { 247 + add("IUserAutoSaleSettingDetail") 248 + } 249 + if !mapsEqualStruct(before.CharacterRebirths, after.CharacterRebirths) { 250 + add("IUserCharacterRebirth") 251 + } 252 + if !mapsEqualStruct(before.CageOrnamentRewards, after.CageOrnamentRewards) { 253 + add("IUserCageOrnamentReward") 254 + } 255 + 256 + if !mapsEqualStruct(before.BigHuntMaxScores, after.BigHuntMaxScores) { 257 + add("IUserBigHuntMaxScore") 258 + } 259 + if !mapsEqualStruct(before.BigHuntStatuses, after.BigHuntStatuses) { 260 + add("IUserBigHuntStatus") 261 + } 262 + if !mapsEqualStruct(before.BigHuntScheduleMaxScores, after.BigHuntScheduleMaxScores) { 263 + add("IUserBigHuntScheduleMaxScore") 264 + } 265 + if !mapsEqualStruct(before.BigHuntWeeklyMaxScores, after.BigHuntWeeklyMaxScores) { 266 + add("IUserBigHuntWeeklyMaxScore") 267 + } 268 + if !mapsEqualStruct(before.BigHuntWeeklyStatuses, after.BigHuntWeeklyStatuses) { 269 + add("IUserBigHuntWeeklyStatus") 270 + } 271 + 272 + if !gimmickStateEqual(before.Gimmick, after.Gimmick) { 273 + if !mapsEqualStruct(before.Gimmick.Progress, after.Gimmick.Progress) { 274 + add("IUserGimmick") 275 + } 276 + if !mapsEqualStruct(before.Gimmick.OrnamentProgress, after.Gimmick.OrnamentProgress) { 277 + add("IUserGimmickOrnamentProgress") 278 + } 279 + if !mapsEqualStruct(before.Gimmick.Sequences, after.Gimmick.Sequences) { 280 + add("IUserGimmickSequence") 281 + } 282 + if !mapsEqualStruct(before.Gimmick.Unlocks, after.Gimmick.Unlocks) { 283 + add("IUserGimmickUnlock") 284 + } 285 + } 286 + 287 + return changed 288 + } 289 + 290 + func ComputeDelta(before, after *store.UserState, changedTables []string) map[string]*pb.DiffData { 291 + diff := make(map[string]*pb.DiffData, len(changedTables)) 292 + for _, table := range changedTables { 293 + afterJSON := projectTable(table, *after) 294 + deleteKeys := "[]" 295 + if kf := keyFieldsForTable(table); len(kf) > 0 { 296 + beforeJSON := projectTable(table, *before) 297 + deleteKeys = ComputeDeleteKeys( 298 + parseJSONRecords(beforeJSON), 299 + parseJSONRecords(afterJSON), 300 + kf, 301 + ) 302 + } 303 + diff[table] = &pb.DiffData{ 304 + UpdateRecordsJson: afterJSON, 305 + DeleteKeysJson: deleteKeys, 306 + } 307 + } 308 + return diff 309 + } 310 + 311 + func AllTableNames() []string { 312 + return slices.Sorted(maps.Keys(projectors)) 313 + } 314 + 315 + func SortedChangedNames(tables []string) string { 316 + sorted := make([]string, len(tables)) 317 + copy(sorted, tables) 318 + sort.Strings(sorted) 319 + return strings.Join(sorted, ",") 320 + } 321 + 322 + func parseJSONRecords(jsonStr string) []map[string]any { 323 + if jsonStr == "" || jsonStr == "[]" { 324 + return nil 325 + } 326 + var records []map[string]any 327 + if err := json.Unmarshal([]byte(jsonStr), &records); err != nil { 328 + return nil 329 + } 330 + return records 331 + } 332 + 333 + func keyFieldsForTable(table string) []string { 334 + switch table { 335 + case "IUserWeapon": 336 + return []string{"userId", "userWeaponUuid"} 337 + case "IUserWeaponSkill": 338 + return []string{"userId", "userWeaponUuid", "slotNumber"} 339 + case "IUserWeaponAbility": 340 + return []string{"userId", "userWeaponUuid", "slotNumber"} 341 + case "IUserWeaponAwaken": 342 + return []string{"userId", "userWeaponUuid"} 343 + case "IUserCostume": 344 + return []string{"userId", "userCostumeUuid"} 345 + case "IUserCompanion": 346 + return []string{"userId", "userCompanionUuid"} 347 + case "IUserThought": 348 + return []string{"userId", "userThoughtUuid"} 349 + case "IUserParts": 350 + return []string{"userId", "userPartsUuid"} 351 + case "IUserDeckCharacter": 352 + return []string{"userId", "userDeckCharacterUuid"} 353 + case "IUserDeck": 354 + return []string{"userId", "deckType", "userDeckNumber"} 355 + case "IUserDeckSubWeaponGroup": 356 + return []string{"userId", "userDeckCharacterUuid", "sortOrder"} 357 + case "IUserDeckPartsGroup": 358 + return []string{"userId", "userDeckCharacterUuid", "sortOrder"} 359 + case "IUserDeckCharacterDressupCostume": 360 + return []string{"userId", "userDeckCharacterUuid"} 361 + case "IUserCharacter": 362 + return []string{"userId", "characterId"} 363 + case "IUserConsumableItem": 364 + return []string{"userId", "consumableItemId"} 365 + case "IUserMaterial": 366 + return []string{"userId", "materialId"} 367 + case "IUserImportantItem": 368 + return []string{"userId", "importantItemId"} 369 + case "IUserPremiumItem": 370 + return []string{"userId", "premiumItemId"} 371 + case "IUserQuest": 372 + return []string{"userId", "questId"} 373 + case "IUserQuestMission": 374 + return []string{"userId", "questId", "questMissionId"} 375 + case "IUserMission": 376 + return []string{"userId", "missionId"} 377 + case "IUserWeaponStory": 378 + return []string{"userId", "weaponId"} 379 + case "IUserWeaponNote": 380 + return []string{"userId", "weaponId"} 381 + case "IUserTutorialProgress": 382 + return []string{"userId", "tutorialType"} 383 + case "IUserGimmick": 384 + return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId", "gimmickId"} 385 + case "IUserGimmickOrnamentProgress": 386 + return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId", "gimmickId", "gimmickOrnamentIndex"} 387 + case "IUserGimmickSequence": 388 + return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId"} 389 + case "IUserGimmickUnlock": 390 + return []string{"userId", "gimmickSequenceScheduleId", "gimmickSequenceId", "gimmickId"} 391 + case "IUserCostumeActiveSkill": 392 + return []string{"userId", "userCostumeUuid"} 393 + case "IUserCostumeAwakenStatusUp": 394 + return []string{"userId", "userCostumeUuid", "statusCalculationType"} 395 + case "IUserCostumeLotteryEffect": 396 + return []string{"userId", "userCostumeUuid", "slotNumber"} 397 + case "IUserCostumeLotteryEffectPending": 398 + return []string{"userId", "userCostumeUuid"} 399 + case "IUserCharacterBoard": 400 + return []string{"userId", "characterBoardId"} 401 + case "IUserCharacterBoardAbility": 402 + return []string{"userId", "characterId", "abilityId"} 403 + case "IUserCharacterBoardStatusUp": 404 + return []string{"userId", "characterId", "statusCalculationType"} 405 + case "IUserExploreScore": 406 + return []string{"userId", "exploreId"} 407 + case "IUserPartsGroupNote": 408 + return []string{"userId", "partsGroupId"} 409 + case "IUserPartsPreset": 410 + return []string{"userId", "userPartsPresetNumber"} 411 + case "IUserCageOrnamentReward": 412 + return []string{"userId", "cageOrnamentId"} 413 + case "IUserAutoSaleSettingDetail": 414 + return []string{"userId", "possessionAutoSaleItemType"} 415 + case "IUserCharacterRebirth": 416 + return []string{"userId", "characterId"} 417 + case "IUserShopItem": 418 + return []string{"userId", "shopItemId"} 419 + case "IUserShopReplaceableLineup": 420 + return []string{"userId", "slotNumber"} 421 + case "IUserNaviCutIn": 422 + return []string{"userId", "naviCutInId"} 423 + case "IUserMovie": 424 + return []string{"userId", "movieId"} 425 + case "IUserContentsStory": 426 + return []string{"userId", "contentsStoryId"} 427 + case "IUserOmikuji": 428 + return []string{"userId", "omikujiId"} 429 + case "IUserDokan": 430 + return []string{"userId", "dokanId"} 431 + case "IUserSideStoryQuest": 432 + return []string{"userId", "sideStoryQuestId"} 433 + case "IUserQuestLimitContentStatus": 434 + return []string{"userId", "questId"} 435 + case "IUserBigHuntMaxScore": 436 + return []string{"userId", "bigHuntBossId"} 437 + case "IUserBigHuntStatus": 438 + return []string{"userId", "bigHuntBossQuestId"} 439 + case "IUserBigHuntScheduleMaxScore": 440 + return []string{"userId", "bigHuntScheduleId", "bigHuntBossId"} 441 + case "IUserBigHuntWeeklyMaxScore": 442 + return []string{"userId", "bigHuntWeeklyVersion", "attributeType"} 443 + case "IUserBigHuntWeeklyStatus": 444 + return []string{"userId", "bigHuntWeeklyVersion"} 445 + case "IUserDeckTypeNote": 446 + return []string{"userId", "deckType"} 447 + default: 448 + return nil 449 + } 450 + }
+7 -6
server/internal/userdata/proj_bighunt.go
··· 4 4 "sort" 5 5 6 6 "lunar-tear/server/internal/store" 7 + "lunar-tear/server/internal/utils" 7 8 ) 8 9 9 10 func init() { 10 11 register("IUserBigHuntProgressStatus", func(user store.UserState) string { 11 - s, _ := encodeJSONMaps(map[string]any{ 12 + s, _ := utils.EncodeJSONMaps(map[string]any{ 12 13 "userId": user.UserId, 13 14 "currentBigHuntBossQuestId": user.BigHuntProgress.CurrentBigHuntBossQuestId, 14 15 "currentBigHuntQuestId": user.BigHuntProgress.CurrentBigHuntQuestId, ··· 39 40 "latestVersion": ms.LatestVersion, 40 41 }) 41 42 } 42 - s, _ := encodeJSONMaps(records...) 43 + s, _ := utils.EncodeJSONMaps(records...) 43 44 return s 44 45 }) 45 46 ··· 63 64 "latestVersion": st.LatestVersion, 64 65 }) 65 66 } 66 - s, _ := encodeJSONMaps(records...) 67 + s, _ := utils.EncodeJSONMaps(records...) 67 68 return s 68 69 }) 69 70 ··· 97 98 "latestVersion": ms.LatestVersion, 98 99 }) 99 100 } 100 - s, _ := encodeJSONMaps(records...) 101 + s, _ := utils.EncodeJSONMaps(records...) 101 102 return s 102 103 }) 103 104 ··· 130 131 "latestVersion": ms.LatestVersion, 131 132 }) 132 133 } 133 - s, _ := encodeJSONMaps(records...) 134 + s, _ := utils.EncodeJSONMaps(records...) 134 135 return s 135 136 }) 136 137 ··· 153 154 "latestVersion": ws.LatestVersion, 154 155 }) 155 156 } 156 - s, _ := encodeJSONMaps(records...) 157 + s, _ := utils.EncodeJSONMaps(records...) 157 158 return s 158 159 }) 159 160 }
+4 -3
server/internal/userdata/proj_characterboard.go
··· 4 4 "sort" 5 5 6 6 "lunar-tear/server/internal/store" 7 + "lunar-tear/server/internal/utils" 7 8 ) 8 9 9 10 func init() { 10 11 register("IUserCharacterBoard", func(user store.UserState) string { 11 - s, _ := encodeJSONMaps(sortedCharacterBoardRecords(user)...) 12 + s, _ := utils.EncodeJSONMaps(sortedCharacterBoardRecords(user)...) 12 13 return s 13 14 }) 14 15 register("IUserCharacterBoardAbility", func(user store.UserState) string { 15 - s, _ := encodeJSONMaps(sortedCharacterBoardAbilityRecords(user)...) 16 + s, _ := utils.EncodeJSONMaps(sortedCharacterBoardAbilityRecords(user)...) 16 17 return s 17 18 }) 18 19 register("IUserCharacterBoardStatusUp", func(user store.UserState) string { 19 - s, _ := encodeJSONMaps(sortedCharacterBoardStatusUpRecords(user)...) 20 + s, _ := utils.EncodeJSONMaps(sortedCharacterBoardStatusUpRecords(user)...) 20 21 return s 21 22 }) 22 23 registerStatic("IUserCharacterBoardCompleteReward")
+7 -6
server/internal/userdata/proj_deck.go
··· 5 5 6 6 "lunar-tear/server/internal/model" 7 7 "lunar-tear/server/internal/store" 8 + "lunar-tear/server/internal/utils" 8 9 ) 9 10 10 11 func init() { 11 12 register("IUserDeck", func(user store.UserState) string { 12 - s, _ := encodeJSONMaps(sortedDeckRecords(user)...) 13 + s, _ := utils.EncodeJSONMaps(sortedDeckRecords(user)...) 13 14 return s 14 15 }) 15 16 register("IUserDeckCharacter", func(user store.UserState) string { 16 - s, _ := encodeJSONMaps(sortedDeckCharacterRecords(user)...) 17 + s, _ := utils.EncodeJSONMaps(sortedDeckCharacterRecords(user)...) 17 18 return s 18 19 }) 19 20 register("IUserDeckSubWeaponGroup", func(user store.UserState) string { 20 - s, _ := encodeJSONMaps(sortedDeckSubWeaponGroupRecords(user)...) 21 + s, _ := utils.EncodeJSONMaps(sortedDeckSubWeaponGroupRecords(user)...) 21 22 return s 22 23 }) 23 24 register("IUserDeckTypeNote", func(user store.UserState) string { 24 - s, _ := encodeJSONMaps(sortedDeckTypeNoteRecords(user)...) 25 + s, _ := utils.EncodeJSONMaps(sortedDeckTypeNoteRecords(user)...) 25 26 return s 26 27 }) 27 28 register("IUserDeckPartsGroup", func(user store.UserState) string { 28 - s, _ := encodeJSONMaps(sortedDeckPartsGroupRecords(user)...) 29 + s, _ := utils.EncodeJSONMaps(sortedDeckPartsGroupRecords(user)...) 29 30 return s 30 31 }) 31 32 register("IUserDeckCharacterDressupCostume", func(user store.UserState) string { 32 - s, _ := encodeJSONMaps(sortedDeckDressupCostumeRecords(user)...) 33 + s, _ := utils.EncodeJSONMaps(sortedDeckDressupCostumeRecords(user)...) 33 34 return s 34 35 }) 35 36 registerStatic(
+5 -4
server/internal/userdata/proj_gimmick.go
··· 4 4 "sort" 5 5 6 6 "lunar-tear/server/internal/store" 7 + "lunar-tear/server/internal/utils" 7 8 ) 8 9 9 10 func init() { 10 11 register("IUserGimmick", func(user store.UserState) string { 11 - s, _ := encodeJSONMaps(sortedGimmickRecords(user)...) 12 + s, _ := utils.EncodeJSONMaps(sortedGimmickRecords(user)...) 12 13 return s 13 14 }) 14 15 register("IUserGimmickOrnamentProgress", func(user store.UserState) string { 15 - s, _ := encodeJSONMaps(sortedGimmickOrnamentProgressRecords(user)...) 16 + s, _ := utils.EncodeJSONMaps(sortedGimmickOrnamentProgressRecords(user)...) 16 17 return s 17 18 }) 18 19 register("IUserGimmickSequence", func(user store.UserState) string { 19 - s, _ := encodeJSONMaps(sortedGimmickSequenceRecords(user)...) 20 + s, _ := utils.EncodeJSONMaps(sortedGimmickSequenceRecords(user)...) 20 21 return s 21 22 }) 22 23 register("IUserGimmickUnlock", func(user store.UserState) string { 23 - s, _ := encodeJSONMaps(sortedGimmickUnlockRecords(user)...) 24 + s, _ := utils.EncodeJSONMaps(sortedGimmickUnlockRecords(user)...) 24 25 return s 25 26 }) 26 27 }
+28 -27
server/internal/userdata/proj_inventory.go
··· 6 6 7 7 "lunar-tear/server/internal/gametime" 8 8 "lunar-tear/server/internal/store" 9 + "lunar-tear/server/internal/utils" 9 10 ) 10 11 11 12 func init() { 12 13 register("IUserCharacter", func(user store.UserState) string { 13 - s, _ := encodeJSONMaps(sortedCharacterRecords(user)...) 14 + s, _ := utils.EncodeJSONMaps(sortedCharacterRecords(user)...) 14 15 return s 15 16 }) 16 17 register("IUserCostume", func(user store.UserState) string { 17 - s, _ := encodeJSONMaps(sortedCostumeRecords(user)...) 18 + s, _ := utils.EncodeJSONMaps(sortedCostumeRecords(user)...) 18 19 return s 19 20 }) 20 21 register("IUserWeapon", func(user store.UserState) string { 21 - s, _ := encodeJSONMaps(SortedWeaponRecords(user)...) 22 + s, _ := utils.EncodeJSONMaps(SortedWeaponRecords(user)...) 22 23 return s 23 24 }) 24 25 register("IUserWeaponStory", func(user store.UserState) string { 25 - s, _ := encodeJSONMaps(sortedWeaponStoryRecords(user)...) 26 + s, _ := utils.EncodeJSONMaps(sortedWeaponStoryRecords(user)...) 26 27 return s 27 28 }) 28 29 register("IUserWeaponNote", func(user store.UserState) string { 29 - s, _ := encodeJSONMaps(sortedWeaponNoteRecords(user)...) 30 + s, _ := utils.EncodeJSONMaps(sortedWeaponNoteRecords(user)...) 30 31 return s 31 32 }) 32 33 register("IUserCompanion", func(user store.UserState) string { 33 - s, _ := encodeJSONMaps(sortedCompanionRecords(user)...) 34 + s, _ := utils.EncodeJSONMaps(sortedCompanionRecords(user)...) 34 35 return s 35 36 }) 36 37 register("IUserThought", func(user store.UserState) string { 37 - s, _ := encodeJSONMaps(sortedThoughtRecords(user)...) 38 + s, _ := utils.EncodeJSONMaps(sortedThoughtRecords(user)...) 38 39 return s 39 40 }) 40 41 register("IUserConsumableItem", func(user store.UserState) string { 41 - s, _ := encodeJSONMaps(SortedConsumableItemRecords(user)...) 42 + s, _ := utils.EncodeJSONMaps(SortedConsumableItemRecords(user)...) 42 43 return s 43 44 }) 44 45 register("IUserMaterial", func(user store.UserState) string { 45 - s, _ := encodeJSONMaps(SortedMaterialRecords(user)...) 46 + s, _ := utils.EncodeJSONMaps(SortedMaterialRecords(user)...) 46 47 return s 47 48 }) 48 49 register("IUserImportantItem", func(user store.UserState) string { 49 - s, _ := encodeJSONMaps(sortedImportantItemRecords(user)...) 50 + s, _ := utils.EncodeJSONMaps(sortedImportantItemRecords(user)...) 50 51 return s 51 52 }) 52 53 register("IUserPremiumItem", func(user store.UserState) string { 53 - s, _ := encodeJSONMaps(sortedPremiumItemRecords(user)...) 54 + s, _ := utils.EncodeJSONMaps(sortedPremiumItemRecords(user)...) 54 55 return s 55 56 }) 56 57 register("IUserParts", func(user store.UserState) string { 57 - s, _ := encodeJSONMaps(SortedPartsRecords(user)...) 58 + s, _ := utils.EncodeJSONMaps(SortedPartsRecords(user)...) 58 59 return s 59 60 }) 60 61 register("IUserCostumeActiveSkill", func(user store.UserState) string { 61 - s, _ := encodeJSONMaps(sortedCostumeActiveSkillRecords(user)...) 62 + s, _ := utils.EncodeJSONMaps(sortedCostumeActiveSkillRecords(user)...) 62 63 return s 63 64 }) 64 65 register("IUserWeaponSkill", func(user store.UserState) string { 65 - s, _ := encodeJSONMaps(SortedWeaponSkillRecords(user)...) 66 + s, _ := utils.EncodeJSONMaps(SortedWeaponSkillRecords(user)...) 66 67 return s 67 68 }) 68 69 register("IUserWeaponAbility", func(user store.UserState) string { 69 - s, _ := encodeJSONMaps(SortedWeaponAbilityRecords(user)...) 70 + s, _ := utils.EncodeJSONMaps(SortedWeaponAbilityRecords(user)...) 70 71 return s 71 72 }) 72 73 register("IUserExplore", func(user store.UserState) string { 73 - s, _ := encodeJSONMaps(exploreRecord(user)) 74 + s, _ := utils.EncodeJSONMaps(exploreRecord(user)) 74 75 return s 75 76 }) 76 77 register("IUserExploreScore", func(user store.UserState) string { 77 - s, _ := encodeJSONMaps(sortedExploreScoreRecords(user)...) 78 + s, _ := utils.EncodeJSONMaps(sortedExploreScoreRecords(user)...) 78 79 return s 79 80 }) 80 81 register("IUserPartsGroupNote", func(user store.UserState) string { 81 - s, _ := encodeJSONMaps(sortedPartsGroupNoteRecords(user)...) 82 + s, _ := utils.EncodeJSONMaps(sortedPartsGroupNoteRecords(user)...) 82 83 return s 83 84 }) 84 85 register("IUserPartsPreset", func(user store.UserState) string { 85 - s, _ := encodeJSONMaps(sortedPartsPresetRecords(user)...) 86 + s, _ := utils.EncodeJSONMaps(sortedPartsPresetRecords(user)...) 86 87 return s 87 88 }) 88 89 register("IUserCostumeAwakenStatusUp", func(user store.UserState) string { 89 - s, _ := encodeJSONMaps(sortedCostumeAwakenStatusUpRecords(user)...) 90 + s, _ := utils.EncodeJSONMaps(sortedCostumeAwakenStatusUpRecords(user)...) 90 91 return s 91 92 }) 92 93 register("IUserAutoSaleSettingDetail", func(user store.UserState) string { 93 - s, _ := encodeJSONMaps(sortedAutoSaleSettingRecords(user)...) 94 + s, _ := utils.EncodeJSONMaps(sortedAutoSaleSettingRecords(user)...) 94 95 return s 95 96 }) 96 97 register("IUserCharacterRebirth", func(user store.UserState) string { 97 - s, _ := encodeJSONMaps(sortedCharacterRebirthRecords(user)...) 98 + s, _ := utils.EncodeJSONMaps(sortedCharacterRebirthRecords(user)...) 98 99 return s 99 100 }) 100 101 register("IUserCageOrnamentReward", func(user store.UserState) string { 101 - s, _ := encodeJSONMaps(sortedCageOrnamentRewardRecords(user)...) 102 + s, _ := utils.EncodeJSONMaps(sortedCageOrnamentRewardRecords(user)...) 102 103 return s 103 104 }) 104 105 register("IUserWeaponAwaken", func(user store.UserState) string { 105 - s, _ := encodeJSONMaps(SortedWeaponAwakenRecords(user)...) 106 + s, _ := utils.EncodeJSONMaps(SortedWeaponAwakenRecords(user)...) 106 107 return s 107 108 }) 108 109 register("IUserCostumeLotteryEffect", func(user store.UserState) string { 109 - s, _ := encodeJSONMaps(sortedCostumeLotteryEffectRecords(user)...) 110 + s, _ := utils.EncodeJSONMaps(sortedCostumeLotteryEffectRecords(user)...) 110 111 return s 111 112 }) 112 113 register("IUserCostumeLotteryEffectPending", func(user store.UserState) string { 113 - s, _ := encodeJSONMaps(SortedCostumeLotteryEffectPendingRecords(user)...) 114 + s, _ := utils.EncodeJSONMaps(SortedCostumeLotteryEffectPendingRecords(user)...) 114 115 return s 115 116 }) 116 117 registerStatic( ··· 285 286 "latestVersion": row.LatestVersion, 286 287 }) 287 288 } 288 - s, _ := encodeJSONMaps(records...) 289 + s, _ := utils.EncodeJSONMaps(records...) 289 290 return s 290 291 } 291 292
+13 -12
server/internal/userdata/proj_quest.go
··· 4 4 "sort" 5 5 6 6 "lunar-tear/server/internal/store" 7 + "lunar-tear/server/internal/utils" 7 8 ) 8 9 9 10 func sortedQuestRecords(user store.UserState) []map[string]any { ··· 60 61 61 62 func init() { 62 63 register("IUserQuest", func(user store.UserState) string { 63 - s, _ := encodeJSONMaps(sortedQuestRecords(user)...) 64 + s, _ := utils.EncodeJSONMaps(sortedQuestRecords(user)...) 64 65 return s 65 66 }) 66 67 register("IUserQuestMission", func(user store.UserState) string { 67 - s, _ := encodeJSONMaps(sortedQuestMissionRecords(user)...) 68 + s, _ := utils.EncodeJSONMaps(sortedQuestMissionRecords(user)...) 68 69 return s 69 70 }) 70 71 register("IUserMainQuestFlowStatus", func(user store.UserState) string { 71 - s, _ := encodeJSONMaps(map[string]any{ 72 + s, _ := utils.EncodeJSONMaps(map[string]any{ 72 73 "userId": user.UserId, 73 74 "currentQuestFlowType": user.MainQuest.CurrentQuestFlowType, 74 75 "latestVersion": user.MainQuest.LatestVersion, ··· 76 77 return s 77 78 }) 78 79 register("IUserMainQuestMainFlowStatus", func(user store.UserState) string { 79 - s, _ := encodeJSONMaps(map[string]any{ 80 + s, _ := utils.EncodeJSONMaps(map[string]any{ 80 81 "userId": user.UserId, 81 82 "currentMainQuestRouteId": user.MainQuest.CurrentMainQuestRouteId, 82 83 "currentQuestSceneId": user.MainQuest.CurrentQuestSceneId, ··· 87 88 return s 88 89 }) 89 90 register("IUserMainQuestProgressStatus", func(user store.UserState) string { 90 - s, _ := encodeJSONMaps(map[string]any{ 91 + s, _ := utils.EncodeJSONMaps(map[string]any{ 91 92 "userId": user.UserId, 92 93 "currentQuestSceneId": user.MainQuest.ProgressQuestSceneId, 93 94 "headQuestSceneId": user.MainQuest.ProgressHeadQuestSceneId, ··· 97 98 return s 98 99 }) 99 100 register("IUserMainQuestSeasonRoute", func(user store.UserState) string { 100 - s, _ := encodeJSONMaps(map[string]any{ 101 + s, _ := utils.EncodeJSONMaps(map[string]any{ 101 102 "userId": user.UserId, 102 103 "mainQuestSeasonId": user.MainQuest.MainQuestSeasonId, 103 104 "mainQuestRouteId": user.MainQuest.CurrentMainQuestRouteId, ··· 106 107 return s 107 108 }) 108 109 register("IUserEventQuestProgressStatus", func(user store.UserState) string { 109 - s, _ := encodeJSONMaps(map[string]any{ 110 + s, _ := utils.EncodeJSONMaps(map[string]any{ 110 111 "userId": user.UserId, 111 112 "currentEventQuestChapterId": user.EventQuest.CurrentEventQuestChapterId, 112 113 "currentQuestId": user.EventQuest.CurrentQuestId, ··· 117 118 return s 118 119 }) 119 120 register("IUserExtraQuestProgressStatus", func(user store.UserState) string { 120 - s, _ := encodeJSONMaps(map[string]any{ 121 + s, _ := utils.EncodeJSONMaps(map[string]any{ 121 122 "userId": user.UserId, 122 123 "currentQuestId": user.ExtraQuest.CurrentQuestId, 123 124 "currentQuestSceneId": user.ExtraQuest.CurrentQuestSceneId, ··· 127 128 return s 128 129 }) 129 130 register("IUserMainQuestReplayFlowStatus", func(user store.UserState) string { 130 - s, _ := encodeJSONMaps(map[string]any{ 131 + s, _ := utils.EncodeJSONMaps(map[string]any{ 131 132 "userId": user.UserId, 132 133 "currentHeadQuestSceneId": user.MainQuest.ReplayFlowHeadQuestSceneId, 133 134 "currentQuestSceneId": user.MainQuest.ReplayFlowCurrentQuestSceneId, ··· 136 137 return s 137 138 }) 138 139 register("IUserSideStoryQuestSceneProgressStatus", func(user store.UserState) string { 139 - s, _ := encodeJSONMaps(map[string]any{ 140 + s, _ := utils.EncodeJSONMaps(map[string]any{ 140 141 "userId": user.UserId, 141 142 "currentSideStoryQuestId": user.SideStoryActiveProgress.CurrentSideStoryQuestId, 142 143 "currentSideStoryQuestSceneId": user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId, ··· 164 165 "latestVersion": progress.LatestVersion, 165 166 }) 166 167 } 167 - s, _ := encodeJSONMaps(records...) 168 + s, _ := utils.EncodeJSONMaps(records...) 168 169 return s 169 170 }) 170 171 register("IUserQuestLimitContentStatus", func(user store.UserState) string { ··· 187 188 "latestVersion": st.LatestVersion, 188 189 }) 189 190 } 190 - s, _ := encodeJSONMaps(records...) 191 + s, _ := utils.EncodeJSONMaps(records...) 191 192 return s 192 193 }) 193 194 registerStatic(
+56 -40
server/internal/userdata/proj_user.go
··· 5 5 6 6 "lunar-tear/server/internal/gametime" 7 7 "lunar-tear/server/internal/store" 8 + "lunar-tear/server/internal/utils" 8 9 ) 9 10 10 11 func init() { 11 12 register("IUser", func(user store.UserState) string { 12 - s, _ := encodeJSONMaps(map[string]any{ 13 + s, _ := utils.EncodeJSONMaps(map[string]any{ 13 14 "userId": user.UserId, 14 15 "playerId": user.PlayerId, 15 16 "osType": user.OsType, ··· 22 23 return s 23 24 }) 24 25 register("IUserSetting", func(user store.UserState) string { 25 - s, _ := encodeJSONRecords(&EntityIUserSetting{ 26 - UserId: user.UserId, 27 - IsNotifyPurchaseAlert: user.Setting.IsNotifyPurchaseAlert, 28 - LatestVersion: user.Setting.LatestVersion, 26 + s, _ := utils.EncodeJSONMaps(map[string]any{ 27 + "userId": user.UserId, 28 + "isNotifyPurchaseAlert": user.Setting.IsNotifyPurchaseAlert, 29 + "latestVersion": user.Setting.LatestVersion, 29 30 }) 30 31 return s 31 32 }) 32 33 register("IUserStatus", func(user store.UserState) string { 33 - s, _ := encodeJSONMaps(map[string]any{ 34 + s, _ := utils.EncodeJSONMaps(map[string]any{ 34 35 "userId": user.UserId, 35 36 "level": user.Status.Level, 36 37 "exp": user.Status.Exp, ··· 41 42 return s 42 43 }) 43 44 register("IUserGem", func(user store.UserState) string { 44 - s, _ := encodeJSONRecords(&EntityIUserGem{ 45 - UserId: user.UserId, 46 - PaidGem: user.Gem.PaidGem, 47 - FreeGem: user.Gem.FreeGem, 48 - LatestVersion: gametime.NowMillis(), 45 + s, _ := utils.EncodeJSONMaps(map[string]any{ 46 + "userId": user.UserId, 47 + "paidGem": user.Gem.PaidGem, 48 + "freeGem": user.Gem.FreeGem, 49 + "latestVersion": gametime.NowMillis(), 49 50 }) 50 51 return s 51 52 }) 52 53 register("IUserProfile", func(user store.UserState) string { 53 - s, _ := encodeJSONMaps(map[string]any{ 54 + s, _ := utils.EncodeJSONMaps(map[string]any{ 54 55 "userId": user.UserId, 55 56 "name": user.Profile.Name, 56 57 "nameUpdateDatetime": user.Profile.NameUpdateDatetime, ··· 63 64 return s 64 65 }) 65 66 register("IUserLogin", func(user store.UserState) string { 66 - s, _ := encodeJSONRecords(&EntityIUserLogin{ 67 - UserId: user.UserId, 68 - TotalLoginCount: user.Login.TotalLoginCount, 69 - ContinualLoginCount: user.Login.ContinualLoginCount, 70 - MaxContinualLoginCount: user.Login.MaxContinualLoginCount, 71 - LastLoginDatetime: user.Login.LastLoginDatetime, 72 - LastComebackLoginDatetime: user.Login.LastComebackLoginDatetime, 73 - LatestVersion: user.Login.LatestVersion, 67 + s, _ := utils.EncodeJSONMaps(map[string]any{ 68 + "userId": user.UserId, 69 + "totalLoginCount": user.Login.TotalLoginCount, 70 + "continualLoginCount": user.Login.ContinualLoginCount, 71 + "maxContinualLoginCount": user.Login.MaxContinualLoginCount, 72 + "lastLoginDatetime": user.Login.LastLoginDatetime, 73 + "lastComebackLoginDatetime": user.Login.LastComebackLoginDatetime, 74 + "latestVersion": user.Login.LatestVersion, 74 75 }) 75 76 return s 76 77 }) 77 78 register("IUserLoginBonus", func(user store.UserState) string { 78 - s, _ := encodeJSONRecords(&EntityIUserLoginBonus{ 79 - UserId: user.UserId, 80 - LoginBonusId: user.LoginBonus.LoginBonusId, 81 - CurrentPageNumber: user.LoginBonus.CurrentPageNumber, 82 - CurrentStampNumber: user.LoginBonus.CurrentStampNumber, 83 - LatestRewardReceiveDatetime: user.LoginBonus.LatestRewardReceiveDatetime, 84 - LatestVersion: user.LoginBonus.LatestVersion, 79 + s, _ := utils.EncodeJSONMaps(map[string]any{ 80 + "userId": user.UserId, 81 + "loginBonusId": user.LoginBonus.LoginBonusId, 82 + "currentPageNumber": user.LoginBonus.CurrentPageNumber, 83 + "currentStampNumber": user.LoginBonus.CurrentStampNumber, 84 + "latestRewardReceiveDatetime": user.LoginBonus.LatestRewardReceiveDatetime, 85 + "latestVersion": user.LoginBonus.LatestVersion, 85 86 }) 86 87 return s 87 88 }) 88 89 register("IUserTutorialProgress", func(user store.UserState) string { 89 - s, _ := encodeJSONMaps(sortedTutorialRecords(user)...) 90 + s, _ := utils.EncodeJSONMaps(sortedTutorialRecords(user)...) 90 91 return s 91 92 }) 92 93 register("IUserMission", func(user store.UserState) string { 93 - s, _ := encodeJSONMaps(sortedMissionRecords(user)...) 94 + s, _ := utils.EncodeJSONMaps(sortedMissionRecords(user)...) 94 95 return s 95 96 }) 96 97 register("IUserNaviCutIn", func(user store.UserState) string { 97 - s, _ := encodeJSONMaps(sortedNaviCutInRecords(user)...) 98 + s, _ := utils.EncodeJSONMaps(sortedNaviCutInRecords(user)...) 98 99 return s 99 100 }) 100 101 register("IUserMovie", func(user store.UserState) string { 101 - s, _ := encodeJSONMaps(sortedMovieRecords(user)...) 102 + s, _ := utils.EncodeJSONMaps(sortedMovieRecords(user)...) 102 103 return s 103 104 }) 104 105 register("IUserContentsStory", func(user store.UserState) string { 105 - s, _ := encodeJSONMaps(sortedContentsStoryRecords(user)...) 106 + s, _ := utils.EncodeJSONMaps(sortedContentsStoryRecords(user)...) 106 107 return s 107 108 }) 108 109 register("IUserOmikuji", func(user store.UserState) string { 109 - s, _ := encodeJSONMaps(sortedOmikujiRecords(user)...) 110 + s, _ := utils.EncodeJSONMaps(sortedOmikujiRecords(user)...) 110 111 return s 111 112 }) 112 113 register("IUserDokan", func(user store.UserState) string { 113 - s, _ := encodeJSONMaps(sortedDokanRecords(user)...) 114 + s, _ := utils.EncodeJSONMaps(sortedDokanRecords(user)...) 114 115 return s 115 116 }) 116 117 register("IUserPortalCageStatus", func(user store.UserState) string { 117 - s, _ := encodeJSONMaps(map[string]any{ 118 + s, _ := utils.EncodeJSONMaps(map[string]any{ 118 119 "userId": user.UserId, 119 120 "isCurrentProgress": user.PortalCageStatus.IsCurrentProgress, 120 121 "dropItemStartDatetime": user.PortalCageStatus.DropItemStartDatetime, ··· 124 125 return s 125 126 }) 126 127 register("IUserEventQuestGuerrillaFreeOpen", func(user store.UserState) string { 127 - s, _ := encodeJSONMaps(map[string]any{ 128 + s, _ := utils.EncodeJSONMaps(map[string]any{ 128 129 "userId": user.UserId, 129 130 "startDatetime": user.GuerrillaFreeOpen.StartDatetime, 130 131 "openMinutes": user.GuerrillaFreeOpen.OpenMinutes, ··· 135 136 }) 136 137 137 138 register("IUserShopItem", func(user store.UserState) string { 138 - s, _ := encodeJSONMaps(sortedShopItemRecords(user)...) 139 + s, _ := utils.EncodeJSONMaps(sortedShopItemRecords(user)...) 139 140 return s 140 141 }) 141 142 register("IUserShopReplaceable", func(user store.UserState) string { 142 - s, _ := encodeJSONMaps(map[string]any{ 143 + s, _ := utils.EncodeJSONMaps(map[string]any{ 143 144 "userId": user.UserId, 144 145 "lineupUpdateCount": user.ShopReplaceable.LineupUpdateCount, 145 146 "latestLineupUpdateDatetime": user.ShopReplaceable.LatestLineupUpdateDatetime, ··· 148 149 return s 149 150 }) 150 151 register("IUserShopReplaceableLineup", func(user store.UserState) string { 151 - s, _ := encodeJSONMaps(sortedShopReplaceableLineupRecords(user)...) 152 + s, _ := utils.EncodeJSONMaps(sortedShopReplaceableLineupRecords(user)...) 152 153 return s 153 154 }) 154 155 155 - registerStatic() 156 + register("IUserFacebook", func(user store.UserState) string { 157 + return ProjectFacebook(user.UserId, user.FacebookId) 158 + }) 159 + registerStatic("IUserApple") 160 + } 161 + 162 + func ProjectFacebook(userId int64, facebookId int64) string { 163 + if facebookId == 0 { 164 + return "[]" 165 + } 166 + s, _ := utils.EncodeJSONMaps(map[string]any{ 167 + "userId": userId, 168 + "facebookId": facebookId, 169 + "latestVersion": gametime.NowMillis(), 170 + }) 171 + return s 156 172 } 157 173 158 174 func sortedTutorialRecords(user store.UserState) []map[string]any {
+2
server/internal/userdata/state_projection.go
··· 99 99 "IUserBigHuntScheduleMaxScore": projectTable("IUserBigHuntScheduleMaxScore", user), 100 100 "IUserBigHuntWeeklyMaxScore": projectTable("IUserBigHuntWeeklyMaxScore", user), 101 101 "IUserBigHuntWeeklyStatus": projectTable("IUserBigHuntWeeklyStatus", user), 102 + "IUserFacebook": projectTable("IUserFacebook", user), 103 + "IUserApple": projectTable("IUserApple", user), 102 104 } 103 105 } 104 106
-302
server/internal/userdata/userdata.go
··· 1 - package userdata 2 - 3 - import ( 4 - "encoding/base64" 5 - "encoding/json" 6 - "fmt" 7 - 8 - "lunar-tear/server/internal/gametime" 9 - 10 - "github.com/vmihailenco/msgpack/v5" 11 - ) 12 - 13 - // EntityIUser mirrors the game's EntityIUser [MessagePackObject] with [Key(0..7)]. 14 - // Serialized as a MessagePack array of 8 elements. 15 - type EntityIUser struct { 16 - _msgpack struct{} `msgpack:",asArray"` 17 - UserId int64 // Key(0) 18 - PlayerId int64 // Key(1) 19 - OsType int32 // Key(2) — 2 = Android 20 - PlatformType int32 // Key(3) — 2 = GooglePlay 21 - UserRestrictionType int32 // Key(4) — 0 = None 22 - RegisterDatetime int64 // Key(5) — unix millis 23 - GameStartDatetime int64 // Key(6) — unix millis 24 - LatestVersion int64 // Key(7) 25 - } 26 - 27 - // EntityIUserSetting mirrors EntityIUserSetting [Key(0..2)]. 28 - type EntityIUserSetting struct { 29 - _msgpack struct{} `msgpack:",asArray"` 30 - UserId int64 `json:"userId"` // Key(0) 31 - IsNotifyPurchaseAlert bool `json:"isNotifyPurchaseAlert"` // Key(1) 32 - LatestVersion int64 `json:"latestVersion"` // Key(2) 33 - } 34 - 35 - // EntityIUserTutorialProgress mirrors EntityIUserTutorialProgress [Key(0..4)]. 36 - type EntityIUserTutorialProgress struct { 37 - _msgpack struct{} `msgpack:",asArray"` 38 - UserId int64 // Key(0) 39 - TutorialType int32 // Key(1) 40 - ProgressPhase int32 // Key(2) 41 - ChoiceId int32 // Key(3) 42 - LatestVersion int64 // Key(4) 43 - } 44 - 45 - // EntityIUserQuest mirrors EntityIUserQuest [Key(0..9)]. 46 - type EntityIUserQuest struct { 47 - _msgpack struct{} `msgpack:",asArray"` 48 - UserId int64 // Key(0) 49 - QuestId int32 // Key(1) 50 - QuestStateType int32 // Key(2) — 2 = Cleared 51 - IsBattleOnly bool // Key(3) 52 - LatestStartDatetime int64 // Key(4) — unix millis 53 - ClearCount int32 // Key(5) 54 - DailyClearCount int32 // Key(6) 55 - LastClearDatetime int64 // Key(7) — unix millis 56 - ShortestClearFrames int32 // Key(8) 57 - LatestVersion int64 // Key(9) 58 - } 59 - 60 - // EntityIUserMainQuestFlowStatus mirrors EntityIUserMainQuestFlowStatus [Key(0..2)]. 61 - type EntityIUserMainQuestFlowStatus struct { 62 - _msgpack struct{} `msgpack:",asArray"` 63 - UserId int64 // Key(0) 64 - CurrentQuestFlowType int32 // Key(1) // QuestFlowType: 0=UNKNOWN, 1=MAIN_FLOW, 2=SUB_FLOW, 3=REPLAY_FLOW, 4=ANOTHER_ROUTE_REPLAY_FLOW 65 - LatestVersion int64 // Key(2) 66 - } 67 - 68 - // EntityIUserMainQuestMainFlowStatus mirrors EntityIUserMainQuestMainFlowStatus [Key(0..5)]. 69 - type EntityIUserMainQuestMainFlowStatus struct { 70 - _msgpack struct{} `msgpack:",asArray"` 71 - UserId int64 // Key(0) 72 - CurrentMainQuestRouteId int32 // Key(1) 73 - CurrentQuestSceneId int32 // Key(2) 74 - HeadQuestSceneId int32 // Key(3) 75 - IsReachedLastQuestScene bool // Key(4) 76 - LatestVersion int64 // Key(5) 77 - } 78 - 79 - // EntityIUserMainQuestProgressStatus mirrors EntityIUserMainQuestProgressStatus [Key(0..4)]. 80 - // This table is used by ActivePlayerToEntityPlayingMainQuestStatus (0x2AB4A48). 81 - type EntityIUserMainQuestProgressStatus struct { 82 - _msgpack struct{} `msgpack:",asArray"` 83 - UserId int64 // Key(0) 84 - CurrentQuestSceneId int32 // Key(1) 85 - HeadQuestSceneId int32 // Key(2) 86 - CurrentQuestFlowType int32 // Key(3) // QuestFlowType: 0=UNKNOWN, 1=MAIN_FLOW, 2=SUB_FLOW, 3=REPLAY_FLOW, 4=ANOTHER_ROUTE_REPLAY_FLOW 87 - LatestVersion int64 // Key(4) 88 - } 89 - 90 - // EntityIUserMainQuestSeasonRoute mirrors EntityIUserMainQuestSeasonRoute [Key(0..3)]. 91 - type EntityIUserMainQuestSeasonRoute struct { 92 - _msgpack struct{} `msgpack:",asArray"` 93 - UserId int64 // Key(0) 94 - MainQuestSeasonId int32 // Key(1) 95 - MainQuestRouteId int32 // Key(2) 96 - LatestVersion int64 // Key(3) 97 - } 98 - 99 - // EntityIUserStatus mirrors EntityIUserStatus [Key(0..5)]. 100 - type EntityIUserStatus struct { 101 - _msgpack struct{} `msgpack:",asArray"` 102 - UserId int64 // Key(0) 103 - Level int32 // Key(1) 104 - Exp int32 // Key(2) 105 - StaminaMilliValue int32 // Key(3) 106 - StaminaUpdateDatetime int64 // Key(4) 107 - LatestVersion int64 // Key(5) 108 - } 109 - 110 - // EntityIUserGem mirrors EntityIUserGem [Key(0..3)]. 111 - type EntityIUserGem struct { 112 - _msgpack struct{} `msgpack:",asArray"` 113 - UserId int64 `json:"userId"` // Key(0) 114 - PaidGem int32 `json:"paidGem"` // Key(1) 115 - FreeGem int32 `json:"freeGem"` // Key(2) 116 - LatestVersion int64 `json:"latestVersion"` // Key(3) 117 - } 118 - 119 - // EntityIUserProfile mirrors EntityIUserProfile [Key(0..7)]. 120 - type EntityIUserProfile struct { 121 - _msgpack struct{} `msgpack:",asArray"` 122 - UserId int64 // Key(0) 123 - Name string // Key(1) 124 - NameUpdateDatetime int64 // Key(2) 125 - Message string // Key(3) 126 - MessageUpdateDatetime int64 // Key(4) 127 - FavoriteCostumeId int32 // Key(5) 128 - FavoriteCostumeIdUpdateDatetime int64 // Key(6) 129 - LatestVersion int64 // Key(7) 130 - } 131 - 132 - // EntityIUserCharacter mirrors EntityIUserCharacter [Key(0..4)]. 133 - type EntityIUserCharacter struct { 134 - _msgpack struct{} `msgpack:",asArray"` 135 - UserId int64 // Key(0) 136 - CharacterId int32 // Key(1) 137 - Level int32 // Key(2) 138 - Exp int32 // Key(3) 139 - LatestVersion int64 // Key(4) 140 - } 141 - 142 - // EntityIUserCostume mirrors EntityIUserCostume [Key(0..9)]. 143 - type EntityIUserCostume struct { 144 - _msgpack struct{} `msgpack:",asArray"` 145 - UserId int64 // Key(0) 146 - UserCostumeUuid string // Key(1) 147 - CostumeId int32 // Key(2) 148 - LimitBreakCount int32 // Key(3) 149 - Level int32 // Key(4) 150 - Exp int32 // Key(5) 151 - HeadupDisplayViewId int32 // Key(6) 152 - AcquisitionDatetime int64 // Key(7) 153 - AwakenCount int32 // Key(8) 154 - LatestVersion int64 // Key(9) 155 - } 156 - 157 - // EntityIUserWeapon mirrors EntityIUserWeapon [Key(0..8)]. 158 - type EntityIUserWeapon struct { 159 - _msgpack struct{} `msgpack:",asArray"` 160 - UserId int64 // Key(0) 161 - UserWeaponUuid string // Key(1) 162 - WeaponId int32 // Key(2) 163 - Level int32 // Key(3) 164 - Exp int32 // Key(4) 165 - LimitBreakCount int32 // Key(5) 166 - IsProtected bool // Key(6) 167 - AcquisitionDatetime int64 // Key(7) 168 - LatestVersion int64 // Key(8) 169 - } 170 - 171 - // EntityIUserCompanion mirrors EntityIUserCompanion [Key(0..6)]. 172 - type EntityIUserCompanion struct { 173 - _msgpack struct{} `msgpack:",asArray"` 174 - UserId int64 // Key(0) 175 - UserCompanionUuid string // Key(1) 176 - CompanionId int32 // Key(2) 177 - HeadupDisplayViewId int32 // Key(3) 178 - Level int32 // Key(4) 179 - AcquisitionDatetime int64 // Key(5) 180 - LatestVersion int64 // Key(6) 181 - } 182 - 183 - // EntityIUserDeckCharacter mirrors EntityIUserDeckCharacter [Key(0..7)]. 184 - type EntityIUserDeckCharacter struct { 185 - _msgpack struct{} `msgpack:",asArray"` 186 - UserId int64 // Key(0) 187 - UserDeckCharacterUuid string // Key(1) 188 - UserCostumeUuid string // Key(2) 189 - MainUserWeaponUuid string // Key(3) 190 - UserCompanionUuid string // Key(4) 191 - Power int32 // Key(5) 192 - UserThoughtUuid string // Key(6) 193 - LatestVersion int64 // Key(7) 194 - } 195 - 196 - // EntityIUserDeck mirrors EntityIUserDeck [Key(0..8)]. 197 - type EntityIUserDeck struct { 198 - _msgpack struct{} `msgpack:",asArray"` 199 - UserId int64 // Key(0) 200 - DeckType int32 // Key(1) 201 - UserDeckNumber int32 // Key(2) 202 - UserDeckCharacterUuid01 string // Key(3) 203 - UserDeckCharacterUuid02 string // Key(4) 204 - UserDeckCharacterUuid03 string // Key(5) 205 - Name string // Key(6) 206 - Power int32 // Key(7) 207 - LatestVersion int64 // Key(8) 208 - } 209 - 210 - // EntityIUserLogin mirrors EntityIUserLogin [Key(0..6)]. 211 - type EntityIUserLogin struct { 212 - _msgpack struct{} `msgpack:",asArray"` 213 - UserId int64 `json:"userId"` // Key(0) 214 - TotalLoginCount int32 `json:"totalLoginCount"` // Key(1) 215 - ContinualLoginCount int32 `json:"continualLoginCount"` // Key(2) 216 - MaxContinualLoginCount int32 `json:"maxContinualLoginCount"` // Key(3) 217 - LastLoginDatetime int64 `json:"lastLoginDatetime"` // Key(4) 218 - LastComebackLoginDatetime int64 `json:"lastComebackLoginDatetime"` // Key(5) 219 - LatestVersion int64 `json:"latestVersion"` // Key(6) 220 - } 221 - 222 - // EntityIUserLoginBonus mirrors EntityIUserLoginBonus [Key(0..5)]. 223 - type EntityIUserLoginBonus struct { 224 - _msgpack struct{} `msgpack:",asArray"` 225 - UserId int64 `json:"userId"` // Key(0) 226 - LoginBonusId int32 `json:"loginBonusId"` // Key(1) 227 - CurrentPageNumber int32 `json:"currentPageNumber"` // Key(2) 228 - CurrentStampNumber int32 `json:"currentStampNumber"` // Key(3) 229 - LatestRewardReceiveDatetime int64 `json:"latestRewardReceiveDatetime"` // Key(4) 230 - LatestVersion int64 `json:"latestVersion"` // Key(5) 231 - } 232 - 233 - // EntityIUserMission mirrors EntityIUserMission [Key(0..6)]. 234 - type EntityIUserMission struct { 235 - _msgpack struct{} `msgpack:",asArray"` 236 - UserId int64 // Key(0) 237 - MissionId int32 // Key(1) 238 - StartDatetime int64 // Key(2) 239 - ProgressValue int32 // Key(3) 240 - MissionProgressStatusType int32 // Key(4) 241 - ClearDatetime int64 // Key(5) 242 - LatestVersion int64 // Key(6) 243 - } 244 - 245 - // EncodeRecords serializes a slice of entities to the client-expected format: 246 - // a JSON array of base64-encoded MessagePack byte strings. 247 - func EncodeRecords(entities ...any) (string, error) { 248 - b64List := make([]string, 0, len(entities)) 249 - for _, e := range entities { 250 - data, err := msgpack.Marshal(e) 251 - if err != nil { 252 - return "", fmt.Errorf("msgpack marshal: %w", err) 253 - } 254 - b64List = append(b64List, base64.StdEncoding.EncodeToString(data)) 255 - } 256 - jsonBytes, err := json.Marshal(b64List) 257 - if err != nil { 258 - return "", fmt.Errorf("json marshal: %w", err) 259 - } 260 - return string(jsonBytes), nil 261 - } 262 - 263 - func encodeJSONRecords(entities ...any) (string, error) { 264 - jsonBytes, err := json.Marshal(entities) 265 - if err != nil { 266 - return "", fmt.Errorf("json marshal records: %w", err) 267 - } 268 - return string(jsonBytes), nil 269 - } 270 - 271 - func encodeJSONMaps(records ...map[string]any) (string, error) { 272 - jsonBytes, err := json.Marshal(records) 273 - if err != nil { 274 - return "", fmt.Errorf("json marshal maps: %w", err) 275 - } 276 - return string(jsonBytes), nil 277 - } 278 - 279 - // DefaultUserData returns pre-built user data tables for a fresh user. 280 - // We provide BOTH msgpack-encoded (base64) and plain JSON variants. 281 - // The server tries msgpack first; if the client doesn't accept it, switch to JSON. 282 - func DefaultUserData(userId int64) map[string]string { 283 - now := gametime.Now().Unix() 284 - 285 - userRecord, _ := EncodeRecords(&EntityIUser{ 286 - UserId: userId, 287 - PlayerId: userId, 288 - OsType: 2, 289 - PlatformType: 2, 290 - RegisterDatetime: now, 291 - }) 292 - 293 - settingRecord, _ := EncodeRecords(&EntityIUserSetting{ 294 - UserId: userId, 295 - }) 296 - 297 - data := map[string]string{ 298 - "user": userRecord, 299 - "user_setting": settingRecord, 300 - } 301 - return data 302 - }
+12 -11
server/internal/utils/utils.go
··· 3 3 import ( 4 4 "encoding/json" 5 5 "fmt" 6 - "os" 7 - "path/filepath" 6 + "lunar-tear/server/internal/masterdata/memorydb" 8 7 ) 9 8 10 - func ReadJSON[T any](filename string) ([]T, error) { 11 - path := filepath.Join("assets", "master_data", filename) 12 - data, err := os.ReadFile(path) 9 + // ReadTable deserializes a master data table from the in-memory binary store. 10 + // The key is the snake_case table name as it appears in the binary header 11 + // (e.g. "m_weapon", "m_costume"). 12 + func ReadTable[T any](key string) ([]T, error) { 13 + return memorydb.ReadTable[T](key) 14 + } 15 + 16 + func EncodeJSONMaps(records ...map[string]any) (string, error) { 17 + jsonBytes, err := json.Marshal(records) 13 18 if err != nil { 14 - return nil, fmt.Errorf("read %s: %w", path, err) 19 + return "", fmt.Errorf("json marshal maps: %w", err) 15 20 } 16 - var out []T 17 - if err := json.Unmarshal(data, &out); err != nil { 18 - return nil, fmt.Errorf("unmarshal %s: %w", path, err) 19 - } 20 - return out, nil 21 + return string(jsonBytes), nil 21 22 }
+8
server/migrations/20260417165746_add_facebook_id.sql
··· 1 + -- +goose Up 2 + ALTER TABLE users ADD COLUMN facebook_id INTEGER; 3 + CREATE UNIQUE INDEX idx_users_facebook_id ON users(facebook_id) WHERE facebook_id IS NOT NULL; 4 + 5 + -- +goose Down 6 + DROP INDEX IF EXISTS idx_users_facebook_id; 7 + ALTER TABLE users DROP COLUMN facebook_id; 8 +