Compare commits

..

22 commits

Author SHA1 Message Date
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
087991cb85 Added sample config (#5)
Reviewed-on: Shorekeeper/Shorekeeper#5
2024-09-30 19:16:49 +00:00
1d5321c805 Changes for duimper (#4)
Reviewed-on: Shorekeeper/Shorekeeper#4
2024-09-28 20:35:15 +00:00
d93c61cc92 Implement explore tools and some logic adjustments 2024-09-16 03:09:26 +03:00
73060a426d Censorship fix, courtesy of @.novinity (#3)
Reviewed-on: Shorekeeper/Shorekeeper#3
Co-authored-by: xavo95 <xavo95@xeondev.com>
Co-committed-by: xavo95 <xavo95@xeondev.com>
2024-09-15 20:30:46 +00:00
96d1994fe2 Support for Nested combat messages and JSPatch Notify from files (#2)
Reviewed-on: Shorekeeper/Shorekeeper#2
Co-authored-by: xavo95 <xavo95@xeondev.com>
Co-committed-by: xavo95 <xavo95@xeondev.com>
2024-09-14 09:05:17 +00:00
e5892ed9e5 docker-test (#1)
Co-authored-by: a <a>
Reviewed-on: Shorekeeper/Shorekeeper#1
Co-authored-by: xavo95 <xavo95@xeondev.com>
Co-committed-by: xavo95 <xavo95@xeondev.com>
2024-09-12 21:29:40 +00:00
218 changed files with 6874 additions and 199631 deletions

12
.dockerignore Normal file
View file

@ -0,0 +1,12 @@
.git
assets
postgres
**/target
.gitignore
docker-compose.yml
Dockerfile-builder
Dockerfile-service
LICENSE
*.md
*.zip
*.png

4
.gitignore vendored
View file

@ -1,7 +1,9 @@
.idea
/target /target
/hotpatch.toml /hotpatch.toml
/configserver.toml /configserver.toml
/loginserver.toml /loginserver.toml
/gateway.toml /gateway.toml
/gameserver.toml /gameserver.toml
/shorekeeper-protocol/generated /wicked-waifus-protocol-internal/generated
/wicked-waifus-protocol/generated

6
.gitmodules vendored Normal file
View file

@ -0,0 +1,6 @@
[submodule "data/assets/config-server"]
path = data/assets/config-server
url = https://git.xeondev.com/wickedwaifus/wicked-waifus-config-server-files.git
[submodule "data/assets/game-data"]
path = data/assets/game-data
url = https://git.xeondev.com/wickedwaifus/wicked-waifus-data.git

1494
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["common", "config-server", "hotpatch-server", "login-server", "gateway-server", "gateway-server/kcp", "shorekeeper-database", "shorekeeper-http", "shorekeeper-protokey", "shorekeeper-protocol", "shorekeeper-protocol/shorekeeper-protocol-derive", "game-server", "shorekeeper-network", "shorekeeper-data"] members = ["wicked-waifus-commons", "wicked-waifus-config-server", "wicked-waifus-hotpatch-server", "wicked-waifus-login-server", "wicked-waifus-gateway-server", "wicked-waifus-gateway-server/kcp", "wicked-waifus-database", "wicked-waifus-http", "wicked-waifus-protokey", "wicked-waifus-protocol-internal", "wicked-waifus-game-server", "wicked-waifus-network", "wicked-waifus-data"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
@ -8,6 +8,7 @@ version = "0.1.0"
[workspace.dependencies] [workspace.dependencies]
# Framework # Framework
tokio = { version = "1.39.3", features = ["full"] } tokio = { version = "1.39.3", features = ["full"] }
tower-http = { version = "0.6.1", features = ["fs", "trace"] }
axum = "0.7.5" axum = "0.7.5"
axum-server = "0.7.1" axum-server = "0.7.1"
zeromq = { version = "0.4.0", default-features = false, features = ["tokio-runtime", "tcp-transport"] } zeromq = { version = "0.4.0", default-features = false, features = ["tokio-runtime", "tcp-transport"] }
@ -19,12 +20,14 @@ sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio-rustls"] }
aes = "0.8.4" aes = "0.8.4"
cbc = { version = "0.1.2", features = ["alloc"] } cbc = { version = "0.1.2", features = ["alloc"] }
cipher = "0.4.4" cipher = "0.4.4"
crc32fast = "1.4.2"
rand = "0.8.5" rand = "0.8.5"
rsa = { version = "0.9.6", features = ["pem"] } rsa = { version = "0.9.6", features = ["pem"] }
# Serialization # Serialization
serde = { version = "1.0.209", features = ["derive"] } serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.128" serde_json = "1.0.128"
serde_repr = "0.1.19"
toml = "0.8.19" toml = "0.8.19"
prost = "0.13.2" prost = "0.13.2"
prost-build = "0.13.2" prost-build = "0.13.2"
@ -37,22 +40,23 @@ rbase64 = "2.0.3"
dashmap = "6.1.0" dashmap = "6.1.0"
hex = "0.4.3" hex = "0.4.3"
byteorder = "1.5.0" byteorder = "1.5.0"
crc32fast = "1.4.2"
# Tracing # Tracing
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
# Internal # Internal
kcp = { path = "gateway-server/kcp" } kcp = { path = "wicked-waifus-gateway-server/kcp" }
common = { path = "common/" } wicked-waifus-commons = { path = "wicked-waifus-commons" }
shorekeeper-http = { path = "shorekeeper-http/" } wicked-waifus-http = { path = "wicked-waifus-http" }
shorekeeper-data = { path = "shorekeeper-data/" } wicked-waifus-data = { path = "wicked-waifus-data" }
shorekeeper-database = { path = "shorekeeper-database/" } wicked-waifus-database = { path = "wicked-waifus-database" }
shorekeeper-network = { path = "shorekeeper-network/" } wicked-waifus-network = { path = "wicked-waifus-network" }
shorekeeper-protocol = { path = "shorekeeper-protocol/" } wicked-waifus-protocol-internal = { path = "wicked-waifus-protocol-internal" }
shorekeeper-protocol-derive = { path = "shorekeeper-protocol/shorekeeper-protocol-derive" } wicked-waifus-protokey = { path = "wicked-waifus-protokey" }
shorekeeper-protokey = { path = "shorekeeper-protokey/" }
wicked-waifus-protocol = { git = "https://git.xeondev.com/wickedwaifus/wicked-waifus-proto" }
wicked-waifus-protocol-derive = { git = "https://git.xeondev.com/wickedwaifus/wicked-waifus-proto" }
[profile.release] [profile.release]
strip = true # Automatically strip symbols from the binary. strip = true # Automatically strip symbols from the binary.

6
Dockerfile-builder Normal file
View file

@ -0,0 +1,6 @@
FROM rust:1.82-alpine3.20
WORKDIR /app
COPY . .
# No need to manually strip symbols(strip target/release/$MICROSERVICE) since workspace its already prepared for that
RUN apk add musl-dev protoc && cargo build --release

6
Dockerfile-service Normal file
View file

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

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 ## About
**Shorekeeper is an open-source Wuthering Waves server emulator written in Rust**. The goal of this project is to ensure a clean, easy-to-understand code environment. Shorekeeper uses **tokio** for asynchronous networking operations, **axum** as http framework and **ZeroMQ** for communication between servers. It also implements **performant and extensible ECS** for emulation of the game world. **Wicked Waifus is an open-source Wuthering Waves server emulator written in Rust**.
The goal of this project is to ensure a clean, easy-to-understand code environment.
Wicked Waifus uses **tokio** for asynchronous networking operations, **axum** as http framework and **ZeroMQ** for communication between servers.
It also implements **performant and extensible ECS** for emulation of the game world.
## Getting started ## Getting started
#### Requirements #### Requirements
@ -15,19 +18,34 @@
##### a) building from sources ##### a) building from sources
```sh ```sh
git clone https://git.xeondev.com/Shorekeeper/Shorekeeper.git git clone --recursive https://git.xeondev.com/wickedwaifus/wicked-waifus-rs.git
cd Shorekeeper cd wicked-waifus-rs
cargo run --bin config-server cargo run --bin wicked-waifus-config-server
cargo run --bin hotpatch-server cargo run --bin wicked-waifus-hotpatch-server
cargo run --bin login-server cargo run --bin wicked-waifus-login-server
cargo run --bin gateway-server cargo run --bin wicked-waifus-gateway-server
cargo run --bin game-server cargo run --bin wicked-waifus-game-server
``` ```
##### b) using pre-built binaries ##### b) building from sources(docker edition)
Navigate to the [Releases](https://git.xeondev.com/Shorekeeper/Shorekeeper/releases) If you are to wheelchair'd for option A, you can fallback to option b.
In this case you will need [Docker Desktop](https://www.docker.com/products/docker-desktop/)
Once installed, to build the images, run:
```sh
# or builder.bat if you run it on windows
./builder.sh
```
And to run the containers:
```sh
docker compose up -d
```
##### c) using pre-built binaries
Navigate to the [Releases](https://git.xeondev.com/wickedwaifus/wicked-waifus-rs/releases)
page and download the latest release for your platform.<br> page and download the latest release for your platform.<br>
Launch all servers: `config-server`, `hotpatch-server`, `login-server`, `gateway-server`, `game-server` Launch all servers: `wicked-waifus-config-server`, `wicked-waifus-hotpatch-server`, `wicked-waifus-login-server`, `wicked-waifus-gateway-server`, `wicked-waifus-game-server`
##### NOTE: you don't have to install Rust and Protoc if you're going to use pre-built binaries, although the preferred way is building from sources.<br>We don't provide any support for pre-built binaries. ##### NOTE: you don't have to install Rust and Protoc if you're going to use pre-built binaries, although the preferred way is building from sources.<br>We don't provide any support for pre-built binaries.
@ -42,15 +60,15 @@ You have to specify credentials for **PostgreSQL**<br>
host = "localhost:5432" host = "localhost:5432"
user_name = "postgres" user_name = "postgres"
password = "" password = ""
db_name = "shorekeeper" db_name = "wicked_waifus_db"
``` ```
##### NOTE: don't forget to create database with specified `db_name` (default: `shorekeeper`). For example, you can do so with PgAdmin. ##### NOTE: don't forget to create database with specified `db_name` (default: `wicked_waifus_db`). For example, you can do so with PgAdmin.
#### Data #### Data
The data files: Logic JSON collections (`assets/logic/json`) and config/hotpatch indexes (`assets/config`, `assets/hotpatch`) are included in this repository. Keep in mind that you need to have the `assets` subdirectory in current working directory. The data files: Logic JSON collections (`data/assets/game-data/BinData`) and config/hotpatch indexes (`data/assets/config-server`, `data/assets/hotpatch-server`) are included in this repository. Keep in mind that you need to have the `data` subdirectory in current working directory.
#### Connecting #### Connecting
You have to download client of Wuthering Waves Beta 1.3, apply the [shorekeeper-patch](https://git.xeondev.com/xeon/shorekeeper-patch/releases) and add necessary `.pak` files, which you can get here: [shorekeeper-pak](https://git.xeondev.com/Shorekeeper/shorekeeper-pak) You have to download client of Wuthering Waves Beta 2.1, apply the [wicked-waifus-win-patch](https://git.xeondev.com/wickedwaifus/wicked-waifus-win-patch/releases) and add necessary `.pak` files, which you can get here: [wicked-waifus-pak](https://git.xeondev.com/wickedwaifus/wicked-waifus-pak)
### Troubleshooting ### Troubleshooting
[Visit our discord](https://discord.gg/reversedrooms) if you have any questions/issues [Visit our discord](https://discord.gg/reversedrooms) if you have any questions/issues

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,60 +0,0 @@
{
"PackageVersion": "1.3.0",
"LauncherVersion": "1.3.9",
"ResourceVersion": "1.3.9",
"LauncherIndexSha1": {
"1.3.1": "90FDF17EA0B4015D43C344CB7229E76AB32549DD",
"1.3.2": "C9A587AB1FA6CA57CD23E0FB3F0103BFDCAA8E37",
"1.3.3": "1C7AF02F13DBE69637DB43039E2FFB8C9AD9A04B",
"1.3.4": "DA50F315041E216568A7713074C6475F6AB4530E",
"1.3.5": "EA9C6F6D5E920F47F96D8F8BC366A4CED62A0346",
"1.3.6": "8CA7E6573A52B16CFAA29E996D389918B6829E7A",
"1.3.7": "FCAAED58E5983027A82F52C350418CCE7BD531D2",
"1.3.8": "91D6231B3F4C9A6605B79E23D0C02F9790DD6BCF",
"1.3.9": "B36BD648AB2A637A4E087B7B115A6CCBDAEBDF9A"
},
"ResourceIndexSha1": {
"1.3.1": "2D635E549EB6F99659571D72741B62249473A77A",
"1.3.2": "C5814A80EA3E7D80D4CFBCD884D1FD158BF0AD9D",
"1.3.3": "1E0F05333B09A9215B4AA5C437BFC7DC4014E348",
"1.3.4": "6155D492540A99ECF0DA06D2B7EEBFE36231FBC2",
"1.3.5": "1E60C8F60CA1AAA9955441B4F4265C8288B95F33",
"1.3.6": "AA10A8DD1025D5033E291060C686B816513ADCAD",
"1.3.7": "A9881305EBD3DC5A6892D49BDAF540F56EE56232",
"1.3.8": "261CA25DAD6877DF3C57DA39947130867FCC09CE",
"1.3.9": "88A9E40631FC1C11A91A61CB3F4BE8C13C5E2BD3"
},
"ChangeList": "2333675",
"CompatibleChangeLists": [],
"Versions": [
{
"Name": "en",
"Version": "1.3.0",
"IndexSha1": {
"1.3.0": "6FB5B66EF8B3EECBBBEBE74A82BC23E3FC35450B"
}
},
{
"Name": "ja",
"Version": "1.3.0",
"IndexSha1": {
"1.3.0": "E4DA1960DB36CE8166C042AD8B9AF98C1A9119F3"
}
},
{
"Name": "ko",
"Version": "1.3.0",
"IndexSha1": {
"1.3.0": "498B379E95FC617385CCD832B8C359FA5AC220CE"
}
},
{
"Name": "zh",
"Version": "1.3.0",
"IndexSha1": {
"1.3.0": "CC58C357A80E7B3846264918197FC3ECAA1FE190"
}
}
],
"UpdateTime": 1725869509
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

12
builder.bat Normal file
View file

@ -0,0 +1,12 @@
docker build -t wicked-waifus-builder:2.1.0-SNAPSHOT -f Dockerfile-builder .
docker build -t wicked-waifus-config-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-config-server -f Dockerfile-service .
docker build -t wicked-waifus-hotpatch-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-server -f Dockerfile-service .
docker build -t wicked-waifus-login-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-login-server -f Dockerfile-service .
docker build -t wicked-waifus-gateway-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-gateway-server -f Dockerfile-service .
docker build -t wicked-waifus-game-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-game-server -f Dockerfile-service .
docker rmi wicked-waifus-builder:2.1.0-SNAPSHOT
: Persistence for the application
: docker volume create wicked-waifus-postgres-vol

12
builder.sh Normal file
View file

@ -0,0 +1,12 @@
docker build -t wicked-waifus-builder:2.1.0-SNAPSHOT -f Dockerfile-builder .
docker build -t wicked-waifus-config-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-config-server -f Dockerfile-service .
docker build -t wicked-waifus-hotpatch-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-hotpatch-server -f Dockerfile-service .
docker build -t wicked-waifus-login-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-login-server -f Dockerfile-service .
docker build -t wicked-waifus-gateway-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-gateway-server -f Dockerfile-service .
docker build -t wicked-waifus-game-server:2.1.0-SNAPSHOT --build-arg MICROSERVICE=wicked-waifus-game-server -f Dockerfile-service .
docker rmi wicked-waifus-builder:2.1.0-SNAPSHOT
# Persistence for the application
# docker volume create wicked-waifus-postgres-vol

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 /_/ ");
}

@ -0,0 +1 @@
Subproject commit 146c610632c8d9c3948c6a64ffc6cfe0258bcced

1
data/assets/game-data Submodule

@ -0,0 +1 @@
Subproject commit c00cdaefddc9c81ae6d79589c31ee169d59f2382

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

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

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"

20
data/docker/gateway.toml Normal file
View file

@ -0,0 +1,20 @@
service_id = 1
[network]
kcp_port = 7777
[protokey]
builtin_encryption_msg_id = [111, 112]
use_client_key = true
[service_end_point]
addr = "tcp://0.0.0.0:10003"
[game_server_end_point]
addr = "tcp://wicked-waifus-game-server:10004"
[database]
host = "wicked-waifus-postgres:5432"
user_name = "wicked_waifus_user"
password = "wicked_waifus_pass"
db_name = "wicked_waifus_db"

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"

63
docker-compose.yml Normal file
View file

@ -0,0 +1,63 @@
name: wicked-waifus-ps
services:
wicked-waifus-config-server:
image: wicked-waifus-config-server:2.1.0-SNAPSHOT
depends_on:
wicked-waifus-postgres:
condition: service_healthy
ports:
- '10001:10001'
volumes:
- "./data/docker/configserver.toml:/app/configserver.toml"
- "./data/assets/config:/app/assets/config"
wicked-waifus-login-server:
image: wicked-waifus-login-server:2.1.0-SNAPSHOT
depends_on:
wicked-waifus-postgres:
condition: service_healthy
ports:
- '5500:5500'
volumes:
- "./data/docker/loginserver.toml:/app/loginserver.toml"
wicked-waifus-gateway-server:
image: wicked-waifus-gateway-server:2.1.0-SNAPSHOT
depends_on:
wicked-waifus-postgres:
condition: service_healthy
ports:
# Uncomment this if you want to have manual access
# - '10003:10003'
- '7777:7777/udp'
volumes:
- "./data/docker/gateway.toml:/app/gateway.toml"
wicked-waifus-game-server:
image: wicked-waifus-game-server:2.1.0-SNAPSHOT
depends_on:
wicked-waifus-postgres:
condition: service_healthy
# Uncomment this if you want to have manual access
# ports:
# - '10004:10004'
volumes:
- "./data/docker/gameserver.toml:/app/gameserver.toml"
- "./data/assets/game-data:/app/data/assets/game-data"
wicked-waifus-postgres:
image: postgres:16.4-alpine3.20
user: postgres
# Uncomment this if you want to have manual access
ports:
- '5432:5432'
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
environment:
- "POSTGRES_PASSWORD=toor"
volumes:
- "./data/docker/postgres/scripts:/docker-entrypoint-initdb.d"
- wicked-waifus-postgres-vol:/var/lib/postgresql/data
volumes:
wicked-waifus-postgres-vol:
external: true

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,201 +0,0 @@
use std::collections::HashMap;
use shorekeeper_data::BasePropertyData;
use shorekeeper_protocol::{
entity_component_pb::ComponentPb, AttrData, AttributeComponentPb, EAttributeType,
EntityComponentPb, LivingStatus,
};
use crate::logic::ecs::component::Component;
pub struct Attribute {
pub attr_map: HashMap<EAttributeType, (i32, i32)>,
}
macro_rules! impl_from_data {
($($name:ident),*) => {
pub fn from_data(base_property: &BasePropertyData) -> Self {
Self {
attr_map: HashMap::from([$(
::paste::paste!((EAttributeType::[<$name:camel>], (base_property.$name, 0))),
)*]),
}
}
};
}
impl Component for Attribute {
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
pb.living_status = self
.is_alive()
.then_some(LivingStatus::Alive)
.unwrap_or(LivingStatus::Dead)
.into();
pb.component_pbs.push(EntityComponentPb {
component_pb: Some(ComponentPb::AttributeComponent(AttributeComponentPb {
attr_data: self
.attr_map
.iter()
.map(|(ty, (base, incr))| AttrData {
attribute_type: (*ty).into(),
base_value: *base,
increment: *incr,
})
.collect(),
hardness_mode_id: 0,
rage_mode_id: 0,
})),
})
}
}
impl Attribute {
pub fn is_alive(&self) -> bool {
self.attr_map
.get(&EAttributeType::Life)
.copied()
.unwrap_or_default()
.0
> 0
}
impl_from_data!(
lv,
life_max,
life,
sheild,
sheild_damage_change,
sheild_damage_reduce,
atk,
crit,
crit_damage,
def,
energy_efficiency,
cd_reduse,
reaction_efficiency,
damage_change_normal_skill,
damage_change,
damage_reduce,
damage_change_auto,
damage_change_cast,
damage_change_ultra,
damage_change_qte,
damage_change_phys,
damage_change_element1,
damage_change_element2,
damage_change_element3,
damage_change_element4,
damage_change_element5,
damage_change_element6,
damage_resistance_phys,
damage_resistance_element1,
damage_resistance_element2,
damage_resistance_element3,
damage_resistance_element4,
damage_resistance_element5,
damage_resistance_element6,
heal_change,
healed_change,
damage_reduce_phys,
damage_reduce_element1,
damage_reduce_element2,
damage_reduce_element3,
damage_reduce_element4,
damage_reduce_element5,
damage_reduce_element6,
reaction_change1,
reaction_change2,
reaction_change3,
reaction_change4,
reaction_change5,
reaction_change6,
reaction_change7,
reaction_change8,
reaction_change9,
reaction_change10,
reaction_change11,
reaction_change12,
reaction_change13,
reaction_change14,
reaction_change15,
energy_max,
energy,
special_energy_1_max,
special_energy_1,
special_energy_2_max,
special_energy_2,
special_energy_3_max,
special_energy_3,
special_energy_4_max,
special_energy_4,
strength_max,
strength,
strength_recover,
strength_punish_time,
strength_run,
strength_swim,
strength_fast_swim,
hardness_max,
hardness,
hardness_recover,
hardness_punish_time,
hardness_change,
hardness_reduce,
rage_max,
rage,
rage_recover,
rage_punish_time,
rage_change,
rage_reduce,
tough_max,
tough,
tough_recover,
tough_change,
tough_reduce,
tough_recover_delay_time,
element_power1,
element_power2,
element_power3,
element_power4,
element_power5,
element_power6,
special_damage_change,
strength_fast_climb_cost,
element_property_type,
weak_time,
ignore_def_rate,
ignore_damage_resistance_phys,
ignore_damage_resistance_element1,
ignore_damage_resistance_element2,
ignore_damage_resistance_element3,
ignore_damage_resistance_element4,
ignore_damage_resistance_element5,
ignore_damage_resistance_element6,
skill_tough_ratio,
strength_climb_jump,
strength_gliding,
mass,
braking_friction_factor,
gravity_scale,
speed_ratio,
damage_change_phantom,
auto_attack_speed,
cast_attack_speed,
status_build_up_1_max,
status_build_up_1,
status_build_up_2_max,
status_build_up_2,
status_build_up_3_max,
status_build_up_3,
status_build_up_4_max,
status_build_up_4,
status_build_up_5_max,
status_build_up_5,
paralysis_time_max,
paralysis_time,
paralysis_time_recover,
element_energy_max,
element_energy
);
}

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,15 +0,0 @@
mod attribute;
mod entity_config;
mod movement;
mod owner_player;
mod player_entity_marker;
mod position;
mod visibility;
pub use attribute::Attribute;
pub use entity_config::EntityConfig;
pub use movement::Movement;
pub use owner_player::OwnerPlayer;
pub use player_entity_marker::PlayerEntityMarker;
pub use position::Position;
pub use visibility::Visibility;

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_insert(Vec::new()))
}
pub fn is_in_world(&self, entity_id: i64) -> bool {
self.entity_manager.get(entity_id).is_some()
}
pub fn components(&self) -> &HashMap<Entity, Vec<RefCell<ComponentContainer>>> {
&self.components
}
pub fn get_entity_components(&self, entity: Entity) -> Vec<RefMut<ComponentContainer>> {
let Some(components) = self.components.get(&entity) else {
return Vec::with_capacity(0);
};
components.iter().map(|rc| rc.borrow_mut()).collect()
}
pub fn player_ids(&self) -> Keys<'_, i32, InWorldPlayer> {
self.in_world_players.keys()
}
pub fn players(&self) -> Values<'_, i32, InWorldPlayer> {
self.in_world_players.values()
}
pub fn set_in_world_player_data(&mut self, in_world_player: InWorldPlayer) {
self.in_world_players
.insert(in_world_player.player_id, in_world_player);
}
}

View file

@ -1,80 +0,0 @@
mod scene;
pub use scene::*;
use shorekeeper_protocol::message::Message;
macro_rules! handle_request {
($($name:ident;)*) => {
fn handle_request(player: &mut super::player::Player, mut msg: Message) {
use ::shorekeeper_protocol::{MessageID, Protobuf};
::paste::paste! {
match msg.get_message_id() {
$(
::shorekeeper_protocol::[<$name Request>]::MESSAGE_ID => {
let Ok(request) = ::shorekeeper_protocol::[<$name Request>]::decode(&*msg.remove_payload()) else {
tracing::debug!("failed to decode {}, player_id: {}", stringify!([<$name Request>]), player.basic_info.id);
return;
};
tracing::debug!("logic: processing request {}", stringify!([<$name Request>]));
let mut response = ::shorekeeper_protocol::[<$name Response>]::default();
[<on_ $name:snake _request>](player, request, &mut response);
player.respond(response, msg.get_rpc_id());
},
)*
unhandled => ::tracing::warn!("can't find handler for request with message_id={unhandled}")
}
}
}
};
}
macro_rules! handle_push {
($($name:ident;)*) => {
fn handle_push(player: &mut super::player::Player, mut msg: Message) {
use ::shorekeeper_protocol::{MessageID, Protobuf};
::paste::paste! {
match msg.get_message_id() {
$(
::shorekeeper_protocol::[<$name Push>]::MESSAGE_ID => {
let Ok(push) = ::shorekeeper_protocol::[<$name Push>]::decode(&*msg.remove_payload()) else {
tracing::debug!("failed to decode {}, player_id: {}", stringify!([<$name Push>]), player.basic_info.id);
return;
};
[<on_ $name:snake _push>](player, push);
},
)*
unhandled => ::tracing::warn!("can't find handler for push with message_id={unhandled}")
}
}
}
};
}
handle_request! {
UpdateSceneDate;
EntityActive;
EntityOnLanded;
}
handle_push! {
MovePackage;
}
pub fn handle_logic_message(player: &mut super::player::Player, msg: Message) {
match msg {
Message::Request { .. } => handle_request(player, msg),
Message::Push { .. } => handle_push(player, msg),
_ => tracing::warn!(
"handle_logic_message: wrong message type: {}, message_id: {}, player_id: {}",
msg.get_message_type(),
msg.get_message_id(),
player.basic_info.id,
),
}
}

View file

@ -1,77 +0,0 @@
use shorekeeper_protocol::{
EntityActiveRequest, EntityActiveResponse, EntityOnLandedRequest, EntityOnLandedResponse,
ErrorCode, MovePackagePush, UpdateSceneDateRequest, UpdateSceneDateResponse,
};
use crate::{logic::ecs::component::ComponentContainer, logic::player::Player, query_components};
pub fn on_update_scene_date_request(
_player: &Player,
_request: UpdateSceneDateRequest,
response: &mut UpdateSceneDateResponse,
) {
response.error_code = ErrorCode::Success.into();
}
pub fn on_entity_active_request(
player: &Player,
request: EntityActiveRequest,
response: &mut EntityActiveResponse,
) {
let world = player.world.borrow();
if !world.is_in_world(request.entity_id) {
tracing::debug!(
"EntityActiveRequest: entity with id {} doesn't exist, player_id: {}",
request.entity_id,
player.basic_info.id
);
return;
};
if let Some(position) = query_components!(world, request.entity_id, Position).0 {
// TODO: proper entity "activation" logic
response.pos = Some(position.0.get_position_protobuf());
response.rot = Some(position.0.get_rotation_protobuf());
}
response.component_pbs = Vec::new(); // not implemented
response.error_code = ErrorCode::Success.into();
}
pub fn on_entity_on_landed_request(
_: &Player,
request: EntityOnLandedRequest,
_: &mut EntityOnLandedResponse,
) {
tracing::debug!(
"EntityOnLandedRequest: entity with id {} landed",
request.entity_id
);
}
pub fn on_move_package_push(player: &mut Player, push: MovePackagePush) {
let world = player.world.borrow();
for moving_entity in push.moving_entities {
if !world.is_in_world(moving_entity.entity_id) {
tracing::debug!(
"MovePackage: entity with id {} doesn't exist",
moving_entity.entity_id
);
continue;
}
let Some(mut movement) = query_components!(world, moving_entity.entity_id, Movement).0
else {
tracing::warn!(
"MovePackage: entity {} doesn't have movement component",
moving_entity.entity_id
);
continue;
};
movement
.pending_movement_vec
.extend(moving_entity.move_infos);
}
}

View file

@ -1,243 +0,0 @@
use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc};
use basic_info::PlayerBasicInfo;
use common::time_util;
use location::PlayerLocation;
use player_func::PlayerFunc;
use shorekeeper_protocol::{
message::Message, PbGetRoleListNotify, PlayerBasicData, PlayerRoleData, PlayerSaveData,
ProtocolUnit,
};
use crate::session::Session;
use super::{
ecs::world::World,
role::{Role, RoleFormation},
};
mod basic_info;
mod in_world_player;
mod location;
mod player_func;
pub use in_world_player::InWorldPlayer;
pub struct Player {
session: Option<Arc<Session>>,
pub basic_info: PlayerBasicInfo,
pub role_list: Vec<Role>,
pub formation_list: Vec<RoleFormation>,
pub location: PlayerLocation,
pub func: PlayerFunc,
pub world: Rc<RefCell<World>>,
pub last_save_time: u64,
}
impl Player {
pub fn init(&mut self) {
if self.role_list.is_empty() {
self.on_first_enter();
}
// we need shorekeeper
// TODO: remove this part after implementing team switch
if !self.role_list.iter().any(|r| r.role_id == 1505) {
self.role_list.push(Role::new(1505));
}
self.formation_list.clear();
self.formation_list.push(RoleFormation {
id: 1,
cur_role: 1505,
role_id_set: HashSet::from([1505]),
is_current: true,
});
// End shorekeeper hardcode part
self.ensure_current_formation();
}
pub fn notify_general_data(&self) {
self.notify(self.basic_info.build_notify());
self.notify(self.func.build_func_open_notify());
self.notify(self.build_role_list_notify());
}
fn on_first_enter(&mut self) {
self.role_list.push(Self::create_main_character_role(
self.basic_info.name.clone(),
self.basic_info.sex,
));
let role = &self.role_list[0];
self.formation_list.push(RoleFormation {
id: 1,
cur_role: role.role_id,
role_id_set: HashSet::from([role.role_id]),
is_current: true,
});
self.location = PlayerLocation::default();
}
fn ensure_current_formation(&mut self) {
if self.formation_list.is_empty() {
let role = &self.role_list[0];
self.formation_list.push(RoleFormation {
id: 1,
cur_role: role.role_id,
role_id_set: HashSet::from([role.role_id]),
is_current: true,
});
}
if !self.formation_list.iter().any(|rf| rf.is_current) {
self.formation_list[0].is_current = true;
}
if let Some(rf) = self.formation_list.iter_mut().find(|rf| rf.is_current) {
if rf.role_id_set.is_empty() {
rf.role_id_set.insert(self.role_list[0].role_id);
}
if !rf.role_id_set.contains(&rf.cur_role) {
rf.cur_role = *rf.role_id_set.iter().nth(0).unwrap();
}
}
}
pub fn build_in_world_player(&self) -> InWorldPlayer {
InWorldPlayer {
player_id: self.basic_info.id,
player_name: self.basic_info.name.clone(),
player_icon: 0,
level: self.basic_info.level,
group_type: 1,
}
}
pub fn get_current_formation_role_list(&self) -> Vec<&Role> {
self.formation_list
.iter()
.find(|rf| rf.is_current)
.unwrap()
.role_id_set
.iter()
.map(|id| self.role_list.iter().find(|r| r.role_id == *id))
.flatten()
.collect()
}
pub fn get_cur_role_id(&self) -> i32 {
self.formation_list
.iter()
.find(|rf| rf.is_current)
.unwrap()
.cur_role
}
pub fn load_from_save(save_data: PlayerSaveData) -> Self {
let role_data = save_data.role_data.unwrap_or_default();
Self {
session: None,
basic_info: PlayerBasicInfo::load_from_save(save_data.basic_data.unwrap_or_default()),
role_list: role_data
.role_list
.into_iter()
.map(Role::load_from_save)
.collect(),
formation_list: role_data
.role_formation_list
.into_iter()
.map(RoleFormation::load_from_save)
.collect(),
location: save_data
.location_data
.map(PlayerLocation::load_from_save)
.unwrap_or_default(),
func: save_data
.func_data
.map(PlayerFunc::load_from_save)
.unwrap_or_default(),
world: Rc::new(RefCell::new(World::new())),
last_save_time: time_util::unix_timestamp(),
}
}
pub fn build_save_data(&self) -> PlayerSaveData {
PlayerSaveData {
basic_data: Some(self.basic_info.build_save_data()),
role_data: Some(PlayerRoleData {
role_list: self.role_list.iter().map(|r| r.build_save_data()).collect(),
role_formation_list: self
.formation_list
.iter()
.map(|rf| rf.build_save_data())
.collect(),
}),
location_data: Some(self.location.build_save_data()),
func_data: Some(self.func.build_save_data()),
}
}
pub fn set_session(&mut self, session: Arc<Session>) {
self.session = Some(session);
}
pub fn build_role_list_notify(&self) -> PbGetRoleListNotify {
PbGetRoleListNotify {
role_list: self.role_list.iter().map(|r| r.to_protobuf()).collect(),
}
}
pub fn notify(&self, content: impl ProtocolUnit) {
if let Some(session) = self.session.as_ref() {
session.forward_to_gateway(Message::Push {
sequence_number: 0,
message_id: content.get_message_id(),
payload: Some(content.encode_to_vec().into_boxed_slice()),
});
}
}
pub fn respond(&self, content: impl ProtocolUnit, rpc_id: u16) {
if let Some(session) = self.session.as_ref() {
session.forward_to_gateway(Message::Response {
sequence_number: 0,
message_id: content.get_message_id(),
rpc_id,
payload: Some(content.encode_to_vec().into_boxed_slice()),
});
}
}
fn create_main_character_role(name: String, sex: i32) -> Role {
let mut role = match sex {
0 => Role::new(Role::MAIN_CHARACTER_FEMALE_ID),
1 => Role::new(Role::MAIN_CHARACTER_MALE_ID),
_ => unreachable!(),
};
role.name = name;
role
}
pub fn create_default_save_data(id: i32, name: String, sex: i32) -> PlayerSaveData {
PlayerSaveData {
basic_data: Some(PlayerBasicData {
id,
name,
sex,
level: 1,
head_photo: 1505,
head_frame: 80060009,
..Default::default()
}),
..Default::default()
}
}
}

View file

@ -1,26 +0,0 @@
use crate::logic::ecs::component::ComponentContainer;
use shorekeeper_protocol::{EntityPb, PlayerSceneAoiData};
use crate::{logic::ecs::world::World, query_with};
pub fn build_scene_add_on_init_data(world: &World) -> PlayerSceneAoiData {
let entities = query_with!(world, PlayerEntityMarker)
.into_iter()
.map(|(e, _)| e)
.collect::<Vec<_>>();
let mut aoi_data = PlayerSceneAoiData::default();
for entity in entities {
let mut pb = EntityPb::default();
pb.id = entity.into();
world
.get_entity_components(entity)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
aoi_data.entities.push(pb);
}
aoi_data
}

View file

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

View file

@ -1,127 +0,0 @@
use shorekeeper_data::base_property_data;
use shorekeeper_protocol::{
EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, SceneMode,
ScenePlayerInformation, SceneTimeInfo,
};
use crate::{
logic::{
components::{
Attribute, EntityConfig, Movement, OwnerPlayer, PlayerEntityMarker, Position,
Visibility,
},
ecs::{component::ComponentContainer, world::World},
player::Player,
},
query_with,
};
use super::entity_serializer;
pub fn add_player_entities(world: &mut World, player: &Player) {
let cur_role_id = player.get_cur_role_id();
for role in player.get_current_formation_role_list() {
let id = world
.create_entity()
.with(ComponentContainer::PlayerEntityMarker(PlayerEntityMarker))
.with(ComponentContainer::EntityConfig(EntityConfig {
config_id: role.role_id,
config_type: EntityConfigType::Character,
}))
.with(ComponentContainer::OwnerPlayer(OwnerPlayer(
player.basic_info.id,
)))
.with(ComponentContainer::Position(Position(
player.location.position.clone(),
)))
.with(ComponentContainer::Visibility(Visibility(
role.role_id == cur_role_id,
)))
.with(ComponentContainer::Attribute(Attribute::from_data(
base_property_data::iter()
.find(|d| d.id == role.role_id)
.unwrap(),
)))
.with(ComponentContainer::Movement(Movement::default()))
.build();
tracing::debug!(
"created player entity, id: {}, role_id: {}",
i64::from(id),
role.role_id
);
}
}
pub fn build_scene_information(world: &World, instance_id: i32, owner_id: i32) -> SceneInformation {
SceneInformation {
scene_id: String::new(),
instance_id,
owner_id,
dynamic_entity_list: Vec::new(),
blackboard_params: Vec::new(),
end_time: 0,
aoi_data: Some(entity_serializer::build_scene_add_on_init_data(world)),
player_infos: build_player_info_list(world),
mode: SceneMode::Single.into(),
time_info: Some(SceneTimeInfo {
owner_time_clock_time_span: 0,
hour: 8,
minute: 0,
}),
cur_context_id: owner_id as i64,
..Default::default()
}
}
fn build_player_info_list(world: &World) -> Vec<ScenePlayerInformation> {
world
.players()
.map(|sp| {
let (cur_role_id, transform) = query_with!(
world,
PlayerEntityMarker,
OwnerPlayer,
Visibility,
EntityConfig,
Position
)
.into_iter()
.find_map(|(_, _, owner, visibility, conf, pos)| {
(sp.player_id == owner.0 && visibility.0).then_some((conf.config_id, pos.0.clone()))
})
.unwrap_or_default();
let active_characters =
query_with!(world, PlayerEntityMarker, OwnerPlayer, EntityConfig)
.into_iter()
.filter(|(_, _, owner, _)| owner.0 == sp.player_id);
ScenePlayerInformation {
cur_role: cur_role_id,
group_type: sp.group_type,
player_id: sp.player_id,
player_icon: sp.player_icon,
player_name: sp.player_name.clone(),
level: sp.level,
location: Some(transform.get_position_protobuf()),
rotation: Some(transform.get_rotation_protobuf()),
fight_role_infos: Vec::from([FightRoleInfos {
group_type: sp.group_type,
living_status: LivingStatus::Alive.into(),
cur_role: cur_role_id,
is_retain: true,
fight_role_infos: active_characters
.map(|(id, _, _, conf)| FightRoleInfo {
entity_id: id.into(),
role_id: conf.config_id,
})
.collect(),
..Default::default()
}]),
..Default::default()
}
})
.collect()
}

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,50 +0,0 @@
use paste::paste;
mod misc_data;
pub use misc_data::*;
#[derive(thiserror::Error, Debug)]
pub enum LoadDataError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse json: {0}")]
Json(#[from] serde_json::Error),
}
macro_rules! json_data {
($($table_type:ident;)*) => {
$(paste! {
mod [<$table_type:snake>];
pub use [<$table_type:snake>]::[<$table_type Data>];
})*
$(paste! {
pub mod [<$table_type:snake _data>] {
use std::sync::OnceLock;
type Data = super::[<$table_type Data>];
pub(crate) static TABLE: OnceLock<Vec<Data>> = OnceLock::new();
pub fn iter() -> std::slice::Iter<'static, Data> {
TABLE.get().unwrap().iter()
}
}
})*
pub fn load_json_data(base_path: &str) -> Result<(), LoadDataError> {
$(paste! {
let json_content = std::fs::read_to_string(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
let _ = [<$table_type:snake _data>]::TABLE.set(serde_json::from_str(&json_content)?);
})*
Ok(())
}
};
}
json_data! {
RoleInfo;
WeaponConf;
BaseProperty;
InstanceDungeon;
FunctionCondition;
}

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,128 +0,0 @@
use std::{
fs,
io::{self, BufRead},
path::Path,
};
use quote::quote;
const CODEGEN_OUT_DIR: &str = "generated/";
pub fn main() {
let _ = fs::create_dir(CODEGEN_OUT_DIR);
let config_file = "proto/config.csv";
let config_path = Path::new("proto/config.csv");
if config_path.exists() {
println!("cargo:rerun-if-changed={config_file}");
impl_proto_config(config_path, &Path::new("generated/proto_config.rs")).unwrap();
}
let proto_file = "proto/shorekeeper.proto";
if Path::new(&proto_file).exists() {
println!("cargo:rerun-if-changed={proto_file}");
prost_build::Config::new()
.out_dir(CODEGEN_OUT_DIR)
.type_attribute(".", "#[derive(shorekeeper_protocol_derive::MessageID)]")
.compile_protos(&[proto_file], &["shorekeeper"])
.unwrap();
impl_message_id(Path::new("generated/shorekeeper.rs")).unwrap();
}
let proto_file = "proto/internal.proto";
if Path::new(&proto_file).exists() {
println!("cargo:rerun-if-changed={proto_file}");
prost_build::Config::new()
.out_dir(CODEGEN_OUT_DIR)
.type_attribute(".", "#[derive(shorekeeper_protocol_derive::MessageID)]")
.compile_protos(&[proto_file], &["internal"])
.unwrap();
impl_message_id(Path::new("generated/internal.rs")).unwrap();
}
let proto_file = "proto/data.proto";
if Path::new(&proto_file).exists() {
println!("cargo:rerun-if-changed={proto_file}");
prost_build::Config::new()
.out_dir(CODEGEN_OUT_DIR)
.compile_protos(&[proto_file], &["data"])
.unwrap();
}
}
pub fn impl_proto_config(csv_path: &Path, codegen_path: &Path) -> io::Result<()> {
let file = fs::File::open(csv_path)?;
let reader = io::BufReader::new(file);
let mut match_arms = quote! {};
for line in reader.lines() {
let line = line?;
let mut row = line.split(',');
let message_id = row.next().unwrap().parse::<u16>().unwrap();
let flags = row.next().unwrap().parse::<u8>().unwrap();
match_arms = quote! {
#match_arms
#message_id => MessageFlags(#flags),
}
}
let generated_code = quote! {
pub mod proto_config {
#[derive(Clone, Copy)]
pub struct MessageFlags(u8);
impl MessageFlags {
pub fn value(self) -> u8 {
self.0
}
}
pub fn get_message_flags(id: u16) -> MessageFlags {
match id {
#match_arms
_ => MessageFlags(0),
}
}
}
}
.to_string();
fs::write(codegen_path, generated_code.as_bytes())?;
Ok(())
}
pub fn impl_message_id(path: &Path) -> io::Result<()> {
let file = fs::File::open(path)?;
let reader = io::BufReader::new(file);
let mut output = Vec::new();
let mut attr = None;
for line in reader.lines() {
let line = line?;
if line.contains("MessageId:") {
attr = Some(make_message_id_attr(&line).unwrap());
} else {
output.push(line);
if let Some(attr) = attr.take() {
output.push(attr);
}
}
}
fs::write(path, output.join("\n").as_bytes())?;
Ok(())
}
fn make_message_id_attr(line: &str) -> Option<String> {
let id = line.trim_start().split(' ').nth(2)?.parse::<u16>().ok()?;
Some(format!("#[message_id({id})]"))
}

File diff suppressed because it is too large Load diff

View file

@ -1,74 +0,0 @@
syntax = "proto3";
package data;
message VectorData {
float x = 1;
float y = 2;
float z = 3;
}
message TransformData {
VectorData position = 1;
VectorData rotation = 2;
}
message PlayerBasicData {
int32 id = 1;
string name = 2;
int32 sex = 3;
int32 level = 4;
int32 exp = 5;
int32 head_photo = 6;
int32 head_frame = 7;
}
message RoleSkillNodeData {
int32 node_id = 1;
bool is_active = 2;
int32 skill_id = 3;
}
message RoleData {
int32 role_id = 1;
string name = 2;
int32 level = 3;
int32 exp = 4;
int32 breakthrough = 5;
map<int32, int32> skill_map = 6;
map<int32, int32> phantom_map = 7;
int32 star = 8;
int32 favor = 9;
uint32 create_time = 10;
int32 cur_model = 11;
repeated int32 models = 12;
repeated RoleSkillNodeData skill_node_state = 13;
int32 resonant_chain_group_index = 14;
}
message RoleFormationData {
int32 formation_id = 1;
int32 cur_role = 2;
repeated int32 role_id_list = 3;
bool is_current = 4;
}
message PlayerRoleData {
repeated RoleData role_list = 1;
repeated RoleFormationData role_formation_list = 2;
}
message PlayerLocationData {
int32 instance_id = 1;
TransformData position = 2;
}
message PlayerFuncData {
map<int32, int32> func_map = 1;
}
message PlayerSaveData {
PlayerBasicData basic_data = 1;
PlayerRoleData role_data = 2;
PlayerLocationData location_data = 3;
PlayerFuncData func_data = 4;
}

File diff suppressed because it is too large Load diff

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

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

View file

@ -4,7 +4,7 @@ pub trait TomlConfig: DeserializeOwned {
const DEFAULT_TOML: &str; const DEFAULT_TOML: &str;
} }
pub fn load_or_create<'a, C>(path: &str) -> C pub fn load_or_create<C>(path: &str) -> C
where where
C: DeserializeOwned + TomlConfig, C: DeserializeOwned + TomlConfig,
{ {

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

@ -4,7 +4,7 @@ pub fn unix_timestamp() -> u64 {
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_secs() as u64 .as_secs()
} }
pub fn unix_timestamp_ms() -> u64 { pub fn unix_timestamp_ms() -> u64 {

View file

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

View file

@ -0,0 +1,10 @@
[network]
http_addr = "0.0.0.0:10001"
[encryption]
key = "t+AEu5SGdpz06tomonajLMau9AJgmyTvVhz9VtGf1+0="
iv = "fprc5lBWADQB7tim0R2JxQ=="
[serve]
serve_web_path = "/"
serve_dir_path = "data/assets/config-server"

View file

@ -1,19 +1,25 @@
use std::fs;
use std::sync::LazyLock; use std::sync::LazyLock;
use anyhow::Result; use anyhow::Result;
use common::config_util::{self, TomlConfig};
use serde::Deserialize; use serde::Deserialize;
use shorekeeper_http::{
config::{AesSettings, NetworkSettings}, use wicked_waifus_commons::config_util::{self, TomlConfig};
use wicked_waifus_http::{
Application, Application,
config::{AesSettings, NetworkSettings},
}; };
#[derive(Deserialize)]
pub struct ServeConfig {
pub serve_web_path: String,
pub serve_dir_path: String,
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ServerConfig { pub struct ServerConfig {
pub network: NetworkSettings, pub network: NetworkSettings,
pub encryption: AesSettings, pub encryption: AesSettings,
pub serve: ServeConfig,
} }
impl TomlConfig for ServerConfig { impl TomlConfig for ServerConfig {
@ -25,21 +31,21 @@ async fn main() -> Result<()> {
static CONFIG: LazyLock<ServerConfig> = static CONFIG: LazyLock<ServerConfig> =
LazyLock::new(|| config_util::load_or_create("configserver.toml")); LazyLock::new(|| config_util::load_or_create("configserver.toml"));
::common::splash::print_splash(); ::wicked_waifus_commons::splash::print_splash();
::common::logging::init(::tracing::Level::DEBUG); ::wicked_waifus_commons::logging::init_axum(::tracing::Level::DEBUG);
tracing::debug!(
"Serving files from {} at {}",
&CONFIG.serve.serve_web_path,
&CONFIG.serve.serve_dir_path
);
Application::new() Application::new()
.get("/index.json", get_index) .serve_dir(&CONFIG.serve.serve_web_path, &CONFIG.serve.serve_dir_path)
.with_encryption(&CONFIG.encryption) .with_encryption(&CONFIG.encryption)
.with_logger()
.serve(&CONFIG.network) .serve(&CONFIG.network)
.await?; .await?;
Ok(()) Ok(())
} }
async fn get_index() -> &'static str {
static INDEX: LazyLock<String> =
LazyLock::new(|| fs::read_to_string("assets/config/index.json").unwrap());
&*INDEX
}

View file

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

View file

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

View file

@ -0,0 +1,30 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct AreaData {
pub area_id: i32,
pub level: i32,
pub country_id: i32,
pub delivery_mark_id: i32,
pub area_name: String,
pub map_config_id: i32,
pub title: String,
pub father: i32,
pub tag: Vec<i32>,
pub record: i32,
pub tips: i32,
pub is_init_actived: bool,
#[serde(rename = "WorldMonsterLevelMax")]
pub world_monster_level_max: HashMap<i32, i32>,
#[serde(rename = "WuYinQuID")]
pub wu_yin_qu_id: i32,
pub state_id: i32,
pub atmosphere_id: i32,
pub edge_wall_name: String,
pub delivery_mark_type: i32,
pub sort_index: i32,
pub enter_area_tags: HashMap<String, i32>,
pub leave_area_tags: HashMap<String, i32>,
}

View file

@ -16,7 +16,6 @@ pub struct BasePropertyData {
pub def: i32, pub def: i32,
pub energy_efficiency: i32, pub energy_efficiency: i32,
pub cd_reduse: i32, pub cd_reduse: i32,
pub reaction_efficiency: i32,
pub damage_change_normal_skill: i32, pub damage_change_normal_skill: i32,
pub damage_change: i32, pub damage_change: i32,
pub damage_reduce: i32, pub damage_reduce: i32,
@ -140,4 +139,5 @@ pub struct BasePropertyData {
pub paralysis_time_recover: i32, pub paralysis_time_recover: i32,
pub element_energy_max: i32, pub element_energy_max: i32,
pub element_energy: i32, pub element_energy: i32,
pub element_efficiency: i32,
} }

View file

@ -0,0 +1,12 @@
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct DragonPoolData {
pub id: i32,
pub core_id: i32,
pub goal: Vec<i32>,
pub drop_ids: Vec<i32>,
pub dark_coast_delivery_list: Vec<i32>,
pub auto_take: bool,
}

View file

@ -0,0 +1,17 @@
use std::collections::HashMap;
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ExploreToolsData {
pub phantom_skill_id: i32,
pub skill_type: i32,
pub sort_id: i32,
pub auto_fill: bool,
pub show_unlock: bool,
pub skill_group_id: i32,
pub is_use_in_phantom_team: bool,
pub summon_config_id: i32,
pub authorization: HashMap<i32, i32>,
}

View file

@ -0,0 +1,9 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GachaData {
pub id: i32,
pub rule_group_id: i32,
pub sort: i32,
}

View file

@ -0,0 +1,9 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GachaPoolData {
pub id: i32,
pub gacha_id: i32,
pub sort: i32,
}

View file

@ -0,0 +1,22 @@
use serde::Deserialize;
use crate::GachaViewTypeInfoId;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GachaViewInfoData {
pub id: i32,
pub r#type: GachaViewTypeInfoId,
pub summary_title: String,
pub summary_describe: String,
// TODO: Effect path
pub theme_color: String,
pub content_texture_path: String,
pub content_texture_bg_path: String,
pub under_bg_texture_path: String,
pub tag_not_selected_sprite_path: String,
pub tag_selected_sprite_path: String,
pub weapon_prefab_path: String,
pub up_list: Vec<i32>,
pub show_id_list: Vec<i32>,
pub preview_id_list: Vec<i32>,
}

View file

@ -0,0 +1,11 @@
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct GuideTutorialData {
pub id: i32,
pub tutorial_type: i32,
pub tutorial_order: i32,
pub page_id: Vec<i32>,
pub tutorial_tip: bool,
}

View file

@ -0,0 +1,17 @@
use serde::Deserialize;
use crate::{ComponentsData, RawVectorData};
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct LevelEntityConfigData {
pub id: i32,
pub map_id: i32,
pub entity_id: i64,
pub blueprint_type: String,
pub name: String,
pub in_sleep: bool,
pub is_hidden: bool,
pub area_id: i32,
pub transform: Vec<RawVectorData>,
pub components_data: ComponentsData,
}

View file

@ -0,0 +1,194 @@
use std::fs::File;
use std::io::BufReader;
use paste::paste;
pub use misc_data::*;
mod misc_data;
#[derive(thiserror::Error, Debug)]
pub enum LoadDataError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Failed to parse json: {0}")]
Json(#[from] serde_json::Error),
}
macro_rules! json_data {
($($table_type:ident;)*) => {
$(paste! {
mod [<$table_type:snake>];
pub use [<$table_type:snake>]::[<$table_type Data>];
})*
$(paste! {
pub mod [<$table_type:snake _data>] {
use std::sync::OnceLock;
type Data = super::[<$table_type Data>];
pub(crate) static TABLE: OnceLock<Vec<Data>> = OnceLock::new();
pub fn iter() -> std::slice::Iter<'static, Data> {
TABLE.get().unwrap().iter()
}
}
})*
fn load_json_data(base_path: &str) -> Result<(), LoadDataError> {
$(paste! {
let file = File::open(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
let reader = BufReader::new(file);
let _ = [<$table_type:snake _data>]::TABLE.set(serde_json::from_reader(reader)?);
})*
Ok(())
}
};
}
macro_rules! json_hash_table_data {
($($table_type:ident, $key_param:expr, $key_type:ty;)*) => {
$(paste! {
mod [<$table_type:snake>];
pub use [<$table_type:snake>]::[<$table_type Data>];
})*
$(paste! {
pub mod [<$table_type:snake _data>] {
use std::collections::HashMap;
use std::sync::OnceLock;
pub(crate) type Data = super::[<$table_type Data>];
pub(crate) static TABLE: OnceLock<HashMap<$key_type, Data>> = OnceLock::new();
pub fn iter() -> std::collections::hash_map::Iter<'static, $key_type, Data> {
TABLE.get().unwrap().iter()
}
pub fn get(k: &$key_type) -> Option<&Data> {
TABLE.get().unwrap().get(k)
}
}
})*
fn load_json_hash_table_data(base_path: &str) -> Result<(), LoadDataError> {
$(paste! {
let file = File::open(&format!("{}/{}.json", base_path, stringify!($table_type)))?;
let reader = BufReader::new(file);
let _ = [<$table_type:snake _data>]::TABLE.set(
serde_json::from_reader::<BufReader<File>, Vec<[<$table_type:snake _data>]::Data>>(reader)?
.into_iter()
.map(|element| (element.$key_param, element))
.collect::<std::collections::HashMap<_, _>>()
);
})*
Ok(())
}
};
}
pub fn load_all_json_data(base_path: &str) -> Result<(), LoadDataError> {
load_json_data(base_path)?;
load_json_hash_table_data(base_path)?;
Ok(())
}
json_data! {
AdventureTask;
Area;
BaseProperty;
ExploreTools;
FunctionCondition;
Gacha;
GachaPool;
GachaViewInfo;
GuideTutorial;
InstanceDungeon;
LordGym;
RoleInfo;
Teleporter;
WeaponConf;
}
json_hash_table_data! {
DragonPool, id, i32;
LevelEntityConfig, entity_id, i64;
// TemplateConfig, blueprint_type, String;
}
mod textmap;
pub mod text_map_data {
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::BufReader;
use std::sync::OnceLock;
use crate::LoadDataError;
use crate::textmap::TextMapData;
use crate::gacha_view_info_data;
static EMPTY: OnceLock<HashMap<String, String>> = OnceLock::new();
static TABLE: OnceLock<HashMap<String, HashMap<String, String>>> = OnceLock::new();
pub fn load_textmaps(base_path: &str) -> Result<(), LoadDataError> {
// TODO: Ideally we would expose a function here to allow other components to add to the
// filter, since right now only gacha uses it, we can do this
let filters = get_filters();
let languages = std::fs::read_dir(base_path)?
.filter_map(|entry| entry.ok())
.filter(|entry| entry.path().is_dir())
.collect::<Vec<_>>();
let mut result: HashMap<String, HashMap<String, String>> = HashMap::new();
for language in languages {
let lang_id = language.file_name().to_str().unwrap().to_string();
let file = File::open(&format!("{base_path}/{lang_id}/multi_text/MultiText.json"))?;
let reader = BufReader::new(file);
result.insert(
lang_id,
serde_json::from_reader::<BufReader<File>, Vec<TextMapData>>(reader)?
.into_iter()
.filter(|element| filters.contains(&element.id))
.map(|element| (element.id, element.content))
.collect::<HashMap<_, _>>(),
);
}
let _ = TABLE.set(result);
Ok(())
}
pub fn get_textmap(language: i32) -> &'static HashMap<String, String> {
let (text_code, _audio_code) = get_language_from_i32(language);
TABLE.get_or_init(|| HashMap::new())
.get(text_code)
.unwrap_or(EMPTY.get_or_init(|| HashMap::new()))
}
fn get_language_from_i32(language: i32) -> (&'static str, &'static str) {
match language {
0 => ("zh-Hans", "zh"),
1 => ("en", "en"),
2 => ("ja", "ja"),
3 => ("ko", "ko"),
4 => ("ru", "en"),
5 => ("zh-Hant", "zh"),
6 => ("de", "en"),
7 => ("es", "en"),
8 => ("pt", "en"),
9 => ("id", "en"),
10 => ("fr", "en"),
11 => ("vi", "en"),
12 => ("th", "en"),
_ => ("en", "en"),
}
}
fn get_filters() -> HashSet<String> {
let mut filters = HashSet::new();
for gacha_view_info in gacha_view_info_data::iter() {
filters.insert(gacha_view_info.summary_title.clone());
filters.insert(gacha_view_info.summary_describe.clone());
}
filters
}
}

View file

@ -0,0 +1,20 @@
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct LordGymData {
pub id: i32,
pub difficulty: i32,
pub reward_id: i32,
pub play_id: i32,
pub gym_title: String,
pub icon_path: String,
pub play_description: String,
pub help_id: i32,
pub monster_list: Vec<i32>,
pub monster_level: i32,
pub lock_con: i32,
pub lock_description: String,
pub filter_type: i32,
pub is_debug: bool,
}

View file

@ -0,0 +1,252 @@
use serde::Deserialize;
use serde_repr::Deserialize_repr;
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct PropValueData {
pub id: i32,
pub value: f32,
pub is_ratio: bool,
}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct VectorData([f32; 3]);
impl VectorData {
pub fn get_x(&self) -> f32 {
self.0[0]
}
pub fn get_y(&self) -> f32 {
self.0[1]
}
pub fn get_z(&self) -> f32 {
self.0[2]
}
}
#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct RawVectorData {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl RawVectorData {
pub fn get_x(&self) -> f32 {
self.x
}
pub fn get_y(&self) -> f32 {
self.y
}
pub fn get_z(&self) -> f32 {
self.z
}
}
#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct EntranceEntityData {
pub dungeon_id: i32,
pub entrance_entity_id: i32,
}
#[derive(Deserialize_repr, PartialEq, Debug, Copy, Clone)]
#[repr(i32)]
pub enum GachaViewTypeInfoId {
NoviceConvene = 1,
FeaturedResonatorConvene = 2,
FeaturedWeaponConvene = 3,
StandardResonatorConvene = 4,
StandardWeaponConvene = 5,
BeginnersChoiceConvene = 6,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct Category {
pub main_type: Option<String>,
pub monster_match_type: Option<i32>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct ScanFunction {
pub scan_id: Option<i32>,
pub is_concealed: Option<bool>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct WorldLevelBonusType {
pub r#type: Option<i32>,
pub world_level_bonus_id: Option<i32>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct BaseInfoComponent {
pub tid_name: Option<String>,
pub category: Option<Category>,
pub camp: Option<i32>,
pub online_interact_type: Option<i32>,
pub scan_function: Option<ScanFunction>,
pub aoi_layer: Option<i32>,
pub entity_property_id: Option<i32>,
pub focus_priority: Option<i32>,
pub aoi_zradius: Option<i32>,
// TODO: Add more
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct AiComponent {
pub disabled: Option<bool>,
pub ai_id: Option<i32>,
// TODO: Add more
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct AttributeComponent {
pub property_id: Option<i32>,
pub level: Option<i32>,
pub world_level_bonus_type: Option<WorldLevelBonusType>,
pub rage_mode_id: Option<i32>,
pub hardness_mode_id: Option<i32>,
pub monster_prop_extra_rate_id: Option<i32>,
pub world_level_bonus_id: Option<i32>,
pub fight_music: Option<String>,
// TODO: Add more
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct TeleportPosition {
pub x: Option<f32>,
pub y: Option<f32>,
pub z: Option<f32>,
pub a: Option<f32>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct TeleportComponent {
pub disabled: Option<bool>,
pub teleporter_id: Option<i32>,
#[serde(rename = "TeleportPos")]
pub teleport_position: Option<TeleportPosition>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ComponentsData {
pub base_info_component: Option<BaseInfoComponent>,
pub ai_component: Option<AiComponent>,
pub attribute_component: Option<AttributeComponent>,
pub teleport_component: Option<TeleportComponent>,
// TODO: Implement this ones
// pub scene_actor_ref_component: Option<serde_json::Value>,
// pub effect_area_component: Option<serde_json::Value>,
// pub entity_state_component: Option<serde_json::Value>,
// pub condition_listener_component: Option<serde_json::Value>,
// pub interact_component: Option<serde_json::Value>,
// pub npc_perform_component: Option<serde_json::Value>,
// pub var_component: Option<serde_json::Value>,
// pub entity_visible_component: Option<serde_json::Value>,
// pub level_ai_component: Option<serde_json::Value>,
// pub trigger_component: Option<serde_json::Value>,
// pub range_component: Option<serde_json::Value>,
// pub spline_component: Option<serde_json::Value>,
// pub bubble_component: Option<serde_json::Value>,
// pub reward_component: Option<serde_json::Value>,
// pub refresh_component: Option<serde_json::Value>,
// pub passerby_npc_spawn_component: Option<serde_json::Value>,
// pub vision_capture_component: Option<serde_json::Value>,
// pub refresh_group_component: Option<serde_json::Value>,
// pub collect_component: Option<serde_json::Value>,
// pub target_gear_component: Option<serde_json::Value>,
// pub fight_interact_component: Option<serde_json::Value>,
// pub guide_line_creator_component: Option<serde_json::Value>,
// pub photo_target_component: Option<serde_json::Value>,
// pub model_component: Option<serde_json::Value>,
// pub entity_group_component: Option<serde_json::Value>,
// pub scene_item_life_cycle_component: Option<serde_json::Value>,
// pub entity_state_audio_component: Option<serde_json::Value>,
// pub animal_component: Option<serde_json::Value>,
// pub monster_component: Option<serde_json::Value>,
// pub nearby_tracking_component: Option<serde_json::Value>,
// pub follow_track_component: Option<serde_json::Value>,
// pub jigsaw_foundation: Option<serde_json::Value>,
// pub treasure_box_component: Option<serde_json::Value>,
// pub hook_lock_point: Option<serde_json::Value>,
// pub explore_skill_interact_component: Option<serde_json::Value>,
// pub attach_target_component: Option<serde_json::Value>,
// pub target_gear_group_component: Option<serde_json::Value>,
// pub spawn_monster_component: Option<serde_json::Value>,
// pub skybox_component: Option<serde_json::Value>,
// pub destructible_item: Option<serde_json::Value>,
// pub fan_component: Option<serde_json::Value>,
// pub state_hint_component: Option<serde_json::Value>,
// pub buff_consumer_component: Option<serde_json::Value>,
// pub reset_entities_pos_component: Option<serde_json::Value>,
// pub group_ai_component: Option<serde_json::Value>,
// pub pulling_object_foundation: Option<serde_json::Value>,
// pub lift_component: Option<serde_json::Value>,
// pub scene_item_movement_component: Option<serde_json::Value>,
// pub reset_self_pos_component: Option<serde_json::Value>,
// pub jigsaw_item: Option<serde_json::Value>,
// pub level_play_component: Option<serde_json::Value>,
// pub interact_gear_component: Option<serde_json::Value>,
// pub ai_gear_strategy_component: Option<serde_json::Value>,
// pub pick_interact_component: Option<serde_json::Value>,
// pub level_sequence_frame_event_component: Option<serde_json::Value>,
// pub air_wall_spawner_component: Option<serde_json::Value>,
// pub progress_bar_control_component: Option<serde_json::Value>,
// pub batch_bullet_caster_component: Option<serde_json::Value>,
// pub client_trigger_component: Option<serde_json::Value>,
// pub enrichment_area_component: Option<serde_json::Value>,
// pub vehicle_component: Option<serde_json::Value>,
// pub item_foundation2: Option<serde_json::Value>,
// pub tele_control2: Option<serde_json::Value>,
// pub interact_audio_component: Option<serde_json::Value>,
// pub level_qte_component: Option<serde_json::Value>,
// pub resurrection_component: Option<serde_json::Value>,
// pub ai_alert_notify_component: Option<serde_json::Value>,
// pub trample_component: Option<serde_json::Value>,
// pub dungeon_entry_component: Option<serde_json::Value>,
// pub level_prefab_perform_component: Option<serde_json::Value>,
// pub render_specified_range_component: Option<serde_json::Value>,
// pub walking_pattern_component: Option<serde_json::Value>,
// pub no_render_portal_component: Option<serde_json::Value>,
// pub adsorb_component: Option<serde_json::Value>,
// pub beam_cast_component: Option<serde_json::Value>,
// pub beam_receive_component: Option<serde_json::Value>,
// pub timeline_track_control_component: Option<serde_json::Value>,
// pub scene_bullet_component: Option<serde_json::Value>,
// pub edit_custom_aoi_component: Option<serde_json::Value>,
// pub combat_component: Option<serde_json::Value>,
// pub location_safety_component: Option<serde_json::Value>,
// pub turntable_control_component: Option<serde_json::Value>,
// pub scene_item_ai_component: Option<serde_json::Value>,
// pub buff_producer_component: Option<serde_json::Value>,
// pub portal_component: Option<serde_json::Value>,
// pub inhalation_ability_component: Option<serde_json::Value>,
// pub inhaled_item_component: Option<serde_json::Value>,
// pub monster_gacha_base_component: Option<serde_json::Value>,
// pub monster_gacha_item_component: Option<serde_json::Value>,
// pub time_stop_component: Option<serde_json::Value>,
// pub hit_component: Option<serde_json::Value>,
// pub levitate_magnet_component: Option<serde_json::Value>,
// pub rebound_component: Option<serde_json::Value>,
// pub rotator_component2: Option<serde_json::Value>,
// pub conveyor_belt_component: Option<serde_json::Value>,
// pub dynamic_portal_creator_component: Option<serde_json::Value>,
// pub connector_component: Option<serde_json::Value>,
// pub monitor_component: Option<serde_json::Value>,
}

View file

@ -17,6 +17,7 @@ pub struct RoleInfoData {
pub priority: i32, pub priority: i32,
pub show_property: Vec<i32>, pub show_property: Vec<i32>,
pub element_id: i32, pub element_id: i32,
pub skin_id: i32,
pub spillover_item: HashMap<i32, i32>, pub spillover_item: HashMap<i32, i32>,
pub breach_model: i32, pub breach_model: i32,
pub special_energy_bar_id: i32, pub special_energy_bar_id: i32,

View file

@ -0,0 +1,17 @@
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct TeleporterData {
pub id: i32,
pub map_id: i32,
pub object_id: i32,
pub area_id: i32,
pub fog_id: i32,
pub r#type: i32,
pub teleport_entity_config_id: i64,
pub plot: String,
pub after_network_action: i32,
pub show_world_map: bool,
}

View file

@ -0,0 +1,11 @@
use serde::Deserialize;
use crate::ComponentsData;
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct TemplateConfigData {
pub id: i32,
pub blueprint_type: String,
pub name: String,
pub components_data: ComponentsData,
}

View file

@ -0,0 +1,8 @@
use serde::Deserialize;
#[derive(Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct TextMapData {
pub id: String,
pub content: String,
}

View file

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

View file

@ -0,0 +1,34 @@
[package]
name = "wicked-waifus-game-server"
edition = "2021"
version.workspace = true
[dependencies]
# Framework
tokio.workspace = true
# Serialization
serde.workspace = true
# Util
uuid = { version = "1.11.0", features = ["v4"] }
rand = "0.9.0-alpha.2"
# Used for debug
#serde_json = "1.0.135"
anyhow.workspace = true
thiserror.workspace = true
paste.workspace = true
dashmap.workspace = true
hex.workspace = true
# Tracing
tracing.workspace = true
# Internal
wicked-waifus-commons.workspace = true
wicked-waifus-data.workspace = true
wicked-waifus-database.workspace = true
wicked-waifus-network.workspace = true
wicked-waifus-protocol-internal.workspace = true
wicked-waifus-protocol.workspace = true

View file

@ -11,3 +11,8 @@ addr = "tcp://127.0.0.1:10004"
[gateway_end_point] [gateway_end_point]
addr = "tcp://127.0.0.1:10003" addr = "tcp://127.0.0.1:10003"
[game_server_config]
load_textmaps = true
# Do not change yet, issues to be solved
quadrant_size = 1000000

View file

@ -0,0 +1,12 @@
const UE = require("ue");
const { CharacterDitherEffectController } = require("../NewWorld/Character/Common/Component/Effect/CharacterDitherEffectController");
CharacterDitherEffectController.prototype.SetDitherEffect = function(t, i = 3, s = true) { };
CharacterDitherEffectController.prototype.EnterAppearEffect = function(t = 1, i = 3, s = true) {
this.SetHiddenInGame(false, true);
};
CharacterDitherEffectController.prototype.EnterDisappearEffect = function(t = 1, i = 3, s = true) {
this.SetHiddenInGame(true, true);
};

View file

@ -0,0 +1,9 @@
setTimeout(() => {
const UiManager_1 = require("../Ui/UiManager");
const UE = require("ue");
const ControllerManagerBase_1 = require("../../Core/Framework/ControllerManagerBase");
const UiText = UiManager_1.UiManager.GetViewByName("UidView").GetText(0);
UiText.SetText("{PLAYER_USERNAME} - Reversed Rooms");
UiText.SetColor(UE. Color.FromHex("{SELECTED_COLOR}"));
}, 10000);

View file

@ -0,0 +1 @@
require('../Module/WaterMask/WaterMaskController').WaterMaskView.EOo();

View file

@ -1,7 +1,8 @@
use common::config_util::TomlConfig;
use serde::Deserialize; use serde::Deserialize;
use shorekeeper_database::DatabaseSettings;
use shorekeeper_network::config::ServiceEndPoint; use wicked_waifus_commons::config_util::TomlConfig;
use wicked_waifus_database::DatabaseSettings;
use wicked_waifus_network::config::ServiceEndPoint;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ServiceConfig { pub struct ServiceConfig {
@ -9,6 +10,13 @@ pub struct ServiceConfig {
pub database: DatabaseSettings, pub database: DatabaseSettings,
pub service_end_point: ServiceEndPoint, pub service_end_point: ServiceEndPoint,
pub gateway_end_point: ServiceEndPoint, pub gateway_end_point: ServiceEndPoint,
pub game_server_config: GameServerConfig,
}
#[derive(Deserialize)]
pub struct GameServerConfig {
pub load_textmaps: bool,
pub quadrant_size: f32,
} }
impl TomlConfig for ServiceConfig { impl TomlConfig for ServiceConfig {

View file

@ -1,6 +1,6 @@
use std::sync::OnceLock; use std::sync::OnceLock;
use shorekeeper_network::{config::ServiceEndPoint, ServiceClient, ServiceMessage}; use wicked_waifus_network::{config::ServiceEndPoint, ServiceClient, ServiceMessage};
static CLIENT: OnceLock<ServiceClient> = OnceLock::new(); static CLIENT: OnceLock<ServiceClient> = OnceLock::new();

View file

@ -0,0 +1,65 @@
use wicked_waifus_data::BasePropertyData;
use wicked_waifus_protocol::{
entity_component_pb::ComponentPb, AttrData, AttributeComponentPb, EAttributeType,
EntityComponentPb, LivingStatus,
};
use std::collections::HashMap;
use crate::logic::ecs::component::Component;
use crate::logic::utils::load_role_info::attribute_from_data;
pub struct Attribute {
pub attr_map: HashMap<EAttributeType, (i32, i32)>,
}
impl Component for Attribute {
fn set_pb_data(&self, pb: &mut wicked_waifus_protocol::EntityPb) {
pb.living_status = (if self.is_alive() {
LivingStatus::Alive
} else {
LivingStatus::Dead
})
.into();
pb.component_pbs.push(EntityComponentPb {
component_pb: Some(ComponentPb::AttributeComponent(
self.build_entity_attribute(),
)),
});
}
}
impl Attribute {
pub fn is_alive(&self) -> bool {
self.attr_map
.get(&EAttributeType::Life)
.copied()
.unwrap_or_default()
.0
> 0
}
#[inline(always)]
pub fn from_data(base_property: &BasePropertyData) -> Self {
Self {
attr_map: attribute_from_data(base_property),
}
}
#[inline(always)]
pub fn build_entity_attribute(&self) -> AttributeComponentPb {
AttributeComponentPb {
attr_data: self
.attr_map
.iter()
.map(|(ty, (base, incr))| AttrData {
attribute_type: (*ty).into(),
current_value: *base,
value_increment: *incr,
})
.collect(),
hardness_mode_id: 0,
rage_mode_id: 0,
}
}
}

View file

@ -0,0 +1,19 @@
use wicked_waifus_protocol::{AutonomousComponentPb, EntityComponentPb};
use wicked_waifus_protocol::entity_component_pb::ComponentPb;
use crate::logic::ecs::component::Component;
pub struct Autonomous {
pub autonomous_id: i32
}
impl Component for Autonomous {
fn set_pb_data(&self, pb: &mut wicked_waifus_protocol::EntityPb) {
pb.component_pbs.push(EntityComponentPb {
component_pb: Some(ComponentPb::AutonomousComponentPb(AutonomousComponentPb {
autonomous_id: self.autonomous_id,
ji: vec![],
})),
})
}
}

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