Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
c0b872ede4 | |||
d52c04b88a | |||
66231d552f | |||
b316e5f0ac | |||
4e7e7bbba6 | |||
90179f1001 | |||
46e57fe2dc | |||
432c68ff74 | |||
be69d00e2c | |||
44e7ec1527 | |||
af97b37e51 | |||
3c9e3dc907 | |||
d909cf1aa3 | |||
c6b66eacb4 | |||
85fbc933ba | |||
395f249207 | |||
087991cb85 | |||
1d5321c805 | |||
d93c61cc92 | |||
73060a426d | |||
96d1994fe2 | |||
e5892ed9e5 |
218 changed files with 6874 additions and 199631 deletions
12
.dockerignore
Normal file
12
.dockerignore
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.git
|
||||||
|
assets
|
||||||
|
postgres
|
||||||
|
**/target
|
||||||
|
.gitignore
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile-builder
|
||||||
|
Dockerfile-service
|
||||||
|
LICENSE
|
||||||
|
*.md
|
||||||
|
*.zip
|
||||||
|
*.png
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,7 +1,9 @@
|
||||||
|
.idea
|
||||||
/target
|
/target
|
||||||
/hotpatch.toml
|
/hotpatch.toml
|
||||||
/configserver.toml
|
/configserver.toml
|
||||||
/loginserver.toml
|
/loginserver.toml
|
||||||
/gateway.toml
|
/gateway.toml
|
||||||
/gameserver.toml
|
/gameserver.toml
|
||||||
/shorekeeper-protocol/generated
|
/wicked-waifus-protocol-internal/generated
|
||||||
|
/wicked-waifus-protocol/generated
|
||||||
|
|
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "data/assets/config-server"]
|
||||||
|
path = data/assets/config-server
|
||||||
|
url = https://git.xeondev.com/wickedwaifus/wicked-waifus-config-server-files.git
|
||||||
|
[submodule "data/assets/game-data"]
|
||||||
|
path = data/assets/game-data
|
||||||
|
url = https://git.xeondev.com/wickedwaifus/wicked-waifus-data.git
|
1494
Cargo.lock
generated
1494
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
28
Cargo.toml
28
Cargo.toml
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["common", "config-server", "hotpatch-server", "login-server", "gateway-server", "gateway-server/kcp", "shorekeeper-database", "shorekeeper-http", "shorekeeper-protokey", "shorekeeper-protocol", "shorekeeper-protocol/shorekeeper-protocol-derive", "game-server", "shorekeeper-network", "shorekeeper-data"]
|
members = ["wicked-waifus-commons", "wicked-waifus-config-server", "wicked-waifus-hotpatch-server", "wicked-waifus-login-server", "wicked-waifus-gateway-server", "wicked-waifus-gateway-server/kcp", "wicked-waifus-database", "wicked-waifus-http", "wicked-waifus-protokey", "wicked-waifus-protocol-internal", "wicked-waifus-game-server", "wicked-waifus-network", "wicked-waifus-data"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
@ -8,6 +8,7 @@ version = "0.1.0"
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Framework
|
# Framework
|
||||||
tokio = { version = "1.39.3", features = ["full"] }
|
tokio = { version = "1.39.3", features = ["full"] }
|
||||||
|
tower-http = { version = "0.6.1", features = ["fs", "trace"] }
|
||||||
axum = "0.7.5"
|
axum = "0.7.5"
|
||||||
axum-server = "0.7.1"
|
axum-server = "0.7.1"
|
||||||
zeromq = { version = "0.4.0", default-features = false, features = ["tokio-runtime", "tcp-transport"] }
|
zeromq = { version = "0.4.0", default-features = false, features = ["tokio-runtime", "tcp-transport"] }
|
||||||
|
@ -19,12 +20,14 @@ sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio-rustls"] }
|
||||||
aes = "0.8.4"
|
aes = "0.8.4"
|
||||||
cbc = { version = "0.1.2", features = ["alloc"] }
|
cbc = { version = "0.1.2", features = ["alloc"] }
|
||||||
cipher = "0.4.4"
|
cipher = "0.4.4"
|
||||||
|
crc32fast = "1.4.2"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rsa = { version = "0.9.6", features = ["pem"] }
|
rsa = { version = "0.9.6", features = ["pem"] }
|
||||||
|
|
||||||
# Serialization
|
# Serialization
|
||||||
serde = { version = "1.0.209", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_json = "1.0.128"
|
serde_json = "1.0.128"
|
||||||
|
serde_repr = "0.1.19"
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
prost = "0.13.2"
|
prost = "0.13.2"
|
||||||
prost-build = "0.13.2"
|
prost-build = "0.13.2"
|
||||||
|
@ -37,22 +40,23 @@ rbase64 = "2.0.3"
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
crc32fast = "1.4.2"
|
|
||||||
|
|
||||||
# Tracing
|
# Tracing
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
kcp = { path = "gateway-server/kcp" }
|
kcp = { path = "wicked-waifus-gateway-server/kcp" }
|
||||||
common = { path = "common/" }
|
wicked-waifus-commons = { path = "wicked-waifus-commons" }
|
||||||
shorekeeper-http = { path = "shorekeeper-http/" }
|
wicked-waifus-http = { path = "wicked-waifus-http" }
|
||||||
shorekeeper-data = { path = "shorekeeper-data/" }
|
wicked-waifus-data = { path = "wicked-waifus-data" }
|
||||||
shorekeeper-database = { path = "shorekeeper-database/" }
|
wicked-waifus-database = { path = "wicked-waifus-database" }
|
||||||
shorekeeper-network = { path = "shorekeeper-network/" }
|
wicked-waifus-network = { path = "wicked-waifus-network" }
|
||||||
shorekeeper-protocol = { path = "shorekeeper-protocol/" }
|
wicked-waifus-protocol-internal = { path = "wicked-waifus-protocol-internal" }
|
||||||
shorekeeper-protocol-derive = { path = "shorekeeper-protocol/shorekeeper-protocol-derive" }
|
wicked-waifus-protokey = { path = "wicked-waifus-protokey" }
|
||||||
shorekeeper-protokey = { path = "shorekeeper-protokey/" }
|
|
||||||
|
wicked-waifus-protocol = { git = "https://git.xeondev.com/wickedwaifus/wicked-waifus-proto" }
|
||||||
|
wicked-waifus-protocol-derive = { git = "https://git.xeondev.com/wickedwaifus/wicked-waifus-proto" }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true # Automatically strip symbols from the binary.
|
strip = true # Automatically strip symbols from the binary.
|
||||||
|
|
6
Dockerfile-builder
Normal file
6
Dockerfile-builder
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
FROM rust:1.82-alpine3.20
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
# No need to manually strip symbols(strip target/release/$MICROSERVICE) since workspace its already prepared for that
|
||||||
|
RUN apk add musl-dev protoc && cargo build --release
|
6
Dockerfile-service
Normal file
6
Dockerfile-service
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
FROM alpine:3.20
|
||||||
|
ARG MICROSERVICE
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=wicked-waifus-builder:2.1.0-SNAPSHOT /app/target/release/$MICROSERVICE ./service
|
||||||
|
CMD ["./service"]
|
52
README.md
52
README.md
|
@ -1,9 +1,12 @@
|
||||||
# Shorekeeper
|
# Wicked Waifus
|
||||||
|
|
||||||
![Screenshot](https://git.xeondev.com/Shorekeeper/Shorekeeper/raw/branch/master/screenshot.png)
|
![Screenshot](screenshot.png)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
**Shorekeeper is an open-source Wuthering Waves server emulator written in Rust**. The goal of this project is to ensure a clean, easy-to-understand code environment. Shorekeeper uses **tokio** for asynchronous networking operations, **axum** as http framework and **ZeroMQ** for communication between servers. It also implements **performant and extensible ECS** for emulation of the game world.
|
**Wicked Waifus is an open-source Wuthering Waves server emulator written in Rust**.
|
||||||
|
The goal of this project is to ensure a clean, easy-to-understand code environment.
|
||||||
|
Wicked Waifus uses **tokio** for asynchronous networking operations, **axum** as http framework and **ZeroMQ** for communication between servers.
|
||||||
|
It also implements **performant and extensible ECS** for emulation of the game world.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
#### Requirements
|
#### Requirements
|
||||||
|
@ -15,19 +18,34 @@
|
||||||
##### a) building from sources
|
##### a) building from sources
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://git.xeondev.com/Shorekeeper/Shorekeeper.git
|
git clone --recursive https://git.xeondev.com/wickedwaifus/wicked-waifus-rs.git
|
||||||
cd Shorekeeper
|
cd wicked-waifus-rs
|
||||||
cargo run --bin config-server
|
cargo run --bin wicked-waifus-config-server
|
||||||
cargo run --bin hotpatch-server
|
cargo run --bin wicked-waifus-hotpatch-server
|
||||||
cargo run --bin login-server
|
cargo run --bin wicked-waifus-login-server
|
||||||
cargo run --bin gateway-server
|
cargo run --bin wicked-waifus-gateway-server
|
||||||
cargo run --bin game-server
|
cargo run --bin wicked-waifus-game-server
|
||||||
```
|
```
|
||||||
|
|
||||||
##### b) using pre-built binaries
|
##### b) building from sources(docker edition)
|
||||||
Navigate to the [Releases](https://git.xeondev.com/Shorekeeper/Shorekeeper/releases)
|
If you are to wheelchair'd for option A, you can fallback to option b.
|
||||||
|
In this case you will need [Docker Desktop](https://www.docker.com/products/docker-desktop/)
|
||||||
|
|
||||||
|
Once installed, to build the images, run:
|
||||||
|
```sh
|
||||||
|
# or builder.bat if you run it on windows
|
||||||
|
./builder.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
And to run the containers:
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
##### c) using pre-built binaries
|
||||||
|
Navigate to the [Releases](https://git.xeondev.com/wickedwaifus/wicked-waifus-rs/releases)
|
||||||
page and download the latest release for your platform.<br>
|
page and download the latest release for your platform.<br>
|
||||||
Launch all servers: `config-server`, `hotpatch-server`, `login-server`, `gateway-server`, `game-server`
|
Launch all servers: `wicked-waifus-config-server`, `wicked-waifus-hotpatch-server`, `wicked-waifus-login-server`, `wicked-waifus-gateway-server`, `wicked-waifus-game-server`
|
||||||
|
|
||||||
##### NOTE: you don't have to install Rust and Protoc if you're going to use pre-built binaries, although the preferred way is building from sources.<br>We don't provide any support for pre-built binaries.
|
##### NOTE: you don't have to install Rust and Protoc if you're going to use pre-built binaries, although the preferred way is building from sources.<br>We don't provide any support for pre-built binaries.
|
||||||
|
|
||||||
|
@ -42,15 +60,15 @@ You have to specify credentials for **PostgreSQL**<br>
|
||||||
host = "localhost:5432"
|
host = "localhost:5432"
|
||||||
user_name = "postgres"
|
user_name = "postgres"
|
||||||
password = ""
|
password = ""
|
||||||
db_name = "shorekeeper"
|
db_name = "wicked_waifus_db"
|
||||||
```
|
```
|
||||||
##### NOTE: don't forget to create database with specified `db_name` (default: `shorekeeper`). For example, you can do so with PgAdmin.
|
##### NOTE: don't forget to create database with specified `db_name` (default: `wicked_waifus_db`). For example, you can do so with PgAdmin.
|
||||||
|
|
||||||
#### Data
|
#### Data
|
||||||
The data files: Logic JSON collections (`assets/logic/json`) and config/hotpatch indexes (`assets/config`, `assets/hotpatch`) are included in this repository. Keep in mind that you need to have the `assets` subdirectory in current working directory.
|
The data files: Logic JSON collections (`data/assets/game-data/BinData`) and config/hotpatch indexes (`data/assets/config-server`, `data/assets/hotpatch-server`) are included in this repository. Keep in mind that you need to have the `data` subdirectory in current working directory.
|
||||||
|
|
||||||
#### Connecting
|
#### Connecting
|
||||||
You have to download client of Wuthering Waves Beta 1.3, apply the [shorekeeper-patch](https://git.xeondev.com/xeon/shorekeeper-patch/releases) and add necessary `.pak` files, which you can get here: [shorekeeper-pak](https://git.xeondev.com/Shorekeeper/shorekeeper-pak)
|
You have to download client of Wuthering Waves Beta 2.1, apply the [wicked-waifus-win-patch](https://git.xeondev.com/wickedwaifus/wicked-waifus-win-patch/releases) and add necessary `.pak` files, which you can get here: [wicked-waifus-pak](https://git.xeondev.com/wickedwaifus/wicked-waifus-pak)
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
[Visit our discord](https://discord.gg/reversedrooms) if you have any questions/issues
|
[Visit our discord](https://discord.gg/reversedrooms) if you have any questions/issues
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
{
|
|
||||||
"default": {
|
|
||||||
"CdnUrl": [
|
|
||||||
{
|
|
||||||
"url": "http://127.0.0.1:10002/prod/client/",
|
|
||||||
"weight": "2323"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"SecondaryUrl": [],
|
|
||||||
"PriceRatio": 1,
|
|
||||||
"SpeedRatio": 1,
|
|
||||||
"GachaUrl": {
|
|
||||||
"GachaRecord": "http://127.0.0.1:10001/gacha/record",
|
|
||||||
"GachaPoolDetail": "http://127.0.0.1:10001/gacha/detail"
|
|
||||||
},
|
|
||||||
"LogReport": {
|
|
||||||
"name": "pioneer-upload-log-1319073642",
|
|
||||||
"region": "dev-reversedrooms"
|
|
||||||
},
|
|
||||||
"PackageUpdateDescUrl": {
|
|
||||||
"MainUrl": "http://127.0.0.1:10001/force_update/UpdateDesc.html",
|
|
||||||
"SubUrl": "http://127.0.0.1:10001/force_update/UpdateDesc.html"
|
|
||||||
},
|
|
||||||
"PackageUpdateUrl": {
|
|
||||||
"MainUrl": "http://127.0.0.1:10001/force_update/UpdateJs.html",
|
|
||||||
"SubUrl": "http://127.0.0.1:10001/force_update/UpdateJs.html"
|
|
||||||
},
|
|
||||||
"TDCfg": {
|
|
||||||
"AppID": "3e2e647670b7498fa645eb9574f78c2c",
|
|
||||||
"URL": "http://127.0.0.1:10001/TDCfg"
|
|
||||||
},
|
|
||||||
"GmOpen": false,
|
|
||||||
"IosAuditFirstDownloadTip": false,
|
|
||||||
"NoticeUrl": "http://127.0.0.1:10001/notice",
|
|
||||||
"MixUri": "rODM5DcqOhYsIOtsEuZWNGFa2guZgl57",
|
|
||||||
"ResUri": "rODM5DcqOhYsIOtsEuZWNGFa2guZgl57",
|
|
||||||
"LoginServers": [
|
|
||||||
{
|
|
||||||
"id": "f9e0fc655c1931bc03ad976e9fc14473",
|
|
||||||
"ip": "http://127.0.0.1:5500",
|
|
||||||
"name": "Shorekeeper"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"PrivateServers": {
|
|
||||||
"enable": false,
|
|
||||||
"serverUrl": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p1": {
|
|
||||||
"IosAuditFirstDownloadTip": false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
"PackageVersion": "1.3.0",
|
|
||||||
"LauncherVersion": "1.3.9",
|
|
||||||
"ResourceVersion": "1.3.9",
|
|
||||||
"LauncherIndexSha1": {
|
|
||||||
"1.3.1": "90FDF17EA0B4015D43C344CB7229E76AB32549DD",
|
|
||||||
"1.3.2": "C9A587AB1FA6CA57CD23E0FB3F0103BFDCAA8E37",
|
|
||||||
"1.3.3": "1C7AF02F13DBE69637DB43039E2FFB8C9AD9A04B",
|
|
||||||
"1.3.4": "DA50F315041E216568A7713074C6475F6AB4530E",
|
|
||||||
"1.3.5": "EA9C6F6D5E920F47F96D8F8BC366A4CED62A0346",
|
|
||||||
"1.3.6": "8CA7E6573A52B16CFAA29E996D389918B6829E7A",
|
|
||||||
"1.3.7": "FCAAED58E5983027A82F52C350418CCE7BD531D2",
|
|
||||||
"1.3.8": "91D6231B3F4C9A6605B79E23D0C02F9790DD6BCF",
|
|
||||||
"1.3.9": "B36BD648AB2A637A4E087B7B115A6CCBDAEBDF9A"
|
|
||||||
},
|
|
||||||
"ResourceIndexSha1": {
|
|
||||||
"1.3.1": "2D635E549EB6F99659571D72741B62249473A77A",
|
|
||||||
"1.3.2": "C5814A80EA3E7D80D4CFBCD884D1FD158BF0AD9D",
|
|
||||||
"1.3.3": "1E0F05333B09A9215B4AA5C437BFC7DC4014E348",
|
|
||||||
"1.3.4": "6155D492540A99ECF0DA06D2B7EEBFE36231FBC2",
|
|
||||||
"1.3.5": "1E60C8F60CA1AAA9955441B4F4265C8288B95F33",
|
|
||||||
"1.3.6": "AA10A8DD1025D5033E291060C686B816513ADCAD",
|
|
||||||
"1.3.7": "A9881305EBD3DC5A6892D49BDAF540F56EE56232",
|
|
||||||
"1.3.8": "261CA25DAD6877DF3C57DA39947130867FCC09CE",
|
|
||||||
"1.3.9": "88A9E40631FC1C11A91A61CB3F4BE8C13C5E2BD3"
|
|
||||||
},
|
|
||||||
"ChangeList": "2333675",
|
|
||||||
"CompatibleChangeLists": [],
|
|
||||||
"Versions": [
|
|
||||||
{
|
|
||||||
"Name": "en",
|
|
||||||
"Version": "1.3.0",
|
|
||||||
"IndexSha1": {
|
|
||||||
"1.3.0": "6FB5B66EF8B3EECBBBEBE74A82BC23E3FC35450B"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "ja",
|
|
||||||
"Version": "1.3.0",
|
|
||||||
"IndexSha1": {
|
|
||||||
"1.3.0": "E4DA1960DB36CE8166C042AD8B9AF98C1A9119F3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "ko",
|
|
||||||
"Version": "1.3.0",
|
|
||||||
"IndexSha1": {
|
|
||||||
"1.3.0": "498B379E95FC617385CCD832B8C359FA5AC220CE"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "zh",
|
|
||||||
"Version": "1.3.0",
|
|
||||||
"IndexSha1": {
|
|
||||||
"1.3.0": "CC58C357A80E7B3846264918197FC3ECAA1FE190"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"UpdateTime": 1725869509
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
12
builder.bat
Normal file
12
builder.bat
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
docker build -t wicked-waifus-builder:2.1.0-SNAPSHOT -f Dockerfile-builder .
|
||||||
|
|
||||||
|
docker build -t wicked-waifus-config-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-config-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-hotpatch-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-login-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-login-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-gateway-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-gateway-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-game-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-game-server -f Dockerfile-service .
|
||||||
|
|
||||||
|
docker rmi wicked-waifus-builder:2.1.0-SNAPSHOT
|
||||||
|
|
||||||
|
: Persistence for the application
|
||||||
|
: docker volume create wicked-waifus-postgres-vol
|
12
builder.sh
Normal file
12
builder.sh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
docker build -t wicked-waifus-builder:2.1.0-SNAPSHOT -f Dockerfile-builder .
|
||||||
|
|
||||||
|
docker build -t wicked-waifus-config-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-config-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-hotpatch-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-hotpatch-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-login-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-login-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-gateway-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-gateway-server -f Dockerfile-service .
|
||||||
|
docker build -t wicked-waifus-game-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-game-server -f Dockerfile-service .
|
||||||
|
|
||||||
|
docker rmi wicked-waifus-builder:2.1.0-SNAPSHOT
|
||||||
|
|
||||||
|
# Persistence for the application
|
||||||
|
# docker volume create wicked-waifus-postgres-vol
|
|
@ -1,8 +0,0 @@
|
||||||
use tracing::Level;
|
|
||||||
|
|
||||||
pub fn init(max_level: Level) {
|
|
||||||
tracing_subscriber::fmt()
|
|
||||||
.with_max_level(max_level)
|
|
||||||
.with_target(false)
|
|
||||||
.init();
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
pub fn print_splash() {
|
|
||||||
println!(" _____ __ __ \n / ___// /_ ____ ________ / /_____ ___ ____ ___ _____\n \\__ \\/ __ \\/ __ \\/ ___/ _ \\/ //_/ _ \\/ _ \\/ __ \\/ _ \\/ ___/\n ___/ / / / / /_/ / / / __/ ,< / __/ __/ /_/ / __/ / \n/____/_/ /_/\\____/_/ \\___/_/|_|\\___/\\___/ .___/\\___/_/ \n /_/ ");
|
|
||||||
}
|
|
1
data/assets/config-server
Submodule
1
data/assets/config-server
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 146c610632c8d9c3948c6a64ffc6cfe0258bcced
|
1
data/assets/game-data
Submodule
1
data/assets/game-data
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit c00cdaefddc9c81ae6d79589c31ee169d59f2382
|
44
data/assets/hotpatch-server/prod/Windows/config.json
Normal file
44
data/assets/hotpatch-server/prod/Windows/config.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"PackageVersion": "1.4.0",
|
||||||
|
"LauncherVersion": "1.4.0",
|
||||||
|
"ResourceVersion": "1.4.3",
|
||||||
|
"LauncherIndexSha1": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"ResourceIndexSha1": {
|
||||||
|
"1.4.1": "0C747E444EC6DBA11D0C57B34E2638DE5182029F",
|
||||||
|
"1.4.2": "41751A94DBD406653DB52262E85BB716E45D9864",
|
||||||
|
"1.4.3": "424EDFD97BA8699CBC2CB9E27D3FCBB6F7C70806"
|
||||||
|
},
|
||||||
|
"ChangeList": "2585778",
|
||||||
|
"CompatibleChangeLists": [],
|
||||||
|
"Versions": [
|
||||||
|
{
|
||||||
|
"Name": "en",
|
||||||
|
"Version": "1.4.0",
|
||||||
|
"IndexSha1": {
|
||||||
|
"1.4.0": "34F9AC326B3E81E972B93094E43751967A6A3396"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ja",
|
||||||
|
"Version": "1.4.0",
|
||||||
|
"IndexSha1": {
|
||||||
|
"1.4.0": "3233223428D586FBC0A2EC817F4E94DDD10C646F"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ko",
|
||||||
|
"Version": "1.4.0",
|
||||||
|
"IndexSha1": {
|
||||||
|
"1.4.0": "3CC0312BDFFF5FA4D3F5256E2618D0EF6AA84912"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "zh",
|
||||||
|
"Version": "1.4.0",
|
||||||
|
"IndexSha1": {
|
||||||
|
"1.4.0": "2F3AD1412C6991FE0E146C5E2F6F86D8D9F44A5D"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UpdateTime": 1729243476
|
||||||
|
}
|
10
data/docker/configserver.toml
Normal file
10
data/docker/configserver.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[network]
|
||||||
|
http_addr = "0.0.0.0:10001"
|
||||||
|
|
||||||
|
[encryption]
|
||||||
|
key = "t+AEu5SGdpz06tomonajLMau9AJgmyTvVhz9VtGf1+0="
|
||||||
|
iv = "fprc5lBWADQB7tim0R2JxQ=="
|
||||||
|
|
||||||
|
[serve]
|
||||||
|
serve_web_path = "/"
|
||||||
|
serve_dir_path = "assets/config-server"
|
13
data/docker/gameserver.toml
Normal file
13
data/docker/gameserver.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
service_id = 2
|
||||||
|
|
||||||
|
[database]
|
||||||
|
host = "wicked-waifus-postgres:5432"
|
||||||
|
user_name = "wicked_waifus_user"
|
||||||
|
password = "wicked_waifus_pass"
|
||||||
|
db_name = "wicked_waifus_db"
|
||||||
|
|
||||||
|
[service_end_point]
|
||||||
|
addr = "tcp://0.0.0.0:10004"
|
||||||
|
|
||||||
|
[gateway_end_point]
|
||||||
|
addr = "tcp://wicked-waifus-gateway-server:10003"
|
20
data/docker/gateway.toml
Normal file
20
data/docker/gateway.toml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
service_id = 1
|
||||||
|
|
||||||
|
[network]
|
||||||
|
kcp_port = 7777
|
||||||
|
|
||||||
|
[protokey]
|
||||||
|
builtin_encryption_msg_id = [111, 112]
|
||||||
|
use_client_key = true
|
||||||
|
|
||||||
|
[service_end_point]
|
||||||
|
addr = "tcp://0.0.0.0:10003"
|
||||||
|
|
||||||
|
[game_server_end_point]
|
||||||
|
addr = "tcp://wicked-waifus-game-server:10004"
|
||||||
|
|
||||||
|
[database]
|
||||||
|
host = "wicked-waifus-postgres:5432"
|
||||||
|
user_name = "wicked_waifus_user"
|
||||||
|
password = "wicked_waifus_pass"
|
||||||
|
db_name = "wicked_waifus_db"
|
12
data/docker/loginserver.toml
Normal file
12
data/docker/loginserver.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[network]
|
||||||
|
http_addr = "0.0.0.0:5500"
|
||||||
|
|
||||||
|
[gateway]
|
||||||
|
host = "host.docker.internal"
|
||||||
|
port = 7777
|
||||||
|
|
||||||
|
[database]
|
||||||
|
host = "wicked-waifus-postgres:5432"
|
||||||
|
user_name = "wicked_waifus_user"
|
||||||
|
password = "wicked_waifus_pass"
|
||||||
|
db_name = "wicked_waifus_db"
|
3
data/docker/postgres/scripts/0_INIT.sql
Normal file
3
data/docker/postgres/scripts/0_INIT.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
CREATE DATABASE wicked_waifus_db;
|
||||||
|
CREATE USER wicked_waifus_user WITH encrypted password 'wicked_waifus_pass';
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE wicked_waifus_db to wicked_waifus_user;
|
2
data/docker/postgres/scripts/1_PERMS.sql
Normal file
2
data/docker/postgres/scripts/1_PERMS.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
\c wicked_waifus_db;
|
||||||
|
GRANT ALL ON SCHEMA public TO wicked_waifus_user;
|
13
docker-compose-hotpatch.yml
Normal file
13
docker-compose-hotpatch.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name: wicked-waifus-ps
|
||||||
|
|
||||||
|
services:
|
||||||
|
wicked-waifus-hotpatch-server:
|
||||||
|
image: wicked-waifus-hotpatch-server:2.1.0-SNAPSHOT
|
||||||
|
depends_on:
|
||||||
|
wicked-waifus-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- '10002:10002'
|
||||||
|
volumes:
|
||||||
|
- "./data/docker/hotpatch.toml:/app/hotpatch.toml"
|
||||||
|
- "./data/assets/hotpatch-server:/app/assets/hotpatch"
|
63
docker-compose.yml
Normal file
63
docker-compose.yml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
name: wicked-waifus-ps
|
||||||
|
|
||||||
|
services:
|
||||||
|
wicked-waifus-config-server:
|
||||||
|
image: wicked-waifus-config-server:2.1.0-SNAPSHOT
|
||||||
|
depends_on:
|
||||||
|
wicked-waifus-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- '10001:10001'
|
||||||
|
volumes:
|
||||||
|
- "./data/docker/configserver.toml:/app/configserver.toml"
|
||||||
|
- "./data/assets/config:/app/assets/config"
|
||||||
|
wicked-waifus-login-server:
|
||||||
|
image: wicked-waifus-login-server:2.1.0-SNAPSHOT
|
||||||
|
depends_on:
|
||||||
|
wicked-waifus-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
- '5500:5500'
|
||||||
|
volumes:
|
||||||
|
- "./data/docker/loginserver.toml:/app/loginserver.toml"
|
||||||
|
wicked-waifus-gateway-server:
|
||||||
|
image: wicked-waifus-gateway-server:2.1.0-SNAPSHOT
|
||||||
|
depends_on:
|
||||||
|
wicked-waifus-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
ports:
|
||||||
|
# Uncomment this if you want to have manual access
|
||||||
|
# - '10003:10003'
|
||||||
|
- '7777:7777/udp'
|
||||||
|
volumes:
|
||||||
|
- "./data/docker/gateway.toml:/app/gateway.toml"
|
||||||
|
wicked-waifus-game-server:
|
||||||
|
image: wicked-waifus-game-server:2.1.0-SNAPSHOT
|
||||||
|
depends_on:
|
||||||
|
wicked-waifus-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
# Uncomment this if you want to have manual access
|
||||||
|
# ports:
|
||||||
|
# - '10004:10004'
|
||||||
|
volumes:
|
||||||
|
- "./data/docker/gameserver.toml:/app/gameserver.toml"
|
||||||
|
- "./data/assets/game-data:/app/data/assets/game-data"
|
||||||
|
wicked-waifus-postgres:
|
||||||
|
image: postgres:16.4-alpine3.20
|
||||||
|
user: postgres
|
||||||
|
# Uncomment this if you want to have manual access
|
||||||
|
ports:
|
||||||
|
- '5432:5432'
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
environment:
|
||||||
|
- "POSTGRES_PASSWORD=toor"
|
||||||
|
volumes:
|
||||||
|
- "./data/docker/postgres/scripts:/docker-entrypoint-initdb.d"
|
||||||
|
- wicked-waifus-postgres-vol:/var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
wicked-waifus-postgres-vol:
|
||||||
|
external: true
|
|
@ -1,28 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "game-server"
|
|
||||||
edition = "2021"
|
|
||||||
version.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# Framework
|
|
||||||
tokio.workspace = true
|
|
||||||
|
|
||||||
# Serialization
|
|
||||||
serde.workspace = true
|
|
||||||
|
|
||||||
# Util
|
|
||||||
anyhow.workspace = true
|
|
||||||
thiserror.workspace = true
|
|
||||||
paste.workspace = true
|
|
||||||
dashmap.workspace = true
|
|
||||||
hex.workspace = true
|
|
||||||
|
|
||||||
# Tracing
|
|
||||||
tracing.workspace = true
|
|
||||||
|
|
||||||
# Internal
|
|
||||||
common.workspace = true
|
|
||||||
shorekeeper-data.workspace = true
|
|
||||||
shorekeeper-database.workspace = true
|
|
||||||
shorekeeper-network.workspace = true
|
|
||||||
shorekeeper-protocol.workspace = true
|
|
|
@ -1,201 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use shorekeeper_data::BasePropertyData;
|
|
||||||
use shorekeeper_protocol::{
|
|
||||||
entity_component_pb::ComponentPb, AttrData, AttributeComponentPb, EAttributeType,
|
|
||||||
EntityComponentPb, LivingStatus,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::logic::ecs::component::Component;
|
|
||||||
|
|
||||||
pub struct Attribute {
|
|
||||||
pub attr_map: HashMap<EAttributeType, (i32, i32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_from_data {
|
|
||||||
($($name:ident),*) => {
|
|
||||||
pub fn from_data(base_property: &BasePropertyData) -> Self {
|
|
||||||
Self {
|
|
||||||
attr_map: HashMap::from([$(
|
|
||||||
::paste::paste!((EAttributeType::[<$name:camel>], (base_property.$name, 0))),
|
|
||||||
)*]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Attribute {
|
|
||||||
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
|
|
||||||
pb.living_status = self
|
|
||||||
.is_alive()
|
|
||||||
.then_some(LivingStatus::Alive)
|
|
||||||
.unwrap_or(LivingStatus::Dead)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
pb.component_pbs.push(EntityComponentPb {
|
|
||||||
component_pb: Some(ComponentPb::AttributeComponent(AttributeComponentPb {
|
|
||||||
attr_data: self
|
|
||||||
.attr_map
|
|
||||||
.iter()
|
|
||||||
.map(|(ty, (base, incr))| AttrData {
|
|
||||||
attribute_type: (*ty).into(),
|
|
||||||
base_value: *base,
|
|
||||||
increment: *incr,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
hardness_mode_id: 0,
|
|
||||||
rage_mode_id: 0,
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Attribute {
|
|
||||||
pub fn is_alive(&self) -> bool {
|
|
||||||
self.attr_map
|
|
||||||
.get(&EAttributeType::Life)
|
|
||||||
.copied()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.0
|
|
||||||
> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_from_data!(
|
|
||||||
lv,
|
|
||||||
life_max,
|
|
||||||
life,
|
|
||||||
sheild,
|
|
||||||
sheild_damage_change,
|
|
||||||
sheild_damage_reduce,
|
|
||||||
atk,
|
|
||||||
crit,
|
|
||||||
crit_damage,
|
|
||||||
def,
|
|
||||||
energy_efficiency,
|
|
||||||
cd_reduse,
|
|
||||||
reaction_efficiency,
|
|
||||||
damage_change_normal_skill,
|
|
||||||
damage_change,
|
|
||||||
damage_reduce,
|
|
||||||
damage_change_auto,
|
|
||||||
damage_change_cast,
|
|
||||||
damage_change_ultra,
|
|
||||||
damage_change_qte,
|
|
||||||
damage_change_phys,
|
|
||||||
damage_change_element1,
|
|
||||||
damage_change_element2,
|
|
||||||
damage_change_element3,
|
|
||||||
damage_change_element4,
|
|
||||||
damage_change_element5,
|
|
||||||
damage_change_element6,
|
|
||||||
damage_resistance_phys,
|
|
||||||
damage_resistance_element1,
|
|
||||||
damage_resistance_element2,
|
|
||||||
damage_resistance_element3,
|
|
||||||
damage_resistance_element4,
|
|
||||||
damage_resistance_element5,
|
|
||||||
damage_resistance_element6,
|
|
||||||
heal_change,
|
|
||||||
healed_change,
|
|
||||||
damage_reduce_phys,
|
|
||||||
damage_reduce_element1,
|
|
||||||
damage_reduce_element2,
|
|
||||||
damage_reduce_element3,
|
|
||||||
damage_reduce_element4,
|
|
||||||
damage_reduce_element5,
|
|
||||||
damage_reduce_element6,
|
|
||||||
reaction_change1,
|
|
||||||
reaction_change2,
|
|
||||||
reaction_change3,
|
|
||||||
reaction_change4,
|
|
||||||
reaction_change5,
|
|
||||||
reaction_change6,
|
|
||||||
reaction_change7,
|
|
||||||
reaction_change8,
|
|
||||||
reaction_change9,
|
|
||||||
reaction_change10,
|
|
||||||
reaction_change11,
|
|
||||||
reaction_change12,
|
|
||||||
reaction_change13,
|
|
||||||
reaction_change14,
|
|
||||||
reaction_change15,
|
|
||||||
energy_max,
|
|
||||||
energy,
|
|
||||||
special_energy_1_max,
|
|
||||||
special_energy_1,
|
|
||||||
special_energy_2_max,
|
|
||||||
special_energy_2,
|
|
||||||
special_energy_3_max,
|
|
||||||
special_energy_3,
|
|
||||||
special_energy_4_max,
|
|
||||||
special_energy_4,
|
|
||||||
strength_max,
|
|
||||||
strength,
|
|
||||||
strength_recover,
|
|
||||||
strength_punish_time,
|
|
||||||
strength_run,
|
|
||||||
strength_swim,
|
|
||||||
strength_fast_swim,
|
|
||||||
hardness_max,
|
|
||||||
hardness,
|
|
||||||
hardness_recover,
|
|
||||||
hardness_punish_time,
|
|
||||||
hardness_change,
|
|
||||||
hardness_reduce,
|
|
||||||
rage_max,
|
|
||||||
rage,
|
|
||||||
rage_recover,
|
|
||||||
rage_punish_time,
|
|
||||||
rage_change,
|
|
||||||
rage_reduce,
|
|
||||||
tough_max,
|
|
||||||
tough,
|
|
||||||
tough_recover,
|
|
||||||
tough_change,
|
|
||||||
tough_reduce,
|
|
||||||
tough_recover_delay_time,
|
|
||||||
element_power1,
|
|
||||||
element_power2,
|
|
||||||
element_power3,
|
|
||||||
element_power4,
|
|
||||||
element_power5,
|
|
||||||
element_power6,
|
|
||||||
special_damage_change,
|
|
||||||
strength_fast_climb_cost,
|
|
||||||
element_property_type,
|
|
||||||
weak_time,
|
|
||||||
ignore_def_rate,
|
|
||||||
ignore_damage_resistance_phys,
|
|
||||||
ignore_damage_resistance_element1,
|
|
||||||
ignore_damage_resistance_element2,
|
|
||||||
ignore_damage_resistance_element3,
|
|
||||||
ignore_damage_resistance_element4,
|
|
||||||
ignore_damage_resistance_element5,
|
|
||||||
ignore_damage_resistance_element6,
|
|
||||||
skill_tough_ratio,
|
|
||||||
strength_climb_jump,
|
|
||||||
strength_gliding,
|
|
||||||
mass,
|
|
||||||
braking_friction_factor,
|
|
||||||
gravity_scale,
|
|
||||||
speed_ratio,
|
|
||||||
damage_change_phantom,
|
|
||||||
auto_attack_speed,
|
|
||||||
cast_attack_speed,
|
|
||||||
status_build_up_1_max,
|
|
||||||
status_build_up_1,
|
|
||||||
status_build_up_2_max,
|
|
||||||
status_build_up_2,
|
|
||||||
status_build_up_3_max,
|
|
||||||
status_build_up_3,
|
|
||||||
status_build_up_4_max,
|
|
||||||
status_build_up_4,
|
|
||||||
status_build_up_5_max,
|
|
||||||
status_build_up_5,
|
|
||||||
paralysis_time_max,
|
|
||||||
paralysis_time,
|
|
||||||
paralysis_time_recover,
|
|
||||||
element_energy_max,
|
|
||||||
element_energy
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
use shorekeeper_protocol::EntityConfigType;
|
|
||||||
|
|
||||||
use crate::logic::ecs::component::Component;
|
|
||||||
|
|
||||||
pub struct EntityConfig {
|
|
||||||
pub config_id: i32,
|
|
||||||
pub config_type: EntityConfigType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for EntityConfig {
|
|
||||||
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
|
|
||||||
pb.config_id = self.config_id;
|
|
||||||
pb.config_type = self.config_type.into();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
mod attribute;
|
|
||||||
mod entity_config;
|
|
||||||
mod movement;
|
|
||||||
mod owner_player;
|
|
||||||
mod player_entity_marker;
|
|
||||||
mod position;
|
|
||||||
mod visibility;
|
|
||||||
|
|
||||||
pub use attribute::Attribute;
|
|
||||||
pub use entity_config::EntityConfig;
|
|
||||||
pub use movement::Movement;
|
|
||||||
pub use owner_player::OwnerPlayer;
|
|
||||||
pub use player_entity_marker::PlayerEntityMarker;
|
|
||||||
pub use position::Position;
|
|
||||||
pub use visibility::Visibility;
|
|
|
@ -1,57 +0,0 @@
|
||||||
use std::{cell::RefCell, collections::HashSet};
|
|
||||||
|
|
||||||
use super::component::ComponentContainer;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
|
||||||
pub struct Entity(i64);
|
|
||||||
|
|
||||||
pub struct EntityBuilder<'comp>(Entity, &'comp mut Vec<RefCell<ComponentContainer>>);
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct EntityManager {
|
|
||||||
entity_id_counter: i64,
|
|
||||||
active_entity_set: HashSet<Entity>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EntityManager {
|
|
||||||
pub fn create(&mut self) -> Entity {
|
|
||||||
self.entity_id_counter += 1;
|
|
||||||
let entity = Entity(self.entity_id_counter);
|
|
||||||
|
|
||||||
self.active_entity_set.insert(entity);
|
|
||||||
entity
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, id: i64) -> Option<Entity> {
|
|
||||||
self.active_entity_set.get(&Entity(id)).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(dead_code)]
|
|
||||||
pub fn remove(&mut self, entity: Entity) -> bool {
|
|
||||||
self.active_entity_set.remove(&entity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'comp> EntityBuilder<'comp> {
|
|
||||||
pub fn builder(
|
|
||||||
entity: Entity,
|
|
||||||
components: &'comp mut Vec<RefCell<ComponentContainer>>,
|
|
||||||
) -> Self {
|
|
||||||
Self(entity, components)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with(self, component: ComponentContainer) -> Self {
|
|
||||||
self.1.push(RefCell::new(component));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> Entity {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Entity> for i64 {
|
|
||||||
fn from(value: Entity) -> Self {
|
|
||||||
value.0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
pub mod component;
|
|
||||||
pub mod entity;
|
|
||||||
pub mod world;
|
|
||||||
|
|
||||||
// Query specified components from all entities
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! query_with {
|
|
||||||
($world:expr, $($comp:ident),*) => {
|
|
||||||
$world.components().iter().filter(|(_, comps)| {
|
|
||||||
$(comps.iter().any(|comp| matches!(&*comp.borrow(), ComponentContainer::$comp(_))) && )* true
|
|
||||||
})
|
|
||||||
.map(|(e, comps)| {
|
|
||||||
(*e,
|
|
||||||
$(
|
|
||||||
comps.iter().find_map(|comp| {
|
|
||||||
let r = comp.try_borrow_mut().ok()?;
|
|
||||||
if matches!(&*r, ComponentContainer::$comp(_)) {
|
|
||||||
Some(::std::cell::RefMut::map(r, |r| {
|
|
||||||
let ComponentContainer::$comp(comp_inner) = r else { unreachable!() };
|
|
||||||
comp_inner
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}).unwrap(),
|
|
||||||
)*
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! ident_as_none {
|
|
||||||
($t:ident) => {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query components of specified entity
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! query_components {
|
|
||||||
($world:expr, $entity_id:expr, $($comp:ident),*) => {
|
|
||||||
$world.components().iter().find(|(id, _)| $entity_id == i64::from(**id))
|
|
||||||
.map(|(_, comps)| {
|
|
||||||
($(
|
|
||||||
comps.iter().find_map(|comp| {
|
|
||||||
let r = comp.try_borrow_mut().ok()?;
|
|
||||||
if matches!(&*r, ComponentContainer::$comp(_)) {
|
|
||||||
Some(::std::cell::RefMut::map(r, |r| {
|
|
||||||
let ComponentContainer::$comp(comp_inner) = r else { unreachable!() };
|
|
||||||
comp_inner
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)*)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| ($( crate::ident_as_none!($comp), )*))
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
use std::cell::{RefCell, RefMut};
|
|
||||||
use std::collections::hash_map::{Keys, Values};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::logic::player::InWorldPlayer;
|
|
||||||
|
|
||||||
use super::component::ComponentContainer;
|
|
||||||
use super::entity::{Entity, EntityBuilder, EntityManager};
|
|
||||||
|
|
||||||
pub struct World {
|
|
||||||
components: HashMap<Entity, Vec<RefCell<ComponentContainer>>>,
|
|
||||||
entity_manager: EntityManager,
|
|
||||||
in_world_players: HashMap<i32, InWorldPlayer>, // joined players metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
impl World {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
components: HashMap::new(),
|
|
||||||
entity_manager: EntityManager::default(),
|
|
||||||
in_world_players: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_entity(&mut self) -> EntityBuilder {
|
|
||||||
let entity = self.entity_manager.create();
|
|
||||||
EntityBuilder::builder(entity, self.components.entry(entity).or_insert(Vec::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_in_world(&self, entity_id: i64) -> bool {
|
|
||||||
self.entity_manager.get(entity_id).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn components(&self) -> &HashMap<Entity, Vec<RefCell<ComponentContainer>>> {
|
|
||||||
&self.components
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_entity_components(&self, entity: Entity) -> Vec<RefMut<ComponentContainer>> {
|
|
||||||
let Some(components) = self.components.get(&entity) else {
|
|
||||||
return Vec::with_capacity(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
components.iter().map(|rc| rc.borrow_mut()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn player_ids(&self) -> Keys<'_, i32, InWorldPlayer> {
|
|
||||||
self.in_world_players.keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn players(&self) -> Values<'_, i32, InWorldPlayer> {
|
|
||||||
self.in_world_players.values()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_in_world_player_data(&mut self, in_world_player: InWorldPlayer) {
|
|
||||||
self.in_world_players
|
|
||||||
.insert(in_world_player.player_id, in_world_player);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
mod scene;
|
|
||||||
pub use scene::*;
|
|
||||||
|
|
||||||
use shorekeeper_protocol::message::Message;
|
|
||||||
|
|
||||||
macro_rules! handle_request {
|
|
||||||
($($name:ident;)*) => {
|
|
||||||
fn handle_request(player: &mut super::player::Player, mut msg: Message) {
|
|
||||||
use ::shorekeeper_protocol::{MessageID, Protobuf};
|
|
||||||
|
|
||||||
::paste::paste! {
|
|
||||||
match msg.get_message_id() {
|
|
||||||
$(
|
|
||||||
::shorekeeper_protocol::[<$name Request>]::MESSAGE_ID => {
|
|
||||||
let Ok(request) = ::shorekeeper_protocol::[<$name Request>]::decode(&*msg.remove_payload()) else {
|
|
||||||
tracing::debug!("failed to decode {}, player_id: {}", stringify!([<$name Request>]), player.basic_info.id);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!("logic: processing request {}", stringify!([<$name Request>]));
|
|
||||||
|
|
||||||
let mut response = ::shorekeeper_protocol::[<$name Response>]::default();
|
|
||||||
[<on_ $name:snake _request>](player, request, &mut response);
|
|
||||||
|
|
||||||
player.respond(response, msg.get_rpc_id());
|
|
||||||
},
|
|
||||||
)*
|
|
||||||
unhandled => ::tracing::warn!("can't find handler for request with message_id={unhandled}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! handle_push {
|
|
||||||
($($name:ident;)*) => {
|
|
||||||
fn handle_push(player: &mut super::player::Player, mut msg: Message) {
|
|
||||||
use ::shorekeeper_protocol::{MessageID, Protobuf};
|
|
||||||
|
|
||||||
::paste::paste! {
|
|
||||||
match msg.get_message_id() {
|
|
||||||
$(
|
|
||||||
::shorekeeper_protocol::[<$name Push>]::MESSAGE_ID => {
|
|
||||||
let Ok(push) = ::shorekeeper_protocol::[<$name Push>]::decode(&*msg.remove_payload()) else {
|
|
||||||
tracing::debug!("failed to decode {}, player_id: {}", stringify!([<$name Push>]), player.basic_info.id);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
[<on_ $name:snake _push>](player, push);
|
|
||||||
},
|
|
||||||
)*
|
|
||||||
unhandled => ::tracing::warn!("can't find handler for push with message_id={unhandled}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_request! {
|
|
||||||
UpdateSceneDate;
|
|
||||||
EntityActive;
|
|
||||||
EntityOnLanded;
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_push! {
|
|
||||||
MovePackage;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_logic_message(player: &mut super::player::Player, msg: Message) {
|
|
||||||
match msg {
|
|
||||||
Message::Request { .. } => handle_request(player, msg),
|
|
||||||
Message::Push { .. } => handle_push(player, msg),
|
|
||||||
_ => tracing::warn!(
|
|
||||||
"handle_logic_message: wrong message type: {}, message_id: {}, player_id: {}",
|
|
||||||
msg.get_message_type(),
|
|
||||||
msg.get_message_id(),
|
|
||||||
player.basic_info.id,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
use shorekeeper_protocol::{
|
|
||||||
EntityActiveRequest, EntityActiveResponse, EntityOnLandedRequest, EntityOnLandedResponse,
|
|
||||||
ErrorCode, MovePackagePush, UpdateSceneDateRequest, UpdateSceneDateResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{logic::ecs::component::ComponentContainer, logic::player::Player, query_components};
|
|
||||||
|
|
||||||
pub fn on_update_scene_date_request(
|
|
||||||
_player: &Player,
|
|
||||||
_request: UpdateSceneDateRequest,
|
|
||||||
response: &mut UpdateSceneDateResponse,
|
|
||||||
) {
|
|
||||||
response.error_code = ErrorCode::Success.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_entity_active_request(
|
|
||||||
player: &Player,
|
|
||||||
request: EntityActiveRequest,
|
|
||||||
response: &mut EntityActiveResponse,
|
|
||||||
) {
|
|
||||||
let world = player.world.borrow();
|
|
||||||
|
|
||||||
if !world.is_in_world(request.entity_id) {
|
|
||||||
tracing::debug!(
|
|
||||||
"EntityActiveRequest: entity with id {} doesn't exist, player_id: {}",
|
|
||||||
request.entity_id,
|
|
||||||
player.basic_info.id
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(position) = query_components!(world, request.entity_id, Position).0 {
|
|
||||||
// TODO: proper entity "activation" logic
|
|
||||||
response.pos = Some(position.0.get_position_protobuf());
|
|
||||||
response.rot = Some(position.0.get_rotation_protobuf());
|
|
||||||
}
|
|
||||||
response.component_pbs = Vec::new(); // not implemented
|
|
||||||
response.error_code = ErrorCode::Success.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_entity_on_landed_request(
|
|
||||||
_: &Player,
|
|
||||||
request: EntityOnLandedRequest,
|
|
||||||
_: &mut EntityOnLandedResponse,
|
|
||||||
) {
|
|
||||||
tracing::debug!(
|
|
||||||
"EntityOnLandedRequest: entity with id {} landed",
|
|
||||||
request.entity_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_move_package_push(player: &mut Player, push: MovePackagePush) {
|
|
||||||
let world = player.world.borrow();
|
|
||||||
|
|
||||||
for moving_entity in push.moving_entities {
|
|
||||||
if !world.is_in_world(moving_entity.entity_id) {
|
|
||||||
tracing::debug!(
|
|
||||||
"MovePackage: entity with id {} doesn't exist",
|
|
||||||
moving_entity.entity_id
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(mut movement) = query_components!(world, moving_entity.entity_id, Movement).0
|
|
||||||
else {
|
|
||||||
tracing::warn!(
|
|
||||||
"MovePackage: entity {} doesn't have movement component",
|
|
||||||
moving_entity.entity_id
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
movement
|
|
||||||
.pending_movement_vec
|
|
||||||
.extend(moving_entity.move_infos);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,243 +0,0 @@
|
||||||
use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc};
|
|
||||||
|
|
||||||
use basic_info::PlayerBasicInfo;
|
|
||||||
use common::time_util;
|
|
||||||
use location::PlayerLocation;
|
|
||||||
use player_func::PlayerFunc;
|
|
||||||
use shorekeeper_protocol::{
|
|
||||||
message::Message, PbGetRoleListNotify, PlayerBasicData, PlayerRoleData, PlayerSaveData,
|
|
||||||
ProtocolUnit,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::session::Session;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
ecs::world::World,
|
|
||||||
role::{Role, RoleFormation},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod basic_info;
|
|
||||||
mod in_world_player;
|
|
||||||
mod location;
|
|
||||||
mod player_func;
|
|
||||||
|
|
||||||
pub use in_world_player::InWorldPlayer;
|
|
||||||
|
|
||||||
pub struct Player {
|
|
||||||
session: Option<Arc<Session>>,
|
|
||||||
pub basic_info: PlayerBasicInfo,
|
|
||||||
pub role_list: Vec<Role>,
|
|
||||||
pub formation_list: Vec<RoleFormation>,
|
|
||||||
pub location: PlayerLocation,
|
|
||||||
pub func: PlayerFunc,
|
|
||||||
pub world: Rc<RefCell<World>>,
|
|
||||||
pub last_save_time: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Player {
|
|
||||||
pub fn init(&mut self) {
|
|
||||||
if self.role_list.is_empty() {
|
|
||||||
self.on_first_enter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// we need shorekeeper
|
|
||||||
// TODO: remove this part after implementing team switch
|
|
||||||
if !self.role_list.iter().any(|r| r.role_id == 1505) {
|
|
||||||
self.role_list.push(Role::new(1505));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.formation_list.clear();
|
|
||||||
self.formation_list.push(RoleFormation {
|
|
||||||
id: 1,
|
|
||||||
cur_role: 1505,
|
|
||||||
role_id_set: HashSet::from([1505]),
|
|
||||||
is_current: true,
|
|
||||||
});
|
|
||||||
// End shorekeeper hardcode part
|
|
||||||
|
|
||||||
self.ensure_current_formation();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notify_general_data(&self) {
|
|
||||||
self.notify(self.basic_info.build_notify());
|
|
||||||
self.notify(self.func.build_func_open_notify());
|
|
||||||
self.notify(self.build_role_list_notify());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_first_enter(&mut self) {
|
|
||||||
self.role_list.push(Self::create_main_character_role(
|
|
||||||
self.basic_info.name.clone(),
|
|
||||||
self.basic_info.sex,
|
|
||||||
));
|
|
||||||
|
|
||||||
let role = &self.role_list[0];
|
|
||||||
|
|
||||||
self.formation_list.push(RoleFormation {
|
|
||||||
id: 1,
|
|
||||||
cur_role: role.role_id,
|
|
||||||
role_id_set: HashSet::from([role.role_id]),
|
|
||||||
is_current: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.location = PlayerLocation::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_current_formation(&mut self) {
|
|
||||||
if self.formation_list.is_empty() {
|
|
||||||
let role = &self.role_list[0];
|
|
||||||
|
|
||||||
self.formation_list.push(RoleFormation {
|
|
||||||
id: 1,
|
|
||||||
cur_role: role.role_id,
|
|
||||||
role_id_set: HashSet::from([role.role_id]),
|
|
||||||
is_current: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.formation_list.iter().any(|rf| rf.is_current) {
|
|
||||||
self.formation_list[0].is_current = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rf) = self.formation_list.iter_mut().find(|rf| rf.is_current) {
|
|
||||||
if rf.role_id_set.is_empty() {
|
|
||||||
rf.role_id_set.insert(self.role_list[0].role_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rf.role_id_set.contains(&rf.cur_role) {
|
|
||||||
rf.cur_role = *rf.role_id_set.iter().nth(0).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_in_world_player(&self) -> InWorldPlayer {
|
|
||||||
InWorldPlayer {
|
|
||||||
player_id: self.basic_info.id,
|
|
||||||
player_name: self.basic_info.name.clone(),
|
|
||||||
player_icon: 0,
|
|
||||||
level: self.basic_info.level,
|
|
||||||
group_type: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_current_formation_role_list(&self) -> Vec<&Role> {
|
|
||||||
self.formation_list
|
|
||||||
.iter()
|
|
||||||
.find(|rf| rf.is_current)
|
|
||||||
.unwrap()
|
|
||||||
.role_id_set
|
|
||||||
.iter()
|
|
||||||
.map(|id| self.role_list.iter().find(|r| r.role_id == *id))
|
|
||||||
.flatten()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_cur_role_id(&self) -> i32 {
|
|
||||||
self.formation_list
|
|
||||||
.iter()
|
|
||||||
.find(|rf| rf.is_current)
|
|
||||||
.unwrap()
|
|
||||||
.cur_role
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_from_save(save_data: PlayerSaveData) -> Self {
|
|
||||||
let role_data = save_data.role_data.unwrap_or_default();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
session: None,
|
|
||||||
basic_info: PlayerBasicInfo::load_from_save(save_data.basic_data.unwrap_or_default()),
|
|
||||||
role_list: role_data
|
|
||||||
.role_list
|
|
||||||
.into_iter()
|
|
||||||
.map(Role::load_from_save)
|
|
||||||
.collect(),
|
|
||||||
formation_list: role_data
|
|
||||||
.role_formation_list
|
|
||||||
.into_iter()
|
|
||||||
.map(RoleFormation::load_from_save)
|
|
||||||
.collect(),
|
|
||||||
location: save_data
|
|
||||||
.location_data
|
|
||||||
.map(PlayerLocation::load_from_save)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
func: save_data
|
|
||||||
.func_data
|
|
||||||
.map(PlayerFunc::load_from_save)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
world: Rc::new(RefCell::new(World::new())),
|
|
||||||
last_save_time: time_util::unix_timestamp(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_save_data(&self) -> PlayerSaveData {
|
|
||||||
PlayerSaveData {
|
|
||||||
basic_data: Some(self.basic_info.build_save_data()),
|
|
||||||
role_data: Some(PlayerRoleData {
|
|
||||||
role_list: self.role_list.iter().map(|r| r.build_save_data()).collect(),
|
|
||||||
role_formation_list: self
|
|
||||||
.formation_list
|
|
||||||
.iter()
|
|
||||||
.map(|rf| rf.build_save_data())
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
location_data: Some(self.location.build_save_data()),
|
|
||||||
func_data: Some(self.func.build_save_data()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_session(&mut self, session: Arc<Session>) {
|
|
||||||
self.session = Some(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_role_list_notify(&self) -> PbGetRoleListNotify {
|
|
||||||
PbGetRoleListNotify {
|
|
||||||
role_list: self.role_list.iter().map(|r| r.to_protobuf()).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn notify(&self, content: impl ProtocolUnit) {
|
|
||||||
if let Some(session) = self.session.as_ref() {
|
|
||||||
session.forward_to_gateway(Message::Push {
|
|
||||||
sequence_number: 0,
|
|
||||||
message_id: content.get_message_id(),
|
|
||||||
payload: Some(content.encode_to_vec().into_boxed_slice()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn respond(&self, content: impl ProtocolUnit, rpc_id: u16) {
|
|
||||||
if let Some(session) = self.session.as_ref() {
|
|
||||||
session.forward_to_gateway(Message::Response {
|
|
||||||
sequence_number: 0,
|
|
||||||
message_id: content.get_message_id(),
|
|
||||||
rpc_id,
|
|
||||||
payload: Some(content.encode_to_vec().into_boxed_slice()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_main_character_role(name: String, sex: i32) -> Role {
|
|
||||||
let mut role = match sex {
|
|
||||||
0 => Role::new(Role::MAIN_CHARACTER_FEMALE_ID),
|
|
||||||
1 => Role::new(Role::MAIN_CHARACTER_MALE_ID),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
role.name = name;
|
|
||||||
role
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_default_save_data(id: i32, name: String, sex: i32) -> PlayerSaveData {
|
|
||||||
PlayerSaveData {
|
|
||||||
basic_data: Some(PlayerBasicData {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
sex,
|
|
||||||
level: 1,
|
|
||||||
head_photo: 1505,
|
|
||||||
head_frame: 80060009,
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
use crate::logic::ecs::component::ComponentContainer;
|
|
||||||
use shorekeeper_protocol::{EntityPb, PlayerSceneAoiData};
|
|
||||||
|
|
||||||
use crate::{logic::ecs::world::World, query_with};
|
|
||||||
|
|
||||||
pub fn build_scene_add_on_init_data(world: &World) -> PlayerSceneAoiData {
|
|
||||||
let entities = query_with!(world, PlayerEntityMarker)
|
|
||||||
.into_iter()
|
|
||||||
.map(|(e, _)| e)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut aoi_data = PlayerSceneAoiData::default();
|
|
||||||
for entity in entities {
|
|
||||||
let mut pb = EntityPb::default();
|
|
||||||
pb.id = entity.into();
|
|
||||||
|
|
||||||
world
|
|
||||||
.get_entity_components(entity)
|
|
||||||
.into_iter()
|
|
||||||
.for_each(|comp| comp.set_pb_data(&mut pb));
|
|
||||||
|
|
||||||
aoi_data.entities.push(pb);
|
|
||||||
}
|
|
||||||
|
|
||||||
aoi_data
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod entity_serializer;
|
|
||||||
pub mod world_util;
|
|
|
@ -1,127 +0,0 @@
|
||||||
use shorekeeper_data::base_property_data;
|
|
||||||
use shorekeeper_protocol::{
|
|
||||||
EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, SceneMode,
|
|
||||||
ScenePlayerInformation, SceneTimeInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
logic::{
|
|
||||||
components::{
|
|
||||||
Attribute, EntityConfig, Movement, OwnerPlayer, PlayerEntityMarker, Position,
|
|
||||||
Visibility,
|
|
||||||
},
|
|
||||||
ecs::{component::ComponentContainer, world::World},
|
|
||||||
player::Player,
|
|
||||||
},
|
|
||||||
query_with,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::entity_serializer;
|
|
||||||
|
|
||||||
pub fn add_player_entities(world: &mut World, player: &Player) {
|
|
||||||
let cur_role_id = player.get_cur_role_id();
|
|
||||||
|
|
||||||
for role in player.get_current_formation_role_list() {
|
|
||||||
let id = world
|
|
||||||
.create_entity()
|
|
||||||
.with(ComponentContainer::PlayerEntityMarker(PlayerEntityMarker))
|
|
||||||
.with(ComponentContainer::EntityConfig(EntityConfig {
|
|
||||||
config_id: role.role_id,
|
|
||||||
config_type: EntityConfigType::Character,
|
|
||||||
}))
|
|
||||||
.with(ComponentContainer::OwnerPlayer(OwnerPlayer(
|
|
||||||
player.basic_info.id,
|
|
||||||
)))
|
|
||||||
.with(ComponentContainer::Position(Position(
|
|
||||||
player.location.position.clone(),
|
|
||||||
)))
|
|
||||||
.with(ComponentContainer::Visibility(Visibility(
|
|
||||||
role.role_id == cur_role_id,
|
|
||||||
)))
|
|
||||||
.with(ComponentContainer::Attribute(Attribute::from_data(
|
|
||||||
base_property_data::iter()
|
|
||||||
.find(|d| d.id == role.role_id)
|
|
||||||
.unwrap(),
|
|
||||||
)))
|
|
||||||
.with(ComponentContainer::Movement(Movement::default()))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
"created player entity, id: {}, role_id: {}",
|
|
||||||
i64::from(id),
|
|
||||||
role.role_id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_scene_information(world: &World, instance_id: i32, owner_id: i32) -> SceneInformation {
|
|
||||||
SceneInformation {
|
|
||||||
scene_id: String::new(),
|
|
||||||
instance_id,
|
|
||||||
owner_id,
|
|
||||||
dynamic_entity_list: Vec::new(),
|
|
||||||
blackboard_params: Vec::new(),
|
|
||||||
end_time: 0,
|
|
||||||
aoi_data: Some(entity_serializer::build_scene_add_on_init_data(world)),
|
|
||||||
player_infos: build_player_info_list(world),
|
|
||||||
mode: SceneMode::Single.into(),
|
|
||||||
time_info: Some(SceneTimeInfo {
|
|
||||||
owner_time_clock_time_span: 0,
|
|
||||||
hour: 8,
|
|
||||||
minute: 0,
|
|
||||||
}),
|
|
||||||
cur_context_id: owner_id as i64,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_player_info_list(world: &World) -> Vec<ScenePlayerInformation> {
|
|
||||||
world
|
|
||||||
.players()
|
|
||||||
.map(|sp| {
|
|
||||||
let (cur_role_id, transform) = query_with!(
|
|
||||||
world,
|
|
||||||
PlayerEntityMarker,
|
|
||||||
OwnerPlayer,
|
|
||||||
Visibility,
|
|
||||||
EntityConfig,
|
|
||||||
Position
|
|
||||||
)
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|(_, _, owner, visibility, conf, pos)| {
|
|
||||||
(sp.player_id == owner.0 && visibility.0).then_some((conf.config_id, pos.0.clone()))
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let active_characters =
|
|
||||||
query_with!(world, PlayerEntityMarker, OwnerPlayer, EntityConfig)
|
|
||||||
.into_iter()
|
|
||||||
.filter(|(_, _, owner, _)| owner.0 == sp.player_id);
|
|
||||||
|
|
||||||
ScenePlayerInformation {
|
|
||||||
cur_role: cur_role_id,
|
|
||||||
group_type: sp.group_type,
|
|
||||||
player_id: sp.player_id,
|
|
||||||
player_icon: sp.player_icon,
|
|
||||||
player_name: sp.player_name.clone(),
|
|
||||||
level: sp.level,
|
|
||||||
location: Some(transform.get_position_protobuf()),
|
|
||||||
rotation: Some(transform.get_rotation_protobuf()),
|
|
||||||
fight_role_infos: Vec::from([FightRoleInfos {
|
|
||||||
group_type: sp.group_type,
|
|
||||||
living_status: LivingStatus::Alive.into(),
|
|
||||||
cur_role: cur_role_id,
|
|
||||||
is_retain: true,
|
|
||||||
fight_role_infos: active_characters
|
|
||||||
.map(|(id, _, _, conf)| FightRoleInfo {
|
|
||||||
entity_id: id.into(),
|
|
||||||
role_id: conf.config_id,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
..Default::default()
|
|
||||||
}]),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
use std::{process, sync::LazyLock};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use config::{GatewayConfig, ServerConfig};
|
|
||||||
use shorekeeper_database::PgPool;
|
|
||||||
use shorekeeper_http::{Application, StatusCode};
|
|
||||||
|
|
||||||
mod config;
|
|
||||||
mod handler;
|
|
||||||
mod schema;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ServiceState {
|
|
||||||
pub pool: PgPool,
|
|
||||||
pub gateway: &'static GatewayConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
static CONFIG: LazyLock<ServerConfig> =
|
|
||||||
LazyLock::new(|| ::common::config_util::load_or_create("loginserver.toml"));
|
|
||||||
|
|
||||||
::common::splash::print_splash();
|
|
||||||
::common::logging::init(::tracing::Level::DEBUG);
|
|
||||||
|
|
||||||
let Ok(pool) = shorekeeper_database::connect_to(&CONFIG.database).await else {
|
|
||||||
tracing::error!(
|
|
||||||
"Failed to connect to database with connection string: {}",
|
|
||||||
&CONFIG.database
|
|
||||||
);
|
|
||||||
process::exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
shorekeeper_database::run_migrations(&pool).await?;
|
|
||||||
|
|
||||||
Application::new_with_state(ServiceState {
|
|
||||||
pool,
|
|
||||||
gateway: &CONFIG.gateway,
|
|
||||||
})
|
|
||||||
.get("/health", || async { StatusCode::OK })
|
|
||||||
.get("/api/login", handler::handle_login_api_call)
|
|
||||||
.serve(&CONFIG.network)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
Before Width: | Height: | Size: 6 MiB After Width: | Height: | Size: 7.7 MiB |
|
@ -1,50 +0,0 @@
|
||||||
use paste::paste;
|
|
||||||
|
|
||||||
mod misc_data;
|
|
||||||
pub use misc_data::*;
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum LoadDataError {
|
|
||||||
#[error("I/O error: {0}")]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error("Failed to parse json: {0}")]
|
|
||||||
Json(#[from] serde_json::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! json_data {
|
|
||||||
($($table_type:ident;)*) => {
|
|
||||||
$(paste! {
|
|
||||||
mod [<$table_type:snake>];
|
|
||||||
pub use [<$table_type:snake>]::[<$table_type Data>];
|
|
||||||
})*
|
|
||||||
|
|
||||||
$(paste! {
|
|
||||||
pub mod [<$table_type:snake _data>] {
|
|
||||||
use std::sync::OnceLock;
|
|
||||||
type Data = super::[<$table_type Data>];
|
|
||||||
pub(crate) static TABLE: OnceLock<Vec<Data>> = OnceLock::new();
|
|
||||||
|
|
||||||
pub fn iter() -> std::slice::Iter<'static, Data> {
|
|
||||||
TABLE.get().unwrap().iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})*
|
|
||||||
|
|
||||||
pub fn load_json_data(base_path: &str) -> Result<(), LoadDataError> {
|
|
||||||
$(paste! {
|
|
||||||
let json_content = std::fs::read_to_string(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
|
|
||||||
let _ = [<$table_type:snake _data>]::TABLE.set(serde_json::from_str(&json_content)?);
|
|
||||||
})*
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
json_data! {
|
|
||||||
RoleInfo;
|
|
||||||
WeaponConf;
|
|
||||||
BaseProperty;
|
|
||||||
InstanceDungeon;
|
|
||||||
FunctionCondition;
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct PropValueData {
|
|
||||||
pub id: i32,
|
|
||||||
pub value: f32,
|
|
||||||
pub is_ratio: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct VectorData([f32; 3]);
|
|
||||||
|
|
||||||
impl VectorData {
|
|
||||||
pub fn get_x(&self) -> f32 {
|
|
||||||
self.0[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_y(&self) -> f32 {
|
|
||||||
self.0[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_z(&self) -> f32 {
|
|
||||||
self.0[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(rename_all = "PascalCase")]
|
|
||||||
pub struct EntranceEntityData {
|
|
||||||
pub dungeon_id: i32,
|
|
||||||
pub entrance_entity_id: i32,
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
use std::{
|
|
||||||
fs,
|
|
||||||
io::{self, BufRead},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use quote::quote;
|
|
||||||
|
|
||||||
const CODEGEN_OUT_DIR: &str = "generated/";
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let _ = fs::create_dir(CODEGEN_OUT_DIR);
|
|
||||||
|
|
||||||
let config_file = "proto/config.csv";
|
|
||||||
let config_path = Path::new("proto/config.csv");
|
|
||||||
if config_path.exists() {
|
|
||||||
println!("cargo:rerun-if-changed={config_file}");
|
|
||||||
impl_proto_config(config_path, &Path::new("generated/proto_config.rs")).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let proto_file = "proto/shorekeeper.proto";
|
|
||||||
|
|
||||||
if Path::new(&proto_file).exists() {
|
|
||||||
println!("cargo:rerun-if-changed={proto_file}");
|
|
||||||
|
|
||||||
prost_build::Config::new()
|
|
||||||
.out_dir(CODEGEN_OUT_DIR)
|
|
||||||
.type_attribute(".", "#[derive(shorekeeper_protocol_derive::MessageID)]")
|
|
||||||
.compile_protos(&[proto_file], &["shorekeeper"])
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
impl_message_id(Path::new("generated/shorekeeper.rs")).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let proto_file = "proto/internal.proto";
|
|
||||||
if Path::new(&proto_file).exists() {
|
|
||||||
println!("cargo:rerun-if-changed={proto_file}");
|
|
||||||
|
|
||||||
prost_build::Config::new()
|
|
||||||
.out_dir(CODEGEN_OUT_DIR)
|
|
||||||
.type_attribute(".", "#[derive(shorekeeper_protocol_derive::MessageID)]")
|
|
||||||
.compile_protos(&[proto_file], &["internal"])
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
impl_message_id(Path::new("generated/internal.rs")).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let proto_file = "proto/data.proto";
|
|
||||||
if Path::new(&proto_file).exists() {
|
|
||||||
println!("cargo:rerun-if-changed={proto_file}");
|
|
||||||
|
|
||||||
prost_build::Config::new()
|
|
||||||
.out_dir(CODEGEN_OUT_DIR)
|
|
||||||
.compile_protos(&[proto_file], &["data"])
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn impl_proto_config(csv_path: &Path, codegen_path: &Path) -> io::Result<()> {
|
|
||||||
let file = fs::File::open(csv_path)?;
|
|
||||||
let reader = io::BufReader::new(file);
|
|
||||||
|
|
||||||
let mut match_arms = quote! {};
|
|
||||||
for line in reader.lines() {
|
|
||||||
let line = line?;
|
|
||||||
let mut row = line.split(',');
|
|
||||||
|
|
||||||
let message_id = row.next().unwrap().parse::<u16>().unwrap();
|
|
||||||
let flags = row.next().unwrap().parse::<u8>().unwrap();
|
|
||||||
|
|
||||||
match_arms = quote! {
|
|
||||||
#match_arms
|
|
||||||
#message_id => MessageFlags(#flags),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let generated_code = quote! {
|
|
||||||
pub mod proto_config {
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct MessageFlags(u8);
|
|
||||||
|
|
||||||
impl MessageFlags {
|
|
||||||
pub fn value(self) -> u8 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_message_flags(id: u16) -> MessageFlags {
|
|
||||||
match id {
|
|
||||||
#match_arms
|
|
||||||
_ => MessageFlags(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
fs::write(codegen_path, generated_code.as_bytes())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn impl_message_id(path: &Path) -> io::Result<()> {
|
|
||||||
let file = fs::File::open(path)?;
|
|
||||||
let reader = io::BufReader::new(file);
|
|
||||||
let mut output = Vec::new();
|
|
||||||
|
|
||||||
let mut attr = None;
|
|
||||||
for line in reader.lines() {
|
|
||||||
let line = line?;
|
|
||||||
|
|
||||||
if line.contains("MessageId:") {
|
|
||||||
attr = Some(make_message_id_attr(&line).unwrap());
|
|
||||||
} else {
|
|
||||||
output.push(line);
|
|
||||||
if let Some(attr) = attr.take() {
|
|
||||||
output.push(attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::write(path, output.join("\n").as_bytes())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_message_id_attr(line: &str) -> Option<String> {
|
|
||||||
let id = line.trim_start().split(' ').nth(2)?.parse::<u16>().ok()?;
|
|
||||||
Some(format!("#[message_id({id})]"))
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,74 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package data;
|
|
||||||
|
|
||||||
message VectorData {
|
|
||||||
float x = 1;
|
|
||||||
float y = 2;
|
|
||||||
float z = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TransformData {
|
|
||||||
VectorData position = 1;
|
|
||||||
VectorData rotation = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerBasicData {
|
|
||||||
int32 id = 1;
|
|
||||||
string name = 2;
|
|
||||||
int32 sex = 3;
|
|
||||||
int32 level = 4;
|
|
||||||
int32 exp = 5;
|
|
||||||
int32 head_photo = 6;
|
|
||||||
int32 head_frame = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RoleSkillNodeData {
|
|
||||||
int32 node_id = 1;
|
|
||||||
bool is_active = 2;
|
|
||||||
int32 skill_id = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RoleData {
|
|
||||||
int32 role_id = 1;
|
|
||||||
string name = 2;
|
|
||||||
int32 level = 3;
|
|
||||||
int32 exp = 4;
|
|
||||||
int32 breakthrough = 5;
|
|
||||||
map<int32, int32> skill_map = 6;
|
|
||||||
map<int32, int32> phantom_map = 7;
|
|
||||||
int32 star = 8;
|
|
||||||
int32 favor = 9;
|
|
||||||
uint32 create_time = 10;
|
|
||||||
int32 cur_model = 11;
|
|
||||||
repeated int32 models = 12;
|
|
||||||
repeated RoleSkillNodeData skill_node_state = 13;
|
|
||||||
int32 resonant_chain_group_index = 14;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RoleFormationData {
|
|
||||||
int32 formation_id = 1;
|
|
||||||
int32 cur_role = 2;
|
|
||||||
repeated int32 role_id_list = 3;
|
|
||||||
bool is_current = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerRoleData {
|
|
||||||
repeated RoleData role_list = 1;
|
|
||||||
repeated RoleFormationData role_formation_list = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerLocationData {
|
|
||||||
int32 instance_id = 1;
|
|
||||||
TransformData position = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerFuncData {
|
|
||||||
map<int32, int32> func_map = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PlayerSaveData {
|
|
||||||
PlayerBasicData basic_data = 1;
|
|
||||||
PlayerRoleData role_data = 2;
|
|
||||||
PlayerLocationData location_data = 3;
|
|
||||||
PlayerFuncData func_data = 4;
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,12 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "shorekeeper-protocol-derive"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
syn = "2.0.53"
|
|
||||||
quote = "1.0.35"
|
|
||||||
proc-macro2 = "1.0.79"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
|
@ -1,27 +0,0 @@
|
||||||
use proc_macro::TokenStream;
|
|
||||||
use quote::{quote, ToTokens};
|
|
||||||
use syn::{parse_macro_input, DeriveInput, Meta, MetaList};
|
|
||||||
|
|
||||||
#[proc_macro_derive(MessageID, attributes(message_id))]
|
|
||||||
pub fn message_id_derive(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
|
||||||
let struct_name = input.ident;
|
|
||||||
|
|
||||||
let id = match input
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.find(|attr| attr.path().is_ident("message_id"))
|
|
||||||
{
|
|
||||||
Some(attr) => match attr.meta {
|
|
||||||
Meta::List(MetaList { ref tokens, .. }) => tokens.into_token_stream(),
|
|
||||||
_ => panic!("Invalid message_id attribute value"),
|
|
||||||
},
|
|
||||||
_ => 0u16.into_token_stream(),
|
|
||||||
};
|
|
||||||
|
|
||||||
TokenStream::from(quote! {
|
|
||||||
impl crate::MessageID for #struct_name {
|
|
||||||
const MESSAGE_ID: u16 = #id;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
include!("../generated/proto_config.rs");
|
|
||||||
include!("../generated/data.rs");
|
|
||||||
include!("../generated/shorekeeper.rs");
|
|
||||||
include!("../generated/internal.rs");
|
|
||||||
|
|
||||||
pub mod message;
|
|
||||||
pub use prost::DecodeError as ProtobufDecodeError;
|
|
||||||
pub use prost::Message as Protobuf;
|
|
||||||
|
|
||||||
pub trait MessageID {
|
|
||||||
const MESSAGE_ID: u16;
|
|
||||||
|
|
||||||
fn get_message_id(&self) -> u16 {
|
|
||||||
Self::MESSAGE_ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ProtocolUnit: Protobuf + MessageID {}
|
|
||||||
impl<T: Protobuf + MessageID> ProtocolUnit for T {}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum MessageRoute {
|
|
||||||
None,
|
|
||||||
Gateway,
|
|
||||||
GameServer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<proto_config::MessageFlags> for MessageRoute {
|
|
||||||
fn from(flags: proto_config::MessageFlags) -> Self {
|
|
||||||
match flags.value() & 3 {
|
|
||||||
0 => Self::Gateway,
|
|
||||||
2 => Self::GameServer,
|
|
||||||
_ => Self::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,198 +0,0 @@
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("IO Error: {0}")]
|
|
||||||
Io(#[from] io::Error),
|
|
||||||
#[error("Invalid message type: {0}")]
|
|
||||||
InvalidMessageType(u8),
|
|
||||||
#[error("Checksum mismatch, received: {0}, calculated: {1}")]
|
|
||||||
InvalidChecksum(u32, u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Message {
|
|
||||||
Request {
|
|
||||||
sequence_number: u32,
|
|
||||||
rpc_id: u16,
|
|
||||||
message_id: u16,
|
|
||||||
payload: Option<Box<[u8]>>,
|
|
||||||
},
|
|
||||||
Response {
|
|
||||||
sequence_number: u32,
|
|
||||||
rpc_id: u16,
|
|
||||||
message_id: u16,
|
|
||||||
payload: Option<Box<[u8]>>,
|
|
||||||
},
|
|
||||||
Push {
|
|
||||||
sequence_number: u32,
|
|
||||||
message_id: u16,
|
|
||||||
payload: Option<Box<[u8]>>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message {
|
|
||||||
const TYPE_REQUEST: u8 = 1;
|
|
||||||
const TYPE_RESPONSE: u8 = 2;
|
|
||||||
const TYPE_PUSH: u8 = 4;
|
|
||||||
|
|
||||||
pub fn encode(&self, out: &mut [u8]) -> io::Result<()> {
|
|
||||||
let mut w = io::Cursor::new(out);
|
|
||||||
|
|
||||||
let (sequence_number, message_id, payload) = match self {
|
|
||||||
Self::Request {
|
|
||||||
sequence_number,
|
|
||||||
message_id,
|
|
||||||
payload,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Self::Response {
|
|
||||||
sequence_number,
|
|
||||||
message_id,
|
|
||||||
payload,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| Self::Push {
|
|
||||||
sequence_number,
|
|
||||||
message_id,
|
|
||||||
payload,
|
|
||||||
} => (sequence_number, message_id, payload),
|
|
||||||
};
|
|
||||||
|
|
||||||
w.write_u8(self.get_message_type())?;
|
|
||||||
w.write_u32::<LE>(*sequence_number)?;
|
|
||||||
match self {
|
|
||||||
Self::Request { rpc_id, .. } | Self::Response { rpc_id, .. } => {
|
|
||||||
w.write_u16::<LE>(*rpc_id)?
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
w.write_u16::<LE>(*message_id)?;
|
|
||||||
|
|
||||||
if let Some(payload) = payload.as_ref() {
|
|
||||||
w.write_u32::<LE>(crc32fast::hash(payload))?;
|
|
||||||
w.write_all(payload)?;
|
|
||||||
} else {
|
|
||||||
w.write_u32::<LE>(0)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode(src: &[u8]) -> Result<Self, Error> {
|
|
||||||
let mut r = io::Cursor::new(src);
|
|
||||||
let message_type = r.read_u8()?;
|
|
||||||
let sequence_number = r.read_u32::<LE>()?;
|
|
||||||
let rpc_id = match message_type {
|
|
||||||
Self::TYPE_REQUEST | Self::TYPE_RESPONSE => r.read_u16::<LE>()?,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
let message_id = r.read_u16::<LE>()?;
|
|
||||||
let recv_crc = r.read_u32::<LE>()?;
|
|
||||||
|
|
||||||
let mut payload = vec![0u8; src.len() - r.position() as usize].into_boxed_slice();
|
|
||||||
r.read(&mut payload)?;
|
|
||||||
|
|
||||||
let calc_crc = crc32fast::hash(&payload);
|
|
||||||
|
|
||||||
(recv_crc == calc_crc)
|
|
||||||
.then_some(())
|
|
||||||
.ok_or(Error::InvalidChecksum(recv_crc, calc_crc))?;
|
|
||||||
|
|
||||||
let msg = match message_type {
|
|
||||||
Self::TYPE_REQUEST => Self::Request {
|
|
||||||
sequence_number,
|
|
||||||
rpc_id,
|
|
||||||
message_id,
|
|
||||||
payload: Some(payload),
|
|
||||||
},
|
|
||||||
Self::TYPE_RESPONSE => Self::Response {
|
|
||||||
sequence_number,
|
|
||||||
rpc_id,
|
|
||||||
message_id,
|
|
||||||
payload: Some(payload),
|
|
||||||
},
|
|
||||||
Self::TYPE_PUSH => Self::Push {
|
|
||||||
sequence_number,
|
|
||||||
message_id,
|
|
||||||
payload: Some(payload),
|
|
||||||
},
|
|
||||||
_ => return Err(Error::InvalidMessageType(message_type)),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_encoding_length(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Self::Request { payload, .. } | Self::Response { payload, .. } => {
|
|
||||||
13 + payload.as_ref().map(|p| p.len()).unwrap_or_default()
|
|
||||||
}
|
|
||||||
Self::Push { payload, .. } => {
|
|
||||||
11 + payload.as_ref().map(|p| p.len()).unwrap_or_default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_message_type(&self) -> u8 {
|
|
||||||
match self {
|
|
||||||
Self::Request { .. } => Self::TYPE_REQUEST,
|
|
||||||
Self::Response { .. } => Self::TYPE_RESPONSE,
|
|
||||||
Self::Push { .. } => Self::TYPE_PUSH,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_request(&self) -> bool {
|
|
||||||
matches!(self, Self::Request { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_push(&self) -> bool {
|
|
||||||
matches!(self, Self::Push { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_message_id(&self) -> u16 {
|
|
||||||
match self {
|
|
||||||
Self::Request { message_id, .. }
|
|
||||||
| Self::Response { message_id, .. }
|
|
||||||
| Self::Push { message_id, .. } => *message_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rpc_id(&self) -> u16 {
|
|
||||||
match self {
|
|
||||||
Self::Request { rpc_id, .. } | Self::Response { rpc_id, .. } => *rpc_id,
|
|
||||||
_ => 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_payload(&mut self) -> Box<[u8]> {
|
|
||||||
match self {
|
|
||||||
Self::Request { payload, .. }
|
|
||||||
| Self::Response { payload, .. }
|
|
||||||
| Self::Push { payload, .. } => payload.take().unwrap_or_else(|| Box::new([0u8; 0])),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_payload(&mut self, new_payload: Box<[u8]>) {
|
|
||||||
match self {
|
|
||||||
Self::Request { payload, .. }
|
|
||||||
| Self::Response { payload, .. }
|
|
||||||
| Self::Push { payload, .. } => *payload = Some(new_payload),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_sequence_number(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
Self::Request {
|
|
||||||
sequence_number, ..
|
|
||||||
}
|
|
||||||
| Self::Response {
|
|
||||||
sequence_number, ..
|
|
||||||
}
|
|
||||||
| Self::Push {
|
|
||||||
sequence_number, ..
|
|
||||||
} => *sequence_number,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "common"
|
name = "wicked-waifus-commons"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
|
@ -4,7 +4,7 @@ pub trait TomlConfig: DeserializeOwned {
|
||||||
const DEFAULT_TOML: &str;
|
const DEFAULT_TOML: &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_or_create<'a, C>(path: &str) -> C
|
pub fn load_or_create<C>(path: &str) -> C
|
||||||
where
|
where
|
||||||
C: DeserializeOwned + TomlConfig,
|
C: DeserializeOwned + TomlConfig,
|
||||||
{
|
{
|
28
wicked-waifus-commons/src/logging.rs
Normal file
28
wicked-waifus-commons/src/logging.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
|
||||||
|
pub fn init(max_level: Level) {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(max_level)
|
||||||
|
.with_target(false)
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_axum(max_level: Level) {
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
|
// axum logs rejections from built-in extractors with the `axum::rejection`
|
||||||
|
// target, at `TRACE` level. `axum::rejection=trace` enables showing those events
|
||||||
|
format!(
|
||||||
|
"{}={},tower_http={},axum::rejection=trace",
|
||||||
|
env!("CARGO_CRATE_NAME"),
|
||||||
|
max_level.as_str(),
|
||||||
|
max_level.as_str()
|
||||||
|
).into()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.with(tracing_subscriber::fmt::layer().with_target(false))
|
||||||
|
.init();
|
||||||
|
}
|
10
wicked-waifus-commons/src/splash.rs
Normal file
10
wicked-waifus-commons/src/splash.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
pub fn print_splash() {
|
||||||
|
println!("
|
||||||
|
██╗ ██╗██╗ ██████╗██╗ ██╗███████╗██████╗ ██╗ ██╗ █████╗ ██╗███████╗██╗ ██╗███████╗ ██████╗ ███████╗
|
||||||
|
██║ ██║██║██╔════╝██║ ██╔╝██╔════╝██╔══██╗ ██║ ██║██╔══██╗██║██╔════╝██║ ██║██╔════╝ ██╔══██╗██╔════╝
|
||||||
|
██║ █╗ ██║██║██║ █████╔╝ █████╗ ██║ ██║ ██║ █╗ ██║███████║██║█████╗ ██║ ██║███████╗ ██████╔╝███████╗
|
||||||
|
██║███╗██║██║██║ ██╔═██╗ ██╔══╝ ██║ ██║ ██║███╗██║██╔══██║██║██╔══╝ ██║ ██║╚════██║ ██╔═══╝ ╚════██║
|
||||||
|
╚███╔███╔╝██║╚██████╗██║ ██╗███████╗██████╔╝ ╚███╔███╔╝██║ ██║██║██║ ╚██████╔╝███████║ ██║ ███████║
|
||||||
|
╚══╝╚══╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝
|
||||||
|
");
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ pub fn unix_timestamp() -> u64 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() as u64
|
.as_secs()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unix_timestamp_ms() -> u64 {
|
pub fn unix_timestamp_ms() -> u64 {
|
|
@ -1,12 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "config-server"
|
name = "wicked-waifus-config-server"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Framework
|
# Framework
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
shorekeeper-http.workspace = true
|
|
||||||
|
|
||||||
# Serialization
|
# Serialization
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
@ -19,4 +18,5 @@ anyhow.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
common.workspace = true
|
wicked-waifus-commons.workspace = true
|
||||||
|
wicked-waifus-http.workspace = true
|
10
wicked-waifus-config-server/configserver.default.toml
Normal file
10
wicked-waifus-config-server/configserver.default.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[network]
|
||||||
|
http_addr = "0.0.0.0:10001"
|
||||||
|
|
||||||
|
[encryption]
|
||||||
|
key = "t+AEu5SGdpz06tomonajLMau9AJgmyTvVhz9VtGf1+0="
|
||||||
|
iv = "fprc5lBWADQB7tim0R2JxQ=="
|
||||||
|
|
||||||
|
[serve]
|
||||||
|
serve_web_path = "/"
|
||||||
|
serve_dir_path = "data/assets/config-server"
|
|
@ -1,19 +1,25 @@
|
||||||
use std::fs;
|
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use common::config_util::{self, TomlConfig};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use shorekeeper_http::{
|
|
||||||
config::{AesSettings, NetworkSettings},
|
use wicked_waifus_commons::config_util::{self, TomlConfig};
|
||||||
|
use wicked_waifus_http::{
|
||||||
Application,
|
Application,
|
||||||
|
config::{AesSettings, NetworkSettings},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ServeConfig {
|
||||||
|
pub serve_web_path: String,
|
||||||
|
pub serve_dir_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
pub network: NetworkSettings,
|
pub network: NetworkSettings,
|
||||||
pub encryption: AesSettings,
|
pub encryption: AesSettings,
|
||||||
|
pub serve: ServeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TomlConfig for ServerConfig {
|
impl TomlConfig for ServerConfig {
|
||||||
|
@ -25,21 +31,21 @@ async fn main() -> Result<()> {
|
||||||
static CONFIG: LazyLock<ServerConfig> =
|
static CONFIG: LazyLock<ServerConfig> =
|
||||||
LazyLock::new(|| config_util::load_or_create("configserver.toml"));
|
LazyLock::new(|| config_util::load_or_create("configserver.toml"));
|
||||||
|
|
||||||
::common::splash::print_splash();
|
::wicked_waifus_commons::splash::print_splash();
|
||||||
::common::logging::init(::tracing::Level::DEBUG);
|
::wicked_waifus_commons::logging::init_axum(::tracing::Level::DEBUG);
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"Serving files from {} at {}",
|
||||||
|
&CONFIG.serve.serve_web_path,
|
||||||
|
&CONFIG.serve.serve_dir_path
|
||||||
|
);
|
||||||
|
|
||||||
Application::new()
|
Application::new()
|
||||||
.get("/index.json", get_index)
|
.serve_dir(&CONFIG.serve.serve_web_path, &CONFIG.serve.serve_dir_path)
|
||||||
.with_encryption(&CONFIG.encryption)
|
.with_encryption(&CONFIG.encryption)
|
||||||
|
.with_logger()
|
||||||
.serve(&CONFIG.network)
|
.serve(&CONFIG.network)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_index() -> &'static str {
|
|
||||||
static INDEX: LazyLock<String> =
|
|
||||||
LazyLock::new(|| fs::read_to_string("assets/config/index.json").unwrap());
|
|
||||||
|
|
||||||
&*INDEX
|
|
||||||
}
|
|
|
@ -1,10 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "shorekeeper-data"
|
name = "wicked-waifus-data"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
serde_repr.workspace = true
|
||||||
paste.workspace = true
|
paste.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
15
wicked-waifus-data/src/adventure_task.rs
Normal file
15
wicked-waifus-data/src/adventure_task.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct AdventureTaskData {
|
||||||
|
pub id: i32,
|
||||||
|
pub chapter_id: i32,
|
||||||
|
pub task_text: String,
|
||||||
|
pub record_id: Vec<i32>,
|
||||||
|
pub need_progress: i32,
|
||||||
|
pub drop_ids: i32,
|
||||||
|
pub path_id: i32,
|
||||||
|
pub jump_to: HashMap<String, String>,
|
||||||
|
}
|
30
wicked-waifus-data/src/area.rs
Normal file
30
wicked-waifus-data/src/area.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct AreaData {
|
||||||
|
pub area_id: i32,
|
||||||
|
pub level: i32,
|
||||||
|
pub country_id: i32,
|
||||||
|
pub delivery_mark_id: i32,
|
||||||
|
pub area_name: String,
|
||||||
|
pub map_config_id: i32,
|
||||||
|
pub title: String,
|
||||||
|
pub father: i32,
|
||||||
|
pub tag: Vec<i32>,
|
||||||
|
pub record: i32,
|
||||||
|
pub tips: i32,
|
||||||
|
pub is_init_actived: bool,
|
||||||
|
#[serde(rename = "WorldMonsterLevelMax")]
|
||||||
|
pub world_monster_level_max: HashMap<i32, i32>,
|
||||||
|
#[serde(rename = "WuYinQuID")]
|
||||||
|
pub wu_yin_qu_id: i32,
|
||||||
|
pub state_id: i32,
|
||||||
|
pub atmosphere_id: i32,
|
||||||
|
pub edge_wall_name: String,
|
||||||
|
pub delivery_mark_type: i32,
|
||||||
|
pub sort_index: i32,
|
||||||
|
pub enter_area_tags: HashMap<String, i32>,
|
||||||
|
pub leave_area_tags: HashMap<String, i32>,
|
||||||
|
}
|
|
@ -16,7 +16,6 @@ pub struct BasePropertyData {
|
||||||
pub def: i32,
|
pub def: i32,
|
||||||
pub energy_efficiency: i32,
|
pub energy_efficiency: i32,
|
||||||
pub cd_reduse: i32,
|
pub cd_reduse: i32,
|
||||||
pub reaction_efficiency: i32,
|
|
||||||
pub damage_change_normal_skill: i32,
|
pub damage_change_normal_skill: i32,
|
||||||
pub damage_change: i32,
|
pub damage_change: i32,
|
||||||
pub damage_reduce: i32,
|
pub damage_reduce: i32,
|
||||||
|
@ -140,4 +139,5 @@ pub struct BasePropertyData {
|
||||||
pub paralysis_time_recover: i32,
|
pub paralysis_time_recover: i32,
|
||||||
pub element_energy_max: i32,
|
pub element_energy_max: i32,
|
||||||
pub element_energy: i32,
|
pub element_energy: i32,
|
||||||
|
pub element_efficiency: i32,
|
||||||
}
|
}
|
12
wicked-waifus-data/src/dragon_pool.rs
Normal file
12
wicked-waifus-data/src/dragon_pool.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct DragonPoolData {
|
||||||
|
pub id: i32,
|
||||||
|
pub core_id: i32,
|
||||||
|
pub goal: Vec<i32>,
|
||||||
|
pub drop_ids: Vec<i32>,
|
||||||
|
pub dark_coast_delivery_list: Vec<i32>,
|
||||||
|
pub auto_take: bool,
|
||||||
|
}
|
17
wicked-waifus-data/src/explore_tools.rs
Normal file
17
wicked-waifus-data/src/explore_tools.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct ExploreToolsData {
|
||||||
|
pub phantom_skill_id: i32,
|
||||||
|
pub skill_type: i32,
|
||||||
|
pub sort_id: i32,
|
||||||
|
pub auto_fill: bool,
|
||||||
|
pub show_unlock: bool,
|
||||||
|
pub skill_group_id: i32,
|
||||||
|
pub is_use_in_phantom_team: bool,
|
||||||
|
pub summon_config_id: i32,
|
||||||
|
pub authorization: HashMap<i32, i32>,
|
||||||
|
}
|
9
wicked-waifus-data/src/gacha.rs
Normal file
9
wicked-waifus-data/src/gacha.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct GachaData {
|
||||||
|
pub id: i32,
|
||||||
|
pub rule_group_id: i32,
|
||||||
|
pub sort: i32,
|
||||||
|
}
|
9
wicked-waifus-data/src/gacha_pool.rs
Normal file
9
wicked-waifus-data/src/gacha_pool.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct GachaPoolData {
|
||||||
|
pub id: i32,
|
||||||
|
pub gacha_id: i32,
|
||||||
|
pub sort: i32,
|
||||||
|
}
|
22
wicked-waifus-data/src/gacha_view_info.rs
Normal file
22
wicked-waifus-data/src/gacha_view_info.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use crate::GachaViewTypeInfoId;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct GachaViewInfoData {
|
||||||
|
pub id: i32,
|
||||||
|
pub r#type: GachaViewTypeInfoId,
|
||||||
|
pub summary_title: String,
|
||||||
|
pub summary_describe: String,
|
||||||
|
// TODO: Effect path
|
||||||
|
pub theme_color: String,
|
||||||
|
pub content_texture_path: String,
|
||||||
|
pub content_texture_bg_path: String,
|
||||||
|
pub under_bg_texture_path: String,
|
||||||
|
pub tag_not_selected_sprite_path: String,
|
||||||
|
pub tag_selected_sprite_path: String,
|
||||||
|
pub weapon_prefab_path: String,
|
||||||
|
pub up_list: Vec<i32>,
|
||||||
|
pub show_id_list: Vec<i32>,
|
||||||
|
pub preview_id_list: Vec<i32>,
|
||||||
|
}
|
11
wicked-waifus-data/src/guide_tutorial.rs
Normal file
11
wicked-waifus-data/src/guide_tutorial.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct GuideTutorialData {
|
||||||
|
pub id: i32,
|
||||||
|
pub tutorial_type: i32,
|
||||||
|
pub tutorial_order: i32,
|
||||||
|
pub page_id: Vec<i32>,
|
||||||
|
pub tutorial_tip: bool,
|
||||||
|
}
|
17
wicked-waifus-data/src/level_entity_config.rs
Normal file
17
wicked-waifus-data/src/level_entity_config.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use crate::{ComponentsData, RawVectorData};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct LevelEntityConfigData {
|
||||||
|
pub id: i32,
|
||||||
|
pub map_id: i32,
|
||||||
|
pub entity_id: i64,
|
||||||
|
pub blueprint_type: String,
|
||||||
|
pub name: String,
|
||||||
|
pub in_sleep: bool,
|
||||||
|
pub is_hidden: bool,
|
||||||
|
pub area_id: i32,
|
||||||
|
pub transform: Vec<RawVectorData>,
|
||||||
|
pub components_data: ComponentsData,
|
||||||
|
}
|
194
wicked-waifus-data/src/lib.rs
Normal file
194
wicked-waifus-data/src/lib.rs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
use paste::paste;
|
||||||
|
|
||||||
|
pub use misc_data::*;
|
||||||
|
|
||||||
|
mod misc_data;
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum LoadDataError {
|
||||||
|
#[error("I/O error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("Failed to parse json: {0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! json_data {
|
||||||
|
($($table_type:ident;)*) => {
|
||||||
|
$(paste! {
|
||||||
|
mod [<$table_type:snake>];
|
||||||
|
pub use [<$table_type:snake>]::[<$table_type Data>];
|
||||||
|
})*
|
||||||
|
|
||||||
|
$(paste! {
|
||||||
|
pub mod [<$table_type:snake _data>] {
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
type Data = super::[<$table_type Data>];
|
||||||
|
pub(crate) static TABLE: OnceLock<Vec<Data>> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn iter() -> std::slice::Iter<'static, Data> {
|
||||||
|
TABLE.get().unwrap().iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
|
||||||
|
fn load_json_data(base_path: &str) -> Result<(), LoadDataError> {
|
||||||
|
$(paste! {
|
||||||
|
let file = File::open(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let _ = [<$table_type:snake _data>]::TABLE.set(serde_json::from_reader(reader)?);
|
||||||
|
})*
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! json_hash_table_data {
|
||||||
|
($($table_type:ident, $key_param:expr, $key_type:ty;)*) => {
|
||||||
|
$(paste! {
|
||||||
|
mod [<$table_type:snake>];
|
||||||
|
pub use [<$table_type:snake>]::[<$table_type Data>];
|
||||||
|
})*
|
||||||
|
|
||||||
|
$(paste! {
|
||||||
|
pub mod [<$table_type:snake _data>] {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
pub(crate) type Data = super::[<$table_type Data>];
|
||||||
|
pub(crate) static TABLE: OnceLock<HashMap<$key_type, Data>> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn iter() -> std::collections::hash_map::Iter<'static, $key_type, Data> {
|
||||||
|
TABLE.get().unwrap().iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(k: &$key_type) -> Option<&Data> {
|
||||||
|
TABLE.get().unwrap().get(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})*
|
||||||
|
|
||||||
|
fn load_json_hash_table_data(base_path: &str) -> Result<(), LoadDataError> {
|
||||||
|
$(paste! {
|
||||||
|
let file = File::open(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let _ = [<$table_type:snake _data>]::TABLE.set(
|
||||||
|
serde_json::from_reader::<BufReader<File>, Vec<[<$table_type:snake _data>]::Data>>(reader)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|element| (element.$key_param, element))
|
||||||
|
.collect::<std::collections::HashMap<_, _>>()
|
||||||
|
);
|
||||||
|
})*
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_all_json_data(base_path: &str) -> Result<(), LoadDataError> {
|
||||||
|
load_json_data(base_path)?;
|
||||||
|
load_json_hash_table_data(base_path)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
json_data! {
|
||||||
|
AdventureTask;
|
||||||
|
Area;
|
||||||
|
BaseProperty;
|
||||||
|
ExploreTools;
|
||||||
|
FunctionCondition;
|
||||||
|
Gacha;
|
||||||
|
GachaPool;
|
||||||
|
GachaViewInfo;
|
||||||
|
GuideTutorial;
|
||||||
|
InstanceDungeon;
|
||||||
|
LordGym;
|
||||||
|
RoleInfo;
|
||||||
|
Teleporter;
|
||||||
|
WeaponConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_hash_table_data! {
|
||||||
|
DragonPool, id, i32;
|
||||||
|
LevelEntityConfig, entity_id, i64;
|
||||||
|
// TemplateConfig, blueprint_type, String;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod textmap;
|
||||||
|
|
||||||
|
pub mod text_map_data {
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use crate::LoadDataError;
|
||||||
|
use crate::textmap::TextMapData;
|
||||||
|
use crate::gacha_view_info_data;
|
||||||
|
|
||||||
|
static EMPTY: OnceLock<HashMap<String, String>> = OnceLock::new();
|
||||||
|
static TABLE: OnceLock<HashMap<String, HashMap<String, String>>> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn load_textmaps(base_path: &str) -> Result<(), LoadDataError> {
|
||||||
|
// TODO: Ideally we would expose a function here to allow other components to add to the
|
||||||
|
// filter, since right now only gacha uses it, we can do this
|
||||||
|
let filters = get_filters();
|
||||||
|
let languages = std::fs::read_dir(base_path)?
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| entry.path().is_dir())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut result: HashMap<String, HashMap<String, String>> = HashMap::new();
|
||||||
|
for language in languages {
|
||||||
|
let lang_id = language.file_name().to_str().unwrap().to_string();
|
||||||
|
let file = File::open(&format!("{base_path}/{lang_id}/multi_text/MultiText.json"))?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
result.insert(
|
||||||
|
lang_id,
|
||||||
|
serde_json::from_reader::<BufReader<File>, Vec<TextMapData>>(reader)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|element| filters.contains(&element.id))
|
||||||
|
.map(|element| (element.id, element.content))
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let _ = TABLE.set(result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_textmap(language: i32) -> &'static HashMap<String, String> {
|
||||||
|
let (text_code, _audio_code) = get_language_from_i32(language);
|
||||||
|
TABLE.get_or_init(|| HashMap::new())
|
||||||
|
.get(text_code)
|
||||||
|
.unwrap_or(EMPTY.get_or_init(|| HashMap::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_language_from_i32(language: i32) -> (&'static str, &'static str) {
|
||||||
|
match language {
|
||||||
|
0 => ("zh-Hans", "zh"),
|
||||||
|
1 => ("en", "en"),
|
||||||
|
2 => ("ja", "ja"),
|
||||||
|
3 => ("ko", "ko"),
|
||||||
|
4 => ("ru", "en"),
|
||||||
|
5 => ("zh-Hant", "zh"),
|
||||||
|
6 => ("de", "en"),
|
||||||
|
7 => ("es", "en"),
|
||||||
|
8 => ("pt", "en"),
|
||||||
|
9 => ("id", "en"),
|
||||||
|
10 => ("fr", "en"),
|
||||||
|
11 => ("vi", "en"),
|
||||||
|
12 => ("th", "en"),
|
||||||
|
_ => ("en", "en"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_filters() -> HashSet<String> {
|
||||||
|
let mut filters = HashSet::new();
|
||||||
|
for gacha_view_info in gacha_view_info_data::iter() {
|
||||||
|
filters.insert(gacha_view_info.summary_title.clone());
|
||||||
|
filters.insert(gacha_view_info.summary_describe.clone());
|
||||||
|
}
|
||||||
|
filters
|
||||||
|
}
|
||||||
|
}
|
20
wicked-waifus-data/src/lord_gym.rs
Normal file
20
wicked-waifus-data/src/lord_gym.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct LordGymData {
|
||||||
|
pub id: i32,
|
||||||
|
pub difficulty: i32,
|
||||||
|
pub reward_id: i32,
|
||||||
|
pub play_id: i32,
|
||||||
|
pub gym_title: String,
|
||||||
|
pub icon_path: String,
|
||||||
|
pub play_description: String,
|
||||||
|
pub help_id: i32,
|
||||||
|
pub monster_list: Vec<i32>,
|
||||||
|
pub monster_level: i32,
|
||||||
|
pub lock_con: i32,
|
||||||
|
pub lock_description: String,
|
||||||
|
pub filter_type: i32,
|
||||||
|
pub is_debug: bool,
|
||||||
|
}
|
252
wicked-waifus-data/src/misc_data.rs
Normal file
252
wicked-waifus-data/src/misc_data.rs
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_repr::Deserialize_repr;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct PropValueData {
|
||||||
|
pub id: i32,
|
||||||
|
pub value: f32,
|
||||||
|
pub is_ratio: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct VectorData([f32; 3]);
|
||||||
|
|
||||||
|
impl VectorData {
|
||||||
|
pub fn get_x(&self) -> f32 {
|
||||||
|
self.0[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_y(&self) -> f32 {
|
||||||
|
self.0[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_z(&self) -> f32 {
|
||||||
|
self.0[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct RawVectorData {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawVectorData {
|
||||||
|
pub fn get_x(&self) -> f32 {
|
||||||
|
self.x
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_y(&self) -> f32 {
|
||||||
|
self.y
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_z(&self) -> f32 {
|
||||||
|
self.z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct EntranceEntityData {
|
||||||
|
pub dungeon_id: i32,
|
||||||
|
pub entrance_entity_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize_repr, PartialEq, Debug, Copy, Clone)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum GachaViewTypeInfoId {
|
||||||
|
NoviceConvene = 1,
|
||||||
|
FeaturedResonatorConvene = 2,
|
||||||
|
FeaturedWeaponConvene = 3,
|
||||||
|
StandardResonatorConvene = 4,
|
||||||
|
StandardWeaponConvene = 5,
|
||||||
|
BeginnersChoiceConvene = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct Category {
|
||||||
|
pub main_type: Option<String>,
|
||||||
|
pub monster_match_type: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct ScanFunction {
|
||||||
|
pub scan_id: Option<i32>,
|
||||||
|
pub is_concealed: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct WorldLevelBonusType {
|
||||||
|
pub r#type: Option<i32>,
|
||||||
|
pub world_level_bonus_id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct BaseInfoComponent {
|
||||||
|
pub tid_name: Option<String>,
|
||||||
|
pub category: Option<Category>,
|
||||||
|
pub camp: Option<i32>,
|
||||||
|
pub online_interact_type: Option<i32>,
|
||||||
|
pub scan_function: Option<ScanFunction>,
|
||||||
|
pub aoi_layer: Option<i32>,
|
||||||
|
pub entity_property_id: Option<i32>,
|
||||||
|
pub focus_priority: Option<i32>,
|
||||||
|
pub aoi_zradius: Option<i32>,
|
||||||
|
// TODO: Add more
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct AiComponent {
|
||||||
|
pub disabled: Option<bool>,
|
||||||
|
pub ai_id: Option<i32>,
|
||||||
|
// TODO: Add more
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct AttributeComponent {
|
||||||
|
pub property_id: Option<i32>,
|
||||||
|
pub level: Option<i32>,
|
||||||
|
pub world_level_bonus_type: Option<WorldLevelBonusType>,
|
||||||
|
pub rage_mode_id: Option<i32>,
|
||||||
|
pub hardness_mode_id: Option<i32>,
|
||||||
|
pub monster_prop_extra_rate_id: Option<i32>,
|
||||||
|
pub world_level_bonus_id: Option<i32>,
|
||||||
|
pub fight_music: Option<String>,
|
||||||
|
// TODO: Add more
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct TeleportPosition {
|
||||||
|
pub x: Option<f32>,
|
||||||
|
pub y: Option<f32>,
|
||||||
|
pub z: Option<f32>,
|
||||||
|
pub a: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct TeleportComponent {
|
||||||
|
pub disabled: Option<bool>,
|
||||||
|
pub teleporter_id: Option<i32>,
|
||||||
|
#[serde(rename = "TeleportPos")]
|
||||||
|
pub teleport_position: Option<TeleportPosition>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct ComponentsData {
|
||||||
|
pub base_info_component: Option<BaseInfoComponent>,
|
||||||
|
pub ai_component: Option<AiComponent>,
|
||||||
|
pub attribute_component: Option<AttributeComponent>,
|
||||||
|
pub teleport_component: Option<TeleportComponent>,
|
||||||
|
// TODO: Implement this ones
|
||||||
|
// pub scene_actor_ref_component: Option<serde_json::Value>,
|
||||||
|
// pub effect_area_component: Option<serde_json::Value>,
|
||||||
|
// pub entity_state_component: Option<serde_json::Value>,
|
||||||
|
// pub condition_listener_component: Option<serde_json::Value>,
|
||||||
|
// pub interact_component: Option<serde_json::Value>,
|
||||||
|
// pub npc_perform_component: Option<serde_json::Value>,
|
||||||
|
// pub var_component: Option<serde_json::Value>,
|
||||||
|
// pub entity_visible_component: Option<serde_json::Value>,
|
||||||
|
// pub level_ai_component: Option<serde_json::Value>,
|
||||||
|
// pub trigger_component: Option<serde_json::Value>,
|
||||||
|
// pub range_component: Option<serde_json::Value>,
|
||||||
|
// pub spline_component: Option<serde_json::Value>,
|
||||||
|
// pub bubble_component: Option<serde_json::Value>,
|
||||||
|
// pub reward_component: Option<serde_json::Value>,
|
||||||
|
// pub refresh_component: Option<serde_json::Value>,
|
||||||
|
// pub passerby_npc_spawn_component: Option<serde_json::Value>,
|
||||||
|
// pub vision_capture_component: Option<serde_json::Value>,
|
||||||
|
// pub refresh_group_component: Option<serde_json::Value>,
|
||||||
|
// pub collect_component: Option<serde_json::Value>,
|
||||||
|
// pub target_gear_component: Option<serde_json::Value>,
|
||||||
|
// pub fight_interact_component: Option<serde_json::Value>,
|
||||||
|
// pub guide_line_creator_component: Option<serde_json::Value>,
|
||||||
|
// pub photo_target_component: Option<serde_json::Value>,
|
||||||
|
// pub model_component: Option<serde_json::Value>,
|
||||||
|
// pub entity_group_component: Option<serde_json::Value>,
|
||||||
|
// pub scene_item_life_cycle_component: Option<serde_json::Value>,
|
||||||
|
// pub entity_state_audio_component: Option<serde_json::Value>,
|
||||||
|
// pub animal_component: Option<serde_json::Value>,
|
||||||
|
// pub monster_component: Option<serde_json::Value>,
|
||||||
|
// pub nearby_tracking_component: Option<serde_json::Value>,
|
||||||
|
// pub follow_track_component: Option<serde_json::Value>,
|
||||||
|
// pub jigsaw_foundation: Option<serde_json::Value>,
|
||||||
|
// pub treasure_box_component: Option<serde_json::Value>,
|
||||||
|
// pub hook_lock_point: Option<serde_json::Value>,
|
||||||
|
// pub explore_skill_interact_component: Option<serde_json::Value>,
|
||||||
|
// pub attach_target_component: Option<serde_json::Value>,
|
||||||
|
// pub target_gear_group_component: Option<serde_json::Value>,
|
||||||
|
// pub spawn_monster_component: Option<serde_json::Value>,
|
||||||
|
// pub skybox_component: Option<serde_json::Value>,
|
||||||
|
// pub destructible_item: Option<serde_json::Value>,
|
||||||
|
// pub fan_component: Option<serde_json::Value>,
|
||||||
|
// pub state_hint_component: Option<serde_json::Value>,
|
||||||
|
// pub buff_consumer_component: Option<serde_json::Value>,
|
||||||
|
// pub reset_entities_pos_component: Option<serde_json::Value>,
|
||||||
|
// pub group_ai_component: Option<serde_json::Value>,
|
||||||
|
// pub pulling_object_foundation: Option<serde_json::Value>,
|
||||||
|
// pub lift_component: Option<serde_json::Value>,
|
||||||
|
// pub scene_item_movement_component: Option<serde_json::Value>,
|
||||||
|
// pub reset_self_pos_component: Option<serde_json::Value>,
|
||||||
|
// pub jigsaw_item: Option<serde_json::Value>,
|
||||||
|
// pub level_play_component: Option<serde_json::Value>,
|
||||||
|
// pub interact_gear_component: Option<serde_json::Value>,
|
||||||
|
// pub ai_gear_strategy_component: Option<serde_json::Value>,
|
||||||
|
// pub pick_interact_component: Option<serde_json::Value>,
|
||||||
|
// pub level_sequence_frame_event_component: Option<serde_json::Value>,
|
||||||
|
// pub air_wall_spawner_component: Option<serde_json::Value>,
|
||||||
|
// pub progress_bar_control_component: Option<serde_json::Value>,
|
||||||
|
// pub batch_bullet_caster_component: Option<serde_json::Value>,
|
||||||
|
// pub client_trigger_component: Option<serde_json::Value>,
|
||||||
|
// pub enrichment_area_component: Option<serde_json::Value>,
|
||||||
|
// pub vehicle_component: Option<serde_json::Value>,
|
||||||
|
// pub item_foundation2: Option<serde_json::Value>,
|
||||||
|
// pub tele_control2: Option<serde_json::Value>,
|
||||||
|
// pub interact_audio_component: Option<serde_json::Value>,
|
||||||
|
// pub level_qte_component: Option<serde_json::Value>,
|
||||||
|
// pub resurrection_component: Option<serde_json::Value>,
|
||||||
|
// pub ai_alert_notify_component: Option<serde_json::Value>,
|
||||||
|
// pub trample_component: Option<serde_json::Value>,
|
||||||
|
// pub dungeon_entry_component: Option<serde_json::Value>,
|
||||||
|
// pub level_prefab_perform_component: Option<serde_json::Value>,
|
||||||
|
// pub render_specified_range_component: Option<serde_json::Value>,
|
||||||
|
// pub walking_pattern_component: Option<serde_json::Value>,
|
||||||
|
// pub no_render_portal_component: Option<serde_json::Value>,
|
||||||
|
// pub adsorb_component: Option<serde_json::Value>,
|
||||||
|
// pub beam_cast_component: Option<serde_json::Value>,
|
||||||
|
// pub beam_receive_component: Option<serde_json::Value>,
|
||||||
|
// pub timeline_track_control_component: Option<serde_json::Value>,
|
||||||
|
// pub scene_bullet_component: Option<serde_json::Value>,
|
||||||
|
// pub edit_custom_aoi_component: Option<serde_json::Value>,
|
||||||
|
// pub combat_component: Option<serde_json::Value>,
|
||||||
|
// pub location_safety_component: Option<serde_json::Value>,
|
||||||
|
// pub turntable_control_component: Option<serde_json::Value>,
|
||||||
|
// pub scene_item_ai_component: Option<serde_json::Value>,
|
||||||
|
// pub buff_producer_component: Option<serde_json::Value>,
|
||||||
|
// pub portal_component: Option<serde_json::Value>,
|
||||||
|
// pub inhalation_ability_component: Option<serde_json::Value>,
|
||||||
|
// pub inhaled_item_component: Option<serde_json::Value>,
|
||||||
|
// pub monster_gacha_base_component: Option<serde_json::Value>,
|
||||||
|
// pub monster_gacha_item_component: Option<serde_json::Value>,
|
||||||
|
// pub time_stop_component: Option<serde_json::Value>,
|
||||||
|
// pub hit_component: Option<serde_json::Value>,
|
||||||
|
// pub levitate_magnet_component: Option<serde_json::Value>,
|
||||||
|
// pub rebound_component: Option<serde_json::Value>,
|
||||||
|
// pub rotator_component2: Option<serde_json::Value>,
|
||||||
|
// pub conveyor_belt_component: Option<serde_json::Value>,
|
||||||
|
// pub dynamic_portal_creator_component: Option<serde_json::Value>,
|
||||||
|
// pub connector_component: Option<serde_json::Value>,
|
||||||
|
// pub monitor_component: Option<serde_json::Value>,
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ pub struct RoleInfoData {
|
||||||
pub priority: i32,
|
pub priority: i32,
|
||||||
pub show_property: Vec<i32>,
|
pub show_property: Vec<i32>,
|
||||||
pub element_id: i32,
|
pub element_id: i32,
|
||||||
|
pub skin_id: i32,
|
||||||
pub spillover_item: HashMap<i32, i32>,
|
pub spillover_item: HashMap<i32, i32>,
|
||||||
pub breach_model: i32,
|
pub breach_model: i32,
|
||||||
pub special_energy_bar_id: i32,
|
pub special_energy_bar_id: i32,
|
17
wicked-waifus-data/src/teleporter.rs
Normal file
17
wicked-waifus-data/src/teleporter.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct TeleporterData {
|
||||||
|
pub id: i32,
|
||||||
|
pub map_id: i32,
|
||||||
|
pub object_id: i32,
|
||||||
|
pub area_id: i32,
|
||||||
|
pub fog_id: i32,
|
||||||
|
pub r#type: i32,
|
||||||
|
pub teleport_entity_config_id: i64,
|
||||||
|
pub plot: String,
|
||||||
|
pub after_network_action: i32,
|
||||||
|
pub show_world_map: bool,
|
||||||
|
}
|
||||||
|
|
11
wicked-waifus-data/src/template_config.rs
Normal file
11
wicked-waifus-data/src/template_config.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use crate::ComponentsData;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct TemplateConfigData {
|
||||||
|
pub id: i32,
|
||||||
|
pub blueprint_type: String,
|
||||||
|
pub name: String,
|
||||||
|
pub components_data: ComponentsData,
|
||||||
|
}
|
8
wicked-waifus-data/src/textmap.rs
Normal file
8
wicked-waifus-data/src/textmap.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct TextMapData {
|
||||||
|
pub id: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "shorekeeper-database"
|
name = "wicked-waifus-database"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
34
wicked-waifus-game-server/Cargo.toml
Normal file
34
wicked-waifus-game-server/Cargo.toml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[package]
|
||||||
|
name = "wicked-waifus-game-server"
|
||||||
|
edition = "2021"
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Framework
|
||||||
|
tokio.workspace = true
|
||||||
|
|
||||||
|
# Serialization
|
||||||
|
serde.workspace = true
|
||||||
|
|
||||||
|
# Util
|
||||||
|
uuid = { version = "1.11.0", features = ["v4"] }
|
||||||
|
rand = "0.9.0-alpha.2"
|
||||||
|
# Used for debug
|
||||||
|
#serde_json = "1.0.135"
|
||||||
|
|
||||||
|
anyhow.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
paste.workspace = true
|
||||||
|
dashmap.workspace = true
|
||||||
|
hex.workspace = true
|
||||||
|
|
||||||
|
# Tracing
|
||||||
|
tracing.workspace = true
|
||||||
|
|
||||||
|
# Internal
|
||||||
|
wicked-waifus-commons.workspace = true
|
||||||
|
wicked-waifus-data.workspace = true
|
||||||
|
wicked-waifus-database.workspace = true
|
||||||
|
wicked-waifus-network.workspace = true
|
||||||
|
wicked-waifus-protocol-internal.workspace = true
|
||||||
|
wicked-waifus-protocol.workspace = true
|
|
@ -11,3 +11,8 @@ addr = "tcp://127.0.0.1:10004"
|
||||||
|
|
||||||
[gateway_end_point]
|
[gateway_end_point]
|
||||||
addr = "tcp://127.0.0.1:10003"
|
addr = "tcp://127.0.0.1:10003"
|
||||||
|
|
||||||
|
[game_server_config]
|
||||||
|
load_textmaps = true
|
||||||
|
# Do not change yet, issues to be solved
|
||||||
|
quadrant_size = 1000000
|
12
wicked-waifus-game-server/scripts/censorshipfix.js
Normal file
12
wicked-waifus-game-server/scripts/censorshipfix.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const UE = require("ue");
|
||||||
|
const { CharacterDitherEffectController } = require("../NewWorld/Character/Common/Component/Effect/CharacterDitherEffectController");
|
||||||
|
|
||||||
|
CharacterDitherEffectController.prototype.SetDitherEffect = function(t, i = 3, s = true) { };
|
||||||
|
|
||||||
|
CharacterDitherEffectController.prototype.EnterAppearEffect = function(t = 1, i = 3, s = true) {
|
||||||
|
this.SetHiddenInGame(false, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
CharacterDitherEffectController.prototype.EnterDisappearEffect = function(t = 1, i = 3, s = true) {
|
||||||
|
this.SetHiddenInGame(true, true);
|
||||||
|
};
|
9
wicked-waifus-game-server/scripts/uidfix.js
Normal file
9
wicked-waifus-game-server/scripts/uidfix.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
setTimeout(() => {
|
||||||
|
const UiManager_1 = require("../Ui/UiManager");
|
||||||
|
const UE = require("ue");
|
||||||
|
const ControllerManagerBase_1 = require("../../Core/Framework/ControllerManagerBase");
|
||||||
|
|
||||||
|
const UiText = UiManager_1.UiManager.GetViewByName("UidView").GetText(0);
|
||||||
|
UiText.SetText("{PLAYER_USERNAME} - Reversed Rooms");
|
||||||
|
UiText.SetColor(UE. Color.FromHex("{SELECTED_COLOR}"));
|
||||||
|
}, 10000);
|
1
wicked-waifus-game-server/scripts/watermask-disable.js
Normal file
1
wicked-waifus-game-server/scripts/watermask-disable.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
require('../Module/WaterMask/WaterMaskController').WaterMaskView.EOo();
|
|
@ -1,7 +1,8 @@
|
||||||
use common::config_util::TomlConfig;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use shorekeeper_database::DatabaseSettings;
|
|
||||||
use shorekeeper_network::config::ServiceEndPoint;
|
use wicked_waifus_commons::config_util::TomlConfig;
|
||||||
|
use wicked_waifus_database::DatabaseSettings;
|
||||||
|
use wicked_waifus_network::config::ServiceEndPoint;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ServiceConfig {
|
pub struct ServiceConfig {
|
||||||
|
@ -9,6 +10,13 @@ pub struct ServiceConfig {
|
||||||
pub database: DatabaseSettings,
|
pub database: DatabaseSettings,
|
||||||
pub service_end_point: ServiceEndPoint,
|
pub service_end_point: ServiceEndPoint,
|
||||||
pub gateway_end_point: ServiceEndPoint,
|
pub gateway_end_point: ServiceEndPoint,
|
||||||
|
pub game_server_config: GameServerConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct GameServerConfig {
|
||||||
|
pub load_textmaps: bool,
|
||||||
|
pub quadrant_size: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TomlConfig for ServiceConfig {
|
impl TomlConfig for ServiceConfig {
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use shorekeeper_network::{config::ServiceEndPoint, ServiceClient, ServiceMessage};
|
use wicked_waifus_network::{config::ServiceEndPoint, ServiceClient, ServiceMessage};
|
||||||
|
|
||||||
static CLIENT: OnceLock<ServiceClient> = OnceLock::new();
|
static CLIENT: OnceLock<ServiceClient> = OnceLock::new();
|
||||||
|
|
65
wicked-waifus-game-server/src/logic/components/attribute.rs
Normal file
65
wicked-waifus-game-server/src/logic/components/attribute.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use wicked_waifus_data::BasePropertyData;
|
||||||
|
use wicked_waifus_protocol::{
|
||||||
|
entity_component_pb::ComponentPb, AttrData, AttributeComponentPb, EAttributeType,
|
||||||
|
EntityComponentPb, LivingStatus,
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::logic::ecs::component::Component;
|
||||||
|
use crate::logic::utils::load_role_info::attribute_from_data;
|
||||||
|
|
||||||
|
pub struct Attribute {
|
||||||
|
pub attr_map: HashMap<EAttributeType, (i32, i32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Attribute {
|
||||||
|
fn set_pb_data(&self, pb: &mut wicked_waifus_protocol::EntityPb) {
|
||||||
|
pb.living_status = (if self.is_alive() {
|
||||||
|
LivingStatus::Alive
|
||||||
|
} else {
|
||||||
|
LivingStatus::Dead
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
pb.component_pbs.push(EntityComponentPb {
|
||||||
|
component_pb: Some(ComponentPb::AttributeComponent(
|
||||||
|
self.build_entity_attribute(),
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Attribute {
|
||||||
|
pub fn is_alive(&self) -> bool {
|
||||||
|
self.attr_map
|
||||||
|
.get(&EAttributeType::Life)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0
|
||||||
|
> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_data(base_property: &BasePropertyData) -> Self {
|
||||||
|
Self {
|
||||||
|
attr_map: attribute_from_data(base_property),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn build_entity_attribute(&self) -> AttributeComponentPb {
|
||||||
|
AttributeComponentPb {
|
||||||
|
attr_data: self
|
||||||
|
.attr_map
|
||||||
|
.iter()
|
||||||
|
.map(|(ty, (base, incr))| AttrData {
|
||||||
|
attribute_type: (*ty).into(),
|
||||||
|
current_value: *base,
|
||||||
|
value_increment: *incr,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
hardness_mode_id: 0,
|
||||||
|
rage_mode_id: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
wicked-waifus-game-server/src/logic/components/autonomous.rs
Normal file
19
wicked-waifus-game-server/src/logic/components/autonomous.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use wicked_waifus_protocol::{AutonomousComponentPb, EntityComponentPb};
|
||||||
|
use wicked_waifus_protocol::entity_component_pb::ComponentPb;
|
||||||
|
|
||||||
|
use crate::logic::ecs::component::Component;
|
||||||
|
|
||||||
|
pub struct Autonomous {
|
||||||
|
pub autonomous_id: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Autonomous {
|
||||||
|
fn set_pb_data(&self, pb: &mut wicked_waifus_protocol::EntityPb) {
|
||||||
|
pb.component_pbs.push(EntityComponentPb {
|
||||||
|
component_pb: Some(ComponentPb::AutonomousComponentPb(AutonomousComponentPb {
|
||||||
|
autonomous_id: self.autonomous_id,
|
||||||
|
ji: vec![],
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue