Compare commits

...

19 commits

Author SHA1 Message Date
a6501d8f26
Merge branch 'master' of https://git.xeondev.com/WutheringSlaves/wicked-waifus-rs 2025-03-09 10:18:02 +01:00
ab868a158a 2.2.2 (#3)
Ongoing changes for 2.2.2

Reviewed-on: WutheringSlaves/wicked-waifus-rs#3
2025-03-09 09:10:08 +00:00
c04f065e44
multimap support 2025-02-08 05:40:00 +01:00
c0b872ede4
Urgent fix 2025-01-27 14:50:38 +01:00
d52c04b88a
Fix dependency bug, updated cargo 2025-01-22 04:24:23 +01:00
66231d552f 2.1.0 functional (#2)
Reviewed-on: WutheringSlaves/wicked-waifus-rs#2
2025-01-22 02:12:44 +00:00
b316e5f0ac
Fix 2024-11-03 21:38:32 +01:00
4e7e7bbba6
Fix 2024-11-03 21:22:23 +01:00
90179f1001
Quadrant System beta test started 2024-11-03 20:44:02 +01:00
46e57fe2dc
Start pushing quadrant 2024-11-03 03:12:05 +01:00
432c68ff74
Update docker 2024-11-03 00:27:17 +01:00
be69d00e2c
Update docker 2024-11-03 00:21:51 +01:00
44e7ec1527
Log improvement 2024-11-01 20:27:31 +01:00
af97b37e51
minor rename 2024-11-01 20:15:44 +01:00
3c9e3dc907
Update config server, allows multi version support 2024-11-01 20:13:29 +01:00
d909cf1aa3
Update banner and proto 2024-10-25 11:33:23 +02:00
c6b66eacb4
[PATCH] feat: implement formation and switch roles functionality
- Add support for multi-character formations and role switching
- Refactor entity management system to handle multiple entities per map
- Update attribute component to use a map-based approach for properties
- Implement formation-related handlers and notifications
- Add world entity management per map instance
- Update player save/load logic to handle formations
- Add visibility controls for active character display
2024-10-24 13:15:50 +02:00
85fbc933ba
1.4 Walking simulator, rest of features incoming during weekend.
Any question from anyone trying to run from this commit will be ignored and blocked.
2024-10-19 14:57:25 +02:00
395f249207
1.4.2 protos 2024-10-19 03:46:13 +02:00
316 changed files with 16509 additions and 200755 deletions

6
.gitignore vendored
View file

@ -1,7 +1,11 @@
.idea
/target
/hotpatch.toml
/configserver.toml
/loginserver.toml
/gateway.toml
/gameserver.toml
/shorekeeper-protocol/generated
/wicked-waifus-protocol-internal/generated
/wicked-waifus-protocol/generated
/data/assets/config-server
/data/assets/game-data

0
.gitmodules vendored Normal file
View file

1819
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
[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-asset-updater", "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"
[workspace.package]
@ -8,6 +8,7 @@ version = "0.1.0"
[workspace.dependencies]
# Framework
tokio = { version = "1.39.3", features = ["full"] }
tower-http = { version = "0.6.1", features = ["fs", "trace"] }
axum = "0.7.5"
axum-server = "0.7.1"
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"
cbc = { version = "0.1.2", features = ["alloc"] }
cipher = "0.4.4"
crc32fast = "1.4.2"
rand = "0.8.5"
rsa = { version = "0.9.6", features = ["pem"] }
# Serialization
serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.128"
serde_repr = "0.1.19"
toml = "0.8.19"
prost = "0.13.2"
prost-build = "0.13.2"
@ -37,22 +40,24 @@ rbase64 = "2.0.3"
dashmap = "6.1.0"
hex = "0.4.3"
byteorder = "1.5.0"
crc32fast = "1.4.2"
# Tracing
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
# Internal
kcp = { path = "gateway-server/kcp" }
common = { path = "common/" }
shorekeeper-http = { path = "shorekeeper-http/" }
shorekeeper-data = { path = "shorekeeper-data/" }
shorekeeper-database = { path = "shorekeeper-database/" }
shorekeeper-network = { path = "shorekeeper-network/" }
shorekeeper-protocol = { path = "shorekeeper-protocol/" }
shorekeeper-protocol-derive = { path = "shorekeeper-protocol/shorekeeper-protocol-derive" }
shorekeeper-protokey = { path = "shorekeeper-protokey/" }
kcp = { path = "wicked-waifus-gateway-server/kcp" }
wicked-waifus-asset-updater = { path = "wicked-waifus-asset-updater" }
wicked-waifus-commons = { path = "wicked-waifus-commons" }
wicked-waifus-http = { path = "wicked-waifus-http" }
wicked-waifus-data = { path = "wicked-waifus-data" }
wicked-waifus-database = { path = "wicked-waifus-database" }
wicked-waifus-network = { path = "wicked-waifus-network" }
wicked-waifus-protocol-internal = { path = "wicked-waifus-protocol-internal" }
wicked-waifus-protokey = { path = "wicked-waifus-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]
strip = true # Automatically strip symbols from the binary.

View file

@ -1,4 +1,4 @@
FROM rust:1.81-alpine3.20
FROM rust:1.82-alpine3.20
WORKDIR /app
COPY . .

View file

@ -2,5 +2,5 @@ FROM alpine:3.20
ARG MICROSERVICE
WORKDIR /app
COPY --from=shorekeeper-builder:1.3.0-SNAPSHOT /app/target/release/$MICROSERVICE ./service
COPY --from=wicked-waifus-builder:2.1.0-SNAPSHOT /app/target/release/$MICROSERVICE ./service
CMD ["./service"]

View file

@ -1,9 +1,12 @@
# Shorekeeper
# Wicked Waifus
![Screenshot](https://git.xeondev.com/Shorekeeper/Shorekeeper/raw/branch/master/screenshot.png)
![Screenshot](screenshot.png)
## 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
#### Requirements
@ -15,13 +18,13 @@
##### a) building from sources
```sh
git clone https://git.xeondev.com/Shorekeeper/Shorekeeper.git
cd Shorekeeper
cargo run --bin config-server
cargo run --bin hotpatch-server
cargo run --bin login-server
cargo run --bin gateway-server
cargo run --bin game-server
git clone --recursive https://git.xeondev.com/wickedwaifus/wicked-waifus-rs.git
cd wicked-waifus-rs
cargo run --bin wicked-waifus-config-server
cargo run --bin wicked-waifus-hotpatch-server
cargo run --bin wicked-waifus-login-server
cargo run --bin wicked-waifus-gateway-server
cargo run --bin wicked-waifus-game-server
```
##### b) building from sources(docker edition)
@ -40,9 +43,9 @@ docker compose up -d
```
##### c) using pre-built binaries
Navigate to the [Releases](https://git.xeondev.com/Shorekeeper/Shorekeeper/releases)
Navigate to the [Releases](https://git.xeondev.com/wickedwaifus/wicked-waifus-rs/releases)
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.
@ -57,15 +60,15 @@ You have to specify credentials for **PostgreSQL**<br>
host = "localhost:5432"
user_name = "postgres"
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
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
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
[Visit our discord](https://discord.gg/reversedrooms) if you have any questions/issues

View file

@ -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
}
}

View file

@ -1,64 +0,0 @@
{
"default": {
"CdnUrl": [
{
"url": "https://cdn-huoshan-cn-mc.aki-game.com/prod/client/",
"weight": "2323"
},
{
"url": "https://cdn-qiniu-cn-mc.aki-game.com/prod/client/",
"weight": "0"
},
{
"url": "https://cdn-qcloud-cn-mc.aki-game.com/prod/client/",
"weight": "5443"
},
{
"url": "https://cdn-aliyun-cn-mc.aki-game.com/prod/client/",
"weight": "0"
}
],
"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
}
}

View file

@ -1,64 +0,0 @@
{
"default": {
"CdnUrl": [
{
"url": "https://cdn-huoshan-cn-mc.aki-game.com/prod/client/",
"weight": "0"
},
{
"url": "https://cdn-qiniu-cn-mc.aki-game.com/prod/client/",
"weight": "4181"
},
{
"url": "https://cdn-qcloud-cn-mc.aki-game.com/prod/client/",
"weight": "2937"
},
{
"url": "https://cdn-aliyun-cn-mc.aki-game.com/prod/client/",
"weight": "0"
}
],
"SecondaryUrl": [],
"PriceRatio": 1,
"SpeedRatio": 1,
"GachaUrl": {
"GachaRecord": "https://aki-gm-resources.aki-game.com",
"GachaPoolDetail": "https://aki-gm-resources.aki-game.com"
},
"LogReport": {
"name": "aki-upload-log-1319073642",
"region": "ap-shanghai"
},
"PackageUpdateDescUrl": {
"MainUrl": "https://aki-gm-resources-back.aki-game.com/force_update/UpdateDesc.html",
"SubUrl": "https://aki-gm-resources-back-qiniu.aki-game.com/force_update/UpdateDesc.html"
},
"PackageUpdateUrl": {
"MainUrl": "https://aki-gm-resources-back.aki-game.com/force_update/UpdateJs.html",
"SubUrl": "https://aki-gm-resources-back-qiniu.aki-game.com/force_update/UpdateJs.html"
},
"TDCfg": {
"AppID": "7be70c9c181f4fa8bc013e9b89d2b63b",
"URL": "http://cn-datareceiver.aki-game.com"
},
"GmOpen": false,
"IosAuditFirstDownloadTip": false,
"NoticeUrl": "https://aki-gm-resources-back.aki-game.com",
"MixUri": "e66lKLx7RnUf9QlkMCGaW0jdyjRV1weo",
"ResUri": "e66lKLx7RnUf9QlkMCGaW0jdyjRV1weo",
"LoginServers": [
{
"id": "76402e5b20be2c39f095a152090afddc",
"ip": "https://login-sh.aki-game.com",
"name": "鸣潮"
}
],
"PrivateServers": {
"enable": false,
"serverUrl": ""
}
},
"p1": {
"IosAuditFirstDownloadTip": false
}
}

View file

@ -1,115 +0,0 @@
{
"default": {
"CdnUrl": [
{
"url": "https://cdn-aws-hw-mc.aki-game.net/prod/client/",
"weight": "829"
},
{
"url": "https://cdn-qcloud-hw-mc.aki-game.net/prod/client/",
"weight": "0"
},
{
"url": "https://cdn-akamai-hw-mc.aki-game.net/prod/client/",
"weight": "262"
},
{
"url": "https://cdn-huoshan-hw-mc.aki-game.net/prod/client/",
"weight": "829"
}
],
"SecondaryUrl": [],
"PriceRatio": 1,
"SpeedRatio": 1,
"GachaUrl": {
"GachaRecord": "https://aki-gm-resources-oversea.aki-game.net",
"GachaPoolDetail": "https://aki-gm-resources-oversea.aki-game.net"
},
"LogReport": {
"name": "hw-aki-upload-log-1319073642",
"region": "ap-singapore"
},
"PackageUpdateDescUrl": {
"MainUrl": "https://aki-gm-resources-back.aki-game.net/force_update/UpdateDesc.html",
"SubUrl": "https://aki-gm-resources-back-aws.aki-game.net/force_update/UpdateDesc.html"
},
"PackageUpdateUrl": {
"MainUrl": "https://aki-gm-resources-back.aki-game.net/force_update/UpdateJs.html",
"SubUrl": "https://aki-gm-resources-back-aws.aki-game.net/force_update/UpdateJs.html"
},
"GARUrl": "https://gar-service.aki-game.net",
"TDCfg": {
"URL": "http://us-datareceiver.aki-game.net",
"AppID": "bf3f44edc6cf43c582e347ba660876c0"
},
"GmOpen": false,
"IosAuditFirstDownloadTip": false,
"NoticeUrl": "https://aki-gm-resources-back.aki-game.net",
"MixUri": "u4S3uXaif7gNKnNGnbZ6WnW67vWFmQgd",
"ResUri": "u4S3uXaif7gNKnNGnbZ6WnW67vWFmQgd",
"LoginServers": [
{
"PingUrl": "49.51.79.239",
"Region": "America",
"TDCfg": {
"AppID": "12c7c870b57541caa2093e275212f083",
"URL": "http://us-datareceiver.aki-game.net"
},
"id": "591d6af3a3090d8ea00d8f86cf6d7501",
"ip": "https://login-us.aki-game.net",
"name": "America"
},
{
"PingUrl": "124.156.236.245",
"Region": "Asia",
"TDCfg": {
"AppID": "dc2e8c02609546e89d78c2eb2974becb",
"URL": "http://jp-datareceiver.aki-game.net"
},
"id": "86d52186155b148b5c138ceb41be9650",
"ip": "https://login-jp.aki-game.net",
"name": "Asia"
},
{
"PingUrl": "49.51.129.88",
"Region": "Europe",
"TDCfg": {
"AppID": "0fb77e4801db41a7a1fb1a3431625fbd",
"URL": "http://eu-datareceiver.aki-game.net"
},
"id": "6eb2a235b30d05efd77bedb5cf60999e",
"ip": "https://login-eu.aki-game.net",
"name": "Europe"
},
{
"PingUrl": "43.129.150.38",
"Region": "HMT",
"TDCfg": {
"AppID": "5ccd297c928d405a9e6b18a122f4d48d",
"URL": "http://hk-datareceiver.aki-game.net"
},
"id": "919752ae5ea09c1ced910dd668a63ffb",
"ip": "https://login-hk.aki-game.net",
"name": "HMT(HK, MO, TW)"
},
{
"PingUrl": "101.33.100.22",
"Region": "SEA",
"TDCfg": {
"AppID": "dffd1143291b4877a8866fb24c623038",
"URL": "http://as-datareceiver.aki-game.net"
},
"id": "10cd7254d57e58ae560b15d51e34b4c8",
"ip": "https://login-sg.aki-game.net",
"name": "SEA"
}
],
"PrivateServers": {
"enable": false,
"serverUrl": ""
}
},
"p1": {
"IosAuditFirstDownloadTip": false
}
}

View file

@ -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

View file

@ -1,246 +0,0 @@
[
{
"PhantomSkillId": 1004,
"Name": "ExploreTools_1004_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1004_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT3.SP_IconT3",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT3.SP_IconT3",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT3.SP_IconT3",
"SortId": 3,
"AutoFill": true,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1001,
"Name": "ExploreTools_1001_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1001_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT6.SP_IconT6",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT6.SP_IconT6",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT6.SP_IconT6",
"SortId": 1,
"AutoFill": true,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1003,
"Name": "ExploreTools_1003_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1003_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT1.SP_IconT1",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT1.SP_IconT1",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT1.SP_IconT1",
"SortId": 4,
"AutoFill": true,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1005,
"Name": "ExploreTools_1005_Name",
"SkillType": 2,
"CurrentSkillInfo": "ExploreTools_1005_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT2.SP_IconT2",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT2.SP_IconT2",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT2.SP_IconT2",
"SortId": 5,
"AutoFill": false,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1006,
"Name": "ExploreTools_1006_Name",
"SkillType": 2,
"CurrentSkillInfo": "ExploreTools_1006_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT5.SP_IconT5",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT5.SP_IconT5",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT5.SP_IconT5",
"SortId": 6,
"AutoFill": false,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1007,
"Name": "ExploreTools_1007_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1007_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT29.SP_IconT29",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT29.SP_IconT29",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT29.SP_IconT29",
"SortId": 7,
"AutoFill": false,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1009,
"Name": "ExploreTools_1009_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1009_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"SortId": 9,
"AutoFill": false,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": true,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 3001,
"Name": "ExploreTools_1009_Name",
"SkillType": 3,
"CurrentSkillInfo": "ExploreTools_1009_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"SortId": 10,
"AutoFill": false,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 3002,
"Name": "ExploreTools_1009_Name",
"SkillType": 3,
"CurrentSkillInfo": "ExploreTools_1009_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT9.SP_IconT9",
"BattleViewIcon": "/Game/Aki/UI/UIResources/UiSkillswitching/Atlas/SP_SkillswitchingItemNone.SP_SkillswitchingItemNone",
"SortId": 10,
"AutoFill": false,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1010,
"Name": "ExploreTools_1010_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1010_CurrentSkillInfo",
"HelpId": 30,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT30.SP_IconT30",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT30.SP_IconT30",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT30.SP_IconT30",
"SortId": 11,
"AutoFill": true,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {
"10808": 1
},
"Authorization": {},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1011,
"Name": "ExploreTools_1011_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1011_CurrentSkillInfo",
"HelpId": 28,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT32.SP_IconT32",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT32.SP_IconT32",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT32.SP_IconT32",
"SortId": 12,
"AutoFill": true,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {
"10807": 1
},
"Authorization": {
"1": 10805,
"900": 10805
},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1012,
"Name": "ExploreTools_1012_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1012_CurrentSkillInfo",
"HelpId": 29,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT31.SP_IconT31",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT31.SP_IconT31",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT31.SP_IconT31",
"SortId": 13,
"AutoFill": true,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {
"1": 10806,
"900": 10806
},
"SummonConfigId": 0
},
{
"PhantomSkillId": 1013,
"Name": "ExploreTools_1013_Name",
"SkillType": 1,
"CurrentSkillInfo": "ExploreTools_1013_CurrentSkillInfo",
"HelpId": 0,
"Icon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT34.SP_IconT34",
"BackGround": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT34.SP_IconT34",
"BattleViewIcon": "/Game/Aki/UI/UIResources/Common/Atlas/SkillIcon/SkillIconNor/SP_IconT34.SP_IconT34",
"SortId": 14,
"AutoFill": true,
"ShowUnlock": false,
"SkillGroupId": 1,
"IsUseInPhantomTeam": false,
"Cost": {},
"Authorization": {},
"SummonConfigId": 24000022
}
]

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

View file

@ -1,12 +1,12 @@
docker build -t shorekeeper-builder:1.3.0-SNAPSHOT -f Dockerfile-builder .
docker build -t wicked-waifus-builder:2.1.0-SNAPSHOT -f Dockerfile-builder .
docker build -t shorekeeper-config-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=config-server -f Dockerfile-service .
docker build -t shorekeeper-hotpatch-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=hotpatch-server -f Dockerfile-service .
docker build -t shorekeeper-login-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=login-server -f Dockerfile-service .
docker build -t shorekeeper-gateway-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=gateway-server -f Dockerfile-service .
docker build -t shorekeeper-game-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=game-server -f Dockerfile-service .
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 shorekeeper-builder:1.3.0-SNAPSHOT
docker rmi wicked-waifus-builder:2.1.0-SNAPSHOT
: Persistence for the application
docker volume create shorekeeper-postgres-vol
: docker volume create wicked-waifus-postgres-vol

View file

@ -1,12 +1,12 @@
docker build -t shorekeeper-builder:1.3.0-SNAPSHOT -f Dockerfile-builder .
docker build -t wicked-waifus-builder:2.1.0-SNAPSHOT -f Dockerfile-builder .
docker build -t shorekeeper-config-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=config-server -f Dockerfile-service .
docker build -t shorekeeper-hotpatch-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=hotpatch-server -f Dockerfile-service .
docker build -t shorekeeper-login-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=login-server -f Dockerfile-service .
docker build -t shorekeeper-gateway-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=gateway-server -f Dockerfile-service .
docker build -t shorekeeper-game-server:1.3.0-SNAPSHOT --build-arg MICROSERVICE=game-server -f Dockerfile-service .
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 shorekeeper-builder:1.3.0-SNAPSHOT
docker rmi wicked-waifus-builder:2.1.0-SNAPSHOT
# Persistence for the application
docker volume create shorekeeper-postgres-vol
# docker volume create wicked-waifus-postgres-vol

View file

@ -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();
}

View file

@ -1,3 +0,0 @@
pub fn print_splash() {
println!(" _____ __ __ \n / ___// /_ ____ ________ / /_____ ___ ____ ___ _____\n \\__ \\/ __ \\/ __ \\/ ___/ _ \\/ //_/ _ \\/ _ \\/ __ \\/ _ \\/ ___/\n ___/ / / / / /_/ / / / __/ ,< / __/ __/ /_/ / __/ / \n/____/_/ /_/\\____/_/ \\___/_/|_|\\___/\\___/ .___/\\___/_/ \n /_/ ");
}

View file

@ -1,6 +0,0 @@
[network]
http_addr = "0.0.0.0:10001"
[encryption]
key = "t+AEu5SGdpz06tomonajLMau9AJgmyTvVhz9VtGf1+0="
iv = "fprc5lBWADQB7tim0R2JxQ=="

View file

@ -1,45 +0,0 @@
use std::fs;
use std::sync::LazyLock;
use anyhow::Result;
use common::config_util::{self, TomlConfig};
use serde::Deserialize;
use shorekeeper_http::{
config::{AesSettings, NetworkSettings},
Application,
};
#[derive(Deserialize)]
pub struct ServerConfig {
pub network: NetworkSettings,
pub encryption: AesSettings,
}
impl TomlConfig for ServerConfig {
const DEFAULT_TOML: &str = include_str!("../configserver.default.toml");
}
#[tokio::main]
async fn main() -> Result<()> {
static CONFIG: LazyLock<ServerConfig> =
LazyLock::new(|| config_util::load_or_create("configserver.toml"));
::common::splash::print_splash();
::common::logging::init(::tracing::Level::DEBUG);
Application::new()
.get("/index.json", get_index)
.with_encryption(&CONFIG.encryption)
.serve(&CONFIG.network)
.await?;
Ok(())
}
async fn get_index() -> &'static str {
static INDEX: LazyLock<String> =
LazyLock::new(|| fs::read_to_string("assets/config/index.json").unwrap());
&*INDEX
}

View 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
}

View file

@ -4,3 +4,7 @@ http_addr = "0.0.0.0:10001"
[encryption]
key = "t+AEu5SGdpz06tomonajLMau9AJgmyTvVhz9VtGf1+0="
iv = "fprc5lBWADQB7tim0R2JxQ=="
[serve]
serve_web_path = "/"
serve_dir_path = "assets/config-server"

View 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"

View file

@ -11,10 +11,10 @@ use_client_key = true
addr = "tcp://0.0.0.0:10003"
[game_server_end_point]
addr = "tcp://shorekeeper-game-server:10004"
addr = "tcp://wicked-waifus-game-server:10004"
[database]
host = "shorekeeper-postgres:5432"
user_name = "shorekeeper_user"
password = "shorekeeper_pass"
db_name = "shorekeeper_db"
host = "wicked-waifus-postgres:5432"
user_name = "wicked_waifus_user"
password = "wicked_waifus_pass"
db_name = "wicked_waifus_db"

View 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"

View 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;

View file

@ -0,0 +1,2 @@
\c wicked_waifus_db;
GRANT ALL ON SCHEMA public TO wicked_waifus_user;

View 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"

View file

@ -1,63 +1,53 @@
name: shorekeeper-ps
name: wicked-waifus-ps
services:
shorekeeper-config-server:
image: shorekeeper-config-server:1.3.0-SNAPSHOT
wicked-waifus-config-server:
image: wicked-waifus-config-server:2.1.0-SNAPSHOT
depends_on:
shorekeeper-postgres:
wicked-waifus-postgres:
condition: service_healthy
ports:
- '10001:10001'
volumes:
- "./docker/configserver.toml:/app/configserver.toml"
- "./assets/config:/app/assets/config"
shorekeeper-hotpatch-server:
image: shorekeeper-hotpatch-server:1.3.0-SNAPSHOT
- "./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:
shorekeeper-postgres:
condition: service_healthy
ports:
- '10002:10002'
volumes:
- "./docker/hotpatch.toml:/app/hotpatch.toml"
- "./assets/hotpatch:/app/assets/hotpatch"
shorekeeper-login-server:
image: shorekeeper-login-server:1.3.0-SNAPSHOT
depends_on:
shorekeeper-postgres:
wicked-waifus-postgres:
condition: service_healthy
ports:
- '5500:5500'
volumes:
- "./docker/loginserver.toml:/app/loginserver.toml"
shorekeeper-gateway-server:
image: shorekeeper-gateway-server:1.3.0-SNAPSHOT
- "./data/docker/loginserver.toml:/app/loginserver.toml"
wicked-waifus-gateway-server:
image: wicked-waifus-gateway-server:2.1.0-SNAPSHOT
depends_on:
shorekeeper-postgres:
wicked-waifus-postgres:
condition: service_healthy
ports:
# Uncomment this if you want to have manual access
# - '10003:10003'
- '7777:7777/udp'
volumes:
- "./docker/gateway.toml:/app/gateway.toml"
shorekeeper-game-server:
image: shorekeeper-game-server:1.3.0-SNAPSHOT
- "./data/docker/gateway.toml:/app/gateway.toml"
wicked-waifus-game-server:
image: wicked-waifus-game-server:2.1.0-SNAPSHOT
depends_on:
shorekeeper-postgres:
wicked-waifus-postgres:
condition: service_healthy
# Uncomment this if you want to have manual access
# ports:
# - '10004:10004'
volumes:
- "./docker/gameserver.toml:/app/gameserver.toml"
- "./assets/logic:/app/assets/logic"
shorekeeper-postgres:
- "./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'
ports:
- '5432:5432'
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
@ -66,8 +56,8 @@ services:
environment:
- "POSTGRES_PASSWORD=toor"
volumes:
- "./docker/postgres/scripts:/docker-entrypoint-initdb.d"
- shorekeeper-postgres-vol:/var/lib/postgresql/data
- "./data/docker/postgres/scripts:/docker-entrypoint-initdb.d"
- wicked-waifus-postgres-vol:/var/lib/postgresql/data
volumes:
shorekeeper-postgres-vol:
wicked-waifus-postgres-vol:
external: true

View file

@ -1,13 +0,0 @@
service_id = 2
[database]
host = "shorekeeper-postgres:5432"
user_name = "shorekeeper_user"
password = "shorekeeper_pass"
db_name = "shorekeeper_db"
[service_end_point]
addr = "tcp://0.0.0.0:10004"
[gateway_end_point]
addr = "tcp://shorekeeper-gateway-server:10003"

View file

@ -1,12 +0,0 @@
[network]
http_addr = "0.0.0.0:5500"
[gateway]
host = "host.docker.internal"
port = 7777
[database]
host = "shorekeeper-postgres:5432"
user_name = "shorekeeper_user"
password = "shorekeeper_pass"
db_name = "shorekeeper_db"

View file

@ -1,3 +0,0 @@
CREATE DATABASE shorekeeper_db;
CREATE USER shorekeeper_user WITH encrypted password 'shorekeeper_pass';
GRANT ALL PRIVILEGES ON DATABASE shorekeeper_db to shorekeeper_user;

View file

@ -1,2 +0,0 @@
\c shorekeeper_db;
GRANT ALL ON SCHEMA public TO shorekeeper_user;

View file

@ -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

View file

@ -1,13 +0,0 @@
service_id = 2
[database]
host = "localhost:5432"
user_name = "postgres"
password = ""
db_name = "shorekeeper"
[service_end_point]
addr = "tcp://127.0.0.1:10004"
[gateway_end_point]
addr = "tcp://127.0.0.1:10003"

View file

@ -1,16 +0,0 @@
use common::config_util::TomlConfig;
use serde::Deserialize;
use shorekeeper_database::DatabaseSettings;
use shorekeeper_network::config::ServiceEndPoint;
#[derive(Deserialize)]
pub struct ServiceConfig {
pub service_id: u32,
pub database: DatabaseSettings,
pub service_end_point: ServiceEndPoint,
pub gateway_end_point: ServiceEndPoint,
}
impl TomlConfig for ServiceConfig {
const DEFAULT_TOML: &str = include_str!("../gameserver.default.toml");
}

View file

@ -1,198 +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 = (if self.is_alive() { LivingStatus::Alive } else { 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
);
}

View file

@ -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();
}
}

View file

@ -1,19 +0,0 @@
mod attribute;
mod entity_config;
mod equip;
mod movement;
mod owner_player;
mod player_entity_marker;
mod position;
mod visibility;
mod vision_skill;
pub use attribute::Attribute;
pub use entity_config::EntityConfig;
pub use equip::Equip;
pub use movement::Movement;
pub use owner_player::OwnerPlayer;
pub use player_entity_marker::PlayerEntityMarker;
pub use position::Position;
pub use visibility::Visibility;
pub use vision_skill::VisionSkill;

View file

@ -1,11 +0,0 @@
use shorekeeper_protocol::EEntityType;
use crate::logic::ecs::component::Component;
pub struct PlayerEntityMarker;
impl Component for PlayerEntityMarker {
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
pb.entity_type = EEntityType::Player.into();
}
}

View file

@ -1,9 +0,0 @@
use crate::logic::ecs::component::Component;
pub struct Visibility(pub bool);
impl Component for Visibility {
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
pb.is_visible = self.0;
}
}

View file

@ -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
}
}

View file

@ -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), )*))
};
}

View file

@ -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_default())
}
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);
}
}

View file

@ -1,11 +0,0 @@
use shorekeeper_protocol::{IYa, InputSettingRequest, InputSettingResponse};
use crate::logic::player::Player;
pub fn on_input_setting_request(
_: &Player,
_: InputSettingRequest,
response: &mut InputSettingResponse,
) {
response.i_ya = Some(IYa::default());
}

View file

@ -1,94 +0,0 @@
mod misc;
mod scene;
mod skill;
pub use misc::*;
pub use scene::*;
pub use skill::*;
use shorekeeper_protocol::message::Message;
macro_rules! handle_request {
($($name:ident $(, $inner_package: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::$($inner_package::)?[<$name Request>]::MESSAGE_ID => {
let Ok(request) = ::shorekeeper_protocol::$($inner_package::)?[<$name Request>]::decode(&*msg.remove_payload()) else {
tracing::debug!("failed to decode {}, player_id: {}", stringify!($($inner_package::)?[<$name Request>]), player.basic_info.id);
return;
};
tracing::debug!("logic: processing request {}", stringify!($($inner_package::)?[<$name Request>]));
let mut response = ::shorekeeper_protocol::$($inner_package::)?[<$name Response>]::default();
[<on_ $($inner_package:snake _)? $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 $(, $inner_package: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::$($inner_package::)?[<$name Push>]::MESSAGE_ID => {
let Ok(push) = ::shorekeeper_protocol::$($inner_package::)?[<$name Push>]::decode(&*msg.remove_payload()) else {
tracing::debug!("failed to decode {}, player_id: {}", stringify!($($inner_package::)?[<$name Push>]), player.basic_info.id);
return;
};
tracing::debug!("logic: processing push {}", stringify!($($inner_package::)?[<$name Push>]));
[<on_ $($inner_package:snake _)? $name:snake _push>](player, push);
},
)*
unhandled => ::tracing::warn!("can't find handler for push with message_id={unhandled}")
}
}
}
};
}
handle_request! {
// Scene
UpdateSceneDate;
EntityActive;
EntityOnLanded;
CombatSendPack, combat_message;
// Skill
VisionExploreSkillSet;
// Misc
InputSetting;
}
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,
),
}
}

View file

@ -1,87 +0,0 @@
use shorekeeper_protocol::combat_message::{CombatSendPackRequest, CombatSendPackResponse};
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_combat_message_combat_send_pack_request(
_player: &Player,
_request: CombatSendPackRequest,
response: &mut CombatSendPackResponse,
) {
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);
}
}

View file

@ -1,32 +0,0 @@
use crate::logic::ecs::component::ComponentContainer;
use shorekeeper_protocol::{
VisionExploreSkillSetRequest, VisionExploreSkillSetResponse, VisionSkillChangeNotify,
VisionSkillInformation,
};
use crate::{logic::player::Player, query_with};
pub fn on_vision_explore_skill_set_request(
player: &mut Player,
request: VisionExploreSkillSetRequest,
response: &mut VisionExploreSkillSetResponse,
) {
player.explore_tools.active_explore_skill = request.skill_id;
let world = player.world.borrow();
for (entity, owner, mut vision_skill) in query_with!(world, OwnerPlayer, VisionSkill) {
if owner.0 == player.basic_info.id {
vision_skill.skill_id = request.skill_id;
player.notify(VisionSkillChangeNotify {
entity_id: entity.into(),
vision_skill_infos: vec![VisionSkillInformation {
skill_id: request.skill_id,
..Default::default()
}],
..Default::default()
})
}
}
response.skill_id = request.skill_id;
}

View file

@ -1,42 +0,0 @@
use shorekeeper_protocol::{Vector, VectorData};
#[derive(Default, Clone, PartialEq, Debug)]
pub struct Vector3f {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vector3f {
pub fn to_protobuf(&self) -> Vector {
Vector {
x: self.x,
y: self.y,
z: self.z,
}
}
pub fn from_save(data: VectorData) -> Self {
Self {
x: data.x,
y: data.y,
z: data.z,
}
}
pub fn save_data(&self) -> VectorData {
VectorData {
x: self.x,
y: self.y,
z: self.z,
}
}
pub fn from_data(data: &shorekeeper_data::VectorData) -> Self {
Self {
x: data.get_x(),
y: data.get_y(),
z: data.get_z(),
}
}
}

View file

@ -1,268 +0,0 @@
use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc};
use basic_info::PlayerBasicInfo;
use common::time_util;
use explore_tools::ExploreTools;
use location::PlayerLocation;
use player_func::PlayerFunc;
use shorekeeper_protocol::{
message::Message, ItemPkgOpenNotify, PbGetRoleListNotify, PlayerBasicData, PlayerRoleData,
PlayerSaveData, ProtocolUnit,
};
use crate::session::Session;
use super::{
ecs::world::World,
role::{Role, RoleFormation},
};
mod basic_info;
mod explore_tools;
mod in_world_player;
mod location;
mod player_func;
pub use in_world_player::InWorldPlayer;
pub struct Player {
session: Option<Arc<Session>>,
// Persistent
pub basic_info: PlayerBasicInfo,
pub role_list: Vec<Role>,
pub formation_list: Vec<RoleFormation>,
pub location: PlayerLocation,
pub func: PlayerFunc,
pub explore_tools: ExploreTools,
// Runtime
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) {
let mut shorekeeper = Role::new(1505);
shorekeeper.equip_weapon = 21050036;
self.role_list.push(shorekeeper);
}
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();
self.ensure_basic_unlock_func();
}
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());
self.notify(self.explore_tools.build_explore_tool_all_notify());
self.notify(self.explore_tools.build_roulette_update_notify());
self.notify(ItemPkgOpenNotify {
open_pkg: (0..8).collect(),
});
}
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();
}
// Ensure basic functionality is unlocked
// Should be handled by quest progression,
// but as of right now, just unlock what we need
fn ensure_basic_unlock_func(&mut self) {
self.func.unlock(10026); // explore tools
}
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()
.flat_map(|id| self.role_list.iter().find(|r| r.role_id == *id))
.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(),
explore_tools: save_data
.explore_tools_data
.map(ExploreTools::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()),
explore_tools_data: Some(self.explore_tools.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()
}
}
}

View file

@ -1,30 +0,0 @@
use std::collections::HashSet;
use shorekeeper_protocol::RoleFormationData;
pub struct RoleFormation {
pub id: i32,
pub cur_role: i32,
pub role_id_set: HashSet<i32>,
pub is_current: bool,
}
impl RoleFormation {
pub fn load_from_save(data: RoleFormationData) -> Self {
Self {
id: data.formation_id,
cur_role: data.cur_role,
role_id_set: data.role_id_list.into_iter().collect(),
is_current: data.is_current,
}
}
pub fn build_save_data(&self) -> RoleFormationData {
RoleFormationData {
formation_id: self.id,
cur_role: self.cur_role,
role_id_list: self.role_id_set.iter().cloned().collect(),
is_current: self.is_current,
}
}
}

View file

@ -1,92 +0,0 @@
use std::collections::HashMap;
use common::time_util;
pub use formation::RoleFormation;
use shorekeeper_data::role_info_data;
use shorekeeper_protocol::{ArrayIntInt, RoleData, RoleInfo};
mod formation;
pub struct Role {
pub role_id: i32,
pub name: String,
pub level: i32,
pub exp: i32,
pub breakthrough: i32,
pub skill_map: HashMap<i32, i32>,
pub star: i32,
pub favor: i32,
pub create_time: u32,
pub equip_weapon: i32,
}
impl Role {
pub const MAIN_CHARACTER_MALE_ID: i32 = 1501;
pub const MAIN_CHARACTER_FEMALE_ID: i32 = 1502;
pub fn new(role_id: i32) -> Self {
let data = role_info_data::iter().find(|d| d.id == role_id).unwrap();
Self {
role_id,
name: String::with_capacity(0),
level: data.max_level,
exp: 0,
breakthrough: 0,
skill_map: HashMap::new(), // TODO!
star: 0,
favor: 0,
create_time: time_util::unix_timestamp() as u32,
equip_weapon: data.init_weapon_item_id,
}
}
pub fn to_protobuf(&self) -> RoleInfo {
RoleInfo {
role_id: self.role_id,
name: self.name.clone(),
level: self.level,
exp: self.exp,
breakthrough: self.breakthrough,
create_time: self.create_time,
skills: self
.skill_map
.iter()
.map(|(k, v)| ArrayIntInt { key: *k, value: *v })
.collect(),
star: self.star,
favor: self.favor,
..Default::default()
}
}
pub fn load_from_save(data: RoleData) -> Self {
Self {
role_id: data.role_id,
name: data.name,
level: data.level,
exp: data.exp,
breakthrough: data.breakthrough,
skill_map: data.skill_map,
star: data.star,
favor: data.favor,
create_time: data.create_time,
equip_weapon: data.equip_weapon,
}
}
pub fn build_save_data(&self) -> RoleData {
RoleData {
role_id: self.role_id,
name: self.name.clone(),
level: self.level,
exp: self.exp,
breakthrough: self.breakthrough,
skill_map: self.skill_map.clone(),
star: self.star,
favor: self.favor,
create_time: self.create_time,
equip_weapon: self.equip_weapon,
..Default::default()
}
}
}

View file

@ -1,25 +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 { id: entity.into(), ..Default::default() };
world
.get_entity_components(entity)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
aoi_data.entities.push(pb);
}
aoi_data
}

View file

@ -1,2 +0,0 @@
pub mod entity_serializer;
pub mod world_util;

View file

@ -1,133 +0,0 @@
use shorekeeper_data::base_property_data;
use shorekeeper_protocol::{
EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, SceneMode,
ScenePlayerInformation, SceneTimeInfo,
};
use super::entity_serializer;
use crate::{
logic::{
components::{
Attribute, EntityConfig, Equip, Movement, OwnerPlayer, PlayerEntityMarker, Position,
Visibility, VisionSkill,
},
ecs::{component::ComponentContainer, world::World},
player::Player,
},
query_with,
};
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()))
.with(ComponentContainer::Equip(Equip {
weapon_id: role.equip_weapon,
weapon_breach_level: 0, // TODO: store this too
}))
.with(ComponentContainer::VisionSkill(VisionSkill {
skill_id: player.explore_tools.active_explore_skill,
}))
.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()
}

View file

@ -1,35 +0,0 @@
use std::sync::{Arc, LazyLock};
use anyhow::Result;
use common::config_util;
use config::ServiceConfig;
use session::SessionManager;
mod config;
mod gateway_connection;
mod logic;
mod player_save_task;
mod service_message_handler;
mod session;
#[tokio::main]
async fn main() -> Result<()> {
static CONFIG: LazyLock<ServiceConfig> =
LazyLock::new(|| config_util::load_or_create("gameserver.toml"));
static SESSION_MGR: LazyLock<SessionManager> = LazyLock::new(SessionManager::default);
::common::splash::print_splash();
::common::logging::init(::tracing::Level::DEBUG);
shorekeeper_data::load_json_data("assets/logic/json")?;
let database = Arc::new(shorekeeper_database::connect_to(&CONFIG.database).await?);
shorekeeper_database::run_migrations(database.as_ref()).await?;
logic::thread_mgr::start_logic_threads(1);
player_save_task::start(database.clone());
gateway_connection::init(CONFIG.service_id, &CONFIG.gateway_end_point);
service_message_handler::run(&CONFIG.service_end_point, &SESSION_MGR, database).await?;
Ok(())
}

View file

@ -1,38 +0,0 @@
const UE = require("ue"),
Info_1 = require("../../../Core/Common/Info"),
MathUtils_1 = require("../../../Core/Utils/MathUtils"),
EventDefine_1 = require("../../Common/Event/EventDefine"),
EventSystem_1 = require("../../Common/Event/EventSystem"),
UiControllerBase_1 = require("../../Ui/Base/UiControllerBase"),
UiLayerType_1 = require("../../Ui/Define/UiLayerType"),
UiLayer_1 = require("../../Ui/UiLayer");
var _a = require('../Module/WaterMask/WaterMaskController').WaterMaskView;
_a.vOo = function () {
void 0 !== _a.SOo && _a.EOo();
var e = UiLayer_1.UiLayer.GetLayerRootUiItem(UiLayerType_1.ELayerType.WaterMask),
t = (_a.SOo = UE.KuroActorManager.SpawnActor(Info_1.Info.World, UE.UIContainerActor.StaticClass(),
MathUtils_1.MathUtils.DefaultTransform, void 0), _a.SOo.RootComponent),
e = (t.SetDisplayName("WaterMaskContainer"), UE.KuroStaticLibrary.SetActorPermanent(_a.SOo, !0, !0), _a.SOo
.K2_AttachRootComponentTo(e), t.GetRootCanvas().GetOwner().RootComponent),
i = e.widget.width % _a.yOo / 2,
r = e.widget.height % _a.IOo / 2,
n = e.widget.width / 2,
_ = e.widget.height / 2,
s = Math.ceil(e.widget.width / _a.yOo),
o = Math.ceil(e.widget.height / _a.IOo),
v = "discord.gg/reversedrooms";
for (let a = 0; a < s; a++)
for (let e = 0; e < o; e++) {
var E = UE.KuroActorManager.SpawnActor(Info_1.Info.World, UE.UITextActor.StaticClass(), MathUtils_1
.MathUtils.DefaultTransform, void 0),
U = E.RootComponent,
U = (E.K2_AttachRootComponentTo(t), U.SetDisplayName("WaterMaskText"), E.GetComponentByClass(UE
.UIText.StaticClass()));
U.SetFontSize(_a.vFt), U.SetOverflowType(0), U.SetAlpha(_a.LOo), U.SetFont(UE.LGUIFontData
.GetDefaultFont()), U.SetText(v), U.SetUIRelativeLocation(new UE.Vector(a * _a.yOo - n + i, e *
_a.IOo - _ + r, 0)), U.SetUIRelativeRotation(new UE.Rotator(0, _a.TOo, 0)), UE.KuroStaticLibrary
.SetActorPermanent(E, !0, !0)
}
};
_a.vOo();

View file

@ -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(())
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 MiB

After

Width:  |  Height:  |  Size: 7.7 MiB

View file

@ -1,10 +0,0 @@
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct FunctionConditionData {
pub function_id: i32,
pub name: String,
pub is_on: bool,
pub open_condition_id: i32,
}

View file

@ -1,51 +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;
ExploreTools;
}

View file

@ -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,
}

View file

@ -1,41 +0,0 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct RoleInfoData {
pub id: i32,
pub quality_id: i32,
pub role_type: i32,
pub is_trial: bool,
pub name: String,
pub nick_name: String,
pub introduction: String,
pub tag: Vec<i32>,
pub parent_id: i32,
pub priority: i32,
pub show_property: Vec<i32>,
pub element_id: i32,
pub spillover_item: HashMap<i32, i32>,
pub breach_model: i32,
pub special_energy_bar_id: i32,
pub entity_property: i32,
pub max_level: i32,
pub level_consume_id: i32,
pub breach_id: i32,
pub skill_id: i32,
pub skill_tree_group_id: i32,
pub resonance_id: i32,
pub resonant_chain_group_id: i32,
pub is_show: bool,
pub exchange_consume: HashMap<i32, i32>,
pub init_weapon_item_id: i32,
pub weapon_type: i32,
pub party_id: i32,
pub item_quality_id: i32,
pub num_limit: i32,
pub trial_role: i32,
pub is_aim: bool,
pub role_guide: i32,
}

View file

@ -1,190 +0,0 @@
use std::{
fs,
io::{self, BufRead},
path::Path,
};
use std::io::Write;
use quote::{format_ident, 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)]")
.type_attribute(".", "#[derive(serde::Serialize,serde::Deserialize)]")
.compile_protos(&[proto_file], &["shorekeeper"])
.unwrap();
impl_dumper(Path::new("generated/shorekeeper.rs")).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),
}
}
}
};
let syntax_tree = syn::parse2(generated_code).unwrap();
let formatted = prettyplease::unparse(&syntax_tree);
fs::write(codegen_path, formatted.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})]"))
}
pub fn impl_dumper(codegen_path: &Path) -> io::Result<()> {
let file = fs::File::open(codegen_path)?;
let reader = io::BufReader::new(file);
let mut match_arms = quote! {};
let mut id = None;
for line in reader.lines() {
let line = line?;
if line.contains("MessageId:") {
id = Some(
line.trim_start().split(' ').nth(2).unwrap().parse::<u16>().ok().unwrap()
);
} else if line.contains("pub struct") {
if let Some(id) = id.take() {
let name = line.trim_start().split(' ').nth(2).unwrap().to_string();
let name_ident = format_ident!("{}", name);
match_arms = quote! {
#match_arms
#id => Ok((#name, serde_json::to_string_pretty(&#name_ident::decode(data)?)?)),
};
}
}
}
let generated_code = quote! {
pub mod proto_dumper {
use prost::Message;
use crate::*;
use crate::ai::*;
use crate::combat_message::*;
use crate::debug::*;
use crate::summon::*;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("serde_json::Error: {0}")]
Json(#[from] serde_json::Error),
#[error("serde_json::Error: {0}")]
Decode(#[from] prost::DecodeError),
}
pub fn get_debug_info(id: u16, data: &[u8]) -> Result<(&str, String), Error> {
match id {
#match_arms
_ => Ok(("UnknownType", "".to_string())),
}
}
}
};
let syntax_tree = syn::parse2(generated_code).unwrap();
let formatted = prettyplease::unparse(&syntax_tree);
let mut file = fs::OpenOptions::new().append(true).open(codegen_path)?;
file.write(formatted.as_bytes())?;
Ok(())
}

File diff suppressed because it is too large Load diff

View file

@ -1,82 +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;
int32 equip_weapon = 15;
}
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 PlayerExploreToolsData {
repeated int32 unlocked_skill_list = 1;
int32 active_skill_id = 2;
repeated int32 roulette = 3;
}
message PlayerSaveData {
PlayerBasicData basic_data = 1;
PlayerRoleData role_data = 2;
PlayerLocationData location_data = 3;
PlayerFuncData func_data = 4;
PlayerExploreToolsData explore_tools_data = 5;
}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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;
}
})
}

View file

@ -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,
}
}
}

View file

@ -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();
let _ = 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,
}
}
}

View file

@ -0,0 +1,12 @@
[package]
name = "wicked-waifus-asset-updater"
edition = "2021"
version.workspace = true
[dependencies]
git2 = "0.20.0"
ureq = "3.0.5"
zip-extract = "0.2.1"
tracing.workspace = true
thiserror.workspace = true

View file

@ -0,0 +1,145 @@
use std::io::{Cursor, Read, Write};
use std::path::Path;
use git2::{FetchOptions, RemoteCallbacks, Repository};
use git2::build::RepoBuilder;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("UReq error: {0}")]
UReq(#[from] ureq::Error),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("ZipExtract error: {0}")]
ZipExtract(#[from] zip_extract::ZipExtractError),
#[error("Git error: {0}")]
Git(#[from] git2::Error),
#[error("Option conversion error")]
Option(),
}
pub fn update_from_releases<T: AsRef<str> + std::fmt::Display>(object_url: T,
target_folder: T,
max_buffer: usize) -> Result<(), Error> {
let path = Path::new(target_folder.as_ref());
std::fs::create_dir_all(path)?;
let version = path.join("version.txt");
let new_version = object_url.as_ref();
if version.exists() {
tracing::debug!("Version marker exists, checking version");
let old_version = std::fs::read_to_string(&version)?;
if old_version.eq(new_version) {
tracing::debug!("The version to download is the current one, skipping ...");
return Ok(())
} else {
tracing::debug!("The old version is: {old_version}, new version: {new_version}")
}
} else {
tracing::debug!("Version marker doesn't exist, fetching resources for first time")
}
tracing::debug!("Starting the download of static assets from {object_url}");
let mut bytes: Vec<u8> = Vec::with_capacity(max_buffer);
let agent = ureq::config::Config::builder()
.timeout_send_body(Some(std::time::Duration::from_secs(60)))
.timeout_recv_body(Some(std::time::Duration::from_secs(60)))
.build()
.new_agent();
let response = agent.get(new_version).call()?;
response.into_body().as_reader().read_to_end(&mut bytes)?;
tracing::debug!("Downloading assets from: {object_url} finished, extracting into {target_folder}");
zip_extract::extract(Cursor::new(bytes), path, true)?;
tracing::debug!("Resources extracted");
let mut file = std::fs::File::create(&version)?;
file.write_all(new_version.as_bytes())?;
Ok(())
}
pub fn clone_or_update_repository<T: AsRef<str> + std::fmt::Display>(repository_url: T,
repository_path: T,
ref_name: T,
discard_local_changes: bool) -> Result<(), Error> {
// Open existing repository or clone if not existing
let path = Path::new(repository_path.as_ref());
let repository = match path.exists() {
true => {
tracing::debug!("Path {repository_path} exists, opening repository");
Repository::open(repository_path.as_ref())
}
false => clone_repository(repository_url.as_ref(), path)
}?;
// TODO: Fetch remote updates????
// Get reference
let (object, reference) = repository.revparse_ext(ref_name.as_ref())?;
tracing::debug!("Reference {ref_name} found in repository");
let update_needed = match &reference {
Some(reference) => repository.head()?.eq(reference),
None => repository.head()?.target().ok_or(Error::Option())?.eq(&object.id()),
};
// If we are already in commit, no need to modify anything
if update_needed {
// Discard all local changes
if discard_local_changes {
tracing::debug!("discard_local_changes was set to true, changes will be discarded");
repository.checkout_head(None)?;
} else {
tracing::warn!("discard_local_changes was set to false, changes will be kept");
}
// Checkout required remote
repository.checkout_tree(&object, None)?;
// Checkout the needed commit
match reference {
Some(repo_reference) => repository.set_head(repo_reference.name().unwrap())?,
None => repository.set_head_detached(object.id())?,
}
tracing::debug!("Repository has been updated to {ref_name}");
} else {
tracing::debug!("Repository is already at the desired reference, no update needed");
};
Ok(())
}
fn clone_repository(repository_url: &str, path: &Path) -> Result<Repository, git2::Error> {
tracing::debug!(
"Path {path:?} doesn't exists, cloning repository at {repository_url}"
);
let mut remote_callbacks = RemoteCallbacks::new();
remote_callbacks.sideband_progress(|data| {
tracing::debug!("remote: {}", std::str::from_utf8(data).unwrap());
std::io::stdout().flush().unwrap();
true
});
remote_callbacks.transfer_progress(|stats| {
if stats.received_objects() == stats.total_objects() {
tracing::debug!(
"Resolving deltas {}/{}\r",
stats.indexed_deltas(),
stats.total_deltas()
);
} else if stats.total_objects() > 0 {
tracing::debug!(
"Received {}/{} objects ({}) in {} bytes\r",
stats.received_objects(),
stats.total_objects(),
stats.indexed_objects(),
stats.received_bytes()
);
}
std::io::stdout().flush().unwrap();
true
});
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(remote_callbacks);
let result = RepoBuilder::new()
.fetch_options(fetch_options)
.clone(repository_url, path);
result
}

View file

@ -1,5 +1,5 @@
[package]
name = "common"
name = "wicked-waifus-commons"
edition = "2021"
version.workspace = true

View 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();
}

View file

@ -0,0 +1,10 @@
pub fn print_splash() {
println!("
");
}

View file

@ -1,5 +1,17 @@
use std::time::{SystemTime, UNIX_EPOCH};
const SECONDS_PER_MINUTE: u64 = 60;
const SECONDS_PER_HOUR: u64 = 60 * SECONDS_PER_MINUTE;
const SECONDS_PER_DAY: u64 = 24 * SECONDS_PER_HOUR;
pub fn unix_days() -> i32 {
(SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
/ SECONDS_PER_DAY) as i32
}
pub fn unix_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)

View file

@ -1,12 +1,11 @@
[package]
name = "config-server"
name = "wicked-waifus-config-server"
edition = "2021"
version.workspace = true
[dependencies]
# Framework
tokio.workspace = true
shorekeeper-http.workspace = true
# Serialization
serde.workspace = true
@ -19,4 +18,6 @@ anyhow.workspace = true
tracing.workspace = true
# Internal
common.workspace = true
wicked-waifus-asset-updater.workspace = true
wicked-waifus-commons.workspace = true
wicked-waifus-http.workspace = true

View file

@ -0,0 +1,15 @@
[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"
[asset_config]
repository_url = "https://git.xeondev.com/wickedwaifus/wicked-waifus-config-server-files.git"
reference = "4d51547b78"
discard_local_changes = true

View file

@ -0,0 +1,68 @@
use std::sync::LazyLock;
use anyhow::Result;
use serde::Deserialize;
use wicked_waifus_commons::config_util::{self, TomlConfig};
use wicked_waifus_http::{
Application,
config::{AesSettings, NetworkSettings},
};
#[derive(Deserialize)]
pub struct ServeConfig {
pub serve_web_path: String,
pub serve_dir_path: String,
}
#[derive(Deserialize)]
pub struct AssetConfig {
pub repository_url: String,
pub reference: String,
pub discard_local_changes: bool,
}
#[derive(Deserialize)]
pub struct ServerConfig {
pub network: NetworkSettings,
pub encryption: AesSettings,
pub serve: ServeConfig,
pub asset_config: Option<AssetConfig>,
}
impl TomlConfig for ServerConfig {
const DEFAULT_TOML: &str = include_str!("../configserver.default.toml");
}
#[tokio::main]
async fn main() -> Result<()> {
static CONFIG: LazyLock<ServerConfig> =
LazyLock::new(|| config_util::load_or_create("configserver.toml"));
::wicked_waifus_commons::splash::print_splash();
::wicked_waifus_commons::logging::init_axum(::tracing::Level::DEBUG);
if let Some(asset_config) = &CONFIG.asset_config {
wicked_waifus_asset_updater::clone_or_update_repository(
&asset_config.repository_url,
&CONFIG.serve.serve_dir_path,
&asset_config.reference,
asset_config.discard_local_changes,
).unwrap();
}
tracing::debug!(
"Serving files from {} at {}",
&CONFIG.serve.serve_web_path,
&CONFIG.serve.serve_dir_path
);
Application::new()
.serve_dir(&CONFIG.serve.serve_web_path, &CONFIG.serve.serve_dir_path)
.with_encryption(&CONFIG.encryption)
.with_logger()
.serve(&CONFIG.network)
.await?;
Ok(())
}

View file

@ -1,10 +1,15 @@
[package]
name = "shorekeeper-data"
name = "wicked-waifus-data"
edition = "2021"
version.workspace = true
[features]
strict_json_fields = []
[dependencies]
serde.workspace = true
serde_json.workspace = true
serde_repr.workspace = true
paste.workspace = true
thiserror.workspace = true
tracing.workspace = true

View file

@ -0,0 +1,21 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct AchievementData {
pub id: i32,
pub group_id: i32,
pub level: i32,
#[cfg(feature = "strict_json_fields")]
pub name: String,
#[cfg(feature = "strict_json_fields")]
pub desc: String,
#[cfg(feature = "strict_json_fields")]
pub icon_path: String,
pub override_drop_id: i32,
pub hidden: bool,
pub next_link: i32,
pub client_trigger: bool,
pub third_party_trophy_id: i32,
}

View file

@ -0,0 +1,19 @@
#[cfg(feature = "strict_json_fields")]
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct AdventureTaskData {
pub id: i32,
pub chapter_id: i32,
#[cfg(feature = "strict_json_fields")]
pub task_text: String,
pub record_id: Vec<i32>,
pub need_progress: i32,
pub drop_ids: i32,
pub path_id: i32,
#[cfg(feature = "strict_json_fields")]
pub jump_to: HashMap<String, String>,
}

View file

@ -0,0 +1,41 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct SubBehaviorConfigs {
#[cfg(feature = "strict_json_fields")]
pub ai_alert: Option<String>,
#[cfg(feature = "strict_json_fields")]
pub ai_base_skill: Option<String>,
#[cfg(feature = "strict_json_fields")]
pub ai_battle_wander: Option<String>,
#[cfg(feature = "strict_json_fields")]
pub ai_flee: Option<String>,
#[cfg(feature = "strict_json_fields")]
pub ai_hate: Option<String>,
#[cfg(feature = "strict_json_fields")]
pub ai_patrol: Option<String>,
#[cfg(feature = "strict_json_fields")]
pub ai_sense: Option<String>,
#[cfg(feature = "strict_json_fields")]
pub ai_wander: Option<String>,
}
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct AiBaseData {
pub id: i32,
pub state_machine: String,
#[cfg(feature = "strict_json_fields")]
pub ai_controller: String,
#[cfg(feature = "strict_json_fields")]
pub behavior_tree: String,
#[cfg(feature = "strict_json_fields")]
pub sub_behavior_configs: SubBehaviorConfigs,
#[cfg(feature = "strict_json_fields")]
pub team: bool,
#[cfg(feature = "strict_json_fields")]
pub monster_type: i32,
}

View file

@ -0,0 +1,10 @@
use serde::Deserialize;
use crate::StateMachineJson;
#[derive(Clone, Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct AiStateMachineConfigData {
pub id: String,
pub state_machine_json: StateMachineJson,
}

View file

@ -0,0 +1,35 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct AreaData {
pub area_id: i32,
pub level: i32,
pub country_id: i32,
pub delivery_mark_id: i32,
#[cfg(feature = "strict_json_fields")]
pub area_name: String,
pub map_config_id: i32,
pub dungeon_id: i32,
#[cfg(feature = "strict_json_fields")]
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,
#[cfg(feature = "strict_json_fields")]
pub edge_wall_name: String,
pub delivery_mark_type: i32,
pub sort_index: i32,
pub enter_area_tags: HashMap<i32, i32>,
pub leave_area_tags: HashMap<i32, i32>,
}

View file

@ -1,6 +1,7 @@
use serde::Deserialize;
#[derive(Deserialize)]
#[derive(Deserialize, Debug, Clone)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct BasePropertyData {
pub id: i32,
@ -16,7 +17,6 @@ pub struct BasePropertyData {
pub def: i32,
pub energy_efficiency: i32,
pub cd_reduse: i32,
pub reaction_efficiency: i32,
pub damage_change_normal_skill: i32,
pub damage_change: i32,
pub damage_reduce: i32,
@ -140,4 +140,5 @@ pub struct BasePropertyData {
pub paralysis_time_recover: i32,
pub element_energy_max: i32,
pub element_energy: i32,
pub element_efficiency: i32,
}

View file

@ -0,0 +1,19 @@
use serde::Deserialize;
use crate::{EntityLogic, EntityType};
#[derive(Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct BlueprintConfigData {
pub id: i32,
pub blueprint_type: String,
pub entity_type: EntityType,
pub entity_logic: EntityLogic,
#[cfg(feature = "strict_json_fields")]
pub model_id: i32,
#[cfg(feature = "strict_json_fields")]
pub half_height: i32,
#[cfg(feature = "strict_json_fields")]
pub track_height: i32,
}

View file

@ -0,0 +1,22 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct CalabashDevelopRewardData {
pub monster_id: i32,
pub develop_condition: Vec<i32>,
pub monster_info_id: i32,
pub all_exp: i32,
pub sort_id: i32,
pub monster_probe_id: i32,
#[cfg(feature = "strict_json_fields")]
pub hand_book_bp: String,
pub monster_body_type: i32,
#[cfg(feature = "strict_json_fields")]
pub hand_book_camera: String,
#[cfg(feature = "strict_json_fields")]
pub monster_number: String,
pub interaction_radius: i32,
pub is_show: bool,
}

View file

@ -0,0 +1,24 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct CalabashLevelData {
pub level: i32,
pub level_up_exp: i32,
pub level_up_condition: i32,
pub temp_catch_gain: i32,
pub buff_ids: Vec<i32>,
#[cfg(feature = "strict_json_fields")]
pub buff_description: String,
#[cfg(feature = "strict_json_fields")]
pub level_up_description: String,
#[cfg(feature = "strict_json_fields")]
pub quality_description: String,
pub buff_description_map: HashMap<i32, i32>,
pub cost: i32,
pub reward_id: i32,
pub quality_drop_weight: HashMap<i32, i32>,
}

View file

@ -0,0 +1,43 @@
use serde::Deserialize;
#[derive(Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct DamageData {
pub id: i64,
pub calculate_type: i32,
pub element: i32,
pub damage_text_type: i32,
pub payload_id: i32,
pub r#type: i32,
pub sub_type: Vec<i32>,
pub smash_type: i32,
pub cure_base_value: Vec<i32>,
pub related_property: i32,
pub rate_lv: Vec<i32>,
pub hardness_lv: Vec<i32>,
pub tough_lv: Vec<i32>,
pub energy: Vec<i32>,
pub special_energy1: Vec<i32>,
pub special_energy2: Vec<i32>,
pub special_energy3: Vec<i32>,
pub special_energy4: Vec<i32>,
pub element_power_type: i32,
pub element_power: Vec<i32>,
pub formula_type: i32,
pub formula_param1: Vec<i32>,
pub formula_param2: Vec<i32>,
pub formula_param3: Vec<i32>,
pub formula_param4: Vec<i32>,
pub formula_param5: Vec<i32>,
pub formula_param6: Vec<i32>,
pub formula_param7: Vec<i32>,
pub formula_param8: Vec<i32>,
pub formula_param9: Vec<i32>,
pub formula_param10: Vec<i32>,
pub immune_type: i32,
pub percent0: Vec<i32>,
pub percent1: Vec<i32>,
pub fluctuation_lower: Vec<i32>,
pub fluctuation_upper: Vec<i32>,
}

View file

@ -0,0 +1,13 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[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,
}

View file

@ -0,0 +1,14 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "strict_json_fields", serde(deny_unknown_fields))]
#[serde(rename_all = "PascalCase")]
pub struct DropPackageData {
pub id: i32,
pub show_bg: bool,
#[cfg(feature = "strict_json_fields")]
pub title: String,
pub drop_plan: i32,
pub drop_preview: HashMap<i32, i32>,
}

Some files were not shown because too many files have changed in this diff Show more