mirror of
https://github.com/Walter-Sparrow/lunar-tear.git
synced 2026-07-02 05:43:41 +03:00
Add SQLite persistence, import-snapshot tool, and karma functionality
This commit is contained in:
+2
-4
@@ -5,6 +5,7 @@
|
|||||||
server/bin/
|
server/bin/
|
||||||
server/tmp/
|
server/tmp/
|
||||||
server/lunar-tear
|
server/lunar-tear
|
||||||
|
server/import-snapshot
|
||||||
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
@@ -13,12 +14,9 @@ node_modules/
|
|||||||
|
|
||||||
# Go
|
# Go
|
||||||
server/vendor/
|
server/vendor/
|
||||||
|
|
||||||
# Certs (regenerate per-environment)
|
|
||||||
server/certs/
|
server/certs/
|
||||||
|
|
||||||
# Server assets (binary data, too large for git)
|
|
||||||
server/assets/
|
server/assets/
|
||||||
|
db/
|
||||||
|
|
||||||
# Snapshots (recorded user state)
|
# Snapshots (recorded user state)
|
||||||
snapshots/
|
snapshots/
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ Discord server: https://discord.gg/MZAf5aVkJG
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.24+
|
- Go 1.25+
|
||||||
|
- [goose](https://github.com/pressly/goose) migration tool
|
||||||
- Populated `server/assets/` directory
|
- Populated `server/assets/` directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/pressly/goose/v3/cmd/goose@latest
|
||||||
|
```
|
||||||
|
|
||||||
### Regenerate protobuf stubs
|
### Regenerate protobuf stubs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -17,14 +22,54 @@ cd server
|
|||||||
make proto
|
make proto
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
Player state is stored in a SQLite database. Run migrations before starting the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
make migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
mkdir -p db
|
||||||
|
goose -dir migrations sqlite3 db/game.db up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Importing a Snapshot
|
||||||
|
|
||||||
|
To import a JSON snapshot into the database, use the import tool. The `--uuid` flag must match the UUID your game client sends during authentication:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
make import SNAPSHOT=snapshots/scene_1.json UUID=<your-client-uuid>
|
||||||
|
```
|
||||||
|
|
||||||
|
Or directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run ./cmd/import-snapshot \
|
||||||
|
--snapshot snapshots/scene_1.json \
|
||||||
|
--uuid <your-client-uuid> \
|
||||||
|
--db db/game.db
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
| ------------ | ------------ | --------------------------------------------- |
|
||||||
|
| `--snapshot` | *(required)* | Path to JSON snapshot file |
|
||||||
|
| `--uuid` | *(required)* | UUID to assign (must match the client's UUID) |
|
||||||
|
| `--db` | `db/game.db` | SQLite database path |
|
||||||
|
|
||||||
### Run
|
### Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd server
|
cd server
|
||||||
sudo go run ./cmd/lunar-tear \
|
sudo go run ./cmd/lunar-tear \
|
||||||
--host 10.0.2.2 \
|
--host 10.0.2.2 \
|
||||||
--http-port 8080 \
|
--http-port 8080
|
||||||
--scene 13
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`sudo` is needed because gRPC binds to port 443 (privileged). On Linux you can use `setcap` instead:
|
`sudo` is needed because gRPC binds to port 443 (privileged). On Linux you can use `setcap` instead:
|
||||||
@@ -32,7 +77,7 @@ sudo go run ./cmd/lunar-tear \
|
|||||||
```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 --scene 13
|
./lunar-tear --host 10.0.2.2 --http-port 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ports
|
### Ports
|
||||||
@@ -45,10 +90,33 @@ sudo setcap cap_net_bind_service=+ep ./lunar-tear
|
|||||||
### Flags
|
### Flags
|
||||||
|
|
||||||
| Flag | Default | Description |
|
| Flag | Default | Description |
|
||||||
| ---------------------- | ------------------- | -------------------------------------------------------- |
|
| ------------- | ------------ | ------------------------------- |
|
||||||
| `--host` | `127.0.0.1` | hostname/IP given to the client |
|
| `--host` | `127.0.0.1` | hostname/IP given to the client |
|
||||||
| `--http-port` | `8080` | HTTP/Octo server port |
|
| `--http-port` | `8080` | HTTP/Octo server port |
|
||||||
| `--scene` | `0` | bootstrap new users to scene N (0 = fresh start) |
|
| `--db` | `db/game.db` | SQLite database path |
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
Migrations run automatically on container start.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The `db/` directory is mounted as a volume so the database persists across restarts. Make sure `assets/` is populated before starting.
|
||||||
|
|
||||||
|
### Makefile Targets
|
||||||
|
|
||||||
|
All targets run from the `server/` directory.
|
||||||
|
|
||||||
|
| Target | Description |
|
||||||
|
| -------------- | ------------------------------------------------------- |
|
||||||
|
| `make proto` | Regenerate protobuf stubs |
|
||||||
|
| `make build` | Build the server binary |
|
||||||
|
| `make build-import` | Build the import-snapshot tool |
|
||||||
|
| `make migrate` | Run goose migrations on `db/game.db` |
|
||||||
|
| `make import` | Import a snapshot (`SNAPSHOT=... UUID=...` required) |
|
||||||
|
|
||||||
## ⚠️ Legal Disclaimer
|
## ⚠️ Legal Disclaimer
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ RUN apk add --no-cache \
|
|||||||
|
|
||||||
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest &&\
|
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@latest &&\
|
||||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest &&\
|
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest &&\
|
||||||
|
go install github.com/pressly/goose/v3/cmd/goose@latest &&\
|
||||||
PATH="$PATH:$(go env GOPATH)/bin" make proto &&\
|
PATH="$PATH:$(go env GOPATH)/bin" make proto &&\
|
||||||
go build -o lunar-tear ./cmd/lunar-tear &&\
|
go build -o lunar-tear ./cmd/lunar-tear &&\
|
||||||
setcap cap_net_bind_service=+ep ./lunar-tear
|
setcap cap_net_bind_service=+ep ./lunar-tear
|
||||||
@@ -26,6 +27,8 @@ RUN chown 1000:1000 /opt/lunar-tear
|
|||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
COPY --from=builder /usr/local/src/lunar-tear .
|
COPY --from=builder /usr/local/src/lunar-tear .
|
||||||
|
COPY --from=builder /root/go/bin/goose /usr/local/bin/goose
|
||||||
|
COPY --from=builder /usr/local/src/migrations ./migrations
|
||||||
|
|
||||||
COPY entrypoint.sh .
|
COPY entrypoint.sh .
|
||||||
|
|
||||||
|
|||||||
+20
-1
@@ -7,4 +7,23 @@ 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/"
|
||||||
|
|
||||||
.PHONY: proto
|
build:
|
||||||
|
go build -o lunar-tear ./cmd/lunar-tear
|
||||||
|
|
||||||
|
build-import:
|
||||||
|
go build -o import-snapshot ./cmd/import-snapshot
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
mkdir -p db
|
||||||
|
goose -dir migrations sqlite3 db/game.db up
|
||||||
|
|
||||||
|
import:
|
||||||
|
ifndef SNAPSHOT
|
||||||
|
$(error SNAPSHOT is required, e.g. make import SNAPSHOT=snapshots/scene_1.json UUID=...)
|
||||||
|
endif
|
||||||
|
ifndef UUID
|
||||||
|
$(error UUID is required, e.g. make import SNAPSHOT=snapshots/scene_1.json UUID=...)
|
||||||
|
endif
|
||||||
|
go run ./cmd/import-snapshot --snapshot $(SNAPSHOT) --uuid $(UUID)
|
||||||
|
|
||||||
|
.PHONY: proto build build-import migrate import
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/database"
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
"lunar-tear/server/internal/store/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
||||||
|
snapshotPath := flag.String("snapshot", "", "Path to JSON snapshot file (required)")
|
||||||
|
userUuid := flag.String("uuid", "", "UUID to assign to the imported user (must match the client's UUID)")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *snapshotPath == "" {
|
||||||
|
log.Fatal("--snapshot flag is required")
|
||||||
|
}
|
||||||
|
if *userUuid == "" {
|
||||||
|
log.Fatal("--uuid flag is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(*snapshotPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("read snapshot: %v", err)
|
||||||
|
}
|
||||||
|
log.Printf("read %d bytes from %s", len(data), *snapshotPath)
|
||||||
|
|
||||||
|
var u store.UserState
|
||||||
|
if err := json.Unmarshal(data, &u); err != nil {
|
||||||
|
log.Fatalf("unmarshal snapshot: %v", err)
|
||||||
|
}
|
||||||
|
u.EnsureMaps()
|
||||||
|
u.Uuid = *userUuid
|
||||||
|
|
||||||
|
log.Printf("parsed user %d (uuid=%s, costumes=%d, weapons=%d, characters=%d, quests=%d)",
|
||||||
|
u.UserId, u.Uuid, len(u.Costumes), len(u.Weapons), len(u.Characters), len(u.Quests))
|
||||||
|
|
||||||
|
db, err := database.Open(*dbPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("open database: %v", err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
userStore := sqlite.New(db, nil)
|
||||||
|
|
||||||
|
if err := userStore.ImportUser(&u); err != nil {
|
||||||
|
log.Fatalf("import user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("imported user %d successfully", u.UserId)
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"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/memory"
|
"lunar-tear/server/internal/store"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -38,9 +38,13 @@ func (l loggingListener) Accept() (net.Conn, error) {
|
|||||||
func startGRPC(
|
func startGRPC(
|
||||||
host string,
|
host string,
|
||||||
octoURL string,
|
octoURL string,
|
||||||
userStore *memory.MemoryStore,
|
userStore interface {
|
||||||
|
store.UserRepository
|
||||||
|
store.SessionRepository
|
||||||
|
},
|
||||||
questEngine *questflow.QuestHandler,
|
questEngine *questflow.QuestHandler,
|
||||||
gachaHandler *gacha.GachaHandler,
|
gachaHandler *gacha.GachaHandler,
|
||||||
|
gachaEntries []store.GachaCatalogEntry,
|
||||||
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
|
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
|
||||||
loginBonusCatalog *masterdata.LoginBonusCatalog,
|
loginBonusCatalog *masterdata.LoginBonusCatalog,
|
||||||
characterViewerCatalog *masterdata.CharacterViewerCatalog,
|
characterViewerCatalog *masterdata.CharacterViewerCatalog,
|
||||||
@@ -77,6 +81,7 @@ func startGRPC(
|
|||||||
userStore,
|
userStore,
|
||||||
questEngine,
|
questEngine,
|
||||||
gachaHandler,
|
gachaHandler,
|
||||||
|
gachaEntries,
|
||||||
cageOrnamentCatalog,
|
cageOrnamentCatalog,
|
||||||
loginBonusCatalog,
|
loginBonusCatalog,
|
||||||
characterViewerCatalog,
|
characterViewerCatalog,
|
||||||
@@ -111,9 +116,13 @@ func registerServices(
|
|||||||
srv *grpc.Server,
|
srv *grpc.Server,
|
||||||
host string,
|
host string,
|
||||||
octoURL string,
|
octoURL string,
|
||||||
userStore *memory.MemoryStore,
|
userStore interface {
|
||||||
|
store.UserRepository
|
||||||
|
store.SessionRepository
|
||||||
|
},
|
||||||
questEngine *questflow.QuestHandler,
|
questEngine *questflow.QuestHandler,
|
||||||
gachaHandler *gacha.GachaHandler,
|
gachaHandler *gacha.GachaHandler,
|
||||||
|
gachaEntries []store.GachaCatalogEntry,
|
||||||
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
|
cageOrnamentCatalog *masterdata.CageOrnamentCatalog,
|
||||||
loginBonusCatalog *masterdata.LoginBonusCatalog,
|
loginBonusCatalog *masterdata.LoginBonusCatalog,
|
||||||
characterViewerCatalog *masterdata.CharacterViewerCatalog,
|
characterViewerCatalog *masterdata.CharacterViewerCatalog,
|
||||||
@@ -133,13 +142,13 @@ func registerServices(
|
|||||||
sideStoryCatalog *masterdata.SideStoryCatalog,
|
sideStoryCatalog *masterdata.SideStoryCatalog,
|
||||||
bigHuntCatalog *masterdata.BigHuntCatalog,
|
bigHuntCatalog *masterdata.BigHuntCatalog,
|
||||||
) {
|
) {
|
||||||
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(userStore))
|
pb.RegisterBannerServiceServer(srv, service.NewBannerServiceServer(gachaEntries))
|
||||||
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore))
|
pb.RegisterUserServiceServer(srv, service.NewUserServiceServer(userStore, userStore))
|
||||||
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
|
pb.RegisterBattleServiceServer(srv, service.NewBattleServiceServer(userStore, userStore))
|
||||||
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(host, int32(443), octoURL))
|
pb.RegisterConfigServiceServer(srv, service.NewConfigServiceServer(host, int32(443), 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, userStore, gachaHandler))
|
pb.RegisterGachaServiceServer(srv, service.NewGachaServiceServer(userStore, userStore, gachaEntries, gachaHandler))
|
||||||
pb.RegisterGiftServiceServer(srv, service.NewGiftServiceServer(userStore, userStore))
|
pb.RegisterGiftServiceServer(srv, service.NewGiftServiceServer(userStore, userStore))
|
||||||
pb.RegisterGamePlayServiceServer(srv, service.NewGameplayServiceServer())
|
pb.RegisterGamePlayServiceServer(srv, service.NewGameplayServiceServer())
|
||||||
pb.RegisterGimmickServiceServer(srv, service.NewGimmickServiceServer(userStore, userStore, gimmickCatalog))
|
pb.RegisterGimmickServiceServer(srv, service.NewGimmickServiceServer(userStore, userStore, gimmickCatalog))
|
||||||
|
|||||||
@@ -3,23 +3,21 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"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/questflow"
|
"lunar-tear/server/internal/questflow"
|
||||||
"lunar-tear/server/internal/store/memory"
|
"lunar-tear/server/internal/store/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
httpPort := flag.Int("http-port", 8080, "HTTP server port (Octo API)")
|
httpPort := flag.Int("http-port", 8080, "HTTP server port (Octo API)")
|
||||||
host := flag.String("host", "127.0.0.1", "hostname the client will connect to")
|
host := flag.String("host", "127.0.0.1", "hostname the client will connect to")
|
||||||
scene := flag.Int("scene", 0, "Bootstrap to scene N (0 = fresh start)")
|
dbPath := flag.String("db", "db/game.db", "SQLite database path")
|
||||||
latestScene := flag.Bool("latest-scene", false, "Bootstrap from the most recently saved snapshot (overrides -scene)")
|
|
||||||
starterItems := flag.Bool("starter-items", false, "Grant starter items to new users")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
octoURL := "http://" + *host + ":" + strconv.Itoa(*httpPort)
|
octoURL := "http://" + *host + ":" + strconv.Itoa(*httpPort)
|
||||||
@@ -34,18 +32,12 @@ func main() {
|
|||||||
|
|
||||||
go startHTTP(*httpPort, resourcesBaseURL)
|
go startHTTP(*httpPort, resourcesBaseURL)
|
||||||
|
|
||||||
snapshotDir := "snapshots"
|
db, err := database.Open(*dbPath)
|
||||||
if err := os.MkdirAll(snapshotDir, 0755); err != nil {
|
if err != nil {
|
||||||
log.Fatalf("create snapshot dir: %v", err)
|
log.Fatalf("open database: %v", err)
|
||||||
}
|
|
||||||
if *latestScene {
|
|
||||||
if id, ok := memory.LatestSnapshotSceneId(snapshotDir); ok {
|
|
||||||
*scene = int(id)
|
|
||||||
log.Printf("[latest-scene] auto-selected most recent snapshot: scene=%d", id)
|
|
||||||
} else {
|
|
||||||
log.Printf("[latest-scene] no snapshots found in %q; starting fresh", snapshotDir)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
log.Printf("database opened: %s", *dbPath)
|
||||||
|
|
||||||
gameConfig, err := masterdata.LoadGameConfig()
|
gameConfig, err := masterdata.LoadGameConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -65,14 +57,7 @@ func main() {
|
|||||||
log.Fatalf("load quest catalog: %v", err)
|
log.Fatalf("load quest catalog: %v", err)
|
||||||
}
|
}
|
||||||
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig)
|
questHandler := questflow.NewQuestHandler(questCatalog, gameConfig)
|
||||||
userStore := memory.New(gametime.Now,
|
userStore := sqlite.New(db, gametime.Now)
|
||||||
memory.WithSnapshotDir(snapshotDir),
|
|
||||||
memory.WithSceneId(int32(*scene)),
|
|
||||||
memory.WithStarterItems(*starterItems),
|
|
||||||
)
|
|
||||||
if *scene != 0 {
|
|
||||||
log.Printf("bootstrap scene: %d (from snapshot)", *scene)
|
|
||||||
}
|
|
||||||
|
|
||||||
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
gachaEntries, medalInfo, err := masterdata.LoadGachaCatalog()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -99,7 +84,6 @@ func main() {
|
|||||||
gachaPool.BuildFeaturedMapping(gachaEntries)
|
gachaPool.BuildFeaturedMapping(gachaEntries)
|
||||||
gachaPool.BuildBannerPools(gachaEntries)
|
gachaPool.BuildBannerPools(gachaEntries)
|
||||||
masterdata.EnrichCatalogPromotions(gachaEntries, gachaPool)
|
masterdata.EnrichCatalogPromotions(gachaEntries, gachaPool)
|
||||||
userStore.ReplaceCatalog(gachaEntries)
|
|
||||||
|
|
||||||
dupExchange, err := masterdata.LoadDupExchange()
|
dupExchange, err := masterdata.LoadDupExchange()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -185,6 +169,7 @@ func main() {
|
|||||||
userStore,
|
userStore,
|
||||||
questHandler,
|
questHandler,
|
||||||
gachaHandler,
|
gachaHandler,
|
||||||
|
gachaEntries,
|
||||||
cageOrnamentCatalog,
|
cageOrnamentCatalog,
|
||||||
loginBonusCatalog,
|
loginBonusCatalog,
|
||||||
characterViewerCatalog,
|
characterViewerCatalog,
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
LUNAR_HOST: 127.0.0.1
|
LUNAR_HOST: 127.0.0.1
|
||||||
LUNAR_HTTP_PORT: 8080
|
LUNAR_HTTP_PORT: 8080
|
||||||
LUNAR_SCENE: 0
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./assets:/opt/lunar-tear/assets
|
- ./assets:/opt/lunar-tear/assets
|
||||||
|
- ./db:/opt/lunar-tear/db
|
||||||
ports:
|
ports:
|
||||||
- 443:443 # grpc, hardcoded by the client, not configurable
|
- 443:443 # grpc, hardcoded by the client, not configurable
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
./lunar-tear --host "${LUNAR_HOST}" --http-port "${LUNAR_HTTP_PORT}" --scene "${LUNAR_SCENE}"
|
mkdir -p db
|
||||||
|
goose -dir migrations sqlite3 db/game.db up
|
||||||
|
|
||||||
|
exec ./lunar-tear --host "${LUNAR_HOST}" --http-port "${LUNAR_HTTP_PORT}"
|
||||||
|
|||||||
+22
-6
@@ -1,17 +1,33 @@
|
|||||||
module lunar-tear/server
|
module lunar-tear/server
|
||||||
|
|
||||||
go 1.24.2
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
|
golang.org/x/net v0.50.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
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
github.com/pressly/goose/v3 v3.27.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // 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
|
||||||
|
modernc.org/libc v1.70.0 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
|
modernc.org/sqlite v1.48.2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+49
-14
@@ -1,5 +1,9 @@
|
|||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
@@ -10,33 +14,64 @@ 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/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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||||
|
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
|
||||||
|
github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||||
|
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
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.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
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-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
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-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||||
|
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
|
||||||
|
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Open(path string) (*sql.DB, error) {
|
||||||
|
if dir := filepath.Dir(path); dir != "." && dir != "" {
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return nil, fmt.Errorf("create db directory %q: %w", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open sqlite %q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pragmas := []string{
|
||||||
|
"PRAGMA journal_mode=WAL",
|
||||||
|
"PRAGMA foreign_keys=ON",
|
||||||
|
"PRAGMA busy_timeout=5000",
|
||||||
|
}
|
||||||
|
for _, p := range pragmas {
|
||||||
|
if _, err := db.Exec(p); err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, fmt.Errorf("exec %q: %w", p, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Ping(); err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, fmt.Errorf("ping sqlite %q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
@@ -35,6 +35,9 @@ type GameConfig struct {
|
|||||||
QuestSkipMaxCountAtOnce int32
|
QuestSkipMaxCountAtOnce int32
|
||||||
|
|
||||||
WeaponLimitBreakAvailableCount int32
|
WeaponLimitBreakAvailableCount int32
|
||||||
|
|
||||||
|
CostumeLotteryEffectUnlockSlotConsumeGold int32
|
||||||
|
CostumeLotteryEffectDrawSlotConsumeGold int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadGameConfig() (*GameConfig, error) {
|
func LoadGameConfig() (*GameConfig, error) {
|
||||||
@@ -73,6 +76,9 @@ func LoadGameConfig() (*GameConfig, error) {
|
|||||||
|
|
||||||
cfg.WeaponLimitBreakAvailableCount = parseInt32(kv, "WEAPON_LIMIT_BREAK_AVAILABLE_COUNT")
|
cfg.WeaponLimitBreakAvailableCount = parseInt32(kv, "WEAPON_LIMIT_BREAK_AVAILABLE_COUNT")
|
||||||
|
|
||||||
|
cfg.CostumeLotteryEffectUnlockSlotConsumeGold = parseInt32(kv, "COSTUME_LOTTERY_EFFECT_UNLOCK_SLOT_CONSUME_GOLD")
|
||||||
|
cfg.CostumeLotteryEffectDrawSlotConsumeGold = parseInt32(kv, "COSTUME_LOTTERY_EFFECT_DRAW_SLOT_CONSUME_GOLD")
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,31 @@ type CostumeActiveSkillEnhanceMaterialRow struct {
|
|||||||
SortOrder int32 `json:"SortOrder"`
|
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]CostumeMasterRow
|
||||||
Materials map[int32]MaterialRow
|
Materials map[int32]MaterialRow
|
||||||
@@ -96,6 +121,10 @@ type CostumeCatalog struct {
|
|||||||
ActiveSkillEnhanceMats map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow // key: [enhancementMaterialId, skillLevel]
|
ActiveSkillEnhanceMats map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow // 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]
|
||||||
|
LotteryEffectMats map[int32][]CostumeLotteryEffectMaterialGroupRow // key: materialGroupId (both unlock and draw)
|
||||||
|
LotteryEffectOdds map[int32][]CostumeLotteryEffectOddsRow // key: oddsGroupId
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
||||||
@@ -149,6 +178,19 @@ func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
|||||||
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")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load costume lottery effect table: %w", err)
|
||||||
|
}
|
||||||
|
lotteryEffectMatRows, err := utils.ReadJSON[CostumeLotteryEffectMaterialGroupRow]("EntityMCostumeLotteryEffectMaterialGroupTable.json")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load costume lottery effect material group table: %w", err)
|
||||||
|
}
|
||||||
|
lotteryEffectOddsRows, err := utils.ReadJSON[CostumeLotteryEffectOddsRow]("EntityMCostumeLotteryEffectOddsGroupTable.json")
|
||||||
|
if err != nil {
|
||||||
|
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]CostumeMasterRow, len(costumes)),
|
||||||
Materials: matCatalog.ByType[model.MaterialTypeCostumeEnhancement],
|
Materials: matCatalog.ByType[model.MaterialTypeCostumeEnhancement],
|
||||||
@@ -167,6 +209,10 @@ func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
|||||||
ActiveSkillEnhanceMats: make(map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow),
|
ActiveSkillEnhanceMats: make(map[[2]int32][]CostumeActiveSkillEnhanceMaterialRow),
|
||||||
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)),
|
||||||
|
LotteryEffectMats: make(map[int32][]CostumeLotteryEffectMaterialGroupRow),
|
||||||
|
LotteryEffectOdds: make(map[int32][]CostumeLotteryEffectOddsRow),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, row := range costumes {
|
for _, row := range costumes {
|
||||||
@@ -242,5 +288,18 @@ func LoadCostumeCatalog(matCatalog *MaterialCatalog) (*CostumeCatalog, error) {
|
|||||||
catalog.ActiveSkillEnhanceMats[key] = append(catalog.ActiveSkillEnhanceMats[key], row)
|
catalog.ActiveSkillEnhanceMats[key] = append(catalog.ActiveSkillEnhanceMats[key], row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, row := range lotteryEffectRows {
|
||||||
|
key := [2]int32{row.CostumeId, row.SlotNumber}
|
||||||
|
catalog.LotteryEffects[key] = row
|
||||||
|
}
|
||||||
|
for _, row := range lotteryEffectMatRows {
|
||||||
|
gid := row.CostumeLotteryEffectMaterialGroupId
|
||||||
|
catalog.LotteryEffectMats[gid] = append(catalog.LotteryEffectMats[gid], row)
|
||||||
|
}
|
||||||
|
for _, row := range lotteryEffectOddsRows {
|
||||||
|
gid := row.CostumeLotteryEffectOddsGroupId
|
||||||
|
catalog.LotteryEffectOdds[gid] = append(catalog.LotteryEffectOdds[gid], row)
|
||||||
|
}
|
||||||
|
|
||||||
return catalog, nil
|
return catalog, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,14 @@ const (
|
|||||||
CostumeAwakenEffectTypeItemAcquire CostumeAwakenEffectType = 3
|
CostumeAwakenEffectTypeItemAcquire CostumeAwakenEffectType = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CostumeLotteryEffectType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
CostumeLotteryEffectTypeUnknown CostumeLotteryEffectType = 0
|
||||||
|
CostumeLotteryEffectTypeAbility CostumeLotteryEffectType = 1
|
||||||
|
CostumeLotteryEffectTypeStatusUp CostumeLotteryEffectType = 2
|
||||||
|
)
|
||||||
|
|
||||||
type WeaponAwakenEffectType int32
|
type WeaponAwakenEffectType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"lunar-tear/server/internal/gameutil"
|
"lunar-tear/server/internal/gameutil"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
@@ -276,7 +278,7 @@ func (h *QuestHandler) grantCompanion(user *store.UserState, companionId int32,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf("reward-companion-%d", companionId)
|
key := uuid.New().String()
|
||||||
user.Companions[key] = store.CompanionState{
|
user.Companions[key] = store.CompanionState{
|
||||||
UserCompanionUuid: key,
|
UserCompanionUuid: key,
|
||||||
CompanionId: companionId,
|
CompanionId: companionId,
|
||||||
@@ -306,7 +308,7 @@ func (h *QuestHandler) grantParts(user *store.UserState, partsId int32, nowMilli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf("reward-parts-%d", partsId)
|
key := uuid.New().String()
|
||||||
user.Parts[key] = store.PartsState{
|
user.Parts[key] = store.PartsState{
|
||||||
UserPartsUuid: key,
|
UserPartsUuid: key,
|
||||||
PartsId: partsId,
|
PartsId: partsId,
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ import (
|
|||||||
|
|
||||||
type BannerServiceServer struct {
|
type BannerServiceServer struct {
|
||||||
pb.UnimplementedBannerServiceServer
|
pb.UnimplementedBannerServiceServer
|
||||||
gacha store.GachaRepository
|
catalog []store.GachaCatalogEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBannerServiceServer(gacha store.GachaRepository) *BannerServiceServer {
|
func NewBannerServiceServer(catalog []store.GachaCatalogEntry) *BannerServiceServer {
|
||||||
return &BannerServiceServer{gacha: gacha}
|
return &BannerServiceServer{catalog: catalog}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BannerServiceServer) GetMamaBanner(ctx context.Context, req *pb.GetMamaBannerRequest) (*pb.GetMamaBannerResponse, error) {
|
func (s *BannerServiceServer) GetMamaBanner(ctx context.Context, req *pb.GetMamaBannerRequest) (*pb.GetMamaBannerResponse, error) {
|
||||||
catalog, _ := s.gacha.SnapshotCatalog()
|
catalog := s.catalog
|
||||||
var termLimited []*pb.GachaBanner
|
var termLimited []*pb.GachaBanner
|
||||||
var latestChapter *pb.GachaBanner
|
var latestChapter *pb.GachaBanner
|
||||||
for _, entry := range catalog {
|
for _, entry := range catalog {
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ 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.SelectTables(
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
|
||||||
userdata.FullClientTableMap(user),
|
|
||||||
[]string{
|
[]string{
|
||||||
"IUserMaterial", "IUserConsumableItem", "IUserGem",
|
"IUserMaterial", "IUserConsumableItem", "IUserGem",
|
||||||
"IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter",
|
"IUserCostume", "IUserCostumeActiveSkill", "IUserCharacter",
|
||||||
@@ -82,8 +81,7 @@ func (s *CageOrnamentServiceServer) RecordAccess(ctx context.Context, req *pb.Re
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
|
||||||
userdata.FullClientTableMap(user),
|
|
||||||
[]string{"IUserCageOrnamentReward"},
|
[]string{"IUserCageOrnamentReward"},
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
|
|||||||
return &pb.RebirthResponse{}, nil
|
return &pb.RebirthResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
|
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ func (s *CharacterServiceServer) Rebirth(ctx context.Context, req *pb.RebirthReq
|
|||||||
}
|
}
|
||||||
|
|
||||||
rebirthTables := []string{"IUserCharacterRebirth", "IUserMaterial", "IUserConsumableItem"}
|
rebirthTables := []string{"IUserCharacterRebirth", "IUserMaterial", "IUserConsumableItem"}
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), rebirthTables)
|
tables := userdata.ProjectTables(snapshot, rebirthTables)
|
||||||
diff := tracker.Apply(snapshot, tables)
|
diff := tracker.Apply(snapshot, tables)
|
||||||
|
|
||||||
return &pb.RebirthResponse{DiffUserData: diff}, nil
|
return &pb.RebirthResponse{DiffUserData: diff}, nil
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.
|
|||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}).
|
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}).
|
||||||
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
|
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
|
||||||
@@ -54,7 +54,7 @@ func (s *CharacterBoardServiceServer) ReleasePanel(ctx context.Context, req *pb.
|
|||||||
"IUserConsumableItem",
|
"IUserConsumableItem",
|
||||||
"IUserGem",
|
"IUserGem",
|
||||||
}
|
}
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(user), boardTables)
|
tables := userdata.ProjectTables(user, boardTables)
|
||||||
diff := tracker.Apply(user, tables)
|
diff := tracker.Apply(user, tables)
|
||||||
|
|
||||||
return &pb.ReleasePanelResponse{DiffUserData: diff}, nil
|
return &pb.ReleasePanelResponse{DiffUserData: diff}, nil
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func (s *CharacterViewerServiceServer) CharacterViewerTop(ctx context.Context, _
|
|||||||
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.SnapshotUser(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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,8 +77,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, companionDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, companionDiffTables))
|
|
||||||
|
|
||||||
return &pb.CompanionEnhanceResponse{
|
return &pb.CompanionEnhanceResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (s *ConsumableItemServiceServer) Sell(ctx context.Context, req *pb.Consumab
|
|||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
|
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"})
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ 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.SelectTables(userdata.FullClientTableMap(snapshot), []string{"IUserConsumableItem"})
|
tables := userdata.ProjectTables(snapshot, []string{"IUserConsumableItem"})
|
||||||
diff := tracker.Apply(snapshot, tables)
|
diff := tracker.Apply(snapshot, tables)
|
||||||
|
|
||||||
return &pb.ConsumableItemSellResponse{
|
return &pb.ConsumableItemSellResponse{
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ func (s *ContentsStoryServiceServer) RegisterPlayed(ctx context.Context, req *pb
|
|||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserContentsStory"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserContentsStory"}))
|
|
||||||
|
|
||||||
return &pb.ContentsStoryRegisterPlayedResponse{
|
return &pb.ContentsStoryRegisterPlayedResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
@@ -95,8 +98,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, costumeDiffTables))
|
|
||||||
|
|
||||||
return &pb.EnhanceResponse{
|
return &pb.EnhanceResponse{
|
||||||
IsGreatSuccess: false,
|
IsGreatSuccess: false,
|
||||||
@@ -177,8 +179,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, awakenDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, awakenDiffTables))
|
|
||||||
|
|
||||||
return &pb.AwakenResponse{
|
return &pb.AwakenResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
@@ -229,10 +230,12 @@ func (s *CostumeServiceServer) applyAwakenItemAcquire(user *store.UserState, ite
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf("awaken-thought-%d", acq.PossessionId)
|
for _, t := range user.Thoughts {
|
||||||
if _, exists := user.Thoughts[key]; exists {
|
if t.ThoughtId == acq.PossessionId {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
key := uuid.New().String()
|
||||||
user.Thoughts[key] = store.ThoughtState{
|
user.Thoughts[key] = store.ThoughtState{
|
||||||
UserThoughtUuid: key,
|
UserThoughtUuid: key,
|
||||||
ThoughtId: acq.PossessionId,
|
ThoughtId: acq.PossessionId,
|
||||||
@@ -329,8 +332,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, activeSkillDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, activeSkillDiffTables))
|
|
||||||
|
|
||||||
return &pb.EnhanceActiveSkillResponse{
|
return &pb.EnhanceActiveSkillResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
@@ -387,10 +389,216 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, costumeDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, costumeDiffTables))
|
|
||||||
|
|
||||||
return &pb.LimitBreakResponse{
|
return &pb.LimitBreakResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lotteryEffectDiffTables = []string{
|
||||||
|
"IUserCostume",
|
||||||
|
"IUserCostumeLotteryEffect",
|
||||||
|
"IUserCostumeLotteryEffectAbility",
|
||||||
|
"IUserCostumeLotteryEffectStatusUp",
|
||||||
|
"IUserCostumeLotteryEffectPending",
|
||||||
|
"IUserConsumableItem",
|
||||||
|
"IUserMaterial",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CostumeServiceServer) UnlockLotteryEffectSlot(ctx context.Context, req *pb.UnlockLotteryEffectSlotRequest) (*pb.UnlockLotteryEffectSlotResponse, error) {
|
||||||
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
||||||
|
|
||||||
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
|
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
effectRow, ok := s.catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= s.config.CostumeLotteryEffectUnlockSlotConsumeGold
|
||||||
|
|
||||||
|
mats := s.catalog.LotteryEffectMats[effectRow.CostumeLotteryEffectUnlockMaterialGroupId]
|
||||||
|
for _, mat := range mats {
|
||||||
|
cur := user.Materials[mat.MaterialId]
|
||||||
|
cost := mat.Count
|
||||||
|
if cur < cost {
|
||||||
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: insufficient material id=%d have=%d need=%d", mat.MaterialId, cur, cost)
|
||||||
|
cost = cur
|
||||||
|
}
|
||||||
|
user.Materials[mat.MaterialId] = cur - cost
|
||||||
|
}
|
||||||
|
|
||||||
|
key := store.CostumeLotteryEffectKey{
|
||||||
|
UserCostumeUuid: req.UserCostumeUuid,
|
||||||
|
SlotNumber: req.SlotNumber,
|
||||||
|
}
|
||||||
|
user.CostumeLotteryEffects[key] = store.CostumeLotteryEffectState{
|
||||||
|
UserCostumeUuid: req.UserCostumeUuid,
|
||||||
|
SlotNumber: req.SlotNumber,
|
||||||
|
OddsNumber: 0,
|
||||||
|
LatestVersion: nowMillis,
|
||||||
|
}
|
||||||
|
|
||||||
|
costume.CostumeLotteryEffectUnlockedSlotCount++
|
||||||
|
costume.LatestVersion = nowMillis
|
||||||
|
user.Costumes[req.UserCostumeUuid] = costume
|
||||||
|
log.Printf("[CostumeService] UnlockLotteryEffectSlot: costumeId=%d slot=%d unlocked slotCount=%d", costume.CostumeId, req.SlotNumber, costume.CostumeLotteryEffectUnlockedSlotCount)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("costume unlock lottery effect slot: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
|
||||||
|
|
||||||
|
return &pb.UnlockLotteryEffectSlotResponse{
|
||||||
|
DiffUserData: diff,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CostumeServiceServer) DrawLotteryEffect(ctx context.Context, req *pb.DrawLotteryEffectRequest) (*pb.DrawLotteryEffectResponse, error) {
|
||||||
|
log.Printf("[CostumeService] DrawLotteryEffect: uuid=%s slot=%d", req.UserCostumeUuid, req.SlotNumber)
|
||||||
|
|
||||||
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
|
tracker := userdata.NewDeleteTracker().
|
||||||
|
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"}).
|
||||||
|
Track("IUserConsumableItem", oldUser, userdata.SortedConsumableItemRecords, []string{"userId", "consumableItemId"}).
|
||||||
|
Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"})
|
||||||
|
|
||||||
|
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
costume, ok := user.Costumes[req.UserCostumeUuid]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("[CostumeService] DrawLotteryEffect: costume uuid=%s not found", req.UserCostumeUuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
effectRow, ok := s.catalog.LotteryEffects[[2]int32{costume.CostumeId, req.SlotNumber}]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("[CostumeService] DrawLotteryEffect: no lottery effect for costumeId=%d slot=%d", costume.CostumeId, req.SlotNumber)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oddsPool := s.catalog.LotteryEffectOdds[effectRow.CostumeLotteryEffectOddsGroupId]
|
||||||
|
if len(oddsPool) == 0 {
|
||||||
|
log.Printf("[CostumeService] DrawLotteryEffect: empty odds pool for groupId=%d", effectRow.CostumeLotteryEffectOddsGroupId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user.ConsumableItems[s.config.ConsumableItemIdForGold] -= s.config.CostumeLotteryEffectDrawSlotConsumeGold
|
||||||
|
|
||||||
|
mats := s.catalog.LotteryEffectMats[effectRow.CostumeLotteryEffectDrawMaterialGroupId]
|
||||||
|
for _, mat := range mats {
|
||||||
|
cur := user.Materials[mat.MaterialId]
|
||||||
|
cost := mat.Count
|
||||||
|
if cur < cost {
|
||||||
|
log.Printf("[CostumeService] DrawLotteryEffect: insufficient material id=%d have=%d need=%d", mat.MaterialId, cur, cost)
|
||||||
|
cost = cur
|
||||||
|
}
|
||||||
|
user.Materials[mat.MaterialId] = cur - cost
|
||||||
|
}
|
||||||
|
|
||||||
|
totalWeight := int32(0)
|
||||||
|
for _, row := range oddsPool {
|
||||||
|
totalWeight += row.Weight
|
||||||
|
}
|
||||||
|
roll := rand.Int31n(totalWeight)
|
||||||
|
var picked masterdata.CostumeLotteryEffectOddsRow
|
||||||
|
for _, row := range oddsPool {
|
||||||
|
roll -= row.Weight
|
||||||
|
if roll < 0 {
|
||||||
|
picked = row
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key := store.CostumeLotteryEffectKey{
|
||||||
|
UserCostumeUuid: req.UserCostumeUuid,
|
||||||
|
SlotNumber: req.SlotNumber,
|
||||||
|
}
|
||||||
|
existing := user.CostumeLotteryEffects[key]
|
||||||
|
if existing.OddsNumber == 0 {
|
||||||
|
existing.UserCostumeUuid = req.UserCostumeUuid
|
||||||
|
existing.SlotNumber = req.SlotNumber
|
||||||
|
existing.OddsNumber = picked.OddsNumber
|
||||||
|
existing.LatestVersion = nowMillis
|
||||||
|
user.CostumeLotteryEffects[key] = existing
|
||||||
|
} else {
|
||||||
|
user.CostumeLotteryEffectPending[req.UserCostumeUuid] = store.CostumeLotteryEffectPendingState{
|
||||||
|
UserCostumeUuid: req.UserCostumeUuid,
|
||||||
|
SlotNumber: req.SlotNumber,
|
||||||
|
OddsNumber: picked.OddsNumber,
|
||||||
|
LatestVersion: nowMillis,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[CostumeService] DrawLotteryEffect: costumeId=%d slot=%d drew oddsNumber=%d type=%d targetId=%d firstDraw=%v",
|
||||||
|
costume.CostumeId, req.SlotNumber, picked.OddsNumber, picked.CostumeLotteryEffectType, picked.CostumeLotteryEffectTargetId, existing.OddsNumber == 0)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("costume draw lottery effect: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
|
||||||
|
|
||||||
|
return &pb.DrawLotteryEffectResponse{
|
||||||
|
DiffUserData: diff,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CostumeServiceServer) ConfirmLotteryEffect(ctx context.Context, req *pb.ConfirmLotteryEffectRequest) (*pb.ConfirmLotteryEffectResponse, error) {
|
||||||
|
log.Printf("[CostumeService] ConfirmLotteryEffect: uuid=%s accept=%v", req.UserCostumeUuid, req.IsAccept)
|
||||||
|
|
||||||
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
|
tracker := userdata.NewDeleteTracker().
|
||||||
|
Track("IUserCostumeLotteryEffectPending", oldUser, userdata.SortedCostumeLotteryEffectPendingRecords, []string{"userId", "userCostumeUuid"})
|
||||||
|
|
||||||
|
snapshot, err := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
|
pending, ok := user.CostumeLotteryEffectPending[req.UserCostumeUuid]
|
||||||
|
if !ok {
|
||||||
|
log.Printf("[CostumeService] ConfirmLotteryEffect: no pending for uuid=%s", req.UserCostumeUuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.IsAccept {
|
||||||
|
key := store.CostumeLotteryEffectKey{
|
||||||
|
UserCostumeUuid: pending.UserCostumeUuid,
|
||||||
|
SlotNumber: pending.SlotNumber,
|
||||||
|
}
|
||||||
|
effect := user.CostumeLotteryEffects[key]
|
||||||
|
effect.UserCostumeUuid = pending.UserCostumeUuid
|
||||||
|
effect.SlotNumber = pending.SlotNumber
|
||||||
|
effect.OddsNumber = pending.OddsNumber
|
||||||
|
effect.LatestVersion = nowMillis
|
||||||
|
user.CostumeLotteryEffects[key] = effect
|
||||||
|
log.Printf("[CostumeService] ConfirmLotteryEffect: accepted oddsNumber=%d for slot=%d", pending.OddsNumber, pending.SlotNumber)
|
||||||
|
} else {
|
||||||
|
log.Printf("[CostumeService] ConfirmLotteryEffect: rejected oddsNumber=%d for slot=%d", pending.OddsNumber, pending.SlotNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(user.CostumeLotteryEffectPending, req.UserCostumeUuid)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("costume confirm lottery effect: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := tracker.Apply(snapshot, userdata.ProjectTables(snapshot, lotteryEffectDiffTables))
|
||||||
|
|
||||||
|
return &pb.ConfirmLotteryEffectResponse{
|
||||||
|
DiffUserData: diff,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ func (s *DataServiceServer) GetUserData(ctx context.Context, req *pb.UserDataGet
|
|||||||
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.SnapshotUser(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)
|
||||||
}
|
}
|
||||||
|
|
||||||
defaults := userdata.FirstEntranceClientTableMap(user)
|
defaults := userdata.FullClientTableMap(user)
|
||||||
result := userdata.SelectTables(defaults, req.TableName)
|
result := userdata.SelectTables(defaults, req.TableName)
|
||||||
return &pb.UserDataGetResponse{
|
return &pb.UserDataGetResponse{
|
||||||
UserDataJson: result,
|
UserDataJson: result,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func (s *DeckServiceServer) UpdateName(ctx context.Context, req *pb.UpdateNameRe
|
|||||||
user.Decks[deckKey] = deck
|
user.Decks[deckKey] = deck
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.SelectTables(userdata.FullClientTableMap(user), []string{"IUserDeck"})
|
result := userdata.ProjectTables(user, []string{"IUserDeck"})
|
||||||
return &pb.UpdateNameResponse{
|
return &pb.UpdateNameResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(result),
|
DiffUserData: userdata.BuildDiffFromTables(result),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -81,7 +81,7 @@ func (s *DeckServiceServer) RefreshDeckPower(ctx context.Context, req *pb.Refres
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.SelectTables(userdata.FullClientTableMap(user), []string{
|
result := userdata.ProjectTables(user, []string{
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
|
"IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
|
||||||
})
|
})
|
||||||
return &pb.RefreshDeckPowerResponse{
|
return &pb.RefreshDeckPowerResponse{
|
||||||
@@ -133,7 +133,7 @@ func (s *DeckServiceServer) RefreshMultiDeckPower(ctx context.Context, req *pb.R
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.SelectTables(userdata.FullClientTableMap(user), []string{
|
result := userdata.ProjectTables(user, []string{
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
|
"IUserDeck", "IUserDeckCharacter", "IUserDeckTypeNote",
|
||||||
})
|
})
|
||||||
return &pb.RefreshMultiDeckPowerResponse{
|
return &pb.RefreshMultiDeckPowerResponse{
|
||||||
@@ -173,7 +173,7 @@ func (s *DeckServiceServer) ReplaceDeck(ctx context.Context, req *pb.ReplaceDeck
|
|||||||
}
|
}
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
|
Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
|
||||||
[]string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
|
[]string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
|
||||||
@@ -189,7 +189,7 @@ func (s *DeckServiceServer) ReplaceDeck(ctx context.Context, req *pb.ReplaceDeck
|
|||||||
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.SelectTables(userdata.FullClientTableMap(user), []string{
|
result := userdata.ProjectTables(user, []string{
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
|
"IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
|
||||||
"IUserDeckCharacterDressupCostume",
|
"IUserDeckCharacterDressupCostume",
|
||||||
})
|
})
|
||||||
@@ -202,7 +202,7 @@ func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.Repla
|
|||||||
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.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
|
Track("IUserDeckSubWeaponGroup", oldUser, userdata.DeckSubWeaponRecords,
|
||||||
[]string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
|
[]string{"userId", "userDeckCharacterUuid", "userWeaponUuid"}).
|
||||||
@@ -231,7 +231,7 @@ func (s *DeckServiceServer) ReplaceTripleDeck(ctx context.Context, req *pb.Repla
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
result := userdata.SelectTables(userdata.FullClientTableMap(user), []string{
|
result := userdata.ProjectTables(user, []string{
|
||||||
"IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
|
"IUserDeck", "IUserDeckCharacter", "IUserDeckSubWeaponGroup", "IUserDeckPartsGroup",
|
||||||
"IUserDeckCharacterDressupCostume",
|
"IUserDeckCharacterDressupCostume",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserDokan"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserDokan"}))
|
|
||||||
|
|
||||||
return &pb.RegisterDokanConfirmedResponse{
|
return &pb.RegisterDokanConfirmedResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -71,8 +71,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, exploreDiffTables))
|
|
||||||
|
|
||||||
return &pb.StartExploreResponse{
|
return &pb.StartExploreResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
@@ -124,8 +123,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, exploreFinishDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, exploreFinishDiffTables))
|
|
||||||
|
|
||||||
rewards := []*pb.ExploreReward{
|
rewards := []*pb.ExploreReward{
|
||||||
{
|
{
|
||||||
@@ -161,8 +159,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserExplore"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserExplore"}))
|
|
||||||
|
|
||||||
return &pb.RetireExploreResponse{
|
return &pb.RetireExploreResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -34,20 +34,20 @@ type GachaServiceServer struct {
|
|||||||
pb.UnimplementedGachaServiceServer
|
pb.UnimplementedGachaServiceServer
|
||||||
users store.UserRepository
|
users store.UserRepository
|
||||||
sessions store.SessionRepository
|
sessions store.SessionRepository
|
||||||
gacha store.GachaRepository
|
catalog []store.GachaCatalogEntry
|
||||||
handler *gacha.GachaHandler
|
handler *gacha.GachaHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGachaServiceServer(
|
func NewGachaServiceServer(
|
||||||
users store.UserRepository,
|
users store.UserRepository,
|
||||||
sessions store.SessionRepository,
|
sessions store.SessionRepository,
|
||||||
gachaRepo store.GachaRepository,
|
catalog []store.GachaCatalogEntry,
|
||||||
handler *gacha.GachaHandler,
|
handler *gacha.GachaHandler,
|
||||||
) *GachaServiceServer {
|
) *GachaServiceServer {
|
||||||
return &GachaServiceServer{
|
return &GachaServiceServer{
|
||||||
users: users,
|
users: users,
|
||||||
sessions: sessions,
|
sessions: sessions,
|
||||||
gacha: gachaRepo,
|
catalog: catalog,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ func NewGachaServiceServer(
|
|||||||
func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaListRequest) (*pb.GetGachaListResponse, error) {
|
func (s *GachaServiceServer) GetGachaList(ctx context.Context, req *pb.GetGachaListRequest) (*pb.GetGachaListResponse, error) {
|
||||||
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
|
log.Printf("[GachaService] GetGachaList: labels=%v", req.GachaLabelType)
|
||||||
|
|
||||||
catalog, _ := s.gacha.SnapshotCatalog()
|
catalog := s.catalog
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
@@ -132,10 +132,10 @@ func (s *GachaServiceServer) autoConvertExpiredMedals(user *store.UserState, cat
|
|||||||
func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaRequest) (*pb.GetGachaResponse, error) {
|
func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaRequest) (*pb.GetGachaResponse, error) {
|
||||||
log.Printf("[GachaService] GetGacha: ids=%v", req.GachaId)
|
log.Printf("[GachaService] GetGacha: ids=%v", req.GachaId)
|
||||||
|
|
||||||
catalog, _ := s.gacha.SnapshotCatalog()
|
catalog := s.catalog
|
||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.SnapshotUser(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)
|
||||||
}
|
}
|
||||||
@@ -160,8 +160,7 @@ func (s *GachaServiceServer) GetGacha(ctx context.Context, req *pb.GetGachaReque
|
|||||||
func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb.DrawResponse, error) {
|
func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb.DrawResponse, error) {
|
||||||
log.Printf("[GachaService] Draw: gachaId=%d phaseId=%d execCount=%d", req.GachaId, req.GachaPricePhaseId, req.ExecCount)
|
log.Printf("[GachaService] Draw: gachaId=%d phaseId=%d execCount=%d", req.GachaId, req.GachaPricePhaseId, req.ExecCount)
|
||||||
|
|
||||||
catalog, _ := s.gacha.SnapshotCatalog()
|
entry := findCatalogEntry(s.catalog, req.GachaId)
|
||||||
entry := findCatalogEntry(catalog, req.GachaId)
|
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||||
}
|
}
|
||||||
@@ -293,8 +292,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
|
|||||||
|
|
||||||
changedStoryIds := s.handler.Granter.DrainChangedStoryWeaponIds()
|
changedStoryIds := s.handler.Granter.DrainChangedStoryWeaponIds()
|
||||||
diffOrder := append(gachaDiffTables, "IUserWeaponStory")
|
diffOrder := append(gachaDiffTables, "IUserWeaponStory")
|
||||||
allTables := userdata.FullClientTableMap(updatedUser)
|
diff := userdata.BuildDiffFromTablesOrdered(userdata.ProjectTables(updatedUser, diffOrder), diffOrder)
|
||||||
diff := userdata.BuildDiffFromTablesOrdered(userdata.SelectTables(allTables, diffOrder), diffOrder)
|
|
||||||
userdata.AddWeaponStoryDiff(diff, updatedUser, changedStoryIds)
|
userdata.AddWeaponStoryDiff(diff, updatedUser, changedStoryIds)
|
||||||
|
|
||||||
return &pb.DrawResponse{
|
return &pb.DrawResponse{
|
||||||
@@ -309,8 +307,7 @@ func (s *GachaServiceServer) Draw(ctx context.Context, req *pb.DrawRequest) (*pb
|
|||||||
func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBoxGachaRequest) (*pb.ResetBoxGachaResponse, error) {
|
func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBoxGachaRequest) (*pb.ResetBoxGachaResponse, error) {
|
||||||
log.Printf("[GachaService] ResetBoxGacha: gachaId=%d", req.GachaId)
|
log.Printf("[GachaService] ResetBoxGacha: gachaId=%d", req.GachaId)
|
||||||
|
|
||||||
catalog, _ := s.gacha.SnapshotCatalog()
|
entry := findCatalogEntry(s.catalog, req.GachaId)
|
||||||
entry := findCatalogEntry(catalog, req.GachaId)
|
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
return nil, fmt.Errorf("gacha %d not found", req.GachaId)
|
||||||
}
|
}
|
||||||
@@ -336,7 +333,7 @@ func (s *GachaServiceServer) ResetBoxGacha(ctx context.Context, req *pb.ResetBox
|
|||||||
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.SnapshotUser(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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func (s *GiftServiceServer) GetGiftList(ctx context.Context, req *pb.GetGiftList
|
|||||||
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.SnapshotUser(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)
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ func (s *GiftServiceServer) GetGiftList(ctx context.Context, req *pb.GetGiftList
|
|||||||
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.SnapshotUser(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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func (s *GimmickServiceServer) UpdateSequence(ctx context.Context, req *pb.Updat
|
|||||||
user.Gimmick.Sequences[key] = sequence
|
user.Gimmick.Sequences[key] = sequence
|
||||||
})
|
})
|
||||||
return &pb.UpdateSequenceResponse{
|
return &pb.UpdateSequenceResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), []string{"IUserGimmickSequence"})),
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickSequence"})),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ 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.SelectTables(userdata.FullClientTableMap(user), []string{
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{
|
||||||
"IUserGimmick",
|
"IUserGimmick",
|
||||||
"IUserGimmickOrnamentProgress",
|
"IUserGimmickOrnamentProgress",
|
||||||
})),
|
})),
|
||||||
@@ -98,7 +98,7 @@ func (s *GimmickServiceServer) InitSequenceSchedule(ctx context.Context, _ *empt
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
return &pb.InitSequenceScheduleResponse{
|
return &pb.InitSequenceScheduleResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), gimmickDiffTables)),
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, gimmickDiffTables)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +119,6 @@ func (s *GimmickServiceServer) Unlock(ctx context.Context, req *pb.UnlockRequest
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
return &pb.UnlockResponse{
|
return &pb.UnlockResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), []string{"IUserGimmickUnlock"})),
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserGimmickUnlock"})),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
pb "lunar-tear/server/gen/proto"
|
pb "lunar-tear/server/gen/proto"
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/masterdata"
|
"lunar-tear/server/internal/masterdata"
|
||||||
@@ -55,7 +56,7 @@ func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb
|
|||||||
GrantDatetime: now,
|
GrantDatetime: now,
|
||||||
},
|
},
|
||||||
ExpirationDatetime: now + int64(30*24*time.Hour/time.Millisecond),
|
ExpirationDatetime: now + int64(30*24*time.Hour/time.Millisecond),
|
||||||
UserGiftUuid: fmt.Sprintf("login-bonus-%d-%d", userId, nextStamp),
|
UserGiftUuid: uuid.New().String(),
|
||||||
})
|
})
|
||||||
user.Notifications.GiftNotReceiveCount = int32(len(user.Gifts.NotReceived))
|
user.Notifications.GiftNotReceiveCount = int32(len(user.Gifts.NotReceived))
|
||||||
user.LoginBonus.CurrentStampNumber = nextStamp
|
user.LoginBonus.CurrentStampNumber = nextStamp
|
||||||
@@ -63,8 +64,7 @@ func (s *LoginBonusServiceServer) ReceiveStamp(ctx context.Context, req *emptypb
|
|||||||
user.LoginBonus.LatestVersion = now
|
user.LoginBonus.LatestVersion = now
|
||||||
})
|
})
|
||||||
|
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user,
|
||||||
userdata.FullClientTableMap(user),
|
|
||||||
[]string{"IUserLoginBonus"},
|
[]string{"IUserLoginBonus"},
|
||||||
))
|
))
|
||||||
setCommonResponseTrailers(ctx, diff, false)
|
setCommonResponseTrailers(ctx, diff, false)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (s *MaterialServiceServer) Sell(ctx context.Context, req *pb.MaterialSellRe
|
|||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
|
Track("IUserMaterial", oldUser, userdata.SortedMaterialRecords, []string{"userId", "materialId"})
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ 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.SelectTables(userdata.FullClientTableMap(snapshot), materialDiffTables)
|
tables := userdata.ProjectTables(snapshot, materialDiffTables)
|
||||||
diff := tracker.Apply(snapshot, tables)
|
diff := tracker.Apply(snapshot, tables)
|
||||||
|
|
||||||
return &pb.MaterialSellResponse{
|
return &pb.MaterialSellResponse{
|
||||||
|
|||||||
@@ -24,13 +24,12 @@ func (s *MissionServiceServer) UpdateMissionProgress(ctx context.Context, req *p
|
|||||||
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.SnapshotUser(userId)
|
snapshot, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMission"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserMission"}))
|
|
||||||
|
|
||||||
return &pb.UpdateMissionProgressResponse{
|
return &pb.UpdateMissionProgressResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserMovie"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserMovie"}))
|
|
||||||
|
|
||||||
return &pb.SaveViewedMovieResponse{
|
return &pb.SaveViewedMovieResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ func (s *NaviCutInServiceServer) RegisterPlayed(ctx context.Context, req *pb.Reg
|
|||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserNaviCutIn"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserNaviCutIn"}))
|
|
||||||
|
|
||||||
return &pb.RegisterPlayedResponse{
|
return &pb.RegisterPlayedResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ 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.SnapshotUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &pb.GetHeaderNotificationResponse{
|
return &pb.GetHeaderNotificationResponse{
|
||||||
GiftNotReceiveCount: 0,
|
GiftNotReceiveCount: 0,
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ func (s *OmikujiServiceServer) OmikujiDraw(ctx context.Context, req *pb.OmikujiD
|
|||||||
return nil, fmt.Errorf("update user: %w", err)
|
return nil, fmt.Errorf("update user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserOmikuji"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserOmikuji"}))
|
|
||||||
|
|
||||||
return &pb.OmikujiDrawResponse{
|
return &pb.OmikujiDrawResponse{
|
||||||
OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId),
|
OmikujiResultAssetId: s.catalog.LookupAssetId(req.OmikujiId),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func (s *PartsServiceServer) Sell(ctx context.Context, req *pb.PartsSellRequest)
|
|||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserParts", oldUser, userdata.SortedPartsRecords, []string{"userId", "userPartsUuid"})
|
Track("IUserParts", oldUser, userdata.SortedPartsRecords, []string{"userId", "userPartsUuid"})
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ 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.SelectTables(userdata.FullClientTableMap(snapshot), partsDiffTables)
|
tables := userdata.ProjectTables(snapshot, partsDiffTables)
|
||||||
diff := tracker.Apply(snapshot, tables)
|
diff := tracker.Apply(snapshot, tables)
|
||||||
|
|
||||||
return &pb.PartsSellResponse{
|
return &pb.PartsSellResponse{
|
||||||
@@ -158,8 +158,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, partsDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, partsDiffTables))
|
|
||||||
|
|
||||||
return &pb.PartsEnhanceResponse{
|
return &pb.PartsEnhanceResponse{
|
||||||
IsSuccess: isSuccess,
|
IsSuccess: isSuccess,
|
||||||
@@ -187,8 +186,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserPartsPreset"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserPartsPreset"}))
|
|
||||||
|
|
||||||
return &pb.PartsReplacePresetResponse{
|
return &pb.PartsReplacePresetResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ func (s *PortalCageServiceServer) UpdatePortalCageSceneProgress(ctx context.Cont
|
|||||||
user.PortalCageStatus.LatestVersion = now
|
user.PortalCageStatus.LatestVersion = now
|
||||||
})
|
})
|
||||||
|
|
||||||
tables := userdata.SelectTables(
|
tables := userdata.ProjectTables(user,
|
||||||
userdata.FullClientTableMap(user),
|
|
||||||
[]string{"IUserPortalCageStatus"},
|
[]string{"IUserPortalCageStatus"},
|
||||||
)
|
)
|
||||||
return &pb.UpdatePortalCageSceneProgressResponse{
|
return &pb.UpdatePortalCageSceneProgressResponse{
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ var bigHuntDiffTables = []string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildBigHuntDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
func buildBigHuntDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(user), tableNames)
|
tables := userdata.ProjectTables(user, tableNames)
|
||||||
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ func (s *BigHuntServiceServer) GetBigHuntTopData(ctx context.Context, _ *emptypb
|
|||||||
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.SnapshotUser(userId)
|
user, _ := s.users.LoadUser(userId)
|
||||||
|
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
weeklyVersion := gametime.WeeklyVersion(nowMillis)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func NewQuestServiceServer(users store.UserRepository, sessions store.SessionRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildSelectedQuestDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
func buildSelectedQuestDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(user), tableNames)
|
tables := userdata.ProjectTables(user, tableNames)
|
||||||
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func NewSideStoryQuestServiceServer(users store.UserRepository, sessions store.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildSideStoryDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
func buildSideStoryDiff(user store.UserState, tableNames []string) map[string]*pb.DiffData {
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(user), tableNames)
|
tables := userdata.ProjectTables(user, tableNames)
|
||||||
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
return userdata.BuildDiffFromTablesOrdered(tables, tableNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (s *RewardServiceServer) ReceiveBigHuntReward(ctx context.Context, _ *empty
|
|||||||
weeklyScoreResults = []*pb.WeeklyScoreResult{}
|
weeklyScoreResults = []*pb.WeeklyScoreResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(user), []string{
|
tables := userdata.ProjectTables(user, []string{
|
||||||
"IUserBigHuntWeeklyStatus",
|
"IUserBigHuntWeeklyStatus",
|
||||||
"IUserBigHuntWeeklyMaxScore",
|
"IUserBigHuntWeeklyMaxScore",
|
||||||
"IUserConsumableItem",
|
"IUserConsumableItem",
|
||||||
|
|||||||
@@ -89,8 +89,7 @@ func (s *ShopServiceServer) Buy(ctx context.Context, req *pb.BuyRequest) (*pb.Bu
|
|||||||
return nil, fmt.Errorf("shop buy: %w", err)
|
return nil, fmt.Errorf("shop buy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables))
|
|
||||||
userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds())
|
userdata.AddWeaponStoryDiff(diff, snapshot, s.granter.DrainChangedStoryWeaponIds())
|
||||||
|
|
||||||
return &pb.BuyResponse{
|
return &pb.BuyResponse{
|
||||||
@@ -132,8 +131,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables))
|
|
||||||
|
|
||||||
return &pb.RefreshResponse{
|
return &pb.RefreshResponse{
|
||||||
DiffUserData: diff,
|
DiffUserData: diff,
|
||||||
@@ -195,8 +193,7 @@ 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)
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables))
|
|
||||||
|
|
||||||
return &pb.CreatePurchaseTransactionResponse{
|
return &pb.CreatePurchaseTransactionResponse{
|
||||||
PurchaseTransactionId: txId,
|
PurchaseTransactionId: txId,
|
||||||
@@ -208,13 +205,12 @@ func (s *ShopServiceServer) PurchaseGooglePlayStoreProduct(ctx context.Context,
|
|||||||
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.SnapshotUser(userId)
|
snapshot, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, shopDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, shopDiffTables))
|
|
||||||
|
|
||||||
return &pb.PurchaseGooglePlayStoreProductResponse{
|
return &pb.PurchaseGooglePlayStoreProductResponse{
|
||||||
OverflowPossession: []*pb.Possession{},
|
OverflowPossession: []*pb.Possession{},
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ var startedGameStartTables = []string{
|
|||||||
"IUserWeapon",
|
"IUserWeapon",
|
||||||
"IUserWeaponSkill",
|
"IUserWeaponSkill",
|
||||||
"IUserWeaponAbility",
|
"IUserWeaponAbility",
|
||||||
"IUserWeaponStory",
|
|
||||||
"IUserCompanion",
|
"IUserCompanion",
|
||||||
"IUserDeckCharacter",
|
"IUserDeckCharacter",
|
||||||
"IUserDeck",
|
"IUserDeck",
|
||||||
@@ -47,7 +46,7 @@ var gimmickDiffTables = []string{
|
|||||||
|
|
||||||
func currentUserId(ctx context.Context, users store.UserRepository, sessions store.SessionRepository) int64 {
|
func currentUserId(ctx context.Context, users store.UserRepository, sessions store.SessionRepository) int64 {
|
||||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||||
if vals := md.Get("x-session-key"); len(vals) > 0 {
|
if vals := md.Get("x-apb-session-key"); len(vals) > 0 {
|
||||||
if userId, err := sessions.ResolveUserId(vals[0]); err == nil {
|
if userId, err := sessions.ResolveUserId(vals[0]); err == nil {
|
||||||
return userId
|
return userId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,10 @@ func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb
|
|||||||
ChoiceId: req.ChoiceId,
|
ChoiceId: req.ChoiceId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if req.TutorialType == int32(model.TutorialTypeMenuFirst) ||
|
grants = s.engine.ApplyTutorialReward(user, model.TutorialType(req.TutorialType), req.ChoiceId, nowMillis)
|
||||||
req.TutorialType == int32(model.TutorialTypeMenuSecond) {
|
if req.TutorialType == int32(model.TutorialTypeMenuFirst) && req.ProgressPhase == 20 {
|
||||||
store.EnsureDefaultDeck(user, nowMillis)
|
store.EnsureDefaultDeck(user, nowMillis)
|
||||||
}
|
}
|
||||||
grants = s.engine.ApplyTutorialReward(user, model.TutorialType(req.TutorialType), req.ChoiceId, nowMillis)
|
|
||||||
})
|
})
|
||||||
tables := []string{"IUserTutorialProgress"}
|
tables := []string{"IUserTutorialProgress"}
|
||||||
if req.TutorialType == int32(model.TutorialTypeMenuFirst) ||
|
if req.TutorialType == int32(model.TutorialTypeMenuFirst) ||
|
||||||
@@ -55,7 +54,7 @@ func (s *TutorialServiceServer) SetTutorialProgress(ctx context.Context, req *pb
|
|||||||
if len(grants) > 0 {
|
if len(grants) > 0 {
|
||||||
tables = append(tables, "IUserCompanion")
|
tables = append(tables, "IUserCompanion")
|
||||||
}
|
}
|
||||||
result := userdata.SelectTables(userdata.FullClientTableMap(user), tables)
|
result := userdata.ProjectTables(user, tables)
|
||||||
for _, t := range tables {
|
for _, t := range tables {
|
||||||
log.Printf("[TutorialService] DiffTable %s -> %s", t, result[t])
|
log.Printf("[TutorialService] DiffTable %s -> %s", t, result[t])
|
||||||
}
|
}
|
||||||
@@ -89,7 +88,7 @@ func (s *TutorialServiceServer) SetTutorialProgressAndReplaceDeck(ctx context.Co
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
return &pb.SetTutorialProgressAndReplaceDeckResponse{
|
return &pb.SetTutorialProgressAndReplaceDeckResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), []string{
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{
|
||||||
"IUserTutorialProgress",
|
"IUserTutorialProgress",
|
||||||
"IUserDeck",
|
"IUserDeck",
|
||||||
"IUserDeckCharacter",
|
"IUserDeckCharacter",
|
||||||
|
|||||||
@@ -50,9 +50,13 @@ func setCommonResponseTrailers(ctx context.Context, diff map[string]*pb.DiffData
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserServiceServer) RegisterUser(ctx context.Context, req *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) {
|
func (s *UserServiceServer) RegisterUser(ctx context.Context, req *pb.RegisterUserRequest) (*pb.RegisterUserResponse, error) {
|
||||||
user, err := s.users.EnsureUser(req.Uuid)
|
userId, err := s.users.CreateUser(req.Uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ensure user: %w", err)
|
return nil, fmt.Errorf("create user: %w", err)
|
||||||
|
}
|
||||||
|
user, err := s.users.LoadUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load user: %w", err)
|
||||||
}
|
}
|
||||||
log.Printf("[UserService] RegisterUser: uuid=%s terminalId=%s -> userId=%d", req.Uuid, req.TerminalId, user.UserId)
|
log.Printf("[UserService] RegisterUser: uuid=%s terminalId=%s -> userId=%d", req.Uuid, req.TerminalId, user.UserId)
|
||||||
|
|
||||||
@@ -66,10 +70,14 @@ func (s *UserServiceServer) RegisterUser(ctx context.Context, req *pb.RegisterUs
|
|||||||
func (s *UserServiceServer) Auth(ctx context.Context, req *pb.AuthUserRequest) (*pb.AuthUserResponse, error) {
|
func (s *UserServiceServer) Auth(ctx context.Context, req *pb.AuthUserRequest) (*pb.AuthUserResponse, error) {
|
||||||
log.Printf("[UserService] Auth: uuid=%s", req.Uuid)
|
log.Printf("[UserService] Auth: uuid=%s", req.Uuid)
|
||||||
|
|
||||||
user, session, err := s.sessions.CreateSession(req.Uuid, 24*time.Hour)
|
session, err := s.sessions.CreateSession(req.Uuid, 24*time.Hour)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create session: %w", err)
|
return nil, fmt.Errorf("create session: %w", err)
|
||||||
}
|
}
|
||||||
|
user, err := s.users.LoadUser(session.UserId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.AuthUserResponse{
|
return &pb.AuthUserResponse{
|
||||||
SessionKey: session.SessionKey,
|
SessionKey: session.SessionKey,
|
||||||
@@ -84,7 +92,7 @@ func (s *UserServiceServer) GameStart(ctx context.Context, _ *emptypb.Empty) (*p
|
|||||||
log.Printf("[UserService] GameStart")
|
log.Printf("[UserService] GameStart")
|
||||||
|
|
||||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||||
if vals := md.Get("x-session-key"); len(vals) > 0 {
|
if vals := md.Get("x-apb-session-key"); len(vals) > 0 {
|
||||||
log.Printf("[UserService] GameStart session: %s", vals[0])
|
log.Printf("[UserService] GameStart session: %s", vals[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,8 +101,7 @@ func (s *UserServiceServer) GameStart(ctx context.Context, _ *emptypb.Empty) (*p
|
|||||||
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
user, _ := s.users.UpdateUser(userId, func(user *store.UserState) {
|
||||||
user.GameStartDatetime = gametime.NowMillis()
|
user.GameStartDatetime = gametime.NowMillis()
|
||||||
})
|
})
|
||||||
fullTables := userdata.FullClientTableMap(user)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(user, startedGameStartTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(fullTables, startedGameStartTables))
|
|
||||||
setCommonResponseTrailers(ctx, diff, true)
|
setCommonResponseTrailers(ctx, diff, true)
|
||||||
|
|
||||||
return &pb.GameStartResponse{
|
return &pb.GameStartResponse{
|
||||||
@@ -106,12 +113,12 @@ func (s *UserServiceServer) GameStart(ctx context.Context, _ *emptypb.Empty) (*p
|
|||||||
|
|
||||||
func (s *UserServiceServer) TransferUser(ctx context.Context, req *pb.TransferUserRequest) (*pb.TransferUserResponse, error) {
|
func (s *UserServiceServer) TransferUser(ctx context.Context, req *pb.TransferUserRequest) (*pb.TransferUserResponse, error) {
|
||||||
log.Printf("[UserService] TransferUser")
|
log.Printf("[UserService] TransferUser")
|
||||||
user, err := s.users.EnsureUser(req.Uuid)
|
userId, err := s.users.CreateUser(req.Uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ensure user: %w", err)
|
return nil, fmt.Errorf("create user: %w", err)
|
||||||
}
|
}
|
||||||
return &pb.TransferUserResponse{
|
return &pb.TransferUserResponse{
|
||||||
UserId: user.UserId,
|
UserId: userId,
|
||||||
Signature: "transferred-sig",
|
Signature: "transferred-sig",
|
||||||
DiffUserData: userdata.EmptyDiff(),
|
DiffUserData: userdata.EmptyDiff(),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -126,7 +133,7 @@ func (s *UserServiceServer) SetUserName(ctx context.Context, req *pb.SetUserName
|
|||||||
user.Profile.NameUpdateDatetime = nowMillis
|
user.Profile.NameUpdateDatetime = nowMillis
|
||||||
})
|
})
|
||||||
return &pb.SetUserNameResponse{
|
return &pb.SetUserNameResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), []string{"IUserProfile"})),
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +146,7 @@ func (s *UserServiceServer) SetUserMessage(ctx context.Context, req *pb.SetUserM
|
|||||||
user.Profile.MessageUpdateDatetime = nowMillis
|
user.Profile.MessageUpdateDatetime = nowMillis
|
||||||
})
|
})
|
||||||
return &pb.SetUserMessageResponse{
|
return &pb.SetUserMessageResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), []string{"IUserProfile"})),
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +159,7 @@ func (s *UserServiceServer) SetUserFavoriteCostumeId(ctx context.Context, req *p
|
|||||||
user.Profile.FavoriteCostumeIdUpdateDatetime = nowMillis
|
user.Profile.FavoriteCostumeIdUpdateDatetime = nowMillis
|
||||||
})
|
})
|
||||||
return &pb.SetUserFavoriteCostumeIdResponse{
|
return &pb.SetUserFavoriteCostumeIdResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), []string{"IUserProfile"})),
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserProfile"})),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +169,7 @@ func (s *UserServiceServer) GetUserProfile(ctx context.Context, req *pb.GetUserP
|
|||||||
if userId == 0 {
|
if userId == 0 {
|
||||||
userId = currentUserId(ctx, s.users, s.sessions)
|
userId = currentUserId(ctx, s.users, s.sessions)
|
||||||
}
|
}
|
||||||
user, err := s.users.SnapshotUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &pb.GetUserProfileResponse{DiffUserData: userdata.EmptyDiff()}, nil
|
return &pb.GetUserProfileResponse{DiffUserData: userdata.EmptyDiff()}, nil
|
||||||
}
|
}
|
||||||
@@ -219,7 +226,7 @@ func (s *UserServiceServer) SetBirthYearMonth(ctx context.Context, req *pb.SetBi
|
|||||||
|
|
||||||
func (s *UserServiceServer) GetBirthYearMonth(ctx context.Context, _ *emptypb.Empty) (*pb.GetBirthYearMonthResponse, error) {
|
func (s *UserServiceServer) GetBirthYearMonth(ctx context.Context, _ *emptypb.Empty) (*pb.GetBirthYearMonthResponse, error) {
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.SnapshotUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &pb.GetBirthYearMonthResponse{BirthYear: 2000, BirthMonth: 1, DiffUserData: userdata.EmptyDiff()}, nil
|
return &pb.GetBirthYearMonthResponse{BirthYear: 2000, BirthMonth: 1, DiffUserData: userdata.EmptyDiff()}, nil
|
||||||
}
|
}
|
||||||
@@ -228,7 +235,7 @@ func (s *UserServiceServer) GetBirthYearMonth(ctx context.Context, _ *emptypb.Em
|
|||||||
|
|
||||||
func (s *UserServiceServer) GetChargeMoney(ctx context.Context, _ *emptypb.Empty) (*pb.GetChargeMoneyResponse, error) {
|
func (s *UserServiceServer) GetChargeMoney(ctx context.Context, _ *emptypb.Empty) (*pb.GetChargeMoneyResponse, error) {
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.SnapshotUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: 0, DiffUserData: userdata.EmptyDiff()}, nil
|
return &pb.GetChargeMoneyResponse{ChargeMoneyThisMonth: 0, DiffUserData: userdata.EmptyDiff()}, nil
|
||||||
}
|
}
|
||||||
@@ -242,7 +249,7 @@ func (s *UserServiceServer) SetUserSetting(ctx context.Context, req *pb.SetUserS
|
|||||||
user.Setting.IsNotifyPurchaseAlert = req.IsNotifyPurchaseAlert
|
user.Setting.IsNotifyPurchaseAlert = req.IsNotifyPurchaseAlert
|
||||||
})
|
})
|
||||||
return &pb.SetUserSettingResponse{
|
return &pb.SetUserSettingResponse{
|
||||||
DiffUserData: userdata.BuildDiffFromTables(userdata.SelectTables(userdata.FullClientTableMap(user), []string{"IUserSetting"})),
|
DiffUserData: userdata.BuildDiffFromTables(userdata.ProjectTables(user, []string{"IUserSetting"})),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +259,7 @@ func (s *UserServiceServer) GetAndroidArgs(ctx context.Context, req *pb.GetAndro
|
|||||||
|
|
||||||
func (s *UserServiceServer) GetBackupToken(ctx context.Context, req *pb.GetBackupTokenRequest) (*pb.GetBackupTokenResponse, error) {
|
func (s *UserServiceServer) GetBackupToken(ctx context.Context, req *pb.GetBackupTokenRequest) (*pb.GetBackupTokenResponse, error) {
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
user, err := s.users.SnapshotUser(userId)
|
user, err := s.users.LoadUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &pb.GetBackupTokenResponse{BackupToken: "mock-backup-token", DiffUserData: userdata.EmptyDiff()}, nil
|
return &pb.GetBackupTokenResponse{BackupToken: "mock-backup-token", DiffUserData: userdata.EmptyDiff()}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ func (s *WeaponServiceServer) Protect(ctx context.Context, req *pb.ProtectReques
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserWeapon"}))
|
|
||||||
return &pb.ProtectResponse{DiffUserData: diff}, nil
|
return &pb.ProtectResponse{DiffUserData: diff}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,8 +94,7 @@ func (s *WeaponServiceServer) Unprotect(ctx context.Context, req *pb.UnprotectRe
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, []string{"IUserWeapon"}))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, []string{"IUserWeapon"}))
|
|
||||||
return &pb.UnprotectResponse{DiffUserData: diff}, nil
|
return &pb.UnprotectResponse{DiffUserData: diff}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,8 +163,7 @@ func (s *WeaponServiceServer) EnhanceByMaterial(ctx context.Context, req *pb.Enh
|
|||||||
return nil, fmt.Errorf("weapon enhance by material: %w", err)
|
return nil, fmt.Errorf("weapon enhance by material: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables))
|
|
||||||
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
|
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
|
||||||
|
|
||||||
return &pb.EnhanceByMaterialResponse{
|
return &pb.EnhanceByMaterialResponse{
|
||||||
@@ -181,7 +178,7 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
|
|||||||
|
|
||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
|
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
|
||||||
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
|
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
|
||||||
@@ -229,7 +226,7 @@ func (s *WeaponServiceServer) Sell(ctx context.Context, req *pb.SellRequest) (*p
|
|||||||
}
|
}
|
||||||
|
|
||||||
sellDiffTables := []string{"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeaponAwaken", "IUserConsumableItem"}
|
sellDiffTables := []string{"IUserWeapon", "IUserWeaponSkill", "IUserWeaponAbility", "IUserWeaponAwaken", "IUserConsumableItem"}
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), sellDiffTables)
|
tables := userdata.ProjectTables(snapshot, sellDiffTables)
|
||||||
diff := tracker.Apply(snapshot, tables)
|
diff := tracker.Apply(snapshot, tables)
|
||||||
|
|
||||||
return &pb.SellResponse{DiffUserData: diff}, nil
|
return &pb.SellResponse{DiffUserData: diff}, nil
|
||||||
@@ -307,8 +304,7 @@ func (s *WeaponServiceServer) Evolve(ctx context.Context, req *pb.EvolveRequest)
|
|||||||
return nil, fmt.Errorf("weapon evolve: %w", err)
|
return nil, fmt.Errorf("weapon evolve: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables))
|
|
||||||
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
|
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
|
||||||
|
|
||||||
return &pb.EvolveResponse{DiffUserData: diff}, nil
|
return &pb.EvolveResponse{DiffUserData: diff}, nil
|
||||||
@@ -407,8 +403,7 @@ func (s *WeaponServiceServer) EnhanceSkill(ctx context.Context, req *pb.EnhanceS
|
|||||||
return nil, fmt.Errorf("weapon enhance skill: %w", err)
|
return nil, fmt.Errorf("weapon enhance skill: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables))
|
|
||||||
|
|
||||||
return &pb.EnhanceSkillResponse{DiffUserData: diff}, nil
|
return &pb.EnhanceSkillResponse{DiffUserData: diff}, nil
|
||||||
}
|
}
|
||||||
@@ -506,8 +501,7 @@ func (s *WeaponServiceServer) EnhanceAbility(ctx context.Context, req *pb.Enhanc
|
|||||||
return nil, fmt.Errorf("weapon enhance ability: %w", err)
|
return nil, fmt.Errorf("weapon enhance ability: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponDiffTables))
|
|
||||||
|
|
||||||
return &pb.EnhanceAbilityResponse{DiffUserData: diff}, nil
|
return &pb.EnhanceAbilityResponse{DiffUserData: diff}, nil
|
||||||
}
|
}
|
||||||
@@ -578,8 +572,7 @@ func (s *WeaponServiceServer) LimitBreakByMaterial(ctx context.Context, req *pb.
|
|||||||
return nil, fmt.Errorf("weapon limit break by material: %w", err)
|
return nil, fmt.Errorf("weapon limit break by material: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, limitBreakDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, limitBreakDiffTables))
|
|
||||||
|
|
||||||
return &pb.LimitBreakByMaterialResponse{DiffUserData: diff}, nil
|
return &pb.LimitBreakByMaterialResponse{DiffUserData: diff}, nil
|
||||||
}
|
}
|
||||||
@@ -590,7 +583,7 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
|||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
|
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
|
||||||
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
|
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
|
||||||
@@ -665,7 +658,7 @@ func (s *WeaponServiceServer) LimitBreakByWeapon(ctx context.Context, req *pb.Li
|
|||||||
return nil, fmt.Errorf("weapon limit break by weapon: %w", err)
|
return nil, fmt.Errorf("weapon limit break by weapon: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), limitBreakDiffTables)
|
tables := userdata.ProjectTables(snapshot, limitBreakDiffTables)
|
||||||
diff := tracker.Apply(snapshot, tables)
|
diff := tracker.Apply(snapshot, tables)
|
||||||
|
|
||||||
return &pb.LimitBreakByWeaponResponse{DiffUserData: diff}, nil
|
return &pb.LimitBreakByWeaponResponse{DiffUserData: diff}, nil
|
||||||
@@ -677,7 +670,7 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
|||||||
userId := currentUserId(ctx, s.users, s.sessions)
|
userId := currentUserId(ctx, s.users, s.sessions)
|
||||||
nowMillis := gametime.NowMillis()
|
nowMillis := gametime.NowMillis()
|
||||||
|
|
||||||
oldUser, _ := s.users.SnapshotUser(userId)
|
oldUser, _ := s.users.LoadUser(userId)
|
||||||
tracker := userdata.NewDeleteTracker().
|
tracker := userdata.NewDeleteTracker().
|
||||||
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
|
Track("IUserWeapon", oldUser, userdata.SortedWeaponRecords, []string{"userId", "userWeaponUuid"}).
|
||||||
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
|
Track("IUserWeaponSkill", oldUser, userdata.SortedWeaponSkillRecords, []string{"userId", "userWeaponUuid", "slotNumber"}).
|
||||||
@@ -753,7 +746,7 @@ func (s *WeaponServiceServer) EnhanceByWeapon(ctx context.Context, req *pb.Enhan
|
|||||||
return nil, fmt.Errorf("weapon enhance by weapon: %w", err)
|
return nil, fmt.Errorf("weapon enhance by weapon: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.SelectTables(userdata.FullClientTableMap(snapshot), weaponDiffTables)
|
tables := userdata.ProjectTables(snapshot, weaponDiffTables)
|
||||||
diff := tracker.Apply(snapshot, tables)
|
diff := tracker.Apply(snapshot, tables)
|
||||||
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
|
userdata.AddWeaponStoryDiff(diff, snapshot, changedStoryIds)
|
||||||
|
|
||||||
@@ -864,8 +857,7 @@ func (s *WeaponServiceServer) Awaken(ctx context.Context, req *pb.WeaponAwakenRe
|
|||||||
return nil, fmt.Errorf("weapon awaken: %w", err)
|
return nil, fmt.Errorf("weapon awaken: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tables := userdata.FullClientTableMap(snapshot)
|
diff := userdata.BuildDiffFromTables(userdata.ProjectTables(snapshot, weaponAwakenDiffTables))
|
||||||
diff := userdata.BuildDiffFromTables(userdata.SelectTables(tables, weaponAwakenDiffTables))
|
|
||||||
|
|
||||||
return &pb.WeaponAwakenResponse{DiffUserData: diff}, nil
|
return &pb.WeaponAwakenResponse{DiffUserData: diff}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
package memory
|
package store
|
||||||
|
|
||||||
import (
|
import "maps"
|
||||||
"maps"
|
|
||||||
|
|
||||||
"lunar-tear/server/internal/store"
|
func CloneUserState(u UserState) UserState {
|
||||||
)
|
|
||||||
|
|
||||||
func cloneUserState(u store.UserState) store.UserState {
|
|
||||||
out := u
|
out := u
|
||||||
out.Tutorials = maps.Clone(u.Tutorials)
|
out.Tutorials = maps.Clone(u.Tutorials)
|
||||||
out.Characters = maps.Clone(u.Characters)
|
out.Characters = maps.Clone(u.Characters)
|
||||||
@@ -15,14 +11,14 @@ func cloneUserState(u store.UserState) store.UserState {
|
|||||||
out.Companions = maps.Clone(u.Companions)
|
out.Companions = maps.Clone(u.Companions)
|
||||||
out.Thoughts = maps.Clone(u.Thoughts)
|
out.Thoughts = maps.Clone(u.Thoughts)
|
||||||
out.DeckCharacters = maps.Clone(u.DeckCharacters)
|
out.DeckCharacters = maps.Clone(u.DeckCharacters)
|
||||||
out.DeckSubWeapons = maps.Clone(u.DeckSubWeapons)
|
out.DeckSubWeapons = cloneSliceMap(u.DeckSubWeapons)
|
||||||
out.DeckParts = cloneSliceMap(u.DeckParts)
|
out.DeckParts = cloneSliceMap(u.DeckParts)
|
||||||
out.Decks = maps.Clone(u.Decks)
|
out.Decks = maps.Clone(u.Decks)
|
||||||
out.Quests = maps.Clone(u.Quests)
|
out.Quests = maps.Clone(u.Quests)
|
||||||
out.QuestMissions = maps.Clone(u.QuestMissions)
|
out.QuestMissions = maps.Clone(u.QuestMissions)
|
||||||
out.WeaponStories = maps.Clone(u.WeaponStories)
|
out.WeaponStories = maps.Clone(u.WeaponStories)
|
||||||
out.Missions = maps.Clone(u.Missions)
|
out.Missions = maps.Clone(u.Missions)
|
||||||
out.Gimmick = store.GimmickState{
|
out.Gimmick = GimmickState{
|
||||||
Progress: maps.Clone(u.Gimmick.Progress),
|
Progress: maps.Clone(u.Gimmick.Progress),
|
||||||
OrnamentProgress: maps.Clone(u.Gimmick.OrnamentProgress),
|
OrnamentProgress: maps.Clone(u.Gimmick.OrnamentProgress),
|
||||||
Sequences: maps.Clone(u.Gimmick.Sequences),
|
Sequences: maps.Clone(u.Gimmick.Sequences),
|
||||||
@@ -38,6 +34,7 @@ func cloneUserState(u store.UserState) store.UserState {
|
|||||||
out.CostumeActiveSkills = maps.Clone(u.CostumeActiveSkills)
|
out.CostumeActiveSkills = maps.Clone(u.CostumeActiveSkills)
|
||||||
out.WeaponSkills = cloneSliceMap(u.WeaponSkills)
|
out.WeaponSkills = cloneSliceMap(u.WeaponSkills)
|
||||||
out.WeaponAbilities = cloneSliceMap(u.WeaponAbilities)
|
out.WeaponAbilities = cloneSliceMap(u.WeaponAbilities)
|
||||||
|
out.WeaponAwakens = maps.Clone(u.WeaponAwakens)
|
||||||
out.DeckTypeNotes = maps.Clone(u.DeckTypeNotes)
|
out.DeckTypeNotes = maps.Clone(u.DeckTypeNotes)
|
||||||
out.WeaponNotes = maps.Clone(u.WeaponNotes)
|
out.WeaponNotes = maps.Clone(u.WeaponNotes)
|
||||||
out.NaviCutInPlayed = maps.Clone(u.NaviCutInPlayed)
|
out.NaviCutInPlayed = maps.Clone(u.NaviCutInPlayed)
|
||||||
@@ -50,40 +47,46 @@ func cloneUserState(u store.UserState) store.UserState {
|
|||||||
out.ShopReplaceableLineup = maps.Clone(u.ShopReplaceableLineup)
|
out.ShopReplaceableLineup = maps.Clone(u.ShopReplaceableLineup)
|
||||||
out.Explore = u.Explore
|
out.Explore = u.Explore
|
||||||
out.ExploreScores = maps.Clone(u.ExploreScores)
|
out.ExploreScores = maps.Clone(u.ExploreScores)
|
||||||
out.Gacha = store.GachaState{
|
out.Gacha = GachaState{
|
||||||
RewardAvailable: u.Gacha.RewardAvailable,
|
RewardAvailable: u.Gacha.RewardAvailable,
|
||||||
TodaysCurrentDrawCount: u.Gacha.TodaysCurrentDrawCount,
|
TodaysCurrentDrawCount: u.Gacha.TodaysCurrentDrawCount,
|
||||||
DailyMaxCount: u.Gacha.DailyMaxCount,
|
DailyMaxCount: u.Gacha.DailyMaxCount,
|
||||||
LastRewardDrawDate: u.Gacha.LastRewardDrawDate,
|
LastRewardDrawDate: u.Gacha.LastRewardDrawDate,
|
||||||
ConvertedGachaMedal: store.ConvertedGachaMedalState{
|
ConvertedGachaMedal: ConvertedGachaMedalState{
|
||||||
ConvertedMedalPossession: append([]store.ConsumableItemState(nil), u.Gacha.ConvertedGachaMedal.ConvertedMedalPossession...),
|
ConvertedMedalPossession: append([]ConsumableItemState(nil), u.Gacha.ConvertedGachaMedal.ConvertedMedalPossession...),
|
||||||
ObtainPossession: cloneConsumableItemPtr(u.Gacha.ConvertedGachaMedal.ObtainPossession),
|
ObtainPossession: cloneConsumableItemPtr(u.Gacha.ConvertedGachaMedal.ObtainPossession),
|
||||||
},
|
},
|
||||||
BannerStates: cloneBannerStates(u.Gacha.BannerStates),
|
BannerStates: cloneBannerStates(u.Gacha.BannerStates),
|
||||||
}
|
}
|
||||||
out.Gifts = store.GiftState{
|
out.Gifts = GiftState{
|
||||||
NotReceived: cloneNotReceivedGifts(u.Gifts.NotReceived),
|
NotReceived: cloneNotReceivedGifts(u.Gifts.NotReceived),
|
||||||
Received: cloneReceivedGifts(u.Gifts.Received),
|
Received: cloneReceivedGifts(u.Gifts.Received),
|
||||||
}
|
}
|
||||||
out.Battle = u.Battle
|
out.Battle = u.Battle
|
||||||
|
out.SideStoryQuests = maps.Clone(u.SideStoryQuests)
|
||||||
|
out.QuestLimitContentStatus = maps.Clone(u.QuestLimitContentStatus)
|
||||||
|
out.BigHuntMaxScores = maps.Clone(u.BigHuntMaxScores)
|
||||||
|
out.BigHuntStatuses = maps.Clone(u.BigHuntStatuses)
|
||||||
|
out.BigHuntScheduleMaxScores = maps.Clone(u.BigHuntScheduleMaxScores)
|
||||||
|
out.BigHuntWeeklyMaxScores = maps.Clone(u.BigHuntWeeklyMaxScores)
|
||||||
|
out.BigHuntWeeklyStatuses = maps.Clone(u.BigHuntWeeklyStatuses)
|
||||||
|
out.BigHuntBattleBinary = append([]byte(nil), u.BigHuntBattleBinary...)
|
||||||
|
out.CharacterBoards = maps.Clone(u.CharacterBoards)
|
||||||
|
out.CharacterBoardAbilities = maps.Clone(u.CharacterBoardAbilities)
|
||||||
|
out.CharacterBoardStatusUps = maps.Clone(u.CharacterBoardStatusUps)
|
||||||
out.CostumeAwakenStatusUps = maps.Clone(u.CostumeAwakenStatusUps)
|
out.CostumeAwakenStatusUps = maps.Clone(u.CostumeAwakenStatusUps)
|
||||||
|
out.CostumeLotteryEffects = maps.Clone(u.CostumeLotteryEffects)
|
||||||
|
out.CostumeLotteryEffectPending = maps.Clone(u.CostumeLotteryEffectPending)
|
||||||
out.AutoSaleSettings = maps.Clone(u.AutoSaleSettings)
|
out.AutoSaleSettings = maps.Clone(u.AutoSaleSettings)
|
||||||
out.CharacterRebirths = maps.Clone(u.CharacterRebirths)
|
out.CharacterRebirths = maps.Clone(u.CharacterRebirths)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneGachaCatalogEntry(entry store.GachaCatalogEntry) store.GachaCatalogEntry {
|
func cloneBannerStates(m map[int32]GachaBannerState) map[int32]GachaBannerState {
|
||||||
out := entry
|
|
||||||
out.PricePhases = append([]store.GachaPricePhaseEntry(nil), entry.PricePhases...)
|
|
||||||
out.PromotionItems = append([]store.GachaPromotionItem(nil), entry.PromotionItems...)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneBannerStates(m map[int32]store.GachaBannerState) map[int32]store.GachaBannerState {
|
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := make(map[int32]store.GachaBannerState, len(m))
|
out := make(map[int32]GachaBannerState, len(m))
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
bs := v
|
bs := v
|
||||||
bs.BoxDrewCounts = maps.Clone(v.BoxDrewCounts)
|
bs.BoxDrewCounts = maps.Clone(v.BoxDrewCounts)
|
||||||
@@ -92,7 +95,7 @@ func cloneBannerStates(m map[int32]store.GachaBannerState) map[int32]store.Gacha
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneConsumableItemPtr(item *store.ConsumableItemState) *store.ConsumableItemState {
|
func cloneConsumableItemPtr(item *ConsumableItemState) *ConsumableItemState {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -100,11 +103,11 @@ func cloneConsumableItemPtr(item *store.ConsumableItemState) *store.ConsumableIt
|
|||||||
return &out
|
return &out
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneNotReceivedGifts(gifts []store.NotReceivedGiftState) []store.NotReceivedGiftState {
|
func cloneNotReceivedGifts(gifts []NotReceivedGiftState) []NotReceivedGiftState {
|
||||||
out := make([]store.NotReceivedGiftState, len(gifts))
|
out := make([]NotReceivedGiftState, len(gifts))
|
||||||
for i, gift := range gifts {
|
for i, gift := range gifts {
|
||||||
out[i] = store.NotReceivedGiftState{
|
out[i] = NotReceivedGiftState{
|
||||||
GiftCommon: store.GiftCommonState{
|
GiftCommon: GiftCommonState{
|
||||||
PossessionType: gift.GiftCommon.PossessionType,
|
PossessionType: gift.GiftCommon.PossessionType,
|
||||||
PossessionId: gift.GiftCommon.PossessionId,
|
PossessionId: gift.GiftCommon.PossessionId,
|
||||||
Count: gift.GiftCommon.Count,
|
Count: gift.GiftCommon.Count,
|
||||||
@@ -119,6 +122,24 @@ func cloneNotReceivedGifts(gifts []store.NotReceivedGiftState) []store.NotReceiv
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cloneReceivedGifts(gifts []ReceivedGiftState) []ReceivedGiftState {
|
||||||
|
out := make([]ReceivedGiftState, len(gifts))
|
||||||
|
for i, gift := range gifts {
|
||||||
|
out[i] = ReceivedGiftState{
|
||||||
|
GiftCommon: GiftCommonState{
|
||||||
|
PossessionType: gift.GiftCommon.PossessionType,
|
||||||
|
PossessionId: gift.GiftCommon.PossessionId,
|
||||||
|
Count: gift.GiftCommon.Count,
|
||||||
|
GrantDatetime: gift.GiftCommon.GrantDatetime,
|
||||||
|
DescriptionGiftTextId: gift.GiftCommon.DescriptionGiftTextId,
|
||||||
|
EquipmentData: append([]byte(nil), gift.GiftCommon.EquipmentData...),
|
||||||
|
},
|
||||||
|
ReceivedDatetime: gift.ReceivedDatetime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func cloneSliceMap[T any](m map[string][]T) map[string][]T {
|
func cloneSliceMap[T any](m map[string][]T) map[string][]T {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -129,21 +150,3 @@ func cloneSliceMap[T any](m map[string][]T) map[string][]T {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneReceivedGifts(gifts []store.ReceivedGiftState) []store.ReceivedGiftState {
|
|
||||||
out := make([]store.ReceivedGiftState, len(gifts))
|
|
||||||
for i, gift := range gifts {
|
|
||||||
out[i] = store.ReceivedGiftState{
|
|
||||||
GiftCommon: store.GiftCommonState{
|
|
||||||
PossessionType: gift.GiftCommon.PossessionType,
|
|
||||||
PossessionId: gift.GiftCommon.PossessionId,
|
|
||||||
Count: gift.GiftCommon.Count,
|
|
||||||
GrantDatetime: gift.GiftCommon.GrantDatetime,
|
|
||||||
DescriptionGiftTextId: gift.GiftCommon.DescriptionGiftTextId,
|
|
||||||
EquipmentData: append([]byte(nil), gift.GiftCommon.EquipmentData...),
|
|
||||||
},
|
|
||||||
ReceivedDatetime: gift.ReceivedDatetime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"lunar-tear/server/internal/gametime"
|
"lunar-tear/server/internal/gametime"
|
||||||
"lunar-tear/server/internal/model"
|
"lunar-tear/server/internal/model"
|
||||||
)
|
)
|
||||||
@@ -139,7 +141,7 @@ func (g *PossessionGranter) GrantCostume(user *UserState, costumeId int32, nowMi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf("reward-costume-%d", costumeId)
|
key := uuid.New().String()
|
||||||
user.Costumes[key] = CostumeState{
|
user.Costumes[key] = CostumeState{
|
||||||
UserCostumeUuid: key,
|
UserCostumeUuid: key,
|
||||||
CostumeId: costumeId,
|
CostumeId: costumeId,
|
||||||
@@ -155,16 +157,7 @@ func (g *PossessionGranter) GrantCostume(user *UserState, costumeId int32, nowMi
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *PossessionGranter) GrantWeapon(user *UserState, weaponId int32, nowMillis int64) {
|
func (g *PossessionGranter) GrantWeapon(user *UserState, weaponId int32, nowMillis int64) {
|
||||||
key := fmt.Sprintf("reward-weapon-%d-%d", weaponId, nowMillis)
|
key := uuid.New().String()
|
||||||
if _, exists := user.Weapons[key]; exists {
|
|
||||||
for i := 2; ; i++ {
|
|
||||||
candidate := fmt.Sprintf("%s-%d", key, i)
|
|
||||||
if _, exists := user.Weapons[candidate]; !exists {
|
|
||||||
key = candidate
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user.Weapons[key] = WeaponState{
|
user.Weapons[key] = WeaponState{
|
||||||
UserWeaponUuid: key,
|
UserWeaponUuid: key,
|
||||||
WeaponId: weaponId,
|
WeaponId: weaponId,
|
||||||
@@ -269,16 +262,29 @@ func EnsureDefaultDeck(user *UserState, nowMillis int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
costumeUuid := FirstSortedKey(user.Costumes)
|
const rionCostumeId = int32(10100)
|
||||||
weaponUuid := FirstSortedKey(user.Weapons)
|
const rionWeaponId = int32(101001)
|
||||||
companionUuid := FirstSortedKey(user.Companions)
|
|
||||||
|
|
||||||
dcUuid := "default-deck-character-0001"
|
var costumeUuid, weaponUuid string
|
||||||
|
for k, v := range user.Costumes {
|
||||||
|
if v.CostumeId == rionCostumeId {
|
||||||
|
costumeUuid = k
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range user.Weapons {
|
||||||
|
if v.WeaponId == rionWeaponId {
|
||||||
|
weaponUuid = k
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dcUuid := uuid.New().String()
|
||||||
user.DeckCharacters[dcUuid] = DeckCharacterState{
|
user.DeckCharacters[dcUuid] = DeckCharacterState{
|
||||||
UserDeckCharacterUuid: dcUuid,
|
UserDeckCharacterUuid: dcUuid,
|
||||||
|
UserCompanionUuid: "",
|
||||||
UserCostumeUuid: costumeUuid,
|
UserCostumeUuid: costumeUuid,
|
||||||
MainUserWeaponUuid: weaponUuid,
|
MainUserWeaponUuid: weaponUuid,
|
||||||
UserCompanionUuid: companionUuid,
|
|
||||||
Power: 100,
|
Power: 100,
|
||||||
LatestVersion: nowMillis,
|
LatestVersion: nowMillis,
|
||||||
}
|
}
|
||||||
@@ -324,14 +330,17 @@ func ApplyDeckReplacement(user *UserState, deckType model.DeckType, userDeckNumb
|
|||||||
deck.Power = 100
|
deck.Power = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
uuids := []*string{&deck.UserDeckCharacterUuid01, &deck.UserDeckCharacterUuid02, &deck.UserDeckCharacterUuid03}
|
uuidPtrs := []*string{&deck.UserDeckCharacterUuid01, &deck.UserDeckCharacterUuid02, &deck.UserDeckCharacterUuid03}
|
||||||
for i, uuid := range uuids {
|
for i, uuidPtr := range uuidPtrs {
|
||||||
if i >= len(slots) || slots[i].UserCostumeUuid == "" {
|
if i >= len(slots) || slots[i].UserCostumeUuid == "" {
|
||||||
*uuid = ""
|
*uuidPtr = ""
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
slot := slots[i]
|
slot := slots[i]
|
||||||
dcUuid := fmt.Sprintf("deck-%d-%d-%d", deckType, userDeckNumber, i+1)
|
dcUuid := *uuidPtr
|
||||||
|
if dcUuid == "" {
|
||||||
|
dcUuid = uuid.New().String()
|
||||||
|
}
|
||||||
dc := user.DeckCharacters[dcUuid]
|
dc := user.DeckCharacters[dcUuid]
|
||||||
dc.UserDeckCharacterUuid = dcUuid
|
dc.UserDeckCharacterUuid = dcUuid
|
||||||
dc.UserCostumeUuid = slot.UserCostumeUuid
|
dc.UserCostumeUuid = slot.UserCostumeUuid
|
||||||
@@ -343,7 +352,7 @@ func ApplyDeckReplacement(user *UserState, deckType model.DeckType, userDeckNumb
|
|||||||
user.DeckCharacters[dcUuid] = dc
|
user.DeckCharacters[dcUuid] = dc
|
||||||
user.DeckSubWeapons[dcUuid] = slot.SubWeaponUuids
|
user.DeckSubWeapons[dcUuid] = slot.SubWeaponUuids
|
||||||
user.DeckParts[dcUuid] = slot.PartsUuids
|
user.DeckParts[dcUuid] = slot.PartsUuids
|
||||||
*uuid = dcUuid
|
*uuidPtr = dcUuid
|
||||||
}
|
}
|
||||||
|
|
||||||
deck.LatestVersion = nowMillis
|
deck.LatestVersion = nowMillis
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
package memory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"lunar-tear/server/internal/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Option func(*MemoryStore)
|
|
||||||
|
|
||||||
func WithSnapshotDir(dir string) Option {
|
|
||||||
return func(s *MemoryStore) {
|
|
||||||
s.snapshotDir = dir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithSceneId(sceneId int32) Option {
|
|
||||||
return func(s *MemoryStore) {
|
|
||||||
s.bootstrapSceneId = sceneId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithStarterItems(v bool) Option {
|
|
||||||
return func(s *MemoryStore) {
|
|
||||||
s.starterItems = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryStore struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
clock store.Clock
|
|
||||||
bootstrapSceneId int32
|
|
||||||
snapshotDir string
|
|
||||||
starterItems bool
|
|
||||||
lastSnapshotSceneId int32
|
|
||||||
nextUserId int64
|
|
||||||
users map[int64]*store.UserState
|
|
||||||
userIdsByUuid map[string]int64
|
|
||||||
sessionToUserId map[string]int64
|
|
||||||
sessions map[string]store.SessionState
|
|
||||||
gachaCatalog map[int32]store.GachaCatalogEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ store.UserRepository = (*MemoryStore)(nil)
|
|
||||||
_ store.SessionRepository = (*MemoryStore)(nil)
|
|
||||||
_ store.GachaRepository = (*MemoryStore)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(clock store.Clock, options ...Option) *MemoryStore {
|
|
||||||
if clock == nil {
|
|
||||||
clock = time.Now
|
|
||||||
}
|
|
||||||
s := &MemoryStore{
|
|
||||||
clock: clock,
|
|
||||||
nextUserId: defaultUserId,
|
|
||||||
users: make(map[int64]*store.UserState),
|
|
||||||
userIdsByUuid: make(map[string]int64),
|
|
||||||
sessionToUserId: make(map[string]int64),
|
|
||||||
sessions: make(map[string]store.SessionState),
|
|
||||||
gachaCatalog: make(map[int32]store.GachaCatalogEntry),
|
|
||||||
}
|
|
||||||
for _, opt := range options {
|
|
||||||
opt(s)
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) EnsureUser(uuid string) (store.UserState, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
return cloneUserState(*s.getOrCreateLocked(normalizeUUID(uuid))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) CreateSession(uuid string, ttl time.Duration) (store.UserState, store.SessionState, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
user := s.getOrCreateLocked(normalizeUUID(uuid))
|
|
||||||
now := s.clock()
|
|
||||||
session := store.SessionState{
|
|
||||||
SessionKey: fmt.Sprintf("session_%d_%d", user.UserId, now.UnixNano()),
|
|
||||||
UserId: user.UserId,
|
|
||||||
Uuid: user.Uuid,
|
|
||||||
ExpireAt: now.Add(ttl),
|
|
||||||
}
|
|
||||||
|
|
||||||
s.sessionToUserId[session.SessionKey] = user.UserId
|
|
||||||
s.sessions[session.SessionKey] = session
|
|
||||||
|
|
||||||
return cloneUserState(*user), session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) ResolveUserId(sessionKey string) (int64, error) {
|
|
||||||
s.mu.RLock()
|
|
||||||
defer s.mu.RUnlock()
|
|
||||||
|
|
||||||
userId, ok := s.sessionToUserId[sessionKey]
|
|
||||||
if !ok {
|
|
||||||
return 0, store.ErrNotFound
|
|
||||||
}
|
|
||||||
return userId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) SnapshotUser(userId int64) (store.UserState, error) {
|
|
||||||
s.mu.RLock()
|
|
||||||
defer s.mu.RUnlock()
|
|
||||||
|
|
||||||
user, ok := s.users[userId]
|
|
||||||
if !ok {
|
|
||||||
return store.UserState{}, store.ErrNotFound
|
|
||||||
}
|
|
||||||
return cloneUserState(*user), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) UpdateUser(userId int64, mutate func(*store.UserState)) (store.UserState, error) {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
user, ok := s.users[userId]
|
|
||||||
if !ok {
|
|
||||||
return store.UserState{}, store.ErrNotFound
|
|
||||||
}
|
|
||||||
mutate(user)
|
|
||||||
sceneId := user.MainQuest.CurrentQuestSceneId
|
|
||||||
if s.snapshotDir != "" && sceneId != 0 && sceneId != s.lastSnapshotSceneId {
|
|
||||||
saveSnapshot(user, s.snapshotDir)
|
|
||||||
s.lastSnapshotSceneId = sceneId
|
|
||||||
}
|
|
||||||
return cloneUserState(*user), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) DefaultUserId() (int64, error) {
|
|
||||||
s.mu.RLock()
|
|
||||||
defer s.mu.RUnlock()
|
|
||||||
|
|
||||||
if _, ok := s.users[defaultUserId]; ok {
|
|
||||||
return defaultUserId, nil
|
|
||||||
}
|
|
||||||
if len(s.users) == 0 {
|
|
||||||
return defaultUserId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var minUserId int64
|
|
||||||
for userId := range s.users {
|
|
||||||
if minUserId == 0 || userId < minUserId {
|
|
||||||
minUserId = userId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return minUserId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) SnapshotCatalog() ([]store.GachaCatalogEntry, error) {
|
|
||||||
s.mu.RLock()
|
|
||||||
defer s.mu.RUnlock()
|
|
||||||
|
|
||||||
out := make([]store.GachaCatalogEntry, 0, len(s.gachaCatalog))
|
|
||||||
for _, entry := range s.gachaCatalog {
|
|
||||||
out = append(out, cloneGachaCatalogEntry(entry))
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) ReplaceCatalog(entries []store.GachaCatalogEntry) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
defer s.mu.Unlock()
|
|
||||||
|
|
||||||
s.gachaCatalog = make(map[int32]store.GachaCatalogEntry, len(entries))
|
|
||||||
for _, entry := range entries {
|
|
||||||
s.gachaCatalog[entry.GachaId] = cloneGachaCatalogEntry(entry)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MemoryStore) getOrCreateLocked(uuid string) *store.UserState {
|
|
||||||
if userId, ok := s.userIdsByUuid[uuid]; ok {
|
|
||||||
return s.users[userId]
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := s.nextUserId
|
|
||||||
s.nextUserId++
|
|
||||||
|
|
||||||
user := seedUserState(userId, uuid, s.clock().UnixMilli(), s.bootstrapSceneId, s.snapshotDir, s.starterItems)
|
|
||||||
s.users[userId] = user
|
|
||||||
s.userIdsByUuid[uuid] = userId
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeUUID(uuid string) string {
|
|
||||||
uuid = strings.TrimSpace(uuid)
|
|
||||||
if uuid == "" {
|
|
||||||
return defaultUUID
|
|
||||||
}
|
|
||||||
return uuid
|
|
||||||
}
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
package memory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"lunar-tear/server/internal/model"
|
|
||||||
"lunar-tear/server/internal/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultUUID = "default-user"
|
|
||||||
defaultUserId = int64(1001)
|
|
||||||
|
|
||||||
starterMissionId = int32(1)
|
|
||||||
starterMainQuestRouteId = int32(1)
|
|
||||||
starterMainQuestSeasonId = int32(1)
|
|
||||||
missionInProgress = int32(1)
|
|
||||||
giftUUIDPrefix = "default-gift"
|
|
||||||
|
|
||||||
defaultBirthYear = int32(2000)
|
|
||||||
defaultBirthMonth = int32(1)
|
|
||||||
defaultBackupToken = "mock-backup-token"
|
|
||||||
defaultChargeMoneyThisMonth = int64(0)
|
|
||||||
)
|
|
||||||
|
|
||||||
type starterItemDef struct {
|
|
||||||
Type model.PossessionType
|
|
||||||
Id int32
|
|
||||||
Qty int32
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultStarterItems = []starterItemDef{
|
|
||||||
{Type: model.PossessionTypeFreeGem, Id: 0, Qty: 300},
|
|
||||||
{Type: model.PossessionTypeConsumableItem, Id: 9001, Qty: 1000},
|
|
||||||
{Type: model.PossessionTypeConsumableItem, Id: model.ConsumableIdChapterTicket, Qty: 1000},
|
|
||||||
{Type: model.PossessionTypeConsumableItem, Id: 5001, Qty: 1000},
|
|
||||||
{Type: model.PossessionTypeConsumableItem, Id: 5002, Qty: 1000},
|
|
||||||
{Type: model.PossessionTypeConsumableItem, Id: 5003, Qty: 1000},
|
|
||||||
{Type: model.PossessionTypeConsumableItem, Id: 1009, Qty: 1000},
|
|
||||||
}
|
|
||||||
|
|
||||||
func seedUserState(userId int64, uuid string, nowMillis int64, sceneId int32, snapshotDir string, grantStarterItems bool) *store.UserState {
|
|
||||||
if sceneId != 0 && snapshotDir != "" {
|
|
||||||
user, err := loadSnapshot(snapshotDir, sceneId)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[bootstrap] no snapshot for scene=%d: %v", sceneId, err)
|
|
||||||
}
|
|
||||||
log.Printf("[bootstrap] loaded snapshot for scene=%d", sceneId)
|
|
||||||
if grantStarterItems {
|
|
||||||
applyStarterItems(user)
|
|
||||||
}
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &store.UserState{
|
|
||||||
UserId: userId,
|
|
||||||
Uuid: uuid,
|
|
||||||
PlayerId: userId,
|
|
||||||
OsType: 2,
|
|
||||||
PlatformType: 2,
|
|
||||||
UserRestrictionType: 0,
|
|
||||||
RegisterDatetime: nowMillis,
|
|
||||||
GameStartDatetime: nowMillis,
|
|
||||||
LatestVersion: 0,
|
|
||||||
BirthYear: defaultBirthYear,
|
|
||||||
BirthMonth: defaultBirthMonth,
|
|
||||||
BackupToken: defaultBackupToken,
|
|
||||||
ChargeMoneyThisMonth: defaultChargeMoneyThisMonth,
|
|
||||||
Setting: store.UserSettingState{
|
|
||||||
IsNotifyPurchaseAlert: false,
|
|
||||||
LatestVersion: 0,
|
|
||||||
},
|
|
||||||
Status: store.UserStatusState{
|
|
||||||
Level: 1,
|
|
||||||
Exp: 0,
|
|
||||||
StaminaMilliValue: 50000,
|
|
||||||
StaminaUpdateDatetime: nowMillis,
|
|
||||||
LatestVersion: 0,
|
|
||||||
},
|
|
||||||
Gem: store.UserGemState{
|
|
||||||
PaidGem: 10000,
|
|
||||||
FreeGem: 10000,
|
|
||||||
},
|
|
||||||
Profile: store.UserProfileState{
|
|
||||||
Name: "",
|
|
||||||
NameUpdateDatetime: 0,
|
|
||||||
Message: "",
|
|
||||||
MessageUpdateDatetime: nowMillis,
|
|
||||||
FavoriteCostumeId: 0,
|
|
||||||
FavoriteCostumeIdUpdateDatetime: nowMillis,
|
|
||||||
LatestVersion: 0,
|
|
||||||
},
|
|
||||||
Login: store.UserLoginState{
|
|
||||||
TotalLoginCount: 1,
|
|
||||||
ContinualLoginCount: 1,
|
|
||||||
MaxContinualLoginCount: 1,
|
|
||||||
LastLoginDatetime: nowMillis,
|
|
||||||
LastComebackLoginDatetime: 0,
|
|
||||||
LatestVersion: 0,
|
|
||||||
},
|
|
||||||
LoginBonus: store.UserLoginBonusState{
|
|
||||||
LoginBonusId: 1,
|
|
||||||
CurrentPageNumber: 1,
|
|
||||||
CurrentStampNumber: 0,
|
|
||||||
LatestRewardReceiveDatetime: 0,
|
|
||||||
LatestVersion: 0,
|
|
||||||
},
|
|
||||||
Tutorials: map[int32]store.TutorialProgressState{
|
|
||||||
1: {TutorialType: 1},
|
|
||||||
},
|
|
||||||
Battle: store.BattleState{},
|
|
||||||
Gifts: store.GiftState{
|
|
||||||
NotReceived: []store.NotReceivedGiftState{
|
|
||||||
{
|
|
||||||
GiftCommon: store.GiftCommonState{
|
|
||||||
PossessionType: int32(model.PossessionTypeFreeGem),
|
|
||||||
PossessionId: 0,
|
|
||||||
Count: 300,
|
|
||||||
GrantDatetime: nowMillis,
|
|
||||||
},
|
|
||||||
ExpirationDatetime: nowMillis + int64((7*24*time.Hour)/time.Millisecond),
|
|
||||||
UserGiftUuid: fmt.Sprintf("%s-%d-1", giftUUIDPrefix, userId),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Received: []store.ReceivedGiftState{},
|
|
||||||
},
|
|
||||||
Gacha: store.GachaState{
|
|
||||||
ConvertedGachaMedal: store.ConvertedGachaMedalState{
|
|
||||||
ConvertedMedalPossession: []store.ConsumableItemState{},
|
|
||||||
},
|
|
||||||
BannerStates: make(map[int32]store.GachaBannerState),
|
|
||||||
},
|
|
||||||
MainQuest: store.MainQuestState{
|
|
||||||
CurrentMainQuestRouteId: starterMainQuestRouteId,
|
|
||||||
MainQuestSeasonId: starterMainQuestSeasonId,
|
|
||||||
},
|
|
||||||
Notifications: store.NotificationState{
|
|
||||||
GiftNotReceiveCount: 1,
|
|
||||||
},
|
|
||||||
Characters: make(map[int32]store.CharacterState),
|
|
||||||
Costumes: make(map[string]store.CostumeState),
|
|
||||||
Weapons: make(map[string]store.WeaponState),
|
|
||||||
Companions: make(map[string]store.CompanionState),
|
|
||||||
DeckCharacters: make(map[string]store.DeckCharacterState),
|
|
||||||
Decks: make(map[store.DeckKey]store.DeckState),
|
|
||||||
DeckSubWeapons: make(map[string][]string),
|
|
||||||
DeckParts: make(map[string][]string),
|
|
||||||
Quests: make(map[int32]store.UserQuestState),
|
|
||||||
QuestMissions: make(map[store.QuestMissionKey]store.UserQuestMissionState),
|
|
||||||
SideStoryQuests: make(map[int32]store.SideStoryQuestProgress),
|
|
||||||
QuestLimitContentStatus: make(map[int32]store.QuestLimitContentStatus),
|
|
||||||
BigHuntMaxScores: make(map[int32]store.BigHuntMaxScore),
|
|
||||||
BigHuntStatuses: make(map[int32]store.BigHuntStatus),
|
|
||||||
BigHuntScheduleMaxScores: make(map[store.BigHuntScheduleScoreKey]store.BigHuntScheduleMaxScore),
|
|
||||||
BigHuntWeeklyMaxScores: make(map[store.BigHuntWeeklyScoreKey]store.BigHuntWeeklyMaxScore),
|
|
||||||
BigHuntWeeklyStatuses: make(map[int64]store.BigHuntWeeklyStatus),
|
|
||||||
WeaponStories: make(map[int32]store.WeaponStoryState),
|
|
||||||
Missions: map[int32]store.UserMissionState{
|
|
||||||
starterMissionId: {
|
|
||||||
MissionId: starterMissionId,
|
|
||||||
StartDatetime: nowMillis,
|
|
||||||
MissionProgressStatusType: missionInProgress,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Gimmick: store.GimmickState{
|
|
||||||
Progress: make(map[store.GimmickKey]store.GimmickProgressState),
|
|
||||||
OrnamentProgress: make(map[store.GimmickOrnamentKey]store.GimmickOrnamentProgressState),
|
|
||||||
Sequences: make(map[store.GimmickSequenceKey]store.GimmickSequenceState),
|
|
||||||
Unlocks: make(map[store.GimmickKey]store.GimmickUnlockState),
|
|
||||||
},
|
|
||||||
CageOrnamentRewards: make(map[int32]store.CageOrnamentRewardState),
|
|
||||||
ConsumableItems: make(map[int32]int32),
|
|
||||||
Materials: make(map[int32]int32),
|
|
||||||
Thoughts: make(map[string]store.ThoughtState),
|
|
||||||
Parts: make(map[string]store.PartsState),
|
|
||||||
PartsGroupNotes: make(map[int32]store.PartsGroupNoteState),
|
|
||||||
PartsPresets: make(map[int32]store.PartsPresetState),
|
|
||||||
ImportantItems: make(map[int32]int32),
|
|
||||||
CostumeActiveSkills: make(map[string]store.CostumeActiveSkillState),
|
|
||||||
WeaponSkills: make(map[string][]store.WeaponSkillState),
|
|
||||||
WeaponAbilities: make(map[string][]store.WeaponAbilityState),
|
|
||||||
DeckTypeNotes: make(map[model.DeckType]store.DeckTypeNoteState),
|
|
||||||
WeaponNotes: make(map[int32]store.WeaponNoteState),
|
|
||||||
NaviCutInPlayed: make(map[int32]bool),
|
|
||||||
ViewedMovies: make(map[int32]int64),
|
|
||||||
ContentsStories: make(map[int32]int64),
|
|
||||||
DrawnOmikuji: make(map[int32]int64),
|
|
||||||
PremiumItems: make(map[int32]int64),
|
|
||||||
DokanConfirmed: make(map[int32]bool),
|
|
||||||
ShopItems: make(map[int32]store.UserShopItemState),
|
|
||||||
ShopReplaceableLineup: make(map[int32]store.UserShopReplaceableLineupState),
|
|
||||||
ExploreScores: make(map[int32]store.ExploreScoreState),
|
|
||||||
|
|
||||||
CharacterBoards: make(map[int32]store.CharacterBoardState),
|
|
||||||
CharacterBoardAbilities: make(map[store.CharacterBoardAbilityKey]store.CharacterBoardAbilityState),
|
|
||||||
CharacterBoardStatusUps: make(map[store.CharacterBoardStatusUpKey]store.CharacterBoardStatusUpState),
|
|
||||||
|
|
||||||
CostumeAwakenStatusUps: make(map[store.CostumeAwakenStatusKey]store.CostumeAwakenStatusUpState),
|
|
||||||
AutoSaleSettings: make(map[int32]store.AutoSaleSettingState),
|
|
||||||
CharacterRebirths: make(map[int32]store.CharacterRebirthState),
|
|
||||||
}
|
|
||||||
store.EnsureDefaultDeck(user, nowMillis)
|
|
||||||
if grantStarterItems {
|
|
||||||
applyStarterItems(user)
|
|
||||||
}
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyStarterItems(user *store.UserState) {
|
|
||||||
for _, item := range defaultStarterItems {
|
|
||||||
switch item.Type {
|
|
||||||
case model.PossessionTypeFreeGem:
|
|
||||||
user.Gem.FreeGem += item.Qty
|
|
||||||
case model.PossessionTypeConsumableItem:
|
|
||||||
user.ConsumableItems[item.Id] += item.Qty
|
|
||||||
case model.PossessionTypeMaterial:
|
|
||||||
user.Materials[item.Id] += item.Qty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package memory
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"lunar-tear/server/internal/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
func snapshotPath(dir string, sceneId int32) string {
|
|
||||||
return filepath.Join(dir, fmt.Sprintf("scene_%d.json", sceneId))
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveSnapshot(user *store.UserState, dir string) {
|
|
||||||
sceneId := user.MainQuest.CurrentQuestSceneId
|
|
||||||
if sceneId == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data, err := json.MarshalIndent(user, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[snapshot] marshal error for scene=%d: %v", sceneId, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
path := snapshotPath(dir, sceneId)
|
|
||||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
|
||||||
log.Printf("[snapshot] write error for scene=%d: %v", sceneId, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Printf("[snapshot] saved scene=%d (%d bytes)", sceneId, len(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSceneId extracts the numeric scene ID from a filename of the form "scene_<id>.json".
|
|
||||||
// Returns (0, false) if the name does not match the expected format.
|
|
||||||
func parseSceneId(name string) (int32, bool) {
|
|
||||||
if !strings.HasPrefix(name, "scene_") || !strings.HasSuffix(name, ".json") {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
raw := strings.TrimSuffix(strings.TrimPrefix(name, "scene_"), ".json")
|
|
||||||
id, err := strconv.ParseInt(raw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return int32(id), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// LatestSnapshotSceneId scans dir for scene_*.json files and returns the scene ID
|
|
||||||
// of the most recently modified snapshot. Returns (0, false) if none are found.
|
|
||||||
func LatestSnapshotSceneId(dir string) (int32, bool) {
|
|
||||||
entries, err := os.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
var latestId int32
|
|
||||||
var latestMod int64
|
|
||||||
for _, e := range entries {
|
|
||||||
if e.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
id, ok := parseSceneId(e.Name())
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
info, err := e.Info()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if mt := info.ModTime().UnixNano(); mt > latestMod {
|
|
||||||
latestMod = mt
|
|
||||||
latestId = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if latestId == 0 {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return latestId, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadSnapshot(dir string, sceneId int32) (*store.UserState, error) {
|
|
||||||
path := snapshotPath(dir, sceneId)
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("read snapshot scene=%d: %w", sceneId, err)
|
|
||||||
}
|
|
||||||
var user store.UserState
|
|
||||||
if err := json.Unmarshal(data, &user); err != nil {
|
|
||||||
return nil, fmt.Errorf("unmarshal snapshot scene=%d: %w", sceneId, err)
|
|
||||||
}
|
|
||||||
user.EnsureMaps()
|
|
||||||
return &user, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
starterMissionId = int32(1)
|
||||||
|
starterMainQuestRouteId = int32(1)
|
||||||
|
starterMainQuestSeasonId = int32(1)
|
||||||
|
missionInProgress = int32(1)
|
||||||
|
|
||||||
|
defaultBirthYear = int32(2000)
|
||||||
|
defaultBirthMonth = int32(1)
|
||||||
|
defaultBackupToken = "mock-backup-token"
|
||||||
|
defaultChargeMoneyThisMonth = int64(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
func SeedUserState(userId int64, uuid string, nowMillis int64) *UserState {
|
||||||
|
user := &UserState{
|
||||||
|
UserId: userId,
|
||||||
|
Uuid: uuid,
|
||||||
|
PlayerId: userId,
|
||||||
|
OsType: 2,
|
||||||
|
PlatformType: 2,
|
||||||
|
UserRestrictionType: 0,
|
||||||
|
RegisterDatetime: nowMillis,
|
||||||
|
GameStartDatetime: nowMillis,
|
||||||
|
LatestVersion: 0,
|
||||||
|
BirthYear: defaultBirthYear,
|
||||||
|
BirthMonth: defaultBirthMonth,
|
||||||
|
BackupToken: defaultBackupToken,
|
||||||
|
ChargeMoneyThisMonth: defaultChargeMoneyThisMonth,
|
||||||
|
Setting: UserSettingState{
|
||||||
|
IsNotifyPurchaseAlert: false,
|
||||||
|
LatestVersion: 0,
|
||||||
|
},
|
||||||
|
Status: UserStatusState{
|
||||||
|
Level: 1,
|
||||||
|
Exp: 0,
|
||||||
|
StaminaMilliValue: 50000,
|
||||||
|
StaminaUpdateDatetime: nowMillis,
|
||||||
|
LatestVersion: 0,
|
||||||
|
},
|
||||||
|
Gem: UserGemState{
|
||||||
|
PaidGem: 0,
|
||||||
|
FreeGem: 0,
|
||||||
|
},
|
||||||
|
Profile: UserProfileState{
|
||||||
|
Name: "",
|
||||||
|
NameUpdateDatetime: 0,
|
||||||
|
Message: "",
|
||||||
|
MessageUpdateDatetime: nowMillis,
|
||||||
|
FavoriteCostumeId: 0,
|
||||||
|
FavoriteCostumeIdUpdateDatetime: nowMillis,
|
||||||
|
LatestVersion: 0,
|
||||||
|
},
|
||||||
|
Login: UserLoginState{
|
||||||
|
TotalLoginCount: 1,
|
||||||
|
ContinualLoginCount: 1,
|
||||||
|
MaxContinualLoginCount: 1,
|
||||||
|
LastLoginDatetime: nowMillis,
|
||||||
|
LastComebackLoginDatetime: 0,
|
||||||
|
LatestVersion: 0,
|
||||||
|
},
|
||||||
|
LoginBonus: UserLoginBonusState{
|
||||||
|
LoginBonusId: 1,
|
||||||
|
CurrentPageNumber: 1,
|
||||||
|
CurrentStampNumber: 0,
|
||||||
|
LatestRewardReceiveDatetime: 0,
|
||||||
|
LatestVersion: 0,
|
||||||
|
},
|
||||||
|
Tutorials: map[int32]TutorialProgressState{
|
||||||
|
1: {TutorialType: 1},
|
||||||
|
},
|
||||||
|
Battle: BattleState{},
|
||||||
|
Gifts: GiftState{
|
||||||
|
NotReceived: []NotReceivedGiftState{},
|
||||||
|
Received: []ReceivedGiftState{},
|
||||||
|
},
|
||||||
|
Gacha: GachaState{
|
||||||
|
ConvertedGachaMedal: ConvertedGachaMedalState{
|
||||||
|
ConvertedMedalPossession: []ConsumableItemState{},
|
||||||
|
},
|
||||||
|
BannerStates: make(map[int32]GachaBannerState),
|
||||||
|
},
|
||||||
|
MainQuest: MainQuestState{
|
||||||
|
CurrentMainQuestRouteId: starterMainQuestRouteId,
|
||||||
|
MainQuestSeasonId: starterMainQuestSeasonId,
|
||||||
|
},
|
||||||
|
Notifications: NotificationState{
|
||||||
|
GiftNotReceiveCount: 1,
|
||||||
|
},
|
||||||
|
Characters: make(map[int32]CharacterState),
|
||||||
|
Costumes: make(map[string]CostumeState),
|
||||||
|
Weapons: make(map[string]WeaponState),
|
||||||
|
Companions: make(map[string]CompanionState),
|
||||||
|
DeckCharacters: make(map[string]DeckCharacterState),
|
||||||
|
Decks: make(map[DeckKey]DeckState),
|
||||||
|
DeckSubWeapons: make(map[string][]string),
|
||||||
|
DeckParts: make(map[string][]string),
|
||||||
|
Quests: make(map[int32]UserQuestState),
|
||||||
|
QuestMissions: make(map[QuestMissionKey]UserQuestMissionState),
|
||||||
|
SideStoryQuests: make(map[int32]SideStoryQuestProgress),
|
||||||
|
QuestLimitContentStatus: make(map[int32]QuestLimitContentStatus),
|
||||||
|
BigHuntMaxScores: make(map[int32]BigHuntMaxScore),
|
||||||
|
BigHuntStatuses: make(map[int32]BigHuntStatus),
|
||||||
|
BigHuntScheduleMaxScores: make(map[BigHuntScheduleScoreKey]BigHuntScheduleMaxScore),
|
||||||
|
BigHuntWeeklyMaxScores: make(map[BigHuntWeeklyScoreKey]BigHuntWeeklyMaxScore),
|
||||||
|
BigHuntWeeklyStatuses: make(map[int64]BigHuntWeeklyStatus),
|
||||||
|
WeaponStories: make(map[int32]WeaponStoryState),
|
||||||
|
Missions: map[int32]UserMissionState{
|
||||||
|
starterMissionId: {
|
||||||
|
MissionId: starterMissionId,
|
||||||
|
StartDatetime: nowMillis,
|
||||||
|
MissionProgressStatusType: missionInProgress,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Gimmick: GimmickState{
|
||||||
|
Progress: make(map[GimmickKey]GimmickProgressState),
|
||||||
|
OrnamentProgress: make(map[GimmickOrnamentKey]GimmickOrnamentProgressState),
|
||||||
|
Sequences: make(map[GimmickSequenceKey]GimmickSequenceState),
|
||||||
|
Unlocks: make(map[GimmickKey]GimmickUnlockState),
|
||||||
|
},
|
||||||
|
CageOrnamentRewards: make(map[int32]CageOrnamentRewardState),
|
||||||
|
ConsumableItems: make(map[int32]int32),
|
||||||
|
Materials: make(map[int32]int32),
|
||||||
|
Thoughts: make(map[string]ThoughtState),
|
||||||
|
Parts: make(map[string]PartsState),
|
||||||
|
PartsGroupNotes: make(map[int32]PartsGroupNoteState),
|
||||||
|
PartsPresets: make(map[int32]PartsPresetState),
|
||||||
|
ImportantItems: make(map[int32]int32),
|
||||||
|
CostumeActiveSkills: make(map[string]CostumeActiveSkillState),
|
||||||
|
WeaponSkills: make(map[string][]WeaponSkillState),
|
||||||
|
WeaponAbilities: make(map[string][]WeaponAbilityState),
|
||||||
|
DeckTypeNotes: make(map[model.DeckType]DeckTypeNoteState),
|
||||||
|
WeaponNotes: make(map[int32]WeaponNoteState),
|
||||||
|
NaviCutInPlayed: make(map[int32]bool),
|
||||||
|
ViewedMovies: make(map[int32]int64),
|
||||||
|
ContentsStories: make(map[int32]int64),
|
||||||
|
DrawnOmikuji: make(map[int32]int64),
|
||||||
|
PremiumItems: make(map[int32]int64),
|
||||||
|
DokanConfirmed: make(map[int32]bool),
|
||||||
|
ShopItems: make(map[int32]UserShopItemState),
|
||||||
|
ShopReplaceableLineup: make(map[int32]UserShopReplaceableLineupState),
|
||||||
|
ExploreScores: make(map[int32]ExploreScoreState),
|
||||||
|
|
||||||
|
CharacterBoards: make(map[int32]CharacterBoardState),
|
||||||
|
CharacterBoardAbilities: make(map[CharacterBoardAbilityKey]CharacterBoardAbilityState),
|
||||||
|
CharacterBoardStatusUps: make(map[CharacterBoardStatusUpKey]CharacterBoardStatusUpState),
|
||||||
|
|
||||||
|
CostumeAwakenStatusUps: make(map[CostumeAwakenStatusKey]CostumeAwakenStatusUpState),
|
||||||
|
AutoSaleSettings: make(map[int32]AutoSaleSettingState),
|
||||||
|
CharacterRebirths: make(map[int32]CharacterRebirthState),
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
@@ -0,0 +1,724 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/model"
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *SQLiteStore) LoadUser(userId int64) (store.UserState, error) {
|
||||||
|
var u store.UserState
|
||||||
|
|
||||||
|
err := s.db.QueryRow(`SELECT user_id, uuid, player_id, os_type, platform_type, user_restriction_type,
|
||||||
|
register_datetime, game_start_datetime, latest_version, birth_year, birth_month,
|
||||||
|
backup_token, charge_money_this_month FROM users WHERE user_id = ?`, userId).Scan(
|
||||||
|
&u.UserId, &u.Uuid, &u.PlayerId, &u.OsType, &u.PlatformType, &u.UserRestrictionType,
|
||||||
|
&u.RegisterDatetime, &u.GameStartDatetime, &u.LatestVersion, &u.BirthYear, &u.BirthMonth,
|
||||||
|
&u.BackupToken, &u.ChargeMoneyThisMonth)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return u, store.ErrNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return u, fmt.Errorf("load users: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
initMaps(&u)
|
||||||
|
|
||||||
|
load1to1(s.db, userId, &u)
|
||||||
|
loadMapTables(s.db, userId, &u)
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initMaps(u *store.UserState) {
|
||||||
|
u.Tutorials = make(map[int32]store.TutorialProgressState)
|
||||||
|
u.Characters = make(map[int32]store.CharacterState)
|
||||||
|
u.Costumes = make(map[string]store.CostumeState)
|
||||||
|
u.Weapons = make(map[string]store.WeaponState)
|
||||||
|
u.Companions = make(map[string]store.CompanionState)
|
||||||
|
u.Thoughts = make(map[string]store.ThoughtState)
|
||||||
|
u.DeckCharacters = make(map[string]store.DeckCharacterState)
|
||||||
|
u.Decks = make(map[store.DeckKey]store.DeckState)
|
||||||
|
u.DeckSubWeapons = make(map[string][]string)
|
||||||
|
u.DeckParts = make(map[string][]string)
|
||||||
|
u.Quests = make(map[int32]store.UserQuestState)
|
||||||
|
u.QuestMissions = make(map[store.QuestMissionKey]store.UserQuestMissionState)
|
||||||
|
u.Missions = make(map[int32]store.UserMissionState)
|
||||||
|
u.WeaponStories = make(map[int32]store.WeaponStoryState)
|
||||||
|
u.WeaponNotes = make(map[int32]store.WeaponNoteState)
|
||||||
|
u.WeaponSkills = make(map[string][]store.WeaponSkillState)
|
||||||
|
u.WeaponAbilities = make(map[string][]store.WeaponAbilityState)
|
||||||
|
u.WeaponAwakens = make(map[string]store.WeaponAwakenState)
|
||||||
|
u.CostumeActiveSkills = make(map[string]store.CostumeActiveSkillState)
|
||||||
|
u.CostumeAwakenStatusUps = make(map[store.CostumeAwakenStatusKey]store.CostumeAwakenStatusUpState)
|
||||||
|
u.CostumeLotteryEffects = make(map[store.CostumeLotteryEffectKey]store.CostumeLotteryEffectState)
|
||||||
|
u.CostumeLotteryEffectPending = make(map[string]store.CostumeLotteryEffectPendingState)
|
||||||
|
u.Parts = make(map[string]store.PartsState)
|
||||||
|
u.PartsGroupNotes = make(map[int32]store.PartsGroupNoteState)
|
||||||
|
u.PartsPresets = make(map[int32]store.PartsPresetState)
|
||||||
|
u.DeckTypeNotes = make(map[model.DeckType]store.DeckTypeNoteState)
|
||||||
|
u.ConsumableItems = make(map[int32]int32)
|
||||||
|
u.Materials = make(map[int32]int32)
|
||||||
|
u.ImportantItems = make(map[int32]int32)
|
||||||
|
u.PremiumItems = make(map[int32]int64)
|
||||||
|
u.NaviCutInPlayed = make(map[int32]bool)
|
||||||
|
u.ViewedMovies = make(map[int32]int64)
|
||||||
|
u.ContentsStories = make(map[int32]int64)
|
||||||
|
u.DrawnOmikuji = make(map[int32]int64)
|
||||||
|
u.DokanConfirmed = make(map[int32]bool)
|
||||||
|
u.ShopItems = make(map[int32]store.UserShopItemState)
|
||||||
|
u.ShopReplaceableLineup = make(map[int32]store.UserShopReplaceableLineupState)
|
||||||
|
u.ExploreScores = make(map[int32]store.ExploreScoreState)
|
||||||
|
u.CageOrnamentRewards = make(map[int32]store.CageOrnamentRewardState)
|
||||||
|
u.CharacterBoards = make(map[int32]store.CharacterBoardState)
|
||||||
|
u.CharacterBoardAbilities = make(map[store.CharacterBoardAbilityKey]store.CharacterBoardAbilityState)
|
||||||
|
u.CharacterBoardStatusUps = make(map[store.CharacterBoardStatusUpKey]store.CharacterBoardStatusUpState)
|
||||||
|
u.CharacterRebirths = make(map[int32]store.CharacterRebirthState)
|
||||||
|
u.AutoSaleSettings = make(map[int32]store.AutoSaleSettingState)
|
||||||
|
u.SideStoryQuests = make(map[int32]store.SideStoryQuestProgress)
|
||||||
|
u.QuestLimitContentStatus = make(map[int32]store.QuestLimitContentStatus)
|
||||||
|
u.BigHuntMaxScores = make(map[int32]store.BigHuntMaxScore)
|
||||||
|
u.BigHuntStatuses = make(map[int32]store.BigHuntStatus)
|
||||||
|
u.BigHuntScheduleMaxScores = make(map[store.BigHuntScheduleScoreKey]store.BigHuntScheduleMaxScore)
|
||||||
|
u.BigHuntWeeklyMaxScores = make(map[store.BigHuntWeeklyScoreKey]store.BigHuntWeeklyMaxScore)
|
||||||
|
u.BigHuntWeeklyStatuses = make(map[int64]store.BigHuntWeeklyStatus)
|
||||||
|
u.Gacha.BannerStates = make(map[int32]store.GachaBannerState)
|
||||||
|
u.Gacha.ConvertedGachaMedal.ConvertedMedalPossession = []store.ConsumableItemState{}
|
||||||
|
u.Gifts.NotReceived = []store.NotReceivedGiftState{}
|
||||||
|
u.Gifts.Received = []store.ReceivedGiftState{}
|
||||||
|
u.Gimmick.Progress = make(map[store.GimmickKey]store.GimmickProgressState)
|
||||||
|
u.Gimmick.OrnamentProgress = make(map[store.GimmickOrnamentKey]store.GimmickOrnamentProgressState)
|
||||||
|
u.Gimmick.Sequences = make(map[store.GimmickSequenceKey]store.GimmickSequenceState)
|
||||||
|
u.Gimmick.Unlocks = make(map[store.GimmickKey]store.GimmickUnlockState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func load1to1(db *sql.DB, uid int64, u *store.UserState) {
|
||||||
|
var b int
|
||||||
|
_ = db.QueryRow(`SELECT is_notify_purchase_alert, latest_version FROM user_setting WHERE user_id=?`, uid).
|
||||||
|
Scan(&b, &u.Setting.LatestVersion)
|
||||||
|
u.Setting.IsNotifyPurchaseAlert = b != 0
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT level, exp, stamina_milli_value, stamina_update_datetime, latest_version FROM user_status WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.Status.Level, &u.Status.Exp, &u.Status.StaminaMilliValue, &u.Status.StaminaUpdateDatetime, &u.Status.LatestVersion)
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT paid_gem, free_gem FROM user_gem WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.Gem.PaidGem, &u.Gem.FreeGem)
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT name, name_update_datetime, message, message_update_datetime, favorite_costume_id,
|
||||||
|
favorite_costume_id_update_datetime, latest_version FROM user_profile WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.Profile.Name, &u.Profile.NameUpdateDatetime, &u.Profile.Message, &u.Profile.MessageUpdateDatetime,
|
||||||
|
&u.Profile.FavoriteCostumeId, &u.Profile.FavoriteCostumeIdUpdateDatetime, &u.Profile.LatestVersion)
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT total_login_count, continual_login_count, max_continual_login_count,
|
||||||
|
last_login_datetime, last_comeback_login_datetime, latest_version FROM user_login WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.Login.TotalLoginCount, &u.Login.ContinualLoginCount, &u.Login.MaxContinualLoginCount,
|
||||||
|
&u.Login.LastLoginDatetime, &u.Login.LastComebackLoginDatetime, &u.Login.LatestVersion)
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT login_bonus_id, current_page_number, current_stamp_number,
|
||||||
|
latest_reward_receive_datetime, latest_version FROM user_login_bonus WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.LoginBonus.LoginBonusId, &u.LoginBonus.CurrentPageNumber, &u.LoginBonus.CurrentStampNumber,
|
||||||
|
&u.LoginBonus.LatestRewardReceiveDatetime, &u.LoginBonus.LatestVersion)
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT current_quest_flow_type, current_main_quest_route_id, current_quest_scene_id,
|
||||||
|
head_quest_scene_id, is_reached_last_quest_scene, progress_quest_scene_id, progress_head_quest_scene_id,
|
||||||
|
progress_quest_flow_type, main_quest_season_id, latest_version, saved_current_quest_scene_id,
|
||||||
|
saved_head_quest_scene_id, replay_flow_current_quest_scene_id, replay_flow_head_quest_scene_id
|
||||||
|
FROM user_main_quest WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.MainQuest.CurrentQuestFlowType, &u.MainQuest.CurrentMainQuestRouteId, &u.MainQuest.CurrentQuestSceneId,
|
||||||
|
&u.MainQuest.HeadQuestSceneId, &b, &u.MainQuest.ProgressQuestSceneId, &u.MainQuest.ProgressHeadQuestSceneId,
|
||||||
|
&u.MainQuest.ProgressQuestFlowType, &u.MainQuest.MainQuestSeasonId, &u.MainQuest.LatestVersion,
|
||||||
|
&u.MainQuest.SavedCurrentQuestSceneId, &u.MainQuest.SavedHeadQuestSceneId,
|
||||||
|
&u.MainQuest.ReplayFlowCurrentQuestSceneId, &u.MainQuest.ReplayFlowHeadQuestSceneId)
|
||||||
|
u.MainQuest.IsReachedLastQuestScene = b != 0
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT current_event_quest_chapter_id, current_quest_id, current_quest_scene_id,
|
||||||
|
head_quest_scene_id, latest_version FROM user_event_quest WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.EventQuest.CurrentEventQuestChapterId, &u.EventQuest.CurrentQuestId,
|
||||||
|
&u.EventQuest.CurrentQuestSceneId, &u.EventQuest.HeadQuestSceneId, &u.EventQuest.LatestVersion)
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT current_quest_id, current_quest_scene_id, head_quest_scene_id, latest_version
|
||||||
|
FROM user_extra_quest WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.ExtraQuest.CurrentQuestId, &u.ExtraQuest.CurrentQuestSceneId,
|
||||||
|
&u.ExtraQuest.HeadQuestSceneId, &u.ExtraQuest.LatestVersion)
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT current_side_story_quest_id, current_side_story_quest_scene_id, latest_version
|
||||||
|
FROM user_side_story_active WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.SideStoryActiveProgress.CurrentSideStoryQuestId,
|
||||||
|
&u.SideStoryActiveProgress.CurrentSideStoryQuestSceneId, &u.SideStoryActiveProgress.LatestVersion)
|
||||||
|
|
||||||
|
var isDryRun int
|
||||||
|
_ = db.QueryRow(`SELECT current_big_hunt_boss_quest_id, current_big_hunt_quest_id, current_quest_scene_id,
|
||||||
|
is_dry_run, latest_version, deck_type, user_triple_deck_number, boss_knock_down_count,
|
||||||
|
max_combo_count, total_damage, deck_number, battle_binary
|
||||||
|
FROM user_big_hunt_state WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.BigHuntProgress.CurrentBigHuntBossQuestId, &u.BigHuntProgress.CurrentBigHuntQuestId,
|
||||||
|
&u.BigHuntProgress.CurrentQuestSceneId, &isDryRun, &u.BigHuntProgress.LatestVersion,
|
||||||
|
&u.BigHuntBattleDetail.DeckType, &u.BigHuntBattleDetail.UserTripleDeckNumber,
|
||||||
|
&u.BigHuntBattleDetail.BossKnockDownCount, &u.BigHuntBattleDetail.MaxComboCount,
|
||||||
|
&u.BigHuntBattleDetail.TotalDamage, &u.BigHuntDeckNumber, &u.BigHuntBattleBinary)
|
||||||
|
u.BigHuntProgress.IsDryRun = isDryRun != 0
|
||||||
|
|
||||||
|
var isActive, isUnread int
|
||||||
|
_ = db.QueryRow(`SELECT is_active, start_count, finish_count, last_started_at, last_finished_at,
|
||||||
|
last_user_party_count, last_npc_party_count, last_battle_binary_size, last_elapsed_frame_count
|
||||||
|
FROM user_battle WHERE user_id=?`, uid).
|
||||||
|
Scan(&isActive, &u.Battle.StartCount, &u.Battle.FinishCount, &u.Battle.LastStartedAt,
|
||||||
|
&u.Battle.LastFinishedAt, &u.Battle.LastUserPartyCount, &u.Battle.LastNpcPartyCount,
|
||||||
|
&u.Battle.LastBattleBinarySize, &u.Battle.LastElapsedFrameCount)
|
||||||
|
u.Battle.IsActive = isActive != 0
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT gift_not_receive_count, friend_request_receive_count, is_exist_unread_information
|
||||||
|
FROM user_notification WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.Notifications.GiftNotReceiveCount, &u.Notifications.FriendRequestReceiveCount, &isUnread)
|
||||||
|
u.Notifications.IsExistUnreadInformation = isUnread != 0
|
||||||
|
|
||||||
|
var isCP int
|
||||||
|
_ = db.QueryRow(`SELECT is_current_progress, drop_item_start_datetime, current_drop_item_count, latest_version
|
||||||
|
FROM user_portal_cage WHERE user_id=?`, uid).
|
||||||
|
Scan(&isCP, &u.PortalCageStatus.DropItemStartDatetime, &u.PortalCageStatus.CurrentDropItemCount,
|
||||||
|
&u.PortalCageStatus.LatestVersion)
|
||||||
|
u.PortalCageStatus.IsCurrentProgress = isCP != 0
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT start_datetime, open_minutes, daily_opened_count, latest_version
|
||||||
|
FROM user_guerrilla_free_open WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.GuerrillaFreeOpen.StartDatetime, &u.GuerrillaFreeOpen.OpenMinutes,
|
||||||
|
&u.GuerrillaFreeOpen.DailyOpenedCount, &u.GuerrillaFreeOpen.LatestVersion)
|
||||||
|
|
||||||
|
var isTicket int
|
||||||
|
_ = db.QueryRow(`SELECT is_use_explore_ticket, playing_explore_id, latest_play_datetime, latest_version
|
||||||
|
FROM user_explore WHERE user_id=?`, uid).
|
||||||
|
Scan(&isTicket, &u.Explore.PlayingExploreId, &u.Explore.LatestPlayDatetime, &u.Explore.LatestVersion)
|
||||||
|
u.Explore.IsUseExploreTicket = isTicket != 0
|
||||||
|
|
||||||
|
_ = db.QueryRow(`SELECT lineup_update_count, latest_lineup_update_datetime, latest_version
|
||||||
|
FROM user_shop_replaceable WHERE user_id=?`, uid).
|
||||||
|
Scan(&u.ShopReplaceable.LineupUpdateCount, &u.ShopReplaceable.LatestLineupUpdateDatetime,
|
||||||
|
&u.ShopReplaceable.LatestVersion)
|
||||||
|
|
||||||
|
var rewardAvail int
|
||||||
|
var obtainItemId, obtainCount sql.NullInt64
|
||||||
|
_ = db.QueryRow(`SELECT reward_available, todays_current_draw_count, daily_max_count,
|
||||||
|
last_reward_draw_date, obtain_consumable_item_id, obtain_count
|
||||||
|
FROM user_gacha WHERE user_id=?`, uid).
|
||||||
|
Scan(&rewardAvail, &u.Gacha.TodaysCurrentDrawCount, &u.Gacha.DailyMaxCount,
|
||||||
|
&u.Gacha.LastRewardDrawDate, &obtainItemId, &obtainCount)
|
||||||
|
u.Gacha.RewardAvailable = rewardAvail != 0
|
||||||
|
if obtainItemId.Valid {
|
||||||
|
u.Gacha.ConvertedGachaMedal.ObtainPossession = &store.ConsumableItemState{
|
||||||
|
ConsumableItemId: int32(obtainItemId.Int64),
|
||||||
|
Count: int32(obtainCount.Int64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMapTables(db *sql.DB, uid int64, u *store.UserState) {
|
||||||
|
queryRows(db, `SELECT character_id, level, exp, latest_version FROM user_characters WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CharacterState
|
||||||
|
rows.Scan(&v.CharacterId, &v.Level, &v.Exp, &v.LatestVersion)
|
||||||
|
u.Characters[v.CharacterId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_costume_uuid, costume_id, limit_break_count, level, exp,
|
||||||
|
headup_display_view_id, acquisition_datetime, awaken_count,
|
||||||
|
costume_lottery_effect_unlocked_slot_count, latest_version
|
||||||
|
FROM user_costumes WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.CostumeState
|
||||||
|
rows.Scan(&v.UserCostumeUuid, &v.CostumeId, &v.LimitBreakCount, &v.Level, &v.Exp,
|
||||||
|
&v.HeadupDisplayViewId, &v.AcquisitionDatetime, &v.AwakenCount,
|
||||||
|
&v.CostumeLotteryEffectUnlockedSlotCount, &v.LatestVersion)
|
||||||
|
u.Costumes[v.UserCostumeUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_weapon_uuid, weapon_id, level, exp, limit_break_count,
|
||||||
|
is_protected, acquisition_datetime, latest_version FROM user_weapons WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.WeaponState
|
||||||
|
var prot int
|
||||||
|
rows.Scan(&v.UserWeaponUuid, &v.WeaponId, &v.Level, &v.Exp, &v.LimitBreakCount,
|
||||||
|
&prot, &v.AcquisitionDatetime, &v.LatestVersion)
|
||||||
|
v.IsProtected = prot != 0
|
||||||
|
u.Weapons[v.UserWeaponUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_companion_uuid, companion_id, headup_display_view_id, level,
|
||||||
|
acquisition_datetime, latest_version FROM user_companions WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CompanionState
|
||||||
|
rows.Scan(&v.UserCompanionUuid, &v.CompanionId, &v.HeadupDisplayViewId, &v.Level,
|
||||||
|
&v.AcquisitionDatetime, &v.LatestVersion)
|
||||||
|
u.Companions[v.UserCompanionUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_thought_uuid, thought_id, acquisition_datetime, latest_version
|
||||||
|
FROM user_thoughts WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.ThoughtState
|
||||||
|
rows.Scan(&v.UserThoughtUuid, &v.ThoughtId, &v.AcquisitionDatetime, &v.LatestVersion)
|
||||||
|
u.Thoughts[v.UserThoughtUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_deck_character_uuid, user_costume_uuid, main_user_weapon_uuid,
|
||||||
|
user_companion_uuid, power, user_thought_uuid, dressup_costume_id, latest_version
|
||||||
|
FROM user_deck_characters WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.DeckCharacterState
|
||||||
|
rows.Scan(&v.UserDeckCharacterUuid, &v.UserCostumeUuid, &v.MainUserWeaponUuid,
|
||||||
|
&v.UserCompanionUuid, &v.Power, &v.UserThoughtUuid, &v.DressupCostumeId, &v.LatestVersion)
|
||||||
|
u.DeckCharacters[v.UserDeckCharacterUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT deck_type, user_deck_number, user_deck_character_uuid01, user_deck_character_uuid02,
|
||||||
|
user_deck_character_uuid03, name, power, latest_version FROM user_decks WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.DeckState
|
||||||
|
var dt int32
|
||||||
|
rows.Scan(&dt, &v.UserDeckNumber, &v.UserDeckCharacterUuid01, &v.UserDeckCharacterUuid02,
|
||||||
|
&v.UserDeckCharacterUuid03, &v.Name, &v.Power, &v.LatestVersion)
|
||||||
|
v.DeckType = model.DeckType(dt)
|
||||||
|
u.Decks[store.DeckKey{DeckType: v.DeckType, UserDeckNumber: v.UserDeckNumber}] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_deck_character_uuid, ordinal, user_weapon_uuid
|
||||||
|
FROM user_deck_sub_weapons WHERE user_id=? ORDER BY user_deck_character_uuid, ordinal`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var key, val string
|
||||||
|
var ord int
|
||||||
|
rows.Scan(&key, &ord, &val)
|
||||||
|
u.DeckSubWeapons[key] = append(u.DeckSubWeapons[key], val)
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_deck_character_uuid, ordinal, user_parts_uuid
|
||||||
|
FROM user_deck_parts WHERE user_id=? ORDER BY user_deck_character_uuid, ordinal`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var key, val string
|
||||||
|
var ord int
|
||||||
|
rows.Scan(&key, &ord, &val)
|
||||||
|
u.DeckParts[key] = append(u.DeckParts[key], val)
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT quest_id, quest_state_type, is_battle_only, user_deck_number, latest_start_datetime,
|
||||||
|
clear_count, daily_clear_count, last_clear_datetime, shortest_clear_frames, is_reward_granted, latest_version
|
||||||
|
FROM user_quests WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.UserQuestState
|
||||||
|
var bo, rg int
|
||||||
|
rows.Scan(&v.QuestId, &v.QuestStateType, &bo, &v.UserDeckNumber, &v.LatestStartDatetime,
|
||||||
|
&v.ClearCount, &v.DailyClearCount, &v.LastClearDatetime, &v.ShortestClearFrames, &rg, &v.LatestVersion)
|
||||||
|
v.IsBattleOnly = bo != 0
|
||||||
|
v.IsRewardGranted = rg != 0
|
||||||
|
u.Quests[v.QuestId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT quest_id, quest_mission_id, progress_value, is_clear, latest_clear_datetime, latest_version
|
||||||
|
FROM user_quest_missions WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.UserQuestMissionState
|
||||||
|
var ic int
|
||||||
|
rows.Scan(&v.QuestId, &v.QuestMissionId, &v.ProgressValue, &ic, &v.LatestClearDatetime, &v.LatestVersion)
|
||||||
|
v.IsClear = ic != 0
|
||||||
|
u.QuestMissions[store.QuestMissionKey{QuestId: v.QuestId, QuestMissionId: v.QuestMissionId}] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT mission_id, start_datetime, progress_value, mission_progress_status_type,
|
||||||
|
clear_datetime, latest_version FROM user_missions WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.UserMissionState
|
||||||
|
rows.Scan(&v.MissionId, &v.StartDatetime, &v.ProgressValue, &v.MissionProgressStatusType,
|
||||||
|
&v.ClearDatetime, &v.LatestVersion)
|
||||||
|
u.Missions[v.MissionId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT tutorial_type, progress_phase, choice_id, latest_version
|
||||||
|
FROM user_tutorials WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.TutorialProgressState
|
||||||
|
rows.Scan(&v.TutorialType, &v.ProgressPhase, &v.ChoiceId, &v.LatestVersion)
|
||||||
|
u.Tutorials[v.TutorialType] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT side_story_quest_id, head_side_story_quest_scene_id, side_story_quest_state_type, latest_version
|
||||||
|
FROM user_side_story_quests WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var id, head, st int32
|
||||||
|
var lv int64
|
||||||
|
rows.Scan(&id, &head, &st, &lv)
|
||||||
|
u.SideStoryQuests[id] = store.SideStoryQuestProgress{
|
||||||
|
HeadSideStoryQuestSceneId: head, SideStoryQuestStateType: model.SideStoryQuestStateType(st), LatestVersion: lv,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT limit_content_id, limit_content_quest_status_type, event_quest_chapter_id, latest_version
|
||||||
|
FROM user_quest_limit_content_status WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var id int32
|
||||||
|
var v store.QuestLimitContentStatus
|
||||||
|
rows.Scan(&id, &v.LimitContentQuestStatusType, &v.EventQuestChapterId, &v.LatestVersion)
|
||||||
|
u.QuestLimitContentStatus[id] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT weapon_id, released_max_story_index, latest_version FROM user_weapon_stories WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.WeaponStoryState
|
||||||
|
rows.Scan(&v.WeaponId, &v.ReleasedMaxStoryIndex, &v.LatestVersion)
|
||||||
|
u.WeaponStories[v.WeaponId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT weapon_id, max_level, max_limit_break_count, first_acquisition_datetime, latest_version
|
||||||
|
FROM user_weapon_notes WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.WeaponNoteState
|
||||||
|
rows.Scan(&v.WeaponId, &v.MaxLevel, &v.MaxLimitBreakCount, &v.FirstAcquisitionDatetime, &v.LatestVersion)
|
||||||
|
u.WeaponNotes[v.WeaponId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_weapon_uuid, slot_number, level FROM user_weapon_skills WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.WeaponSkillState
|
||||||
|
rows.Scan(&v.UserWeaponUuid, &v.SlotNumber, &v.Level)
|
||||||
|
u.WeaponSkills[v.UserWeaponUuid] = append(u.WeaponSkills[v.UserWeaponUuid], v)
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_weapon_uuid, slot_number, level FROM user_weapon_abilities WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.WeaponAbilityState
|
||||||
|
rows.Scan(&v.UserWeaponUuid, &v.SlotNumber, &v.Level)
|
||||||
|
u.WeaponAbilities[v.UserWeaponUuid] = append(u.WeaponAbilities[v.UserWeaponUuid], v)
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_weapon_uuid, latest_version FROM user_weapon_awakens WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.WeaponAwakenState
|
||||||
|
rows.Scan(&v.UserWeaponUuid, &v.LatestVersion)
|
||||||
|
u.WeaponAwakens[v.UserWeaponUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_costume_uuid, level, acquisition_datetime, latest_version
|
||||||
|
FROM user_costume_active_skills WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.CostumeActiveSkillState
|
||||||
|
rows.Scan(&v.UserCostumeUuid, &v.Level, &v.AcquisitionDatetime, &v.LatestVersion)
|
||||||
|
u.CostumeActiveSkills[v.UserCostumeUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_costume_uuid, status_calculation_type, hp, attack, vitality, agility,
|
||||||
|
critical_ratio, critical_attack, latest_version FROM user_costume_awaken_status_ups WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CostumeAwakenStatusUpState
|
||||||
|
var sct int32
|
||||||
|
rows.Scan(&v.UserCostumeUuid, &sct, &v.Hp, &v.Attack, &v.Vitality, &v.Agility,
|
||||||
|
&v.CriticalRatio, &v.CriticalAttack, &v.LatestVersion)
|
||||||
|
v.StatusCalculationType = model.StatusCalculationType(sct)
|
||||||
|
u.CostumeAwakenStatusUps[store.CostumeAwakenStatusKey{
|
||||||
|
UserCostumeUuid: v.UserCostumeUuid, StatusCalculationType: v.StatusCalculationType,
|
||||||
|
}] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_costume_uuid, slot_number, odds_number, latest_version
|
||||||
|
FROM user_costume_lottery_effects WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CostumeLotteryEffectState
|
||||||
|
rows.Scan(&v.UserCostumeUuid, &v.SlotNumber, &v.OddsNumber, &v.LatestVersion)
|
||||||
|
u.CostumeLotteryEffects[store.CostumeLotteryEffectKey{
|
||||||
|
UserCostumeUuid: v.UserCostumeUuid, SlotNumber: v.SlotNumber,
|
||||||
|
}] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_costume_uuid, slot_number, odds_number, latest_version
|
||||||
|
FROM user_costume_lottery_effect_pending WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CostumeLotteryEffectPendingState
|
||||||
|
rows.Scan(&v.UserCostumeUuid, &v.SlotNumber, &v.OddsNumber, &v.LatestVersion)
|
||||||
|
u.CostumeLotteryEffectPending[v.UserCostumeUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_parts_uuid, parts_id, level, parts_status_main_id, is_protected,
|
||||||
|
acquisition_datetime, latest_version FROM user_parts WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.PartsState
|
||||||
|
var prot int
|
||||||
|
rows.Scan(&v.UserPartsUuid, &v.PartsId, &v.Level, &v.PartsStatusMainId, &prot,
|
||||||
|
&v.AcquisitionDatetime, &v.LatestVersion)
|
||||||
|
v.IsProtected = prot != 0
|
||||||
|
u.Parts[v.UserPartsUuid] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT parts_group_id, first_acquisition_datetime, latest_version
|
||||||
|
FROM user_parts_group_notes WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.PartsGroupNoteState
|
||||||
|
rows.Scan(&v.PartsGroupId, &v.FirstAcquisitionDatetime, &v.LatestVersion)
|
||||||
|
u.PartsGroupNotes[v.PartsGroupId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT user_parts_preset_number, user_parts_uuid01, user_parts_uuid02, user_parts_uuid03,
|
||||||
|
name, user_parts_preset_tag_number, latest_version FROM user_parts_presets WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.PartsPresetState
|
||||||
|
rows.Scan(&v.UserPartsPresetNumber, &v.UserPartsUuid01, &v.UserPartsUuid02, &v.UserPartsUuid03,
|
||||||
|
&v.Name, &v.UserPartsPresetTagNumber, &v.LatestVersion)
|
||||||
|
u.PartsPresets[v.UserPartsPresetNumber] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT deck_type, max_deck_power, latest_version FROM user_deck_type_notes WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var dt int32
|
||||||
|
var v store.DeckTypeNoteState
|
||||||
|
rows.Scan(&dt, &v.MaxDeckPower, &v.LatestVersion)
|
||||||
|
v.DeckType = model.DeckType(dt)
|
||||||
|
u.DeckTypeNotes[v.DeckType] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
loadSimpleMap(db, uid, `SELECT consumable_item_id, count FROM user_consumable_items WHERE user_id=?`, u.ConsumableItems)
|
||||||
|
loadSimpleMap(db, uid, `SELECT material_id, count FROM user_materials WHERE user_id=?`, u.Materials)
|
||||||
|
loadSimpleMap(db, uid, `SELECT important_item_id, count FROM user_important_items WHERE user_id=?`, u.ImportantItems)
|
||||||
|
|
||||||
|
queryRows(db, `SELECT premium_item_id, count FROM user_premium_items WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var k int32
|
||||||
|
var v int64
|
||||||
|
rows.Scan(&k, &v)
|
||||||
|
u.PremiumItems[k] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT explore_id, max_score, max_score_update_datetime, latest_version
|
||||||
|
FROM user_explore_scores WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.ExploreScoreState
|
||||||
|
rows.Scan(&v.ExploreId, &v.MaxScore, &v.MaxScoreUpdateDatetime, &v.LatestVersion)
|
||||||
|
u.ExploreScores[v.ExploreId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT possession_auto_sale_item_type, possession_auto_sale_item_value
|
||||||
|
FROM user_auto_sale_settings WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.AutoSaleSettingState
|
||||||
|
rows.Scan(&v.PossessionAutoSaleItemType, &v.PossessionAutoSaleItemValue)
|
||||||
|
u.AutoSaleSettings[v.PossessionAutoSaleItemType] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT navi_cutin_id FROM user_navi_cutin_played WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var id int32
|
||||||
|
rows.Scan(&id)
|
||||||
|
u.NaviCutInPlayed[id] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
loadTimestampMap(db, uid, `SELECT movie_id, timestamp FROM user_viewed_movies WHERE user_id=?`, u.ViewedMovies)
|
||||||
|
loadTimestampMap(db, uid, `SELECT contents_story_id, timestamp FROM user_contents_stories WHERE user_id=?`, u.ContentsStories)
|
||||||
|
loadTimestampMap(db, uid, `SELECT omikuji_id, timestamp FROM user_drawn_omikuji WHERE user_id=?`, u.DrawnOmikuji)
|
||||||
|
|
||||||
|
queryRows(db, `SELECT dokan_id FROM user_dokan_confirmed WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var id int32
|
||||||
|
rows.Scan(&id)
|
||||||
|
u.DokanConfirmed[id] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gifts
|
||||||
|
queryRows(db, `SELECT user_gift_uuid, is_received, possession_type, possession_id, count, grant_datetime,
|
||||||
|
description_gift_text_id, equipment_data, expiration_datetime, received_datetime
|
||||||
|
FROM user_gifts WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var uuid string
|
||||||
|
var isRecv int
|
||||||
|
var gc store.GiftCommonState
|
||||||
|
var expDt, recvDt sql.NullInt64
|
||||||
|
var equipData []byte
|
||||||
|
rows.Scan(&uuid, &isRecv, &gc.PossessionType, &gc.PossessionId, &gc.Count, &gc.GrantDatetime,
|
||||||
|
&gc.DescriptionGiftTextId, &equipData, &expDt, &recvDt)
|
||||||
|
gc.EquipmentData = equipData
|
||||||
|
if isRecv == 0 {
|
||||||
|
u.Gifts.NotReceived = append(u.Gifts.NotReceived, store.NotReceivedGiftState{
|
||||||
|
GiftCommon: gc, ExpirationDatetime: expDt.Int64, UserGiftUuid: uuid,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
u.Gifts.Received = append(u.Gifts.Received, store.ReceivedGiftState{
|
||||||
|
GiftCommon: gc, ReceivedDatetime: recvDt.Int64,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gacha converted medals
|
||||||
|
queryRows(db, `SELECT consumable_item_id, count FROM user_gacha_converted_medals WHERE user_id=? ORDER BY ordinal`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.ConsumableItemState
|
||||||
|
rows.Scan(&v.ConsumableItemId, &v.Count)
|
||||||
|
u.Gacha.ConvertedGachaMedal.ConvertedMedalPossession = append(u.Gacha.ConvertedGachaMedal.ConvertedMedalPossession, v)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gacha banners
|
||||||
|
queryRows(db, `SELECT gacha_id, medal_count, step_number, loop_count, draw_count, box_number
|
||||||
|
FROM user_gacha_banners WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.GachaBannerState
|
||||||
|
rows.Scan(&v.GachaId, &v.MedalCount, &v.StepNumber, &v.LoopCount, &v.DrawCount, &v.BoxNumber)
|
||||||
|
v.BoxDrewCounts = make(map[int32]int32)
|
||||||
|
u.Gacha.BannerStates[v.GachaId] = v
|
||||||
|
})
|
||||||
|
queryRows(db, `SELECT gacha_id, box_item_id, count FROM user_gacha_banner_box_drew_counts WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var gachaId, boxItemId, count int32
|
||||||
|
rows.Scan(&gachaId, &boxItemId, &count)
|
||||||
|
if bs, ok := u.Gacha.BannerStates[gachaId]; ok {
|
||||||
|
bs.BoxDrewCounts[boxItemId] = count
|
||||||
|
u.Gacha.BannerStates[gachaId] = bs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Character boards
|
||||||
|
queryRows(db, `SELECT character_board_id, panel_release_bit1, panel_release_bit2, panel_release_bit3,
|
||||||
|
panel_release_bit4, latest_version FROM user_character_boards WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CharacterBoardState
|
||||||
|
rows.Scan(&v.CharacterBoardId, &v.PanelReleaseBit1, &v.PanelReleaseBit2,
|
||||||
|
&v.PanelReleaseBit3, &v.PanelReleaseBit4, &v.LatestVersion)
|
||||||
|
u.CharacterBoards[v.CharacterBoardId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT character_id, ability_id, level, latest_version
|
||||||
|
FROM user_character_board_abilities WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.CharacterBoardAbilityState
|
||||||
|
rows.Scan(&v.CharacterId, &v.AbilityId, &v.Level, &v.LatestVersion)
|
||||||
|
u.CharacterBoardAbilities[store.CharacterBoardAbilityKey{CharacterId: v.CharacterId, AbilityId: v.AbilityId}] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT character_id, status_calculation_type, hp, attack, vitality, agility,
|
||||||
|
critical_ratio, critical_attack, latest_version FROM user_character_board_status_ups WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CharacterBoardStatusUpState
|
||||||
|
rows.Scan(&v.CharacterId, &v.StatusCalculationType, &v.Hp, &v.Attack, &v.Vitality, &v.Agility,
|
||||||
|
&v.CriticalRatio, &v.CriticalAttack, &v.LatestVersion)
|
||||||
|
u.CharacterBoardStatusUps[store.CharacterBoardStatusUpKey{
|
||||||
|
CharacterId: v.CharacterId, StatusCalculationType: v.StatusCalculationType,
|
||||||
|
}] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT character_id, rebirth_count, latest_version FROM user_character_rebirths WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.CharacterRebirthState
|
||||||
|
rows.Scan(&v.CharacterId, &v.RebirthCount, &v.LatestVersion)
|
||||||
|
u.CharacterRebirths[v.CharacterId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT cage_ornament_id, acquisition_datetime, latest_version
|
||||||
|
FROM user_cage_ornament_rewards WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.CageOrnamentRewardState
|
||||||
|
rows.Scan(&v.CageOrnamentId, &v.AcquisitionDatetime, &v.LatestVersion)
|
||||||
|
u.CageOrnamentRewards[v.CageOrnamentId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT shop_item_id, bought_count, latest_bought_count_changed_datetime, latest_version
|
||||||
|
FROM user_shop_items WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.UserShopItemState
|
||||||
|
rows.Scan(&v.ShopItemId, &v.BoughtCount, &v.LatestBoughtCountChangedDatetime, &v.LatestVersion)
|
||||||
|
u.ShopItems[v.ShopItemId] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT slot_number, shop_item_id, latest_version FROM user_shop_replaceable_lineup WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.UserShopReplaceableLineupState
|
||||||
|
rows.Scan(&v.SlotNumber, &v.ShopItemId, &v.LatestVersion)
|
||||||
|
u.ShopReplaceableLineup[v.SlotNumber] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gimmick tables
|
||||||
|
queryRows(db, `SELECT gimmick_sequence_schedule_id, gimmick_sequence_id, gimmick_id,
|
||||||
|
is_gimmick_cleared, start_datetime, latest_version FROM user_gimmick_progress WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.GimmickProgressState
|
||||||
|
var ic int
|
||||||
|
rows.Scan(&v.Key.GimmickSequenceScheduleId, &v.Key.GimmickSequenceId, &v.Key.GimmickId,
|
||||||
|
&ic, &v.StartDatetime, &v.LatestVersion)
|
||||||
|
v.IsGimmickCleared = ic != 0
|
||||||
|
u.Gimmick.Progress[v.Key] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT gimmick_sequence_schedule_id, gimmick_sequence_id, gimmick_id,
|
||||||
|
gimmick_ornament_index, progress_value_bit, base_datetime, latest_version
|
||||||
|
FROM user_gimmick_ornament_progress WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var v store.GimmickOrnamentProgressState
|
||||||
|
rows.Scan(&v.Key.GimmickSequenceScheduleId, &v.Key.GimmickSequenceId, &v.Key.GimmickId,
|
||||||
|
&v.Key.GimmickOrnamentIndex, &v.ProgressValueBit, &v.BaseDatetime, &v.LatestVersion)
|
||||||
|
u.Gimmick.OrnamentProgress[v.Key] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT gimmick_sequence_schedule_id, gimmick_sequence_id,
|
||||||
|
is_gimmick_sequence_cleared, clear_datetime, latest_version FROM user_gimmick_sequences WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.GimmickSequenceState
|
||||||
|
var ic int
|
||||||
|
rows.Scan(&v.Key.GimmickSequenceScheduleId, &v.Key.GimmickSequenceId,
|
||||||
|
&ic, &v.ClearDatetime, &v.LatestVersion)
|
||||||
|
v.IsGimmickSequenceCleared = ic != 0
|
||||||
|
u.Gimmick.Sequences[v.Key] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT gimmick_sequence_schedule_id, gimmick_sequence_id, gimmick_id,
|
||||||
|
is_unlocked, latest_version FROM user_gimmick_unlocks WHERE user_id=?`, uid,
|
||||||
|
func(rows *sql.Rows) {
|
||||||
|
var v store.GimmickUnlockState
|
||||||
|
var iu int
|
||||||
|
rows.Scan(&v.Key.GimmickSequenceScheduleId, &v.Key.GimmickSequenceId, &v.Key.GimmickId,
|
||||||
|
&iu, &v.LatestVersion)
|
||||||
|
v.IsUnlocked = iu != 0
|
||||||
|
u.Gimmick.Unlocks[v.Key] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
// Big hunt maps
|
||||||
|
queryRows(db, `SELECT big_hunt_boss_id, max_score, max_score_update_datetime, latest_version
|
||||||
|
FROM user_big_hunt_max_scores WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var id int32
|
||||||
|
var v store.BigHuntMaxScore
|
||||||
|
rows.Scan(&id, &v.MaxScore, &v.MaxScoreUpdateDatetime, &v.LatestVersion)
|
||||||
|
u.BigHuntMaxScores[id] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT big_hunt_boss_id, daily_challenge_count, latest_challenge_datetime, latest_version
|
||||||
|
FROM user_big_hunt_statuses WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var id int32
|
||||||
|
var v store.BigHuntStatus
|
||||||
|
rows.Scan(&id, &v.DailyChallengeCount, &v.LatestChallengeDatetime, &v.LatestVersion)
|
||||||
|
u.BigHuntStatuses[id] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT big_hunt_schedule_id, big_hunt_boss_id, max_score, max_score_update_datetime, latest_version
|
||||||
|
FROM user_big_hunt_schedule_max_scores WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var k store.BigHuntScheduleScoreKey
|
||||||
|
var v store.BigHuntScheduleMaxScore
|
||||||
|
rows.Scan(&k.BigHuntScheduleId, &k.BigHuntBossId, &v.MaxScore, &v.MaxScoreUpdateDatetime, &v.LatestVersion)
|
||||||
|
u.BigHuntScheduleMaxScores[k] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT big_hunt_weekly_version, attribute_type, max_score, latest_version
|
||||||
|
FROM user_big_hunt_weekly_max_scores WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var k store.BigHuntWeeklyScoreKey
|
||||||
|
var v store.BigHuntWeeklyMaxScore
|
||||||
|
rows.Scan(&k.BigHuntWeeklyVersion, &k.AttributeType, &v.MaxScore, &v.LatestVersion)
|
||||||
|
u.BigHuntWeeklyMaxScores[k] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
queryRows(db, `SELECT big_hunt_weekly_version, is_received_weekly_reward, latest_version
|
||||||
|
FROM user_big_hunt_weekly_statuses WHERE user_id=?`, uid, func(rows *sql.Rows) {
|
||||||
|
var ver int64
|
||||||
|
var ir int
|
||||||
|
var lv int64
|
||||||
|
rows.Scan(&ver, &ir, &lv)
|
||||||
|
u.BigHuntWeeklyStatuses[ver] = store.BigHuntWeeklyStatus{IsReceivedWeeklyReward: ir != 0, LatestVersion: lv}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryRows(db *sql.DB, query string, uid int64, scan func(*sql.Rows)) {
|
||||||
|
rows, err := db.Query(query, uid)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
scan(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSimpleMap(db *sql.DB, uid int64, query string, m map[int32]int32) {
|
||||||
|
queryRows(db, query, uid, func(rows *sql.Rows) {
|
||||||
|
var k, v int32
|
||||||
|
rows.Scan(&k, &v)
|
||||||
|
m[k] = v
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTimestampMap(db *sql.DB, uid int64, query string, m map[int32]int64) {
|
||||||
|
queryRows(db, query, uid, func(rows *sql.Rows) {
|
||||||
|
var k int32
|
||||||
|
var v int64
|
||||||
|
rows.Scan(&k, &v)
|
||||||
|
m[k] = v
|
||||||
|
})
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *SQLiteStore) CreateSession(uuid string, ttl time.Duration) (store.SessionState, error) {
|
||||||
|
var userId int64
|
||||||
|
err := s.db.QueryRow(`SELECT user_id FROM users WHERE uuid = ?`, uuid).Scan(&userId)
|
||||||
|
if err != nil {
|
||||||
|
return store.SessionState{}, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
now := s.clock()
|
||||||
|
sessionKey := fmt.Sprintf("session_%d_%d", userId, now.UnixNano())
|
||||||
|
expireAt := now.Add(ttl)
|
||||||
|
|
||||||
|
_, err = s.db.Exec(
|
||||||
|
`INSERT INTO sessions (session_key, user_id, uuid, expire_at) VALUES (?, ?, ?, ?)`,
|
||||||
|
sessionKey, userId, uuid, expireAt.Format(time.RFC3339Nano),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return store.SessionState{}, fmt.Errorf("insert session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.SessionState{
|
||||||
|
SessionKey: sessionKey,
|
||||||
|
UserId: userId,
|
||||||
|
Uuid: uuid,
|
||||||
|
ExpireAt: expireAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLiteStore) ResolveUserId(sessionKey string) (int64, error) {
|
||||||
|
var userId int64
|
||||||
|
var expireStr string
|
||||||
|
err := s.db.QueryRow(
|
||||||
|
`SELECT user_id, expire_at FROM sessions WHERE session_key = ?`, sessionKey,
|
||||||
|
).Scan(&userId, &expireStr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
expireAt, err := time.Parse(time.RFC3339Nano, expireStr)
|
||||||
|
if err != nil {
|
||||||
|
return 0, store.ErrNotFound
|
||||||
|
}
|
||||||
|
if s.clock().After(expireAt) {
|
||||||
|
return 0, store.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return userId, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SQLiteStore struct {
|
||||||
|
db *sql.DB
|
||||||
|
clock store.Clock
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ store.UserRepository = (*SQLiteStore)(nil)
|
||||||
|
_ store.SessionRepository = (*SQLiteStore)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(db *sql.DB, clock store.Clock) *SQLiteStore {
|
||||||
|
if clock == nil {
|
||||||
|
clock = time.Now
|
||||||
|
}
|
||||||
|
return &SQLiteStore{db: db, clock: clock}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package sqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"lunar-tear/server/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *SQLiteStore) CreateUser(uuid string) (int64, error) {
|
||||||
|
tx, err := s.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("begin tx: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
var existingId int64
|
||||||
|
err = tx.QueryRow(`SELECT user_id FROM users WHERE uuid = ?`, uuid).Scan(&existingId)
|
||||||
|
if err == nil {
|
||||||
|
return existingId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nowMillis := s.clock().UnixMilli()
|
||||||
|
|
||||||
|
res, err := tx.Exec(`INSERT INTO users (uuid, player_id, os_type, platform_type, user_restriction_type,
|
||||||
|
register_datetime, game_start_datetime, latest_version, birth_year, birth_month,
|
||||||
|
backup_token, charge_money_this_month) VALUES (?, 0, 2, 2, 0, ?, ?, 0, 2000, 1, 'mock-backup-token', 0)`,
|
||||||
|
uuid, nowMillis, nowMillis)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("insert user: %w", err)
|
||||||
|
}
|
||||||
|
userId, err := res.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("last insert id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// player_id = user_id
|
||||||
|
if _, err := tx.Exec(`UPDATE users SET player_id = ? WHERE user_id = ?`, userId, userId); err != nil {
|
||||||
|
return 0, fmt.Errorf("update player_id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := store.SeedUserState(userId, uuid, nowMillis)
|
||||||
|
if err := writeUserState(tx, userId, user); err != nil {
|
||||||
|
return 0, fmt.Errorf("write seed state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return 0, fmt.Errorf("commit: %w", err)
|
||||||
|
}
|
||||||
|
return userId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLiteStore) GetUserByUUID(uuid string) (int64, error) {
|
||||||
|
var userId int64
|
||||||
|
err := s.db.QueryRow(`SELECT user_id FROM users WHERE uuid = ?`, uuid).Scan(&userId)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return 0, store.ErrNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("query user: %w", err)
|
||||||
|
}
|
||||||
|
return userId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLiteStore) DefaultUserId() (int64, error) {
|
||||||
|
var userId int64
|
||||||
|
err := s.db.QueryRow(`SELECT min(user_id) FROM users`).Scan(&userId)
|
||||||
|
if err != nil || userId == 0 {
|
||||||
|
return 0, store.ErrNotFound
|
||||||
|
}
|
||||||
|
return userId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportUser replaces all data for u.UserId in the database with the
|
||||||
|
// contents of u. Any pre-existing rows for that user are deleted first.
|
||||||
|
func (s *SQLiteStore) ImportUser(u *store.UserState) error {
|
||||||
|
tx, err := s.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("begin tx: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
uid := u.UserId
|
||||||
|
|
||||||
|
// Child tables in reverse-dependency order (matches schema's goose Down).
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range childTables {
|
||||||
|
if _, err := tx.Exec(fmt.Sprintf(`DELETE FROM %s WHERE user_id = ?`, t), uid); err != nil {
|
||||||
|
return fmt.Errorf("delete from %s: %w", t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := tx.Exec(`DELETE FROM users WHERE user_id = ?`, uid); err != nil {
|
||||||
|
return fmt.Errorf("delete user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tx.Exec(`INSERT INTO users (user_id, uuid, player_id, os_type, platform_type,
|
||||||
|
user_restriction_type, register_datetime, game_start_datetime, latest_version,
|
||||||
|
birth_year, birth_month, backup_token, charge_money_this_month)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||||
|
uid, u.Uuid, u.PlayerId, u.OsType, u.PlatformType, u.UserRestrictionType,
|
||||||
|
u.RegisterDatetime, u.GameStartDatetime, u.LatestVersion,
|
||||||
|
u.BirthYear, u.BirthMonth, u.BackupToken, u.ChargeMoneyThisMonth); err != nil {
|
||||||
|
return fmt.Errorf("insert user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeUserState(tx, uid, u); err != nil {
|
||||||
|
return fmt.Errorf("write user state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return fmt.Errorf("commit: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLiteStore) UpdateUser(userId int64, mutate func(*store.UserState)) (store.UserState, error) {
|
||||||
|
before, err := s.LoadUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
return store.UserState{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
after := store.CloneUserState(before)
|
||||||
|
mutate(&after)
|
||||||
|
|
||||||
|
tx, err := s.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return store.UserState{}, fmt.Errorf("begin tx: %w", err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
if err := diffAndSave(tx, userId, &before, &after); err != nil {
|
||||||
|
return store.UserState{}, fmt.Errorf("diff and save: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return store.UserState{}, fmt.Errorf("commit: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return after, nil
|
||||||
|
}
|
||||||
@@ -10,18 +10,14 @@ var ErrNotFound = errors.New("store: not found")
|
|||||||
type Clock func() time.Time
|
type Clock func() time.Time
|
||||||
|
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
EnsureUser(uuid string) (UserState, error)
|
CreateUser(uuid string) (int64, error)
|
||||||
SnapshotUser(userId int64) (UserState, error)
|
GetUserByUUID(uuid string) (int64, error)
|
||||||
|
LoadUser(userId int64) (UserState, error)
|
||||||
UpdateUser(userId int64, mutate func(*UserState)) (UserState, error)
|
UpdateUser(userId int64, mutate func(*UserState)) (UserState, error)
|
||||||
DefaultUserId() (int64, error)
|
DefaultUserId() (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionRepository interface {
|
type SessionRepository interface {
|
||||||
CreateSession(uuid string, ttl time.Duration) (UserState, SessionState, error)
|
CreateSession(uuid string, ttl time.Duration) (SessionState, error)
|
||||||
ResolveUserId(sessionKey string) (int64, error)
|
ResolveUserId(sessionKey string) (int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GachaRepository interface {
|
|
||||||
SnapshotCatalog() ([]GachaCatalogEntry, error)
|
|
||||||
ReplaceCatalog(entries []GachaCatalogEntry) error
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ type UserState struct {
|
|||||||
CharacterBoardStatusUps map[CharacterBoardStatusUpKey]CharacterBoardStatusUpState
|
CharacterBoardStatusUps map[CharacterBoardStatusUpKey]CharacterBoardStatusUpState
|
||||||
|
|
||||||
CostumeAwakenStatusUps map[CostumeAwakenStatusKey]CostumeAwakenStatusUpState
|
CostumeAwakenStatusUps map[CostumeAwakenStatusKey]CostumeAwakenStatusUpState
|
||||||
|
CostumeLotteryEffects map[CostumeLotteryEffectKey]CostumeLotteryEffectState
|
||||||
|
CostumeLotteryEffectPending map[string]CostumeLotteryEffectPendingState // key: userCostumeUuid
|
||||||
AutoSaleSettings map[int32]AutoSaleSettingState
|
AutoSaleSettings map[int32]AutoSaleSettingState
|
||||||
CharacterRebirths map[int32]CharacterRebirthState
|
CharacterRebirths map[int32]CharacterRebirthState
|
||||||
}
|
}
|
||||||
@@ -254,6 +256,12 @@ func (u *UserState) EnsureMaps() {
|
|||||||
if u.CostumeAwakenStatusUps == nil {
|
if u.CostumeAwakenStatusUps == nil {
|
||||||
u.CostumeAwakenStatusUps = make(map[CostumeAwakenStatusKey]CostumeAwakenStatusUpState)
|
u.CostumeAwakenStatusUps = make(map[CostumeAwakenStatusKey]CostumeAwakenStatusUpState)
|
||||||
}
|
}
|
||||||
|
if u.CostumeLotteryEffects == nil {
|
||||||
|
u.CostumeLotteryEffects = make(map[CostumeLotteryEffectKey]CostumeLotteryEffectState)
|
||||||
|
}
|
||||||
|
if u.CostumeLotteryEffectPending == nil {
|
||||||
|
u.CostumeLotteryEffectPending = make(map[string]CostumeLotteryEffectPendingState)
|
||||||
|
}
|
||||||
if u.AutoSaleSettings == nil {
|
if u.AutoSaleSettings == nil {
|
||||||
u.AutoSaleSettings = make(map[int32]AutoSaleSettingState)
|
u.AutoSaleSettings = make(map[int32]AutoSaleSettingState)
|
||||||
}
|
}
|
||||||
@@ -366,6 +374,7 @@ type CostumeState struct {
|
|||||||
HeadupDisplayViewId int32
|
HeadupDisplayViewId int32
|
||||||
AcquisitionDatetime int64
|
AcquisitionDatetime int64
|
||||||
AwakenCount int32
|
AwakenCount int32
|
||||||
|
CostumeLotteryEffectUnlockedSlotCount int32
|
||||||
LatestVersion int64
|
LatestVersion int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,3 +1079,41 @@ type CharacterRebirthState struct {
|
|||||||
RebirthCount int32
|
RebirthCount int32
|
||||||
LatestVersion int64
|
LatestVersion int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CostumeLotteryEffectKey struct {
|
||||||
|
UserCostumeUuid string
|
||||||
|
SlotNumber int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k CostumeLotteryEffectKey) MarshalText() ([]byte, error) {
|
||||||
|
return fmt.Appendf(nil, "%s:%d", k.UserCostumeUuid, k.SlotNumber), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *CostumeLotteryEffectKey) UnmarshalText(text []byte) error {
|
||||||
|
s := string(text)
|
||||||
|
idx := strings.LastIndex(s, ":")
|
||||||
|
if idx < 0 {
|
||||||
|
return fmt.Errorf("invalid CostumeLotteryEffectKey: %s", text)
|
||||||
|
}
|
||||||
|
k.UserCostumeUuid = s[:idx]
|
||||||
|
v, err := strconv.ParseInt(s[idx+1:], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
k.SlotNumber = int32(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CostumeLotteryEffectState struct {
|
||||||
|
UserCostumeUuid string
|
||||||
|
SlotNumber int32
|
||||||
|
OddsNumber int32
|
||||||
|
LatestVersion int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type CostumeLotteryEffectPendingState struct {
|
||||||
|
UserCostumeUuid string
|
||||||
|
SlotNumber int32
|
||||||
|
OddsNumber int32
|
||||||
|
LatestVersion int64
|
||||||
|
}
|
||||||
|
|||||||
@@ -105,12 +105,18 @@ func init() {
|
|||||||
s, _ := encodeJSONMaps(SortedWeaponAwakenRecords(user)...)
|
s, _ := encodeJSONMaps(SortedWeaponAwakenRecords(user)...)
|
||||||
return s
|
return s
|
||||||
})
|
})
|
||||||
|
register("IUserCostumeLotteryEffect", func(user store.UserState) string {
|
||||||
|
s, _ := encodeJSONMaps(sortedCostumeLotteryEffectRecords(user)...)
|
||||||
|
return s
|
||||||
|
})
|
||||||
|
register("IUserCostumeLotteryEffectPending", func(user store.UserState) string {
|
||||||
|
s, _ := encodeJSONMaps(SortedCostumeLotteryEffectPendingRecords(user)...)
|
||||||
|
return s
|
||||||
|
})
|
||||||
registerStatic(
|
registerStatic(
|
||||||
"IUserCostumeLevelBonusReleaseStatus",
|
"IUserCostumeLevelBonusReleaseStatus",
|
||||||
"IUserCostumeLotteryEffect",
|
|
||||||
"IUserCostumeLotteryEffectAbility",
|
"IUserCostumeLotteryEffectAbility",
|
||||||
"IUserCostumeLotteryEffectStatusUp",
|
"IUserCostumeLotteryEffectStatusUp",
|
||||||
"IUserCostumeLotteryEffectPending",
|
|
||||||
"IUserPartsPresetTag",
|
"IUserPartsPresetTag",
|
||||||
"IUserPartsStatusSub",
|
"IUserPartsStatusSub",
|
||||||
)
|
)
|
||||||
@@ -152,6 +158,7 @@ func sortedCostumeRecords(user store.UserState) []map[string]any {
|
|||||||
"headupDisplayViewId": row.HeadupDisplayViewId,
|
"headupDisplayViewId": row.HeadupDisplayViewId,
|
||||||
"acquisitionDatetime": row.AcquisitionDatetime,
|
"acquisitionDatetime": row.AcquisitionDatetime,
|
||||||
"awakenCount": row.AwakenCount,
|
"awakenCount": row.AwakenCount,
|
||||||
|
"costumeLotteryEffectUnlockedSlotCount": row.CostumeLotteryEffectUnlockedSlotCount,
|
||||||
"latestVersion": row.LatestVersion,
|
"latestVersion": row.LatestVersion,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -619,3 +626,44 @@ func sortedCageOrnamentRewardRecords(user store.UserState) []map[string]any {
|
|||||||
}
|
}
|
||||||
return records
|
return records
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SortedCostumeLotteryEffectPendingRecords(user store.UserState) []map[string]any {
|
||||||
|
keys := sortedStringKeys(user.CostumeLotteryEffectPending)
|
||||||
|
records := make([]map[string]any, 0, len(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
row := user.CostumeLotteryEffectPending[key]
|
||||||
|
records = append(records, map[string]any{
|
||||||
|
"userId": user.UserId,
|
||||||
|
"userCostumeUuid": row.UserCostumeUuid,
|
||||||
|
"slotNumber": row.SlotNumber,
|
||||||
|
"oddsNumber": row.OddsNumber,
|
||||||
|
"latestVersion": row.LatestVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortedCostumeLotteryEffectRecords(user store.UserState) []map[string]any {
|
||||||
|
keys := make([]store.CostumeLotteryEffectKey, 0, len(user.CostumeLotteryEffects))
|
||||||
|
for k := range user.CostumeLotteryEffects {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Slice(keys, func(i, j int) bool {
|
||||||
|
if keys[i].UserCostumeUuid != keys[j].UserCostumeUuid {
|
||||||
|
return keys[i].UserCostumeUuid < keys[j].UserCostumeUuid
|
||||||
|
}
|
||||||
|
return keys[i].SlotNumber < keys[j].SlotNumber
|
||||||
|
})
|
||||||
|
records := make([]map[string]any, 0, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
row := user.CostumeLotteryEffects[k]
|
||||||
|
records = append(records, map[string]any{
|
||||||
|
"userId": user.UserId,
|
||||||
|
"userCostumeUuid": row.UserCostumeUuid,
|
||||||
|
"slotNumber": row.SlotNumber,
|
||||||
|
"oddsNumber": row.OddsNumber,
|
||||||
|
"latestVersion": row.LatestVersion,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|||||||
@@ -138,6 +138,14 @@ func SelectTables(all map[string]string, requested []string) map[string]string {
|
|||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProjectTables(user store.UserState, requested []string) map[string]string {
|
||||||
|
result := make(map[string]string, len(requested))
|
||||||
|
for _, table := range requested {
|
||||||
|
result[table] = projectTable(table, user)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func BuildDiffFromTables(tables map[string]string) map[string]*pb.DiffData {
|
func BuildDiffFromTables(tables map[string]string) map[string]*pb.DiffData {
|
||||||
diff := make(map[string]*pb.DiffData, len(tables))
|
diff := make(map[string]*pb.DiffData, len(tables))
|
||||||
for table, payload := range tables {
|
for table, payload := range tables {
|
||||||
|
|||||||
@@ -0,0 +1,874 @@
|
|||||||
|
-- +goose Up
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 1. Identity and Sessions
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
user_id INTEGER PRIMARY KEY,
|
||||||
|
uuid TEXT NOT NULL UNIQUE,
|
||||||
|
player_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
os_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
platform_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
user_restriction_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
register_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
game_start_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
birth_year INTEGER NOT NULL DEFAULT 0,
|
||||||
|
birth_month INTEGER NOT NULL DEFAULT 0,
|
||||||
|
backup_token TEXT NOT NULL DEFAULT '',
|
||||||
|
charge_money_this_month INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE sessions (
|
||||||
|
session_key TEXT PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
uuid TEXT NOT NULL,
|
||||||
|
expire_at TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 1b. Per-User 1:1 State Tables
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_setting (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
is_notify_purchase_alert INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_status (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
exp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
stamina_milli_value INTEGER NOT NULL DEFAULT 0,
|
||||||
|
stamina_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_gem (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
paid_gem INTEGER NOT NULL DEFAULT 0,
|
||||||
|
free_gem INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_profile (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
name_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
message TEXT NOT NULL DEFAULT '',
|
||||||
|
message_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
favorite_costume_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
favorite_costume_id_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_login (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
total_login_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
continual_login_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_continual_login_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_login_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_comeback_login_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_login_bonus (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
login_bonus_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_page_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_stamp_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_reward_receive_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_main_quest (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
current_quest_flow_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_main_quest_route_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
head_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_reached_last_quest_scene INTEGER NOT NULL DEFAULT 0,
|
||||||
|
progress_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
progress_head_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
progress_quest_flow_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
main_quest_season_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
saved_current_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
saved_head_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
replay_flow_current_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
replay_flow_head_quest_scene_id INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_event_quest (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
current_event_quest_chapter_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_quest_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
head_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_extra_quest (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
current_quest_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
head_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_side_story_active (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
current_side_story_quest_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_side_story_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_big_hunt_state (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
current_big_hunt_boss_quest_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_big_hunt_quest_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_dry_run INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
deck_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
user_triple_deck_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
boss_knock_down_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_combo_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
total_damage INTEGER NOT NULL DEFAULT 0,
|
||||||
|
deck_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
battle_binary BLOB
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_battle (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
is_active INTEGER NOT NULL DEFAULT 0,
|
||||||
|
start_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
finish_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_started_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_finished_at INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_user_party_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_npc_party_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_battle_binary_size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_elapsed_frame_count INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_notification (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
gift_not_receive_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
friend_request_receive_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_exist_unread_information INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_portal_cage (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
is_current_progress INTEGER NOT NULL DEFAULT 0,
|
||||||
|
drop_item_start_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
current_drop_item_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_guerrilla_free_open (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
start_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
open_minutes INTEGER NOT NULL DEFAULT 0,
|
||||||
|
daily_opened_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_explore (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
is_use_explore_ticket INTEGER NOT NULL DEFAULT 0,
|
||||||
|
playing_explore_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_play_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_shop_replaceable (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
lineup_update_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_lineup_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_gacha (
|
||||||
|
user_id INTEGER PRIMARY KEY REFERENCES users(user_id),
|
||||||
|
reward_available INTEGER NOT NULL DEFAULT 0,
|
||||||
|
todays_current_draw_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
daily_max_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_reward_draw_date INTEGER NOT NULL DEFAULT 0,
|
||||||
|
obtain_consumable_item_id INTEGER,
|
||||||
|
obtain_count INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 2. Characters and Progression
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_characters (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
character_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
exp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, character_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_character_boards (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
character_board_id INTEGER NOT NULL,
|
||||||
|
panel_release_bit1 INTEGER NOT NULL DEFAULT 0,
|
||||||
|
panel_release_bit2 INTEGER NOT NULL DEFAULT 0,
|
||||||
|
panel_release_bit3 INTEGER NOT NULL DEFAULT 0,
|
||||||
|
panel_release_bit4 INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, character_board_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_character_board_abilities (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
character_id INTEGER NOT NULL,
|
||||||
|
ability_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, character_id, ability_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_character_board_status_ups (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
character_id INTEGER NOT NULL,
|
||||||
|
status_calculation_type INTEGER NOT NULL,
|
||||||
|
hp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
attack INTEGER NOT NULL DEFAULT 0,
|
||||||
|
vitality INTEGER NOT NULL DEFAULT 0,
|
||||||
|
agility INTEGER NOT NULL DEFAULT 0,
|
||||||
|
critical_ratio INTEGER NOT NULL DEFAULT 0,
|
||||||
|
critical_attack INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, character_id, status_calculation_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_character_rebirths (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
character_id INTEGER NOT NULL,
|
||||||
|
rebirth_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, character_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 3. Equipment (UUID-keyed)
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_costumes (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_costume_uuid TEXT NOT NULL,
|
||||||
|
costume_id INTEGER NOT NULL,
|
||||||
|
limit_break_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
exp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
headup_display_view_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
awaken_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_costume_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_costume_active_skills (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_costume_uuid TEXT NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_costume_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_costume_awaken_status_ups (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_costume_uuid TEXT NOT NULL,
|
||||||
|
status_calculation_type INTEGER NOT NULL,
|
||||||
|
hp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
attack INTEGER NOT NULL DEFAULT 0,
|
||||||
|
vitality INTEGER NOT NULL DEFAULT 0,
|
||||||
|
agility INTEGER NOT NULL DEFAULT 0,
|
||||||
|
critical_ratio INTEGER NOT NULL DEFAULT 0,
|
||||||
|
critical_attack INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_costume_uuid, status_calculation_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_weapons (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_weapon_uuid TEXT NOT NULL,
|
||||||
|
weapon_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
exp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
limit_break_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_protected INTEGER NOT NULL DEFAULT 0,
|
||||||
|
acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_weapon_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_weapon_skills (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_weapon_uuid TEXT NOT NULL,
|
||||||
|
slot_number INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_weapon_uuid, slot_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_weapon_abilities (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_weapon_uuid TEXT NOT NULL,
|
||||||
|
slot_number INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_weapon_uuid, slot_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_weapon_awakens (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_weapon_uuid TEXT NOT NULL,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_weapon_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_weapon_stories (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
weapon_id INTEGER NOT NULL,
|
||||||
|
released_max_story_index INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, weapon_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_weapon_notes (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
weapon_id INTEGER NOT NULL,
|
||||||
|
max_level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_limit_break_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
first_acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, weapon_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_companions (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_companion_uuid TEXT NOT NULL,
|
||||||
|
companion_id INTEGER NOT NULL,
|
||||||
|
headup_display_view_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_companion_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_thoughts (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_thought_uuid TEXT NOT NULL,
|
||||||
|
thought_id INTEGER NOT NULL,
|
||||||
|
acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_thought_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_parts (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_parts_uuid TEXT NOT NULL,
|
||||||
|
parts_id INTEGER NOT NULL,
|
||||||
|
level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
parts_status_main_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_protected INTEGER NOT NULL DEFAULT 0,
|
||||||
|
acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_parts_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_parts_group_notes (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
parts_group_id INTEGER NOT NULL,
|
||||||
|
first_acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, parts_group_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_parts_presets (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_parts_preset_number INTEGER NOT NULL,
|
||||||
|
user_parts_uuid01 TEXT NOT NULL DEFAULT '',
|
||||||
|
user_parts_uuid02 TEXT NOT NULL DEFAULT '',
|
||||||
|
user_parts_uuid03 TEXT NOT NULL DEFAULT '',
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
user_parts_preset_tag_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_parts_preset_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 4. Deck System
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_deck_characters (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_deck_character_uuid TEXT NOT NULL,
|
||||||
|
user_costume_uuid TEXT NOT NULL DEFAULT '',
|
||||||
|
main_user_weapon_uuid TEXT NOT NULL DEFAULT '',
|
||||||
|
user_companion_uuid TEXT NOT NULL DEFAULT '',
|
||||||
|
power INTEGER NOT NULL DEFAULT 0,
|
||||||
|
user_thought_uuid TEXT NOT NULL DEFAULT '',
|
||||||
|
dressup_costume_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_deck_character_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_decks (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
deck_type INTEGER NOT NULL,
|
||||||
|
user_deck_number INTEGER NOT NULL,
|
||||||
|
user_deck_character_uuid01 TEXT NOT NULL DEFAULT '',
|
||||||
|
user_deck_character_uuid02 TEXT NOT NULL DEFAULT '',
|
||||||
|
user_deck_character_uuid03 TEXT NOT NULL DEFAULT '',
|
||||||
|
name TEXT NOT NULL DEFAULT '',
|
||||||
|
power INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, deck_type, user_deck_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_deck_sub_weapons (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_deck_character_uuid TEXT NOT NULL,
|
||||||
|
ordinal INTEGER NOT NULL,
|
||||||
|
user_weapon_uuid TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, user_deck_character_uuid, ordinal)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_deck_parts (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_deck_character_uuid TEXT NOT NULL,
|
||||||
|
ordinal INTEGER NOT NULL,
|
||||||
|
user_parts_uuid TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, user_deck_character_uuid, ordinal)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_deck_type_notes (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
deck_type INTEGER NOT NULL,
|
||||||
|
max_deck_power INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, deck_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 5. Quests
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_quests (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
quest_id INTEGER NOT NULL,
|
||||||
|
quest_state_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_battle_only INTEGER NOT NULL DEFAULT 0,
|
||||||
|
user_deck_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_start_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
clear_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
daily_clear_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
last_clear_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
shortest_clear_frames INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_reward_granted INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, quest_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_quest_missions (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
quest_id INTEGER NOT NULL,
|
||||||
|
quest_mission_id INTEGER NOT NULL,
|
||||||
|
progress_value INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_clear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_clear_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, quest_id, quest_mission_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_missions (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
mission_id INTEGER NOT NULL,
|
||||||
|
start_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
progress_value INTEGER NOT NULL DEFAULT 0,
|
||||||
|
mission_progress_status_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
clear_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, mission_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_side_story_quests (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
side_story_quest_id INTEGER NOT NULL,
|
||||||
|
head_side_story_quest_scene_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
side_story_quest_state_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, side_story_quest_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_quest_limit_content_status (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
limit_content_id INTEGER NOT NULL,
|
||||||
|
limit_content_quest_status_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
event_quest_chapter_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, limit_content_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 6. Big Hunt
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_big_hunt_max_scores (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
big_hunt_boss_id INTEGER NOT NULL,
|
||||||
|
max_score INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_score_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, big_hunt_boss_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_big_hunt_statuses (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
big_hunt_boss_id INTEGER NOT NULL,
|
||||||
|
daily_challenge_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_challenge_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, big_hunt_boss_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_big_hunt_schedule_max_scores (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
big_hunt_schedule_id INTEGER NOT NULL,
|
||||||
|
big_hunt_boss_id INTEGER NOT NULL,
|
||||||
|
max_score INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_score_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, big_hunt_schedule_id, big_hunt_boss_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_big_hunt_weekly_max_scores (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
big_hunt_weekly_version INTEGER NOT NULL,
|
||||||
|
attribute_type INTEGER NOT NULL,
|
||||||
|
max_score INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, big_hunt_weekly_version, attribute_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_big_hunt_weekly_statuses (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
big_hunt_weekly_version INTEGER NOT NULL,
|
||||||
|
is_received_weekly_reward INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, big_hunt_weekly_version)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 7. Gimmicks
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_gimmick_progress (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
gimmick_sequence_schedule_id INTEGER NOT NULL,
|
||||||
|
gimmick_sequence_id INTEGER NOT NULL,
|
||||||
|
gimmick_id INTEGER NOT NULL,
|
||||||
|
is_gimmick_cleared INTEGER NOT NULL DEFAULT 0,
|
||||||
|
start_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, gimmick_sequence_schedule_id, gimmick_sequence_id, gimmick_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_gimmick_ornament_progress (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
gimmick_sequence_schedule_id INTEGER NOT NULL,
|
||||||
|
gimmick_sequence_id INTEGER NOT NULL,
|
||||||
|
gimmick_id INTEGER NOT NULL,
|
||||||
|
gimmick_ornament_index INTEGER NOT NULL,
|
||||||
|
progress_value_bit INTEGER NOT NULL DEFAULT 0,
|
||||||
|
base_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, gimmick_sequence_schedule_id, gimmick_sequence_id, gimmick_id, gimmick_ornament_index)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_gimmick_sequences (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
gimmick_sequence_schedule_id INTEGER NOT NULL,
|
||||||
|
gimmick_sequence_id INTEGER NOT NULL,
|
||||||
|
is_gimmick_sequence_cleared INTEGER NOT NULL DEFAULT 0,
|
||||||
|
clear_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, gimmick_sequence_schedule_id, gimmick_sequence_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_gimmick_unlocks (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
gimmick_sequence_schedule_id INTEGER NOT NULL,
|
||||||
|
gimmick_sequence_id INTEGER NOT NULL,
|
||||||
|
gimmick_id INTEGER NOT NULL,
|
||||||
|
is_unlocked INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, gimmick_sequence_schedule_id, gimmick_sequence_id, gimmick_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 8. Inventory
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_consumable_items (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
consumable_item_id INTEGER NOT NULL,
|
||||||
|
count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, consumable_item_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_materials (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
material_id INTEGER NOT NULL,
|
||||||
|
count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, material_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_important_items (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
important_item_id INTEGER NOT NULL,
|
||||||
|
count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, important_item_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_premium_items (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
premium_item_id INTEGER NOT NULL,
|
||||||
|
count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, premium_item_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_tutorials (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
tutorial_type INTEGER NOT NULL,
|
||||||
|
progress_phase INTEGER NOT NULL DEFAULT 0,
|
||||||
|
choice_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, tutorial_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_explore_scores (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
explore_id INTEGER NOT NULL,
|
||||||
|
max_score INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_score_update_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, explore_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_auto_sale_settings (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
possession_auto_sale_item_type INTEGER NOT NULL,
|
||||||
|
possession_auto_sale_item_value TEXT NOT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY (user_id, possession_auto_sale_item_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 9. Simple Progress Maps
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_navi_cutin_played (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
navi_cutin_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, navi_cutin_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_viewed_movies (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
movie_id INTEGER NOT NULL,
|
||||||
|
timestamp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, movie_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_contents_stories (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
contents_story_id INTEGER NOT NULL,
|
||||||
|
timestamp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, contents_story_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_drawn_omikuji (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
omikuji_id INTEGER NOT NULL,
|
||||||
|
timestamp INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, omikuji_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_dokan_confirmed (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
dokan_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, dokan_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 10. Gifts
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_gifts (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_gift_uuid TEXT NOT NULL,
|
||||||
|
is_received INTEGER NOT NULL DEFAULT 0,
|
||||||
|
possession_type INTEGER NOT NULL DEFAULT 0,
|
||||||
|
possession_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
grant_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
description_gift_text_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
equipment_data BLOB,
|
||||||
|
expiration_datetime INTEGER,
|
||||||
|
received_datetime INTEGER,
|
||||||
|
PRIMARY KEY (user_id, user_gift_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 11. Gacha
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_gacha_converted_medals (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
ordinal INTEGER NOT NULL,
|
||||||
|
consumable_item_id INTEGER NOT NULL,
|
||||||
|
count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, ordinal)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_gacha_banners (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
gacha_id INTEGER NOT NULL,
|
||||||
|
medal_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
step_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
loop_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
draw_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
box_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, gacha_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_gacha_banner_box_drew_counts (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
gacha_id INTEGER NOT NULL,
|
||||||
|
box_item_id INTEGER NOT NULL,
|
||||||
|
count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, gacha_id, box_item_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 12. Shop
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_shop_items (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
shop_item_id INTEGER NOT NULL,
|
||||||
|
bought_count INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_bought_count_changed_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, shop_item_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_shop_replaceable_lineup (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
slot_number INTEGER NOT NULL,
|
||||||
|
shop_item_id INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, slot_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- 13. Cage Ornaments
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
CREATE TABLE user_cage_ornament_rewards (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
cage_ornament_id INTEGER NOT NULL,
|
||||||
|
acquisition_datetime INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, cage_ornament_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS user_cage_ornament_rewards ;
|
||||||
|
DROP TABLE IF EXISTS user_shop_replaceable_lineup ;
|
||||||
|
DROP TABLE IF EXISTS user_shop_items ;
|
||||||
|
DROP TABLE IF EXISTS user_gacha_banner_box_drew_counts;
|
||||||
|
DROP TABLE IF EXISTS user_gacha_banners ;
|
||||||
|
DROP TABLE IF EXISTS user_gacha_converted_medals ;
|
||||||
|
DROP TABLE IF EXISTS user_gifts ;
|
||||||
|
DROP TABLE IF EXISTS user_dokan_confirmed ;
|
||||||
|
DROP TABLE IF EXISTS user_drawn_omikuji ;
|
||||||
|
DROP TABLE IF EXISTS user_contents_stories ;
|
||||||
|
DROP TABLE IF EXISTS user_viewed_movies ;
|
||||||
|
DROP TABLE IF EXISTS user_navi_cutin_played ;
|
||||||
|
DROP TABLE IF EXISTS user_auto_sale_settings ;
|
||||||
|
DROP TABLE IF EXISTS user_explore_scores ;
|
||||||
|
DROP TABLE IF EXISTS user_tutorials ;
|
||||||
|
DROP TABLE IF EXISTS user_premium_items ;
|
||||||
|
DROP TABLE IF EXISTS user_important_items ;
|
||||||
|
DROP TABLE IF EXISTS user_materials ;
|
||||||
|
DROP TABLE IF EXISTS user_consumable_items ;
|
||||||
|
DROP TABLE IF EXISTS user_gimmick_unlocks ;
|
||||||
|
DROP TABLE IF EXISTS user_gimmick_sequences ;
|
||||||
|
DROP TABLE IF EXISTS user_gimmick_ornament_progress ;
|
||||||
|
DROP TABLE IF EXISTS user_gimmick_progress ;
|
||||||
|
DROP TABLE IF EXISTS user_big_hunt_weekly_statuses ;
|
||||||
|
DROP TABLE IF EXISTS user_big_hunt_weekly_max_scores ;
|
||||||
|
DROP TABLE IF EXISTS user_big_hunt_schedule_max_scores;
|
||||||
|
DROP TABLE IF EXISTS user_big_hunt_statuses ;
|
||||||
|
DROP TABLE IF EXISTS user_big_hunt_max_scores ;
|
||||||
|
DROP TABLE IF EXISTS user_quest_limit_content_status ;
|
||||||
|
DROP TABLE IF EXISTS user_side_story_quests ;
|
||||||
|
DROP TABLE IF EXISTS user_missions ;
|
||||||
|
DROP TABLE IF EXISTS user_quest_missions ;
|
||||||
|
DROP TABLE IF EXISTS user_quests ;
|
||||||
|
DROP TABLE IF EXISTS user_deck_type_notes ;
|
||||||
|
DROP TABLE IF EXISTS user_deck_parts ;
|
||||||
|
DROP TABLE IF EXISTS user_deck_sub_weapons ;
|
||||||
|
DROP TABLE IF EXISTS user_decks ;
|
||||||
|
DROP TABLE IF EXISTS user_deck_characters ;
|
||||||
|
DROP TABLE IF EXISTS user_parts_presets ;
|
||||||
|
DROP TABLE IF EXISTS user_parts_group_notes ;
|
||||||
|
DROP TABLE IF EXISTS user_parts ;
|
||||||
|
DROP TABLE IF EXISTS user_thoughts ;
|
||||||
|
DROP TABLE IF EXISTS user_companions ;
|
||||||
|
DROP TABLE IF EXISTS user_weapon_notes ;
|
||||||
|
DROP TABLE IF EXISTS user_weapon_stories ;
|
||||||
|
DROP TABLE IF EXISTS user_weapon_awakens ;
|
||||||
|
DROP TABLE IF EXISTS user_weapon_abilities ;
|
||||||
|
DROP TABLE IF EXISTS user_weapon_skills ;
|
||||||
|
DROP TABLE IF EXISTS user_weapons ;
|
||||||
|
DROP TABLE IF EXISTS user_costume_awaken_status_ups ;
|
||||||
|
DROP TABLE IF EXISTS user_costume_active_skills ;
|
||||||
|
DROP TABLE IF EXISTS user_costumes ;
|
||||||
|
DROP TABLE IF EXISTS user_character_rebirths ;
|
||||||
|
DROP TABLE IF EXISTS user_character_board_status_ups ;
|
||||||
|
DROP TABLE IF EXISTS user_character_board_abilities ;
|
||||||
|
DROP TABLE IF EXISTS user_character_boards ;
|
||||||
|
DROP TABLE IF EXISTS user_characters ;
|
||||||
|
DROP TABLE IF EXISTS user_gacha ;
|
||||||
|
DROP TABLE IF EXISTS user_shop_replaceable ;
|
||||||
|
DROP TABLE IF EXISTS user_explore ;
|
||||||
|
DROP TABLE IF EXISTS user_guerrilla_free_open ;
|
||||||
|
DROP TABLE IF EXISTS user_portal_cage ;
|
||||||
|
DROP TABLE IF EXISTS user_notification ;
|
||||||
|
DROP TABLE IF EXISTS user_battle ;
|
||||||
|
DROP TABLE IF EXISTS user_big_hunt_state ;
|
||||||
|
DROP TABLE IF EXISTS user_side_story_active ;
|
||||||
|
DROP TABLE IF EXISTS user_extra_quest ;
|
||||||
|
DROP TABLE IF EXISTS user_event_quest ;
|
||||||
|
DROP TABLE IF EXISTS user_main_quest ;
|
||||||
|
DROP TABLE IF EXISTS user_login_bonus ;
|
||||||
|
DROP TABLE IF EXISTS user_login ;
|
||||||
|
DROP TABLE IF EXISTS user_profile ;
|
||||||
|
DROP TABLE IF EXISTS user_gem ;
|
||||||
|
DROP TABLE IF EXISTS user_status ;
|
||||||
|
DROP TABLE IF EXISTS user_setting ;
|
||||||
|
DROP TABLE IF EXISTS sessions ;
|
||||||
|
DROP TABLE IF EXISTS users ;
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
-- +goose Up
|
||||||
|
|
||||||
|
-- Delete deck characters with empty weapons (always a bug).
|
||||||
|
DELETE FROM user_deck_characters
|
||||||
|
WHERE main_user_weapon_uuid = '';
|
||||||
|
|
||||||
|
-- Delete decks that reference deleted deck characters.
|
||||||
|
DELETE FROM user_decks
|
||||||
|
WHERE user_deck_character_uuid01 NOT IN (SELECT user_deck_character_uuid FROM user_deck_characters)
|
||||||
|
AND user_deck_character_uuid01 != '';
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- No rollback needed: EnsureDefaultDeck recreates decks on next SetTutorialProgress call.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE user_costume_lottery_effects (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_costume_uuid TEXT NOT NULL,
|
||||||
|
slot_number INTEGER NOT NULL,
|
||||||
|
odds_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_costume_uuid, slot_number)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE user_costumes ADD COLUMN costume_lottery_effect_unlocked_slot_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
ALTER TABLE user_costumes DROP COLUMN costume_lottery_effect_unlocked_slot_count;
|
||||||
|
DROP TABLE IF EXISTS user_costume_lottery_effects;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
-- +goose Up
|
||||||
|
CREATE TABLE user_costume_lottery_effect_pending (
|
||||||
|
user_id INTEGER NOT NULL REFERENCES users(user_id),
|
||||||
|
user_costume_uuid TEXT NOT NULL,
|
||||||
|
slot_number INTEGER NOT NULL,
|
||||||
|
odds_number INTEGER NOT NULL DEFAULT 0,
|
||||||
|
latest_version INTEGER NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (user_id, user_costume_uuid)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
DROP TABLE IF EXISTS user_costume_lottery_effect_pending;
|
||||||
Reference in New Issue
Block a user