mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Add authentication server, dev CLI, Docker multi-service setup, and cross-platform improvements
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
name: Build and Push lunar-tear to Docker Hub
|
name: Build and Push Docker images to Docker Hub
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -17,10 +17,26 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push game server image
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: ./server
|
context: ./server
|
||||||
file: ./server/Dockerfile
|
file: ./server/Dockerfile
|
||||||
push: true
|
push: true
|
||||||
tags: kretts/lunar-tear:latest
|
tags: kretts/lunar-tear:latest
|
||||||
|
|
||||||
|
- name: Build and push CDN image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ./server
|
||||||
|
file: ./server/Dockerfile.cdn
|
||||||
|
push: true
|
||||||
|
tags: kretts/octo-cdn:latest
|
||||||
|
|
||||||
|
- name: Build and push auth server image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: ./server
|
||||||
|
file: ./server/Dockerfile.auth
|
||||||
|
push: true
|
||||||
|
tags: kretts/auth-server:latest
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ server/bin/
|
|||||||
server/tmp/
|
server/tmp/
|
||||||
server/lunar-tear
|
server/lunar-tear
|
||||||
server/import-snapshot
|
server/import-snapshot
|
||||||
|
server/auth-server
|
||||||
|
server/claim-account
|
||||||
|
server/octo-cdn
|
||||||
|
server/dev
|
||||||
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Or manually:
|
|||||||
```bash
|
```bash
|
||||||
cd server
|
cd server
|
||||||
mkdir -p db
|
mkdir -p db
|
||||||
goose -dir migrations sqlite3 db/game.db up
|
goose -dir migrations -allow-missing sqlite3 db/game.db up
|
||||||
```
|
```
|
||||||
|
|
||||||
### Importing a Snapshot
|
### Importing a Snapshot
|
||||||
@@ -65,48 +65,124 @@ go run ./cmd/import-snapshot \
|
|||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|
||||||
|
The server is split into two binaries: a gRPC game server and an HTTP asset CDN. Both must be running for the client to work.
|
||||||
|
|
||||||
|
**Start the CDN** (serves asset bundles, list.bin, master data, web pages):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
go run ./cmd/octo-cdn \
|
||||||
|
--listen 0.0.0.0:8080 \
|
||||||
|
--public-addr 10.0.2.2:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
**Start the game server** (gRPC, points the client at the CDN):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd server
|
cd server
|
||||||
go run ./cmd/lunar-tear \
|
go run ./cmd/lunar-tear \
|
||||||
--host 10.0.2.2 \
|
--listen 0.0.0.0:8003 \
|
||||||
--http-port 8080 \
|
--public-addr 10.0.2.2:8003 \
|
||||||
--grpc-port 8003
|
--octo-url http://10.0.2.2:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
The default gRPC port is 443, which requires `sudo` (privileged port). Use `--grpc-port` with a high port to avoid this. If you do need port 443, either use `sudo` or grant the binary the capability on Linux:
|
The default listen address is `0.0.0.0:443`, which requires `sudo` (privileged port). Use `--listen` with a high port to avoid this. If you do need port 443, either use `sudo` or grant the binary the capability on Linux:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build -o lunar-tear ./cmd/lunar-tear
|
go build -o lunar-tear ./cmd/lunar-tear
|
||||||
sudo setcap cap_net_bind_service=+ep ./lunar-tear
|
sudo setcap cap_net_bind_service=+ep ./lunar-tear
|
||||||
./lunar-tear --host 10.0.2.2 --http-port 8080
|
./lunar-tear --public-addr 10.0.2.2:443 --octo-url http://10.0.2.2:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The CDN can run on a completely separate machine — just set `--octo-url` on the game server and `--public-addr` on the CDN to the externally-reachable address.
|
||||||
|
|
||||||
|
### Run All Services At Once
|
||||||
|
|
||||||
|
Instead of starting each service individually, use the dev runner to launch all three (auth, CDN, game server) with a single command. No Docker required — works on macOS, Linux, and Windows.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
make dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Or directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
go run ./cmd/dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Each service's output is prefixed with a colored label (`[auth]`, `[cdn]`, `[grpc]`). Press Ctrl+C to shut everything down.
|
||||||
|
|
||||||
|
Override defaults with namespaced flags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/dev --grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000 --cdn.public-addr 192.168.1.50:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Or via `make`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make dev ARGS="--grpc.listen 0.0.0.0:9000 --grpc.public-addr 10.0.2.2:9000"
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
| --------------------- | ------------------ | ---------------------------------------- |
|
||||||
|
| `--auth.listen` | `0.0.0.0:3000` | auth-server listen address |
|
||||||
|
| `--auth.db` | `db/auth.db` | auth-server SQLite database path |
|
||||||
|
| `--cdn.listen` | `0.0.0.0:8080` | octo-cdn local bind address |
|
||||||
|
| `--cdn.public-addr` | `10.0.2.2:8080` | octo-cdn externally-reachable addr |
|
||||||
|
| `--grpc.listen` | `0.0.0.0:8003` | lunar-tear gRPC listen address |
|
||||||
|
| `--grpc.public-addr` | `10.0.2.2:8003` | lunar-tear externally-reachable addr |
|
||||||
|
| `--grpc.octo-url` | `http://10.0.2.2:8080` | Octo CDN base URL passed to lunar-tear |
|
||||||
|
| `--grpc.auth-url` | `http://localhost:3000` | auth server base URL passed to lunar-tear |
|
||||||
|
| `--no-color` | `false` | disable colored output |
|
||||||
|
|
||||||
### Ports
|
### Ports
|
||||||
|
|
||||||
| Protocol | Port | Notes |
|
| Protocol | Port | Binary | Notes |
|
||||||
| -------- | ---- | ----------------------------------------------------------- |
|
| -------- | ---- | ------------- | ----------------------------------------------------------- |
|
||||||
| gRPC | 443 | default; configurable with `--grpc-port` (requires patched client) |
|
| gRPC | 443 | `lunar-tear` | default; configurable with `--listen` (requires patched client) |
|
||||||
| HTTP | 8080 | Octo asset API + game web pages (`--http-port` flag) |
|
| HTTP | 8080 | `octo-cdn` | Octo asset API + game web pages |
|
||||||
|
|
||||||
### Flags
|
### Game Server Flags (`lunar-tear`)
|
||||||
|
|
||||||
| Flag | Default | Description |
|
| Flag | Default | Description |
|
||||||
| ------------- | ------------ | ---------------------------------------------------- |
|
| --------------- | ----------------- | ---------------------------------------------------- |
|
||||||
| `--host` | `127.0.0.1` | hostname/IP given to the client |
|
| `--listen` | `0.0.0.0:443` | gRPC listen address (host:port) |
|
||||||
| `--http-port` | `8080` | HTTP/Octo server port |
|
| `--public-addr` | `127.0.0.1:443` | externally-reachable host:port advertised to clients |
|
||||||
| `--grpc-port` | `443` | gRPC server port (client must be patched to match) |
|
| `--octo-url` | *(required)* | CDN base URL the client uses for assets (e.g. `http://10.0.2.2:8080`) |
|
||||||
| `--db` | `db/game.db` | SQLite database path |
|
| `--db` | `db/game.db` | SQLite database path |
|
||||||
|
| `--auth-url` | *(empty)* | Auth server base URL (e.g. `http://localhost:3000`) |
|
||||||
|
|
||||||
|
### CDN Flags (`octo-cdn`)
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
| --------------- | ----------------- | -------------------------------------------------------- |
|
||||||
|
| `--listen` | `0.0.0.0:8080` | local bind address |
|
||||||
|
| `--public-addr` | `127.0.0.1:8080` | externally-reachable address (used in list.bin rewriting) |
|
||||||
|
| `--assets-dir` | `.` | root directory containing the `assets/` tree |
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
Migrations run automatically on container start.
|
Three services are available via Docker Compose: the game server (`lunar-tear`), the CDN (`octo-cdn`), and the auth server (`auth-server`). Migrations run automatically on game server start.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd server
|
cd server
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
The `db/` directory is mounted as a volume so the database persists across restarts. Make sure `assets/` is populated before starting.
|
The `db/` directory is mounted as a volume so both `game.db` and `auth.db` persist across restarts. Make sure `assets/` is populated before starting.
|
||||||
|
|
||||||
|
Each service has its own image and can be deployed independently:
|
||||||
|
|
||||||
|
| Service | Image | Default Port | Notes |
|
||||||
|
| -------- | --------------------------- | ------------ | ------------------------------ |
|
||||||
|
| `server` | `kretts/lunar-tear:latest` | 8003 | gRPC game server |
|
||||||
|
| `cdn` | `kretts/octo-cdn:latest` | 8080 | HTTP asset CDN |
|
||||||
|
| `auth` | `kretts/auth-server:latest` | 3000 | Account registration and login |
|
||||||
|
|
||||||
|
The game server is configured via environment variables in the compose file: `LUNAR_LISTEN` (bind address), `LUNAR_PUBLIC_ADDR` (client-facing address), `LUNAR_OCTO_URL`, and `LUNAR_AUTH_URL`. Auth is optional — if `LUNAR_AUTH_URL` is unset the game server starts without it.
|
||||||
|
|
||||||
### Makefile Targets
|
### Makefile Targets
|
||||||
|
|
||||||
@@ -115,11 +191,54 @@ All targets run from the `server/` directory.
|
|||||||
| Target | Description |
|
| Target | Description |
|
||||||
| -------------- | ------------------------------------------------------- |
|
| -------------- | ------------------------------------------------------- |
|
||||||
| `make proto` | Regenerate protobuf stubs |
|
| `make proto` | Regenerate protobuf stubs |
|
||||||
| `make build` | Build the server binary |
|
| `make build` | Build the game server binary |
|
||||||
|
| `make build-cdn` | Build the CDN binary |
|
||||||
|
| `make build-auth` | Build the auth server binary |
|
||||||
| `make build-import` | Build the import-snapshot tool |
|
| `make build-import` | Build the import-snapshot tool |
|
||||||
|
| `make build-claim-account` | Build the claim-account tool |
|
||||||
|
| `make dev` | Run all three services with one command |
|
||||||
| `make migrate` | Run goose migrations on `db/game.db` |
|
| `make migrate` | Run goose migrations on `db/game.db` |
|
||||||
| `make import` | Import a snapshot (`SNAPSHOT=... UUID=...` required) |
|
| `make import` | Import a snapshot (`SNAPSHOT=... UUID=...` required) |
|
||||||
|
|
||||||
|
## Claim Account
|
||||||
|
|
||||||
|
Transfers an existing game account to the most recently connected client. Looks up a player by their in-game name, assigns the new client's UUID to that account, and deletes the empty account the new client created.
|
||||||
|
|
||||||
|
Useful when a new client connects and creates a throwaway account, but you want it to load an existing account instead.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
go run ./cmd/claim-account --name "PlayerName" --db db/game.db
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
| -------- | ------------ | ---------------------------------------------------- |
|
||||||
|
| `--name` | *(required)* | In-game player name to claim |
|
||||||
|
| `--db` | `db/game.db` | SQLite database path |
|
||||||
|
|
||||||
|
## Auth Server
|
||||||
|
|
||||||
|
A separate HTTP server that handles player account registration and login. The patched client's Facebook login button is redirected to this server, which presents a username/password form. Tokens issued here are validated by the game server to link or recover accounts.
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
go run ./cmd/auth-server \
|
||||||
|
--listen 0.0.0.0:3000 \
|
||||||
|
--db db/auth.db
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--secret` flag accepts a hex-encoded HMAC key. If omitted, a random key is generated on startup and printed to the console — pass it back on the next restart to keep existing tokens valid.
|
||||||
|
|
||||||
|
### Flags
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
| ---------- | --------------- | -------------------------------------------- |
|
||||||
|
| `--listen` | `0.0.0.0:3000` | HTTP listen address (host:port) |
|
||||||
|
| `--db` | `db/auth.db` | SQLite database path for auth users |
|
||||||
|
| `--secret` | *(generated)* | Hex-encoded HMAC secret for token signing |
|
||||||
|
|
||||||
## ⚠️ Legal Disclaimer
|
## ⚠️ Legal Disclaimer
|
||||||
|
|
||||||
**Lunar Tear** is a fan-made, non-commercial **preservation and research project** dedicated to keeping a certain discontinued mobile game playable for educational and archival purposes.
|
**Lunar Tear** is a fan-made, non-commercial **preservation and research project** dedicated to keeping a certain discontinued mobile game playable for educational and archival purposes.
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
FROM alpine:latest AS builder
|
||||||
|
|
||||||
|
WORKDIR /usr/local/src
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN apk add --no-cache go
|
||||||
|
|
||||||
|
RUN go build -o auth-server ./cmd/auth-server
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
WORKDIR /opt/auth-server
|
||||||
|
|
||||||
|
RUN chown 1000:1000 /opt/auth-server
|
||||||
|
|
||||||
|
USER 1000
|
||||||
|
|
||||||
|
COPY --from=builder /usr/local/src/auth-server .
|
||||||
|
|
||||||
|
ENTRYPOINT ["./auth-server"]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
FROM alpine:latest AS builder
|
||||||
|
|
||||||
|
WORKDIR /usr/local/src
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
protobuf \
|
||||||
|
protobuf-dev \
|
||||||
|
protoc \
|
||||||
|
make \
|
||||||
|
go \
|
||||||
|
libcap
|
||||||
|
|
||||||
|
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest &&\
|
||||||
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest &&\
|
||||||
|
PATH="$PATH:$(go env GOPATH)/bin" make proto &&\
|
||||||
|
go build -o octo-cdn ./cmd/octo-cdn &&\
|
||||||
|
setcap cap_net_bind_service=+ep ./octo-cdn
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
WORKDIR /opt/octo-cdn
|
||||||
|
|
||||||
|
RUN chown 1000:1000 /opt/octo-cdn
|
||||||
|
|
||||||
|
USER 1000
|
||||||
|
|
||||||
|
COPY --from=builder /usr/local/src/octo-cdn .
|
||||||
|
|
||||||
|
ENTRYPOINT ["./octo-cdn"]
|
||||||
+25
-4
@@ -3,19 +3,40 @@
|
|||||||
# (generating all would put them in one package and cause name clashes).
|
# (generating all would put them in one package and cause name clashes).
|
||||||
PROTO_USED = proto/banner.proto proto/battle.proto proto/bighunt.proto proto/cageornament.proto proto/character.proto proto/characterboard.proto proto/characterviewer.proto proto/companion.proto proto/config.proto proto/consumableitem.proto proto/contentsstory.proto proto/costume.proto proto/data.proto proto/deck.proto proto/dokan.proto proto/explore.proto proto/friend.proto proto/gacha.proto proto/gameplay.proto proto/gift.proto proto/gimmick.proto proto/labyrinth.proto proto/loginbonus.proto proto/material.proto proto/mission.proto proto/movie.proto proto/navicutin.proto proto/omikuji.proto proto/notification.proto proto/parts.proto proto/portalcage.proto proto/pvp.proto proto/quest.proto proto/reward.proto proto/shop.proto proto/sidestoryquest.proto proto/tutorial.proto proto/user.proto proto/weapon.proto
|
PROTO_USED = proto/banner.proto proto/battle.proto proto/bighunt.proto proto/cageornament.proto proto/character.proto proto/characterboard.proto proto/characterviewer.proto proto/companion.proto proto/config.proto proto/consumableitem.proto proto/contentsstory.proto proto/costume.proto proto/data.proto proto/deck.proto proto/dokan.proto proto/explore.proto proto/friend.proto proto/gacha.proto proto/gameplay.proto proto/gift.proto proto/gimmick.proto proto/labyrinth.proto proto/loginbonus.proto proto/material.proto proto/mission.proto proto/movie.proto proto/navicutin.proto proto/omikuji.proto proto/notification.proto proto/parts.proto proto/portalcage.proto proto/pvp.proto proto/quest.proto proto/reward.proto proto/shop.proto proto/sidestoryquest.proto proto/tutorial.proto proto/user.proto proto/weapon.proto
|
||||||
|
|
||||||
|
EXE =
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE = .exe
|
||||||
|
endif
|
||||||
|
|
||||||
proto:
|
proto:
|
||||||
protoc -I . $(PROTO_USED) --go_out=. --go_opt=module=lunar-tear/server --go-grpc_out=. --go-grpc_opt=module=lunar-tear/server
|
protoc -I . $(PROTO_USED) --go_out=. --go_opt=module=lunar-tear/server --go-grpc_out=. --go-grpc_opt=module=lunar-tear/server
|
||||||
@echo "Generated in gen/proto/"
|
@echo "Generated in gen/proto/"
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build -o lunar-tear ./cmd/lunar-tear
|
go build -o lunar-tear$(EXE) ./cmd/lunar-tear
|
||||||
|
|
||||||
|
build-cdn:
|
||||||
|
go build -o octo-cdn$(EXE) ./cmd/octo-cdn
|
||||||
|
|
||||||
build-import:
|
build-import:
|
||||||
go build -o import-snapshot ./cmd/import-snapshot
|
go build -o import-snapshot$(EXE) ./cmd/import-snapshot
|
||||||
|
|
||||||
|
build-auth:
|
||||||
|
go build -o auth-server$(EXE) ./cmd/auth-server
|
||||||
|
|
||||||
|
build-claim-account:
|
||||||
|
go build -o claim-account$(EXE) ./cmd/claim-account
|
||||||
|
|
||||||
|
dev:
|
||||||
|
go run ./cmd/dev $(ARGS)
|
||||||
|
|
||||||
migrate:
|
migrate:
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
if not exist db mkdir db
|
||||||
|
else
|
||||||
mkdir -p db
|
mkdir -p db
|
||||||
goose -dir migrations sqlite3 db/game.db up
|
endif
|
||||||
|
goose -dir migrations -allow-missing sqlite3 db/game.db up
|
||||||
|
|
||||||
import:
|
import:
|
||||||
ifndef SNAPSHOT
|
ifndef SNAPSHOT
|
||||||
@@ -26,4 +47,4 @@ ifndef UUID
|
|||||||
endif
|
endif
|
||||||
go run ./cmd/import-snapshot --snapshot $(SNAPSHOT) --uuid $(UUID)
|
go run ./cmd/import-snapshot --snapshot $(SNAPSHOT) --uuid $(UUID)
|
||||||
|
|
||||||
.PHONY: proto build build-import migrate import
|
.PHONY: proto build build-cdn build-auth build-import build-claim-account dev migrate import
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed login.html
|
||||||
|
var loginFS embed.FS
|
||||||
|
|
||||||
|
var loginTmpl = template.Must(template.ParseFS(loginFS, "login.html"))
|
||||||
|
|
||||||
|
type Handlers struct {
|
||||||
|
store *AuthStore
|
||||||
|
tok *TokenService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandlers(store *AuthStore, tok *TokenService) *Handlers {
|
||||||
|
return &Handlers{store: store, tok: tok}
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginPageData struct {
|
||||||
|
RedirectURI string
|
||||||
|
State string
|
||||||
|
Error string
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
func isOAuthPath(path string) bool {
|
||||||
|
// Match /v{N}/dialog/oauth or /v{N}.{M}/dialog/oauth
|
||||||
|
parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(parts[0], "v") && parts[1] == "dialog" && parts[2] == "oauth"
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMePath(path string) bool {
|
||||||
|
p := strings.TrimPrefix(path, "/")
|
||||||
|
if p == "me" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
parts := strings.Split(p, "/")
|
||||||
|
return len(parts) == 2 && strings.HasPrefix(parts[0], "v") && parts[1] == "me"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handlers) HandleOAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if isMePath(r.URL.Path) {
|
||||||
|
h.HandleMe(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isOAuthPath(r.URL.Path) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
h.oauthGet(w, r)
|
||||||
|
case http.MethodPost:
|
||||||
|
h.oauthPost(w, r)
|
||||||
|
default:
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handlers) oauthGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := loginPageData{
|
||||||
|
RedirectURI: r.URL.Query().Get("redirect_uri"),
|
||||||
|
State: r.URL.Query().Get("state"),
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if err := loginTmpl.Execute(w, data); err != nil {
|
||||||
|
log.Printf("render login page: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handlers) oauthPost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "bad form", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := strings.TrimSpace(r.FormValue("username"))
|
||||||
|
password := r.FormValue("password")
|
||||||
|
action := r.FormValue("action")
|
||||||
|
redirectURI := r.FormValue("redirect_uri")
|
||||||
|
state := r.FormValue("state")
|
||||||
|
|
||||||
|
renderErr := func(msg string) {
|
||||||
|
data := loginPageData{
|
||||||
|
RedirectURI: redirectURI,
|
||||||
|
State: state,
|
||||||
|
Error: msg,
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
if err := loginTmpl.Execute(w, data); err != nil {
|
||||||
|
log.Printf("render login page: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if username == "" || password == "" {
|
||||||
|
renderErr("Username and password are required.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var user AuthUser
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "register":
|
||||||
|
user, err = h.store.CreateUser(username, password)
|
||||||
|
if err == ErrUserExists {
|
||||||
|
renderErr("Username is already taken.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("create user: %v", err)
|
||||||
|
renderErr("Server error. Try again.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("registered user %q (id=%d)", user.Username, user.ID)
|
||||||
|
|
||||||
|
case "login":
|
||||||
|
user, err = h.store.VerifyUser(username, password)
|
||||||
|
if err == ErrInvalidCreds {
|
||||||
|
renderErr("Invalid username or password.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("verify user: %v", err)
|
||||||
|
renderErr("Server error. Try again.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("authenticated user %q (id=%d)", user.Username, user.ID)
|
||||||
|
|
||||||
|
default:
|
||||||
|
renderErr("Invalid action.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := h.tok.Generate(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("generate token: %v", err)
|
||||||
|
renderErr("Server error. Try again.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := fmt.Sprintf(`{"user_id":"%d"}`, user.ID)
|
||||||
|
b64 := base64.StdEncoding.EncodeToString([]byte(payload))
|
||||||
|
|
||||||
|
fragment := url.Values{}
|
||||||
|
fragment.Set("access_token", token)
|
||||||
|
fragment.Set("token_type", "bearer")
|
||||||
|
fragment.Set("expires_in", strconv.FormatInt(int64(tokenTTL.Seconds()), 10))
|
||||||
|
fragment.Set("signed_request", "0."+b64)
|
||||||
|
if state != "" {
|
||||||
|
fragment.Set("state", state)
|
||||||
|
}
|
||||||
|
|
||||||
|
target := redirectURI + "?" + fragment.Encode()
|
||||||
|
log.Printf("redirecting to %s", target)
|
||||||
|
http.Redirect(w, r, target, http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handlers) HandleCheckUsername(w http.ResponseWriter, r *http.Request) {
|
||||||
|
username := strings.TrimSpace(r.URL.Query().Get("username"))
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if username == "" {
|
||||||
|
json.NewEncoder(w).Encode(map[string]bool{"exists": false})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(map[string]bool{"exists": h.store.UserExists(username)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handlers) HandleMe(w http.ResponseWriter, r *http.Request) {
|
||||||
|
token := r.URL.Query().Get("access_token")
|
||||||
|
if token == "" {
|
||||||
|
auth := r.Header.Get("Authorization")
|
||||||
|
if strings.HasPrefix(auth, "Bearer ") {
|
||||||
|
token = auth[7:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if token == "" {
|
||||||
|
http.Error(w, `{"error":{"message":"missing access_token","type":"OAuthException","code":190}}`, http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, err := h.tok.Validate(token)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf(`{"error":{"message":"%s","type":"OAuthException","code":190}}`, err), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{
|
||||||
|
"id": strconv.FormatInt(claims.Sub, 10),
|
||||||
|
"name": claims.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Lunar Tear – Login</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||||
|
background: #0a0a0a;
|
||||||
|
color: #e0e0e0;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: #161616;
|
||||||
|
border: 1px solid #2a2a2a;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 40px 32px 32px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 360px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #c8c8c8;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 3px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background: #2a1515;
|
||||||
|
border: 1px solid #5a2020;
|
||||||
|
color: #e08080;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: #0e0e0e;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #e0e0e0;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
input:focus { border-color: #666; }
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 11px 0;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, border-color 0.2s, opacity 0.3s;
|
||||||
|
}
|
||||||
|
.btn-login {
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #111;
|
||||||
|
border-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
.btn-login:hover { background: #fff; border-color: #fff; }
|
||||||
|
.btn-register {
|
||||||
|
background: transparent;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
.btn-register:hover { border-color: #666; color: #e0e0e0; }
|
||||||
|
.hidden { display: none; }
|
||||||
|
@media (max-height: 480px) {
|
||||||
|
body { align-items: stretch; padding: 0; }
|
||||||
|
.card {
|
||||||
|
max-width: none; border-radius: 0; border: none;
|
||||||
|
min-height: 100vh; min-height: 100dvh;
|
||||||
|
padding: 20px 24px;
|
||||||
|
display: flex; flex-direction: column; justify-content: center;
|
||||||
|
}
|
||||||
|
h1 { font-size: 22px; margin-bottom: 4px; }
|
||||||
|
.subtitle { margin-bottom: 16px; }
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"] { padding: 8px 10px; margin-bottom: 12px; }
|
||||||
|
.buttons { margin-top: 4px; }
|
||||||
|
button { padding: 9px 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form class="card" method="POST">
|
||||||
|
<h1>Lunar Tear</h1>
|
||||||
|
<div class="subtitle" id="subtitle">Authentication</div>
|
||||||
|
|
||||||
|
{{if .Error}}
|
||||||
|
<div class="error">{{.Error}}</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<input type="hidden" name="redirect_uri" value="{{.RedirectURI}}">
|
||||||
|
<input type="hidden" name="state" value="{{.State}}">
|
||||||
|
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username" value="{{.Username}}" autocomplete="username" autofocus required>
|
||||||
|
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" autocomplete="current-password" required>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="submit" name="action" value="login" class="btn-login hidden" id="btn-login">Login</button>
|
||||||
|
<button type="submit" name="action" value="register" class="btn-register hidden" id="btn-register">Create Account</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var input = document.getElementById('username');
|
||||||
|
var btnLogin = document.getElementById('btn-login');
|
||||||
|
var btnRegister = document.getElementById('btn-register');
|
||||||
|
var subtitle = document.getElementById('subtitle');
|
||||||
|
var timer = null;
|
||||||
|
var lastChecked = '';
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
var name = input.value.trim();
|
||||||
|
if (name === '') {
|
||||||
|
btnLogin.classList.add('hidden');
|
||||||
|
btnRegister.classList.add('hidden');
|
||||||
|
subtitle.textContent = 'Authentication';
|
||||||
|
lastChecked = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (name === lastChecked) return;
|
||||||
|
lastChecked = name;
|
||||||
|
|
||||||
|
fetch('/check-username?username=' + encodeURIComponent(name))
|
||||||
|
.then(function(r) { return r.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (input.value.trim() !== name) return;
|
||||||
|
if (data.exists) {
|
||||||
|
btnLogin.classList.remove('hidden');
|
||||||
|
btnRegister.classList.add('hidden');
|
||||||
|
subtitle.textContent = 'Welcome back';
|
||||||
|
} else {
|
||||||
|
btnLogin.classList.add('hidden');
|
||||||
|
btnRegister.classList.remove('hidden');
|
||||||
|
subtitle.textContent = 'Create your account';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function() {
|
||||||
|
btnLogin.classList.remove('hidden');
|
||||||
|
btnRegister.classList.remove('hidden');
|
||||||
|
subtitle.textContent = 'Authentication';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('input', function() {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(check, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('blur', function() {
|
||||||
|
clearTimeout(timer);
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (input.value.trim() !== '') check();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
listen := flag.String("listen", "0.0.0.0:3000", "HTTP listen address (host:port)")
|
||||||
|
dbPath := flag.String("db", "db/auth.db", "SQLite database path for auth users")
|
||||||
|
secret := flag.String("secret", "", "HMAC secret for tokens (auto-generated if empty)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
hmacSecret := []byte(*secret)
|
||||||
|
if len(hmacSecret) == 0 {
|
||||||
|
hmacSecret = make([]byte, 32)
|
||||||
|
if _, err := rand.Read(hmacSecret); err != nil {
|
||||||
|
log.Fatalf("generate secret: %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("generated HMAC secret: %s", hex.EncodeToString(hmacSecret))
|
||||||
|
log.Printf("pass --secret=%s to reuse across restarts", hex.EncodeToString(hmacSecret))
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", *dbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("open database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
store, err := NewAuthStore(db)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("init auth store: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tok := NewTokenService(hmacSecret)
|
||||||
|
h := NewHandlers(store, tok)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", h.HandleOAuth)
|
||||||
|
mux.HandleFunc("/me", h.HandleMe)
|
||||||
|
mux.HandleFunc("/check-username", h.HandleCheckUsername)
|
||||||
|
|
||||||
|
log.Printf("auth server listening on %s", *listen)
|
||||||
|
if err := http.ListenAndServe(*listen, mux); err != nil {
|
||||||
|
log.Fatalf("listen: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUserExists = errors.New("username already taken")
|
||||||
|
ErrInvalidCreds = errors.New("invalid username or password")
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthUser struct {
|
||||||
|
ID int64
|
||||||
|
Username string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthStore struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthStore(db *sql.DB) (*AuthStore, error) {
|
||||||
|
_, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS auth_users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
password BLOB NOT NULL,
|
||||||
|
created_at TEXT NOT NULL
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create auth_users table: %w", err)
|
||||||
|
}
|
||||||
|
return &AuthStore{db: db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AuthStore) CreateUser(username, password string) (AuthUser, error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return AuthUser{}, fmt.Errorf("hash password: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.db.Exec(
|
||||||
|
`INSERT INTO auth_users (username, password, created_at) VALUES (?, ?, ?)`,
|
||||||
|
username, hash, time.Now().UTC().Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if isUniqueViolation(err) {
|
||||||
|
return AuthUser{}, ErrUserExists
|
||||||
|
}
|
||||||
|
return AuthUser{}, fmt.Errorf("insert user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, _ := res.LastInsertId()
|
||||||
|
return AuthUser{ID: id, Username: username}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AuthStore) VerifyUser(username, password string) (AuthUser, error) {
|
||||||
|
var id int64
|
||||||
|
var hash []byte
|
||||||
|
err := s.db.QueryRow(
|
||||||
|
`SELECT id, password FROM auth_users WHERE username = ?`, username,
|
||||||
|
).Scan(&id, &hash)
|
||||||
|
if err != nil {
|
||||||
|
return AuthUser{}, ErrInvalidCreds
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword(hash, []byte(password)); err != nil {
|
||||||
|
return AuthUser{}, ErrInvalidCreds
|
||||||
|
}
|
||||||
|
|
||||||
|
return AuthUser{ID: id, Username: username}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AuthStore) UserExists(username string) bool {
|
||||||
|
var n int
|
||||||
|
err := s.db.QueryRow(`SELECT 1 FROM auth_users WHERE username = ?`, username).Scan(&n)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUniqueViolation(err error) bool {
|
||||||
|
return err != nil && (errors.Is(err, sql.ErrNoRows) ||
|
||||||
|
// modernc.org/sqlite returns error strings containing "UNIQUE constraint failed"
|
||||||
|
fmt.Sprintf("%v", err) == fmt.Sprintf("%v", err) &&
|
||||||
|
contains(err.Error(), "UNIQUE constraint failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(s, substr string) bool {
|
||||||
|
return len(s) >= len(substr) && searchStr(s, substr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchStr(s, sub string) bool {
|
||||||
|
for i := 0; i <= len(s)-len(sub); i++ {
|
||||||
|
if s[i:i+len(sub)] == sub {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tokenTTL = 24 * time.Hour
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTokenInvalid = errors.New("invalid token")
|
||||||
|
ErrTokenExpired = errors.New("token expired")
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenClaims struct {
|
||||||
|
Sub int64 `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Iat int64 `json:"iat"`
|
||||||
|
Exp int64 `json:"exp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenService struct {
|
||||||
|
secret []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTokenService(secret []byte) *TokenService {
|
||||||
|
return &TokenService{secret: secret}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenService) Generate(user AuthUser) (string, error) {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
claims := TokenClaims{
|
||||||
|
Sub: user.ID,
|
||||||
|
Name: user.Username,
|
||||||
|
Iat: now,
|
||||||
|
Exp: now + int64(tokenTTL.Seconds()),
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(claims)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("marshal claims: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := base64.RawURLEncoding
|
||||||
|
payloadB64 := enc.EncodeToString(payload)
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, t.secret)
|
||||||
|
mac.Write(payload)
|
||||||
|
sig := enc.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
|
return payloadB64 + "." + sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TokenService) Validate(token string) (TokenClaims, error) {
|
||||||
|
dot := -1
|
||||||
|
for i := range token {
|
||||||
|
if token[i] == '.' {
|
||||||
|
dot = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dot < 0 {
|
||||||
|
return TokenClaims{}, ErrTokenInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadB64 := token[:dot]
|
||||||
|
sigB64 := token[dot+1:]
|
||||||
|
|
||||||
|
enc := base64.RawURLEncoding
|
||||||
|
|
||||||
|
payload, err := enc.DecodeString(payloadB64)
|
||||||
|
if err != nil {
|
||||||
|
return TokenClaims{}, ErrTokenInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := enc.DecodeString(sigB64)
|
||||||
|
if err != nil {
|
||||||
|
return TokenClaims{}, ErrTokenInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, t.secret)
|
||||||
|
mac.Write(payload)
|
||||||
|
if !hmac.Equal(mac.Sum(nil), sig) {
|
||||||
|
return TokenClaims{}, ErrTokenInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var claims TokenClaims
|
||||||
|
if err := json.Unmarshal(payload, &claims); err != nil {
|
||||||
|
return TokenClaims{}, ErrTokenInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().Unix() > claims.Exp {
|
||||||
|
return TokenClaims{}, ErrTokenExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var childTables = []string{
|
||||||
|
"user_cage_ornament_rewards",
|
||||||
|
"user_shop_replaceable_lineup",
|
||||||
|
"user_shop_items",
|
||||||
|
"user_gacha_banner_box_drew_counts",
|
||||||
|
"user_gacha_banners",
|
||||||
|
"user_gacha_converted_medals",
|
||||||
|
"user_gifts",
|
||||||
|
"user_dokan_confirmed",
|
||||||
|
"user_drawn_omikuji",
|
||||||
|
"user_contents_stories",
|
||||||
|
"user_viewed_movies",
|
||||||
|
"user_navi_cutin_played",
|
||||||
|
"user_auto_sale_settings",
|
||||||
|
"user_explore_scores",
|
||||||
|
"user_tutorials",
|
||||||
|
"user_premium_items",
|
||||||
|
"user_important_items",
|
||||||
|
"user_materials",
|
||||||
|
"user_consumable_items",
|
||||||
|
"user_gimmick_unlocks",
|
||||||
|
"user_gimmick_sequences",
|
||||||
|
"user_gimmick_ornament_progress",
|
||||||
|
"user_gimmick_progress",
|
||||||
|
"user_big_hunt_weekly_statuses",
|
||||||
|
"user_big_hunt_weekly_max_scores",
|
||||||
|
"user_big_hunt_schedule_max_scores",
|
||||||
|
"user_big_hunt_statuses",
|
||||||
|
"user_big_hunt_max_scores",
|
||||||
|
"user_quest_limit_content_status",
|
||||||
|
"user_side_story_quests",
|
||||||
|
"user_missions",
|
||||||
|
"user_quest_missions",
|
||||||
|
"user_quests",
|
||||||
|
"user_deck_type_notes",
|
||||||
|
"user_deck_parts",
|
||||||
|
"user_deck_sub_weapons",
|
||||||
|
"user_decks",
|
||||||
|
"user_deck_characters",
|
||||||
|
"user_parts_presets",
|
||||||
|
"user_parts_group_notes",
|
||||||
|
"user_parts",
|
||||||
|
"user_thoughts",
|
||||||
|
"user_companions",
|
||||||
|
"user_weapon_notes",
|
||||||
|
"user_weapon_stories",
|
||||||
|
"user_weapon_awakens",
|
||||||
|
"user_weapon_abilities",
|
||||||
|
"user_weapon_skills",
|
||||||
|
"user_weapons",
|
||||||
|
"user_costume_awaken_status_ups",
|
||||||
|
"user_costume_active_skills",
|
||||||
|
"user_costumes",
|
||||||
|
"user_character_rebirths",
|
||||||
|
"user_character_board_status_ups",
|
||||||
|
"user_character_board_abilities",
|
||||||
|
"user_character_boards",
|
||||||
|
"user_characters",
|
||||||
|
"user_gacha",
|
||||||
|
"user_shop_replaceable",
|
||||||
|
"user_explore",
|
||||||
|
"user_guerrilla_free_open",
|
||||||
|
"user_portal_cage",
|
||||||
|
"user_notification",
|
||||||
|
"user_battle",
|
||||||
|
"user_big_hunt_state",
|
||||||
|
"user_side_story_active",
|
||||||
|
"user_extra_quest",
|
||||||
|
"user_event_quest",
|
||||||
|
"user_main_quest",
|
||||||
|
"user_login_bonus",
|
||||||
|
"user_login",
|
||||||
|
"user_profile",
|
||||||
|
"user_gem",
|
||||||
|
"user_status",
|
||||||
|
"user_setting",
|
||||||
|
"sessions",
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
||||||
|
name := flag.String("name", "", "In-game player name to look up in user_profile (required)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *name == "" {
|
||||||
|
log.Fatal("--name flag is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := database.Open(*dbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("open database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
var targetId int64
|
||||||
|
err = db.QueryRow(`SELECT user_id FROM user_profile WHERE name = ?`, *name).Scan(&targetId)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
log.Fatalf("no user found with name %q", *name)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("query user_profile: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetUuid string
|
||||||
|
err = db.QueryRow(`SELECT uuid FROM users WHERE user_id = ?`, targetId).Scan(&targetUuid)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("query target uuid: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestId int64
|
||||||
|
var latestUuid string
|
||||||
|
err = db.QueryRow(`SELECT user_id, uuid FROM users ORDER BY user_id DESC LIMIT 1`).Scan(&latestId, &latestUuid)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("query latest user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetId == latestId {
|
||||||
|
log.Printf("user %q (id=%d) is already the most recent user, nothing to do", *name, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("target: id=%d uuid=%s (name=%q)", targetId, targetUuid, *name)
|
||||||
|
log.Printf("latest: id=%d uuid=%s (will be deleted)", latestId, latestUuid)
|
||||||
|
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("begin transaction: %v", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
for _, t := range childTables {
|
||||||
|
if _, err := tx.Exec(fmt.Sprintf(`DELETE FROM %s WHERE user_id = ?`, t), latestId); err != nil {
|
||||||
|
log.Fatalf("delete from %s: %v", t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := tx.Exec(`DELETE FROM users WHERE user_id = ?`, latestId); err != nil {
|
||||||
|
log.Fatalf("delete latest user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tx.Exec(`UPDATE users SET uuid = ? WHERE user_id = ?`, latestUuid, targetId); err != nil {
|
||||||
|
log.Fatalf("update target uuid: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tx.Exec(`DELETE FROM sessions WHERE user_id = ?`, targetId); err != nil {
|
||||||
|
log.Fatalf("delete stale sessions: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
log.Fatalf("commit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("claimed account:\n")
|
||||||
|
fmt.Printf(" user %d (%s): uuid changed %s -> %s\n", targetId, *name, targetUuid, latestUuid)
|
||||||
|
fmt.Printf(" user %d: deleted\n", latestId)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
func colorSupported() bool {
|
||||||
|
return term.IsTerminal(int(os.Stdout.Fd()))
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
func colorSupported() bool {
|
||||||
|
fd := os.Stdout.Fd()
|
||||||
|
if !term.IsTerminal(int(fd)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var mode uint32
|
||||||
|
if windows.GetConsoleMode(windows.Handle(fd), &mode) != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return windows.SetConsoleMode(windows.Handle(fd), mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
colorReset = "\033[0m"
|
||||||
|
colorRed = "\033[31m"
|
||||||
|
colorGreen = "\033[32m"
|
||||||
|
colorYellow = "\033[33m"
|
||||||
|
colorCyan = "\033[36m"
|
||||||
|
)
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
label string
|
||||||
|
color string
|
||||||
|
cmd *exec.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// auth-server flags
|
||||||
|
authListen := flag.String("auth.listen", "0.0.0.0:3000", "auth-server listen address (host:port)")
|
||||||
|
authDB := flag.String("auth.db", "db/auth.db", "auth-server SQLite database path")
|
||||||
|
|
||||||
|
// octo-cdn flags
|
||||||
|
cdnListen := flag.String("cdn.listen", "0.0.0.0:8080", "octo-cdn local bind address")
|
||||||
|
cdnPublicAddr := flag.String("cdn.public-addr", "10.0.2.2:8080", "octo-cdn externally-reachable address")
|
||||||
|
|
||||||
|
// lunar-tear (grpc) flags
|
||||||
|
grpcListen := flag.String("grpc.listen", "0.0.0.0:8003", "lunar-tear gRPC listen address (host:port)")
|
||||||
|
grpcPublicAddr := flag.String("grpc.public-addr", "10.0.2.2:8003", "lunar-tear externally-reachable address")
|
||||||
|
grpcOctoURL := flag.String("grpc.octo-url", "", "Octo CDN base URL passed to lunar-tear (default: derived from cdn.public-addr)")
|
||||||
|
grpcAuthURL := flag.String("grpc.auth-url", "", "auth server base URL passed to lunar-tear (default: derived from auth.listen)")
|
||||||
|
|
||||||
|
noColor := flag.Bool("no-color", false, "disable colored output")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *grpcOctoURL == "" {
|
||||||
|
*grpcOctoURL = fmt.Sprintf("http://%s", *cdnPublicAddr)
|
||||||
|
}
|
||||||
|
if *grpcAuthURL == "" {
|
||||||
|
*grpcAuthURL = fmt.Sprintf("http://%s", *authListen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *noColor || !colorSupported() {
|
||||||
|
colorReset = ""
|
||||||
|
colorRed = ""
|
||||||
|
colorGreen = ""
|
||||||
|
colorYellow = ""
|
||||||
|
colorCyan = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
services := []service{
|
||||||
|
{
|
||||||
|
label: "auth",
|
||||||
|
color: colorGreen,
|
||||||
|
cmd: exec.CommandContext(ctx, "go", "run", "./cmd/auth-server",
|
||||||
|
"--listen", *authListen,
|
||||||
|
"--db", *authDB,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "cdn",
|
||||||
|
color: colorCyan,
|
||||||
|
cmd: exec.CommandContext(ctx, "go", "run", "./cmd/octo-cdn",
|
||||||
|
"--listen", *cdnListen,
|
||||||
|
"--public-addr", *cdnPublicAddr,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "grpc",
|
||||||
|
color: colorYellow,
|
||||||
|
cmd: exec.CommandContext(ctx, "go", "run", "./cmd/lunar-tear",
|
||||||
|
"--listen", *grpcListen,
|
||||||
|
"--public-addr", *grpcPublicAddr,
|
||||||
|
"--octo-url", *grpcOctoURL,
|
||||||
|
"--auth-url", *grpcAuthURL,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errCh := make(chan error, len(services))
|
||||||
|
|
||||||
|
for i := range services {
|
||||||
|
svc := &services[i]
|
||||||
|
stdout, err := svc.cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("[%s] stdout pipe: %v", svc.label, err)
|
||||||
|
}
|
||||||
|
stderr, err := svc.cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("[%s] stderr pipe: %v", svc.label, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.cmd.Start(); err != nil {
|
||||||
|
log.Fatalf("[%s] start: %v", svc.label, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := fmt.Sprintf("%s[%s]%s ", svc.color, svc.label, colorReset)
|
||||||
|
wg.Add(2)
|
||||||
|
go prefixLines(&wg, prefix, stdout)
|
||||||
|
go prefixLines(&wg, prefix, stderr)
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(s *service) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := s.cmd.Wait(); err != nil {
|
||||||
|
errCh <- fmt.Errorf("[%s] %w", s.label, err)
|
||||||
|
}
|
||||||
|
}(svc)
|
||||||
|
|
||||||
|
log.Printf("%s%s started (pid %d)%s", svc.color, svc.label, svc.cmd.Process.Pid, colorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Println("shutting down all services...")
|
||||||
|
case err := <-errCh:
|
||||||
|
log.Printf("%s%s%s", colorRed, err, colorReset)
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func prefixLines(wg *sync.WaitGroup, prefix string, r io.Reader) {
|
||||||
|
defer wg.Done()
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
fmt.Printf("%s%s\n", prefix, scanner.Text())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gacha"
|
"lunar-tear/server/internal/gacha"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/interceptor"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/service"
|
"lunar-tear/server/internal/service"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/metadata"
|
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type loggingListener struct {
|
type loggingListener struct {
|
||||||
@@ -36,9 +32,10 @@ func (l loggingListener) Accept() (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startGRPC(
|
func startGRPC(
|
||||||
host string,
|
listenAddr string,
|
||||||
grpcPort int,
|
publicAddr string,
|
||||||
octoURL string,
|
octoURL string,
|
||||||
|
authURL string,
|
||||||
userStore interface {
|
userStore interface {
|
||||||
store.UserRepository
|
store.UserRepository
|
||||||
store.SessionRepository
|
store.SessionRepository
|
||||||
@@ -64,23 +61,23 @@ func startGRPC(
|
|||||||
gameConfig *masterdata.GameConfig,
|
gameConfig *masterdata.GameConfig,
|
||||||
sideStoryCatalog *masterdata.SideStoryCatalog,
|
sideStoryCatalog *masterdata.SideStoryCatalog,
|
||||||
bigHuntCatalog *masterdata.BigHuntCatalog,
|
bigHuntCatalog *masterdata.BigHuntCatalog,
|
||||||
) {
|
) *grpc.Server {
|
||||||
addr := fmt.Sprintf(":%d", grpcPort)
|
lis, err := net.Listen("tcp", listenAddr)
|
||||||
lis, err := net.Listen("tcp", addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to listen on %s: %v", addr, err)
|
log.Fatalf("failed to listen on %s: %v", listenAddr, err)
|
||||||
}
|
}
|
||||||
lis = loggingListener{Listener: lis}
|
lis = loggingListener{Listener: lis}
|
||||||
|
|
||||||
|
diffInterceptor := interceptor.NewDiffInterceptor(userStore, userStore)
|
||||||
grpcServer := grpc.NewServer(
|
grpcServer := grpc.NewServer(
|
||||||
grpc.ChainUnaryInterceptor(loggingInterceptor, timeSyncInterceptor),
|
grpc.ChainUnaryInterceptor(interceptor.Platform, interceptor.Logging, diffInterceptor, interceptor.TimeSync),
|
||||||
grpc.UnknownServiceHandler(loggingUnknownService),
|
grpc.UnknownServiceHandler(interceptor.UnknownService),
|
||||||
)
|
)
|
||||||
|
|
||||||
registerServices(grpcServer,
|
registerServices(grpcServer,
|
||||||
host,
|
publicAddr,
|
||||||
grpcPort,
|
|
||||||
octoURL,
|
octoURL,
|
||||||
|
authURL,
|
||||||
userStore,
|
userStore,
|
||||||
questEngine,
|
questEngine,
|
||||||
gachaHandler,
|
gachaHandler,
|
||||||
@@ -107,19 +104,22 @@ func startGRPC(
|
|||||||
|
|
||||||
reflection.Register(grpcServer)
|
reflection.Register(grpcServer)
|
||||||
|
|
||||||
log.Printf("gRPC server listening on %s", addr)
|
log.Printf("gRPC server listening on %s", lis.Addr())
|
||||||
log.Printf("client host address: %s:%d", host, grpcPort)
|
log.Printf("public address: %s", publicAddr)
|
||||||
|
|
||||||
if err := grpcServer.Serve(lis); err != nil {
|
go func() {
|
||||||
log.Fatalf("failed to serve: %v", err)
|
if err := grpcServer.Serve(lis); err != nil {
|
||||||
}
|
log.Printf("gRPC server stopped: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return grpcServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerServices(
|
func registerServices(
|
||||||
srv *grpc.Server,
|
srv *grpc.Server,
|
||||||
host string,
|
publicAddr string,
|
||||||
grpcPort int,
|
|
||||||
octoURL string,
|
octoURL string,
|
||||||
|
authURL string,
|
||||||
userStore interface {
|
userStore interface {
|
||||||
store.UserRepository
|
store.UserRepository
|
||||||
store.SessionRepository
|
store.SessionRepository
|
||||||
@@ -146,10 +146,13 @@ func registerServices(
|
|||||||
sideStoryCatalog *masterdata.SideStoryCatalog,
|
sideStoryCatalog *masterdata.SideStoryCatalog,
|
||||||
bigHuntCatalog *masterdata.BigHuntCatalog,
|
bigHuntCatalog *masterdata.BigHuntCatalog,
|
||||||
) {
|
) {
|
||||||
|
pubHost, pubPortStr, _ := net.SplitHostPort(publicAddr)
|
||||||
|
pubPort, _ := strconv.Atoi(pubPortStr)
|
||||||
|
|
||||||
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(gachaEntries))
|
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(gachaEntries))
|
||||||
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore))
|
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore, authURL))
|
||||||
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
|
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
|
||||||
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(host, int32(grpcPort), octoURL))
|
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(pubHost, int32(pubPort), octoURL))
|
||||||
pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
|
pb.RegisterDataServiceServer(srv, service.NewDataServiceServer(userStore, userStore))
|
||||||
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, questEngine))
|
pb.RegisterTutorialServiceServer(srv, service.NewTutorialServiceServer(userStore, userStore, questEngine))
|
||||||
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, gachaEntries, gachaHandler))
|
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, gachaEntries, gachaHandler))
|
||||||
@@ -184,39 +187,3 @@ func registerServices(
|
|||||||
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, bigHuntCatalog, questEngine))
|
pb.RegisterBigHuntServiceServer(srv, service.NewBigHuntServiceServer(userStore, userStore, bigHuntCatalog, questEngine))
|
||||||
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, bigHuntCatalog, questEngine.Granter))
|
pb.RegisterRewardServiceServer(srv, service.NewRewardServiceServer(userStore, userStore, bigHuntCatalog, questEngine.Granter))
|
||||||
}
|
}
|
||||||
|
|
||||||
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
|
||||||
log.Printf(">>> %s", info.FullMethod)
|
|
||||||
resp, err := handler(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("<<< %s ERROR: %v", info.FullMethod, err)
|
|
||||||
} else {
|
|
||||||
log.Printf("<<< %s OK", info.FullMethod)
|
|
||||||
}
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func timeSyncInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
|
||||||
resp, err := handler(ctx, req)
|
|
||||||
switch info.FullMethod {
|
|
||||||
case "/apb.api.user.UserService/Auth",
|
|
||||||
"/apb.api.user.UserService/RegisterUser",
|
|
||||||
"/apb.api.user.UserService/TransferUser":
|
|
||||||
default:
|
|
||||||
grpc.SetTrailer(ctx, metadata.Pairs(
|
|
||||||
"x-apb-response-datetime", fmt.Sprintf("%d", gametime.NowMillis()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func loggingUnknownService(_ any, stream grpc.ServerStream) error {
|
|
||||||
fullMethod, ok := grpc.MethodFromServerStream(stream)
|
|
||||||
if !ok {
|
|
||||||
fullMethod = "<unknown>"
|
|
||||||
}
|
|
||||||
log.Printf(">>> %s", fullMethod)
|
|
||||||
err := status.Errorf(codes.Unimplemented, "unknown service or method %s", fullMethod)
|
|
||||||
log.Printf("<<< %s ERROR: %v", fullMethod, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"lunar-tear/server/internal/service"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
"golang.org/x/net/http2/h2c"
|
|
||||||
)
|
|
||||||
|
|
||||||
func startHTTP(port int, resourcesBaseURL string) {
|
|
||||||
octoServer := service.NewOctoHTTPServer(resourcesBaseURL)
|
|
||||||
h2s := &http2.Server{}
|
|
||||||
octoHandler := h2c.NewHandler(octoServer.Handler(), h2s)
|
|
||||||
log.Printf("Octo HTTP server listening on :%d (HTTP/1.1 + h2c)", port)
|
|
||||||
srv := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: octoHandler}
|
|
||||||
http2.ConfigureServer(srv, h2s)
|
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
|
||||||
log.Fatalf("HTTP server on %d failed: %v", port, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +1,41 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
"os"
|
||||||
"strings"
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"lunar-tear/server/internal/database"
|
"lunar-tear/server/internal/database"
|
||||||
"lunar-tear/server/internal/gacha"
|
"lunar-tear/server/internal/gacha"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
|
"lunar-tear/server/internal/masterdata/memorydb"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/store/sqlite"
|
"lunar-tear/server/internal/store/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
httpPort := flag.Int("http-port", 8080, "HTTP server port (Octo API)")
|
listen := flag.String("listen", "0.0.0.0:443", "gRPC listen address (host:port)")
|
||||||
grpcPort := flag.Int("grpc-port", 443, "gRPC server port")
|
publicAddr := flag.String("public-addr", "127.0.0.1:443", "externally-reachable host:port advertised to clients")
|
||||||
host := flag.String("host", "127.0.0.1", "hostname the client will connect to")
|
|
||||||
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
||||||
|
octoURL := flag.String("octo-url", "", "Octo CDN base URL the client will use for assets (e.g. http://10.0.2.2:8080)")
|
||||||
|
authURL := flag.String("auth-url", "", "Auth server base URL for Facebook token validation (e.g. http://localhost:3000)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
octoURL := "http://" + *host + ":" + strconv.Itoa(*httpPort)
|
if *octoURL == "" {
|
||||||
prefix := octoURL + "/"
|
log.Fatalf("--octo-url is required (e.g. http://10.0.2.2:8080)")
|
||||||
padLen := 43 - len(prefix)
|
|
||||||
resourcesBaseURL := ""
|
|
||||||
if padLen < 1 {
|
|
||||||
log.Printf("[config] host:port too long for 43-char resource URL; list.bin will be served unchanged")
|
|
||||||
} else {
|
|
||||||
resourcesBaseURL = prefix + strings.Repeat("r", padLen)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go startHTTP(*httpPort, resourcesBaseURL)
|
if err := memorydb.Init("assets/release/20240404193219.bin.e"); err != nil {
|
||||||
|
log.Fatalf("load master data: %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("master data loaded (%d tables)", memorydb.TableCount())
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
db, err := database.Open(*dbPath)
|
db, err := database.Open(*dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -164,10 +168,11 @@ func main() {
|
|||||||
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
|
sideStoryCatalog := masterdata.LoadSideStoryCatalog()
|
||||||
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
|
bigHuntCatalog := masterdata.LoadBigHuntCatalog()
|
||||||
|
|
||||||
startGRPC(
|
grpcServer := startGRPC(
|
||||||
*host,
|
*listen,
|
||||||
*grpcPort,
|
*publicAddr,
|
||||||
octoURL,
|
*octoURL,
|
||||||
|
*authURL,
|
||||||
userStore,
|
userStore,
|
||||||
questHandler,
|
questHandler,
|
||||||
gachaHandler,
|
gachaHandler,
|
||||||
@@ -191,4 +196,12 @@ func main() {
|
|||||||
sideStoryCatalog,
|
sideStoryCatalog,
|
||||||
bigHuntCatalog,
|
bigHuntCatalog,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
log.Println("shutting down...")
|
||||||
|
|
||||||
|
grpcServer.GracefulStop()
|
||||||
|
database.Checkpoint(db)
|
||||||
|
|
||||||
|
log.Println("shutdown complete")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/service"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
listen := flag.String("listen", "0.0.0.0:8080", "local bind address (host:port)")
|
||||||
|
publicAddr := flag.String("public-addr", "127.0.0.1:8080", "externally-reachable host:port used for list.bin URL rewriting")
|
||||||
|
assetsDir := flag.String("assets-dir", ".", "root directory containing the assets/ tree")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Build resourcesBaseURL from public-addr (must be exactly 43 chars to fit in list.bin protobuf).
|
||||||
|
prefix := "http://" + *publicAddr + "/"
|
||||||
|
padLen := 43 - len(prefix)
|
||||||
|
resourcesBaseURL := ""
|
||||||
|
if padLen < 1 {
|
||||||
|
log.Printf("[config] public-addr too long for 43-char resource URL; list.bin will be served unchanged")
|
||||||
|
} else {
|
||||||
|
resourcesBaseURL = prefix + strings.Repeat("r", padLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
octoServer := service.NewOctoHTTPServer(resourcesBaseURL, *assetsDir)
|
||||||
|
h2s := &http2.Server{}
|
||||||
|
handler := h2c.NewHandler(octoServer.Handler(), h2s)
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: *listen,
|
||||||
|
Handler: handler,
|
||||||
|
}
|
||||||
|
http2.ConfigureServer(srv, h2s)
|
||||||
|
|
||||||
|
// Resolve actual listen address for logging (useful when port is 0).
|
||||||
|
lis, err := net.Listen("tcp", *listen)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to listen on %s: %v", *listen, err)
|
||||||
|
}
|
||||||
|
log.Printf("Octo CDN listening on %s (HTTP/1.1 + h2c)", lis.Addr())
|
||||||
|
log.Printf("public address: %s", *publicAddr)
|
||||||
|
if *assetsDir != "." {
|
||||||
|
log.Printf("assets directory: %s", *assetsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := srv.Serve(lis); err != nil && err != http.ErrServerClosed {
|
||||||
|
fmt.Fprintf(os.Stderr, "HTTP server error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
log.Println("shutting down...")
|
||||||
|
srv.Shutdown(context.Background())
|
||||||
|
log.Println("shutdown complete")
|
||||||
|
}
|
||||||
@@ -1,15 +1,41 @@
|
|||||||
|
name: lunar-tear
|
||||||
|
|
||||||
services:
|
services:
|
||||||
server:
|
server:
|
||||||
build: .
|
build: .
|
||||||
image: kretts/lunar-tear:latest
|
image: kretts/lunar-tear:latest
|
||||||
environment:
|
environment:
|
||||||
LUNAR_HOST: 127.0.0.1
|
LUNAR_LISTEN: 0.0.0.0:8003
|
||||||
LUNAR_HTTP_PORT: 8080
|
LUNAR_PUBLIC_ADDR: 127.0.0.1:8003
|
||||||
LUNAR_GRPC_PORT: 8003
|
LUNAR_OCTO_URL: http://cdn:8080
|
||||||
|
LUNAR_AUTH_URL: http://auth:3000
|
||||||
volumes:
|
volumes:
|
||||||
- ./assets:/opt/lunar-tear/assets
|
|
||||||
- ./db:/opt/lunar-tear/db
|
- ./db:/opt/lunar-tear/db
|
||||||
|
- ./assets:/opt/lunar-tear/assets
|
||||||
ports:
|
ports:
|
||||||
- 8003:8003
|
- 8003:8003
|
||||||
|
depends_on:
|
||||||
|
- cdn
|
||||||
|
- auth
|
||||||
|
|
||||||
|
cdn:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.cdn
|
||||||
|
image: kretts/octo-cdn:latest
|
||||||
|
command: ["--listen", "0.0.0.0:8080", "--public-addr", "10.0.2.2:8080"]
|
||||||
|
volumes:
|
||||||
|
- ./assets:/opt/octo-cdn/assets
|
||||||
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
|
||||||
|
auth:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.auth
|
||||||
|
image: kretts/auth-server:latest
|
||||||
|
command: ["--listen", "0.0.0.0:3000", "--db", "db/auth.db"]
|
||||||
|
volumes:
|
||||||
|
- ./db:/opt/auth-server/db
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
|||||||
+10
-1
@@ -4,4 +4,13 @@ set -e
|
|||||||
mkdir -p db
|
mkdir -p db
|
||||||
goose -dir migrations sqlite3 db/game.db up
|
goose -dir migrations sqlite3 db/game.db up
|
||||||
|
|
||||||
exec ./lunar-tear --host "${LUNAR_HOST}" --http-port "${LUNAR_HTTP_PORT}" --grpc-port "${LUNAR_GRPC_PORT:-443}"
|
AUTH_FLAG=""
|
||||||
|
if [ -n "${LUNAR_AUTH_URL}" ]; then
|
||||||
|
AUTH_FLAG="--auth-url ${LUNAR_AUTH_URL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec ./lunar-tear \
|
||||||
|
--listen "${LUNAR_LISTEN:-0.0.0.0:443}" \
|
||||||
|
--public-addr "${LUNAR_PUBLIC_ADDR}" \
|
||||||
|
--octo-url "${LUNAR_OCTO_URL}" \
|
||||||
|
${AUTH_FLAG}
|
||||||
|
|||||||
+7
-9
@@ -4,30 +4,28 @@ go 1.25.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.26
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
golang.org/x/net v0.50.0
|
golang.org/x/crypto v0.50.0
|
||||||
|
golang.org/x/net v0.52.0
|
||||||
|
golang.org/x/sys v0.43.0
|
||||||
|
golang.org/x/term v0.42.0
|
||||||
google.golang.org/grpc v1.79.1
|
google.golang.org/grpc v1.79.1
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
|
modernc.org/sqlite v1.48.2
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pressly/goose/v3 v3.27.0 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
golang.org/x/text v0.36.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
|
||||||
golang.org/x/sys v0.42.0 // indirect
|
|
||||||
golang.org/x/text v0.34.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
|
||||||
modernc.org/libc v1.70.0 // indirect
|
modernc.org/libc v1.70.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.48.2 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
+42
-16
@@ -12,22 +12,22 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
|
||||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
|
|
||||||
github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
|
||||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||||
@@ -46,17 +46,23 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||||
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
|
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||||
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
|
||||||
@@ -67,11 +73,31 @@ google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBN
|
|||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||||
|
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||||
|
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||||
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||||
|
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
|
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
|
||||||
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package database
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -40,3 +41,9 @@ func Open(path string) (*sql.DB, error) {
|
|||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Checkpoint(db *sql.DB) {
|
||||||
|
if _, err := db.Exec("PRAGMA wal_checkpoint(TRUNCATE)"); err != nil {
|
||||||
|
log.Printf("WAL checkpoint: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package interceptor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
pb "lunar-tear/server/gen/proto"
|
||||||
|
"lunar-tear/server/internal/service"
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
"lunar-tear/server/internal/userdata"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type diffUserDataGetter interface {
|
||||||
|
GetDiffUserData() map[string]*pb.DiffData
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
diffFieldCache sync.Map // map[reflect.Type]bool
|
||||||
|
diffDataMapTyp = reflect.TypeFor[map[string]*pb.DiffData]()
|
||||||
|
)
|
||||||
|
|
||||||
|
func hasDiffField(resp any) bool {
|
||||||
|
t := reflect.TypeOf(resp)
|
||||||
|
if cached, ok := diffFieldCache.Load(t); ok {
|
||||||
|
return cached.(bool)
|
||||||
|
}
|
||||||
|
elem := t
|
||||||
|
if elem.Kind() == reflect.Pointer {
|
||||||
|
elem = elem.Elem()
|
||||||
|
}
|
||||||
|
f, ok := elem.FieldByName("DiffUserData")
|
||||||
|
result := ok && f.Type == diffDataMapTyp
|
||||||
|
diffFieldCache.Store(t, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDiffInterceptor(
|
||||||
|
users store.UserRepository,
|
||||||
|
sessions store.SessionRepository,
|
||||||
|
) grpc.UnaryServerInterceptor {
|
||||||
|
return func(
|
||||||
|
ctx context.Context,
|
||||||
|
req any,
|
||||||
|
info *grpc.UnaryServerInfo,
|
||||||
|
handler grpc.UnaryHandler,
|
||||||
|
) (any, error) {
|
||||||
|
if skipDiffForMethod(info.FullMethod) {
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
userId := service.CurrentUserId(ctx, users, sessions)
|
||||||
|
if userId == 0 {
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
before, err := users.LoadUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, handlerErr := handler(ctx, req)
|
||||||
|
if handlerErr != nil || resp == nil {
|
||||||
|
return resp, handlerErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasDiffField(resp) {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if getter, ok := resp.(diffUserDataGetter); ok {
|
||||||
|
if existing := getter.GetDiffUserData(); len(existing) > 0 {
|
||||||
|
setUpdateNamesTrailer(ctx, existing)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
after, err := users.LoadUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
changed := userdata.ChangedTables(&before, &after)
|
||||||
|
if len(changed) == 0 {
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := userdata.ComputeDelta(&before, &after, changed)
|
||||||
|
reflect.ValueOf(resp).Elem().FieldByName("DiffUserData").Set(reflect.ValueOf(diff))
|
||||||
|
setUpdateNamesTrailer(ctx, diff)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipDiffForMethod(method string) bool {
|
||||||
|
switch method {
|
||||||
|
case "/apb.api.user.UserService/Auth",
|
||||||
|
"/apb.api.user.UserService/RegisterUser",
|
||||||
|
"/apb.api.user.UserService/TransferUser",
|
||||||
|
"/apb.api.user.UserService/TransferUserByFacebook",
|
||||||
|
"/apb.api.config.ConfigService/GetConfig",
|
||||||
|
"/apb.api.data.DataService/GetLatestMasterDataVersion",
|
||||||
|
"/apb.api.data.DataService/GetUserDataNameV2",
|
||||||
|
"/apb.api.data.DataService/GetUserData":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUpdateNamesTrailer(ctx context.Context, diff map[string]*pb.DiffData) {
|
||||||
|
if len(diff) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(diff))
|
||||||
|
for key := range diff {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
value := strings.Join(keys, ",")
|
||||||
|
if err := grpc.SetTrailer(ctx, metadata.Pairs("x-apb-update-user-data-names", value)); err != nil {
|
||||||
|
log.Printf("[DiffInterceptor] failed to set trailer: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package interceptor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Logging(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||||
|
log.Printf(">>> %s", info.FullMethod)
|
||||||
|
resp, err := handler(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("<<< %s ERROR: %v", info.FullMethod, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("<<< %s OK", info.FullMethod)
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnknownService(_ any, stream grpc.ServerStream) error {
|
||||||
|
fullMethod, ok := grpc.MethodFromServerStream(stream)
|
||||||
|
if !ok {
|
||||||
|
fullMethod = "<unknown>"
|
||||||
|
}
|
||||||
|
log.Printf(">>> %s", fullMethod)
|
||||||
|
err := status.Errorf(codes.Unimplemented, "unknown service or method %s", fullMethod)
|
||||||
|
log.Printf("<<< %s ERROR: %v", fullMethod, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package interceptor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Platform(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||||
|
p := model.ClientPlatformFromHeaders(ctx)
|
||||||
|
ctx = model.NewContextWithPlatform(ctx, p)
|
||||||
|
return handler(ctx, req)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package interceptor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/gametime"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TimeSync(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||||
|
resp, err := handler(ctx, req)
|
||||||
|
switch info.FullMethod {
|
||||||
|
case "/apb.api.user.UserService/Auth",
|
||||||
|
"/apb.api.user.UserService/RegisterUser",
|
||||||
|
"/apb.api.user.UserService/TransferUser":
|
||||||
|
default:
|
||||||
|
grpc.SetTrailer(ctx, metadata.Pairs(
|
||||||
|
"x-apb-response-datetime", fmt.Sprintf("%d", gametime.NowMillis()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
@@ -8,15 +8,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bigHuntBossQuestRow struct {
|
|
||||||
BigHuntBossQuestId int32 `json:"BigHuntBossQuestId"`
|
|
||||||
BigHuntBossId int32 `json:"BigHuntBossId"`
|
|
||||||
BigHuntQuestGroupId int32 `json:"BigHuntQuestGroupId"`
|
|
||||||
BigHuntBossQuestScoreCoefficientId int32 `json:"BigHuntBossQuestScoreCoefficientId"`
|
|
||||||
BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"`
|
|
||||||
DailyChallengeCount int32 `json:"DailyChallengeCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BigHuntBossQuestRow struct {
|
type BigHuntBossQuestRow struct {
|
||||||
BigHuntBossQuestId int32
|
BigHuntBossQuestId int32
|
||||||
BigHuntBossId int32
|
BigHuntBossId int32
|
||||||
@@ -25,97 +16,39 @@ type BigHuntBossQuestRow struct {
|
|||||||
DailyChallengeCount int32
|
DailyChallengeCount int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type bigHuntQuestRow struct {
|
|
||||||
BigHuntQuestId int32 `json:"BigHuntQuestId"`
|
|
||||||
QuestId int32 `json:"QuestId"`
|
|
||||||
BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BigHuntQuestRow struct {
|
type BigHuntQuestRow struct {
|
||||||
BigHuntQuestId int32
|
BigHuntQuestId int32
|
||||||
QuestId int32
|
QuestId int32
|
||||||
BigHuntQuestScoreCoefficientId int32
|
BigHuntQuestScoreCoefficientId int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type bigHuntQuestScoreCoefficientRow struct {
|
|
||||||
BigHuntQuestScoreCoefficientId int32 `json:"BigHuntQuestScoreCoefficientId"`
|
|
||||||
ScoreDifficultBonusPermil int32 `json:"ScoreDifficultBonusPermil"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type bigHuntBossRow struct {
|
|
||||||
BigHuntBossId int32 `json:"BigHuntBossId"`
|
|
||||||
BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"`
|
|
||||||
AttributeType int32 `json:"AttributeType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BigHuntBossRow struct {
|
type BigHuntBossRow struct {
|
||||||
BigHuntBossId int32
|
BigHuntBossId int32
|
||||||
BigHuntBossGradeGroupId int32
|
BigHuntBossGradeGroupId int32
|
||||||
AttributeType int32
|
AttributeType int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type bigHuntBossGradeGroupRow struct {
|
|
||||||
BigHuntBossGradeGroupId int32 `json:"BigHuntBossGradeGroupId"`
|
|
||||||
NecessaryScore int64 `json:"NecessaryScore"`
|
|
||||||
AssetGradeIconId int32 `json:"AssetGradeIconId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GradeThreshold struct {
|
type GradeThreshold struct {
|
||||||
NecessaryScore int64
|
NecessaryScore int64
|
||||||
AssetGradeIconId int32
|
AssetGradeIconId int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type bigHuntScheduleRow struct {
|
|
||||||
BigHuntScheduleId int32 `json:"BigHuntScheduleId"`
|
|
||||||
ChallengeStartDatetime int64 `json:"ChallengeStartDatetime"`
|
|
||||||
ChallengeEndDatetime int64 `json:"ChallengeEndDatetime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type scoreRewardScheduleRow struct {
|
|
||||||
BigHuntScoreRewardGroupScheduleId int32 `json:"BigHuntScoreRewardGroupScheduleId"`
|
|
||||||
GroupIndex int32 `json:"GroupIndex"`
|
|
||||||
BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
|
|
||||||
StartDatetime int64 `json:"StartDatetime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScoreRewardScheduleEntry struct {
|
type ScoreRewardScheduleEntry struct {
|
||||||
BigHuntScoreRewardGroupId int32
|
BigHuntScoreRewardGroupId int32
|
||||||
StartDatetime int64
|
StartDatetime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type scoreRewardGroupRow struct {
|
|
||||||
BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
|
|
||||||
NecessaryScore int64 `json:"NecessaryScore"`
|
|
||||||
BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScoreRewardThreshold struct {
|
type ScoreRewardThreshold struct {
|
||||||
NecessaryScore int64
|
NecessaryScore int64
|
||||||
BigHuntRewardGroupId int32
|
BigHuntRewardGroupId int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type rewardGroupRow struct {
|
|
||||||
BigHuntRewardGroupId int32 `json:"BigHuntRewardGroupId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
PossessionType int32 `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RewardItem struct {
|
type RewardItem struct {
|
||||||
PossessionType int32
|
PossessionType int32
|
||||||
PossessionId int32
|
PossessionId int32
|
||||||
Count int32
|
Count int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type weeklyRewardScheduleRow struct {
|
|
||||||
BigHuntWeeklyAttributeScoreRewardGroupScheduleId int32 `json:"BigHuntWeeklyAttributeScoreRewardGroupScheduleId"`
|
|
||||||
AttributeType int32 `json:"AttributeType"`
|
|
||||||
GroupIndex int32 `json:"GroupIndex"`
|
|
||||||
BigHuntScoreRewardGroupId int32 `json:"BigHuntScoreRewardGroupId"`
|
|
||||||
StartDatetime int64 `json:"StartDatetime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BigHuntWeeklyRewardKey struct {
|
type BigHuntWeeklyRewardKey struct {
|
||||||
ScheduleId int32
|
ScheduleId int32
|
||||||
AttributeType int32
|
AttributeType int32
|
||||||
@@ -189,7 +122,7 @@ func (c *BigHuntCatalog) CollectNewRewards(scoreRewardGroupId int32, oldMax, new
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadBigHuntCatalog() *BigHuntCatalog {
|
func LoadBigHuntCatalog() *BigHuntCatalog {
|
||||||
bossQuestRows, err := utils.ReadJSON[bigHuntBossQuestRow]("EntityMBigHuntBossQuestTable.json")
|
bossQuestRows, err := utils.ReadTable[EntityMBigHuntBossQuest]("m_big_hunt_boss_quest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt boss quest table: %v", err)
|
log.Fatalf("load big hunt boss quest table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -204,7 +137,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
questRows, err := utils.ReadJSON[bigHuntQuestRow]("EntityMBigHuntQuestTable.json")
|
questRows, err := utils.ReadTable[EntityMBigHuntQuest]("m_big_hunt_quest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt quest table: %v", err)
|
log.Fatalf("load big hunt quest table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -217,7 +150,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coeffRows, err := utils.ReadJSON[bigHuntQuestScoreCoefficientRow]("EntityMBigHuntQuestScoreCoefficientTable.json")
|
coeffRows, err := utils.ReadTable[EntityMBigHuntQuestScoreCoefficient]("m_big_hunt_quest_score_coefficient")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt quest score coefficient table: %v", err)
|
log.Fatalf("load big hunt quest score coefficient table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -226,7 +159,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
scoreCoefficients[r.BigHuntQuestScoreCoefficientId] = r.ScoreDifficultBonusPermil
|
scoreCoefficients[r.BigHuntQuestScoreCoefficientId] = r.ScoreDifficultBonusPermil
|
||||||
}
|
}
|
||||||
|
|
||||||
bossRows, err := utils.ReadJSON[bigHuntBossRow]("EntityMBigHuntBossTable.json")
|
bossRows, err := utils.ReadTable[EntityMBigHuntBoss]("m_big_hunt_boss")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt boss table: %v", err)
|
log.Fatalf("load big hunt boss table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -239,7 +172,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gradeRows, err := utils.ReadJSON[bigHuntBossGradeGroupRow]("EntityMBigHuntBossGradeGroupTable.json")
|
gradeRows, err := utils.ReadTable[EntityMBigHuntBossGradeGroup]("m_big_hunt_boss_grade_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt boss grade group table: %v", err)
|
log.Fatalf("load big hunt boss grade group table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -256,7 +189,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleRows, err := utils.ReadJSON[bigHuntScheduleRow]("EntityMBigHuntScheduleTable.json")
|
scheduleRows, err := utils.ReadTable[EntityMBigHuntSchedule]("m_big_hunt_schedule")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt schedule table: %v", err)
|
log.Fatalf("load big hunt schedule table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -274,7 +207,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rewardSchedRows, err := utils.ReadJSON[scoreRewardScheduleRow]("EntityMBigHuntScoreRewardGroupScheduleTable.json")
|
rewardSchedRows, err := utils.ReadTable[EntityMBigHuntScoreRewardGroupSchedule]("m_big_hunt_score_reward_group_schedule")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt score reward group schedule table: %v", err)
|
log.Fatalf("load big hunt score reward group schedule table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -294,7 +227,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
rewardGroupRows, err := utils.ReadJSON[scoreRewardGroupRow]("EntityMBigHuntScoreRewardGroupTable.json")
|
rewardGroupRows, err := utils.ReadTable[EntityMBigHuntScoreRewardGroup]("m_big_hunt_score_reward_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt score reward group table: %v", err)
|
log.Fatalf("load big hunt score reward group table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -314,7 +247,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
rewardItemRows, err := utils.ReadJSON[rewardGroupRow]("EntityMBigHuntRewardGroupTable.json")
|
rewardItemRows, err := utils.ReadTable[EntityMBigHuntRewardGroup]("m_big_hunt_reward_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt reward group table: %v", err)
|
log.Fatalf("load big hunt reward group table: %v", err)
|
||||||
}
|
}
|
||||||
@@ -327,7 +260,7 @@ func LoadBigHuntCatalog() *BigHuntCatalog {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
weeklySchedRows, err := utils.ReadJSON[weeklyRewardScheduleRow]("EntityMBigHuntWeeklyAttributeScoreRewardGroupScheduleTable.json")
|
weeklySchedRows, err := utils.ReadTable[EntityMBigHuntWeeklyAttributeScoreRewardGroupSchedule]("m_big_hunt_weekly_attribute_score_reward_group_schedule")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load big hunt weekly attribute score reward group schedule table: %v", err)
|
log.Fatalf("load big hunt weekly attribute score reward group schedule table: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cageOrnament struct {
|
|
||||||
CageOrnamentId int32 `json:"CageOrnamentId"`
|
|
||||||
CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type cageOrnamentRewardRow struct {
|
|
||||||
CageOrnamentRewardId int32 `json:"CageOrnamentRewardId"`
|
|
||||||
PossessionType int32 `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CageOrnamentReward struct {
|
type CageOrnamentReward struct {
|
||||||
PossessionType int32
|
PossessionType int32
|
||||||
PossessionId int32
|
PossessionId int32
|
||||||
@@ -38,11 +26,11 @@ func (c *CageOrnamentCatalog) LookupReward(cageOrnamentId int32) (CageOrnamentRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadCageOrnamentCatalog() *CageOrnamentCatalog {
|
func LoadCageOrnamentCatalog() *CageOrnamentCatalog {
|
||||||
ornaments, err := utils.ReadJSON[cageOrnament]("EntityMCageOrnamentTable.json")
|
ornaments, err := utils.ReadTable[EntityMCageOrnament]("m_cage_ornament")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load cage ornament table: %v", err)
|
log.Fatalf("load cage ornament table: %v", err)
|
||||||
}
|
}
|
||||||
rewards, err := utils.ReadJSON[cageOrnamentRewardRow]("EntityMCageOrnamentRewardTable.json")
|
rewards, err := utils.ReadTable[EntityMCageOrnamentReward]("m_cage_ornament_reward")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load cage ornament reward table: %v", err)
|
log.Fatalf("load cage ornament reward table: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,24 +6,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CharacterRebirthRow struct {
|
|
||||||
CharacterId int32 `json:"CharacterId"`
|
|
||||||
CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterRebirthStepRow struct {
|
|
||||||
CharacterRebirthStepGroupId int32 `json:"CharacterRebirthStepGroupId"`
|
|
||||||
BeforeRebirthCount int32 `json:"BeforeRebirthCount"`
|
|
||||||
CostumeLevelLimitUp int32 `json:"CostumeLevelLimitUp"`
|
|
||||||
CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterRebirthMaterialRow struct {
|
|
||||||
CharacterRebirthMaterialGroupId int32 `json:"CharacterRebirthMaterialGroupId"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StepKey struct {
|
type StepKey struct {
|
||||||
GroupId int32
|
GroupId int32
|
||||||
BeforeRebirthCount int32
|
BeforeRebirthCount int32
|
||||||
@@ -31,22 +13,22 @@ type StepKey struct {
|
|||||||
|
|
||||||
type CharacterRebirthCatalog struct {
|
type CharacterRebirthCatalog struct {
|
||||||
StepGroupByCharacterId map[int32]int32
|
StepGroupByCharacterId map[int32]int32
|
||||||
StepByGroupAndCount map[StepKey]CharacterRebirthStepRow
|
StepByGroupAndCount map[StepKey]EntityMCharacterRebirthStepGroup
|
||||||
MaterialsByGroupId map[int32][]CharacterRebirthMaterialRow
|
MaterialsByGroupId map[int32][]EntityMCharacterRebirthMaterialGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
|
func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
|
||||||
rebirthRows, err := utils.ReadJSON[CharacterRebirthRow]("EntityMCharacterRebirthTable.json")
|
rebirthRows, err := utils.ReadTable[EntityMCharacterRebirth]("m_character_rebirth")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character rebirth table: %w", err)
|
return nil, fmt.Errorf("load character rebirth table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stepRows, err := utils.ReadJSON[CharacterRebirthStepRow]("EntityMCharacterRebirthStepGroupTable.json")
|
stepRows, err := utils.ReadTable[EntityMCharacterRebirthStepGroup]("m_character_rebirth_step_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character rebirth step group table: %w", err)
|
return nil, fmt.Errorf("load character rebirth step group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
materialRows, err := utils.ReadJSON[CharacterRebirthMaterialRow]("EntityMCharacterRebirthMaterialGroupTable.json")
|
materialRows, err := utils.ReadTable[EntityMCharacterRebirthMaterialGroup]("m_character_rebirth_material_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character rebirth material group table: %w", err)
|
return nil, fmt.Errorf("load character rebirth material group table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -56,12 +38,12 @@ func LoadCharacterRebirthCatalog() (*CharacterRebirthCatalog, error) {
|
|||||||
stepGroupByCharacterId[r.CharacterId] = r.CharacterRebirthStepGroupId
|
stepGroupByCharacterId[r.CharacterId] = r.CharacterRebirthStepGroupId
|
||||||
}
|
}
|
||||||
|
|
||||||
stepByGroupAndCount := make(map[StepKey]CharacterRebirthStepRow, len(stepRows))
|
stepByGroupAndCount := make(map[StepKey]EntityMCharacterRebirthStepGroup, len(stepRows))
|
||||||
for _, s := range stepRows {
|
for _, s := range stepRows {
|
||||||
stepByGroupAndCount[StepKey{GroupId: s.CharacterRebirthStepGroupId, BeforeRebirthCount: s.BeforeRebirthCount}] = s
|
stepByGroupAndCount[StepKey{GroupId: s.CharacterRebirthStepGroupId, BeforeRebirthCount: s.BeforeRebirthCount}] = s
|
||||||
}
|
}
|
||||||
|
|
||||||
materialsByGroupId := make(map[int32][]CharacterRebirthMaterialRow)
|
materialsByGroupId := make(map[int32][]EntityMCharacterRebirthMaterialGroup)
|
||||||
for _, m := range materialRows {
|
for _, m := range materialRows {
|
||||||
materialsByGroupId[m.CharacterRebirthMaterialGroupId] = append(materialsByGroupId[m.CharacterRebirthMaterialGroupId], m)
|
materialsByGroupId[m.CharacterRebirthMaterialGroupId] = append(materialsByGroupId[m.CharacterRebirthMaterialGroupId], m)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,66 +7,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CharacterBoardPanelRow struct {
|
|
||||||
CharacterBoardPanelId int32 `json:"CharacterBoardPanelId"`
|
|
||||||
CharacterBoardId int32 `json:"CharacterBoardId"`
|
|
||||||
CharacterBoardPanelUnlockConditionGroupId int32 `json:"CharacterBoardPanelUnlockConditionGroupId"`
|
|
||||||
CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"`
|
|
||||||
CharacterBoardPanelReleaseRewardGroupId int32 `json:"CharacterBoardPanelReleaseRewardGroupId"`
|
|
||||||
CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
ParentCharacterBoardPanelId int32 `json:"ParentCharacterBoardPanelId"`
|
|
||||||
PlaceIndex int32 `json:"PlaceIndex"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardReleasePossessionRow struct {
|
|
||||||
CharacterBoardPanelReleasePossessionGroupId int32 `json:"CharacterBoardPanelReleasePossessionGroupId"`
|
|
||||||
PossessionType int32 `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardReleaseEffectRow struct {
|
|
||||||
CharacterBoardPanelReleaseEffectGroupId int32 `json:"CharacterBoardPanelReleaseEffectGroupId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
CharacterBoardEffectType int32 `json:"CharacterBoardEffectType"`
|
|
||||||
CharacterBoardEffectId int32 `json:"CharacterBoardEffectId"`
|
|
||||||
EffectValue int32 `json:"EffectValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardRow struct {
|
|
||||||
CharacterBoardId int32 `json:"CharacterBoardId"`
|
|
||||||
CharacterBoardGroupId int32 `json:"CharacterBoardGroupId"`
|
|
||||||
CharacterBoardUnlockConditionGroupId int32 `json:"CharacterBoardUnlockConditionGroupId"`
|
|
||||||
ReleaseRank int32 `json:"ReleaseRank"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardStatusUpRow struct {
|
|
||||||
CharacterBoardStatusUpId int32 `json:"CharacterBoardStatusUpId"`
|
|
||||||
CharacterBoardStatusUpType int32 `json:"CharacterBoardStatusUpType"`
|
|
||||||
CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardAbilityRow struct {
|
|
||||||
CharacterBoardAbilityId int32 `json:"CharacterBoardAbilityId"`
|
|
||||||
CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
|
|
||||||
AbilityId int32 `json:"AbilityId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardAbilityMaxLevelRow struct {
|
|
||||||
CharacterId int32 `json:"CharacterId"`
|
|
||||||
AbilityId int32 `json:"AbilityId"`
|
|
||||||
MaxLevel int32 `json:"MaxLevel"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardEffectTargetRow struct {
|
|
||||||
CharacterBoardEffectTargetGroupId int32 `json:"CharacterBoardEffectTargetGroupId"`
|
|
||||||
GroupIndex int32 `json:"GroupIndex"`
|
|
||||||
CharacterBoardEffectTargetType int32 `json:"CharacterBoardEffectTargetType"`
|
|
||||||
TargetValue int32 `json:"TargetValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CharacterBoardAssignmentRow struct {
|
type CharacterBoardAssignmentRow struct {
|
||||||
CharacterId int32 `json:"CharacterId"`
|
CharacterId int32 `json:"CharacterId"`
|
||||||
CharacterBoardCategoryId int32 `json:"CharacterBoardCategoryId"`
|
CharacterBoardCategoryId int32 `json:"CharacterBoardCategoryId"`
|
||||||
@@ -83,68 +23,68 @@ type CharacterBoardGroupRow struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CharacterBoardCatalog struct {
|
type CharacterBoardCatalog struct {
|
||||||
PanelById map[int32]CharacterBoardPanelRow
|
PanelById map[int32]EntityMCharacterBoardPanel
|
||||||
PanelsByBoardId map[int32][]CharacterBoardPanelRow
|
PanelsByBoardId map[int32][]EntityMCharacterBoardPanel
|
||||||
ReleaseCostsByGroupId map[int32][]CharacterBoardReleasePossessionRow
|
ReleaseCostsByGroupId map[int32][]EntityMCharacterBoardPanelReleasePossessionGroup
|
||||||
ReleaseEffectsByGroupId map[int32][]CharacterBoardReleaseEffectRow
|
ReleaseEffectsByGroupId map[int32][]EntityMCharacterBoardPanelReleaseEffectGroup
|
||||||
StatusUpById map[int32]CharacterBoardStatusUpRow
|
StatusUpById map[int32]EntityMCharacterBoardStatusUp
|
||||||
AbilityById map[int32]CharacterBoardAbilityRow
|
AbilityById map[int32]EntityMCharacterBoardAbility
|
||||||
AbilityMaxLevel map[store.CharacterBoardAbilityKey]int32
|
AbilityMaxLevel map[store.CharacterBoardAbilityKey]int32
|
||||||
EffectTargetsByGroupId map[int32][]CharacterBoardEffectTargetRow
|
EffectTargetsByGroupId map[int32][]EntityMCharacterBoardEffectTargetGroup
|
||||||
BoardById map[int32]CharacterBoardRow
|
BoardById map[int32]EntityMCharacterBoard
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadCharacterBoardCatalog() (*CharacterBoardCatalog, error) {
|
func LoadCharacterBoardCatalog() (*CharacterBoardCatalog, error) {
|
||||||
panels, err := utils.ReadJSON[CharacterBoardPanelRow]("EntityMCharacterBoardPanelTable.json")
|
panels, err := utils.ReadTable[EntityMCharacterBoardPanel]("m_character_board_panel")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board panel table: %w", err)
|
return nil, fmt.Errorf("load character board panel table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
costs, err := utils.ReadJSON[CharacterBoardReleasePossessionRow]("EntityMCharacterBoardPanelReleasePossessionGroupTable.json")
|
costs, err := utils.ReadTable[EntityMCharacterBoardPanelReleasePossessionGroup]("m_character_board_panel_release_possession_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board release possession table: %w", err)
|
return nil, fmt.Errorf("load character board release possession table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
effects, err := utils.ReadJSON[CharacterBoardReleaseEffectRow]("EntityMCharacterBoardPanelReleaseEffectGroupTable.json")
|
effects, err := utils.ReadTable[EntityMCharacterBoardPanelReleaseEffectGroup]("m_character_board_panel_release_effect_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board release effect table: %w", err)
|
return nil, fmt.Errorf("load character board release effect table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
boards, err := utils.ReadJSON[CharacterBoardRow]("EntityMCharacterBoardTable.json")
|
boards, err := utils.ReadTable[EntityMCharacterBoard]("m_character_board")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board table: %w", err)
|
return nil, fmt.Errorf("load character board table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusUps, err := utils.ReadJSON[CharacterBoardStatusUpRow]("EntityMCharacterBoardStatusUpTable.json")
|
statusUps, err := utils.ReadTable[EntityMCharacterBoardStatusUp]("m_character_board_status_up")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board status up table: %w", err)
|
return nil, fmt.Errorf("load character board status up table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
abilities, err := utils.ReadJSON[CharacterBoardAbilityRow]("EntityMCharacterBoardAbilityTable.json")
|
abilities, err := utils.ReadTable[EntityMCharacterBoardAbility]("m_character_board_ability")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board ability table: %w", err)
|
return nil, fmt.Errorf("load character board ability table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
abilityMaxLevels, err := utils.ReadJSON[CharacterBoardAbilityMaxLevelRow]("EntityMCharacterBoardAbilityMaxLevelTable.json")
|
abilityMaxLevels, err := utils.ReadTable[EntityMCharacterBoardAbilityMaxLevel]("m_character_board_ability_max_level")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board ability max level table: %w", err)
|
return nil, fmt.Errorf("load character board ability max level table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
targets, err := utils.ReadJSON[CharacterBoardEffectTargetRow]("EntityMCharacterBoardEffectTargetGroupTable.json")
|
targets, err := utils.ReadTable[EntityMCharacterBoardEffectTargetGroup]("m_character_board_effect_target_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load character board effect target table: %w", err)
|
return nil, fmt.Errorf("load character board effect target table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := &CharacterBoardCatalog{
|
catalog := &CharacterBoardCatalog{
|
||||||
PanelById: make(map[int32]CharacterBoardPanelRow, len(panels)),
|
PanelById: make(map[int32]EntityMCharacterBoardPanel, len(panels)),
|
||||||
PanelsByBoardId: make(map[int32][]CharacterBoardPanelRow),
|
PanelsByBoardId: make(map[int32][]EntityMCharacterBoardPanel),
|
||||||
ReleaseCostsByGroupId: make(map[int32][]CharacterBoardReleasePossessionRow),
|
ReleaseCostsByGroupId: make(map[int32][]EntityMCharacterBoardPanelReleasePossessionGroup),
|
||||||
ReleaseEffectsByGroupId: make(map[int32][]CharacterBoardReleaseEffectRow),
|
ReleaseEffectsByGroupId: make(map[int32][]EntityMCharacterBoardPanelReleaseEffectGroup),
|
||||||
StatusUpById: make(map[int32]CharacterBoardStatusUpRow, len(statusUps)),
|
StatusUpById: make(map[int32]EntityMCharacterBoardStatusUp, len(statusUps)),
|
||||||
AbilityById: make(map[int32]CharacterBoardAbilityRow, len(abilities)),
|
AbilityById: make(map[int32]EntityMCharacterBoardAbility, len(abilities)),
|
||||||
AbilityMaxLevel: make(map[store.CharacterBoardAbilityKey]int32, len(abilityMaxLevels)),
|
AbilityMaxLevel: make(map[store.CharacterBoardAbilityKey]int32, len(abilityMaxLevels)),
|
||||||
EffectTargetsByGroupId: make(map[int32][]CharacterBoardEffectTargetRow),
|
EffectTargetsByGroupId: make(map[int32][]EntityMCharacterBoardEffectTargetGroup),
|
||||||
BoardById: make(map[int32]CharacterBoardRow, len(boards)),
|
BoardById: make(map[int32]EntityMCharacterBoard, len(boards)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range panels {
|
for _, p := range panels {
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type characterViewerField struct {
|
|
||||||
CharacterViewerFieldId int32 `json:"CharacterViewerFieldId"`
|
|
||||||
ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type characterViewerFieldEntry struct {
|
type characterViewerFieldEntry struct {
|
||||||
FieldId int32
|
FieldId int32
|
||||||
RequiredQuestId int32
|
RequiredQuestId int32
|
||||||
@@ -39,7 +34,7 @@ func (c *CharacterViewerCatalog) ReleasedFieldIds(user store.UserState) []int32
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadCharacterViewerCatalog(resolver *ConditionResolver) *CharacterViewerCatalog {
|
func LoadCharacterViewerCatalog(resolver *ConditionResolver) *CharacterViewerCatalog {
|
||||||
fields, err := utils.ReadJSON[characterViewerField]("EntityMCharacterViewerFieldTable.json")
|
fields, err := utils.ReadTable[EntityMCharacterViewerField]("m_character_viewer_field")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load character viewer field table: %v", err)
|
log.Fatalf("load character viewer field table: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type companionRow struct {
|
|
||||||
CompanionId int32 `json:"CompanionId"`
|
|
||||||
CompanionCategoryType int32 `json:"CompanionCategoryType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type companionCategoryRow struct {
|
|
||||||
CompanionCategoryType int32 `json:"CompanionCategoryType"`
|
|
||||||
EnhancementCostNumericalFunctionId int32 `json:"EnhancementCostNumericalFunctionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type companionEnhancementMaterialRow struct {
|
|
||||||
CompanionCategoryType int32 `json:"CompanionCategoryType"`
|
|
||||||
Level int32 `json:"Level"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompanionLevelKey struct {
|
type CompanionLevelKey struct {
|
||||||
CategoryType int32
|
CategoryType int32
|
||||||
Level int32
|
Level int32
|
||||||
@@ -34,23 +17,23 @@ type CompanionMaterialCost struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CompanionCatalog struct {
|
type CompanionCatalog struct {
|
||||||
CompanionById map[int32]companionRow
|
CompanionById map[int32]EntityMCompanion
|
||||||
GoldCostByCategory map[int32]NumericalFunc
|
GoldCostByCategory map[int32]NumericalFunc
|
||||||
MaterialsByKey map[CompanionLevelKey]CompanionMaterialCost
|
MaterialsByKey map[CompanionLevelKey]CompanionMaterialCost
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadCompanionCatalog() (*CompanionCatalog, error) {
|
func LoadCompanionCatalog() (*CompanionCatalog, error) {
|
||||||
companions, err := utils.ReadJSON[companionRow]("EntityMCompanionTable.json")
|
companions, err := utils.ReadTable[EntityMCompanion]("m_companion")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load companion table: %w", err)
|
return nil, fmt.Errorf("load companion table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
categories, err := utils.ReadJSON[companionCategoryRow]("EntityMCompanionCategoryTable.json")
|
categories, err := utils.ReadTable[EntityMCompanionCategory]("m_companion_category")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load companion category table: %w", err)
|
return nil, fmt.Errorf("load companion category table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
materials, err := utils.ReadJSON[companionEnhancementMaterialRow]("EntityMCompanionEnhancementMaterialTable.json")
|
materials, err := utils.ReadTable[EntityMCompanionEnhancementMaterial]("m_companion_enhancement_material")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load companion enhancement material table: %w", err)
|
return nil, fmt.Errorf("load companion enhancement material table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -60,7 +43,7 @@ func LoadCompanionCatalog() (*CompanionCatalog, error) {
|
|||||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
companionById := make(map[int32]companionRow, len(companions))
|
companionById := make(map[int32]EntityMCompanion, len(companions))
|
||||||
for _, c := range companions {
|
for _, c := range companions {
|
||||||
companionById[c.CompanionId] = c
|
companionById[c.CompanionId] = c
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type evaluateCondition struct {
|
|
||||||
EvaluateConditionId int32 `json:"EvaluateConditionId"`
|
|
||||||
EvaluateConditionFunctionType model.EvaluateConditionFunctionType `json:"EvaluateConditionFunctionType"`
|
|
||||||
EvaluateConditionEvaluateType model.EvaluateConditionEvaluateType `json:"EvaluateConditionEvaluateType"`
|
|
||||||
EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type evaluateConditionValueGroup struct {
|
|
||||||
EvaluateConditionValueGroupId int32 `json:"EvaluateConditionValueGroupId"`
|
|
||||||
GroupIndex int32 `json:"GroupIndex"`
|
|
||||||
Value int64 `json:"Value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultGroupIndex = 1
|
const defaultGroupIndex = 1
|
||||||
|
|
||||||
type ConditionResolver struct {
|
type ConditionResolver struct {
|
||||||
@@ -27,16 +14,16 @@ type ConditionResolver struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadConditionResolver() (*ConditionResolver, error) {
|
func LoadConditionResolver() (*ConditionResolver, error) {
|
||||||
conditions, err := utils.ReadJSON[evaluateCondition]("EntityMEvaluateConditionTable.json")
|
conditions, err := utils.ReadTable[EntityMEvaluateCondition]("m_evaluate_condition")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load evaluate condition table: %w", err)
|
return nil, fmt.Errorf("load evaluate condition table: %w", err)
|
||||||
}
|
}
|
||||||
valueGroups, err := utils.ReadJSON[evaluateConditionValueGroup]("EntityMEvaluateConditionValueGroupTable.json")
|
valueGroups, err := utils.ReadTable[EntityMEvaluateConditionValueGroup]("m_evaluate_condition_value_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load evaluate condition value group table: %w", err)
|
return nil, fmt.Errorf("load evaluate condition value group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
condById := make(map[int32]evaluateCondition, len(conditions))
|
condById := make(map[int32]EntityMEvaluateCondition, len(conditions))
|
||||||
for _, c := range conditions {
|
for _, c := range conditions {
|
||||||
condById[c.EvaluateConditionId] = c
|
condById[c.EvaluateConditionId] = c
|
||||||
}
|
}
|
||||||
@@ -52,8 +39,8 @@ func LoadConditionResolver() (*ConditionResolver, error) {
|
|||||||
|
|
||||||
resolved := make(map[int32]int32)
|
resolved := make(map[int32]int32)
|
||||||
for _, c := range conditions {
|
for _, c := range conditions {
|
||||||
if c.EvaluateConditionFunctionType == model.EvaluateConditionFunctionTypeQuestClear &&
|
if model.EvaluateConditionFunctionType(c.EvaluateConditionFunctionType) == model.EvaluateConditionFunctionTypeQuestClear &&
|
||||||
c.EvaluateConditionEvaluateType == model.EvaluateConditionEvaluateTypeIdContain {
|
model.EvaluateConditionEvaluateType(c.EvaluateConditionEvaluateType) == model.EvaluateConditionEvaluateTypeIdContain {
|
||||||
if questId, ok := vgByKey[vgKey{c.EvaluateConditionValueGroupId, defaultGroupIndex}]; ok {
|
if questId, ok := vgByKey[vgKey{c.EvaluateConditionValueGroupId, defaultGroupIndex}]; ok {
|
||||||
resolved[c.EvaluateConditionId] = int32(questId)
|
resolved[c.EvaluateConditionId] = int32(questId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configRow struct {
|
|
||||||
ConfigKey string `json:"ConfigKey"`
|
|
||||||
Value string `json:"Value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GameConfig struct {
|
type GameConfig struct {
|
||||||
ConsumableItemIdForGold int32
|
ConsumableItemIdForGold int32
|
||||||
ConsumableItemIdForMedal int32
|
ConsumableItemIdForMedal int32
|
||||||
@@ -41,7 +36,7 @@ type GameConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadGameConfig() (*GameConfig, error) {
|
func LoadGameConfig() (*GameConfig, error) {
|
||||||
rows, err := utils.ReadJSON[configRow]("EntityMConfigTable.json")
|
rows, err := utils.ReadTable[EntityMConfig]("m_config")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load config table: %w", err)
|
return nil, fmt.Errorf("load config table: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,18 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConsumableItemRow struct {
|
|
||||||
ConsumableItemId int32 `json:"ConsumableItemId"`
|
|
||||||
SellPrice int32 `json:"SellPrice"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConsumableItemCatalog struct {
|
type ConsumableItemCatalog struct {
|
||||||
All map[int32]ConsumableItemRow
|
All map[int32]EntityMConsumableItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConsumableItemCatalog() (*ConsumableItemCatalog, error) {
|
func LoadConsumableItemCatalog() (*ConsumableItemCatalog, error) {
|
||||||
rows, err := utils.ReadJSON[ConsumableItemRow]("EntityMConsumableItemTable.json")
|
rows, err := utils.ReadTable[EntityMConsumableItem]("m_consumable_item")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load consumable item table: %w", err)
|
return nil, fmt.Errorf("load consumable item table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := &ConsumableItemCatalog{
|
catalog := &ConsumableItemCatalog{
|
||||||
All: make(map[int32]ConsumableItemRow, len(rows)),
|
All: make(map[int32]EntityMConsumableItem, len(rows)),
|
||||||
}
|
}
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
catalog.All[row.ConsumableItemId] = row
|
catalog.All[row.ConsumableItemId] = row
|
||||||
|
|||||||
@@ -8,132 +8,37 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CostumeMasterRow struct {
|
|
||||||
CostumeId int32 `json:"CostumeId"`
|
|
||||||
CharacterId int32 `json:"CharacterId"`
|
|
||||||
SkillfulWeaponType int32 `json:"SkillfulWeaponType"`
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
|
|
||||||
CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type costumeRarityRow struct {
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
CostumeLimitBreakMaterialRarityGroupId int32 `json:"CostumeLimitBreakMaterialRarityGroupId"`
|
|
||||||
RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
|
|
||||||
EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
|
|
||||||
LimitBreakCostNumericalFunctionId int32 `json:"LimitBreakCostNumericalFunctionId"`
|
|
||||||
MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
|
|
||||||
ActiveSkillMaxLevelNumericalFunctionId int32 `json:"ActiveSkillMaxLevelNumericalFunctionId"`
|
|
||||||
ActiveSkillEnhancementCostNumericalFunctionId int32 `json:"ActiveSkillEnhancementCostNumericalFunctionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeAwakenRow struct {
|
|
||||||
CostumeId int32 `json:"CostumeId"`
|
|
||||||
CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"`
|
|
||||||
CostumeAwakenStepMaterialGroupId int32 `json:"CostumeAwakenStepMaterialGroupId"`
|
|
||||||
CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type costumeAwakenPriceRow struct {
|
|
||||||
CostumeAwakenPriceGroupId int32 `json:"CostumeAwakenPriceGroupId"`
|
|
||||||
AwakenStepLowerLimit int32 `json:"AwakenStepLowerLimit"`
|
|
||||||
Gold int32 `json:"Gold"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeAwakenEffectRow struct {
|
|
||||||
CostumeAwakenEffectGroupId int32 `json:"CostumeAwakenEffectGroupId"`
|
|
||||||
AwakenStep int32 `json:"AwakenStep"`
|
|
||||||
CostumeAwakenEffectType int32 `json:"CostumeAwakenEffectType"`
|
|
||||||
CostumeAwakenEffectId int32 `json:"CostumeAwakenEffectId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeAwakenStatusUpRow struct {
|
|
||||||
CostumeAwakenStatusUpGroupId int32 `json:"CostumeAwakenStatusUpGroupId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
StatusKindType int32 `json:"StatusKindType"`
|
|
||||||
StatusCalculationType int32 `json:"StatusCalculationType"`
|
|
||||||
EffectValue int32 `json:"EffectValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeAwakenItemAcquireRow struct {
|
|
||||||
CostumeAwakenItemAcquireId int32 `json:"CostumeAwakenItemAcquireId"`
|
|
||||||
PossessionType int32 `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeActiveSkillGroupRow struct {
|
|
||||||
CostumeActiveSkillGroupId int32 `json:"CostumeActiveSkillGroupId"`
|
|
||||||
CostumeLimitBreakCountLowerLimit int32 `json:"CostumeLimitBreakCountLowerLimit"`
|
|
||||||
CostumeActiveSkillId int32 `json:"CostumeActiveSkillId"`
|
|
||||||
CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeActiveSkillEnhanceMaterialRow struct {
|
|
||||||
CostumeActiveSkillEnhancementMaterialId int32 `json:"CostumeActiveSkillEnhancementMaterialId"`
|
|
||||||
SkillLevel int32 `json:"SkillLevel"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeLotteryEffectRow struct {
|
|
||||||
CostumeId int32 `json:"CostumeId"`
|
|
||||||
SlotNumber int32 `json:"SlotNumber"`
|
|
||||||
CostumeLotteryEffectOddsGroupId int32 `json:"CostumeLotteryEffectOddsGroupId"`
|
|
||||||
CostumeLotteryEffectUnlockMaterialGroupId int32 `json:"CostumeLotteryEffectUnlockMaterialGroupId"`
|
|
||||||
CostumeLotteryEffectDrawMaterialGroupId int32 `json:"CostumeLotteryEffectDrawMaterialGroupId"`
|
|
||||||
CostumeLotteryEffectReleaseScheduleId int32 `json:"CostumeLotteryEffectReleaseScheduleId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeLotteryEffectMaterialGroupRow struct {
|
|
||||||
CostumeLotteryEffectMaterialGroupId int32 `json:"CostumeLotteryEffectMaterialGroupId"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeLotteryEffectOddsRow struct {
|
|
||||||
CostumeLotteryEffectOddsGroupId int32 `json:"CostumeLotteryEffectOddsGroupId"`
|
|
||||||
OddsNumber int32 `json:"OddsNumber"`
|
|
||||||
Weight int32 `json:"Weight"`
|
|
||||||
CostumeLotteryEffectType int32 `json:"CostumeLotteryEffectType"`
|
|
||||||
CostumeLotteryEffectTargetId int32 `json:"CostumeLotteryEffectTargetId"`
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeCatalog struct {
|
type CostumeCatalog struct {
|
||||||
Costumes map[int32]CostumeMasterRow
|
Costumes map[int32]EntityMCostume
|
||||||
Materials map[int32]MaterialRow
|
Materials map[int32]EntityMMaterial
|
||||||
ExpByRarity map[int32][]int32
|
ExpByRarity map[int32][]int32
|
||||||
EnhanceCostByRarity map[int32]NumericalFunc
|
EnhanceCostByRarity map[int32]NumericalFunc
|
||||||
MaxLevelByRarity map[int32]NumericalFunc
|
MaxLevelByRarity map[int32]NumericalFunc
|
||||||
LimitBreakCostByRarity map[int32]NumericalFunc
|
LimitBreakCostByRarity map[int32]NumericalFunc
|
||||||
|
|
||||||
AwakenByCostumeId map[int32]CostumeAwakenRow
|
AwakenByCostumeId map[int32]EntityMCostumeAwaken
|
||||||
AwakenPriceByGroup map[int32]int32
|
AwakenPriceByGroup map[int32]int32
|
||||||
AwakenEffectsByGroupAndStep map[int32]map[int32]CostumeAwakenEffectRow
|
AwakenEffectsByGroupAndStep map[int32]map[int32]EntityMCostumeAwakenEffectGroup
|
||||||
AwakenStatusUpByGroup map[int32][]CostumeAwakenStatusUpRow
|
AwakenStatusUpByGroup map[int32][]EntityMCostumeAwakenStatusUpGroup
|
||||||
AwakenItemAcquireById map[int32]CostumeAwakenItemAcquireRow
|
AwakenItemAcquireById map[int32]EntityMCostumeAwakenItemAcquire
|
||||||
|
|
||||||
ActiveSkillGroupsByGroupId map[int32][]CostumeActiveSkillGroupRow // sorted by CostumeLimitBreakCountLowerLimit desc
|
ActiveSkillGroupsByGroupId map[int32][]EntityMCostumeActiveSkillGroup // sorted by CostumeLimitBreakCountLowerLimit desc
|
||||||
ActiveSkillEnhanceMats map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel]
|
ActiveSkillEnhanceMats map[[2]int32][]EntityMCostumeActiveSkillEnhancementMaterial // key: [enhancementMaterialId, skillLevel]
|
||||||
ActiveSkillMaxLevelByRarity map[int32]NumericalFunc
|
ActiveSkillMaxLevelByRarity map[int32]NumericalFunc
|
||||||
ActiveSkillCostByRarity map[int32]NumericalFunc
|
ActiveSkillCostByRarity map[int32]NumericalFunc
|
||||||
|
|
||||||
LotteryEffects map[[2]int32]CostumeLotteryEffectRow // key: [costumeId, slotNumber]
|
LotteryEffects map[[2]int32]EntityMCostumeLotteryEffect // key: [costumeId, slotNumber]
|
||||||
LotteryEffectMats map[int32][]CostumeLotteryEffectMaterialGroupRow // key: materialGroupId (both unlock and draw)
|
LotteryEffectMats map[int32][]EntityMCostumeLotteryEffectMaterialGroup // key: materialGroupId (both unlock and draw)
|
||||||
LotteryEffectOdds map[int32][]CostumeLotteryEffectOddsRow // key: oddsGroupId
|
LotteryEffectOdds map[int32][]EntityMCostumeLotteryEffectOddsGroup // key: oddsGroupId
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
||||||
costumes, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json")
|
costumes, err := utils.ReadTable[EntityMCostume]("m_costume")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume table: %w", err)
|
return nil, fmt.Errorf("load costume table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json")
|
rarities, err := utils.ReadTable[EntityMCostumeRarity]("m_costume_rarity")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume rarity table: %w", err)
|
return nil, fmt.Errorf("load costume rarity table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -148,71 +53,71 @@ func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
|||||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
awakenRows, err := utils.ReadJSON[CostumeAwakenRow]("EntityMCostumeAwakenTable.json")
|
awakenRows, err := utils.ReadTable[EntityMCostumeAwaken]("m_costume_awaken")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume awaken table: %w", err)
|
return nil, fmt.Errorf("load costume awaken table: %w", err)
|
||||||
}
|
}
|
||||||
awakenPriceRows, err := utils.ReadJSON[costumeAwakenPriceRow]("EntityMCostumeAwakenPriceGroupTable.json")
|
awakenPriceRows, err := utils.ReadTable[EntityMCostumeAwakenPriceGroup]("m_costume_awaken_price_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume awaken price table: %w", err)
|
return nil, fmt.Errorf("load costume awaken price table: %w", err)
|
||||||
}
|
}
|
||||||
awakenEffectRows, err := utils.ReadJSON[CostumeAwakenEffectRow]("EntityMCostumeAwakenEffectGroupTable.json")
|
awakenEffectRows, err := utils.ReadTable[EntityMCostumeAwakenEffectGroup]("m_costume_awaken_effect_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume awaken effect table: %w", err)
|
return nil, fmt.Errorf("load costume awaken effect table: %w", err)
|
||||||
}
|
}
|
||||||
awakenStatusUpRows, err := utils.ReadJSON[CostumeAwakenStatusUpRow]("EntityMCostumeAwakenStatusUpGroupTable.json")
|
awakenStatusUpRows, err := utils.ReadTable[EntityMCostumeAwakenStatusUpGroup]("m_costume_awaken_status_up_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume awaken status up table: %w", err)
|
return nil, fmt.Errorf("load costume awaken status up table: %w", err)
|
||||||
}
|
}
|
||||||
awakenItemAcquireRows, err := utils.ReadJSON[CostumeAwakenItemAcquireRow]("EntityMCostumeAwakenItemAcquireTable.json")
|
awakenItemAcquireRows, err := utils.ReadTable[EntityMCostumeAwakenItemAcquire]("m_costume_awaken_item_acquire")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume awaken item acquire table: %w", err)
|
return nil, fmt.Errorf("load costume awaken item acquire table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
activeSkillGroupRows, err := utils.ReadJSON[CostumeActiveSkillGroupRow]("EntityMCostumeActiveSkillGroupTable.json")
|
activeSkillGroupRows, err := utils.ReadTable[EntityMCostumeActiveSkillGroup]("m_costume_active_skill_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume active skill group table: %w", err)
|
return nil, fmt.Errorf("load costume active skill group table: %w", err)
|
||||||
}
|
}
|
||||||
activeSkillMatRows, err := utils.ReadJSON[CostumeActiveSkillEnhanceMaterialRow]("EntityMCostumeActiveSkillEnhancementMaterialTable.json")
|
activeSkillMatRows, err := utils.ReadTable[EntityMCostumeActiveSkillEnhancementMaterial]("m_costume_active_skill_enhancement_material")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume active skill enhancement material table: %w", err)
|
return nil, fmt.Errorf("load costume active skill enhancement material table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lotteryEffectRows, err := utils.ReadJSON[CostumeLotteryEffectRow]("EntityMCostumeLotteryEffectTable.json")
|
lotteryEffectRows, err := utils.ReadTable[EntityMCostumeLotteryEffect]("m_costume_lottery_effect")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume lottery effect table: %w", err)
|
return nil, fmt.Errorf("load costume lottery effect table: %w", err)
|
||||||
}
|
}
|
||||||
lotteryEffectMatRows, err := utils.ReadJSON[CostumeLotteryEffectMaterialGroupRow]("EntityMCostumeLotteryEffectMaterialGroupTable.json")
|
lotteryEffectMatRows, err := utils.ReadTable[EntityMCostumeLotteryEffectMaterialGroup]("m_costume_lottery_effect_material_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume lottery effect material group table: %w", err)
|
return nil, fmt.Errorf("load costume lottery effect material group table: %w", err)
|
||||||
}
|
}
|
||||||
lotteryEffectOddsRows, err := utils.ReadJSON[CostumeLotteryEffectOddsRow]("EntityMCostumeLotteryEffectOddsGroupTable.json")
|
lotteryEffectOddsRows, err := utils.ReadTable[EntityMCostumeLotteryEffectOddsGroup]("m_costume_lottery_effect_odds_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume lottery effect odds group table: %w", err)
|
return nil, fmt.Errorf("load costume lottery effect odds group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := &CostumeCatalog{
|
catalog := &CostumeCatalog{
|
||||||
Costumes: make(map[int32]CostumeMasterRow, len(costumes)),
|
Costumes: make(map[int32]EntityMCostume, len(costumes)),
|
||||||
Materials: matCatalog.ByType[model.MaterialTypeCostumeEnhancement],
|
Materials: matCatalog.ByType[model.MaterialTypeCostumeEnhancement],
|
||||||
ExpByRarity: make(map[int32][]int32, len(rarities)),
|
ExpByRarity: make(map[int32][]int32, len(rarities)),
|
||||||
EnhanceCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
EnhanceCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||||
MaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
MaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||||
LimitBreakCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
LimitBreakCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||||
|
|
||||||
AwakenByCostumeId: make(map[int32]CostumeAwakenRow, len(awakenRows)),
|
AwakenByCostumeId: make(map[int32]EntityMCostumeAwaken, len(awakenRows)),
|
||||||
AwakenPriceByGroup: make(map[int32]int32, len(awakenPriceRows)),
|
AwakenPriceByGroup: make(map[int32]int32, len(awakenPriceRows)),
|
||||||
AwakenEffectsByGroupAndStep: make(map[int32]map[int32]CostumeAwakenEffectRow),
|
AwakenEffectsByGroupAndStep: make(map[int32]map[int32]EntityMCostumeAwakenEffectGroup),
|
||||||
AwakenStatusUpByGroup: make(map[int32][]CostumeAwakenStatusUpRow),
|
AwakenStatusUpByGroup: make(map[int32][]EntityMCostumeAwakenStatusUpGroup),
|
||||||
AwakenItemAcquireById: make(map[int32]CostumeAwakenItemAcquireRow, len(awakenItemAcquireRows)),
|
AwakenItemAcquireById: make(map[int32]EntityMCostumeAwakenItemAcquire, len(awakenItemAcquireRows)),
|
||||||
|
|
||||||
ActiveSkillGroupsByGroupId: make(map[int32][]CostumeActiveSkillGroupRow),
|
ActiveSkillGroupsByGroupId: make(map[int32][]EntityMCostumeActiveSkillGroup),
|
||||||
ActiveSkillEnhanceMats: make(map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow),
|
ActiveSkillEnhanceMats: make(map[[2]int32][]EntityMCostumeActiveSkillEnhancementMaterial),
|
||||||
ActiveSkillMaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
ActiveSkillMaxLevelByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||||
ActiveSkillCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
ActiveSkillCostByRarity: make(map[int32]NumericalFunc, len(rarities)),
|
||||||
|
|
||||||
LotteryEffects: make(map[[2]int32]CostumeLotteryEffectRow, len(lotteryEffectRows)),
|
LotteryEffects: make(map[[2]int32]EntityMCostumeLotteryEffect, len(lotteryEffectRows)),
|
||||||
LotteryEffectMats: make(map[int32][]CostumeLotteryEffectMaterialGroupRow),
|
LotteryEffectMats: make(map[int32][]EntityMCostumeLotteryEffectMaterialGroup),
|
||||||
LotteryEffectOdds: make(map[int32][]CostumeLotteryEffectOddsRow),
|
LotteryEffectOdds: make(map[int32][]EntityMCostumeLotteryEffectOddsGroup),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, row := range costumes {
|
for _, row := range costumes {
|
||||||
@@ -259,7 +164,7 @@ func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
|||||||
for _, row := range awakenEffectRows {
|
for _, row := range awakenEffectRows {
|
||||||
m, ok := catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId]
|
m, ok := catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId]
|
||||||
if !ok {
|
if !ok {
|
||||||
m = make(map[int32]CostumeAwakenEffectRow)
|
m = make(map[int32]EntityMCostumeAwakenEffectGroup)
|
||||||
catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId] = m
|
catalog.AwakenEffectsByGroupAndStep[row.CostumeAwakenEffectGroupId] = m
|
||||||
}
|
}
|
||||||
m[row.AwakenStep] = row
|
m[row.AwakenStep] = row
|
||||||
|
|||||||
@@ -5,17 +5,10 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type costumeDupRow struct {
|
|
||||||
CostumeId int32 `json:"CostumeId"`
|
|
||||||
PossessionType int32 `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadDupExchange() (map[int32][]model.DupExchangeEntry, error) {
|
func LoadDupExchange() (map[int32][]model.DupExchangeEntry, error) {
|
||||||
result := make(map[int32][]model.DupExchangeEntry)
|
result := make(map[int32][]model.DupExchangeEntry)
|
||||||
|
|
||||||
costumeRows, err := utils.ReadJSON[costumeDupRow]("EntityMCostumeDuplicationExchangePossessionGroupTable.json")
|
costumeRows, err := utils.ReadTable[EntityMCostumeDuplicationExchangePossessionGroup]("m_costume_duplication_exchange_possession_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -30,20 +23,10 @@ func LoadDupExchange() (map[int32][]model.DupExchangeEntry, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type lbMaterialRow struct {
|
|
||||||
CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type costumeLBRef struct {
|
|
||||||
CostumeId int32 `json:"CostumeId"`
|
|
||||||
CostumeLimitBreakMaterialGroupId int32 `json:"CostumeLimitBreakMaterialGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const dupExchangeFallbackCount int32 = 10
|
const dupExchangeFallbackCount int32 = 10
|
||||||
|
|
||||||
func EnrichDupExchange(dupMap map[int32][]model.DupExchangeEntry, pool *GachaCatalog) (int, error) {
|
func EnrichDupExchange(dupMap map[int32][]model.DupExchangeEntry, pool *GachaCatalog) (int, error) {
|
||||||
lbRows, err := utils.ReadJSON[lbMaterialRow]("EntityMCostumeLimitBreakMaterialGroupTable.json")
|
lbRows, err := utils.ReadTable[EntityMCostumeLimitBreakMaterialGroup]("m_costume_limit_break_material_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -52,7 +35,7 @@ func EnrichDupExchange(dupMap map[int32][]model.DupExchangeEntry, pool *GachaCat
|
|||||||
groupToMaterial[r.CostumeLimitBreakMaterialGroupId] = r.MaterialId
|
groupToMaterial[r.CostumeLimitBreakMaterialGroupId] = r.MaterialId
|
||||||
}
|
}
|
||||||
|
|
||||||
costumeRows, err := utils.ReadJSON[costumeLBRef]("EntityMCostumeTable.json")
|
costumeRows, err := utils.ReadTable[EntityMCostume]("m_costume")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,48 +7,31 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExploreRow struct {
|
|
||||||
ExploreId int32 `json:"ExploreId"`
|
|
||||||
ConsumeItemCount int32 `json:"ConsumeItemCount"`
|
|
||||||
RewardLotteryCount int32 `json:"RewardLotteryCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExploreGradeScoreRow struct {
|
|
||||||
ExploreId int32 `json:"ExploreId"`
|
|
||||||
NecessaryScore int32 `json:"NecessaryScore"`
|
|
||||||
ExploreGradeId int32 `json:"ExploreGradeId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExploreGradeAssetRow struct {
|
|
||||||
ExploreGradeId int32 `json:"ExploreGradeId"`
|
|
||||||
AssetGradeIconId int32 `json:"AssetGradeIconId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExploreCatalog struct {
|
type ExploreCatalog struct {
|
||||||
Explores map[int32]ExploreRow
|
Explores map[int32]EntityMExplore
|
||||||
GradeScores map[int32][]ExploreGradeScoreRow // keyed by ExploreId, sorted desc by NecessaryScore
|
GradeScores map[int32][]EntityMExploreGradeScore // keyed by ExploreId, sorted desc by NecessaryScore
|
||||||
GradeAssets map[int32]int32 // gradeId -> assetGradeIconId
|
GradeAssets map[int32]int32 // gradeId -> assetGradeIconId
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadExploreCatalog() (*ExploreCatalog, error) {
|
func LoadExploreCatalog() (*ExploreCatalog, error) {
|
||||||
explores, err := utils.ReadJSON[ExploreRow]("EntityMExploreTable.json")
|
explores, err := utils.ReadTable[EntityMExplore]("m_explore")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load explore table: %w", err)
|
return nil, fmt.Errorf("load explore table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gradeScores, err := utils.ReadJSON[ExploreGradeScoreRow]("EntityMExploreGradeScoreTable.json")
|
gradeScores, err := utils.ReadTable[EntityMExploreGradeScore]("m_explore_grade_score")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load explore grade score table: %w", err)
|
return nil, fmt.Errorf("load explore grade score table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gradeAssets, err := utils.ReadJSON[ExploreGradeAssetRow]("EntityMExploreGradeAssetTable.json")
|
gradeAssets, err := utils.ReadTable[EntityMExploreGradeAsset]("m_explore_grade_asset")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load explore grade asset table: %w", err)
|
return nil, fmt.Errorf("load explore grade asset table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := &ExploreCatalog{
|
catalog := &ExploreCatalog{
|
||||||
Explores: make(map[int32]ExploreRow, len(explores)),
|
Explores: make(map[int32]EntityMExplore, len(explores)),
|
||||||
GradeScores: make(map[int32][]ExploreGradeScoreRow),
|
GradeScores: make(map[int32][]EntityMExploreGradeScore),
|
||||||
GradeAssets: make(map[int32]int32, len(gradeAssets)),
|
GradeAssets: make(map[int32]int32, len(gradeAssets)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,24 +10,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type gachaMedalRow struct {
|
|
||||||
GachaMedalId int32 `json:"GachaMedalId"`
|
|
||||||
ShopTransitionGachaId int32 `json:"ShopTransitionGachaId"`
|
|
||||||
ConsumableItemId int32 `json:"ConsumableItemId"`
|
|
||||||
AutoConvertDatetime int64 `json:"AutoConvertDatetime"`
|
|
||||||
ConversionRate int32 `json:"ConversionRate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type momBannerRow struct {
|
|
||||||
MomBannerId int32 `json:"MomBannerId"`
|
|
||||||
SortOrderDesc int32 `json:"SortOrderDesc"`
|
|
||||||
DestinationDomainType int32 `json:"DestinationDomainType"`
|
|
||||||
DestinationDomainId int32 `json:"DestinationDomainId"`
|
|
||||||
BannerAssetName string `json:"BannerAssetName"`
|
|
||||||
StartDatetime int64 `json:"StartDatetime"`
|
|
||||||
EndDatetime int64 `json:"EndDatetime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GachaMedalInfo struct {
|
type GachaMedalInfo struct {
|
||||||
GachaMedalId int32
|
GachaMedalId int32
|
||||||
ConsumableItemId int32
|
ConsumableItemId int32
|
||||||
@@ -38,16 +20,16 @@ type GachaMedalInfo struct {
|
|||||||
const chapterGachaIdBase int32 = 200000
|
const chapterGachaIdBase int32 = 200000
|
||||||
|
|
||||||
func LoadGachaCatalog() ([]store.GachaCatalogEntry, map[int32]GachaMedalInfo, error) {
|
func LoadGachaCatalog() ([]store.GachaCatalogEntry, map[int32]GachaMedalInfo, error) {
|
||||||
medals, err := utils.ReadJSON[gachaMedalRow]("EntityMGachaMedalTable.json")
|
medals, err := utils.ReadTable[EntityMGachaMedal]("m_gacha_medal")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("load gacha medal table: %w", err)
|
return nil, nil, fmt.Errorf("load gacha medal table: %w", err)
|
||||||
}
|
}
|
||||||
banners, err := utils.ReadJSON[momBannerRow]("EntityMMomBannerTable.json")
|
banners, err := utils.ReadTable[EntityMMomBanner]("m_mom_banner")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("load mom banner table: %w", err)
|
return nil, nil, fmt.Errorf("load mom banner table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gachaToMedal := make(map[int32]gachaMedalRow)
|
gachaToMedal := make(map[int32]EntityMGachaMedal)
|
||||||
medalInfoByGacha := make(map[int32]GachaMedalInfo)
|
medalInfoByGacha := make(map[int32]GachaMedalInfo)
|
||||||
for _, m := range medals {
|
for _, m := range medals {
|
||||||
gachaToMedal[m.ShopTransitionGachaId] = m
|
gachaToMedal[m.ShopTransitionGachaId] = m
|
||||||
@@ -59,7 +41,7 @@ func LoadGachaCatalog() ([]store.GachaCatalogEntry, map[int32]GachaMedalInfo, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stepupSteps := make(map[int32][]momBannerRow)
|
stepupSteps := make(map[int32][]EntityMMomBanner)
|
||||||
var entries []store.GachaCatalogEntry
|
var entries []store.GachaCatalogEntry
|
||||||
|
|
||||||
for _, b := range banners {
|
for _, b := range banners {
|
||||||
|
|||||||
@@ -46,58 +46,28 @@ type GachaCatalog struct {
|
|||||||
ShopFeaturedByMedal map[int32][]ShopFeaturedEntry // consumableId -> paired entries
|
ShopFeaturedByMedal map[int32][]ShopFeaturedEntry // consumableId -> paired entries
|
||||||
}
|
}
|
||||||
|
|
||||||
type costumePoolRow struct {
|
|
||||||
CostumeId int32 `json:"CostumeId"`
|
|
||||||
CharacterId int32 `json:"CharacterId"`
|
|
||||||
SkillfulWeaponType int32 `json:"SkillfulWeaponType"`
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type weaponPoolRow struct {
|
|
||||||
WeaponId int32 `json:"WeaponId"`
|
|
||||||
WeaponType int32 `json:"WeaponType"`
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
IsRestrictDiscard bool `json:"IsRestrictDiscard"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type catalogCostumeRow struct {
|
|
||||||
CostumeId int32 `json:"CostumeId"`
|
|
||||||
CatalogTermId int32 `json:"CatalogTermId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type catalogWeaponRow struct {
|
|
||||||
WeaponId int32 `json:"WeaponId"`
|
|
||||||
CatalogTermId int32 `json:"CatalogTermId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type materialPoolRow struct {
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
MaterialType int32 `json:"MaterialType"`
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadGachaPool() (*GachaCatalog, error) {
|
func LoadGachaPool() (*GachaCatalog, error) {
|
||||||
costumes, err := utils.ReadJSON[costumePoolRow]("EntityMCostumeTable.json")
|
costumes, err := utils.ReadTable[EntityMCostume]("m_costume")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume table: %w", err)
|
return nil, fmt.Errorf("load costume table: %w", err)
|
||||||
}
|
}
|
||||||
weapons, err := utils.ReadJSON[weaponPoolRow]("EntityMWeaponTable.json")
|
weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon table: %w", err)
|
return nil, fmt.Errorf("load weapon table: %w", err)
|
||||||
}
|
}
|
||||||
catalogCostumes, err := utils.ReadJSON[catalogCostumeRow]("EntityMCatalogCostumeTable.json")
|
catalogCostumes, err := utils.ReadTable[EntityMCatalogCostume]("m_catalog_costume")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load catalog costume table: %w", err)
|
return nil, fmt.Errorf("load catalog costume table: %w", err)
|
||||||
}
|
}
|
||||||
catalogWeapons, err := utils.ReadJSON[catalogWeaponRow]("EntityMCatalogWeaponTable.json")
|
catalogWeapons, err := utils.ReadTable[EntityMCatalogWeapon]("m_catalog_weapon")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load catalog weapon table: %w", err)
|
return nil, fmt.Errorf("load catalog weapon table: %w", err)
|
||||||
}
|
}
|
||||||
materials, err := utils.ReadJSON[materialPoolRow]("EntityMMaterialTable.json")
|
materials, err := utils.ReadTable[EntityMMaterial]("m_material")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load material table: %w", err)
|
return nil, fmt.Errorf("load material table: %w", err)
|
||||||
}
|
}
|
||||||
evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json")
|
evoGroupRows, err := utils.ReadTable[EntityMWeaponEvolutionGroup]("m_weapon_evolution_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
|
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -414,8 +384,8 @@ func (pool *GachaCatalog) BuildBannerPools(entries []store.GachaCatalogEntry) {
|
|||||||
len(pool.BannerPools), len(allFeaturedCostumes), len(allFeaturedWeapons))
|
len(pool.BannerPools), len(allFeaturedCostumes), len(allFeaturedWeapons))
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildEvolvedWeaponSet(rows []WeaponEvolutionGroupRow) map[int32]bool {
|
func buildEvolvedWeaponSet(rows []EntityMWeaponEvolutionGroup) map[int32]bool {
|
||||||
grouped := make(map[int32][]WeaponEvolutionGroupRow)
|
grouped := make(map[int32][]EntityMWeaponEvolutionGroup)
|
||||||
for _, r := range rows {
|
for _, r := range rows {
|
||||||
grouped[r.WeaponEvolutionGroupId] = append(grouped[r.WeaponEvolutionGroupId], r)
|
grouped[r.WeaponEvolutionGroupId] = append(grouped[r.WeaponEvolutionGroupId], r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type gimmickScheduleRow struct {
|
|
||||||
GimmickSequenceScheduleId int32 `json:"GimmickSequenceScheduleId"`
|
|
||||||
StartDatetime int64 `json:"StartDatetime"`
|
|
||||||
EndDatetime int64 `json:"EndDatetime"`
|
|
||||||
FirstGimmickSequenceId int32 `json:"FirstGimmickSequenceId"`
|
|
||||||
ReleaseEvaluateConditionId int32 `json:"ReleaseEvaluateConditionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type gimmickScheduleEntry struct {
|
type gimmickScheduleEntry struct {
|
||||||
ScheduleId int32
|
ScheduleId int32
|
||||||
StartDatetime int64
|
StartDatetime int64
|
||||||
@@ -30,7 +22,7 @@ type GimmickCatalog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadGimmickCatalog(resolver *ConditionResolver) (*GimmickCatalog, error) {
|
func LoadGimmickCatalog(resolver *ConditionResolver) (*GimmickCatalog, error) {
|
||||||
rows, err := utils.ReadJSON[gimmickScheduleRow]("EntityMGimmickSequenceScheduleTable.json")
|
rows, err := utils.ReadTable[EntityMGimmickSequenceSchedule]("m_gimmick_sequence_schedule")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load gimmick sequence schedule table: %w", err)
|
return nil, fmt.Errorf("load gimmick sequence schedule table: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type loginBonusStamp struct {
|
|
||||||
LoginBonusId int32 `json:"LoginBonusId"`
|
|
||||||
LowerPageNumber int32 `json:"LowerPageNumber"`
|
|
||||||
StampNumber int32 `json:"StampNumber"`
|
|
||||||
RewardPossessionType int32 `json:"RewardPossessionType"`
|
|
||||||
RewardPossessionId int32 `json:"RewardPossessionId"`
|
|
||||||
RewardCount int32 `json:"RewardCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type loginBonusStampKey struct {
|
type loginBonusStampKey struct {
|
||||||
LoginBonusId int32
|
LoginBonusId int32
|
||||||
LowerPageNumber int32
|
LowerPageNumber int32
|
||||||
@@ -36,7 +27,7 @@ func (c *LoginBonusCatalog) LookupStampReward(loginBonusId, pageNumber, stampNum
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadLoginBonusCatalog() *LoginBonusCatalog {
|
func LoadLoginBonusCatalog() *LoginBonusCatalog {
|
||||||
stamps, err := utils.ReadJSON[loginBonusStamp]("EntityMLoginBonusStampTable.json")
|
stamps, err := utils.ReadTable[EntityMLoginBonusStamp]("m_login_bonus_stamp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load login bonus stamp table: %v", err)
|
log.Fatalf("load login bonus stamp table: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,29 +7,15 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MaterialRow struct {
|
func LoadParameterMap() ([]EntityMNumericalParameterMap, error) {
|
||||||
MaterialId int32 `json:"MaterialId"`
|
rows, err := utils.ReadTable[EntityMNumericalParameterMap]("m_numerical_parameter_map")
|
||||||
MaterialType model.MaterialType `json:"MaterialType"`
|
|
||||||
WeaponType int32 `json:"WeaponType"`
|
|
||||||
EffectValue int32 `json:"EffectValue"`
|
|
||||||
SellPrice int32 `json:"SellPrice"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type numericalParameterMapRow struct {
|
|
||||||
NumericalParameterMapId int32 `json:"NumericalParameterMapId"`
|
|
||||||
ParameterKey int32 `json:"ParameterKey"`
|
|
||||||
ParameterValue int32 `json:"ParameterValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadParameterMap() ([]numericalParameterMapRow, error) {
|
|
||||||
rows, err := utils.ReadJSON[numericalParameterMapRow]("EntityMNumericalParameterMapTable.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load numerical parameter map table: %w", err)
|
return nil, fmt.Errorf("load numerical parameter map table: %w", err)
|
||||||
}
|
}
|
||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildExpThresholds(paramMapRows []numericalParameterMapRow, mapId int32) []int32 {
|
func BuildExpThresholds(paramMapRows []EntityMNumericalParameterMap, mapId int32) []int32 {
|
||||||
maxKey := int32(0)
|
maxKey := int32(0)
|
||||||
for _, r := range paramMapRows {
|
for _, r := range paramMapRows {
|
||||||
if r.NumericalParameterMapId == mapId && r.ParameterKey > maxKey {
|
if r.NumericalParameterMapId == mapId && r.ParameterKey > maxKey {
|
||||||
@@ -46,26 +32,27 @@ func BuildExpThresholds(paramMapRows []numericalParameterMapRow, mapId int32) []
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MaterialCatalog struct {
|
type MaterialCatalog struct {
|
||||||
All map[int32]MaterialRow
|
All map[int32]EntityMMaterial
|
||||||
ByType map[model.MaterialType]map[int32]MaterialRow
|
ByType map[model.MaterialType]map[int32]EntityMMaterial
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadMaterialCatalog() (*MaterialCatalog, error) {
|
func LoadMaterialCatalog() (*MaterialCatalog, error) {
|
||||||
rows, err := utils.ReadJSON[MaterialRow]("EntityMMaterialTable.json")
|
rows, err := utils.ReadTable[EntityMMaterial]("m_material")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load material table: %w", err)
|
return nil, fmt.Errorf("load material table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := &MaterialCatalog{
|
catalog := &MaterialCatalog{
|
||||||
All: make(map[int32]MaterialRow, len(rows)),
|
All: make(map[int32]EntityMMaterial, len(rows)),
|
||||||
ByType: make(map[model.MaterialType]map[int32]MaterialRow),
|
ByType: make(map[model.MaterialType]map[int32]EntityMMaterial),
|
||||||
}
|
}
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
catalog.All[row.MaterialId] = row
|
catalog.All[row.MaterialId] = row
|
||||||
if catalog.ByType[row.MaterialType] == nil {
|
mt := model.MaterialType(row.MaterialType)
|
||||||
catalog.ByType[row.MaterialType] = make(map[int32]MaterialRow)
|
if catalog.ByType[mt] == nil {
|
||||||
|
catalog.ByType[mt] = make(map[int32]EntityMMaterial)
|
||||||
}
|
}
|
||||||
catalog.ByType[row.MaterialType][row.MaterialId] = row
|
catalog.ByType[mt][row.MaterialId] = row
|
||||||
}
|
}
|
||||||
return catalog, nil
|
return catalog, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,257 @@
|
|||||||
|
package memorydb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pierrec/lz4/v4"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tables map[string][]byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
aesKeyHex = "36436230313332314545356536624265"
|
||||||
|
aesIVHex = "45666341656634434165356536446141"
|
||||||
|
lz4ExtCode = int8(99)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init(binPath string) error {
|
||||||
|
encrypted, err := os.ReadFile(binPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read bin.e: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted, err := decrypt(encrypted)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decrypt: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toc, dataBlob, err := parseHeader(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tables = make(map[string][]byte, len(toc))
|
||||||
|
for name, offLen := range toc {
|
||||||
|
off := offLen[0]
|
||||||
|
length := offLen[1]
|
||||||
|
if off+length > len(dataBlob) {
|
||||||
|
return fmt.Errorf("table %q: offset %d + length %d exceeds data blob size %d", name, off, length, len(dataBlob))
|
||||||
|
}
|
||||||
|
tables[name] = dataBlob[off : off+length]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TableCount() int {
|
||||||
|
return len(tables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TableBytes(key string) ([]byte, bool) {
|
||||||
|
b, ok := tables[key]
|
||||||
|
return b, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadTable[T any](key string) ([]T, error) {
|
||||||
|
raw, ok := TableBytes(key)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("table %q not found in master data", key)
|
||||||
|
}
|
||||||
|
return decompressAndUnmarshal[T](raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decrypt(data []byte) ([]byte, error) {
|
||||||
|
key, err := hex.DecodeString(aesKeyHex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode key: %w", err)
|
||||||
|
}
|
||||||
|
iv, err := hex.DecodeString(aesIVHex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decode iv: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("new cipher: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data)%aes.BlockSize != 0 {
|
||||||
|
return nil, fmt.Errorf("ciphertext length %d is not a multiple of block size %d", len(data), aes.BlockSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted := make([]byte, len(data))
|
||||||
|
cbc := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
cbc.CryptBlocks(decrypted, data)
|
||||||
|
|
||||||
|
decrypted, err = pkcs7Unpad(decrypted, aes.BlockSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unpad: %w", err)
|
||||||
|
}
|
||||||
|
return decrypted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil, fmt.Errorf("empty data")
|
||||||
|
}
|
||||||
|
padLen := int(data[len(data)-1])
|
||||||
|
if padLen == 0 || padLen > blockSize || padLen > len(data) {
|
||||||
|
return nil, fmt.Errorf("invalid padding length %d", padLen)
|
||||||
|
}
|
||||||
|
for _, b := range data[len(data)-padLen:] {
|
||||||
|
if int(b) != padLen {
|
||||||
|
return nil, fmt.Errorf("invalid padding byte")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data[:len(data)-padLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHeader(data []byte) (map[string][2]int, []byte, error) {
|
||||||
|
// Decode the header (first msgpack object) using Decode into interface{},
|
||||||
|
// then compute how many bytes it consumed to find the data blob start.
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
dec := msgpack.NewDecoder(r)
|
||||||
|
dec.UseLooseInterfaceDecoding(true)
|
||||||
|
|
||||||
|
var headerRaw interface{}
|
||||||
|
if err := dec.Decode(&headerRaw); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("decode header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headerMap, ok := headerRaw.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, fmt.Errorf("header is not a map, got %T", headerRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
toc := make(map[string][2]int, len(headerMap))
|
||||||
|
for name, val := range headerMap {
|
||||||
|
arr, ok := val.([]interface{})
|
||||||
|
if !ok || len(arr) != 2 {
|
||||||
|
return nil, nil, fmt.Errorf("table %q: expected [offset, length] array, got %T", name, val)
|
||||||
|
}
|
||||||
|
offset, err := toInt(arr[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("table %q offset: %w", name, err)
|
||||||
|
}
|
||||||
|
length, err := toInt(arr[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("table %q length: %w", name, err)
|
||||||
|
}
|
||||||
|
toc[name] = [2]int{offset, length}
|
||||||
|
}
|
||||||
|
|
||||||
|
consumed := int(int64(len(data)) - int64(r.Len()))
|
||||||
|
return toc, data[consumed:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInt(v interface{}) (int, error) {
|
||||||
|
switch n := v.(type) {
|
||||||
|
case int8:
|
||||||
|
return int(n), nil
|
||||||
|
case int16:
|
||||||
|
return int(n), nil
|
||||||
|
case int32:
|
||||||
|
return int(n), nil
|
||||||
|
case int64:
|
||||||
|
return int(n), nil
|
||||||
|
case uint8:
|
||||||
|
return int(n), nil
|
||||||
|
case uint16:
|
||||||
|
return int(n), nil
|
||||||
|
case uint32:
|
||||||
|
return int(n), nil
|
||||||
|
case uint64:
|
||||||
|
return int(n), nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("cannot convert %T to int", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decompressAndUnmarshal[T any](raw []byte) ([]T, error) {
|
||||||
|
// Peek at the raw msgpack to check if it's an ext type (LZ4 compressed)
|
||||||
|
// or a plain array.
|
||||||
|
if len(raw) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to decode as ext type first
|
||||||
|
dec := msgpack.NewDecoder(bytes.NewReader(raw))
|
||||||
|
code, extData, err := decodeExt(dec)
|
||||||
|
if err == nil && code == lz4ExtCode {
|
||||||
|
uncompressedSize, lz4Data, err := readLZ4ExtHeader(extData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read lz4 ext header: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed := make([]byte, uncompressedSize)
|
||||||
|
n, err := lz4.UncompressBlock(lz4Data, decompressed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("lz4 decompress: %w", err)
|
||||||
|
}
|
||||||
|
decompressed = decompressed[:n]
|
||||||
|
|
||||||
|
var result []T
|
||||||
|
if err := msgpack.Unmarshal(decompressed, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal decompressed table: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not LZ4 compressed, try as plain array
|
||||||
|
var result []T
|
||||||
|
if err := msgpack.Unmarshal(raw, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal plain table: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeExt(dec *msgpack.Decoder) (int8, []byte, error) {
|
||||||
|
var ext msgpack.RawMessage
|
||||||
|
if err := dec.Decode(&ext); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
// ext is the full msgpack ext bytes including the header.
|
||||||
|
// Re-decode just the header to get the type code, then the body is the rest.
|
||||||
|
innerDec := msgpack.NewDecoder(bytes.NewReader(ext))
|
||||||
|
extID, extLen, err := innerDec.DecodeExtHeader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
extData := make([]byte, extLen)
|
||||||
|
if _, err := innerDec.Buffered().Read(extData); err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("read ext data: %w", err)
|
||||||
|
}
|
||||||
|
return extID, extData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLZ4ExtHeader(data []byte) (int, []byte, error) {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0, nil, fmt.Errorf("empty ext data")
|
||||||
|
}
|
||||||
|
tag := data[0]
|
||||||
|
switch {
|
||||||
|
case tag == 0xd2: // big-endian int32
|
||||||
|
if len(data) < 5 {
|
||||||
|
return 0, nil, fmt.Errorf("not enough data for int32 size")
|
||||||
|
}
|
||||||
|
size := int(int32(binary.BigEndian.Uint32(data[1:5])))
|
||||||
|
return size, data[5:], nil
|
||||||
|
case tag == 0xce: // big-endian uint32
|
||||||
|
if len(data) < 5 {
|
||||||
|
return 0, nil, fmt.Errorf("not enough data for uint32 size")
|
||||||
|
}
|
||||||
|
size := int(binary.BigEndian.Uint32(data[1:5]))
|
||||||
|
return size, data[5:], nil
|
||||||
|
case tag <= 0x7f: // positive fixint
|
||||||
|
return int(tag), data[1:], nil
|
||||||
|
default:
|
||||||
|
return 0, nil, fmt.Errorf("unexpected tag 0x%02x in LZ4 ext header", tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,18 +8,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type numericalFunctionRow struct {
|
|
||||||
NumericalFunctionId int32 `json:"NumericalFunctionId"`
|
|
||||||
NumericalFunctionType int32 `json:"NumericalFunctionType"`
|
|
||||||
NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type numericalFunctionParameterRow struct {
|
|
||||||
NumericalFunctionParameterGroupId int32 `json:"NumericalFunctionParameterGroupId"`
|
|
||||||
ParameterIndex int32 `json:"ParameterIndex"`
|
|
||||||
ParameterValue int32 `json:"ParameterValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type NumericalFunc struct {
|
type NumericalFunc struct {
|
||||||
Type model.NumericalFunctionType
|
Type model.NumericalFunctionType
|
||||||
Params []int32
|
Params []int32
|
||||||
@@ -61,17 +49,17 @@ type FunctionResolver struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadFunctionResolver() (*FunctionResolver, error) {
|
func LoadFunctionResolver() (*FunctionResolver, error) {
|
||||||
funcRows, err := utils.ReadJSON[numericalFunctionRow]("EntityMNumericalFunctionTable.json")
|
funcRows, err := utils.ReadTable[EntityMNumericalFunction]("m_numerical_function")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load numerical function table: %w", err)
|
return nil, fmt.Errorf("load numerical function table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
paramRows, err := utils.ReadJSON[numericalFunctionParameterRow]("EntityMNumericalFunctionParameterGroupTable.json")
|
paramRows, err := utils.ReadTable[EntityMNumericalFunctionParameterGroup]("m_numerical_function_parameter_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load numerical function parameter group table: %w", err)
|
return nil, fmt.Errorf("load numerical function parameter group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
paramsByGroup := make(map[int32][]numericalFunctionParameterRow, len(paramRows))
|
paramsByGroup := make(map[int32][]EntityMNumericalFunctionParameterGroup, len(paramRows))
|
||||||
for _, r := range paramRows {
|
for _, r := range paramRows {
|
||||||
paramsByGroup[r.NumericalFunctionParameterGroupId] = append(
|
paramsByGroup[r.NumericalFunctionParameterGroupId] = append(
|
||||||
paramsByGroup[r.NumericalFunctionParameterGroupId], r)
|
paramsByGroup[r.NumericalFunctionParameterGroupId], r)
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type omikujiEntry struct {
|
|
||||||
OmikujiId int32 `json:"OmikujiId"`
|
|
||||||
OmikujiAssetId int32 `json:"OmikujiAssetId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OmikujiCatalog struct {
|
type OmikujiCatalog struct {
|
||||||
assetIds map[int32]int32
|
assetIds map[int32]int32
|
||||||
}
|
}
|
||||||
@@ -22,7 +17,7 @@ func (c *OmikujiCatalog) LookupAssetId(omikujiId int32) int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadOmikujiCatalog() *OmikujiCatalog {
|
func LoadOmikujiCatalog() *OmikujiCatalog {
|
||||||
entries, err := utils.ReadJSON[omikujiEntry]("EntityMOmikujiTable.json")
|
entries, err := utils.ReadTable[EntityMOmikuji]("m_omikuji")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load omikuji table: %v", err)
|
log.Fatalf("load omikuji table: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,63 +7,37 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PartsRow struct {
|
|
||||||
PartsId int32 `json:"PartsId"`
|
|
||||||
RarityType model.RarityType `json:"RarityType"`
|
|
||||||
PartsGroupId int32 `json:"PartsGroupId"`
|
|
||||||
PartsStatusMainLotteryGroupId int32 `json:"PartsStatusMainLotteryGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PartsRarityRow struct {
|
|
||||||
RarityType model.RarityType `json:"RarityType"`
|
|
||||||
PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"`
|
|
||||||
PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"`
|
|
||||||
SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type partsLevelUpRateRow struct {
|
|
||||||
PartsLevelUpRateGroupId int32 `json:"PartsLevelUpRateGroupId"`
|
|
||||||
LevelLowerLimit int32 `json:"LevelLowerLimit"`
|
|
||||||
SuccessRatePermil int32 `json:"SuccessRatePermil"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type partsLevelUpPriceRow struct {
|
|
||||||
PartsLevelUpPriceGroupId int32 `json:"PartsLevelUpPriceGroupId"`
|
|
||||||
LevelLowerLimit int32 `json:"LevelLowerLimit"`
|
|
||||||
Gold int32 `json:"Gold"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PartsCatalog struct {
|
type PartsCatalog struct {
|
||||||
PartsById map[int32]PartsRow
|
PartsById map[int32]EntityMParts
|
||||||
DefaultPartsStatusMainByLotteryGroup map[int32]int32
|
DefaultPartsStatusMainByLotteryGroup map[int32]int32
|
||||||
RarityByRarityType map[model.RarityType]PartsRarityRow
|
RarityByRarityType map[model.RarityType]EntityMPartsRarity
|
||||||
RateByGroupAndLevel map[int32]map[int32]int32
|
RateByGroupAndLevel map[int32]map[int32]int32
|
||||||
PriceByGroupAndLevel map[int32]map[int32]int32
|
PriceByGroupAndLevel map[int32]map[int32]int32
|
||||||
SellPriceByRarity map[model.RarityType]NumericalFunc
|
SellPriceByRarity map[model.RarityType]NumericalFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPartsCatalog() (*PartsCatalog, error) {
|
func LoadPartsCatalog() (*PartsCatalog, error) {
|
||||||
partsRows, err := utils.ReadJSON[PartsRow]("EntityMPartsTable.json")
|
partsRows, err := utils.ReadTable[EntityMParts]("m_parts")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load parts table: %w", err)
|
return nil, fmt.Errorf("load parts table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rarityRows, err := utils.ReadJSON[PartsRarityRow]("EntityMPartsRarityTable.json")
|
rarityRows, err := utils.ReadTable[EntityMPartsRarity]("m_parts_rarity")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load parts rarity table: %w", err)
|
return nil, fmt.Errorf("load parts rarity table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rateRows, err := utils.ReadJSON[partsLevelUpRateRow]("EntityMPartsLevelUpRateGroupTable.json")
|
rateRows, err := utils.ReadTable[EntityMPartsLevelUpRateGroup]("m_parts_level_up_rate_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load parts level up rate table: %w", err)
|
return nil, fmt.Errorf("load parts level up rate table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
priceRows, err := utils.ReadJSON[partsLevelUpPriceRow]("EntityMPartsLevelUpPriceGroupTable.json")
|
priceRows, err := utils.ReadTable[EntityMPartsLevelUpPriceGroup]("m_parts_level_up_price_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load parts level up price table: %w", err)
|
return nil, fmt.Errorf("load parts level up price table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
partsById := make(map[int32]PartsRow, len(partsRows))
|
partsById := make(map[int32]EntityMParts, len(partsRows))
|
||||||
for _, p := range partsRows {
|
for _, p := range partsRows {
|
||||||
partsById[p.PartsId] = p
|
partsById[p.PartsId] = p
|
||||||
}
|
}
|
||||||
@@ -84,7 +58,7 @@ func LoadPartsCatalog() (*PartsCatalog, error) {
|
|||||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rarityByRarityType := make(map[model.RarityType]PartsRarityRow, len(rarityRows))
|
rarityByRarityType := make(map[model.RarityType]EntityMPartsRarity, len(rarityRows))
|
||||||
sellPriceByRarity := make(map[model.RarityType]NumericalFunc, len(rarityRows))
|
sellPriceByRarity := make(map[model.RarityType]NumericalFunc, len(rarityRows))
|
||||||
for _, r := range rarityRows {
|
for _, r := range rarityRows {
|
||||||
rarityByRarityType[r.RarityType] = r
|
rarityByRarityType[r.RarityType] = r
|
||||||
|
|||||||
@@ -4,214 +4,34 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"lunar-tear/server/internal/model"
|
|
||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QuestSceneRow struct {
|
|
||||||
QuestSceneId int32 `json:"QuestSceneId"`
|
|
||||||
QuestId int32 `json:"QuestId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
QuestSceneType model.QuestSceneType `json:"QuestSceneType"`
|
|
||||||
AssetBackgroundId int32 `json:"AssetBackgroundId"`
|
|
||||||
EventMapNumberUpper int32 `json:"EventMapNumberUpper"`
|
|
||||||
EventMapNumberLower int32 `json:"EventMapNumberLower"`
|
|
||||||
IsMainFlowQuestTarget bool `json:"IsMainFlowQuestTarget"`
|
|
||||||
IsBattleOnlyTarget bool `json:"IsBattleOnlyTarget"`
|
|
||||||
QuestResultType model.QuestResultType `json:"QuestResultType"`
|
|
||||||
IsStorySkipTarget bool `json:"IsStorySkipTarget"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestRow struct {
|
|
||||||
QuestId int32 `json:"QuestId"`
|
|
||||||
NameQuestTextId int32 `json:"NameQuestTextId"`
|
|
||||||
PictureBookNameQuestTextId int32 `json:"PictureBookNameQuestTextId"`
|
|
||||||
QuestReleaseConditionListId int32 `json:"QuestReleaseConditionListId"`
|
|
||||||
StoryQuestTextId int32 `json:"StoryQuestTextId"`
|
|
||||||
QuestDisplayAttributeGroupId int32 `json:"QuestDisplayAttributeGroupId"`
|
|
||||||
RecommendedDeckPower int32 `json:"RecommendedDeckPower"`
|
|
||||||
QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
|
|
||||||
QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"`
|
|
||||||
QuestDeckRestrictionGroupId int32 `json:"QuestDeckRestrictionGroupId"`
|
|
||||||
QuestMissionGroupId int32 `json:"QuestMissionGroupId"`
|
|
||||||
Stamina int32 `json:"Stamina"`
|
|
||||||
UserExp int32 `json:"UserExp"`
|
|
||||||
CharacterExp int32 `json:"CharacterExp"`
|
|
||||||
CostumeExp int32 `json:"CostumeExp"`
|
|
||||||
Gold int32 `json:"Gold"`
|
|
||||||
DailyClearableCount int32 `json:"DailyClearableCount"`
|
|
||||||
IsRunInTheBackground bool `json:"IsRunInTheBackground"`
|
|
||||||
IsCountedAsQuest bool `json:"IsCountedAsQuest"`
|
|
||||||
QuestBonusId int32 `json:"QuestBonusId"`
|
|
||||||
IsNotShowAfterClear bool `json:"IsNotShowAfterClear"`
|
|
||||||
IsBigWinTarget bool `json:"IsBigWinTarget"`
|
|
||||||
IsUsableSkipTicket bool `json:"IsUsableSkipTicket"`
|
|
||||||
QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"`
|
|
||||||
InvisibleQuestMissionGroupId int32 `json:"InvisibleQuestMissionGroupId"`
|
|
||||||
FieldEffectGroupId int32 `json:"FieldEffectGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestMissionRow struct {
|
|
||||||
QuestMissionId int32 `json:"QuestMissionId"`
|
|
||||||
QuestMissionConditionType model.QuestMissionConditionType `json:"QuestMissionConditionType"`
|
|
||||||
QuestMissionRewardId int32 `json:"QuestMissionRewardId"`
|
|
||||||
QuestMissionConditionValueGroupId int32 `json:"QuestMissionConditionValueGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestMissionGroupRow struct {
|
|
||||||
QuestMissionGroupId int32 `json:"QuestMissionGroupId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
QuestMissionId int32 `json:"QuestMissionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestMissionRewardRow struct {
|
|
||||||
QuestMissionRewardId int32 `json:"QuestMissionRewardId"`
|
|
||||||
PossessionType model.PossessionType `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MainQuestSequenceRow struct {
|
|
||||||
MainQuestSequenceId int32 `json:"MainQuestSequenceId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
QuestId int32 `json:"QuestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MainQuestRouteRow struct {
|
|
||||||
MainQuestRouteId int32 `json:"MainQuestRouteId"`
|
|
||||||
MainQuestSeasonId int32 `json:"MainQuestSeasonId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
CharacterId int32 `json:"CharacterId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MainQuestChapterRow struct {
|
|
||||||
MainQuestChapterId int32 `json:"MainQuestChapterId"`
|
|
||||||
MainQuestRouteId int32 `json:"MainQuestRouteId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
MainQuestSequenceGroupId int32 `json:"MainQuestSequenceGroupId"`
|
|
||||||
PortalCageCharacterGroupId int32 `json:"PortalCageCharacterGroupId"`
|
|
||||||
StartDatetime int64 `json:"StartDatetime"`
|
|
||||||
IsInvisibleInLibrary bool `json:"IsInvisibleInLibrary"`
|
|
||||||
JoinLibraryChapterId int32 `json:"JoinLibraryChapterId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestFirstClearRewardSwitchRow struct {
|
|
||||||
QuestId int32 `json:"QuestId"`
|
|
||||||
QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
|
|
||||||
SwitchConditionClearQuestId int32 `json:"SwitchConditionClearQuestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestFirstClearRewardGroupRow struct {
|
|
||||||
QuestFirstClearRewardGroupId int32 `json:"QuestFirstClearRewardGroupId"`
|
|
||||||
QuestFirstClearRewardType int32 `json:"QuestFirstClearRewardType"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
PossessionType model.PossessionType `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
IsPickup bool `json:"IsPickup"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestReplayFlowRewardGroupRow struct {
|
|
||||||
QuestReplayFlowRewardGroupId int32 `json:"QuestReplayFlowRewardGroupId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
PossessionType model.PossessionType `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestSceneGrantRow struct {
|
|
||||||
QuestSceneId int32 `json:"QuestSceneId"`
|
|
||||||
PossessionType model.PossessionType `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestPickupRewardGroupRow struct {
|
|
||||||
QuestPickupRewardGroupId int32 `json:"QuestPickupRewardGroupId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
BattleDropRewardId int32 `json:"BattleDropRewardId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BattleDropRewardRow struct {
|
|
||||||
BattleDropRewardId int32 `json:"BattleDropRewardId"`
|
|
||||||
PossessionType model.PossessionType `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestSceneBattleRow struct {
|
|
||||||
QuestSceneId int32 `json:"QuestSceneId"`
|
|
||||||
BattleGroupId int32 `json:"BattleGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BattleGroupRow struct {
|
|
||||||
BattleGroupId int32 `json:"BattleGroupId"`
|
|
||||||
WaveNumber int32 `json:"WaveNumber"`
|
|
||||||
BattleId int32 `json:"BattleId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BattleRow struct {
|
|
||||||
BattleId int32 `json:"BattleId"`
|
|
||||||
BattleNpcId int32 `json:"BattleNpcId"`
|
|
||||||
DeckType model.DeckType `json:"DeckType"`
|
|
||||||
BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BattleNpcDeckRow struct {
|
|
||||||
BattleNpcId int32 `json:"BattleNpcId"`
|
|
||||||
DeckType model.DeckType `json:"DeckType"`
|
|
||||||
BattleNpcDeckNumber int32 `json:"BattleNpcDeckNumber"`
|
|
||||||
BattleNpcDeckCharacterUuid01 string `json:"BattleNpcDeckCharacterUuid01"`
|
|
||||||
BattleNpcDeckCharacterUuid02 string `json:"BattleNpcDeckCharacterUuid02"`
|
|
||||||
BattleNpcDeckCharacterUuid03 string `json:"BattleNpcDeckCharacterUuid03"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BattleNpcDropCategoryRow struct {
|
|
||||||
BattleNpcId int32 `json:"BattleNpcId"`
|
|
||||||
BattleNpcDeckCharacterUuid string `json:"BattleNpcDeckCharacterUuid"`
|
|
||||||
BattleDropCategoryId int32 `json:"BattleDropCategoryId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BattleDropInfo struct {
|
type BattleDropInfo struct {
|
||||||
QuestSceneId int32
|
QuestSceneId int32
|
||||||
BattleDropCategoryId int32
|
BattleDropCategoryId int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type TutorialUnlockConditionRow struct {
|
|
||||||
TutorialType int32 `json:"TutorialType"`
|
|
||||||
TutorialUnlockConditionType int32 `json:"TutorialUnlockConditionType"`
|
|
||||||
ConditionValue int32 `json:"ConditionValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RentalDeckRow struct {
|
|
||||||
BattleGroupId int32 `json:"BattleGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserLevelRow struct {
|
|
||||||
UserLevel int32 `json:"UserLevel"`
|
|
||||||
MaxStamina int32 `json:"MaxStamina"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QuestCatalog struct {
|
type QuestCatalog struct {
|
||||||
SceneById map[int32]QuestSceneRow
|
SceneById map[int32]EntityMQuestScene
|
||||||
MissionById map[int32]QuestMissionRow
|
MissionById map[int32]EntityMQuestMission
|
||||||
QuestById map[int32]QuestRow
|
QuestById map[int32]EntityMQuest
|
||||||
MissionIdsByQuestId map[int32][]int32
|
MissionIdsByQuestId map[int32][]int32
|
||||||
RouteIdByQuestId map[int32]int32
|
RouteIdByQuestId map[int32]int32
|
||||||
SceneIdsByQuestId map[int32][]int32
|
SceneIdsByQuestId map[int32][]int32
|
||||||
OrderedQuestIds []int32
|
OrderedQuestIds []int32
|
||||||
FirstClearRewardsByGroupId map[int32][]QuestFirstClearRewardGroupRow
|
FirstClearRewardsByGroupId map[int32][]EntityMQuestFirstClearRewardGroup
|
||||||
FirstClearRewardSwitchesByQuestId map[int32][]QuestFirstClearRewardSwitchRow
|
FirstClearRewardSwitchesByQuestId map[int32][]EntityMQuestFirstClearRewardSwitch
|
||||||
MissionRewardsByMissionId map[int32][]QuestMissionRewardRow
|
MissionRewardsByMissionId map[int32][]EntityMQuestMissionReward
|
||||||
WeaponIdsByReleaseConditionGroupId map[int32][]int32
|
WeaponIdsByReleaseConditionGroupId map[int32][]int32
|
||||||
ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow
|
ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup
|
||||||
SceneGrantsBySceneId map[int32][]QuestSceneGrantRow
|
SceneGrantsBySceneId map[int32][]EntityMUserQuestSceneGrantPossession
|
||||||
BattleDropRewardById map[int32]BattleDropRewardRow
|
BattleDropRewardById map[int32]EntityMBattleDropReward
|
||||||
PickupRewardIdsByGroupId map[int32][]int32
|
PickupRewardIdsByGroupId map[int32][]int32
|
||||||
BattleDropsByQuestId map[int32][]BattleDropInfo
|
BattleDropsByQuestId map[int32][]BattleDropInfo
|
||||||
ReplayFlowRewardsByGroupId map[int32][]QuestReplayFlowRewardGroupRow
|
ReplayFlowRewardsByGroupId map[int32][]EntityMQuestReplayFlowRewardGroup
|
||||||
RentalQuestIds map[int32]bool
|
RentalQuestIds map[int32]bool
|
||||||
TutorialUnlockConditions []TutorialUnlockConditionRow
|
TutorialUnlockConditions []EntityMTutorialUnlockCondition
|
||||||
ChapterLastSceneByQuestId map[int32]int32
|
ChapterLastSceneByQuestId map[int32]int32
|
||||||
SeasonIdByRouteId map[int32]int32
|
SeasonIdByRouteId map[int32]int32
|
||||||
|
|
||||||
@@ -221,8 +41,8 @@ type QuestCatalog struct {
|
|||||||
CostumeMaxLevelByRarity map[int32]NumericalFunc
|
CostumeMaxLevelByRarity map[int32]NumericalFunc
|
||||||
MaxStaminaByLevel map[int32]int32
|
MaxStaminaByLevel map[int32]int32
|
||||||
|
|
||||||
CostumeById map[int32]CostumeMasterRow
|
CostumeById map[int32]EntityMCostume
|
||||||
WeaponById map[int32]WeaponMasterRow
|
WeaponById map[int32]EntityMWeapon
|
||||||
|
|
||||||
WeaponSkillSlots map[int32][]int32
|
WeaponSkillSlots map[int32][]int32
|
||||||
WeaponAbilitySlots map[int32][]int32
|
WeaponAbilitySlots map[int32][]int32
|
||||||
@@ -231,7 +51,7 @@ type QuestCatalog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
||||||
scenes, err := utils.ReadJSON[QuestSceneRow]("EntityMQuestSceneTable.json")
|
scenes, err := utils.ReadTable[EntityMQuestScene]("m_quest_scene")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest scene table: %w", err)
|
return nil, fmt.Errorf("load quest scene table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -245,17 +65,17 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
return scenes[i].QuestSceneId < scenes[j].QuestSceneId
|
return scenes[i].QuestSceneId < scenes[j].QuestSceneId
|
||||||
})
|
})
|
||||||
|
|
||||||
missions, err := utils.ReadJSON[QuestMissionRow]("EntityMQuestMissionTable.json")
|
missions, err := utils.ReadTable[EntityMQuestMission]("m_quest_mission")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest mission table: %w", err)
|
return nil, fmt.Errorf("load quest mission table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
quests, err := utils.ReadJSON[QuestRow]("EntityMQuestTable.json")
|
quests, err := utils.ReadTable[EntityMQuest]("m_quest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest table: %w", err)
|
return nil, fmt.Errorf("load quest table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
missionGroups, err := utils.ReadJSON[QuestMissionGroupRow]("EntityMQuestMissionGroupTable.json")
|
missionGroups, err := utils.ReadTable[EntityMQuestMissionGroup]("m_quest_mission_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest mission group table: %w", err)
|
return nil, fmt.Errorf("load quest mission group table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -269,7 +89,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
return missionGroups[i].QuestMissionId < missionGroups[j].QuestMissionId
|
return missionGroups[i].QuestMissionId < missionGroups[j].QuestMissionId
|
||||||
})
|
})
|
||||||
|
|
||||||
sequences, err := utils.ReadJSON[MainQuestSequenceRow]("EntityMMainQuestSequenceTable.json")
|
sequences, err := utils.ReadTable[EntityMMainQuestSequence]("m_main_quest_sequence")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load main quest sequence table: %w", err)
|
return nil, fmt.Errorf("load main quest sequence table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -283,12 +103,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
return sequences[i].QuestId < sequences[j].QuestId
|
return sequences[i].QuestId < sequences[j].QuestId
|
||||||
})
|
})
|
||||||
|
|
||||||
chapters, err := utils.ReadJSON[MainQuestChapterRow]("EntityMMainQuestChapterTable.json")
|
chapters, err := utils.ReadTable[EntityMMainQuestChapter]("m_main_quest_chapter")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load main quest chapter table: %w", err)
|
return nil, fmt.Errorf("load main quest chapter table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
routes, err := utils.ReadJSON[MainQuestRouteRow]("EntityMMainQuestRouteTable.json")
|
routes, err := utils.ReadTable[EntityMMainQuestRoute]("m_main_quest_route")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load main quest route table: %w", err)
|
return nil, fmt.Errorf("load main quest route table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -297,12 +117,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
seasonIdByRouteId[r.MainQuestRouteId] = r.MainQuestSeasonId
|
seasonIdByRouteId[r.MainQuestRouteId] = r.MainQuestSeasonId
|
||||||
}
|
}
|
||||||
|
|
||||||
firstClearSwitches, err := utils.ReadJSON[QuestFirstClearRewardSwitchRow]("EntityMQuestFirstClearRewardSwitchTable.json")
|
firstClearSwitches, err := utils.ReadTable[EntityMQuestFirstClearRewardSwitch]("m_quest_first_clear_reward_switch")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest first clear reward switch table: %w", err)
|
return nil, fmt.Errorf("load quest first clear reward switch table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
firstClearRewards, err := utils.ReadJSON[QuestFirstClearRewardGroupRow]("EntityMQuestFirstClearRewardGroupTable.json")
|
firstClearRewards, err := utils.ReadTable[EntityMQuestFirstClearRewardGroup]("m_quest_first_clear_reward_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest first clear reward group table: %w", err)
|
return nil, fmt.Errorf("load quest first clear reward group table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -316,7 +136,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
return firstClearRewards[i].QuestFirstClearRewardType < firstClearRewards[j].QuestFirstClearRewardType
|
return firstClearRewards[i].QuestFirstClearRewardType < firstClearRewards[j].QuestFirstClearRewardType
|
||||||
})
|
})
|
||||||
|
|
||||||
replayFlowRewards, err := utils.ReadJSON[QuestReplayFlowRewardGroupRow]("EntityMQuestReplayFlowRewardGroupTable.json")
|
replayFlowRewards, err := utils.ReadTable[EntityMQuestReplayFlowRewardGroup]("m_quest_replay_flow_reward_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest replay flow reward group table: %w", err)
|
return nil, fmt.Errorf("load quest replay flow reward group table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -327,52 +147,52 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
return replayFlowRewards[i].SortOrder < replayFlowRewards[j].SortOrder
|
return replayFlowRewards[i].SortOrder < replayFlowRewards[j].SortOrder
|
||||||
})
|
})
|
||||||
|
|
||||||
missionRewards, err := utils.ReadJSON[QuestMissionRewardRow]("EntityMQuestMissionRewardTable.json")
|
missionRewards, err := utils.ReadTable[EntityMQuestMissionReward]("m_quest_mission_reward")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest mission reward table: %w", err)
|
return nil, fmt.Errorf("load quest mission reward table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json")
|
weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon table: %w", err)
|
return nil, fmt.Errorf("load weapon table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
weaponSkillGroups, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json")
|
weaponSkillGroups, err := utils.ReadTable[EntityMWeaponSkillGroup]("m_weapon_skill_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon skill group table: %w", err)
|
return nil, fmt.Errorf("load weapon skill group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
weaponAbilityGroups, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json")
|
weaponAbilityGroups, err := utils.ReadTable[EntityMWeaponAbilityGroup]("m_weapon_ability_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon ability group table: %w", err)
|
return nil, fmt.Errorf("load weapon ability group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json")
|
releaseConditions, err := utils.ReadTable[EntityMWeaponStoryReleaseConditionGroup]("m_weapon_story_release_condition_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
|
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
costumeMasters, err := utils.ReadJSON[CostumeMasterRow]("EntityMCostumeTable.json")
|
costumeMasters, err := utils.ReadTable[EntityMCostume]("m_costume")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume table: %w", err)
|
return nil, fmt.Errorf("load costume table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
costumeRarities, err := utils.ReadJSON[costumeRarityRow]("EntityMCostumeRarityTable.json")
|
costumeRarities, err := utils.ReadTable[EntityMCostumeRarity]("m_costume_rarity")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load costume rarity table: %w", err)
|
return nil, fmt.Errorf("load costume rarity table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneGrants, err := utils.ReadJSON[QuestSceneGrantRow]("EntityMUserQuestSceneGrantPossessionTable.json")
|
sceneGrants, err := utils.ReadTable[EntityMUserQuestSceneGrantPossession]("m_user_quest_scene_grant_possession")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest scene grant table: %w", err)
|
return nil, fmt.Errorf("load quest scene grant table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
battleDropRewards, err := utils.ReadJSON[BattleDropRewardRow]("EntityMBattleDropRewardTable.json")
|
battleDropRewards, err := utils.ReadTable[EntityMBattleDropReward]("m_battle_drop_reward")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load battle drop reward table: %w", err)
|
return nil, fmt.Errorf("load battle drop reward table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pickupRewardGroups, err := utils.ReadJSON[QuestPickupRewardGroupRow]("EntityMQuestPickupRewardGroupTable.json")
|
pickupRewardGroups, err := utils.ReadTable[EntityMQuestPickupRewardGroup]("m_quest_pickup_reward_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest pickup reward group table: %w", err)
|
return nil, fmt.Errorf("load quest pickup reward group table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -383,37 +203,37 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
return pickupRewardGroups[i].SortOrder < pickupRewardGroups[j].SortOrder
|
return pickupRewardGroups[i].SortOrder < pickupRewardGroups[j].SortOrder
|
||||||
})
|
})
|
||||||
|
|
||||||
sceneBattles, err := utils.ReadJSON[QuestSceneBattleRow]("EntityMQuestSceneBattleTable.json")
|
sceneBattles, err := utils.ReadTable[EntityMQuestSceneBattle]("m_quest_scene_battle")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load quest scene battle table: %w", err)
|
return nil, fmt.Errorf("load quest scene battle table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
battleGroups, err := utils.ReadJSON[BattleGroupRow]("EntityMBattleGroupTable.json")
|
battleGroups, err := utils.ReadTable[EntityMBattleGroup]("m_battle_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load battle group table: %w", err)
|
return nil, fmt.Errorf("load battle group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
battles, err := utils.ReadJSON[BattleRow]("EntityMBattleTable.json")
|
battles, err := utils.ReadTable[EntityMBattle]("m_battle")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load battle table: %w", err)
|
return nil, fmt.Errorf("load battle table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
npcDecks, err := utils.ReadJSON[BattleNpcDeckRow]("EntityMBattleNpcDeckTable.json")
|
npcDecks, err := utils.ReadTable[EntityMBattleNpcDeck]("m_battle_npc_deck")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load battle npc deck table: %w", err)
|
return nil, fmt.Errorf("load battle npc deck table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
npcDropCategories, err := utils.ReadJSON[BattleNpcDropCategoryRow]("EntityMBattleNpcDeckCharacterDropCategoryTable.json")
|
npcDropCategories, err := utils.ReadTable[EntityMBattleNpcDeckCharacterDropCategory]("m_battle_npc_deck_character_drop_category")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load battle npc drop category table: %w", err)
|
return nil, fmt.Errorf("load battle npc drop category table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rentalDecks, err := utils.ReadJSON[RentalDeckRow]("EntityMBattleRentalDeckTable.json")
|
rentalDecks, err := utils.ReadTable[EntityMBattleRentalDeck]("m_battle_rental_deck")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load battle rental deck table: %w", err)
|
return nil, fmt.Errorf("load battle rental deck table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tutorialUnlockConds, err := utils.ReadJSON[TutorialUnlockConditionRow]("EntityMTutorialUnlockConditionTable.json")
|
tutorialUnlockConds, err := utils.ReadTable[EntityMTutorialUnlockCondition]("m_tutorial_unlock_condition")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load tutorial unlock condition table: %w", err)
|
return nil, fmt.Errorf("load tutorial unlock condition table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -423,7 +243,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userLevels, err := utils.ReadJSON[UserLevelRow]("EntityMUserLevelTable.json")
|
userLevels, err := utils.ReadTable[EntityMUserLevel]("m_user_level")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load user level table: %w", err)
|
return nil, fmt.Errorf("load user level table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -450,12 +270,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
costumeById := make(map[int32]CostumeMasterRow, len(costumeMasters))
|
costumeById := make(map[int32]EntityMCostume, len(costumeMasters))
|
||||||
for _, cm := range costumeMasters {
|
for _, cm := range costumeMasters {
|
||||||
costumeById[cm.CostumeId] = cm
|
costumeById[cm.CostumeId] = cm
|
||||||
}
|
}
|
||||||
|
|
||||||
weaponById := make(map[int32]WeaponMasterRow, len(weapons))
|
weaponById := make(map[int32]EntityMWeapon, len(weapons))
|
||||||
for _, w := range weapons {
|
for _, w := range weapons {
|
||||||
weaponById[w.WeaponId] = w
|
weaponById[w.WeaponId] = w
|
||||||
}
|
}
|
||||||
@@ -469,19 +289,19 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
abilitySlots[row.WeaponAbilityGroupId] = append(abilitySlots[row.WeaponAbilityGroupId], row.SlotNumber)
|
abilitySlots[row.WeaponAbilityGroupId] = append(abilitySlots[row.WeaponAbilityGroupId], row.SlotNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneById := make(map[int32]QuestSceneRow, len(scenes))
|
sceneById := make(map[int32]EntityMQuestScene, len(scenes))
|
||||||
sceneIdsByQuestId := make(map[int32][]int32)
|
sceneIdsByQuestId := make(map[int32][]int32)
|
||||||
for _, scene := range scenes {
|
for _, scene := range scenes {
|
||||||
sceneById[scene.QuestSceneId] = scene
|
sceneById[scene.QuestSceneId] = scene
|
||||||
sceneIdsByQuestId[scene.QuestId] = append(sceneIdsByQuestId[scene.QuestId], scene.QuestSceneId)
|
sceneIdsByQuestId[scene.QuestId] = append(sceneIdsByQuestId[scene.QuestId], scene.QuestSceneId)
|
||||||
}
|
}
|
||||||
|
|
||||||
missionById := make(map[int32]QuestMissionRow, len(missions))
|
missionById := make(map[int32]EntityMQuestMission, len(missions))
|
||||||
for _, mission := range missions {
|
for _, mission := range missions {
|
||||||
missionById[mission.QuestMissionId] = mission
|
missionById[mission.QuestMissionId] = mission
|
||||||
}
|
}
|
||||||
|
|
||||||
questById := make(map[int32]QuestRow, len(quests))
|
questById := make(map[int32]EntityMQuest, len(quests))
|
||||||
for _, quest := range quests {
|
for _, quest := range quests {
|
||||||
questById[quest.QuestId] = quest
|
questById[quest.QuestId] = quest
|
||||||
}
|
}
|
||||||
@@ -500,7 +320,7 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
missionIdsByQuestId[questId] = append([]int32(nil), missionIds...)
|
missionIdsByQuestId[questId] = append([]int32(nil), missionIds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
chapterBySequenceId := make(map[int32]MainQuestChapterRow, len(chapters))
|
chapterBySequenceId := make(map[int32]EntityMMainQuestChapter, len(chapters))
|
||||||
for _, chapter := range chapters {
|
for _, chapter := range chapters {
|
||||||
chapterBySequenceId[chapter.MainQuestSequenceGroupId] = chapter
|
chapterBySequenceId[chapter.MainQuestSequenceGroupId] = chapter
|
||||||
}
|
}
|
||||||
@@ -511,12 +331,12 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sortedChapters := make([]MainQuestChapterRow, len(chapters))
|
sortedChapters := make([]EntityMMainQuestChapter, len(chapters))
|
||||||
copy(sortedChapters, chapters)
|
copy(sortedChapters, chapters)
|
||||||
sort.Slice(sortedChapters, func(i, j int) bool {
|
sort.Slice(sortedChapters, func(i, j int) bool {
|
||||||
return sortedChapters[i].SortOrder < sortedChapters[j].SortOrder
|
return sortedChapters[i].SortOrder < sortedChapters[j].SortOrder
|
||||||
})
|
})
|
||||||
sequencesByGroupId := make(map[int32][]MainQuestSequenceRow)
|
sequencesByGroupId := make(map[int32][]EntityMMainQuestSequence)
|
||||||
for _, seq := range sequences {
|
for _, seq := range sequences {
|
||||||
sequencesByGroupId[seq.MainQuestSequenceId] = append(sequencesByGroupId[seq.MainQuestSequenceId], seq)
|
sequencesByGroupId[seq.MainQuestSequenceId] = append(sequencesByGroupId[seq.MainQuestSequenceId], seq)
|
||||||
}
|
}
|
||||||
@@ -544,25 +364,25 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
firstClearRewardsByGroupId := make(map[int32][]QuestFirstClearRewardGroupRow, len(firstClearRewards))
|
firstClearRewardsByGroupId := make(map[int32][]EntityMQuestFirstClearRewardGroup, len(firstClearRewards))
|
||||||
for _, reward := range firstClearRewards {
|
for _, reward := range firstClearRewards {
|
||||||
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId] = append(
|
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId] = append(
|
||||||
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId], reward)
|
firstClearRewardsByGroupId[reward.QuestFirstClearRewardGroupId], reward)
|
||||||
}
|
}
|
||||||
|
|
||||||
replayFlowRewardsByGroupId := make(map[int32][]QuestReplayFlowRewardGroupRow, len(replayFlowRewards))
|
replayFlowRewardsByGroupId := make(map[int32][]EntityMQuestReplayFlowRewardGroup, len(replayFlowRewards))
|
||||||
for _, reward := range replayFlowRewards {
|
for _, reward := range replayFlowRewards {
|
||||||
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId] = append(
|
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId] = append(
|
||||||
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId], reward)
|
replayFlowRewardsByGroupId[reward.QuestReplayFlowRewardGroupId], reward)
|
||||||
}
|
}
|
||||||
|
|
||||||
firstClearRewardSwitchesByQuestId := make(map[int32][]QuestFirstClearRewardSwitchRow, len(firstClearSwitches))
|
firstClearRewardSwitchesByQuestId := make(map[int32][]EntityMQuestFirstClearRewardSwitch, len(firstClearSwitches))
|
||||||
for _, switchRow := range firstClearSwitches {
|
for _, switchRow := range firstClearSwitches {
|
||||||
firstClearRewardSwitchesByQuestId[switchRow.QuestId] = append(
|
firstClearRewardSwitchesByQuestId[switchRow.QuestId] = append(
|
||||||
firstClearRewardSwitchesByQuestId[switchRow.QuestId], switchRow)
|
firstClearRewardSwitchesByQuestId[switchRow.QuestId], switchRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
missionRewardsByMissionId := make(map[int32][]QuestMissionRewardRow, len(missionRewards))
|
missionRewardsByMissionId := make(map[int32][]EntityMQuestMissionReward, len(missionRewards))
|
||||||
for _, reward := range missionRewards {
|
for _, reward := range missionRewards {
|
||||||
missionRewardsByMissionId[reward.QuestMissionRewardId] = append(
|
missionRewardsByMissionId[reward.QuestMissionRewardId] = append(
|
||||||
missionRewardsByMissionId[reward.QuestMissionRewardId], reward)
|
missionRewardsByMissionId[reward.QuestMissionRewardId], reward)
|
||||||
@@ -576,18 +396,18 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseConditionsByGroupId := make(map[int32][]WeaponStoryReleaseConditionRow)
|
releaseConditionsByGroupId := make(map[int32][]EntityMWeaponStoryReleaseConditionGroup)
|
||||||
for _, c := range releaseConditions {
|
for _, c := range releaseConditions {
|
||||||
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId] = append(
|
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId] = append(
|
||||||
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c)
|
releaseConditionsByGroupId[c.WeaponStoryReleaseConditionGroupId], c)
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneGrantsBySceneId := make(map[int32][]QuestSceneGrantRow)
|
sceneGrantsBySceneId := make(map[int32][]EntityMUserQuestSceneGrantPossession)
|
||||||
for _, sg := range sceneGrants {
|
for _, sg := range sceneGrants {
|
||||||
sceneGrantsBySceneId[sg.QuestSceneId] = append(sceneGrantsBySceneId[sg.QuestSceneId], sg)
|
sceneGrantsBySceneId[sg.QuestSceneId] = append(sceneGrantsBySceneId[sg.QuestSceneId], sg)
|
||||||
}
|
}
|
||||||
|
|
||||||
battleDropRewardById := make(map[int32]BattleDropRewardRow, len(battleDropRewards))
|
battleDropRewardById := make(map[int32]EntityMBattleDropReward, len(battleDropRewards))
|
||||||
for _, bdr := range battleDropRewards {
|
for _, bdr := range battleDropRewards {
|
||||||
battleDropRewardById[bdr.BattleDropRewardId] = bdr
|
battleDropRewardById[bdr.BattleDropRewardId] = bdr
|
||||||
}
|
}
|
||||||
@@ -609,22 +429,22 @@ func LoadQuestCatalog(partsCatalog *PartsCatalog) (*QuestCatalog, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type npcDeckKey struct {
|
type npcDeckKey struct {
|
||||||
BattleNpcId int32
|
BattleNpcId int64
|
||||||
DeckType model.DeckType
|
DeckType int32
|
||||||
BattleNpcDeckNumber int32
|
BattleNpcDeckNumber int32
|
||||||
}
|
}
|
||||||
npcDeckByKey := make(map[npcDeckKey]BattleNpcDeckRow, len(npcDecks))
|
npcDeckByKey := make(map[npcDeckKey]EntityMBattleNpcDeck, len(npcDecks))
|
||||||
for _, d := range npcDecks {
|
for _, d := range npcDecks {
|
||||||
npcDeckByKey[npcDeckKey{d.BattleNpcId, d.DeckType, d.BattleNpcDeckNumber}] = d
|
npcDeckByKey[npcDeckKey{d.BattleNpcId, d.DeckType, d.BattleNpcDeckNumber}] = d
|
||||||
}
|
}
|
||||||
|
|
||||||
battleByIdMap := make(map[int32]BattleRow, len(battles))
|
battleByIdMap := make(map[int32]EntityMBattle, len(battles))
|
||||||
for _, b := range battles {
|
for _, b := range battles {
|
||||||
battleByIdMap[b.BattleId] = b
|
battleByIdMap[b.BattleId] = b
|
||||||
}
|
}
|
||||||
|
|
||||||
type dropCatKey struct {
|
type dropCatKey struct {
|
||||||
BattleNpcId int32
|
BattleNpcId int64
|
||||||
Uuid string
|
Uuid string
|
||||||
}
|
}
|
||||||
dropCategoryByKey := make(map[dropCatKey]int32, len(npcDropCategories))
|
dropCategoryByKey := make(map[dropCatKey]int32, len(npcDropCategories))
|
||||||
|
|||||||
@@ -8,96 +8,47 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShopItemRow struct {
|
|
||||||
ShopItemId int32 `json:"ShopItemId"`
|
|
||||||
PriceType int32 `json:"PriceType"`
|
|
||||||
PriceId int32 `json:"PriceId"`
|
|
||||||
Price int32 `json:"Price"`
|
|
||||||
ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShopContentRow struct {
|
|
||||||
ShopItemId int32 `json:"ShopItemId"`
|
|
||||||
PossessionType int32 `json:"PossessionType"`
|
|
||||||
PossessionId int32 `json:"PossessionId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShopContentEffectRow struct {
|
|
||||||
ShopItemId int32 `json:"ShopItemId"`
|
|
||||||
EffectTargetType int32 `json:"EffectTargetType"`
|
|
||||||
EffectValueType int32 `json:"EffectValueType"`
|
|
||||||
EffectValue int32 `json:"EffectValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type shopItemLimitedStockRow struct {
|
|
||||||
ShopItemLimitedStockId int32 `json:"ShopItemLimitedStockId"`
|
|
||||||
MaxCount int32 `json:"MaxCount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type shopRow struct {
|
|
||||||
ShopId int32 `json:"ShopId"`
|
|
||||||
ShopGroupType int32 `json:"ShopGroupType"`
|
|
||||||
ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type shopItemCellGroupRow struct {
|
|
||||||
ShopItemCellGroupId int32 `json:"ShopItemCellGroupId"`
|
|
||||||
ShopItemCellId int32 `json:"ShopItemCellId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type shopItemCellRow struct {
|
|
||||||
ShopItemCellId int32 `json:"ShopItemCellId"`
|
|
||||||
ShopItemId int32 `json:"ShopItemId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExchangeShopCell struct {
|
type ExchangeShopCell struct {
|
||||||
SortOrder int32
|
SortOrder int32
|
||||||
ShopItemId int32
|
ShopItemId int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShopCatalog struct {
|
type ShopCatalog struct {
|
||||||
Items map[int32]ShopItemRow
|
Items map[int32]EntityMShopItem
|
||||||
Contents map[int32][]ShopContentRow
|
Contents map[int32][]EntityMShopItemContentPossession
|
||||||
Effects map[int32][]ShopContentEffectRow
|
Effects map[int32][]EntityMShopItemContentEffect
|
||||||
MaxStaminaMillis map[int32]int32 // level -> max stamina in millis
|
MaxStaminaMillis map[int32]int32 // level -> max stamina in millis
|
||||||
LimitedStock map[int32]int32 // stock id -> max count
|
LimitedStock map[int32]int32 // stock id -> max count
|
||||||
ItemShopPool []int32 // shop item IDs for the replaceable item shop, sorted by cell sort order
|
ItemShopPool []int32 // shop item IDs for the replaceable item shop, sorted by cell sort order
|
||||||
ExchangeShopCells map[int32][]ExchangeShopCell // shopId -> sorted cells for exchange shops
|
ExchangeShopCells map[int32][]ExchangeShopCell // shopId -> sorted cells for exchange shops
|
||||||
}
|
}
|
||||||
|
|
||||||
type userLevelEntry struct {
|
|
||||||
UserLevel int32 `json:"UserLevel"`
|
|
||||||
MaxStamina int32 `json:"MaxStamina"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadShopCatalog() (*ShopCatalog, error) {
|
func LoadShopCatalog() (*ShopCatalog, error) {
|
||||||
items, err := utils.ReadJSON[ShopItemRow]("EntityMShopItemTable.json")
|
items, err := utils.ReadTable[EntityMShopItem]("m_shop_item")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load shop item table: %w", err)
|
return nil, fmt.Errorf("load shop item table: %w", err)
|
||||||
}
|
}
|
||||||
contents, err := utils.ReadJSON[ShopContentRow]("EntityMShopItemContentPossessionTable.json")
|
contents, err := utils.ReadTable[EntityMShopItemContentPossession]("m_shop_item_content_possession")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load shop content possession table: %w", err)
|
return nil, fmt.Errorf("load shop content possession table: %w", err)
|
||||||
}
|
}
|
||||||
effects, err := utils.ReadJSON[ShopContentEffectRow]("EntityMShopItemContentEffectTable.json")
|
effects, err := utils.ReadTable[EntityMShopItemContentEffect]("m_shop_item_content_effect")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load shop content effect table: %w", err)
|
return nil, fmt.Errorf("load shop content effect table: %w", err)
|
||||||
}
|
}
|
||||||
userLevels, err := utils.ReadJSON[userLevelEntry]("EntityMUserLevelTable.json")
|
userLevels, err := utils.ReadTable[EntityMUserLevel]("m_user_level")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load user level table: %w", err)
|
return nil, fmt.Errorf("load user level table: %w", err)
|
||||||
}
|
}
|
||||||
stockRows, err := utils.ReadJSON[shopItemLimitedStockRow]("EntityMShopItemLimitedStockTable.json")
|
stockRows, err := utils.ReadTable[EntityMShopItemLimitedStock]("m_shop_item_limited_stock")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load shop item limited stock table: %w", err)
|
return nil, fmt.Errorf("load shop item limited stock table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := &ShopCatalog{
|
catalog := &ShopCatalog{
|
||||||
Items: make(map[int32]ShopItemRow, len(items)),
|
Items: make(map[int32]EntityMShopItem, len(items)),
|
||||||
Contents: make(map[int32][]ShopContentRow, len(contents)),
|
Contents: make(map[int32][]EntityMShopItemContentPossession, len(contents)),
|
||||||
Effects: make(map[int32][]ShopContentEffectRow, len(effects)),
|
Effects: make(map[int32][]EntityMShopItemContentEffect, len(effects)),
|
||||||
MaxStaminaMillis: make(map[int32]int32, len(userLevels)),
|
MaxStaminaMillis: make(map[int32]int32, len(userLevels)),
|
||||||
LimitedStock: make(map[int32]int32, len(stockRows)),
|
LimitedStock: make(map[int32]int32, len(stockRows)),
|
||||||
}
|
}
|
||||||
@@ -117,15 +68,15 @@ func LoadShopCatalog() (*ShopCatalog, error) {
|
|||||||
catalog.LimitedStock[row.ShopItemLimitedStockId] = row.MaxCount
|
catalog.LimitedStock[row.ShopItemLimitedStockId] = row.MaxCount
|
||||||
}
|
}
|
||||||
|
|
||||||
shops, err := utils.ReadJSON[shopRow]("EntityMShopTable.json")
|
shops, err := utils.ReadTable[EntityMShop]("m_shop")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load shop table: %w", err)
|
return nil, fmt.Errorf("load shop table: %w", err)
|
||||||
}
|
}
|
||||||
cellGroups, err := utils.ReadJSON[shopItemCellGroupRow]("EntityMShopItemCellGroupTable.json")
|
cellGroups, err := utils.ReadTable[EntityMShopItemCellGroup]("m_shop_item_cell_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load shop item cell group table: %w", err)
|
return nil, fmt.Errorf("load shop item cell group table: %w", err)
|
||||||
}
|
}
|
||||||
cells, err := utils.ReadJSON[shopItemCellRow]("EntityMShopItemCellTable.json")
|
cells, err := utils.ReadTable[EntityMShopItemCell]("m_shop_item_cell")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load shop item cell table: %w", err)
|
return nil, fmt.Errorf("load shop item cell table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -135,7 +86,7 @@ func LoadShopCatalog() (*ShopCatalog, error) {
|
|||||||
cellIdToItemId[c.ShopItemCellId] = c.ShopItemId
|
cellIdToItemId[c.ShopItemCellId] = c.ShopItemId
|
||||||
}
|
}
|
||||||
|
|
||||||
cellGroupByCGId := make(map[int32][]shopItemCellGroupRow, len(cellGroups))
|
cellGroupByCGId := make(map[int32][]EntityMShopItemCellGroup, len(cellGroups))
|
||||||
for _, cg := range cellGroups {
|
for _, cg := range cellGroups {
|
||||||
cellGroupByCGId[cg.ShopItemCellGroupId] = append(cellGroupByCGId[cg.ShopItemCellGroupId], cg)
|
cellGroupByCGId[cg.ShopItemCellGroupId] = append(cellGroupByCGId[cg.ShopItemCellGroupId], cg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,12 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sideStorySceneRow struct {
|
|
||||||
SideStoryQuestId int32 `json:"SideStoryQuestId"`
|
|
||||||
SideStoryQuestSceneId int32 `json:"SideStoryQuestSceneId"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SideStoryCatalog struct {
|
type SideStoryCatalog struct {
|
||||||
FirstSceneByQuestId map[int32]int32
|
FirstSceneByQuestId map[int32]int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadSideStoryCatalog() *SideStoryCatalog {
|
func LoadSideStoryCatalog() *SideStoryCatalog {
|
||||||
scenes, err := utils.ReadJSON[sideStorySceneRow]("EntityMSideStoryQuestSceneTable.json")
|
scenes, err := utils.ReadTable[EntityMSideStoryQuestScene]("m_side_story_quest_scene")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("load side story quest scene table: %v", err)
|
log.Fatalf("load side story quest scene table: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,172 +9,55 @@ import (
|
|||||||
"lunar-tear/server/internal/utils"
|
"lunar-tear/server/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WeaponMasterRow struct {
|
|
||||||
WeaponId int32 `json:"WeaponId"`
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
WeaponType int32 `json:"WeaponType"`
|
|
||||||
WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"`
|
|
||||||
WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"`
|
|
||||||
WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"`
|
|
||||||
WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"`
|
|
||||||
WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponStoryReleaseConditionRow struct {
|
|
||||||
WeaponStoryReleaseConditionGroupId int32 `json:"WeaponStoryReleaseConditionGroupId"`
|
|
||||||
StoryIndex int32 `json:"StoryIndex"`
|
|
||||||
WeaponStoryReleaseConditionType model.WeaponStoryReleaseConditionType `json:"WeaponStoryReleaseConditionType"`
|
|
||||||
ConditionValue int32 `json:"ConditionValue"`
|
|
||||||
WeaponStoryReleaseConditionOperationGroupId int32 `json:"WeaponStoryReleaseConditionOperationGroupId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponSkillGroupRow struct {
|
|
||||||
WeaponSkillGroupId int32 `json:"WeaponSkillGroupId"`
|
|
||||||
SlotNumber int32 `json:"SlotNumber"`
|
|
||||||
SkillId int32 `json:"SkillId"`
|
|
||||||
WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponAbilityGroupRow struct {
|
|
||||||
WeaponAbilityGroupId int32 `json:"WeaponAbilityGroupId"`
|
|
||||||
SlotNumber int32 `json:"SlotNumber"`
|
|
||||||
AbilityId int32 `json:"AbilityId"`
|
|
||||||
WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type weaponSpecificEnhanceRow struct {
|
|
||||||
WeaponSpecificEnhanceId int32 `json:"WeaponSpecificEnhanceId"`
|
|
||||||
BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"`
|
|
||||||
SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
|
|
||||||
RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
|
|
||||||
EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"`
|
|
||||||
EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
|
|
||||||
MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
|
|
||||||
EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"`
|
|
||||||
LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"`
|
|
||||||
LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"`
|
|
||||||
MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"`
|
|
||||||
SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"`
|
|
||||||
MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"`
|
|
||||||
AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type weaponConsumeExchangeRow struct {
|
|
||||||
WeaponId int32 `json:"WeaponId"`
|
|
||||||
ConsumableItemId int32 `json:"ConsumableItemId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponEvolutionGroupRow struct {
|
|
||||||
WeaponEvolutionGroupId int32 `json:"WeaponEvolutionGroupId"`
|
|
||||||
EvolutionOrder int32 `json:"EvolutionOrder"`
|
|
||||||
WeaponId int32 `json:"WeaponId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponEvolutionMaterialRow struct {
|
|
||||||
WeaponEvolutionMaterialGroupId int32 `json:"WeaponEvolutionMaterialGroupId"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponSkillEnhanceMaterialRow struct {
|
|
||||||
WeaponSkillEnhancementMaterialId int32 `json:"WeaponSkillEnhancementMaterialId"`
|
|
||||||
SkillLevel int32 `json:"SkillLevel"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponAbilityEnhanceMaterialRow struct {
|
|
||||||
WeaponAbilityEnhancementMaterialId int32 `json:"WeaponAbilityEnhancementMaterialId"`
|
|
||||||
AbilityLevel int32 `json:"AbilityLevel"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponAwakenRow struct {
|
|
||||||
WeaponId int32 `json:"WeaponId"`
|
|
||||||
WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"`
|
|
||||||
WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"`
|
|
||||||
ConsumeGold int32 `json:"ConsumeGold"`
|
|
||||||
LevelLimitUp int32 `json:"LevelLimitUp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponAwakenEffectGroupRow struct {
|
type WeaponAwakenEffectGroupRow struct {
|
||||||
WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"`
|
WeaponAwakenEffectGroupId int32 `json:"WeaponAwakenEffectGroupId"`
|
||||||
WeaponAwakenEffectType int32 `json:"WeaponAwakenEffectType"`
|
WeaponAwakenEffectType int32 `json:"WeaponAwakenEffectType"`
|
||||||
WeaponAwakenEffectId int32 `json:"WeaponAwakenEffectId"`
|
WeaponAwakenEffectId int32 `json:"WeaponAwakenEffectId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WeaponAwakenMaterialGroupRow struct {
|
|
||||||
WeaponAwakenMaterialGroupId int32 `json:"WeaponAwakenMaterialGroupId"`
|
|
||||||
MaterialId int32 `json:"MaterialId"`
|
|
||||||
Count int32 `json:"Count"`
|
|
||||||
SortOrder int32 `json:"SortOrder"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type weaponRarityEnhanceRow struct {
|
|
||||||
RarityType int32 `json:"RarityType"`
|
|
||||||
BaseEnhancementObtainedExp int32 `json:"BaseEnhancementObtainedExp"`
|
|
||||||
SellPriceNumericalFunctionId int32 `json:"SellPriceNumericalFunctionId"`
|
|
||||||
RequiredExpForLevelUpNumericalParameterMapId int32 `json:"RequiredExpForLevelUpNumericalParameterMapId"`
|
|
||||||
EnhancementCostByWeaponNumericalFunctionId int32 `json:"EnhancementCostByWeaponNumericalFunctionId"`
|
|
||||||
EnhancementCostByMaterialNumericalFunctionId int32 `json:"EnhancementCostByMaterialNumericalFunctionId"`
|
|
||||||
MaxLevelNumericalFunctionId int32 `json:"MaxLevelNumericalFunctionId"`
|
|
||||||
EvolutionCostNumericalFunctionId int32 `json:"EvolutionCostNumericalFunctionId"`
|
|
||||||
LimitBreakCostByWeaponNumericalFunctionId int32 `json:"LimitBreakCostByWeaponNumericalFunctionId"`
|
|
||||||
LimitBreakCostByMaterialNumericalFunctionId int32 `json:"LimitBreakCostByMaterialNumericalFunctionId"`
|
|
||||||
MaxSkillLevelNumericalFunctionId int32 `json:"MaxSkillLevelNumericalFunctionId"`
|
|
||||||
SkillEnhancementCostNumericalFunctionId int32 `json:"SkillEnhancementCostNumericalFunctionId"`
|
|
||||||
MaxAbilityLevelNumericalFunctionId int32 `json:"MaxAbilityLevelNumericalFunctionId"`
|
|
||||||
AbilityEnhancementCostNumericalFunctionId int32 `json:"AbilityEnhancementCostNumericalFunctionId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WeaponCatalog struct {
|
type WeaponCatalog struct {
|
||||||
Weapons map[int32]WeaponMasterRow
|
Weapons map[int32]EntityMWeapon
|
||||||
Materials map[int32]MaterialRow
|
Materials map[int32]EntityMMaterial
|
||||||
ExpByEnhanceId map[int32][]int32
|
ExpByEnhanceId map[int32][]int32
|
||||||
GoldCostByEnhanceId map[int32]NumericalFunc
|
GoldCostByEnhanceId map[int32]NumericalFunc
|
||||||
MaxLevelByEnhanceId map[int32]NumericalFunc
|
MaxLevelByEnhanceId map[int32]NumericalFunc
|
||||||
SellPriceByEnhanceId map[int32]NumericalFunc
|
SellPriceByEnhanceId map[int32]NumericalFunc
|
||||||
MedalsByWeaponId map[int32]map[int32]int32 // WeaponId -> ConsumableItemId -> Count
|
MedalsByWeaponId map[int32]map[int32]int32 // WeaponId -> ConsumableItemId -> Count
|
||||||
EvolutionNextWeaponId map[int32]int32
|
EvolutionNextWeaponId map[int32]int32
|
||||||
EvolutionOrder map[int32]int32 // WeaponId -> 0-based position in evolution chain
|
EvolutionOrder map[int32]int32 // WeaponId -> 0-based position in evolution chain
|
||||||
EvolutionMaterials map[int32][]WeaponEvolutionMaterialRow // WeaponEvolutionMaterialGroupId -> materials
|
EvolutionMaterials map[int32][]EntityMWeaponEvolutionMaterialGroup // WeaponEvolutionMaterialGroupId -> materials
|
||||||
EvolutionCostByEnhanceId map[int32]NumericalFunc
|
EvolutionCostByEnhanceId map[int32]NumericalFunc
|
||||||
AbilitySlots map[int32][]int32 // WeaponAbilityGroupId -> slot numbers
|
AbilitySlots map[int32][]int32 // WeaponAbilityGroupId -> slot numbers
|
||||||
SkillGroupsByGroupId map[int32][]WeaponSkillGroupRow
|
SkillGroupsByGroupId map[int32][]EntityMWeaponSkillGroup
|
||||||
SkillEnhanceMats map[[2]int32][]WeaponSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel]
|
SkillEnhanceMats map[[2]int32][]EntityMWeaponSkillEnhancementMaterial // key: [enhancementMaterialId, skillLevel]
|
||||||
SkillMaxLevelByEnhanceId map[int32]NumericalFunc
|
SkillMaxLevelByEnhanceId map[int32]NumericalFunc
|
||||||
SkillCostByEnhanceId map[int32]NumericalFunc
|
SkillCostByEnhanceId map[int32]NumericalFunc
|
||||||
AbilityGroupsByGroupId map[int32][]WeaponAbilityGroupRow
|
AbilityGroupsByGroupId map[int32][]EntityMWeaponAbilityGroup
|
||||||
AbilityEnhanceMats map[[2]int32][]WeaponAbilityEnhanceMaterialRow // key: [enhancementMaterialId, abilityLevel]
|
AbilityEnhanceMats map[[2]int32][]EntityMWeaponAbilityEnhancementMaterial // key: [enhancementMaterialId, abilityLevel]
|
||||||
AbilityMaxLevelByEnhanceId map[int32]NumericalFunc
|
AbilityMaxLevelByEnhanceId map[int32]NumericalFunc
|
||||||
AbilityCostByEnhanceId map[int32]NumericalFunc
|
AbilityCostByEnhanceId map[int32]NumericalFunc
|
||||||
EnhanceCostByWeaponByEnhanceId map[int32]NumericalFunc
|
EnhanceCostByWeaponByEnhanceId map[int32]NumericalFunc
|
||||||
LimitBreakCostByWeaponByEnhanceId map[int32]NumericalFunc
|
LimitBreakCostByWeaponByEnhanceId map[int32]NumericalFunc
|
||||||
LimitBreakCostByMaterialByEnhanceId map[int32]NumericalFunc
|
LimitBreakCostByMaterialByEnhanceId map[int32]NumericalFunc
|
||||||
BaseExpByEnhanceId map[int32]int32
|
BaseExpByEnhanceId map[int32]int32
|
||||||
ReleaseConditionsByGroupId map[int32][]WeaponStoryReleaseConditionRow
|
ReleaseConditionsByGroupId map[int32][]EntityMWeaponStoryReleaseConditionGroup
|
||||||
|
|
||||||
AwakenByWeaponId map[int32]WeaponAwakenRow
|
AwakenByWeaponId map[int32]EntityMWeaponAwaken
|
||||||
AwakenMaterialsByGroupId map[int32][]WeaponAwakenMaterialGroupRow
|
AwakenMaterialsByGroupId map[int32][]EntityMWeaponAwakenMaterialGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
|
func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
|
||||||
weapons, err := utils.ReadJSON[WeaponMasterRow]("EntityMWeaponTable.json")
|
weapons, err := utils.ReadTable[EntityMWeapon]("m_weapon")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon table: %w", err)
|
return nil, fmt.Errorf("load weapon table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
enhanceRows, err := utils.ReadJSON[weaponSpecificEnhanceRow]("EntityMWeaponSpecificEnhanceTable.json")
|
enhanceRows, err := utils.ReadTable[EntityMWeaponSpecificEnhance]("m_weapon_specific_enhance")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon specific enhance table: %w", err)
|
return nil, fmt.Errorf("load weapon specific enhance table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rarityEnhanceRows, err := utils.ReadJSON[weaponRarityEnhanceRow]("EntityMWeaponRarityTable.json")
|
rarityEnhanceRows, err := utils.ReadTable[EntityMWeaponRarity]("m_weapon_rarity")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon rarity table: %w", err)
|
return nil, fmt.Errorf("load weapon rarity table: %w", err)
|
||||||
}
|
}
|
||||||
@@ -189,51 +72,51 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
|
|||||||
return nil, fmt.Errorf("load function resolver: %w", err)
|
return nil, fmt.Errorf("load function resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
exchangeRows, err := utils.ReadJSON[weaponConsumeExchangeRow]("EntityMWeaponConsumeExchangeConsumableItemGroupTable.json")
|
exchangeRows, err := utils.ReadTable[EntityMWeaponConsumeExchangeConsumableItemGroup]("m_weapon_consume_exchange_consumable_item_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon consume exchange table: %w", err)
|
return nil, fmt.Errorf("load weapon consume exchange table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
evoGroupRows, err := utils.ReadJSON[WeaponEvolutionGroupRow]("EntityMWeaponEvolutionGroupTable.json")
|
evoGroupRows, err := utils.ReadTable[EntityMWeaponEvolutionGroup]("m_weapon_evolution_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
|
return nil, fmt.Errorf("load weapon evolution group table: %w", err)
|
||||||
}
|
}
|
||||||
evoMatRows, err := utils.ReadJSON[WeaponEvolutionMaterialRow]("EntityMWeaponEvolutionMaterialGroupTable.json")
|
evoMatRows, err := utils.ReadTable[EntityMWeaponEvolutionMaterialGroup]("m_weapon_evolution_material_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon evolution material group table: %w", err)
|
return nil, fmt.Errorf("load weapon evolution material group table: %w", err)
|
||||||
}
|
}
|
||||||
abilityGroupRows, err := utils.ReadJSON[WeaponAbilityGroupRow]("EntityMWeaponAbilityGroupTable.json")
|
abilityGroupRows, err := utils.ReadTable[EntityMWeaponAbilityGroup]("m_weapon_ability_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon ability group table: %w", err)
|
return nil, fmt.Errorf("load weapon ability group table: %w", err)
|
||||||
}
|
}
|
||||||
skillGroupRows, err := utils.ReadJSON[WeaponSkillGroupRow]("EntityMWeaponSkillGroupTable.json")
|
skillGroupRows, err := utils.ReadTable[EntityMWeaponSkillGroup]("m_weapon_skill_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon skill group table: %w", err)
|
return nil, fmt.Errorf("load weapon skill group table: %w", err)
|
||||||
}
|
}
|
||||||
skillMatRows, err := utils.ReadJSON[WeaponSkillEnhanceMaterialRow]("EntityMWeaponSkillEnhancementMaterialTable.json")
|
skillMatRows, err := utils.ReadTable[EntityMWeaponSkillEnhancementMaterial]("m_weapon_skill_enhancement_material")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon skill enhancement material table: %w", err)
|
return nil, fmt.Errorf("load weapon skill enhancement material table: %w", err)
|
||||||
}
|
}
|
||||||
abilityMatRows, err := utils.ReadJSON[WeaponAbilityEnhanceMaterialRow]("EntityMWeaponAbilityEnhancementMaterialTable.json")
|
abilityMatRows, err := utils.ReadTable[EntityMWeaponAbilityEnhancementMaterial]("m_weapon_ability_enhancement_material")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon ability enhancement material table: %w", err)
|
return nil, fmt.Errorf("load weapon ability enhancement material table: %w", err)
|
||||||
}
|
}
|
||||||
releaseConditions, err := utils.ReadJSON[WeaponStoryReleaseConditionRow]("EntityMWeaponStoryReleaseConditionGroupTable.json")
|
releaseConditions, err := utils.ReadTable[EntityMWeaponStoryReleaseConditionGroup]("m_weapon_story_release_condition_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
|
return nil, fmt.Errorf("load weapon story release condition table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
awakenRows, err := utils.ReadJSON[WeaponAwakenRow]("EntityMWeaponAwakenTable.json")
|
awakenRows, err := utils.ReadTable[EntityMWeaponAwaken]("m_weapon_awaken")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon awaken table: %w", err)
|
return nil, fmt.Errorf("load weapon awaken table: %w", err)
|
||||||
}
|
}
|
||||||
awakenMatRows, err := utils.ReadJSON[WeaponAwakenMaterialGroupRow]("EntityMWeaponAwakenMaterialGroupTable.json")
|
awakenMatRows, err := utils.ReadTable[EntityMWeaponAwakenMaterialGroup]("m_weapon_awaken_material_group")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("load weapon awaken material group table: %w", err)
|
return nil, fmt.Errorf("load weapon awaken material group table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog := &WeaponCatalog{
|
catalog := &WeaponCatalog{
|
||||||
Weapons: make(map[int32]WeaponMasterRow, len(weapons)),
|
Weapons: make(map[int32]EntityMWeapon, len(weapons)),
|
||||||
Materials: matCatalog.ByType[model.MaterialTypeWeaponEnhancement],
|
Materials: matCatalog.ByType[model.MaterialTypeWeaponEnhancement],
|
||||||
ExpByEnhanceId: make(map[int32][]int32, len(enhanceRows)),
|
ExpByEnhanceId: make(map[int32][]int32, len(enhanceRows)),
|
||||||
GoldCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
GoldCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
@@ -242,25 +125,25 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
|
|||||||
MedalsByWeaponId: make(map[int32]map[int32]int32),
|
MedalsByWeaponId: make(map[int32]map[int32]int32),
|
||||||
EvolutionNextWeaponId: make(map[int32]int32),
|
EvolutionNextWeaponId: make(map[int32]int32),
|
||||||
EvolutionOrder: make(map[int32]int32),
|
EvolutionOrder: make(map[int32]int32),
|
||||||
EvolutionMaterials: make(map[int32][]WeaponEvolutionMaterialRow),
|
EvolutionMaterials: make(map[int32][]EntityMWeaponEvolutionMaterialGroup),
|
||||||
EvolutionCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
EvolutionCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
AbilitySlots: make(map[int32][]int32),
|
AbilitySlots: make(map[int32][]int32),
|
||||||
SkillGroupsByGroupId: make(map[int32][]WeaponSkillGroupRow),
|
SkillGroupsByGroupId: make(map[int32][]EntityMWeaponSkillGroup),
|
||||||
SkillEnhanceMats: make(map[[2]int32][]WeaponSkillEnhanceMaterialRow),
|
SkillEnhanceMats: make(map[[2]int32][]EntityMWeaponSkillEnhancementMaterial),
|
||||||
SkillMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
SkillMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
SkillCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
SkillCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
AbilityGroupsByGroupId: make(map[int32][]WeaponAbilityGroupRow),
|
AbilityGroupsByGroupId: make(map[int32][]EntityMWeaponAbilityGroup),
|
||||||
AbilityEnhanceMats: make(map[[2]int32][]WeaponAbilityEnhanceMaterialRow),
|
AbilityEnhanceMats: make(map[[2]int32][]EntityMWeaponAbilityEnhancementMaterial),
|
||||||
AbilityMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
AbilityMaxLevelByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
AbilityCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
AbilityCostByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
EnhanceCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
EnhanceCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
LimitBreakCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
LimitBreakCostByWeaponByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
LimitBreakCostByMaterialByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
LimitBreakCostByMaterialByEnhanceId: make(map[int32]NumericalFunc, len(enhanceRows)),
|
||||||
BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)),
|
BaseExpByEnhanceId: make(map[int32]int32, len(enhanceRows)),
|
||||||
ReleaseConditionsByGroupId: make(map[int32][]WeaponStoryReleaseConditionRow),
|
ReleaseConditionsByGroupId: make(map[int32][]EntityMWeaponStoryReleaseConditionGroup),
|
||||||
|
|
||||||
AwakenByWeaponId: make(map[int32]WeaponAwakenRow, len(awakenRows)),
|
AwakenByWeaponId: make(map[int32]EntityMWeaponAwaken, len(awakenRows)),
|
||||||
AwakenMaterialsByGroupId: make(map[int32][]WeaponAwakenMaterialGroupRow),
|
AwakenMaterialsByGroupId: make(map[int32][]EntityMWeaponAwakenMaterialGroup),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, w := range weapons {
|
for _, w := range weapons {
|
||||||
@@ -338,7 +221,7 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
|
|||||||
catalog.MedalsByWeaponId[ex.WeaponId][ex.ConsumableItemId] = ex.Count
|
catalog.MedalsByWeaponId[ex.WeaponId][ex.ConsumableItemId] = ex.Count
|
||||||
}
|
}
|
||||||
|
|
||||||
grouped := make(map[int32][]WeaponEvolutionGroupRow)
|
grouped := make(map[int32][]EntityMWeaponEvolutionGroup)
|
||||||
for _, row := range evoGroupRows {
|
for _, row := range evoGroupRows {
|
||||||
grouped[row.WeaponEvolutionGroupId] = append(grouped[row.WeaponEvolutionGroupId], row)
|
grouped[row.WeaponEvolutionGroupId] = append(grouped[row.WeaponEvolutionGroupId], row)
|
||||||
}
|
}
|
||||||
@@ -399,7 +282,7 @@ func LoadWeaponCatalog(matCatalog *MaterialCatalog) (*WeaponCatalog, error) {
|
|||||||
|
|
||||||
// Rarity-based enhancement fallback: for weapons with WeaponSpecificEnhanceId == 0,
|
// Rarity-based enhancement fallback: for weapons with WeaponSpecificEnhanceId == 0,
|
||||||
// use EntityMWeaponRarityTable curves via synthetic enhance IDs (-RarityType).
|
// use EntityMWeaponRarityTable curves via synthetic enhance IDs (-RarityType).
|
||||||
rarityByType := make(map[int32]weaponRarityEnhanceRow, len(rarityEnhanceRows))
|
rarityByType := make(map[int32]EntityMWeaponRarity, len(rarityEnhanceRows))
|
||||||
for _, r := range rarityEnhanceRows {
|
for _, r := range rarityEnhanceRows {
|
||||||
rarityByType[r.RarityType] = r
|
rarityByType[r.RarityType] = r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientPlatform struct {
|
||||||
|
OsType int32 // 1=iOS, 2=Android
|
||||||
|
PlatformType int32 // 1=AppStore, 2=GooglePlay, 8=Amazon
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
OsTypeIOS int32 = 1
|
||||||
|
OsTypeAndroid int32 = 2
|
||||||
|
|
||||||
|
PlatformTypeAppStore int32 = 1
|
||||||
|
PlatformTypeGooglePlayStore int32 = 2
|
||||||
|
PlatformTypeAmazonAppStore int32 = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultPlatform = ClientPlatform{OsType: OsTypeAndroid, PlatformType: PlatformTypeGooglePlayStore}
|
||||||
|
|
||||||
|
type platformKey struct{}
|
||||||
|
|
||||||
|
func (p ClientPlatform) String() string {
|
||||||
|
os := "unknown"
|
||||||
|
switch p.OsType {
|
||||||
|
case OsTypeIOS:
|
||||||
|
os = "iOS"
|
||||||
|
case OsTypeAndroid:
|
||||||
|
os = "Android"
|
||||||
|
}
|
||||||
|
plat := "unknown"
|
||||||
|
switch p.PlatformType {
|
||||||
|
case PlatformTypeAppStore:
|
||||||
|
plat = "AppStore"
|
||||||
|
case PlatformTypeGooglePlayStore:
|
||||||
|
plat = "GooglePlay"
|
||||||
|
case PlatformTypeAmazonAppStore:
|
||||||
|
plat = "Amazon"
|
||||||
|
}
|
||||||
|
return os + "/" + plat
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientPlatformFromHeaders(ctx context.Context) ClientPlatform {
|
||||||
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return DefaultPlatform
|
||||||
|
}
|
||||||
|
|
||||||
|
p := DefaultPlatform
|
||||||
|
if vals := md.Get("x-apb-os-type"); len(vals) > 0 {
|
||||||
|
if v, err := strconv.ParseInt(vals[0], 10, 32); err == nil {
|
||||||
|
p.OsType = int32(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vals := md.Get("x-apb-platform-type"); len(vals) > 0 {
|
||||||
|
if v, err := strconv.ParseInt(vals[0], 10, 32); err == nil {
|
||||||
|
p.PlatformType = int32(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextWithPlatform(ctx context.Context, p ClientPlatform) context.Context {
|
||||||
|
return context.WithValue(ctx, platformKey{}, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClientPlatformFromContext(ctx context.Context) ClientPlatform {
|
||||||
|
if p, ok := ctx.Value(platformKey{}).(ClientPlatform); ok {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return DefaultPlatform
|
||||||
|
}
|
||||||
@@ -83,7 +83,7 @@ func (h *QuestHandler) HandleEventQuestSceneProgress(user *store.UserState, ques
|
|||||||
|
|
||||||
h.applySceneGrants(user, questSceneId, nowMillis)
|
h.applySceneGrants(user, questSceneId, nowMillis)
|
||||||
|
|
||||||
if scene.QuestResultType == model.QuestResultTypeHalfResult {
|
if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult {
|
||||||
h.clearQuestMissions(user, scene.QuestId, nowMillis)
|
h.clearQuestMissions(user, scene.QuestId, nowMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func (h *QuestHandler) HandleExtraQuestSceneProgress(user *store.UserState, ques
|
|||||||
|
|
||||||
h.applySceneGrants(user, questSceneId, nowMillis)
|
h.applySceneGrants(user, questSceneId, nowMillis)
|
||||||
|
|
||||||
if scene.QuestResultType == model.QuestResultTypeHalfResult {
|
if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult {
|
||||||
h.clearQuestMissions(user, scene.QuestId, nowMillis)
|
h.clearQuestMissions(user, scene.QuestId, nowMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func BuildGranter(catalog *masterdata.QuestCatalog) *store.PossessionGranter {
|
|||||||
for i, r := range rows {
|
for i, r := range rows {
|
||||||
conds[i] = store.WeaponStoryReleaseCond{
|
conds[i] = store.WeaponStoryReleaseCond{
|
||||||
StoryIndex: r.StoryIndex,
|
StoryIndex: r.StoryIndex,
|
||||||
WeaponStoryReleaseConditionType: r.WeaponStoryReleaseConditionType,
|
WeaponStoryReleaseConditionType: model.WeaponStoryReleaseConditionType(r.WeaponStoryReleaseConditionType),
|
||||||
ConditionValue: r.ConditionValue,
|
ConditionValue: r.ConditionValue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (h *QuestHandler) initQuestState(user *store.UserState, questId int32) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isMainQuestPlayable(quest masterdata.QuestRow) bool {
|
func isMainQuestPlayable(quest masterdata.EntityMQuest) bool {
|
||||||
return !quest.IsRunInTheBackground && quest.IsCountedAsQuest
|
return !quest.IsRunInTheBackground && quest.IsCountedAsQuest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ func (h *QuestHandler) isQuestCleared(user *store.UserState, questId int32) bool
|
|||||||
return quest.QuestStateType == model.UserQuestStateTypeCleared
|
return quest.QuestStateType == model.UserQuestStateTypeCleared
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendMissionRewards(dst []RewardGrant, src []masterdata.QuestMissionRewardRow) []RewardGrant {
|
func appendMissionRewards(dst []RewardGrant, src []masterdata.EntityMQuestMissionReward) []RewardGrant {
|
||||||
for _, r := range src {
|
for _, r := range src {
|
||||||
dst = append(dst, RewardGrant{
|
dst = append(dst, RewardGrant{
|
||||||
PossessionType: r.PossessionType,
|
PossessionType: model.PossessionType(r.PossessionType),
|
||||||
PossessionId: r.PossessionId,
|
PossessionId: r.PossessionId,
|
||||||
Count: r.Count,
|
Count: r.Count,
|
||||||
})
|
})
|
||||||
@@ -31,7 +31,7 @@ func appendMissionRewards(dst []RewardGrant, src []masterdata.QuestMissionReward
|
|||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *QuestHandler) firstClearRewardGroupId(user *store.UserState, questDef masterdata.QuestRow) int32 {
|
func (h *QuestHandler) firstClearRewardGroupId(user *store.UserState, questDef masterdata.EntityMQuest) int32 {
|
||||||
rewardGroupId := questDef.QuestFirstClearRewardGroupId
|
rewardGroupId := questDef.QuestFirstClearRewardGroupId
|
||||||
for _, switchRow := range h.FirstClearRewardSwitchesByQuestId[questDef.QuestId] {
|
for _, switchRow := range h.FirstClearRewardSwitchesByQuestId[questDef.QuestId] {
|
||||||
if h.isQuestCleared(user, switchRow.SwitchConditionClearQuestId) {
|
if h.isQuestCleared(user, switchRow.SwitchConditionClearQuestId) {
|
||||||
@@ -57,7 +57,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
|
|||||||
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
|
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
|
||||||
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
|
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
|
||||||
outcome.FirstClearRewards = append(outcome.FirstClearRewards, RewardGrant{
|
outcome.FirstClearRewards = append(outcome.FirstClearRewards, RewardGrant{
|
||||||
PossessionType: reward.PossessionType,
|
PossessionType: model.PossessionType(reward.PossessionType),
|
||||||
PossessionId: reward.PossessionId,
|
PossessionId: reward.PossessionId,
|
||||||
Count: reward.Count,
|
Count: reward.Count,
|
||||||
})
|
})
|
||||||
@@ -67,7 +67,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
|
|||||||
if user.MainQuest.CurrentQuestFlowType == int32(model.QuestFlowTypeReplayFlow) && questDef.QuestReplayFlowRewardGroupId > 0 {
|
if user.MainQuest.CurrentQuestFlowType == int32(model.QuestFlowTypeReplayFlow) && questDef.QuestReplayFlowRewardGroupId > 0 {
|
||||||
for _, reward := range h.ReplayFlowRewardsByGroupId[questDef.QuestReplayFlowRewardGroupId] {
|
for _, reward := range h.ReplayFlowRewardsByGroupId[questDef.QuestReplayFlowRewardGroupId] {
|
||||||
outcome.ReplayFlowFirstClearRewards = append(outcome.ReplayFlowFirstClearRewards, RewardGrant{
|
outcome.ReplayFlowFirstClearRewards = append(outcome.ReplayFlowFirstClearRewards, RewardGrant{
|
||||||
PossessionType: reward.PossessionType,
|
PossessionType: model.PossessionType(reward.PossessionType),
|
||||||
PossessionId: reward.PossessionId,
|
PossessionId: reward.PossessionId,
|
||||||
Count: reward.Count,
|
Count: reward.Count,
|
||||||
})
|
})
|
||||||
@@ -78,7 +78,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
|
|||||||
regularMissionCount := 0
|
regularMissionCount := 0
|
||||||
for _, questMissionId := range h.MissionIdsByQuestId[questId] {
|
for _, questMissionId := range h.MissionIdsByQuestId[questId] {
|
||||||
missionDef, ok := h.MissionById[questMissionId]
|
missionDef, ok := h.MissionById[questMissionId]
|
||||||
if !ok || missionDef.QuestMissionConditionType == model.QuestMissionConditionTypeComplete {
|
if !ok || model.QuestMissionConditionType(missionDef.QuestMissionConditionType) == model.QuestMissionConditionTypeComplete {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
regularMissionCount++
|
regularMissionCount++
|
||||||
@@ -103,7 +103,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
|
|||||||
if allRegularWillClear {
|
if allRegularWillClear {
|
||||||
for _, questMissionId := range h.MissionIdsByQuestId[questId] {
|
for _, questMissionId := range h.MissionIdsByQuestId[questId] {
|
||||||
missionDef, ok := h.MissionById[questMissionId]
|
missionDef, ok := h.MissionById[questMissionId]
|
||||||
if !ok || missionDef.QuestMissionConditionType != model.QuestMissionConditionTypeComplete {
|
if !ok || model.QuestMissionConditionType(missionDef.QuestMissionConditionType) != model.QuestMissionConditionTypeComplete {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := store.QuestMissionKey{QuestId: questId, QuestMissionId: questMissionId}
|
key := store.QuestMissionKey{QuestId: questId, QuestMissionId: questMissionId}
|
||||||
@@ -122,7 +122,7 @@ func (h *QuestHandler) evaluateFinishOutcome(user *store.UserState, questId int3
|
|||||||
return outcome
|
return outcome
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *QuestHandler) computeDropRewards(questDef masterdata.QuestRow) []RewardGrant {
|
func (h *QuestHandler) computeDropRewards(questDef masterdata.EntityMQuest) []RewardGrant {
|
||||||
if questDef.QuestPickupRewardGroupId == 0 {
|
if questDef.QuestPickupRewardGroupId == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ func (h *QuestHandler) computeDropRewards(questDef masterdata.QuestRow) []Reward
|
|||||||
for _, dropId := range h.PickupRewardIdsByGroupId[questDef.QuestPickupRewardGroupId] {
|
for _, dropId := range h.PickupRewardIdsByGroupId[questDef.QuestPickupRewardGroupId] {
|
||||||
if bdr, ok := h.BattleDropRewardById[dropId]; ok {
|
if bdr, ok := h.BattleDropRewardById[dropId]; ok {
|
||||||
drops = append(drops, RewardGrant{
|
drops = append(drops, RewardGrant{
|
||||||
PossessionType: bdr.PossessionType,
|
PossessionType: model.PossessionType(bdr.PossessionType),
|
||||||
PossessionId: bdr.PossessionId,
|
PossessionId: bdr.PossessionId,
|
||||||
Count: bdr.Count,
|
Count: bdr.Count,
|
||||||
})
|
})
|
||||||
@@ -257,7 +257,7 @@ func (h *QuestHandler) applyQuestRewards(user *store.UserState, questId int32, n
|
|||||||
|
|
||||||
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
|
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
|
||||||
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
|
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
|
||||||
h.applyRewardPossession(user, reward.PossessionType, reward.PossessionId, reward.Count, nowMillis)
|
h.applyRewardPossession(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +365,7 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat
|
|||||||
}
|
}
|
||||||
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
|
rewardGroupId := h.firstClearRewardGroupId(user, questDef)
|
||||||
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
|
for _, reward := range h.FirstClearRewardsByGroupId[rewardGroupId] {
|
||||||
if reward.PossessionType != model.PossessionTypeWeapon {
|
if model.PossessionType(reward.PossessionType) != model.PossessionTypeWeapon {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
weaponId := reward.PossessionId
|
weaponId := reward.PossessionId
|
||||||
@@ -375,7 +375,7 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat
|
|||||||
}
|
}
|
||||||
groupId := weapon.WeaponStoryReleaseConditionGroupId
|
groupId := weapon.WeaponStoryReleaseConditionGroupId
|
||||||
for _, cond := range h.ReleaseConditionsByGroupId[groupId] {
|
for _, cond := range h.ReleaseConditionsByGroupId[groupId] {
|
||||||
if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 {
|
if model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) == model.WeaponStoryReleaseConditionTypeAcquisition && cond.ConditionValue == 0 {
|
||||||
if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
|
if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
|
||||||
changedIds = append(changedIds, weaponId)
|
changedIds = append(changedIds, weaponId)
|
||||||
}
|
}
|
||||||
@@ -387,7 +387,7 @@ func (h *QuestHandler) grantWeaponStoryUnlocksForQuestScene(user *store.UserStat
|
|||||||
if resultType == model.QuestResultTypeFullResult {
|
if resultType == model.QuestResultTypeFullResult {
|
||||||
for groupId, conditions := range h.ReleaseConditionsByGroupId {
|
for groupId, conditions := range h.ReleaseConditionsByGroupId {
|
||||||
for _, cond := range conditions {
|
for _, cond := range conditions {
|
||||||
if cond.WeaponStoryReleaseConditionType == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId {
|
if model.WeaponStoryReleaseConditionType(cond.WeaponStoryReleaseConditionType) == model.WeaponStoryReleaseConditionTypeQuestClear && cond.ConditionValue == questId {
|
||||||
for _, weaponId := range h.WeaponIdsByReleaseConditionGroupId[groupId] {
|
for _, weaponId := range h.WeaponIdsByReleaseConditionGroupId[groupId] {
|
||||||
if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
|
if h.grantWeaponStoryUnlock(user, weaponId, cond.StoryIndex, nowMillis) {
|
||||||
changedIds = append(changedIds, weaponId)
|
changedIds = append(changedIds, weaponId)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func (h *QuestHandler) applySceneGrants(user *store.UserState, questSceneId int3
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, g := range grants {
|
for _, g := range grants {
|
||||||
h.applyRewardPossession(user, g.PossessionType, g.PossessionId, g.Count, nowMillis)
|
h.applyRewardPossession(user, model.PossessionType(g.PossessionType), g.PossessionId, g.Count, nowMillis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ func (h *QuestHandler) HandleMainQuestSceneProgress(user *store.UserState, quest
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isMainQuestPlayable(quest) {
|
if isMainQuestPlayable(quest) {
|
||||||
if scene.QuestResultType == model.QuestResultTypeHalfResult {
|
if model.QuestResultType(scene.QuestResultType) == model.QuestResultTypeHalfResult {
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
h.clearQuestMissions(user, quest.QuestId, nowMillis)
|
h.clearQuestMissions(user, quest.QuestId, nowMillis)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ type assetResolution struct {
|
|||||||
Candidates []assetCandidate
|
Candidates []assetCandidate
|
||||||
}
|
}
|
||||||
|
|
||||||
type assetResolver struct{}
|
type assetResolver struct {
|
||||||
|
baseDir string
|
||||||
|
}
|
||||||
|
|
||||||
func newRevisionTracker() *revisionTracker {
|
func newRevisionTracker() *revisionTracker {
|
||||||
return &revisionTracker{
|
return &revisionTracker{
|
||||||
@@ -28,8 +30,8 @@ func newRevisionTracker() *revisionTracker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAssetResolver() *assetResolver {
|
func newAssetResolver(baseDir string) *assetResolver {
|
||||||
return &assetResolver{}
|
return &assetResolver{baseDir: baseDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeClientAddr(remoteAddr string) string {
|
func normalizeClientAddr(remoteAddr string) string {
|
||||||
@@ -73,7 +75,7 @@ func (r *assetResolver) Resolve(objectId, assetType, activeRevision string) (ass
|
|||||||
resolution := assetResolution{ActiveRevision: activeRevision}
|
resolution := assetResolution{ActiveRevision: activeRevision}
|
||||||
revision := activeRevision
|
revision := activeRevision
|
||||||
|
|
||||||
candidates, listSize, ok := objectIdToFilePathCandidates(revision, assetType, objectId)
|
candidates, listSize, ok := objectIdToFilePathCandidates(r.baseDir, revision, assetType, objectId)
|
||||||
if ok && len(candidates) > 0 {
|
if ok && len(candidates) > 0 {
|
||||||
resolution.ListRevision = revision
|
resolution.ListRevision = revision
|
||||||
resolution.ListSize = listSize
|
resolution.ListSize = listSize
|
||||||
@@ -94,6 +96,6 @@ func (r *assetResolver) Prewarm(activeRevision string) {
|
|||||||
if activeRevision == "" {
|
if activeRevision == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, _ = loadListBinIndex(activeRevision)
|
_, _ = loadListBinIndex(r.baseDir, activeRevision)
|
||||||
_ = loadInfoIndex(activeRevision)
|
_ = loadInfoIndex(r.baseDir, activeRevision)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BannerServiceServer struct {
|
type BannerServiceServer struct {
|
||||||
@@ -44,6 +43,5 @@ func (s *BannerServiceServer) GetMamaBanner(ctx context.Context, req *pb.GetMama
|
|||||||
TermLimitedGacha: termLimited,
|
TermLimitedGacha: termLimited,
|
||||||
LatestChapterGacha: latestChapter,
|
LatestChapterGacha: latestChapter,
|
||||||
IsExistUnreadPop: false,
|
IsExistUnreadPop: false,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BattleServiceServer struct {
|
type BattleServiceServer struct {
|
||||||
@@ -22,7 +21,7 @@ func NewBattleServiceServer(users store.UserRepository, sessions store.SessionRe
|
|||||||
|
|
||||||
func (s *BattleServiceServer) StartWave(ctx context.Context, req *pb.StartWaveRequest) (*pb.StartWaveResponse, error) {
|
func (s *BattleServiceServer) StartWave(ctx context.Context, req *pb.StartWaveRequest) (*pb.StartWaveResponse, error) {
|
||||||
log.Printf("[BattleService] StartWave: userParty=%d npcParty=%d", len(req.UserPartyInitialInfoList), len(req.NpcPartyInitialInfoList))
|
log.Printf("[BattleService] StartWave: userParty=%d npcParty=%d", len(req.UserPartyInitialInfoList), len(req.NpcPartyInitialInfoList))
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.Battle.IsActive = true
|
user.Battle.IsActive = true
|
||||||
user.Battle.StartCount++
|
user.Battle.StartCount++
|
||||||
@@ -30,15 +29,13 @@ func (s *BattleServiceServer) StartWave(ctx context.Context, req *pb.StartWaveRe
|
|||||||
user.Battle.LastUserPartyCount = int32(len(req.UserPartyInitialInfoList))
|
user.Battle.LastUserPartyCount = int32(len(req.UserPartyInitialInfoList))
|
||||||
user.Battle.LastNpcPartyCount = int32(len(req.NpcPartyInitialInfoList))
|
user.Battle.LastNpcPartyCount = int32(len(req.NpcPartyInitialInfoList))
|
||||||
})
|
})
|
||||||
return &pb.StartWaveResponse{
|
return &pb.StartWaveResponse{}, nil
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BattleServiceServer) FinishWave(ctx context.Context, req *pb.FinishWaveRequest) (*pb.FinishWaveResponse, error) {
|
func (s *BattleServiceServer) FinishWave(ctx context.Context, req *pb.FinishWaveRequest) (*pb.FinishWaveResponse, error) {
|
||||||
log.Printf("[BattleService] FinishWave: battleBinary=%d userParty=%d npcParty=%d elapsedFrames=%d",
|
log.Printf("[BattleService] FinishWave: battleBinary=%d userParty=%d npcParty=%d elapsedFrames=%d",
|
||||||
len(req.BattleBinary), len(req.UserPartyResultInfoList), len(req.NpcPartyResultInfoList), req.ElapsedFrameCount)
|
len(req.BattleBinary), len(req.UserPartyResultInfoList), len(req.NpcPartyResultInfoList), req.ElapsedFrameCount)
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.Battle.IsActive = false
|
user.Battle.IsActive = false
|
||||||
user.Battle.FinishCount++
|
user.Battle.FinishCount++
|
||||||
@@ -48,7 +45,5 @@ func (s *BattleServiceServer) FinishWave(ctx context.Context, req *pb.FinishWave
|
|||||||
user.Battle.LastBattleBinarySize = int32(len(req.BattleBinary))
|
user.Battle.LastBattleBinarySize = int32(len(req.BattleBinary))
|
||||||
user.Battle.LastElapsedFrameCount = req.ElapsedFrameCount
|
user.Battle.LastElapsedFrameCount = req.ElapsedFrameCount
|
||||||
})
|
})
|
||||||
return &pb.FinishWaveResponse{
|
return &pb.FinishWaveResponse{}, nil
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CageOrnamentServiceServer struct {
|
type CageOrnamentServiceServer struct {
|
||||||
@@ -32,9 +31,9 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
|
|||||||
log.Fatalf("[CageOrnamentService] ReceiveReward: no reward for cageOrnamentId=%d", req.CageOrnamentId)
|
log.Fatalf("[CageOrnamentService] ReceiveReward: no reward for cageOrnamentId=%d", req.CageOrnamentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{
|
user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{
|
||||||
CageOrnamentId: req.CageOrnamentId,
|
CageOrnamentId: req.CageOrnamentId,
|
||||||
AcquisitionDatetime: nowMillis,
|
AcquisitionDatetime: nowMillis,
|
||||||
@@ -43,17 +42,6 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
|
|||||||
s.granter.GrantFull(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis)
|
s.granter.GrantFull(user, model.PossessionType(reward.PossessionType), reward.PossessionId, reward.Count, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
|
|
||||||
[]string{
|
|
||||||
"IUserMaterial", "IUserConsumableItem", "IUserGem",
|
|
||||||
"IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter",
|
|
||||||
"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility",
|
|
||||||
"IUserWeaponNote",
|
|
||||||
"IUserCageOrnamentReward",
|
|
||||||
},
|
|
||||||
))
|
|
||||||
userdata.AddWeaponStoryDiff(diff, user, s.granter.DrainChangedStoryWeaponIds())
|
|
||||||
|
|
||||||
return &pb.ReceiveRewardResponse{
|
return &pb.ReceiveRewardResponse{
|
||||||
CageOrnamentReward: []*pb.CageOrnamentReward{
|
CageOrnamentReward: []*pb.CageOrnamentReward{
|
||||||
{
|
{
|
||||||
@@ -62,16 +50,15 @@ func (s *CageOrnamentServiceServer) ReceiveReward(ctx context.Context, req *pb.R
|
|||||||
Count: reward.Count,
|
Count: reward.Count,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CageOrnamentServiceServer) RecordAccess(ctx context.Context, req *pb.RecordAccessRequest) (*pb.RecordAccessResponse, error) {
|
func (s *CageOrnamentServiceServer) RecordAccess(ctx context.Context, req *pb.RecordAccessRequest) (*pb.RecordAccessResponse, error) {
|
||||||
log.Printf("[CageOrnamentService] RecordAccess: cageOrnamentId=%d", req.CageOrnamentId)
|
log.Printf("[CageOrnamentService] RecordAccess: cageOrnamentId=%d", req.CageOrnamentId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if _, exists := user.CageOrnamentRewards[req.CageOrnamentId]; !exists {
|
if _, exists := user.CageOrnamentRewards[req.CageOrnamentId]; !exists {
|
||||||
user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{
|
user.CageOrnamentRewards[req.CageOrnamentId] = store.CageOrnamentRewardState{
|
||||||
CageOrnamentId: req.CageOrnamentId,
|
CageOrnamentId: req.CageOrnamentId,
|
||||||
@@ -81,11 +68,5 @@ func (s *CageOrnamentServiceServer) RecordAccess(ctx context.Context, req *pb.Re
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
|
return &pb.RecordAccessResponse{}, nil
|
||||||
[]string{"IUserCageOrnamentReward"},
|
|
||||||
))
|
|
||||||
|
|
||||||
return &pb.RecordAccessResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CharacterServiceServer struct {
|
type CharacterServiceServer struct {
|
||||||
@@ -26,7 +25,7 @@ func NewCharacterServiceServer(users store.UserRepository, sessions store.Sessio
|
|||||||
func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthRequest) (*pb.RebirthResponse, error) {
|
func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthRequest) (*pb.RebirthResponse, error) {
|
||||||
log.Printf("[CharacterService] Rebirth: characterId=%d rebirthCount=%d", req.CharacterId, req.RebirthCount)
|
log.Printf("[CharacterService] Rebirth: characterId=%d rebirthCount=%d", req.CharacterId, req.RebirthCount)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
stepGroupId, ok := s.catalog.StepGroupByCharacterId[req.CharacterId]
|
stepGroupId, ok := s.catalog.StepGroupByCharacterId[req.CharacterId]
|
||||||
@@ -35,11 +34,7 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
|
|||||||
return &pb.RebirthResponse{}, nil
|
return &pb.RebirthResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
|
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
current := user.CharacterRebirths[req.CharacterId]
|
current := user.CharacterRebirths[req.CharacterId]
|
||||||
currentCount := current.RebirthCount
|
currentCount := current.RebirthCount
|
||||||
targetCount := currentCount + req.RebirthCount
|
targetCount := currentCount + req.RebirthCount
|
||||||
@@ -77,9 +72,5 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rebirthTables := []string{"IUserCharacterRebirth", "IUserMaterial", "IUserConsumableItem"}
|
return &pb.RebirthResponse{}, nil
|
||||||
tables := userdata.ProjectTables(snapshot, rebirthTables)
|
|
||||||
diff := tracker.Apply(snapshot, tables)
|
|
||||||
|
|
||||||
return &pb.RebirthResponse{DiffUserData: diff}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type CharacterBoardServiceServer struct {
|
type CharacterBoardServiceServer struct {
|
||||||
@@ -25,14 +24,9 @@ func NewCharacterBoardServiceServer(users store.UserRepository, sessions store.S
|
|||||||
func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.ReleasePanelRequest) (*pb.ReleasePanelResponse, error) {
|
func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.ReleasePanelRequest) (*pb.ReleasePanelResponse, error) {
|
||||||
log.Printf("[CharacterBoardService] ReleasePanel: panelIds=%v", req.CharacterBoardPanelId)
|
log.Printf("[CharacterBoardService] ReleasePanel: panelIds=%v", req.CharacterBoardPanelId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}).
|
|
||||||
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
|
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
for _, panelId := range req.CharacterBoardPanelId {
|
for _, panelId := range req.CharacterBoardPanelId {
|
||||||
panel, ok := s.catalog.PanelById[panelId]
|
panel, ok := s.catalog.PanelById[panelId]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -46,28 +40,17 @@ func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
boardTables := []string{
|
return &pb.ReleasePanelResponse{}, nil
|
||||||
"IUserCharacterBoard",
|
|
||||||
"IUserCharacterBoardAbility",
|
|
||||||
"IUserCharacterBoardStatusUp",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserGem",
|
|
||||||
}
|
|
||||||
tables := userdata.ProjectTables(user, boardTables)
|
|
||||||
diff := tracker.Apply(user, tables)
|
|
||||||
|
|
||||||
return &pb.ReleasePanelResponse{DiffUserData: diff}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.CharacterBoardPanelRow) {
|
func (s *CharacterBoardServiceServer) consumeCosts(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||||
costs := s.catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId]
|
costs := s.catalog.ReleaseCostsByGroupId[panel.CharacterBoardPanelReleasePossessionGroupId]
|
||||||
for _, cost := range costs {
|
for _, cost := range costs {
|
||||||
store.DeductPossession(user, model.PossessionType(cost.PossessionType), cost.PossessionId, cost.Count)
|
store.DeductPossession(user, model.PossessionType(cost.PossessionType), cost.PossessionId, cost.Count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel masterdata.CharacterBoardPanelRow) {
|
func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||||
boardId := panel.CharacterBoardId
|
boardId := panel.CharacterBoardId
|
||||||
board := user.CharacterBoards[boardId]
|
board := user.CharacterBoards[boardId]
|
||||||
board.CharacterBoardId = boardId
|
board.CharacterBoardId = boardId
|
||||||
@@ -90,7 +73,7 @@ func (s *CharacterBoardServiceServer) setReleaseBit(user *store.UserState, panel
|
|||||||
user.CharacterBoards[boardId] = board
|
user.CharacterBoards[boardId] = board
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.CharacterBoardPanelRow) {
|
func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel masterdata.EntityMCharacterBoardPanel) {
|
||||||
effects := s.catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId]
|
effects := s.catalog.ReleaseEffectsByGroupId[panel.CharacterBoardPanelReleaseEffectGroupId]
|
||||||
for _, eff := range effects {
|
for _, eff := range effects {
|
||||||
switch model.CharacterBoardEffectType(eff.CharacterBoardEffectType) {
|
switch model.CharacterBoardEffectType(eff.CharacterBoardEffectType) {
|
||||||
@@ -102,7 +85,7 @@ func (s *CharacterBoardServiceServer) applyEffects(user *store.UserState, panel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.CharacterBoardReleaseEffectRow) {
|
func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||||
ability, ok := s.catalog.AbilityById[eff.CharacterBoardEffectId]
|
ability, ok := s.catalog.AbilityById[eff.CharacterBoardEffectId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CharacterBoardService] unknown abilityId=%d", eff.CharacterBoardEffectId)
|
log.Printf("[CharacterBoardService] unknown abilityId=%d", eff.CharacterBoardEffectId)
|
||||||
@@ -127,7 +110,7 @@ func (s *CharacterBoardServiceServer) applyAbilityEffect(user *store.UserState,
|
|||||||
user.CharacterBoardAbilities[key] = state
|
user.CharacterBoardAbilities[key] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.CharacterBoardReleaseEffectRow) {
|
func (s *CharacterBoardServiceServer) applyStatusUpEffect(user *store.UserState, eff masterdata.EntityMCharacterBoardPanelReleaseEffectGroup) {
|
||||||
statusUp, ok := s.catalog.StatusUpById[eff.CharacterBoardEffectId]
|
statusUp, ok := s.catalog.StatusUpById[eff.CharacterBoardEffectId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CharacterBoardService] unknown statusUpId=%d", eff.CharacterBoardEffectId)
|
log.Printf("[CharacterBoardService] unknown statusUpId=%d", eff.CharacterBoardEffectId)
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
@@ -28,7 +26,7 @@ func NewCharacterViewerServiceServer(users store.UserRepository, sessions store.
|
|||||||
func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _ *emptypb.Empty) (*pb.CharacterViewerTopResponse, error) {
|
func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _ *emptypb.Empty) (*pb.CharacterViewerTopResponse, error) {
|
||||||
log.Printf("[CharacterViewerService] CharacterViewerTop")
|
log.Printf("[CharacterViewerService] CharacterViewerTop")
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("CharacterViewerTop: no user for userId=%d: %v", userId, err))
|
panic(fmt.Sprintf("CharacterViewerTop: no user for userId=%d: %v", userId, err))
|
||||||
@@ -37,32 +35,7 @@ func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _
|
|||||||
released := s.catalog.ReleasedFieldIds(user)
|
released := s.catalog.ReleasedFieldIds(user)
|
||||||
log.Printf("[CharacterViewerService] released %d fields for user %d", len(released), userId)
|
log.Printf("[CharacterViewerService] released %d fields for user %d", len(released), userId)
|
||||||
|
|
||||||
now := gametime.NowMillis()
|
|
||||||
records := make([]map[string]any, 0, len(released))
|
|
||||||
for _, fieldId := range released {
|
|
||||||
records = append(records, map[string]any{
|
|
||||||
"userId": userId,
|
|
||||||
"characterViewerFieldId": fieldId,
|
|
||||||
"releaseDatetime": now,
|
|
||||||
"latestVersion": 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := "[]"
|
|
||||||
if len(records) > 0 {
|
|
||||||
data, _ := json.Marshal(records)
|
|
||||||
payload = string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
diff := map[string]*pb.DiffData{
|
|
||||||
"IUserCharacterViewerField": {
|
|
||||||
UpdateRecordsJson: payload,
|
|
||||||
DeleteKeysJson: "[]",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pb.CharacterViewerTopResponse{
|
return &pb.CharacterViewerTopResponse{
|
||||||
ReleaseCharacterViewerFieldId: released,
|
ReleaseCharacterViewerFieldId: released,
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,10 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const companionMaxLevel = int32(50)
|
const companionMaxLevel = int32(50)
|
||||||
|
|
||||||
var companionDiffTables = []string{
|
|
||||||
"IUserCompanion",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompanionServiceServer struct {
|
type CompanionServiceServer struct {
|
||||||
pb.UnimplementedCompanionServiceServer
|
pb.UnimplementedCompanionServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
@@ -35,10 +28,10 @@ func NewCompanionServiceServer(users store.UserRepository, sessions store.Sessio
|
|||||||
func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionEnhanceRequest) (*pb.CompanionEnhanceResponse, error) {
|
func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionEnhanceRequest) (*pb.CompanionEnhanceResponse, error) {
|
||||||
log.Printf("[CompanionService] Enhance: uuid=%s addLevel=%d", req.UserCompanionUuid, req.AddLevelCount)
|
log.Printf("[CompanionService] Enhance: uuid=%s addLevel=%d", req.UserCompanionUuid, req.AddLevelCount)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
companion, ok := user.Companions[req.UserCompanionUuid]
|
companion, ok := user.Companions[req.UserCompanionUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CompanionService] Enhance: companion uuid=%s not found", req.UserCompanionUuid)
|
log.Printf("[CompanionService] Enhance: companion uuid=%s not found", req.UserCompanionUuid)
|
||||||
@@ -77,9 +70,5 @@ func (s *CompanionServiceServer) Enhance(ctx context.Context, req *pb.CompanionE
|
|||||||
return nil, fmt.Errorf("companion enhance: %w", err)
|
return nil, fmt.Errorf("companion enhance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, companionDiffTables))
|
return &pb.CompanionEnhanceResponse{}, nil
|
||||||
|
|
||||||
return &pb.CompanionEnhanceResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -14,7 +13,7 @@ type ConfigServiceServer struct {
|
|||||||
pb.UnimplementedConfigServiceServer
|
pb.UnimplementedConfigServiceServer
|
||||||
GrpcHost string
|
GrpcHost string
|
||||||
GrpcPort int32
|
GrpcPort int32
|
||||||
OctoURL string // HTTP base URL for Octo (list + assets); client uses this instead of default resources.app.nierreincarnation.com
|
OctoURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigServiceServer(host string, port int32, octoURL string) *ConfigServiceServer {
|
func NewConfigServiceServer(host string, port int32, octoURL string) *ConfigServiceServer {
|
||||||
@@ -42,6 +41,5 @@ func (s *ConfigServiceServer) GetReviewServerConfig(ctx context.Context, _ *empt
|
|||||||
MasterData: &pb.MasterDataConfig{
|
MasterData: &pb.MasterDataConfig{
|
||||||
UrlFormat: s.OctoURL + "/master-data/%s",
|
UrlFormat: s.OctoURL + "/master-data/%s",
|
||||||
},
|
},
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConsumableItemServiceServer struct {
|
type ConsumableItemServiceServer struct {
|
||||||
@@ -26,13 +25,9 @@ func NewConsumableItemServiceServer(users store.UserRepository, sessions store.S
|
|||||||
func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) {
|
func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.ConsumableItemSellRequest) (*pb.ConsumableItemSellResponse, error) {
|
||||||
log.Printf("[ConsumableItemService] Sell: %d item(s)", len(req.ConsumableItemPossession))
|
log.Printf("[ConsumableItemService] Sell: %d item(s)", len(req.ConsumableItemPossession))
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
|
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
totalGold := int32(0)
|
totalGold := int32(0)
|
||||||
for _, item := range req.ConsumableItemPossession {
|
for _, item := range req.ConsumableItemPossession {
|
||||||
row, ok := s.catalog.All[item.ConsumableItemId]
|
row, ok := s.catalog.All[item.ConsumableItemId]
|
||||||
@@ -66,10 +61,5 @@ func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.Consumab
|
|||||||
return nil, fmt.Errorf("consumable item sell: %w", err)
|
return nil, fmt.Errorf("consumable item sell: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.ProjectTables(snapshot, []string{"IUserConsumableItem"})
|
return &pb.ConsumableItemSellResponse{}, nil
|
||||||
diff := tracker.Apply(snapshot, tables)
|
|
||||||
|
|
||||||
return &pb.ConsumableItemSellResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContentsStoryServiceServer struct {
|
type ContentsStoryServiceServer struct {
|
||||||
@@ -24,19 +23,15 @@ func NewContentsStoryServiceServer(users store.UserRepository, sessions store.Se
|
|||||||
func (s *ContentsStoryServiceServer) RegisterPlayed(ctx context.Context, req *pb.ContentsStoryRegisterPlayedRequest) (*pb.ContentsStoryRegisterPlayedResponse, error) {
|
func (s *ContentsStoryServiceServer) RegisterPlayed(ctx context.Context, req *pb.ContentsStoryRegisterPlayedRequest) (*pb.ContentsStoryRegisterPlayedResponse, error) {
|
||||||
log.Printf("[ContentsStoryService] RegisterPlayed: contentsStoryId=%d", req.ContentsStoryId)
|
log.Printf("[ContentsStoryService] RegisterPlayed: contentsStoryId=%d", req.ContentsStoryId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.ContentsStories[req.ContentsStoryId] = nowMillis
|
user.ContentsStories[req.ContentsStoryId] = nowMillis
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserContentsStory"}))
|
return &pb.ContentsStoryRegisterPlayedResponse{}, nil
|
||||||
|
|
||||||
return &pb.ContentsStoryRegisterPlayedResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,8 @@ import (
|
|||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var costumeDiffTables = []string{
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
}
|
|
||||||
|
|
||||||
type CostumeServiceServer struct {
|
type CostumeServiceServer struct {
|
||||||
pb.UnimplementedCostumeServiceServer
|
pb.UnimplementedCostumeServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
@@ -38,10 +31,10 @@ func NewCostumeServiceServer(users store.UserRepository, sessions store.SessionR
|
|||||||
func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceRequest) (*pb.EnhanceResponse, error) {
|
func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceRequest) (*pb.EnhanceResponse, error) {
|
||||||
log.Printf("[CostumeService] Enhance: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
log.Printf("[CostumeService] Enhance: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
costume, ok := user.Costumes[req.UserCostumeUuid]
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] Enhance: costume uuid=%s not found", req.UserCostumeUuid)
|
log.Printf("[CostumeService] Enhance: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
@@ -98,30 +91,19 @@ func (s *CostumeServiceServer) Enhance(ctx context.Context, req *pb.EnhanceReque
|
|||||||
return nil, fmt.Errorf("costume enhance: %w", err)
|
return nil, fmt.Errorf("costume enhance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables))
|
|
||||||
|
|
||||||
return &pb.EnhanceResponse{
|
return &pb.EnhanceResponse{
|
||||||
IsGreatSuccess: false,
|
IsGreatSuccess: false,
|
||||||
SurplusEnhanceMaterial: map[int32]int32{},
|
SurplusEnhanceMaterial: map[int32]int32{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var awakenDiffTables = []string{
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserCostumeAwakenStatusUp",
|
|
||||||
"IUserThought",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest) (*pb.AwakenResponse, error) {
|
func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest) (*pb.AwakenResponse, error) {
|
||||||
log.Printf("[CostumeService] Awaken: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
log.Printf("[CostumeService] Awaken: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
costume, ok := user.Costumes[req.UserCostumeUuid]
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] Awaken: costume uuid=%s not found", req.UserCostumeUuid)
|
log.Printf("[CostumeService] Awaken: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
@@ -179,11 +161,7 @@ func (s *CostumeServiceServer) Awaken(ctx context.Context, req *pb.AwakenRequest
|
|||||||
return nil, fmt.Errorf("costume awaken: %w", err)
|
return nil, fmt.Errorf("costume awaken: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, awakenDiffTables))
|
return &pb.AwakenResponse{}, nil
|
||||||
|
|
||||||
return &pb.AwakenResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) {
|
func (s *CostumeServiceServer) applyAwakenStatusUp(user *store.UserState, costumeUuid string, statusUpGroupId int32, nowMillis int64) {
|
||||||
@@ -245,19 +223,13 @@ func (s *CostumeServiceServer) applyAwakenItemAcquire(user *store.UserState, ite
|
|||||||
log.Printf("[CostumeService] Awaken: granted thought id=%d", acq.PossessionId)
|
log.Printf("[CostumeService] Awaken: granted thought id=%d", acq.PossessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
var activeSkillDiffTables = []string{
|
|
||||||
"IUserCostumeActiveSkill",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.EnhanceActiveSkillRequest) (*pb.EnhanceActiveSkillResponse, error) {
|
func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.EnhanceActiveSkillRequest) (*pb.EnhanceActiveSkillResponse, error) {
|
||||||
log.Printf("[CostumeService] EnhanceActiveSkill: uuid=%s addLevel=%d", req.UserCostumeUuid, req.AddLevelCount)
|
log.Printf("[CostumeService] EnhanceActiveSkill: uuid=%s addLevel=%d", req.UserCostumeUuid, req.AddLevelCount)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
costume, ok := user.Costumes[req.UserCostumeUuid]
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] EnhanceActiveSkill: costume uuid=%s not found", req.UserCostumeUuid)
|
log.Printf("[CostumeService] EnhanceActiveSkill: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
@@ -332,20 +304,16 @@ func (s *CostumeServiceServer) EnhanceActiveSkill(ctx context.Context, req *pb.E
|
|||||||
return nil, fmt.Errorf("costume enhance active skill: %w", err)
|
return nil, fmt.Errorf("costume enhance active skill: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, activeSkillDiffTables))
|
return &pb.EnhanceActiveSkillResponse{}, nil
|
||||||
|
|
||||||
return &pb.EnhanceActiveSkillResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBreakRequest) (*pb.LimitBreakResponse, error) {
|
func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBreakRequest) (*pb.LimitBreakResponse, error) {
|
||||||
log.Printf("[CostumeService] LimitBreak: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
log.Printf("[CostumeService] LimitBreak: uuid=%s materials=%v", req.UserCostumeUuid, req.Materials)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
costume, ok := user.Costumes[req.UserCostumeUuid]
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] LimitBreak: costume uuid=%s not found", req.UserCostumeUuid)
|
log.Printf("[CostumeService] LimitBreak: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
@@ -389,30 +357,16 @@ func (s *CostumeServiceServer) LimitBreak(ctx context.Context, req *pb.LimitBrea
|
|||||||
return nil, fmt.Errorf("costume limit break: %w", err)
|
return nil, fmt.Errorf("costume limit break: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables))
|
return &pb.LimitBreakResponse{}, nil
|
||||||
|
|
||||||
return &pb.LimitBreakResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var lotteryEffectDiffTables = []string{
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserCostumeLotteryEffect",
|
|
||||||
"IUserCostumeLotteryEffectAbility",
|
|
||||||
"IUserCostumeLotteryEffectStatusUp",
|
|
||||||
"IUserCostumeLotteryEffectPending",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req *pb.UnlockLotteryEffectSlotRequest) (*pb.UnlockLotteryEffectSlotResponse, error) {
|
func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req *pb.UnlockLotteryEffectSlotRequest) (*pb.UnlockLotteryEffectSlotResponse, error) {
|
||||||
log.Printf("[CostumeService] UnlockLotteryEffectSlot: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
costume, ok := user.Costumes[req.UserCostumeUuid]
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] UnlockLotteryEffectSlot: costume uuid=%s not found", req.UserCostumeUuid)
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
@@ -458,26 +412,16 @@ func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req
|
|||||||
return nil, fmt.Errorf("costume unlock lottery effect slot: %w", err)
|
return nil, fmt.Errorf("costume unlock lottery effect slot: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
|
return &pb.UnlockLotteryEffectSlotResponse{}, nil
|
||||||
|
|
||||||
return &pb.UnlockLotteryEffectSlotResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.DrawLotteryEffectRequest) (*pb.DrawLotteryEffectResponse, error) {
|
func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.DrawLotteryEffectRequest) (*pb.DrawLotteryEffectResponse, error) {
|
||||||
log.Printf("[CostumeService] DrawLotteryEffect: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
log.Printf("[CostumeService] DrawLotteryEffect: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}).
|
|
||||||
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"}).
|
|
||||||
Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"})
|
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
costume, ok := user.Costumes[req.UserCostumeUuid]
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] DrawLotteryEffect: costume uuid=%s not found", req.UserCostumeUuid)
|
log.Printf("[CostumeService] DrawLotteryEffect: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
@@ -514,7 +458,7 @@ func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.Dr
|
|||||||
totalWeight += row.Weight
|
totalWeight += row.Weight
|
||||||
}
|
}
|
||||||
roll := rand.Int31n(totalWeight)
|
roll := rand.Int31n(totalWeight)
|
||||||
var picked masterdata.CostumeLotteryEffectOddsRow
|
var picked masterdata.EntityMCostumeLotteryEffectOddsGroup
|
||||||
for _, row := range oddsPool {
|
for _, row := range oddsPool {
|
||||||
roll -= row.Weight
|
roll -= row.Weight
|
||||||
if roll < 0 {
|
if roll < 0 {
|
||||||
@@ -550,24 +494,16 @@ func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.Dr
|
|||||||
return nil, fmt.Errorf("costume draw lottery effect: %w", err)
|
return nil, fmt.Errorf("costume draw lottery effect: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
|
return &pb.DrawLotteryEffectResponse{}, nil
|
||||||
|
|
||||||
return &pb.DrawLotteryEffectResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CostumeServiceServer) ConfirmLotteryEffect(ctx context.Context, req *pb.ConfirmLotteryEffectRequest) (*pb.ConfirmLotteryEffectResponse, error) {
|
func (s *CostumeServiceServer) ConfirmLotteryEffect(ctx context.Context, req *pb.ConfirmLotteryEffectRequest) (*pb.ConfirmLotteryEffectResponse, error) {
|
||||||
log.Printf("[CostumeService] ConfirmLotteryEffect: uuid=%s accept=%v", req.UserCostumeUuid, req.IsAccept)
|
log.Printf("[CostumeService] ConfirmLotteryEffect: uuid=%s accept=%v", req.UserCostumeUuid, req.IsAccept)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"})
|
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
pending, ok := user.CostumeLotteryEffectPending[req.UserCostumeUuid]
|
pending, ok := user.CostumeLotteryEffectPending[req.UserCostumeUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[CostumeService] ConfirmLotteryEffect: no pending for uuid=%s", req.UserCostumeUuid)
|
log.Printf("[CostumeService] ConfirmLotteryEffect: no pending for uuid=%s", req.UserCostumeUuid)
|
||||||
@@ -596,9 +532,5 @@ func (s *CostumeServiceServer) ConfirmLotteryEffect(ctx context.Context, req *pb
|
|||||||
return nil, fmt.Errorf("costume confirm lottery effect: %w", err)
|
return nil, fmt.Errorf("costume confirm lottery effect: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
|
return &pb.ConfirmLotteryEffectResponse{}, nil
|
||||||
|
|
||||||
return &pb.ConfirmLotteryEffectResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (s *DataServiceServer) GetUserDataNameV2(ctx context.Context, _ *emptypb.Em
|
|||||||
func (s *DataServiceServer) GetUserData(ctx context.Context, req *pb.UserDataGetRequest) (*pb.UserDataGetResponse, error) {
|
func (s *DataServiceServer) GetUserData(ctx context.Context, req *pb.UserDataGetRequest) (*pb.UserDataGetResponse, error) {
|
||||||
log.Printf("[DataService] GetUserData: tables=%v", req.TableName)
|
log.Printf("[DataService] GetUserData: tables=%v", req.TableName)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("snapshot user: %w", err)
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeckServiceServer struct {
|
type DeckServiceServer struct {
|
||||||
@@ -23,26 +22,23 @@ func NewDeckServiceServer(users store.UserRepository, sessions store.SessionRepo
|
|||||||
|
|
||||||
func (s *DeckServiceServer) UpdateName(ctx context.Context, req *pb.UpdateNameRequest) (*pb.UpdateNameResponse, error) {
|
func (s *DeckServiceServer) UpdateName(ctx context.Context, req *pb.UpdateNameRequest) (*pb.UpdateNameResponse, error) {
|
||||||
log.Printf("[DeckService] UpdateName: deckType=%d deckNumber=%d name=%q", req.DeckType, req.UserDeckNumber, req.Name)
|
log.Printf("[DeckService] UpdateName: deckType=%d deckNumber=%d name=%q", req.DeckType, req.UserDeckNumber, req.Name)
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
deckKey := store.DeckKey{DeckType: model.DeckType(req.DeckType), UserDeckNumber: req.UserDeckNumber}
|
deckKey := store.DeckKey{DeckType: model.DeckType(req.DeckType), UserDeckNumber: req.UserDeckNumber}
|
||||||
deck := user.Decks[deckKey]
|
deck := user.Decks[deckKey]
|
||||||
deck.Name = req.Name
|
deck.Name = req.Name
|
||||||
user.Decks[deckKey] = deck
|
user.Decks[deckKey] = deck
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.ProjectTables(user, []string{"IUserDeck"})
|
return &pb.UpdateNameResponse{}, nil
|
||||||
return &pb.UpdateNameResponse{
|
|
||||||
DiffUserData: userdata.BuildDiffFromTables(result),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DeckServiceServer) RefreshDeckPower(ctx context.Context, req *pb.RefreshDeckPowerRequest) (*pb.RefreshDeckPowerResponse, error) {
|
func (s *DeckServiceServer) RefreshDeckPower(ctx context.Context, req *pb.RefreshDeckPowerRequest) (*pb.RefreshDeckPowerResponse, error) {
|
||||||
log.Printf("[DeckService] RefreshDeckPower: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber)
|
log.Printf("[DeckService] RefreshDeckPower: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber)
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if req.DeckPower == nil {
|
if req.DeckPower == nil {
|
||||||
log.Printf("[DeckService] RefreshDeckPower: deckPower is nil")
|
log.Printf("[DeckService] RefreshDeckPower: deckPower is nil")
|
||||||
return
|
return
|
||||||
@@ -81,19 +77,14 @@ func (s *DeckServiceServer) RefreshDeckPower(ctx context.Context, req *pb.Refres
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.ProjectTables(user, []string{
|
return &pb.RefreshDeckPowerResponse{}, nil
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
|
|
||||||
})
|
|
||||||
return &pb.RefreshDeckPowerResponse{
|
|
||||||
DiffUserData: userdata.BuildDiffFromTables(result),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DeckServiceServer) RefreshMultiDeckPower(ctx context.Context, req *pb.RefreshMultiDeckPowerRequest) (*pb.RefreshMultiDeckPowerResponse, error) {
|
func (s *DeckServiceServer) RefreshMultiDeckPower(ctx context.Context, req *pb.RefreshMultiDeckPowerRequest) (*pb.RefreshMultiDeckPowerResponse, error) {
|
||||||
log.Printf("[DeckService] RefreshMultiDeckPower: %d entries", len(req.DeckPowerInfo))
|
log.Printf("[DeckService] RefreshMultiDeckPower: %d entries", len(req.DeckPowerInfo))
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for _, info := range req.DeckPowerInfo {
|
for _, info := range req.DeckPowerInfo {
|
||||||
if info.DeckPower == nil {
|
if info.DeckPower == nil {
|
||||||
continue
|
continue
|
||||||
@@ -133,12 +124,7 @@ func (s *DeckServiceServer) RefreshMultiDeckPower(ctx context.Context, req *pb.R
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.ProjectTables(user, []string{
|
return &pb.RefreshMultiDeckPowerResponse{}, nil
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
|
|
||||||
})
|
|
||||||
return &pb.RefreshMultiDeckPowerResponse{
|
|
||||||
DiffUserData: userdata.BuildDiffFromTables(result),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deckSlotsFromProto(deck *pb.Deck) []store.DeckCharacterInput {
|
func deckSlotsFromProto(deck *pb.Deck) []store.DeckCharacterInput {
|
||||||
@@ -171,47 +157,23 @@ func (s *DeckServiceServer) ReplaceDeck(ctx context.Context, req *pb.ReplaceDeck
|
|||||||
i+1, ch.UserCostumeUuid, ch.MainUserWeaponUuid, ch.SubUserWeaponUuid, ch.UserCompanionUuid, ch.UserThoughtUuid)
|
i+1, ch.UserCostumeUuid, ch.MainUserWeaponUuid, ch.SubUserWeaponUuid, ch.UserCompanionUuid, ch.UserThoughtUuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
|
|
||||||
[]string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
|
|
||||||
Track("IUserDeckPartsGroup", oldUser, userdata.DeckPartsGroupRecords,
|
|
||||||
[]string{"userId", "userDeckCharacterUuid", "userPartsUuid"}).
|
|
||||||
Track("IUserDeckCharacterDressupCostume", oldUser, userdata.DeckDressupCostumeRecords,
|
|
||||||
[]string{"userId", "userDeckCharacterUuid"})
|
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
if req.Deck == nil {
|
if req.Deck == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.ApplyDeckReplacement(user, model.DeckType(req.DeckType), req.UserDeckNumber, deckSlotsFromProto(req.Deck), gametime.NowMillis())
|
store.ApplyDeckReplacement(user, model.DeckType(req.DeckType), req.UserDeckNumber, deckSlotsFromProto(req.Deck), gametime.NowMillis())
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.ProjectTables(user, []string{
|
return &pb.ReplaceDeckResponse{}, nil
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
|
|
||||||
"IUserDeckCharacterDressupCostume",
|
|
||||||
})
|
|
||||||
return &pb.ReplaceDeckResponse{
|
|
||||||
DiffUserData: tracker.Apply(user, result),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.ReplaceTripleDeckRequest) (*pb.ReplaceTripleDeckResponse, error) {
|
func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.ReplaceTripleDeckRequest) (*pb.ReplaceTripleDeckResponse, error) {
|
||||||
log.Printf("[DeckService] ReplaceTripleDeck: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber)
|
log.Printf("[DeckService] ReplaceTripleDeck: deckType=%d deckNumber=%d", req.DeckType, req.UserDeckNumber)
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
|
|
||||||
[]string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
|
|
||||||
Track("IUserDeckPartsGroup", oldUser, userdata.DeckPartsGroupRecords,
|
|
||||||
[]string{"userId", "userDeckCharacterUuid", "userPartsUuid"}).
|
|
||||||
Track("IUserDeckCharacterDressupCostume", oldUser, userdata.DeckDressupCostumeRecords,
|
|
||||||
[]string{"userId", "userDeckCharacterUuid"})
|
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
for idx, detail := range []*pb.DeckDetail{req.DeckDetail01, req.DeckDetail02, req.DeckDetail03} {
|
for idx, detail := range []*pb.DeckDetail{req.DeckDetail01, req.DeckDetail02, req.DeckDetail03} {
|
||||||
if detail == nil || detail.Deck == nil {
|
if detail == nil || detail.Deck == nil {
|
||||||
@@ -231,11 +193,23 @@ func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.Repla
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.ProjectTables(user, []string{
|
return &pb.ReplaceTripleDeckResponse{}, nil
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
|
}
|
||||||
"IUserDeckCharacterDressupCostume",
|
|
||||||
})
|
func (s *DeckServiceServer) ReplaceMultiDeck(ctx context.Context, req *pb.ReplaceMultiDeckRequest) (*pb.ReplaceMultiDeckResponse, error) {
|
||||||
return &pb.ReplaceTripleDeckResponse{
|
log.Printf("[DeckService] ReplaceMultiDeck: %d entries", len(req.DeckDetail))
|
||||||
DiffUserData: tracker.Apply(user, result),
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
}, nil
|
|
||||||
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
nowMillis := gametime.NowMillis()
|
||||||
|
for idx, detail := range req.DeckDetail {
|
||||||
|
if detail == nil || detail.Deck == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("[DeckService] ReplaceMultiDeck detail %d: deckType=%d deckNumber=%d", idx+1, detail.DeckType, detail.UserDeckNumber)
|
||||||
|
store.ApplyDeckReplacement(user, model.DeckType(detail.DeckType), detail.UserDeckNumber, deckSlotsFromProto(detail.Deck), nowMillis)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return &pb.ReplaceMultiDeckResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DokanServiceServer struct {
|
type DokanServiceServer struct {
|
||||||
@@ -23,8 +22,8 @@ func NewDokanServiceServer(users store.UserRepository, sessions store.SessionRep
|
|||||||
func (s *DokanServiceServer) RegisterDokanConfirmed(ctx context.Context, req *pb.RegisterDokanConfirmedRequest) (*pb.RegisterDokanConfirmedResponse, error) {
|
func (s *DokanServiceServer) RegisterDokanConfirmed(ctx context.Context, req *pb.RegisterDokanConfirmedRequest) (*pb.RegisterDokanConfirmedResponse, error) {
|
||||||
log.Printf("[DokanService] RegisterDokanConfirmed: dokanIds=%v", req.DokanId)
|
log.Printf("[DokanService] RegisterDokanConfirmed: dokanIds=%v", req.DokanId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for _, id := range req.DokanId {
|
for _, id := range req.DokanId {
|
||||||
user.DokanConfirmed[id] = true
|
user.DokanConfirmed[id] = true
|
||||||
}
|
}
|
||||||
@@ -33,9 +32,5 @@ func (s *DokanServiceServer) RegisterDokanConfirmed(ctx context.Context, req *pb
|
|||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserDokan"}))
|
return &pb.RegisterDokanConfirmedResponse{}, nil
|
||||||
|
|
||||||
return &pb.RegisterDokanConfirmedResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -19,18 +18,6 @@ const (
|
|||||||
exploreRewardBaseCount = 1
|
exploreRewardBaseCount = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
var exploreDiffTables = []string{
|
|
||||||
"IUserExplore",
|
|
||||||
"IUserExploreScore",
|
|
||||||
}
|
|
||||||
|
|
||||||
var exploreFinishDiffTables = []string{
|
|
||||||
"IUserExplore",
|
|
||||||
"IUserExploreScore",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserStatus",
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExploreServiceServer struct {
|
type ExploreServiceServer struct {
|
||||||
pb.UnimplementedExploreServiceServer
|
pb.UnimplementedExploreServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
@@ -49,10 +36,10 @@ func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartEx
|
|||||||
return nil, fmt.Errorf("explore id=%d not found", req.ExploreId)
|
return nil, fmt.Errorf("explore id=%d not found", req.ExploreId)
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
explore := s.catalog.Explores[req.ExploreId]
|
explore := s.catalog.Explores[req.ExploreId]
|
||||||
if req.UseConsumableItemId > 0 && explore.ConsumeItemCount > 0 {
|
if req.UseConsumableItemId > 0 && explore.ConsumeItemCount > 0 {
|
||||||
cur := user.ConsumableItems[req.UseConsumableItemId]
|
cur := user.ConsumableItems[req.UseConsumableItemId]
|
||||||
@@ -71,11 +58,7 @@ func (s *ExploreServiceServer) StartExplore(ctx context.Context, req *pb.StartEx
|
|||||||
return nil, fmt.Errorf("start explore: %w", err)
|
return nil, fmt.Errorf("start explore: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreDiffTables))
|
return &pb.StartExploreResponse{}, nil
|
||||||
|
|
||||||
return &pb.StartExploreResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.FinishExploreRequest) (*pb.FinishExploreResponse, error) {
|
func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.FinishExploreRequest) (*pb.FinishExploreResponse, error) {
|
||||||
@@ -88,12 +71,12 @@ func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.Finish
|
|||||||
|
|
||||||
assetGradeIconId := s.catalog.GradeForScore(req.ExploreId, req.Score)
|
assetGradeIconId := s.catalog.GradeForScore(req.ExploreId, req.Score)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
rewardCount := int32(exploreRewardBaseCount) * explore.RewardLotteryCount
|
rewardCount := int32(exploreRewardBaseCount) * explore.RewardLotteryCount
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
existing, exists := user.ExploreScores[req.ExploreId]
|
existing, exists := user.ExploreScores[req.ExploreId]
|
||||||
if !exists || req.Score > existing.MaxScore {
|
if !exists || req.Score > existing.MaxScore {
|
||||||
user.ExploreScores[req.ExploreId] = store.ExploreScoreState{
|
user.ExploreScores[req.ExploreId] = store.ExploreScoreState{
|
||||||
@@ -123,8 +106,6 @@ func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.Finish
|
|||||||
return nil, fmt.Errorf("finish explore: %w", err)
|
return nil, fmt.Errorf("finish explore: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreFinishDiffTables))
|
|
||||||
|
|
||||||
rewards := []*pb.ExploreReward{
|
rewards := []*pb.ExploreReward{
|
||||||
{
|
{
|
||||||
PossessionType: int32(model.PossessionTypeMaterial),
|
PossessionType: int32(model.PossessionTypeMaterial),
|
||||||
@@ -137,17 +118,16 @@ func (s *ExploreServiceServer) FinishExplore(ctx context.Context, req *pb.Finish
|
|||||||
AcquireStaminaCount: exploreStaminaRecovery,
|
AcquireStaminaCount: exploreStaminaRecovery,
|
||||||
ExploreReward: rewards,
|
ExploreReward: rewards,
|
||||||
AssetGradeIconId: assetGradeIconId,
|
AssetGradeIconId: assetGradeIconId,
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ExploreServiceServer) RetireExplore(ctx context.Context, req *pb.RetireExploreRequest) (*pb.RetireExploreResponse, error) {
|
func (s *ExploreServiceServer) RetireExplore(ctx context.Context, req *pb.RetireExploreRequest) (*pb.RetireExploreResponse, error) {
|
||||||
log.Printf("[ExploreService] RetireExplore: exploreId=%d", req.ExploreId)
|
log.Printf("[ExploreService] RetireExplore: exploreId=%d", req.ExploreId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.Explore = store.ExploreState{
|
user.Explore = store.ExploreState{
|
||||||
PlayingExploreId: 0,
|
PlayingExploreId: 0,
|
||||||
IsUseExploreTicket: false,
|
IsUseExploreTicket: false,
|
||||||
@@ -159,9 +139,5 @@ func (s *ExploreServiceServer) RetireExplore(ctx context.Context, req *pb.Retire
|
|||||||
return nil, fmt.Errorf("retire explore: %w", err)
|
return nil, fmt.Errorf("retire explore: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserExplore"}))
|
return &pb.RetireExploreResponse{}, nil
|
||||||
|
|
||||||
return &pb.RetireExploreResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -23,7 +22,7 @@ func NewFriendServiceServer(users store.UserRepository, sessions store.SessionRe
|
|||||||
|
|
||||||
func (s *FriendServiceServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
|
func (s *FriendServiceServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
|
||||||
log.Printf("[FriendService] GetUser: playerId=%d", req.PlayerId)
|
log.Printf("[FriendService] GetUser: playerId=%d", req.PlayerId)
|
||||||
return &pb.GetUserResponse{DiffUserData: userdata.EmptyDiff()}, nil
|
return &pb.GetUserResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FriendServiceServer) GetFriendList(ctx context.Context, req *pb.GetFriendListRequest) (*pb.GetFriendListResponse, error) {
|
func (s *FriendServiceServer) GetFriendList(ctx context.Context, req *pb.GetFriendListRequest) (*pb.GetFriendListResponse, error) {
|
||||||
@@ -32,22 +31,19 @@ func (s *FriendServiceServer) GetFriendList(ctx context.Context, req *pb.GetFrie
|
|||||||
FriendUser: []*pb.FriendUser{},
|
FriendUser: []*pb.FriendUser{},
|
||||||
SendCheerCount: 0,
|
SendCheerCount: 0,
|
||||||
ReceivedCheerCount: 0,
|
ReceivedCheerCount: 0,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FriendServiceServer) GetFriendRequestList(ctx context.Context, req *emptypb.Empty) (*pb.GetFriendRequestListResponse, error) {
|
func (s *FriendServiceServer) GetFriendRequestList(ctx context.Context, req *emptypb.Empty) (*pb.GetFriendRequestListResponse, error) {
|
||||||
log.Printf("[FriendService] GetFriendRequestList")
|
log.Printf("[FriendService] GetFriendRequestList")
|
||||||
return &pb.GetFriendRequestListResponse{
|
return &pb.GetFriendRequestListResponse{
|
||||||
User: []*pb.User{},
|
User: []*pb.User{},
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FriendServiceServer) SearchRecommendedUsers(ctx context.Context, req *emptypb.Empty) (*pb.SearchRecommendedUsersResponse, error) {
|
func (s *FriendServiceServer) SearchRecommendedUsers(ctx context.Context, req *emptypb.Empty) (*pb.SearchRecommendedUsersResponse, error) {
|
||||||
log.Printf("[FriendService] SearchRecommendedUsers")
|
log.Printf("[FriendService] SearchRecommendedUsers")
|
||||||
return &pb.SearchRecommendedUsersResponse{
|
return &pb.SearchRecommendedUsersResponse{
|
||||||
Users: []*pb.User{},
|
Users: []*pb.User{},
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,25 +11,11 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var gachaDiffTables = []string{
|
|
||||||
"IUserGem",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserCostumeActiveSkill",
|
|
||||||
"IUserWeaponNote",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserMaterial",
|
|
||||||
}
|
|
||||||
|
|
||||||
type GachaServiceServer struct {
|
type GachaServiceServer struct {
|
||||||
pb.UnimplementedGachaServiceServer
|
pb.UnimplementedGachaServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
@@ -56,7 +42,7 @@ func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaL
|
|||||||
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
|
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
|
||||||
|
|
||||||
catalog := s.catalog
|
catalog := s.catalog
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
user, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
user, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
@@ -82,7 +68,6 @@ func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaL
|
|||||||
return &pb.GetGachaListResponse{
|
return &pb.GetGachaListResponse{
|
||||||
Gacha: gachaList,
|
Gacha: gachaList,
|
||||||
ConvertedGachaMedal: toProtoConvertedGachaMedal(user.Gacha.ConvertedGachaMedal),
|
ConvertedGachaMedal: toProtoConvertedGachaMedal(user.Gacha.ConvertedGachaMedal),
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +119,7 @@ func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaReque
|
|||||||
|
|
||||||
catalog := s.catalog
|
catalog := s.catalog
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("snapshot user: %w", err)
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||||
@@ -152,8 +137,7 @@ func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &pb.GetGachaResponse{
|
return &pb.GetGachaResponse{
|
||||||
Gacha: byId,
|
Gacha: byId,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +149,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
|
|||||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
execCount := req.ExecCount
|
execCount := req.ExecCount
|
||||||
if execCount <= 0 {
|
if execCount <= 0 {
|
||||||
execCount = 1
|
execCount = 1
|
||||||
@@ -290,17 +274,11 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
|
|||||||
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
|
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
|
||||||
nextGacha := toProtoGacha(*entry, &bs)
|
nextGacha := toProtoGacha(*entry, &bs)
|
||||||
|
|
||||||
changedStoryIds := s.handler.Granter.DrainChangedStoryWeaponIds()
|
|
||||||
diffOrder := append(gachaDiffTables, "IUserWeaponStory")
|
|
||||||
diff := userdata.BuildDiffFromTablesOrdered(userdata.ProjectTables(updatedUser, diffOrder), diffOrder)
|
|
||||||
userdata.AddWeaponStoryDiff(diff, updatedUser, changedStoryIds)
|
|
||||||
|
|
||||||
return &pb.DrawResponse{
|
return &pb.DrawResponse{
|
||||||
NextGacha: nextGacha,
|
NextGacha: nextGacha,
|
||||||
GachaResult: gachaResults,
|
GachaResult: gachaResults,
|
||||||
GachaBonus: bonuses,
|
GachaBonus: bonuses,
|
||||||
MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{},
|
MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +290,7 @@ func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBox
|
|||||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if resetErr := s.handler.HandleResetBox(user, *entry); resetErr != nil {
|
if resetErr := s.handler.HandleResetBox(user, *entry); resetErr != nil {
|
||||||
log.Printf("[GachaService] ResetBoxGacha error: %v", resetErr)
|
log.Printf("[GachaService] ResetBoxGacha error: %v", resetErr)
|
||||||
@@ -325,14 +303,13 @@ func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBox
|
|||||||
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
|
bs := updatedUser.Gacha.BannerStates[entry.GachaId]
|
||||||
|
|
||||||
return &pb.ResetBoxGachaResponse{
|
return &pb.ResetBoxGachaResponse{
|
||||||
Gacha: toProtoGacha(*entry, &bs),
|
Gacha: toProtoGacha(*entry, &bs),
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Empty) (*pb.GetRewardGachaResponse, error) {
|
func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Empty) (*pb.GetRewardGachaResponse, error) {
|
||||||
log.Printf("[GachaService] GetRewardGacha")
|
log.Printf("[GachaService] GetRewardGacha")
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("snapshot user: %w", err)
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||||
@@ -353,14 +330,13 @@ func (s *GachaServiceServer) GetRewardGacha(ctx context.Context, req *emptypb.Em
|
|||||||
Available: drawCount < maxCount,
|
Available: drawCount < maxCount,
|
||||||
TodaysCurrentDrawCount: drawCount,
|
TodaysCurrentDrawCount: drawCount,
|
||||||
DailyMaxCount: maxCount,
|
DailyMaxCount: maxCount,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawRequest) (*pb.RewardDrawResponse, error) {
|
func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawRequest) (*pb.RewardDrawResponse, error) {
|
||||||
log.Printf("[GachaService] RewardDraw: placement=%q reward=%q amount=%q", req.PlacementName, req.RewardName, req.RewardAmount)
|
log.Printf("[GachaService] RewardDraw: placement=%q reward=%q amount=%q", req.PlacementName, req.RewardName, req.RewardAmount)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
var items []gacha.DrawnItem
|
var items []gacha.DrawnItem
|
||||||
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
updatedUser, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
@@ -393,12 +369,8 @@ func (s *GachaServiceServer) RewardDraw(ctx context.Context, req *pb.RewardDrawR
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(updatedUser)
|
|
||||||
diff := userdata.BuildDiffFromTables(tables)
|
|
||||||
|
|
||||||
return &pb.RewardDrawResponse{
|
return &pb.RewardDrawResponse{
|
||||||
RewardGachaResult: results,
|
RewardGachaResult: results,
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GameplayServiceServer struct {
|
type GameplayServiceServer struct {
|
||||||
@@ -23,6 +22,5 @@ func (s *GameplayServiceServer) CheckBeforeGamePlay(ctx context.Context, req *pb
|
|||||||
return &pb.CheckBeforeGamePlayResponse{
|
return &pb.CheckBeforeGamePlayResponse{
|
||||||
IsExistUnreadPop: false,
|
IsExistUnreadPop: false,
|
||||||
MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{},
|
MenuGachaBadgeInfo: []*pb.MenuGachaBadgeInfo{},
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@@ -30,7 +29,7 @@ func NewGiftServiceServer(users store.UserRepository, sessions store.SessionRepo
|
|||||||
func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGiftRequest) (*pb.ReceiveGiftResponse, error) {
|
func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGiftRequest) (*pb.ReceiveGiftResponse, error) {
|
||||||
log.Printf("[GiftService] ReceiveGift: giftUuids=%d", len(req.UserGiftUuid))
|
log.Printf("[GiftService] ReceiveGift: giftUuids=%d", len(req.UserGiftUuid))
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
received := make([]string, 0, len(req.UserGiftUuid))
|
received := make([]string, 0, len(req.UserGiftUuid))
|
||||||
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
@@ -54,7 +53,6 @@ func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGift
|
|||||||
ReceivedGiftUuid: []string{},
|
ReceivedGiftUuid: []string{},
|
||||||
ExpiredGiftUuid: []string{},
|
ExpiredGiftUuid: []string{},
|
||||||
OverflowGiftUuid: []string{},
|
OverflowGiftUuid: []string{},
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +60,6 @@ func (s *GiftServiceServer) ReceiveGift(ctx context.Context, req *pb.ReceiveGift
|
|||||||
ReceivedGiftUuid: received,
|
ReceivedGiftUuid: received,
|
||||||
ExpiredGiftUuid: []string{},
|
ExpiredGiftUuid: []string{},
|
||||||
OverflowGiftUuid: []string{},
|
OverflowGiftUuid: []string{},
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +67,7 @@ func (s *GiftServiceServer) GetGiftList(ctx context.Context, req *pb.GetGiftList
|
|||||||
log.Printf("[GiftService] GetGiftList: rewardKinds=%v expirationType=%d ascending=%v nextCursor=%d previousCursor=%d getCount=%d",
|
log.Printf("[GiftService] GetGiftList: rewardKinds=%v expirationType=%d ascending=%v nextCursor=%d previousCursor=%d getCount=%d",
|
||||||
req.RewardKindType, req.ExpirationType, req.IsAscendingSort, req.NextCursor, req.PreviousCursor, req.GetCount)
|
req.RewardKindType, req.ExpirationType, req.IsAscendingSort, req.NextCursor, req.PreviousCursor, req.GetCount)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("snapshot user: %w", err)
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||||
@@ -101,13 +98,12 @@ func (s *GiftServiceServer) GetGiftList(ctx context.Context, req *pb.GetGiftList
|
|||||||
TotalPageCount: pageCount(len(user.Gifts.NotReceived), int(req.GetCount)),
|
TotalPageCount: pageCount(len(user.Gifts.NotReceived), int(req.GetCount)),
|
||||||
NextCursor: 0,
|
NextCursor: 0,
|
||||||
PreviousCursor: 0,
|
PreviousCursor: 0,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GiftServiceServer) GetGiftReceiveHistoryList(ctx context.Context, req *emptypb.Empty) (*pb.GetGiftReceiveHistoryListResponse, error) {
|
func (s *GiftServiceServer) GetGiftReceiveHistoryList(ctx context.Context, req *emptypb.Empty) (*pb.GetGiftReceiveHistoryListResponse, error) {
|
||||||
log.Printf("[GiftService] GetGiftReceiveHistoryList")
|
log.Printf("[GiftService] GetGiftReceiveHistoryList")
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("snapshot user: %w", err)
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||||
@@ -121,8 +117,7 @@ func (s *GiftServiceServer) GetGiftReceiveHistoryList(ctx context.Context, req *
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return &pb.GetGiftReceiveHistoryListResponse{
|
return &pb.GetGiftReceiveHistoryListResponse{
|
||||||
Gift: items,
|
Gift: items,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -27,8 +26,8 @@ func NewGimmickServiceServer(users store.UserRepository, sessions store.SessionR
|
|||||||
func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.UpdateSequenceRequest) (*pb.UpdateSequenceResponse, error) {
|
func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.UpdateSequenceRequest) (*pb.UpdateSequenceResponse, error) {
|
||||||
log.Printf("[GimmickService] UpdateSequence: scheduleId=%d sequenceId=%d",
|
log.Printf("[GimmickService] UpdateSequence: scheduleId=%d sequenceId=%d",
|
||||||
req.GimmickSequenceScheduleId, req.GimmickSequenceId)
|
req.GimmickSequenceScheduleId, req.GimmickSequenceId)
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
key := store.GimmickSequenceKey{
|
key := store.GimmickSequenceKey{
|
||||||
GimmickSequenceScheduleId: req.GimmickSequenceScheduleId,
|
GimmickSequenceScheduleId: req.GimmickSequenceScheduleId,
|
||||||
GimmickSequenceId: req.GimmickSequenceId,
|
GimmickSequenceId: req.GimmickSequenceId,
|
||||||
@@ -37,16 +36,14 @@ func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.Updat
|
|||||||
sequence.Key = key
|
sequence.Key = key
|
||||||
user.Gimmick.Sequences[key] = sequence
|
user.Gimmick.Sequences[key] = sequence
|
||||||
})
|
})
|
||||||
return &pb.UpdateSequenceResponse{
|
return &pb.UpdateSequenceResponse{}, nil
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickSequence"})),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GimmickServiceServer) UpdateGimmickProgress(ctx context.Context, req *pb.UpdateGimmickProgressRequest) (*pb.UpdateGimmickProgressResponse, error) {
|
func (s *GimmickServiceServer) UpdateGimmickProgress(ctx context.Context, req *pb.UpdateGimmickProgressRequest) (*pb.UpdateGimmickProgressResponse, error) {
|
||||||
log.Printf("[GimmickService] UpdateGimmickProgress: scheduleId=%d sequenceId=%d gimmickId=%d ornamentIndex=%d progressValueBit=%d flowType=%d",
|
log.Printf("[GimmickService] UpdateGimmickProgress: scheduleId=%d sequenceId=%d gimmickId=%d ornamentIndex=%d progressValueBit=%d flowType=%d",
|
||||||
req.GimmickSequenceScheduleId, req.GimmickSequenceId, req.GimmickId, req.GimmickOrnamentIndex, req.ProgressValueBit, req.FlowType)
|
req.GimmickSequenceScheduleId, req.GimmickSequenceId, req.GimmickId, req.GimmickOrnamentIndex, req.ProgressValueBit, req.FlowType)
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
progressKey := store.GimmickKey{
|
progressKey := store.GimmickKey{
|
||||||
GimmickSequenceScheduleId: req.GimmickSequenceScheduleId,
|
GimmickSequenceScheduleId: req.GimmickSequenceScheduleId,
|
||||||
@@ -74,18 +71,14 @@ func (s *GimmickServiceServer) UpdateGimmickProgress(ctx context.Context, req *p
|
|||||||
GimmickOrnamentReward: []*pb.GimmickReward{},
|
GimmickOrnamentReward: []*pb.GimmickReward{},
|
||||||
IsSequenceCleared: false,
|
IsSequenceCleared: false,
|
||||||
GimmickSequenceClearReward: []*pb.GimmickReward{},
|
GimmickSequenceClearReward: []*pb.GimmickReward{},
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{
|
|
||||||
"IUserGimmick",
|
|
||||||
"IUserGimmickOrnamentProgress",
|
|
||||||
})),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *emptypb.Empty) (*pb.InitSequenceScheduleResponse, error) {
|
func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *emptypb.Empty) (*pb.InitSequenceScheduleResponse, error) {
|
||||||
log.Printf("[GimmickService] InitSequenceSchedule")
|
log.Printf("[GimmickService] InitSequenceSchedule")
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
now := gametime.NowMillis()
|
now := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
added := 0
|
added := 0
|
||||||
for _, key := range s.gimmickCatalog.ActiveScheduleKeys(*user, now) {
|
for _, key := range s.gimmickCatalog.ActiveScheduleKeys(*user, now) {
|
||||||
if _, exists := user.Gimmick.Sequences[key]; !exists {
|
if _, exists := user.Gimmick.Sequences[key]; !exists {
|
||||||
@@ -97,15 +90,13 @@ func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *empt
|
|||||||
log.Printf("[GimmickService] InitSequenceSchedule: added %d sequences (total %d)", added, len(user.Gimmick.Sequences))
|
log.Printf("[GimmickService] InitSequenceSchedule: added %d sequences (total %d)", added, len(user.Gimmick.Sequences))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return &pb.InitSequenceScheduleResponse{
|
return &pb.InitSequenceScheduleResponse{}, nil
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, gimmickDiffTables)),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GimmickServiceServer) Unlock(ctx context.Context, req *pb.UnlockRequest) (*pb.UnlockResponse, error) {
|
func (s *GimmickServiceServer) Unlock(ctx context.Context, req *pb.UnlockRequest) (*pb.UnlockResponse, error) {
|
||||||
log.Printf("[GimmickService] Unlock: gimmickKeys=%d", len(req.GimmickKey))
|
log.Printf("[GimmickService] Unlock: gimmickKeys=%d", len(req.GimmickKey))
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for _, item := range req.GimmickKey {
|
for _, item := range req.GimmickKey {
|
||||||
key := store.GimmickKey{
|
key := store.GimmickKey{
|
||||||
GimmickSequenceScheduleId: item.GimmickSequenceScheduleId,
|
GimmickSequenceScheduleId: item.GimmickSequenceScheduleId,
|
||||||
@@ -118,7 +109,5 @@ func (s *GimmickServiceServer) Unlock(ctx context.Context, req *pb.UnlockRequest
|
|||||||
user.Gimmick.Unlocks[key] = unlock
|
user.Gimmick.Unlocks[key] = unlock
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return &pb.UnlockResponse{
|
return &pb.UnlockResponse{}, nil
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickUnlock"})),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ func parseListBin(data []byte) listBinIndex {
|
|||||||
return idx
|
return idx
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadListBinIndex(revision string) (listBinIndex, bool) {
|
func loadListBinIndex(baseDir, revision string) (listBinIndex, bool) {
|
||||||
listBinCacheMu.RLock()
|
listBinCacheMu.RLock()
|
||||||
idx, ok := listBinCache[revision]
|
idx, ok := listBinCache[revision]
|
||||||
listBinCacheMu.RUnlock()
|
listBinCacheMu.RUnlock()
|
||||||
@@ -230,7 +230,7 @@ func loadListBinIndex(revision string) (listBinIndex, bool) {
|
|||||||
listBinInflight[revision] = load
|
listBinInflight[revision] = load
|
||||||
listBinCacheMu.Unlock()
|
listBinCacheMu.Unlock()
|
||||||
|
|
||||||
filePath := filepath.Join("assets", "revisions", revision, "list.bin")
|
filePath := filepath.Join(baseDir, "assets", "revisions", revision, "list.bin")
|
||||||
data, err := os.ReadFile(filePath)
|
data, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listBinCacheMu.Lock()
|
listBinCacheMu.Lock()
|
||||||
@@ -250,7 +250,7 @@ func loadListBinIndex(revision string) (listBinIndex, bool) {
|
|||||||
return idx, true
|
return idx, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadInfoIndex(revision string) map[string]infoAlias {
|
func loadInfoIndex(baseDir, revision string) map[string]infoAlias {
|
||||||
infoCacheMu.RLock()
|
infoCacheMu.RLock()
|
||||||
m, ok := infoCache[revision]
|
m, ok := infoCache[revision]
|
||||||
infoCacheMu.RUnlock()
|
infoCacheMu.RUnlock()
|
||||||
@@ -272,7 +272,7 @@ func loadInfoIndex(revision string) map[string]infoAlias {
|
|||||||
infoInflight[revision] = load
|
infoInflight[revision] = load
|
||||||
infoCacheMu.Unlock()
|
infoCacheMu.Unlock()
|
||||||
|
|
||||||
filePath := filepath.Join("assets", "revisions", revision, "info.json")
|
filePath := filepath.Join(baseDir, "assets", "revisions", revision, "info.json")
|
||||||
data, err := os.ReadFile(filePath)
|
data, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
infoCacheMu.Lock()
|
infoCacheMu.Lock()
|
||||||
@@ -378,7 +378,7 @@ func hasNonASCII(s string) bool {
|
|||||||
// an en locale fallback is appended (marked IsLocaleFallback so callers can skip MD5 validation).
|
// an en locale fallback is appended (marked IsLocaleFallback so callers can skip MD5 validation).
|
||||||
// For paths with non-ASCII characters, mojibake (double-encoded) and fullwidth-to-ASCII
|
// For paths with non-ASCII characters, mojibake (double-encoded) and fullwidth-to-ASCII
|
||||||
// variants are also tried.
|
// variants are also tried.
|
||||||
func pathStrToFullPaths(revision, assetType, pathStr string) []pathCandidate {
|
func pathStrToFullPaths(baseDir, revision, assetType, pathStr string) []pathCandidate {
|
||||||
fsPath := strings.ReplaceAll(pathStr, ")", "/")
|
fsPath := strings.ReplaceAll(pathStr, ")", "/")
|
||||||
if strings.Contains(fsPath, "..") || filepath.IsAbs(fsPath) || strings.HasPrefix(fsPath, "/") {
|
if strings.Contains(fsPath, "..") || filepath.IsAbs(fsPath) || strings.HasPrefix(fsPath, "/") {
|
||||||
return nil
|
return nil
|
||||||
@@ -402,7 +402,7 @@ func pathStrToFullPaths(revision, assetType, pathStr string) []pathCandidate {
|
|||||||
if strings.Contains(pathStr, ")ko)") {
|
if strings.Contains(pathStr, ")ko)") {
|
||||||
entries = append(entries, tagged{strings.ReplaceAll(pathStr, ")ko)", ")en)"), true})
|
entries = append(entries, tagged{strings.ReplaceAll(pathStr, ")ko)", ")en)"), true})
|
||||||
}
|
}
|
||||||
base := filepath.Join("assets", "revisions", revision)
|
base := filepath.Join(baseDir, "assets", "revisions", revision)
|
||||||
var out []pathCandidate
|
var out []pathCandidate
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
@@ -434,13 +434,13 @@ func appendUniqueCandidate(candidates []assetCandidate, seen map[string]bool, ca
|
|||||||
return append(candidates, candidate)
|
return append(candidates, candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func duplicateCandidatePath(candidate assetCandidate, assetType, targetRevision, targetBaseName string) string {
|
func duplicateCandidatePath(baseDir string, candidate assetCandidate, assetType, targetRevision, targetBaseName string) string {
|
||||||
root := filepath.Join("assets", "revisions", candidate.Revision, assetType)
|
root := filepath.Join(baseDir, "assets", "revisions", candidate.Revision, assetType)
|
||||||
rel, err := filepath.Rel(root, candidate.Path)
|
rel, err := filepath.Rel(root, candidate.Path)
|
||||||
if err != nil || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
|
if err != nil || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return filepath.Join("assets", "revisions", targetRevision, assetType, filepath.Dir(rel), targetBaseName)
|
return filepath.Join(baseDir, "assets", "revisions", targetRevision, assetType, filepath.Dir(rel), targetBaseName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// objectIdToFilePathCandidates returns candidate file paths for the object: list.bin path, locale fallbacks
|
// objectIdToFilePathCandidates returns candidate file paths for the object: list.bin path, locale fallbacks
|
||||||
@@ -448,8 +448,8 @@ func duplicateCandidatePath(candidate assetCandidate, assetType, targetRevision,
|
|||||||
// The original locale path is tried first (with MD5 validation); locale fallbacks are tried after
|
// The original locale path is tried first (with MD5 validation); locale fallbacks are tried after
|
||||||
// (without MD5 validation, since the hash in list.bin refers to the original locale's content).
|
// (without MD5 validation, since the hash in list.bin refers to the original locale's content).
|
||||||
// Callers should try each path until one exists on disk.
|
// Callers should try each path until one exists on disk.
|
||||||
func objectIdToFilePathCandidates(revision, assetType, objectId string) (candidates []assetCandidate, size int64, ok bool) {
|
func objectIdToFilePathCandidates(baseDir, revision, assetType, objectId string) (candidates []assetCandidate, size int64, ok bool) {
|
||||||
idx, ok := loadListBinIndex(revision)
|
idx, ok := loadListBinIndex(baseDir, revision)
|
||||||
if !ok || idx == nil {
|
if !ok || idx == nil {
|
||||||
return nil, 0, false
|
return nil, 0, false
|
||||||
}
|
}
|
||||||
@@ -457,7 +457,7 @@ func objectIdToFilePathCandidates(revision, assetType, objectId string) (candida
|
|||||||
if !ok || entry.Path == "" {
|
if !ok || entry.Path == "" {
|
||||||
return nil, 0, false
|
return nil, 0, false
|
||||||
}
|
}
|
||||||
paths := pathStrToFullPaths(revision, assetType, entry.Path)
|
paths := pathStrToFullPaths(baseDir, revision, assetType, entry.Path)
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
return nil, 0, false
|
return nil, 0, false
|
||||||
}
|
}
|
||||||
@@ -474,15 +474,14 @@ func objectIdToFilePathCandidates(revision, assetType, objectId string) (candida
|
|||||||
ExpectedMD5: md5,
|
ExpectedMD5: md5,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Add paths from info.json: when requested file is a "from-name" (duplicate not included), serve "to-name" instead.
|
infoIndex := loadInfoIndex(baseDir, revision)
|
||||||
infoIndex := loadInfoIndex(revision)
|
|
||||||
if len(infoIndex) > 0 {
|
if len(infoIndex) > 0 {
|
||||||
for _, c := range candidates {
|
for _, c := range candidates {
|
||||||
alias, ok := infoIndex[filepath.Base(c.Path)]
|
alias, ok := infoIndex[filepath.Base(c.Path)]
|
||||||
if !ok || alias.ToName == "" {
|
if !ok || alias.ToName == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
alt := duplicateCandidatePath(c, assetType, alias.ToRevision, alias.ToName)
|
alt := duplicateCandidatePath(baseDir, c, assetType, alias.ToRevision, alias.ToName)
|
||||||
if alt == "" {
|
if alt == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -29,9 +28,9 @@ func NewLoginBonusServiceServer(users store.UserRepository, sessions store.Sessi
|
|||||||
|
|
||||||
func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb.Empty) (*pb.ReceiveStampResponse, error) {
|
func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb.Empty) (*pb.ReceiveStampResponse, error) {
|
||||||
log.Printf("[LoginBonusService] ReceiveStamp")
|
log.Printf("[LoginBonusService] ReceiveStamp")
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
now := gametime.NowMillis()
|
now := gametime.NowMillis()
|
||||||
nextStamp := user.LoginBonus.CurrentStampNumber + 1
|
nextStamp := user.LoginBonus.CurrentStampNumber + 1
|
||||||
|
|
||||||
@@ -64,9 +63,5 @@ func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb
|
|||||||
user.LoginBonus.LatestVersion = now
|
user.LoginBonus.LatestVersion = now
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
|
return &pb.ReceiveStampResponse{}, nil
|
||||||
[]string{"IUserLoginBonus"},
|
|
||||||
))
|
|
||||||
setCommonResponseTrailers(ctx, diff, false)
|
|
||||||
return &pb.ReceiveStampResponse{DiffUserData: diff}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,8 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var materialDiffTables = []string{
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
}
|
|
||||||
|
|
||||||
type MaterialServiceServer struct {
|
type MaterialServiceServer struct {
|
||||||
pb.UnimplementedMaterialServiceServer
|
pb.UnimplementedMaterialServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
@@ -31,13 +25,9 @@ func NewMaterialServiceServer(users store.UserRepository, sessions store.Session
|
|||||||
func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRequest) (*pb.MaterialSellResponse, error) {
|
func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRequest) (*pb.MaterialSellResponse, error) {
|
||||||
log.Printf("[MaterialService] Sell: %d item(s)", len(req.MaterialPossession))
|
log.Printf("[MaterialService] Sell: %d item(s)", len(req.MaterialPossession))
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
|
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
totalGold := int32(0)
|
totalGold := int32(0)
|
||||||
for _, item := range req.MaterialPossession {
|
for _, item := range req.MaterialPossession {
|
||||||
mat, ok := s.catalog.All[item.MaterialId]
|
mat, ok := s.catalog.All[item.MaterialId]
|
||||||
@@ -71,10 +61,5 @@ func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRe
|
|||||||
return nil, fmt.Errorf("material sell: %w", err)
|
return nil, fmt.Errorf("material sell: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.ProjectTables(snapshot, materialDiffTables)
|
return &pb.MaterialSellResponse{}, nil
|
||||||
diff := tracker.Apply(snapshot, tables)
|
|
||||||
|
|
||||||
return &pb.MaterialSellResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MissionServiceServer struct {
|
type MissionServiceServer struct {
|
||||||
@@ -23,15 +22,11 @@ func NewMissionServiceServer(users store.UserRepository, sessions store.SessionR
|
|||||||
func (s *MissionServiceServer) UpdateMissionProgress(ctx context.Context, req *pb.UpdateMissionProgressRequest) (*pb.UpdateMissionProgressResponse, error) {
|
func (s *MissionServiceServer) UpdateMissionProgress(ctx context.Context, req *pb.UpdateMissionProgressRequest) (*pb.UpdateMissionProgressResponse, error) {
|
||||||
log.Printf("[MissionService] UpdateMissionProgress: cage=%v pictureBook=%v", req.CageMeasurableValues, req.PictureBookMeasurableValues)
|
log.Printf("[MissionService] UpdateMissionProgress: cage=%v pictureBook=%v", req.CageMeasurableValues, req.PictureBookMeasurableValues)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
snapshot, err := s.users.LoadUser(userId)
|
_, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("snapshot user: %w", err)
|
return nil, fmt.Errorf("snapshot user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMission"}))
|
return &pb.UpdateMissionProgressResponse{}, nil
|
||||||
|
|
||||||
return &pb.UpdateMissionProgressResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type MovieServiceServer struct {
|
type MovieServiceServer struct {
|
||||||
@@ -24,10 +23,10 @@ func NewMovieServiceServer(users store.UserRepository, sessions store.SessionRep
|
|||||||
func (s *MovieServiceServer) SaveViewedMovie(ctx context.Context, req *pb.SaveViewedMovieRequest) (*pb.SaveViewedMovieResponse, error) {
|
func (s *MovieServiceServer) SaveViewedMovie(ctx context.Context, req *pb.SaveViewedMovieRequest) (*pb.SaveViewedMovieResponse, error) {
|
||||||
log.Printf("[MovieService] SaveViewedMovie: movieIds=%v", req.MovieId)
|
log.Printf("[MovieService] SaveViewedMovie: movieIds=%v", req.MovieId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
now := gametime.NowMillis()
|
now := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for _, mid := range req.MovieId {
|
for _, mid := range req.MovieId {
|
||||||
user.ViewedMovies[mid] = now
|
user.ViewedMovies[mid] = now
|
||||||
}
|
}
|
||||||
@@ -36,9 +35,5 @@ func (s *MovieServiceServer) SaveViewedMovie(ctx context.Context, req *pb.SaveVi
|
|||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMovie"}))
|
return &pb.SaveViewedMovieResponse{}, nil
|
||||||
|
|
||||||
return &pb.SaveViewedMovieResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type NaviCutInServiceServer struct {
|
type NaviCutInServiceServer struct {
|
||||||
@@ -23,17 +22,13 @@ func NewNaviCutInServiceServer(users store.UserRepository, sessions store.Sessio
|
|||||||
func (s *NaviCutInServiceServer) RegisterPlayed(ctx context.Context, req *pb.RegisterPlayedRequest) (*pb.RegisterPlayedResponse, error) {
|
func (s *NaviCutInServiceServer) RegisterPlayed(ctx context.Context, req *pb.RegisterPlayedRequest) (*pb.RegisterPlayedResponse, error) {
|
||||||
log.Printf("[NaviCutInService] RegisterPlayed: naviCutId=%d", req.NaviCutId)
|
log.Printf("[NaviCutInService] RegisterPlayed: naviCutId=%d", req.NaviCutId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.NaviCutInPlayed[req.NaviCutId] = true
|
user.NaviCutInPlayed[req.NaviCutId] = true
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserNaviCutIn"}))
|
return &pb.RegisterPlayedResponse{}, nil
|
||||||
|
|
||||||
return &pb.RegisterPlayedResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -23,20 +22,18 @@ func NewNotificationServiceServer(users store.UserRepository, sessions store.Ses
|
|||||||
|
|
||||||
func (s *NotificationServiceServer) GetHeaderNotification(ctx context.Context, req *emptypb.Empty) (*pb.GetHeaderNotificationResponse, error) {
|
func (s *NotificationServiceServer) GetHeaderNotification(ctx context.Context, req *emptypb.Empty) (*pb.GetHeaderNotificationResponse, error) {
|
||||||
log.Printf("[NotificationService] GetHeaderNotification")
|
log.Printf("[NotificationService] GetHeaderNotification")
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.LoadUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &pb.GetHeaderNotificationResponse{
|
return &pb.GetHeaderNotificationResponse{
|
||||||
GiftNotReceiveCount: 0,
|
GiftNotReceiveCount: 0,
|
||||||
FriendRequestReceiveCount: 0,
|
FriendRequestReceiveCount: 0,
|
||||||
IsExistUnreadInformation: false,
|
IsExistUnreadInformation: false,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return &pb.GetHeaderNotificationResponse{
|
return &pb.GetHeaderNotificationResponse{
|
||||||
GiftNotReceiveCount: int32(len(user.Gifts.NotReceived)),
|
GiftNotReceiveCount: int32(len(user.Gifts.NotReceived)),
|
||||||
FriendRequestReceiveCount: user.Notifications.FriendRequestReceiveCount,
|
FriendRequestReceiveCount: user.Notifications.FriendRequestReceiveCount,
|
||||||
IsExistUnreadInformation: user.Notifications.IsExistUnreadInformation,
|
IsExistUnreadInformation: user.Notifications.IsExistUnreadInformation,
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const resourcesURLOriginal = "https://resources.app.nierreincarnation.com"
|
|||||||
type OctoHTTPServer struct {
|
type OctoHTTPServer struct {
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
ResourcesBaseURL string // if non-empty and exactly 43 chars, list.bin is rewritten to use this base for asset URLs
|
ResourcesBaseURL string // if non-empty and exactly 43 chars, list.bin is rewritten to use this base for asset URLs
|
||||||
|
BaseDir string // root directory containing the assets/ tree; empty means current directory
|
||||||
revisions *revisionTracker
|
revisions *revisionTracker
|
||||||
resolver *assetResolver
|
resolver *assetResolver
|
||||||
}
|
}
|
||||||
@@ -124,12 +125,13 @@ func fileMD5Hex(path string, info os.FileInfo) (string, error) {
|
|||||||
return sum, nil
|
return sum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOctoHTTPServer(resourcesBaseURL string) *OctoHTTPServer {
|
func NewOctoHTTPServer(resourcesBaseURL, baseDir string) *OctoHTTPServer {
|
||||||
s := &OctoHTTPServer{
|
s := &OctoHTTPServer{
|
||||||
mux: http.NewServeMux(),
|
mux: http.NewServeMux(),
|
||||||
ResourcesBaseURL: resourcesBaseURL,
|
ResourcesBaseURL: resourcesBaseURL,
|
||||||
|
BaseDir: baseDir,
|
||||||
revisions: newRevisionTracker(),
|
revisions: newRevisionTracker(),
|
||||||
resolver: newAssetResolver(),
|
resolver: newAssetResolver(baseDir),
|
||||||
}
|
}
|
||||||
s.resolver.Prewarm("0")
|
s.resolver.Prewarm("0")
|
||||||
s.mux.HandleFunc("/", s.handleAll)
|
s.mux.HandleFunc("/", s.handleAll)
|
||||||
@@ -226,7 +228,7 @@ func (s *OctoHTTPServer) handleOctoV2(w http.ResponseWriter, r *http.Request, pa
|
|||||||
requestedRevision := parts[len(parts)-1]
|
requestedRevision := parts[len(parts)-1]
|
||||||
if requestedRevision != "" {
|
if requestedRevision != "" {
|
||||||
revision := "0"
|
revision := "0"
|
||||||
filePath := "assets/revisions/0/list.bin"
|
filePath := filepath.Join(s.BaseDir, "assets", "revisions", "0", "list.bin")
|
||||||
if requestedRevision != revision {
|
if requestedRevision != revision {
|
||||||
log.Printf("[OctoV2] Resource list request revision=%s canonicalized to revision=%s", requestedRevision, revision)
|
log.Printf("[OctoV2] Resource list request revision=%s canonicalized to revision=%s", requestedRevision, revision)
|
||||||
}
|
}
|
||||||
@@ -266,7 +268,7 @@ func (s *OctoHTTPServer) serveOctoV1List(w http.ResponseWriter, r *http.Request,
|
|||||||
requestedRevision = parts[len(parts)-1]
|
requestedRevision = parts[len(parts)-1]
|
||||||
}
|
}
|
||||||
revision := "0"
|
revision := "0"
|
||||||
filePath := "assets/revisions/0/list.bin"
|
filePath := filepath.Join(s.BaseDir, "assets", "revisions", "0", "list.bin")
|
||||||
if requestedRevision != revision {
|
if requestedRevision != revision {
|
||||||
log.Printf("[OctoV1] list request revision=%s canonicalized to revision=%s", requestedRevision, revision)
|
log.Printf("[OctoV1] list request revision=%s canonicalized to revision=%s", requestedRevision, revision)
|
||||||
}
|
}
|
||||||
@@ -316,11 +318,11 @@ func (s *OctoHTTPServer) serveUnsoAsset(w http.ResponseWriter, r *http.Request,
|
|||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
baseDir := filepath.Join("assets", "revisions")
|
revDir := filepath.Join(s.BaseDir, "assets", "revisions")
|
||||||
var triedPaths []string
|
var triedPaths []string
|
||||||
var md5Mismatches []string
|
var md5Mismatches []string
|
||||||
for _, candidate := range resolution.Candidates {
|
for _, candidate := range resolution.Candidates {
|
||||||
rel, err := filepath.Rel(baseDir, candidate.Path)
|
rel, err := filepath.Rel(revDir, candidate.Path)
|
||||||
if err != nil || strings.Contains(rel, "..") || filepath.IsAbs(rel) {
|
if err != nil || strings.Contains(rel, "..") || filepath.IsAbs(rel) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -409,9 +411,9 @@ func (s *OctoHTTPServer) serveDatabaseBinE(w http.ResponseWriter, r *http.Reques
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filePath := "assets/release/database.bin.e"
|
filePath := filepath.Join(s.BaseDir, "assets", "release", "database.bin.e")
|
||||||
if version != "" {
|
if version != "" {
|
||||||
vPath := "assets/release/" + version + ".bin.e"
|
vPath := filepath.Join(s.BaseDir, "assets", "release", version+".bin.e")
|
||||||
if _, err := os.Stat(vPath); err == nil {
|
if _, err := os.Stat(vPath); err == nil {
|
||||||
filePath = vPath
|
filePath = vPath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OmikujiServiceServer struct {
|
type OmikujiServiceServer struct {
|
||||||
@@ -26,21 +25,18 @@ func NewOmikujiServiceServer(users store.UserRepository, sessions store.SessionR
|
|||||||
func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiDrawRequest) (*pb.OmikujiDrawResponse, error) {
|
func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiDrawRequest) (*pb.OmikujiDrawResponse, error) {
|
||||||
log.Printf("[OmikujiService] OmikujiDraw: omikujiId=%d", req.OmikujiId)
|
log.Printf("[OmikujiService] OmikujiDraw: omikujiId=%d", req.OmikujiId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
now := gametime.NowMillis()
|
now := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.DrawnOmikuji[req.OmikujiId] = now
|
user.DrawnOmikuji[req.OmikujiId] = now
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserOmikuji"}))
|
|
||||||
|
|
||||||
return &pb.OmikujiDrawResponse{
|
return &pb.OmikujiDrawResponse{
|
||||||
OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId),
|
OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId),
|
||||||
OmikujiItem: []*pb.OmikujiItem{},
|
OmikujiItem: []*pb.OmikujiItem{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,10 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const partsMaxLevel = int32(15)
|
const partsMaxLevel = int32(15)
|
||||||
|
|
||||||
var partsDiffTables = []string{
|
|
||||||
"IUserParts",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
}
|
|
||||||
|
|
||||||
type PartsServiceServer struct {
|
type PartsServiceServer struct {
|
||||||
pb.UnimplementedPartsServiceServer
|
pb.UnimplementedPartsServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
@@ -35,13 +29,9 @@ func NewPartsServiceServer(users store.UserRepository, sessions store.SessionRep
|
|||||||
func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) {
|
func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest) (*pb.PartsSellResponse, error) {
|
||||||
log.Printf("[PartsService] Sell: %d part(s)", len(req.UserPartsUuid))
|
log.Printf("[PartsService] Sell: %d part(s)", len(req.UserPartsUuid))
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.LoadUser(userId)
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
tracker := userdata.NewDeleteTracker().
|
|
||||||
Track("IUserParts", oldUser, userdata.SortedPartsRecords, []string{"userId", "userPartsUuid"})
|
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
|
||||||
totalGold := int32(0)
|
totalGold := int32(0)
|
||||||
for _, uuid := range req.UserPartsUuid {
|
for _, uuid := range req.UserPartsUuid {
|
||||||
part, ok := user.Parts[uuid]
|
part, ok := user.Parts[uuid]
|
||||||
@@ -81,23 +71,18 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
|
|||||||
return nil, fmt.Errorf("parts sell: %w", err)
|
return nil, fmt.Errorf("parts sell: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.ProjectTables(snapshot, partsDiffTables)
|
return &pb.PartsSellResponse{}, nil
|
||||||
diff := tracker.Apply(snapshot, tables)
|
|
||||||
|
|
||||||
return &pb.PartsSellResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRequest) (*pb.PartsEnhanceResponse, error) {
|
func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRequest) (*pb.PartsEnhanceResponse, error) {
|
||||||
log.Printf("[PartsService] Enhance: uuid=%s", req.UserPartsUuid)
|
log.Printf("[PartsService] Enhance: uuid=%s", req.UserPartsUuid)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
isSuccess := false
|
isSuccess := false
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
part, ok := user.Parts[req.UserPartsUuid]
|
part, ok := user.Parts[req.UserPartsUuid]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[PartsService] Enhance: part uuid=%s not found", req.UserPartsUuid)
|
log.Printf("[PartsService] Enhance: part uuid=%s not found", req.UserPartsUuid)
|
||||||
@@ -158,11 +143,8 @@ func (s *PartsServiceServer) Enhance(ctx context.Context, req *pb.PartsEnhanceRe
|
|||||||
return nil, fmt.Errorf("parts enhance: %w", err)
|
return nil, fmt.Errorf("parts enhance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, partsDiffTables))
|
|
||||||
|
|
||||||
return &pb.PartsEnhanceResponse{
|
return &pb.PartsEnhanceResponse{
|
||||||
IsSuccess: isSuccess,
|
IsSuccess: isSuccess,
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,10 +152,10 @@ func (s *PartsServiceServer) ReplacePreset(ctx context.Context, req *pb.PartsRep
|
|||||||
log.Printf("[PartsService] ReplacePreset: preset=%d uuids=[%s, %s, %s]",
|
log.Printf("[PartsService] ReplacePreset: preset=%d uuids=[%s, %s, %s]",
|
||||||
req.UserPartsPresetNumber, req.UserPartsUuid01, req.UserPartsUuid02, req.UserPartsUuid03)
|
req.UserPartsPresetNumber, req.UserPartsUuid01, req.UserPartsUuid02, req.UserPartsUuid03)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
preset := user.PartsPresets[req.UserPartsPresetNumber]
|
preset := user.PartsPresets[req.UserPartsPresetNumber]
|
||||||
preset.UserPartsPresetNumber = req.UserPartsPresetNumber
|
preset.UserPartsPresetNumber = req.UserPartsPresetNumber
|
||||||
preset.UserPartsUuid01 = req.UserPartsUuid01
|
preset.UserPartsUuid01 = req.UserPartsUuid01
|
||||||
@@ -186,9 +168,5 @@ func (s *PartsServiceServer) ReplacePreset(ctx context.Context, req *pb.PartsRep
|
|||||||
return nil, fmt.Errorf("parts replace preset: %w", err)
|
return nil, fmt.Errorf("parts replace preset: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserPartsPreset"}))
|
return &pb.PartsReplacePresetResponse{}, nil
|
||||||
|
|
||||||
return &pb.PartsReplacePresetResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PortalCageServiceServer struct {
|
type PortalCageServiceServer struct {
|
||||||
@@ -23,17 +22,11 @@ func NewPortalCageServiceServer(users store.UserRepository, sessions store.Sessi
|
|||||||
func (s *PortalCageServiceServer) UpdatePortalCageSceneProgress(ctx context.Context, req *pb.UpdatePortalCageSceneProgressRequest) (*pb.UpdatePortalCageSceneProgressResponse, error) {
|
func (s *PortalCageServiceServer) UpdatePortalCageSceneProgress(ctx context.Context, req *pb.UpdatePortalCageSceneProgressRequest) (*pb.UpdatePortalCageSceneProgressResponse, error) {
|
||||||
log.Printf("[PortalCageService] UpdatePortalCageSceneProgress: portalCageSceneId=%d", req.PortalCageSceneId)
|
log.Printf("[PortalCageService] UpdatePortalCageSceneProgress: portalCageSceneId=%d", req.PortalCageSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
now := gametime.NowMillis()
|
now := gametime.NowMillis()
|
||||||
user.PortalCageStatus.IsCurrentProgress = true
|
user.PortalCageStatus.IsCurrentProgress = true
|
||||||
user.PortalCageStatus.LatestVersion = now
|
user.PortalCageStatus.LatestVersion = now
|
||||||
})
|
})
|
||||||
|
return &pb.UpdatePortalCageSceneProgressResponse{}, nil
|
||||||
tables := userdata.ProjectTables(user,
|
|
||||||
[]string{"IUserPortalCageStatus"},
|
|
||||||
)
|
|
||||||
return &pb.UpdatePortalCageSceneProgressResponse{
|
|
||||||
DiffUserData: userdata.BuildDiffFromTablesOrdered(tables, []string{"IUserPortalCageStatus"}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -32,25 +31,11 @@ func NewBigHuntServiceServer(
|
|||||||
return &BigHuntServiceServer{users: users, sessions: sessions, catalog: catalog, engine: engine}
|
return &BigHuntServiceServer{users: users, sessions: sessions, catalog: catalog, engine: engine}
|
||||||
}
|
}
|
||||||
|
|
||||||
var bigHuntDiffTables = []string{
|
|
||||||
"IUserBigHuntProgressStatus",
|
|
||||||
"IUserBigHuntMaxScore",
|
|
||||||
"IUserBigHuntStatus",
|
|
||||||
"IUserBigHuntScheduleMaxScore",
|
|
||||||
"IUserBigHuntWeeklyMaxScore",
|
|
||||||
"IUserBigHuntWeeklyStatus",
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBigHuntDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
|
||||||
tables := userdata.ProjectTables(user, tableNames)
|
|
||||||
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.StartBigHuntQuestRequest) (*pb.StartBigHuntQuestResponse, error) {
|
func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.StartBigHuntQuestRequest) (*pb.StartBigHuntQuestResponse, error) {
|
||||||
log.Printf("[BigHuntService] StartBigHuntQuest: bossQuestId=%d questId=%d deckNumber=%d isDryRun=%v",
|
log.Printf("[BigHuntService] StartBigHuntQuest: bossQuestId=%d questId=%d deckNumber=%d isDryRun=%v",
|
||||||
req.BigHuntBossQuestId, req.BigHuntQuestId, req.UserDeckNumber, req.IsDryRun)
|
req.BigHuntBossQuestId, req.BigHuntQuestId, req.UserDeckNumber, req.IsDryRun)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
bhQuest, ok := s.catalog.QuestById[req.BigHuntQuestId]
|
bhQuest, ok := s.catalog.QuestById[req.BigHuntQuestId]
|
||||||
@@ -58,7 +43,7 @@ func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.St
|
|||||||
log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId)
|
log.Printf("[BigHuntService] StartBigHuntQuest: unknown bigHuntQuestId=%d", req.BigHuntQuestId)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if ok {
|
if ok {
|
||||||
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis)
|
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, req.UserDeckNumber, nowMillis)
|
||||||
}
|
}
|
||||||
@@ -80,31 +65,27 @@ func (s *BigHuntServiceServer) StartBigHuntQuest(ctx context.Context, req *pb.St
|
|||||||
user.BigHuntStatuses[req.BigHuntBossQuestId] = st
|
user.BigHuntStatuses[req.BigHuntBossQuestId] = st
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.StartBigHuntQuestResponse{
|
return &pb.StartBigHuntQuestResponse{}, nil
|
||||||
DiffUserData: buildBigHuntDiff(user, append([]string{"IUserQuest"}, bigHuntDiffTables...)),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BigHuntServiceServer) UpdateBigHuntQuestSceneProgress(ctx context.Context, req *pb.UpdateBigHuntQuestSceneProgressRequest) (*pb.UpdateBigHuntQuestSceneProgressResponse, error) {
|
func (s *BigHuntServiceServer) UpdateBigHuntQuestSceneProgress(ctx context.Context, req *pb.UpdateBigHuntQuestSceneProgressRequest) (*pb.UpdateBigHuntQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[BigHuntService] UpdateBigHuntQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[BigHuntService] UpdateBigHuntQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.BigHuntProgress.CurrentQuestSceneId = req.QuestSceneId
|
user.BigHuntProgress.CurrentQuestSceneId = req.QuestSceneId
|
||||||
user.BigHuntProgress.LatestVersion = nowMillis
|
user.BigHuntProgress.LatestVersion = nowMillis
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.UpdateBigHuntQuestSceneProgressResponse{
|
return &pb.UpdateBigHuntQuestSceneProgressResponse{}, nil
|
||||||
DiffUserData: buildBigHuntDiff(user, []string{"IUserBigHuntProgressStatus"}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.FinishBigHuntQuestRequest) (*pb.FinishBigHuntQuestResponse, error) {
|
func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.FinishBigHuntQuestRequest) (*pb.FinishBigHuntQuestResponse, error) {
|
||||||
log.Printf("[BigHuntService] FinishBigHuntQuest: bossQuestId=%d questId=%d isRetired=%v",
|
log.Printf("[BigHuntService] FinishBigHuntQuest: bossQuestId=%d questId=%d isRetired=%v",
|
||||||
req.BigHuntBossQuestId, req.BigHuntQuestId, req.IsRetired)
|
req.BigHuntBossQuestId, req.BigHuntQuestId, req.IsRetired)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
||||||
@@ -114,7 +95,7 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
|||||||
var scoreInfo *pb.BigHuntScoreInfo
|
var scoreInfo *pb.BigHuntScoreInfo
|
||||||
var scoreRewards []*pb.BigHuntReward
|
var scoreRewards []*pb.BigHuntReward
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleBigHuntQuestFinish(user, bhQuest.QuestId, req.IsRetired, false, nowMillis)
|
s.engine.HandleBigHuntQuestFinish(user, bhQuest.QuestId, req.IsRetired, false, nowMillis)
|
||||||
|
|
||||||
if req.IsRetired || user.BigHuntProgress.IsDryRun {
|
if req.IsRetired || user.BigHuntProgress.IsDryRun {
|
||||||
@@ -229,18 +210,13 @@ func (s *BigHuntServiceServer) FinishBigHuntQuest(ctx context.Context, req *pb.F
|
|||||||
BattleReport: &pb.BigHuntBattleReport{
|
BattleReport: &pb.BigHuntBattleReport{
|
||||||
BattleReportWave: []*pb.BigHuntBattleReportWave{},
|
BattleReportWave: []*pb.BigHuntBattleReportWave{},
|
||||||
},
|
},
|
||||||
DiffUserData: buildBigHuntDiff(user, append([]string{
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
}, bigHuntDiffTables...)),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.RestartBigHuntQuestRequest) (*pb.RestartBigHuntQuestResponse, error) {
|
func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.RestartBigHuntQuestRequest) (*pb.RestartBigHuntQuestResponse, error) {
|
||||||
log.Printf("[BigHuntService] RestartBigHuntQuest: bossQuestId=%d questId=%d", req.BigHuntBossQuestId, req.BigHuntQuestId)
|
log.Printf("[BigHuntService] RestartBigHuntQuest: bossQuestId=%d questId=%d", req.BigHuntBossQuestId, req.BigHuntQuestId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
bhQuest := s.catalog.QuestById[req.BigHuntQuestId]
|
||||||
@@ -248,7 +224,7 @@ func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.
|
|||||||
var battleBinary []byte
|
var battleBinary []byte
|
||||||
var deckNumber int32
|
var deckNumber int32
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, user.BigHuntDeckNumber, nowMillis)
|
s.engine.HandleBigHuntQuestStart(user, bhQuest.QuestId, user.BigHuntDeckNumber, nowMillis)
|
||||||
|
|
||||||
user.BigHuntProgress.CurrentQuestSceneId = 0
|
user.BigHuntProgress.CurrentQuestSceneId = 0
|
||||||
@@ -267,17 +243,16 @@ func (s *BigHuntServiceServer) RestartBigHuntQuest(ctx context.Context, req *pb.
|
|||||||
return &pb.RestartBigHuntQuestResponse{
|
return &pb.RestartBigHuntQuestResponse{
|
||||||
BattleBinary: battleBinary,
|
BattleBinary: battleBinary,
|
||||||
DeckNumber: deckNumber,
|
DeckNumber: deckNumber,
|
||||||
DiffUserData: buildBigHuntDiff(user, append([]string{"IUserQuest"}, bigHuntDiffTables...)),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BigHuntServiceServer) SkipBigHuntQuest(ctx context.Context, req *pb.SkipBigHuntQuestRequest) (*pb.SkipBigHuntQuestResponse, error) {
|
func (s *BigHuntServiceServer) SkipBigHuntQuest(ctx context.Context, req *pb.SkipBigHuntQuestRequest) (*pb.SkipBigHuntQuestResponse, error) {
|
||||||
log.Printf("[BigHuntService] SkipBigHuntQuest: bossQuestId=%d skipCount=%d", req.BigHuntBossQuestId, req.SkipCount)
|
log.Printf("[BigHuntService] SkipBigHuntQuest: bossQuestId=%d skipCount=%d", req.BigHuntBossQuestId, req.SkipCount)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
st := user.BigHuntStatuses[req.BigHuntBossQuestId]
|
st := user.BigHuntStatuses[req.BigHuntBossQuestId]
|
||||||
st.DailyChallengeCount += req.SkipCount
|
st.DailyChallengeCount += req.SkipCount
|
||||||
st.LatestChallengeDatetime = nowMillis
|
st.LatestChallengeDatetime = nowMillis
|
||||||
@@ -286,15 +261,14 @@ func (s *BigHuntServiceServer) SkipBigHuntQuest(ctx context.Context, req *pb.Ski
|
|||||||
})
|
})
|
||||||
|
|
||||||
return &pb.SkipBigHuntQuestResponse{
|
return &pb.SkipBigHuntQuestResponse{
|
||||||
ScoreReward: []*pb.BigHuntReward{},
|
ScoreReward: []*pb.BigHuntReward{},
|
||||||
DiffUserData: buildBigHuntDiff(user, bigHuntDiffTables),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *pb.SaveBigHuntBattleInfoRequest) (*pb.SaveBigHuntBattleInfoResponse, error) {
|
func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *pb.SaveBigHuntBattleInfoRequest) (*pb.SaveBigHuntBattleInfoResponse, error) {
|
||||||
log.Printf("[BigHuntService] SaveBigHuntBattleInfo: elapsedFrames=%d", req.ElapsedFrameCount)
|
log.Printf("[BigHuntService] SaveBigHuntBattleInfo: elapsedFrames=%d", req.ElapsedFrameCount)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
var totalDamage int64
|
var totalDamage int64
|
||||||
@@ -306,7 +280,7 @@ func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.BigHuntBattleBinary = req.BattleBinary
|
user.BigHuntBattleBinary = req.BattleBinary
|
||||||
|
|
||||||
if req.BigHuntBattleDetail != nil {
|
if req.BigHuntBattleDetail != nil {
|
||||||
@@ -322,15 +296,13 @@ func (s *BigHuntServiceServer) SaveBigHuntBattleInfo(ctx context.Context, req *p
|
|||||||
user.BigHuntProgress.LatestVersion = nowMillis
|
user.BigHuntProgress.LatestVersion = nowMillis
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.SaveBigHuntBattleInfoResponse{
|
return &pb.SaveBigHuntBattleInfoResponse{}, nil
|
||||||
DiffUserData: buildBigHuntDiff(user, []string{"IUserBigHuntProgressStatus"}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb.Empty) (*pb.GetBigHuntTopDataResponse, error) {
|
func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb.Empty) (*pb.GetBigHuntTopDataResponse, error) {
|
||||||
log.Printf("[BigHuntService] GetBigHuntTopData")
|
log.Printf("[BigHuntService] GetBigHuntTopData")
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.LoadUser(userId)
|
user, _ := s.users.LoadUser(userId)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
@@ -368,7 +340,6 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
|||||||
WeeklyScoreReward: weeklyRewards,
|
WeeklyScoreReward: weeklyRewards,
|
||||||
IsReceivedWeeklyScoreReward: ws.IsReceivedWeeklyReward,
|
IsReceivedWeeklyScoreReward: ws.IsReceivedWeeklyReward,
|
||||||
LastWeekWeeklyScoreReward: lastWeekRewards,
|
LastWeekWeeklyScoreReward: lastWeekRewards,
|
||||||
DiffUserData: buildBigHuntDiff(user, bigHuntDiffTables),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -16,9 +15,9 @@ import (
|
|||||||
func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) {
|
func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartEventQuestRequest) (*pb.StartEventQuestResponse, error) {
|
||||||
log.Printf("[QuestService] StartEventQuest: chapterId=%d questId=%d isBattleOnly=%v", req.EventQuestChapterId, req.QuestId, req.IsBattleOnly)
|
log.Printf("[QuestService] StartEventQuest: chapterId=%d questId=%d isBattleOnly=%v", req.EventQuestChapterId, req.QuestId, req.IsBattleOnly)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleEventQuestStart(user, req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
s.engine.HandleEventQuestStart(user, req.EventQuestChapterId, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -34,12 +33,6 @@ func (s *QuestServiceServer) StartEventQuest(ctx context.Context, req *pb.StartE
|
|||||||
|
|
||||||
return &pb.StartEventQuestResponse{
|
return &pb.StartEventQuestResponse{
|
||||||
BattleDropReward: pbDrops,
|
BattleDropReward: pbDrops,
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserEventQuestProgressStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,34 +40,12 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
|
|||||||
log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v", req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated)
|
log.Printf("[QuestService] FinishEventQuest: chapterId=%d questId=%d isRetired=%v isAnnihilated=%v", req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
outcome = s.engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
outcome = s.engine.HandleEventQuestFinish(user, req.EventQuestChapterId, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserEventQuestProgressStatus",
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserGem",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserCostumeActiveSkill",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserWeaponNote",
|
|
||||||
"IUserCompanion",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserImportantItem",
|
|
||||||
"IUserParts",
|
|
||||||
"IUserPartsGroupNote",
|
|
||||||
})
|
|
||||||
userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
|
|
||||||
|
|
||||||
return &pb.FinishEventQuestResponse{
|
return &pb.FinishEventQuestResponse{
|
||||||
DropReward: toProtoRewards(outcome.DropRewards),
|
DropReward: toProtoRewards(outcome.DropRewards),
|
||||||
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
||||||
@@ -84,55 +55,31 @@ func (s *QuestServiceServer) FinishEventQuest(ctx context.Context, req *pb.Finis
|
|||||||
IsBigWin: outcome.IsBigWin,
|
IsBigWin: outcome.IsBigWin,
|
||||||
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
||||||
UserStatusCampaignReward: []*pb.QuestReward{},
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) RestartEventQuest(ctx context.Context, req *pb.RestartEventQuestRequest) (*pb.RestartEventQuestResponse, error) {
|
func (s *QuestServiceServer) RestartEventQuest(ctx context.Context, req *pb.RestartEventQuestRequest) (*pb.RestartEventQuestResponse, error) {
|
||||||
log.Printf("[QuestService] RestartEventQuest: chapterId=%d questId=%d", req.EventQuestChapterId, req.QuestId)
|
log.Printf("[QuestService] RestartEventQuest: chapterId=%d questId=%d", req.EventQuestChapterId, req.QuestId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleEventQuestRestart(user, req.EventQuestChapterId, req.QuestId, gametime.NowMillis())
|
s.engine.HandleEventQuestRestart(user, req.EventQuestChapterId, req.QuestId, gametime.NowMillis())
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.RestartEventQuestResponse{
|
return &pb.RestartEventQuestResponse{
|
||||||
BattleDropReward: []*pb.BattleDropReward{},
|
BattleDropReward: []*pb.BattleDropReward{},
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserEventQuestProgressStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context, req *pb.UpdateEventQuestSceneProgressRequest) (*pb.UpdateEventQuestSceneProgressResponse, error) {
|
func (s *QuestServiceServer) UpdateEventQuestSceneProgress(ctx context.Context, req *pb.UpdateEventQuestSceneProgressRequest) (*pb.UpdateEventQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateEventQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateEventQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
s.engine.HandleEventQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := buildSelectedQuestDiff(user, []string{
|
return &pb.UpdateEventQuestSceneProgressResponse{}, nil
|
||||||
"IUserEventQuestProgressStatus",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserCompanion",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserImportantItem",
|
|
||||||
"IUserParts",
|
|
||||||
"IUserPartsGroupNote",
|
|
||||||
})
|
|
||||||
userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
|
|
||||||
|
|
||||||
return &pb.UpdateEventQuestSceneProgressResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultGuerrillaFreeOpenMinutes = int32(60)
|
const defaultGuerrillaFreeOpenMinutes = int32(60)
|
||||||
@@ -140,16 +87,14 @@ const defaultGuerrillaFreeOpenMinutes = int32(60)
|
|||||||
func (s *QuestServiceServer) StartGuerrillaFreeOpen(ctx context.Context, req *emptypb.Empty) (*pb.StartGuerrillaFreeOpenResponse, error) {
|
func (s *QuestServiceServer) StartGuerrillaFreeOpen(ctx context.Context, req *emptypb.Empty) (*pb.StartGuerrillaFreeOpenResponse, error) {
|
||||||
log.Printf("[QuestService] StartGuerrillaFreeOpen")
|
log.Printf("[QuestService] StartGuerrillaFreeOpen")
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.GuerrillaFreeOpen.StartDatetime = nowMillis
|
user.GuerrillaFreeOpen.StartDatetime = nowMillis
|
||||||
user.GuerrillaFreeOpen.OpenMinutes = defaultGuerrillaFreeOpenMinutes
|
user.GuerrillaFreeOpen.OpenMinutes = defaultGuerrillaFreeOpenMinutes
|
||||||
user.GuerrillaFreeOpen.DailyOpenedCount++
|
user.GuerrillaFreeOpen.DailyOpenedCount++
|
||||||
user.GuerrillaFreeOpen.LatestVersion = nowMillis
|
user.GuerrillaFreeOpen.LatestVersion = nowMillis
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.StartGuerrillaFreeOpenResponse{
|
return &pb.StartGuerrillaFreeOpenResponse{}, nil
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{"IUserEventQuestGuerrillaFreeOpen"}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,14 @@ import (
|
|||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) {
|
func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartExtraQuestRequest) (*pb.StartExtraQuestResponse, error) {
|
||||||
log.Printf("[QuestService] StartExtraQuest: questId=%d deckNumber=%d", req.QuestId, req.UserDeckNumber)
|
log.Printf("[QuestService] StartExtraQuest: questId=%d deckNumber=%d", req.QuestId, req.UserDeckNumber)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleExtraQuestStart(user, req.QuestId, req.UserDeckNumber, nowMillis)
|
s.engine.HandleExtraQuestStart(user, req.QuestId, req.UserDeckNumber, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -32,12 +31,6 @@ func (s *QuestServiceServer) StartExtraQuest(ctx context.Context, req *pb.StartE
|
|||||||
|
|
||||||
return &pb.StartExtraQuestResponse{
|
return &pb.StartExtraQuestResponse{
|
||||||
BattleDropReward: pbDrops,
|
BattleDropReward: pbDrops,
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserExtraQuestProgressStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,34 +38,12 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
|
|||||||
log.Printf("[QuestService] FinishExtraQuest: questId=%d isRetired=%v isAnnihilated=%v", req.QuestId, req.IsRetired, req.IsAnnihilated)
|
log.Printf("[QuestService] FinishExtraQuest: questId=%d isRetired=%v isAnnihilated=%v", req.QuestId, req.IsRetired, req.IsAnnihilated)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
outcome = s.engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
outcome = s.engine.HandleExtraQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserExtraQuestProgressStatus",
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserGem",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserCostumeActiveSkill",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserWeaponNote",
|
|
||||||
"IUserCompanion",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserImportantItem",
|
|
||||||
"IUserParts",
|
|
||||||
"IUserPartsGroupNote",
|
|
||||||
})
|
|
||||||
userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
|
|
||||||
|
|
||||||
return &pb.FinishExtraQuestResponse{
|
return &pb.FinishExtraQuestResponse{
|
||||||
DropReward: toProtoRewards(outcome.DropRewards),
|
DropReward: toProtoRewards(outcome.DropRewards),
|
||||||
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
||||||
@@ -81,16 +52,17 @@ func (s *QuestServiceServer) FinishExtraQuest(ctx context.Context, req *pb.Finis
|
|||||||
IsBigWin: outcome.IsBigWin,
|
IsBigWin: outcome.IsBigWin,
|
||||||
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
||||||
UserStatusCampaignReward: []*pb.QuestReward{},
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.RestartExtraQuestRequest) (*pb.RestartExtraQuestResponse, error) {
|
func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.RestartExtraQuestRequest) (*pb.RestartExtraQuestResponse, error) {
|
||||||
log.Printf("[QuestService] RestartExtraQuest: questId=%d", req.QuestId)
|
log.Printf("[QuestService] RestartExtraQuest: questId=%d", req.QuestId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
var deckNumber int32
|
||||||
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleExtraQuestRestart(user, req.QuestId, gametime.NowMillis())
|
s.engine.HandleExtraQuestRestart(user, req.QuestId, gametime.NowMillis())
|
||||||
|
deckNumber = user.Quests[req.QuestId].UserDeckNumber
|
||||||
})
|
})
|
||||||
|
|
||||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
drops := s.engine.BattleDropRewards(req.QuestId)
|
||||||
@@ -105,40 +77,17 @@ func (s *QuestServiceServer) RestartExtraQuest(ctx context.Context, req *pb.Rest
|
|||||||
|
|
||||||
return &pb.RestartExtraQuestResponse{
|
return &pb.RestartExtraQuestResponse{
|
||||||
BattleDropReward: pbDrops,
|
BattleDropReward: pbDrops,
|
||||||
DeckNumber: user.Quests[req.QuestId].UserDeckNumber,
|
DeckNumber: deckNumber,
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserExtraQuestProgressStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context, req *pb.UpdateExtraQuestSceneProgressRequest) (*pb.UpdateExtraQuestSceneProgressResponse, error) {
|
func (s *QuestServiceServer) UpdateExtraQuestSceneProgress(ctx context.Context, req *pb.UpdateExtraQuestSceneProgressRequest) (*pb.UpdateExtraQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateExtraQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateExtraQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
s.engine.HandleExtraQuestSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := buildSelectedQuestDiff(user, []string{
|
return &pb.UpdateExtraQuestSceneProgressResponse{}, nil
|
||||||
"IUserExtraQuestProgressStatus",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserCompanion",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserImportantItem",
|
|
||||||
"IUserParts",
|
|
||||||
"IUserPartsGroupNote",
|
|
||||||
})
|
|
||||||
userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
|
|
||||||
|
|
||||||
return &pb.UpdateExtraQuestSceneProgressResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -28,91 +27,45 @@ func NewQuestServiceServer(users store.UserRepository, sessions store.SessionRep
|
|||||||
return &QuestServiceServer{users: users, sessions: sessions, engine: engine}
|
return &QuestServiceServer{users: users, sessions: sessions, engine: engine}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSelectedQuestDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
|
||||||
tables := userdata.ProjectTables(user, tableNames)
|
|
||||||
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, req *pb.UpdateMainFlowSceneProgressRequest) (*pb.UpdateMainFlowSceneProgressResponse, error) {
|
func (s *QuestServiceServer) UpdateMainFlowSceneProgress(ctx context.Context, req *pb.UpdateMainFlowSceneProgressRequest) (*pb.UpdateMainFlowSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateMainFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateMainFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
s.engine.HandleMainFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := buildSelectedQuestDiff(user, []string{
|
return &pb.UpdateMainFlowSceneProgressResponse{}, nil
|
||||||
"IUserMainQuestFlowStatus",
|
|
||||||
"IUserMainQuestMainFlowStatus",
|
|
||||||
"IUserMainQuestProgressStatus",
|
|
||||||
"IUserMainQuestSeasonRoute",
|
|
||||||
"IUserPortalCageStatus",
|
|
||||||
"IUserSideStoryQuestSceneProgressStatus",
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserCostumeActiveSkill",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserWeaponNote",
|
|
||||||
"IUserCompanion",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserImportantItem",
|
|
||||||
"IUserParts",
|
|
||||||
"IUserPartsGroupNote",
|
|
||||||
})
|
|
||||||
userdata.AddWeaponStoryDiff(diff, user, s.engine.Granter.DrainChangedStoryWeaponIds())
|
|
||||||
|
|
||||||
return &pb.UpdateMainFlowSceneProgressResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) UpdateReplayFlowSceneProgress(ctx context.Context, req *pb.UpdateReplayFlowSceneProgressRequest) (*pb.UpdateReplayFlowSceneProgressResponse, error) {
|
func (s *QuestServiceServer) UpdateReplayFlowSceneProgress(ctx context.Context, req *pb.UpdateReplayFlowSceneProgressRequest) (*pb.UpdateReplayFlowSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateReplayFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateReplayFlowSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleReplayFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
s.engine.HandleReplayFlowSceneProgress(user, req.QuestSceneId, gametime.NowMillis())
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.UpdateReplayFlowSceneProgressResponse{
|
return &pb.UpdateReplayFlowSceneProgressResponse{}, nil
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserMainQuestFlowStatus",
|
|
||||||
"IUserMainQuestReplayFlowStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, req *pb.UpdateMainQuestSceneProgressRequest) (*pb.UpdateMainQuestSceneProgressResponse, error) {
|
func (s *QuestServiceServer) UpdateMainQuestSceneProgress(ctx context.Context, req *pb.UpdateMainQuestSceneProgressRequest) (*pb.UpdateMainQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[QuestService] UpdateMainQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
log.Printf("[QuestService] UpdateMainQuestSceneProgress: questSceneId=%d", req.QuestSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleMainQuestSceneProgress(user, req.QuestSceneId)
|
s.engine.HandleMainQuestSceneProgress(user, req.QuestSceneId)
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.UpdateMainQuestSceneProgressResponse{
|
return &pb.UpdateMainQuestSceneProgressResponse{}, nil
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserMainQuestFlowStatus",
|
|
||||||
"IUserMainQuestMainFlowStatus",
|
|
||||||
"IUserMainQuestProgressStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) {
|
func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMainQuestRequest) (*pb.StartMainQuestResponse, error) {
|
||||||
log.Printf("[QuestService] StartMainQuest: %+v", req)
|
log.Printf("[QuestService] StartMainQuest: %+v", req)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if req.IsReplayFlow {
|
if req.IsReplayFlow {
|
||||||
s.engine.HandleQuestStartReplay(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
s.engine.HandleQuestStartReplay(user, req.QuestId, req.IsBattleOnly, req.UserDeckNumber, nowMillis)
|
||||||
} else {
|
} else {
|
||||||
@@ -132,16 +85,6 @@ func (s *QuestServiceServer) StartMainQuest(ctx context.Context, req *pb.StartMa
|
|||||||
|
|
||||||
return &pb.StartMainQuestResponse{
|
return &pb.StartMainQuestResponse{
|
||||||
BattleDropReward: pbDrops,
|
BattleDropReward: pbDrops,
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserMainQuestFlowStatus",
|
|
||||||
"IUserMainQuestMainFlowStatus",
|
|
||||||
"IUserMainQuestProgressStatus",
|
|
||||||
"IUserMainQuestSeasonRoute",
|
|
||||||
"IUserMainQuestReplayFlowStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,38 +108,12 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
|
|||||||
req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.StorySkipType)
|
req.QuestId, req.IsMainFlow, req.IsRetired, req.IsAnnihilated, req.StorySkipType)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
outcome = s.engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
outcome = s.engine.HandleQuestFinish(user, req.QuestId, req.IsRetired, req.IsAnnihilated, nowMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserMainQuestFlowStatus",
|
|
||||||
"IUserMainQuestMainFlowStatus",
|
|
||||||
"IUserMainQuestProgressStatus",
|
|
||||||
"IUserMainQuestSeasonRoute",
|
|
||||||
"IUserMainQuestReplayFlowStatus",
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserGem",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserCostumeActiveSkill",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserWeaponNote",
|
|
||||||
"IUserCompanion",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserImportantItem",
|
|
||||||
"IUserParts",
|
|
||||||
"IUserPartsGroupNote",
|
|
||||||
})
|
|
||||||
userdata.AddWeaponStoryDiff(diff, user, outcome.ChangedWeaponStoryIds)
|
|
||||||
|
|
||||||
return &pb.FinishMainQuestResponse{
|
return &pb.FinishMainQuestResponse{
|
||||||
DropReward: toProtoRewards(outcome.DropRewards),
|
DropReward: toProtoRewards(outcome.DropRewards),
|
||||||
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
FirstClearReward: toProtoRewards(outcome.FirstClearRewards),
|
||||||
@@ -207,16 +124,17 @@ func (s *QuestServiceServer) FinishMainQuest(ctx context.Context, req *pb.Finish
|
|||||||
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
BigWinClearedQuestMissionIdList: outcome.BigWinClearedQuestMissionIds,
|
||||||
ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards),
|
ReplayFlowFirstClearReward: toProtoRewards(outcome.ReplayFlowFirstClearRewards),
|
||||||
UserStatusCampaignReward: []*pb.QuestReward{},
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) RestartMainQuest(ctx context.Context, req *pb.RestartMainQuestRequest) (*pb.RestartMainQuestResponse, error) {
|
func (s *QuestServiceServer) RestartMainQuest(ctx context.Context, req *pb.RestartMainQuestRequest) (*pb.RestartMainQuestResponse, error) {
|
||||||
log.Printf("[QuestService] RestartMainQuest: questId=%d isMainFlow=%v", req.QuestId, req.IsMainFlow)
|
log.Printf("[QuestService] RestartMainQuest: questId=%d isMainFlow=%v", req.QuestId, req.IsMainFlow)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
var deckNumber int32
|
||||||
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
s.engine.HandleQuestRestart(user, req.QuestId, gametime.NowMillis())
|
s.engine.HandleQuestRestart(user, req.QuestId, gametime.NowMillis())
|
||||||
|
deckNumber = user.Quests[req.QuestId].UserDeckNumber
|
||||||
})
|
})
|
||||||
|
|
||||||
drops := s.engine.BattleDropRewards(req.QuestId)
|
drops := s.engine.BattleDropRewards(req.QuestId)
|
||||||
@@ -231,33 +149,22 @@ func (s *QuestServiceServer) RestartMainQuest(ctx context.Context, req *pb.Resta
|
|||||||
|
|
||||||
return &pb.RestartMainQuestResponse{
|
return &pb.RestartMainQuestResponse{
|
||||||
BattleDropReward: pbDrops,
|
BattleDropReward: pbDrops,
|
||||||
DeckNumber: user.Quests[req.QuestId].UserDeckNumber,
|
DeckNumber: deckNumber,
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserQuestMission",
|
|
||||||
"IUserMainQuestFlowStatus",
|
|
||||||
"IUserMainQuestMainFlowStatus",
|
|
||||||
"IUserMainQuestProgressStatus",
|
|
||||||
"IUserMainQuestSeasonRoute",
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) FinishAutoOrbit(ctx context.Context, req *emptypb.Empty) (*pb.FinishAutoOrbitResponse, error) {
|
func (s *QuestServiceServer) FinishAutoOrbit(ctx context.Context, req *emptypb.Empty) (*pb.FinishAutoOrbitResponse, error) {
|
||||||
log.Printf("[QuestService] FinishAutoOrbit")
|
log.Printf("[QuestService] FinishAutoOrbit")
|
||||||
return &pb.FinishAutoOrbitResponse{
|
return &pb.FinishAutoOrbitResponse{}, nil
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestRequest) (*pb.SkipQuestResponse, error) {
|
func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestRequest) (*pb.SkipQuestResponse, error) {
|
||||||
log.Printf("[QuestService] SkipQuest: questId=%d skipCount=%d useEffectItems=%d", req.QuestId, req.SkipCount, len(req.UseEffectItem))
|
log.Printf("[QuestService] SkipQuest: questId=%d skipCount=%d useEffectItems=%d", req.QuestId, req.SkipCount, len(req.UseEffectItem))
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
var outcome questflow.FinishOutcome
|
var outcome questflow.FinishOutcome
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for _, item := range req.UseEffectItem {
|
for _, item := range req.UseEffectItem {
|
||||||
log.Printf("[QuestService] SkipQuest UseEffectItem: consumableItemId=%d count=%d", item.ConsumableItemId, item.Count)
|
log.Printf("[QuestService] SkipQuest UseEffectItem: consumableItemId=%d count=%d", item.ConsumableItemId, item.Count)
|
||||||
user.ConsumableItems[item.ConsumableItemId] -= item.Count
|
user.ConsumableItems[item.ConsumableItemId] -= item.Count
|
||||||
@@ -271,24 +178,14 @@ func (s *QuestServiceServer) SkipQuest(ctx context.Context, req *pb.SkipQuestReq
|
|||||||
return &pb.SkipQuestResponse{
|
return &pb.SkipQuestResponse{
|
||||||
DropReward: toProtoRewards(outcome.DropRewards),
|
DropReward: toProtoRewards(outcome.DropRewards),
|
||||||
UserStatusCampaignReward: []*pb.QuestReward{},
|
UserStatusCampaignReward: []*pb.QuestReward{},
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserQuest",
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserParts",
|
|
||||||
"IUserPartsGroupNote",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserCostume",
|
|
||||||
}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteRequest) (*pb.SetRouteResponse, error) {
|
func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteRequest) (*pb.SetRouteResponse, error) {
|
||||||
log.Printf("[QuestService] SetRoute: mainQuestRouteId=%d", req.MainQuestRouteId)
|
log.Printf("[QuestService] SetRoute: mainQuestRouteId=%d", req.MainQuestRouteId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.MainQuest.CurrentMainQuestRouteId = req.MainQuestRouteId
|
user.MainQuest.CurrentMainQuestRouteId = req.MainQuestRouteId
|
||||||
if seasonId, ok := s.engine.SeasonIdByRouteId[req.MainQuestRouteId]; ok {
|
if seasonId, ok := s.engine.SeasonIdByRouteId[req.MainQuestRouteId]; ok {
|
||||||
user.MainQuest.MainQuestSeasonId = seasonId
|
user.MainQuest.MainQuestSeasonId = seasonId
|
||||||
@@ -298,30 +195,22 @@ func (s *QuestServiceServer) SetRoute(ctx context.Context, req *pb.SetRouteReque
|
|||||||
user.PortalCageStatus.LatestVersion = now
|
user.PortalCageStatus.LatestVersion = now
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.SetRouteResponse{
|
return &pb.SetRouteResponse{}, nil
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserMainQuestSeasonRoute",
|
|
||||||
"IUserMainQuestMainFlowStatus",
|
|
||||||
"IUserPortalCageStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) SetQuestSceneChoice(ctx context.Context, req *pb.SetQuestSceneChoiceRequest) (*pb.SetQuestSceneChoiceResponse, error) {
|
func (s *QuestServiceServer) SetQuestSceneChoice(ctx context.Context, req *pb.SetQuestSceneChoiceRequest) (*pb.SetQuestSceneChoiceResponse, error) {
|
||||||
log.Printf("[QuestService] SetQuestSceneChoice: questSceneId=%d choiceNumber=%d",
|
log.Printf("[QuestService] SetQuestSceneChoice: questSceneId=%d choiceNumber=%d",
|
||||||
req.QuestSceneId, req.ChoiceNumber)
|
req.QuestSceneId, req.ChoiceNumber)
|
||||||
return &pb.SetQuestSceneChoiceResponse{
|
return &pb.SetQuestSceneChoiceResponse{}, nil
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) ResetLimitContentQuestProgress(ctx context.Context, req *pb.ResetLimitContentQuestProgressRequest) (*pb.ResetLimitContentQuestProgressResponse, error) {
|
func (s *QuestServiceServer) ResetLimitContentQuestProgress(ctx context.Context, req *pb.ResetLimitContentQuestProgressRequest) (*pb.ResetLimitContentQuestProgressResponse, error) {
|
||||||
log.Printf("[QuestService] ResetLimitContentQuestProgress: eventQuestChapterId=%d questId=%d",
|
log.Printf("[QuestService] ResetLimitContentQuestProgress: eventQuestChapterId=%d questId=%d",
|
||||||
req.EventQuestChapterId, req.QuestId)
|
req.EventQuestChapterId, req.QuestId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if _, exists := user.SideStoryQuests[req.QuestId]; exists {
|
if _, exists := user.SideStoryQuests[req.QuestId]; exists {
|
||||||
user.SideStoryQuests[req.QuestId] = store.SideStoryQuestProgress{
|
user.SideStoryQuests[req.QuestId] = store.SideStoryQuestProgress{
|
||||||
HeadSideStoryQuestSceneId: 0,
|
HeadSideStoryQuestSceneId: 0,
|
||||||
@@ -339,20 +228,14 @@ func (s *QuestServiceServer) ResetLimitContentQuestProgress(ctx context.Context,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.ResetLimitContentQuestProgressResponse{
|
return &pb.ResetLimitContentQuestProgressResponse{}, nil
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserSideStoryQuest",
|
|
||||||
"IUserSideStoryQuestSceneProgressStatus",
|
|
||||||
"IUserQuestLimitContentStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuestServiceServer) SetAutoSaleSetting(ctx context.Context, req *pb.SetAutoSaleSettingRequest) (*pb.SetAutoSaleSettingResponse, error) {
|
func (s *QuestServiceServer) SetAutoSaleSetting(ctx context.Context, req *pb.SetAutoSaleSettingRequest) (*pb.SetAutoSaleSettingResponse, error) {
|
||||||
log.Printf("[QuestService] SetAutoSaleSetting: items=%d", len(req.AutoSaleSettingItem))
|
log.Printf("[QuestService] SetAutoSaleSetting: items=%d", len(req.AutoSaleSettingItem))
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.AutoSaleSettings = make(map[int32]store.AutoSaleSettingState, len(req.AutoSaleSettingItem))
|
user.AutoSaleSettings = make(map[int32]store.AutoSaleSettingState, len(req.AutoSaleSettingItem))
|
||||||
for itemType, itemValue := range req.AutoSaleSettingItem {
|
for itemType, itemValue := range req.AutoSaleSettingItem {
|
||||||
user.AutoSaleSettings[itemType] = store.AutoSaleSettingState{
|
user.AutoSaleSettings[itemType] = store.AutoSaleSettingState{
|
||||||
@@ -362,9 +245,5 @@ func (s *QuestServiceServer) SetAutoSaleSetting(ctx context.Context, req *pb.Set
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.SetAutoSaleSettingResponse{
|
return &pb.SetAutoSaleSettingResponse{}, nil
|
||||||
DiffUserData: buildSelectedQuestDiff(user, []string{
|
|
||||||
"IUserAutoSaleSettingDetail",
|
|
||||||
}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SideStoryQuestServiceServer struct {
|
type SideStoryQuestServiceServer struct {
|
||||||
@@ -23,19 +22,14 @@ func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.S
|
|||||||
return &SideStoryQuestServiceServer{users: users, sessions: sessions, catalog: catalog}
|
return &SideStoryQuestServiceServer{users: users, sessions: sessions, catalog: catalog}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildSideStoryDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
|
||||||
tables := userdata.ProjectTables(user, tableNames)
|
|
||||||
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) {
|
func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Context, req *pb.MoveSideStoryQuestRequest) (*pb.MoveSideStoryQuestResponse, error) {
|
||||||
log.Printf("[SideStoryQuestService] MoveSideStoryQuestProgress: sideStoryQuestId=%d", req.SideStoryQuestId)
|
log.Printf("[SideStoryQuestService] MoveSideStoryQuestProgress: sideStoryQuestId=%d", req.SideStoryQuestId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
firstSceneId := s.catalog.FirstSceneByQuestId[req.SideStoryQuestId]
|
firstSceneId := s.catalog.FirstSceneByQuestId[req.SideStoryQuestId]
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
|
existing, exists := user.SideStoryQuests[req.SideStoryQuestId]
|
||||||
|
|
||||||
var sceneId int32
|
var sceneId int32
|
||||||
@@ -58,21 +52,16 @@ func (s *SideStoryQuestServiceServer) MoveSideStoryQuestProgress(ctx context.Con
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.MoveSideStoryQuestResponse{
|
return &pb.MoveSideStoryQuestResponse{}, nil
|
||||||
DiffUserData: buildSideStoryDiff(user, []string{
|
|
||||||
"IUserSideStoryQuest",
|
|
||||||
"IUserSideStoryQuestSceneProgressStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SideStoryQuestServiceServer) UpdateSideStoryQuestSceneProgress(ctx context.Context, req *pb.UpdateSideStoryQuestSceneProgressRequest) (*pb.UpdateSideStoryQuestSceneProgressResponse, error) {
|
func (s *SideStoryQuestServiceServer) UpdateSideStoryQuestSceneProgress(ctx context.Context, req *pb.UpdateSideStoryQuestSceneProgressRequest) (*pb.UpdateSideStoryQuestSceneProgressResponse, error) {
|
||||||
log.Printf("[SideStoryQuestService] UpdateSideStoryQuestSceneProgress: sideStoryQuestId=%d sceneId=%d",
|
log.Printf("[SideStoryQuestService] UpdateSideStoryQuestSceneProgress: sideStoryQuestId=%d sceneId=%d",
|
||||||
req.SideStoryQuestId, req.SideStoryQuestSceneId)
|
req.SideStoryQuestId, req.SideStoryQuestSceneId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId = req.SideStoryQuestSceneId
|
user.SideStoryActiveProgress.CurrentSideStoryQuestSceneId = req.SideStoryQuestSceneId
|
||||||
user.SideStoryActiveProgress.LatestVersion = nowMillis
|
user.SideStoryActiveProgress.LatestVersion = nowMillis
|
||||||
|
|
||||||
@@ -84,10 +73,5 @@ func (s *SideStoryQuestServiceServer) UpdateSideStoryQuestSceneProgress(ctx cont
|
|||||||
user.SideStoryQuests[req.SideStoryQuestId] = progress
|
user.SideStoryQuests[req.SideStoryQuestId] = progress
|
||||||
})
|
})
|
||||||
|
|
||||||
return &pb.UpdateSideStoryQuestSceneProgressResponse{
|
return &pb.UpdateSideStoryQuestSceneProgressResponse{}, nil
|
||||||
DiffUserData: buildSideStoryDiff(user, []string{
|
|
||||||
"IUserSideStoryQuest",
|
|
||||||
"IUserSideStoryQuestSceneProgressStatus",
|
|
||||||
}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
@@ -34,7 +33,7 @@ func NewRewardServiceServer(
|
|||||||
func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *emptypb.Empty) (*pb.ReceiveBigHuntRewardResponse, error) {
|
func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *emptypb.Empty) (*pb.ReceiveBigHuntRewardResponse, error) {
|
||||||
log.Printf("[RewardService] ReceiveBigHuntReward")
|
log.Printf("[RewardService] ReceiveBigHuntReward")
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
|||||||
var weeklyRewards []*pb.BigHuntReward
|
var weeklyRewards []*pb.BigHuntReward
|
||||||
isReceived := false
|
isReceived := false
|
||||||
|
|
||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
ws := user.BigHuntWeeklyStatuses[weeklyVersion]
|
||||||
isReceived = ws.IsReceivedWeeklyReward
|
isReceived = ws.IsReceivedWeeklyReward
|
||||||
|
|
||||||
@@ -106,19 +105,11 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
|||||||
weeklyScoreResults = []*pb.WeeklyScoreResult{}
|
weeklyScoreResults = []*pb.WeeklyScoreResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.ProjectTables(user, []string{
|
|
||||||
"IUserBigHuntWeeklyStatus",
|
|
||||||
"IUserBigHuntWeeklyMaxScore",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
})
|
|
||||||
|
|
||||||
return &pb.ReceiveBigHuntRewardResponse{
|
return &pb.ReceiveBigHuntRewardResponse{
|
||||||
WeeklyScoreResult: weeklyScoreResults,
|
WeeklyScoreResult: weeklyScoreResults,
|
||||||
WeeklyScoreReward: weeklyRewards,
|
WeeklyScoreReward: weeklyRewards,
|
||||||
IsReceivedWeeklyScoreReward: isReceived,
|
IsReceivedWeeklyScoreReward: isReceived,
|
||||||
LastWeekWeeklyScoreReward: []*pb.BigHuntReward{},
|
LastWeekWeeklyScoreReward: []*pb.BigHuntReward{},
|
||||||
DiffUserData: userdata.BuildDiffFromTables(tables),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,30 +10,10 @@ import (
|
|||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
"lunar-tear/server/internal/store"
|
"lunar-tear/server/internal/store"
|
||||||
"lunar-tear/server/internal/userdata"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shopDiffTables = []string{
|
|
||||||
"IUserShopItem",
|
|
||||||
"IUserShopReplaceable",
|
|
||||||
"IUserShopReplaceableLineup",
|
|
||||||
"IUserGem",
|
|
||||||
"IUserConsumableItem",
|
|
||||||
"IUserMaterial",
|
|
||||||
"IUserImportantItem",
|
|
||||||
"IUserPremiumItem",
|
|
||||||
"IUserStatus",
|
|
||||||
"IUserCostume",
|
|
||||||
"IUserCostumeActiveSkill",
|
|
||||||
"IUserCharacter",
|
|
||||||
"IUserWeapon",
|
|
||||||
"IUserWeaponSkill",
|
|
||||||
"IUserWeaponAbility",
|
|
||||||
"IUserWeaponNote",
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShopServiceServer struct {
|
type ShopServiceServer struct {
|
||||||
pb.UnimplementedShopServiceServer
|
pb.UnimplementedShopServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
@@ -49,10 +29,10 @@ func NewShopServiceServer(users store.UserRepository, sessions store.SessionRepo
|
|||||||
func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.BuyResponse, error) {
|
func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.BuyResponse, error) {
|
||||||
log.Printf("[ShopService] Buy: shopId=%d items=%v", req.ShopId, req.ShopItems)
|
log.Printf("[ShopService] Buy: shopId=%d items=%v", req.ShopId, req.ShopItems)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
for shopItemId, qty := range req.ShopItems {
|
for shopItemId, qty := range req.ShopItems {
|
||||||
item, ok := s.catalog.Items[shopItemId]
|
item, ok := s.catalog.Items[shopItemId]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -88,23 +68,18 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("shop buy: %w", err)
|
return nil, fmt.Errorf("shop buy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
|
||||||
userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds())
|
|
||||||
|
|
||||||
return &pb.BuyResponse{
|
return &pb.BuyResponse{
|
||||||
OverflowPossession: []*pb.Possession{},
|
OverflowPossession: []*pb.Possession{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.RefreshRequest) (*pb.RefreshResponse, error) {
|
func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.RefreshRequest) (*pb.RefreshResponse, error) {
|
||||||
log.Printf("[ShopService] RefreshUserData: isGemUsed=%v", req.IsGemUsed)
|
log.Printf("[ShopService] RefreshUserData: isGemUsed=%v", req.IsGemUsed)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
if len(user.ShopReplaceableLineup) == 0 && len(s.catalog.ItemShopPool) > 0 {
|
if len(user.ShopReplaceableLineup) == 0 && len(s.catalog.ItemShopPool) > 0 {
|
||||||
for i, itemId := range s.catalog.ItemShopPool {
|
for i, itemId := range s.catalog.ItemShopPool {
|
||||||
slot := int32(i + 1)
|
slot := int32(i + 1)
|
||||||
@@ -131,18 +106,13 @@ func (s *ShopServiceServer) RefreshUserData(ctx context.Context, req *pb.Refresh
|
|||||||
return nil, fmt.Errorf("shop refresh: %w", err)
|
return nil, fmt.Errorf("shop refresh: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
return &pb.RefreshResponse{}, nil
|
||||||
|
|
||||||
return &pb.RefreshResponse{
|
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShopServiceServer) GetCesaLimit(_ context.Context, _ *emptypb.Empty) (*pb.GetCesaLimitResponse, error) {
|
func (s *ShopServiceServer) GetCesaLimit(_ context.Context, _ *emptypb.Empty) (*pb.GetCesaLimitResponse, error) {
|
||||||
log.Printf("[ShopService] GetCesaLimit")
|
log.Printf("[ShopService] GetCesaLimit")
|
||||||
return &pb.GetCesaLimitResponse{
|
return &pb.GetCesaLimitResponse{
|
||||||
CesaLimit: []*pb.CesaLimit{},
|
CesaLimit: []*pb.CesaLimit{},
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,10 +120,10 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
|
|||||||
log.Printf("[ShopService] CreatePurchaseTransaction: shopId=%d shopItemId=%d productId=%s",
|
log.Printf("[ShopService] CreatePurchaseTransaction: shopId=%d shopItemId=%d productId=%s",
|
||||||
req.ShopId, req.ShopItemId, req.ProductId)
|
req.ShopId, req.ShopItemId, req.ProductId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
_, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
item, ok := s.catalog.Items[req.ShopItemId]
|
item, ok := s.catalog.Items[req.ShopItemId]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Printf("[ShopService] CreatePurchaseTransaction: unknown shopItemId=%d", req.ShopItemId)
|
log.Printf("[ShopService] CreatePurchaseTransaction: unknown shopItemId=%d", req.ShopItemId)
|
||||||
@@ -193,28 +163,22 @@ func (s *ShopServiceServer) CreatePurchaseTransaction(ctx context.Context, req *
|
|||||||
|
|
||||||
txId := fmt.Sprintf("tx_%d_%d_%d", userId, req.ShopItemId, nowMillis)
|
txId := fmt.Sprintf("tx_%d_%d_%d", userId, req.ShopItemId, nowMillis)
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
|
||||||
|
|
||||||
return &pb.CreatePurchaseTransactionResponse{
|
return &pb.CreatePurchaseTransactionResponse{
|
||||||
PurchaseTransactionId: txId,
|
PurchaseTransactionId: txId,
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShopServiceServer) PurchaseGooglePlayStoreProduct(ctx context.Context, req *pb.PurchaseGooglePlayStoreProductRequest) (*pb.PurchaseGooglePlayStoreProductResponse, error) {
|
func (s *ShopServiceServer) PurchaseGooglePlayStoreProduct(ctx context.Context, req *pb.PurchaseGooglePlayStoreProductRequest) (*pb.PurchaseGooglePlayStoreProductResponse, error) {
|
||||||
log.Printf("[ShopService] PurchaseGooglePlayStoreProduct: txId=%s", req.PurchaseTransactionId)
|
log.Printf("[ShopService] PurchaseGooglePlayStoreProduct: txId=%s", req.PurchaseTransactionId)
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := CurrentUserId(ctx, s.users, s.sessions)
|
||||||
snapshot, err := s.users.LoadUser(userId)
|
_, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("purchase google play: %w", err)
|
return nil, fmt.Errorf("purchase google play: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
|
||||||
|
|
||||||
return &pb.PurchaseGooglePlayStoreProductResponse{
|
return &pb.PurchaseGooglePlayStoreProductResponse{
|
||||||
OverflowPossession: []*pb.Possession{},
|
OverflowPossession: []*pb.Possession{},
|
||||||
DiffUserData: diff,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user