Compare commits

...

19 commits

Author SHA1 Message Date
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
116 changed files with 7394452 additions and 186845 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

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
.idea
/target
/hotpatch.toml
/configserver.toml

106
Cargo.lock generated
View file

@ -402,7 +402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
@ -416,7 +416,7 @@ checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
@ -724,13 +724,19 @@ dependencies = [
"allocator-api2",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
[[package]]
name = "hashlink"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown",
"hashbrown 0.14.5",
]
[[package]]
@ -825,6 +831,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range-header"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a"
[[package]]
name = "httparse"
version = "1.9.4"
@ -884,12 +896,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.4.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.15.0",
]
[[package]]
@ -1006,6 +1018,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matchit"
version = "0.7.3"
@ -1043,6 +1064,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1462,8 +1493,17 @@ checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
@ -1474,9 +1514,15 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.4"
@ -1725,6 +1771,7 @@ dependencies = [
"rbase64",
"serde",
"thiserror",
"tower-http",
"tracing",
]
@ -1745,10 +1792,14 @@ version = "0.1.0"
dependencies = [
"byteorder",
"crc32fast",
"prettyplease",
"prost",
"prost-build",
"quote",
"serde",
"serde_json",
"shorekeeper-protocol-derive",
"syn",
"thiserror",
]
@ -1882,7 +1933,7 @@ dependencies = [
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown",
"hashbrown 0.14.5",
"hashlink",
"hex",
"indexmap",
@ -2269,6 +2320,31 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower-http"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"http-range-header",
"httpdate",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
@ -2331,10 +2407,14 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
@ -2345,6 +2425,12 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicase"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-bidi"
version = "0.3.15"

View file

@ -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"] }
@ -41,18 +42,18 @@ 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/" }
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/" }
shorekeeper-protokey = { path = "shorekeeper-protokey" }
[profile.release]
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:1.4.0-SNAPSHOT /app/target/release/$MICROSERVICE ./service
CMD ["./service"]

View file

@ -24,7 +24,22 @@ cargo run --bin gateway-server
cargo run --bin game-server
```
##### b) using pre-built binaries
##### b) building from sources(docker edition)
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/Shorekeeper/Shorekeeper/releases)
page and download the latest release for your platform.<br>
Launch all servers: `config-server`, `hotpatch-server`, `login-server`, `gateway-server`, `game-server`

View file

@ -0,0 +1,64 @@
{
"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

@ -0,0 +1,60 @@
{
"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": "1"
},
{
"url": "https://cdn-qcloud-cn-mc.aki-game.com/prod/client/",
"weight": "5443"
}
],
"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": "fcQrWOPC2RkghpzsAE2kyGWD37QTm2mR",
"ResUri": "fcQrWOPC2RkghpzsAE2kyGWD37QTm2mR",
"LoginServers": [
{
"id": "f9e0fc655c1931bc03ad976e9fc14473",
"ip": "http://127.0.0.1:5500",
"name": "Camellya"
}
],
"PrivateServers": {
"enable": false,
"serverUrl": ""
}
},
"p1": {
"IosAuditFirstDownloadTip": false
}
}

View file

@ -2,8 +2,20 @@
"default": {
"CdnUrl": [
{
"url": "http://127.0.0.1:10002/prod/client/",
"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": [],

View file

@ -0,0 +1,115 @@
{
"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 +1,44 @@
{
"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"
},
"PackageVersion": "1.4.0",
"LauncherVersion": "1.4.0",
"ResourceVersion": "1.4.3",
"LauncherIndexSha1": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"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"
"1.4.1": "0C747E444EC6DBA11D0C57B34E2638DE5182029F",
"1.4.2": "41751A94DBD406653DB52262E85BB716E45D9864",
"1.4.3": "424EDFD97BA8699CBC2CB9E27D3FCBB6F7C70806"
},
"ChangeList": "2333675",
"ChangeList": "2585778",
"CompatibleChangeLists": [],
"Versions": [
{
"Name": "en",
"Version": "1.3.0",
"Version": "1.4.0",
"IndexSha1": {
"1.3.0": "6FB5B66EF8B3EECBBBEBE74A82BC23E3FC35450B"
"1.4.0": "34F9AC326B3E81E972B93094E43751967A6A3396"
}
},
{
"Name": "ja",
"Version": "1.3.0",
"Version": "1.4.0",
"IndexSha1": {
"1.3.0": "E4DA1960DB36CE8166C042AD8B9AF98C1A9119F3"
"1.4.0": "3233223428D586FBC0A2EC817F4E94DDD10C646F"
}
},
{
"Name": "ko",
"Version": "1.3.0",
"Version": "1.4.0",
"IndexSha1": {
"1.3.0": "498B379E95FC617385CCD832B8C359FA5AC220CE"
"1.4.0": "3CC0312BDFFF5FA4D3F5256E2618D0EF6AA84912"
}
},
{
"Name": "zh",
"Version": "1.3.0",
"Version": "1.4.0",
"IndexSha1": {
"1.3.0": "CC58C357A80E7B3846264918197FC3ECAA1FE190"
"1.4.0": "2F3AD1412C6991FE0E146C5E2F6F86D8D9F44A5D"
}
}
],
"UpdateTime": 1725869509
}
"UpdateTime": 1729243476
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,246 @@
[
{
"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

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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 one or more lines are too long

File diff suppressed because one or more lines are too long

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:1.4.0-SNAPSHOT -f Dockerfile-builder .
docker build -t wicked-waifus-config-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=config-server -f Dockerfile-service .
docker build -t wicked-waifus-hotpatch-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=hotpatch-server -f Dockerfile-service .
docker build -t wicked-waifus-login-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=login-server -f Dockerfile-service .
docker build -t wicked-waifus-gateway-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=gateway-server -f Dockerfile-service .
docker build -t wicked-waifus-game-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=game-server -f Dockerfile-service .
docker rmi wicked-waifus-builder:1.4.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:1.4.0-SNAPSHOT -f Dockerfile-builder .
docker build -t wicked-waifus-config-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=config-server -f Dockerfile-service .
docker build -t wicked-waifus-hotpatch-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=hotpatch-server -f Dockerfile-service .
docker build -t wicked-waifus-login-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=login-server -f Dockerfile-service .
docker build -t wicked-waifus-gateway-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=gateway-server -f Dockerfile-service .
docker build -t wicked-waifus-game-server:1.4.0-SNAPSHOT --build-arg MICROSERVICE=game-server -f Dockerfile-service .
docker rmi wicked-waifus-builder:1.4.0-SNAPSHOT
# Persistence for the application
# docker volume create wicked-waifus-postgres-vol

View file

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

View file

@ -1,4 +1,6 @@
use tracing::Level;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
pub fn init(max_level: Level) {
tracing_subscriber::fmt()
@ -6,3 +8,21 @@ pub fn init(max_level: 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

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

View file

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

View file

@ -1,4 +1,3 @@
use std::fs;
use std::sync::LazyLock;
use anyhow::Result;
@ -26,20 +25,14 @@ async fn main() -> Result<()> {
LazyLock::new(|| config_util::load_or_create("configserver.toml"));
::common::splash::print_splash();
::common::logging::init(::tracing::Level::DEBUG);
::common::logging::init_axum(::tracing::Level::DEBUG);
Application::new()
.get("/index.json", get_index)
.serve_dir("/", "assets/config")
.with_encryption(&CONFIG.encryption)
.with_logger()
.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,13 @@
name: wicked-waifus-ps
services:
wicked-waifus-hotpatch-server:
image: wicked-waifus-hotpatch-server:1.4.0-SNAPSHOT
depends_on:
wicked-waifus-postgres:
condition: service_healthy
ports:
- '10002:10002'
volumes:
- "./docker/hotpatch.toml:/app/hotpatch.toml"
- "./assets/hotpatch:/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:1.4.0-SNAPSHOT
depends_on:
wicked-waifus-postgres:
condition: service_healthy
ports:
- '10001:10001'
volumes:
- "./docker/configserver.toml:/app/configserver.toml"
- "./assets/config:/app/assets/config"
wicked-waifus-login-server:
image: wicked-waifus-login-server:1.4.0-SNAPSHOT
depends_on:
wicked-waifus-postgres:
condition: service_healthy
ports:
- '5500:5500'
volumes:
- "./docker/loginserver.toml:/app/loginserver.toml"
wicked-waifus-gateway-server:
image: wicked-waifus-gateway-server:1.4.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:
- "./docker/gateway.toml:/app/gateway.toml"
wicked-waifus-game-server:
image: wicked-waifus-game-server:1.4.0-SNAPSHOT
depends_on:
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"
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:
- "./docker/postgres/scripts:/docker-entrypoint-initdb.d"
- wicked-waifus-postgres-vol:/var/lib/postgresql/data
volumes:
wicked-waifus-postgres-vol:
external: true

6
docker/configserver.toml Normal file
View file

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

13
docker/gameserver.toml Normal file
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
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"

6
docker/hotpatch.toml Normal file
View file

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

12
docker/loginserver.toml Normal file
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,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

@ -1,52 +1,31 @@
use std::collections::HashMap;
use shorekeeper_data::BasePropertyData;
use shorekeeper_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)>,
}
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.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,
})),
})
component_pb: Some(ComponentPb::AttributeComponent(
self.build_entity_attribute(),
)),
});
}
}
@ -60,142 +39,27 @@ impl Attribute {
> 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
);
#[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(),
base_value: *base,
increment: *incr,
})
.collect(),
hardness_mode_id: 0,
rage_mode_id: 0,
}
}
}

View file

@ -1,15 +1,19 @@
use shorekeeper_protocol::EntityConfigType;
use shorekeeper_protocol::{EEntityType, EntityConfigType, EntityState};
use crate::logic::ecs::component::Component;
pub struct EntityConfig {
pub config_id: i32,
pub config_type: EntityConfigType,
pub entity_type: EEntityType,
pub entity_state: EntityState
}
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();
pb.entity_type = self.entity_type.into();
pb.entity_state = self.entity_state.into();
}
}

View file

@ -0,0 +1,19 @@
use crate::logic::ecs::component::Component;
use shorekeeper_protocol::entity_component_pb::ComponentPb;
use shorekeeper_protocol::{EntityComponentPb, EquipComponentPb};
pub struct Equip {
pub weapon_id: i32,
pub weapon_breach_level: i32,
}
impl Component for Equip {
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
pb.component_pbs.push(EntityComponentPb {
component_pb: Some(ComponentPb::EquipComponent(EquipComponentPb {
weapon_id: self.weapon_id,
weapon_breach_level: self.weapon_breach_level,
})),
})
}
}

View file

@ -0,0 +1,25 @@
use crate::logic::ecs::component::Component;
use shorekeeper_protocol::entity_component_pb::ComponentPb;
use shorekeeper_protocol::{DFsm, DFsmBlackBoard, EntityComponentPb, EntityFsmComponentPb, FsmCustomBlackboardDatas};
pub struct Fsm {
pub fsms: Vec<DFsm>,
pub hash_code: i32,
pub common_hash_code: i32,
pub black_board: Vec<DFsmBlackBoard>,
pub fsm_custom_blackboard_datas: Option<FsmCustomBlackboardDatas>,
}
impl Component for Fsm {
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
pb.component_pbs.push(EntityComponentPb {
component_pb: Some(ComponentPb::EntityFsmComponentPb(EntityFsmComponentPb {
fsms: self.fsms.clone(),
hash_code: self.hash_code,
common_hash_code: self.common_hash_code,
black_board: self.black_board.clone(),
fsm_custom_blackboard_datas: self.fsm_custom_blackboard_datas.clone(),
})),
})
}
}

View file

@ -1,15 +1,23 @@
mod attribute;
mod entity_config;
mod equip;
mod movement;
mod owner_player;
mod player_entity_marker;
mod position;
mod visibility;
mod vision_skill;
mod monster_ai;
mod fsm;
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;
pub use monster_ai::MonsterAi;
pub use fsm::Fsm;

View file

@ -0,0 +1,23 @@
use crate::logic::ecs::component::Component;
use shorekeeper_protocol::entity_component_pb::ComponentPb;
use shorekeeper_protocol::{EntityComponentPb, MonsterAiComponentPb};
pub struct MonsterAi {
pub weapon_id: i32,
pub hatred_group_id: i64,
pub ai_team_init_id: i32,
pub combat_message_id: i64,
}
impl Component for MonsterAi {
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
pb.component_pbs.push(EntityComponentPb {
component_pb: Some(ComponentPb::MonsterAiComponentPb(MonsterAiComponentPb {
weapon_id: self.weapon_id,
hatred_group_id: self.hatred_group_id,
ai_team_init_id: self.ai_team_init_id,
combat_message_id: self.combat_message_id,
})),
})
}
}

View file

@ -0,0 +1,23 @@
use shorekeeper_protocol::{
entity_component_pb::ComponentPb, EntityComponentPb, VisionSkillComponentPb,
VisionSkillInformation,
};
use crate::logic::ecs::component::Component;
pub struct VisionSkill {
pub skill_id: i32,
}
impl Component for VisionSkill {
fn set_pb_data(&self, pb: &mut shorekeeper_protocol::EntityPb) {
pb.component_pbs.push(EntityComponentPb {
component_pb: Some(ComponentPb::VisionSkillComponent(VisionSkillComponentPb {
vision_skill_infos: vec![VisionSkillInformation {
skill_id: self.skill_id,
..Default::default()
}],
})),
})
}
}

View file

@ -30,6 +30,10 @@ impl_component_container! {
Attribute;
PlayerEntityMarker;
Movement;
Equip;
VisionSkill;
MonsterAi;
Fsm;
}
pub trait Component {

View file

@ -1,34 +1,118 @@
use std::{cell::RefCell, collections::HashSet};
use super::component::ComponentContainer;
use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{AtomicI32, Ordering};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Entity(i64);
pub struct Entity {
pub entity_id: i32,
pub entity_type: i32,
pub map_id: i32,
}
pub struct EntityBuilder<'comp>(Entity, &'comp mut Vec<RefCell<ComponentContainer>>);
#[derive(Default)]
pub struct EntityManager {
entity_id_counter: i64,
active_entity_set: HashSet<Entity>,
active_entity_set: HashMap<i32, Vec<Entity>>,
next_id: AtomicI32,
recycled_ids: HashMap<i32, VecDeque<i32>>,
}
impl EntityManager {
pub fn create(&mut self) -> Entity {
self.entity_id_counter += 1;
let entity = Entity(self.entity_id_counter);
pub fn create(&mut self, config_id: i32, entity_type: i32, map_id: i32) -> Entity {
let entity_id = self
.recycled_ids
.get_mut(&config_id)
.and_then(|ids| ids.pop_front())
.unwrap_or_else(|| self.next_id.fetch_add(1, Ordering::Relaxed));
let entity = Entity {
entity_id,
entity_type,
map_id,
};
self.active_entity_set
.entry(config_id)
.or_default()
.push(entity);
self.active_entity_set.insert(entity);
entity
}
pub fn get(&self, id: i64) -> Option<Entity> {
self.active_entity_set.get(&Entity(id)).copied()
pub fn get_entity_id(&self, config_id: i32) -> i32 {
self.active_entity_set
.get(&config_id)
.and_then(|entities| entities.first())
.map(|entity| entity.entity_id)
.unwrap_or_else(|| {
tracing::error!("Entity Configuration ID {} not found.", config_id);
-1
})
}
#[expect(dead_code)]
pub fn remove(&mut self, entity: Entity) -> bool {
self.active_entity_set.remove(&entity)
pub fn get_config_id(&self, entity_id: i32) -> i32 {
self.active_entity_set
.iter()
.find_map(|(config_id, entities)| {
entities
.iter()
.any(|e| e.entity_id == entity_id)
.then_some(*config_id)
})
.unwrap_or_else(|| {
tracing::error!("Entity ID {} not found.", entity_id);
-1
})
}
pub fn get(&self, config_id: i32) -> Entity {
self.active_entity_set
.get(&config_id)
.and_then(|entities| entities.first())
.cloned()
.unwrap_or_else(|| {
tracing::error!("Entity Configuration ID {} not found.", config_id);
Entity::default()
})
}
pub fn get_all_entity_id(&self) -> Vec<i32> {
self.active_entity_set
.iter()
.flat_map(|(_, entities)| entities.iter().map(|e| e.entity_id))
.collect()
}
pub fn active_entity_empty(&self) -> bool {
self.active_entity_set.is_empty()
}
pub fn get_entity_ids_by_map(&self, map_id: i32) -> Vec<i32> {
self.active_entity_set
.iter()
.flat_map(|(_, entities)| {
entities
.iter()
.filter(|e| e.map_id == map_id)
.map(|e| e.entity_id)
})
.collect()
}
#[inline(always)]
pub fn remove(&mut self, entity_id: i32) -> bool {
for (config_id, entities) in self.active_entity_set.iter_mut() {
if let Some(index) = entities.iter().position(|e| e.entity_id == entity_id) {
let entity = entities.remove(index);
self.recycled_ids
.entry(*config_id)
.or_default()
.push_back(entity.entity_id);
return true;
}
}
false
}
}
@ -52,6 +136,26 @@ impl<'comp> EntityBuilder<'comp> {
impl From<Entity> for i64 {
fn from(value: Entity) -> Self {
value.0
value.entity_id as i64
}
}
impl Default for EntityManager {
fn default() -> Self {
Self {
active_entity_set: HashMap::new(),
next_id: AtomicI32::new(1),
recycled_ids: HashMap::new(),
}
}
}
impl Default for Entity {
fn default() -> Self {
Self {
entity_id: -1,
entity_type: -1,
map_id: 8,
}
}
}

View file

@ -2,28 +2,56 @@ pub mod component;
pub mod entity;
pub mod world;
// Query specified components from all entities
#[macro_export]
macro_rules! find_component {
($comps:expr, $comp:ident) => {
$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
}
})
};
}
// Query specified components from all entities (and)
#[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
($world_entitys:expr, $($comp:ident),*) => {
$world_entitys.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(),
$crate::find_component!(comps, $comp).unwrap(),
)*
)
})
.collect::<Vec<_>>()
};
}
// Query specified components from all entities (or)
#[macro_export]
macro_rules! query_hn_with {
($world_entitys:expr, $($comp:ident),*) => {
$world_entitys.components().iter().filter(|(_, comps)| {
$(comps.iter().any(|comp| matches!(&*comp.borrow(), ComponentContainer::$comp(_))) || )
* false
})
.map(|(e, comps)| {
(*e,
$(
$crate::find_component!(comps, $comp),
)*
)
})
@ -41,24 +69,24 @@ macro_rules! ident_as_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))
($world_entitys:expr, $entity_id:expr, $($comp:ident),*) => {
$world_entitys.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
}
}),
$crate::find_component!(comps, $comp),
)*)
})
.unwrap_or_else(|| ($( crate::ident_as_none!($comp), )*))
.unwrap_or_else(|| ($( $crate::ident_as_none!($comp), )*))
};
}
#[macro_export]
macro_rules! modify_component {
($comps:expr, $comp:ident, $modifier:expr) => {
$comps.iter_mut().for_each(|comp| {
if let ComponentContainer::$comp(ref mut inner_comp) = &mut **comp {
$modifier(inner_comp);
}
});
};
}

View file

@ -1,48 +1,30 @@
use super::component::ComponentContainer;
use super::entity::{Entity, EntityBuilder, EntityManager};
use crate::logic::player::InWorldPlayer;
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 WorldEntity {
components: HashMap<i32, Vec<RefCell<ComponentContainer>>>,
entity_manager: EntityManager,
}
pub struct World {
components: HashMap<Entity, Vec<RefCell<ComponentContainer>>>,
entity_manager: EntityManager,
in_world_players: HashMap<i32, InWorldPlayer>, // joined players metadata
pub player_cur_map_id: i32,
pub world_entitys: HashMap<i32, WorldEntity>, // i32 -> map_id
pub in_world_players: HashMap<i32, InWorldPlayer>, // joined players metadata
}
impl World {
pub fn new() -> Self {
Self {
components: HashMap::new(),
entity_manager: EntityManager::default(),
player_cur_map_id: 8,
world_entitys: HashMap::new(),
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()
}
@ -55,4 +37,84 @@ impl World {
self.in_world_players
.insert(in_world_player.player_id, in_world_player);
}
pub fn get_mut_world_entity(&mut self) -> &mut WorldEntity {
self.world_entitys
.get_mut(&self.player_cur_map_id)
.unwrap_or_else(|| panic!("Failed to get cur map data: {}", self.player_cur_map_id))
}
pub fn get_world_entity(&self) -> &WorldEntity {
self.world_entitys
.get(&self.player_cur_map_id)
.unwrap_or_else(|| panic!("Failed to get cur map data: {}", self.player_cur_map_id))
}
}
impl WorldEntity {
pub fn create_entity(
&mut self,
config_id: i32,
entity_type: i32,
map_id: i32,
) -> EntityBuilder {
let entity = self.entity_manager.create(config_id, entity_type, map_id);
EntityBuilder::builder(
entity,
self.components
.entry(entity.entity_id)
.or_insert(Vec::new()),
)
}
pub fn is_in_all_world_map(&self, entity_id: i32) -> bool {
self.entity_manager.get_all_entity_id().contains(&entity_id)
}
pub fn is_in_world_map(&self, entity_id: i32, map_id: i32) -> bool {
self.entity_manager
.get_entity_ids_by_map(map_id)
.contains(&entity_id)
}
pub fn get_entity_id(&self, config_id: i32) -> i64 {
self.entity_manager.get_entity_id(config_id) as i64
}
pub fn get_config_id(&self, entity_id: i32) -> i32 {
self.entity_manager.get_config_id(entity_id)
}
pub fn get_entity(&self, config_id: i32) -> Entity {
self.entity_manager.get(config_id)
}
pub fn components(&self) -> &HashMap<i32, Vec<RefCell<ComponentContainer>>> {
&self.components
}
pub fn get_entity_components(&self, entity_id: i32) -> Vec<RefMut<ComponentContainer>> {
if let Some(components) = self.components.get(&entity_id) {
components.iter().map(|rc| rc.borrow_mut()).collect()
} else {
Vec::new()
}
}
pub fn remove_entity(&mut self, entity_id: i32) -> bool {
self.components.remove(&entity_id).is_some() && self.entity_manager.remove(entity_id)
}
pub fn active_entity_empty(&self) -> bool {
self.entity_manager.active_entity_empty()
}
}
impl Default for WorldEntity {
fn default() -> Self {
Self {
components: HashMap::new(),
entity_manager: EntityManager::default(),
}
}
}

View file

@ -0,0 +1,73 @@
use crate::logic::player::Player;
use shorekeeper_protocol::combat_message::{
combat_receive_data, combat_request_data, combat_response_data, combat_send_data,
CombatReceiveData, CombatRequestData, CombatResponseData, CombatSendPackRequest,
CombatSendPackResponse,
};
use shorekeeper_protocol::{ErrorCode, SwitchRoleRequest, SwitchRoleResponse};
#[inline(always)]
fn create_combat_response(
combat_request: &CombatRequestData,
message: combat_response_data::Message,
) -> CombatReceiveData {
CombatReceiveData {
message: Some(combat_receive_data::Message::CombatResponseData(
CombatResponseData {
combat_common: combat_request.combat_common,
request_id: combat_request.request_id,
message: Some(message),
},
)),
}
}
pub fn on_combat_message_combat_send_pack_request(
player: &mut Player,
request: CombatSendPackRequest,
response: &mut CombatSendPackResponse,
) {
for data in request.data.iter() {
if let Some(combat_send_data::Message::Request(ref request_data)) = data.message {
if let Some(ref request_message) = request_data.message {
match request_message {
combat_request_data::Message::SwitchRoleRequest(ref request) => {
handle_switch_role_request(player, request_data, request, response);
}
_ => {}
}
}
}
}
response.error_code = ErrorCode::Success.into();
}
fn handle_switch_role_request(
player: &mut Player,
combat_request: &CombatRequestData,
request: &SwitchRoleRequest,
response: &mut CombatSendPackResponse,
) {
// Find current formation and update current role
if let Some(formation) = player.formation_list.values_mut().find(|f| f.is_current) {
formation.cur_role = request.role_id;
let receive_pack = response
.receive_pack_notify
.get_or_insert_with(Default::default);
receive_pack.data.push(create_combat_response(
combat_request,
combat_response_data::Message::SwitchRoleResponse(SwitchRoleResponse {
error_code: ErrorCode::Success.into(),
role_id: request.role_id,
}),
));
} else {
tracing::error!("Role with id {} not found", request.role_id);
response.error_code = ErrorCode::ErrSwitchRoleEntityNotExist.into();
return;
}
response.error_code = ErrorCode::Success.into();
}

View file

@ -0,0 +1,139 @@
use shorekeeper_protocol::{EntityActiveRequest, EntityActiveResponse, EntityLoadCompleteRequest,
EntityLoadCompleteResponse, EntityOnLandedRequest,
EntityOnLandedResponse, EntityPb, EntityPositionRequest,
EntityPositionResponse, ErrorCode, MovePackagePush};
use crate::{logic, logic::ecs::component::ComponentContainer, logic::player::Player, query_components};
pub fn on_entity_active_request(
player: &Player,
request: EntityActiveRequest,
response: &mut EntityActiveResponse,
) {
let world_ref = player.world.borrow();
let world = world_ref.get_world_entity();
if !world.is_in_all_world_map(request.entity_id as i32) {
tracing::debug!(
"EntityActiveRequest: entity with id {} doesn't exist, player_id: {}",
request.entity_id,
player.basic_info.id
);
return;
};
let component_pbs = {
let mut pb = EntityPb {
id: request.entity_id,
..Default::default()
};
world.get_entity_components(request.entity_id as i32)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
pb.component_pbs
};
// TODO: Remove attribute
if let (Some(position), Some(_attribute)) =
query_components!(world, request.entity_id, Position, Attribute)
{
response.is_visible = true;
response.pos = Some(position.0.get_position_protobuf());
response.rot = Some(position.0.get_rotation_protobuf());
response.component_pbs.extend_from_slice(&component_pbs);
response.error_code = ErrorCode::Success.into();
} else {
tracing::error!(
"EntityActiveRequest: entity with id {} not found",
request.entity_id
);
response.error_code = ErrorCode::ErrEntityNotFound.into(); // TODO: replace with appropriate error code
return;
};
}
pub fn on_entity_on_landed_request(
_: &Player,
request: EntityOnLandedRequest,
_: &mut EntityOnLandedResponse,
) {
// TODO: More implementation?
tracing::debug!(
"EntityOnLandedRequest: entity with id {} landed",
request.entity_id
);
}
pub fn on_entity_position_request(
_: &Player,
request: EntityPositionRequest,
_: &mut EntityPositionResponse,
) {
// TODO: Implement this
tracing::debug!(
"EntityPositionRequest: config with id {} for map {} position requested",
request.config_id,
request.map_id
);
}
pub fn on_entity_load_complete_request(
_: &Player,
request: EntityLoadCompleteRequest,
_: &mut EntityLoadCompleteResponse,
) {
// TODO: Implement this
tracing::debug!(
"EntityLoadCompleteRequest: for ids {:?}",
request.entity_ids
);
}
pub fn on_move_package_push(player: &mut Player, push: MovePackagePush) {
for moving_entity in push.moving_entities {
// Query components borrows world component so lets wrap it
{
let world_ref = player.world.borrow();
let world = world_ref.get_world_entity();
if !world.is_in_all_world_map(moving_entity.entity_id as i32) {
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);
}
// TODO: review instance id vs map id in world
let map = logic::utils::quadrant_util::get_map(player.location.instance_id);
let quadrant_id = map.get_quadrant_id(
player.location.position.position.x * 100.0,
player.location.position.position.y * 100.0,
);
// TODO: This may require some changes for Co-Op
if quadrant_id != player.quadrant_id {
let (entities_to_remove, entities_to_add) = map.get_update_entities(player.quadrant_id, quadrant_id);
player.quadrant_id = quadrant_id;
logic::utils::world_util::remove_entities(player, &entities_to_remove);
logic::utils::world_util::add_entities(player, &entities_to_add);
}
}
}

View file

@ -0,0 +1,12 @@
use shorekeeper_protocol::{GuideInfoRequest, GuideInfoResponse};
use crate::logic::player::Player;
pub fn on_guide_info_request(
_player: &Player,
_request: GuideInfoRequest,
response: &mut GuideInfoResponse,
) {
// TODO: Implement this
response.guide_group_finish_list = Vec::new();
}

View file

@ -0,0 +1,16 @@
use shorekeeper_protocol::{MailBind, MailBindInfoRequest, MailBindInfoResponse};
use crate::logic::player::Player;
pub fn on_mail_bind_info_request(
_: &Player,
_: MailBindInfoRequest,
response: &mut MailBindInfoResponse,
) {
// TODO: Implement this
response.mail_bind = Some(MailBind {
is_bind: true,
is_reward: true,
close_time: -1,
});
}

View file

@ -0,0 +1,49 @@
use shorekeeper_protocol::{
ErrorCode, Hih, InputSettingRequest, InputSettingResponse, InputSettingUpdateRequest,
InputSettingUpdateResponse, LanguageSettingUpdateRequest, LanguageSettingUpdateResponse,
ServerPlayStationPlayOnlyStateRequest, ServerPlayStationPlayOnlyStateResponse, VersionInfoPush,
};
use crate::logic::player::Player;
pub fn on_input_setting_request(
_: &Player,
_: InputSettingRequest,
response: &mut InputSettingResponse,
) {
response.hih = Some(Hih::default());
}
pub fn on_input_setting_update_request(
_: &Player,
_: InputSettingUpdateRequest,
response: &mut InputSettingUpdateResponse,
) {
response.error_code = ErrorCode::Success.into();
}
pub fn on_language_setting_update_request(
_: &Player,
_: LanguageSettingUpdateRequest,
response: &mut LanguageSettingUpdateResponse,
) {
response.error_code = ErrorCode::Success.into();
}
pub fn on_server_play_station_play_only_state_request(
_: &Player,
_: ServerPlayStationPlayOnlyStateRequest,
response: &mut ServerPlayStationPlayOnlyStateResponse,
) {
response.play_station_play_only_state = false;
}
pub fn on_version_info_push(_player: &Player, push: VersionInfoPush) {
// TODO: Shall we do safety check and ensure we have compatible versions?
tracing::debug!(
"Client versions: launcher: {}, app: {}, resources: {}",
push.launcher_version,
push.app_version,
push.resource_version
);
}

View file

@ -1,31 +1,53 @@
mod combat;
mod entity;
mod guide;
mod mail;
mod misc;
mod role;
mod scene;
mod skill;
pub use combat::*;
pub use entity::*;
pub use guide::*;
pub use mail::*;
pub use misc::*;
pub use role::*;
pub use scene::*;
pub use skill::*;
use shorekeeper_protocol::message::Message;
macro_rules! handle_request {
($($name:ident;)*) => {
($($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::[<$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);
::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!([<$name Request>]));
tracing::debug!("logic: processing request {}", stringify!($($inner_package::)?[<$name Request>]));
let mut response = ::shorekeeper_protocol::[<$name Response>]::default();
[<on_ $name:snake _request>](player, request, &mut response);
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}")
unhandled => {
::tracing::warn!("can't find handler for request with message_id={unhandled}");
let tmp = &*msg.remove_payload();
let (name, value) = shorekeeper_protocol::proto_dumper::get_debug_info(
unhandled, tmp,
).unwrap_or_else(|err| ("Error", err.to_string()));
tracing::debug!("trying to log unhandled data for message {name} with:\n{value}")
}
}
}
}
@ -33,23 +55,32 @@ macro_rules! handle_request {
}
macro_rules! handle_push {
($($name:ident;)*) => {
($($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::[<$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);
::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;
};
[<on_ $name:snake _push>](player, push);
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}")
unhandled => {
::tracing::warn!("can't find handler for push with message_id={unhandled}");
let tmp = &*msg.remove_payload();
let (name, value) = shorekeeper_protocol::proto_dumper::get_debug_info(
unhandled, tmp,
).unwrap_or_else(|err| ("Error", err.to_string()));
tracing::debug!("trying to log unhandled data for message {name} with:\n{value}")
}
}
}
}
@ -57,13 +88,49 @@ macro_rules! handle_push {
}
handle_request! {
UpdateSceneDate;
// Combat
CombatSendPack, combat_message;
// Role
RoleShowListUpdate;
ClientCurrentRoleReport;
RoleFavorList;
FormationAttr;
UpdateFormation;
// Entity
EntityActive;
EntityOnLanded;
EntityPosition;
EntityLoadComplete;
// Guide
GuideInfo;
// Mail
MailBindInfo;
// Misc
InputSetting;
InputSettingUpdate;
LanguageSettingUpdate;
ServerPlayStationPlayOnlyState;
// Scene
SceneTrace;
SceneLoadingFinish;
UpdateSceneDate;
// Skill
VisionExploreSkillSet;
}
handle_push! {
// Entity
MovePackage;
// Misc
VersionInfo;
}
pub fn handle_logic_message(player: &mut super::player::Player, msg: Message) {

View file

@ -0,0 +1,141 @@
use crate::logic::player::Player;
use crate::logic::role::{Role, RoleFormation};
use shorekeeper_protocol::{
ClientCurrentRoleReportRequest, ClientCurrentRoleReportResponse, ERemoveEntityType, ErrorCode,
FormationAttrRequest, FormationAttrResponse, RoleFavorListRequest, RoleFavorListResponse,
RoleShowListUpdateRequest, RoleShowListUpdateResponse, UpdateFormationRequest,
UpdateFormationResponse,
};
use std::collections::HashSet;
pub fn on_role_show_list_update_request(
player: &mut Player,
request: RoleShowListUpdateRequest,
response: &mut RoleShowListUpdateResponse,
) {
let role_ids: HashSet<i32> = player.role_list.keys().cloned().collect();
let all_exist = request.role_list.iter().all(|id| role_ids.contains(id));
if all_exist {
player.basic_info.role_show_list = request.role_list;
response.error_code = ErrorCode::Success.into();
} else {
response.error_code = ErrorCode::InvalidRequest.into(); // TODO: replace with appropriate error code
}
}
pub fn on_client_current_role_report_request(
_player: &Player,
request: ClientCurrentRoleReportRequest,
response: &mut ClientCurrentRoleReportResponse,
) {
response.current_entity_id = request.current_entity_id;
response.player_id = request.player_id;
}
pub fn on_role_favor_list_request(
_player: &Player,
_request: RoleFavorListRequest,
response: &mut RoleFavorListResponse,
) {
response.favor_list = vec![]; // TODO: add favor
response.error_code = ErrorCode::Success.into();
}
pub fn on_formation_attr_request(
_player: &Player,
_request: FormationAttrRequest,
response: &mut FormationAttrResponse,
) {
response.error_code = ErrorCode::Success.into();
}
pub fn on_update_formation_request(
player: &mut Player,
request: UpdateFormationRequest,
response: &mut UpdateFormationResponse,
) {
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
for formation in request.formations {
let formation_id = formation.formation_id;
let cur_role = formation.cur_role;
let is_current = formation.is_current;
if is_current {
// update player current formation id
player.cur_formation_id = formation_id;
// search old formation id and set real_formation_id, set is_current to false
let mut real_formation_id = formation_id;
if let Some(rf) = player
.formation_list
.values_mut()
.find(|rf| rf.is_current && rf.id != formation_id)
{
real_formation_id = rf.id;
rf.is_current = false;
}
if let Some(old_formation) = player.formation_list.get(&real_formation_id) {
let removed_entities: Vec<i64> = old_formation
.role_ids
.iter()
.map(|&role_id| world.get_entity_id(role_id))
.collect();
removed_entities.iter().for_each(|&entity_id| {
world.remove_entity(entity_id as i32);
});
player.notify(player.build_player_entity_remove_notify(
removed_entities,
ERemoveEntityType::RemoveTypeNormal,
));
}
let added_roles: Vec<Role> = formation
.role_ids
.iter()
.map(|&role_id| Role::new(role_id))
.collect();
if !added_roles.is_empty() {
// add new roles
player.notify(player.build_player_entity_add_notify(added_roles, world));
}
// send update group formation notify
player.notify(player.build_update_group_formation_notify(
RoleFormation {
id: formation_id,
cur_role,
role_ids: formation.role_ids.clone(),
is_current,
},
world,
));
response.formation = Some(formation.clone());
}
// update all formation and check formation_list
player
.formation_list
.entry(formation_id)
.and_modify(|r| {
r.cur_role = formation.cur_role;
r.role_ids = formation.role_ids.clone();
r.is_current = is_current;
})
.or_insert(RoleFormation {
id: formation_id,
cur_role: formation.cur_role,
role_ids: formation.role_ids,
is_current,
});
}
player.notify(player.build_update_formation_notify());
response.error_code = ErrorCode::Success.into();
}

View file

@ -1,9 +1,26 @@
use shorekeeper_protocol::{
EntityActiveRequest, EntityActiveResponse, EntityOnLandedRequest, EntityOnLandedResponse,
ErrorCode, MovePackagePush, UpdateSceneDateRequest, UpdateSceneDateResponse,
ErrorCode, SceneLoadingFinishRequest, SceneLoadingFinishResponse, SceneTraceRequest,
SceneTraceResponse, UpdateSceneDateRequest, UpdateSceneDateResponse,
};
use crate::{logic::ecs::component::ComponentContainer, logic::player::Player, query_components};
use crate::logic::player::Player;
pub fn on_scene_trace_request(
_player: &Player,
request: SceneTraceRequest,
_: &mut SceneTraceResponse,
) {
tracing::debug!("SceneTraceRequest: trace id {}", request.scene_trace_id);
}
pub fn on_scene_loading_finish_request(
_player: &Player,
_request: SceneLoadingFinishRequest,
response: &mut SceneLoadingFinishResponse,
) {
// TODO: Implement this if needed
response.error_code = ErrorCode::Success.into();
}
pub fn on_update_scene_date_request(
_player: &Player,
@ -12,66 +29,3 @@ pub fn on_update_scene_date_request(
) {
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

@ -0,0 +1,35 @@
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;
for (entity, owner, mut vision_skill) in query_with!(
player.world.borrow().get_world_entity(),
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,3 +1,4 @@
use shorekeeper_data::RawVectorData;
use shorekeeper_protocol::{Rotator, TransformData};
use super::Vector3f;
@ -47,3 +48,12 @@ impl Transform {
}
}
}
impl From<&[RawVectorData]> for Transform {
fn from(transform: &[RawVectorData]) -> Self {
Self {
position: Vector3f::from(&transform[0]),
..Default::default()
}
}
}

View file

@ -1,3 +1,4 @@
use shorekeeper_data::RawVectorData;
use shorekeeper_protocol::{Vector, VectorData};
#[derive(Default, Clone, PartialEq, Debug)]
@ -40,3 +41,13 @@ impl Vector3f {
}
}
}
impl From<&RawVectorData> for Vector3f {
fn from(transform: &RawVectorData) -> Self {
Self {
x: transform.x / 100.0,
y: transform.y / 100.0,
z: transform.z / 100.0,
}
}
}

View file

@ -10,6 +10,8 @@ pub struct PlayerBasicInfo {
pub exp: i32,
pub head_photo: i32,
pub head_frame: i32,
pub cur_map_id: i32,
pub role_show_list: Vec<i32>,
}
impl PlayerBasicInfo {
@ -37,6 +39,8 @@ impl PlayerBasicInfo {
exp: data.exp,
head_photo: data.head_photo,
head_frame: data.head_frame,
cur_map_id: data.cur_map_id,
role_show_list: data.role_show_list,
}
}
@ -49,6 +53,8 @@ impl PlayerBasicInfo {
exp: self.exp,
head_photo: self.head_photo,
head_frame: self.head_frame,
cur_map_id: self.cur_map_id,
role_show_list: self.role_show_list.clone(),
}
}
}

View file

@ -0,0 +1,76 @@
use std::collections::HashSet;
use shorekeeper_data::explore_tools_data;
use shorekeeper_protocol::{
ExploreSkillRoulette, ExploreSkillRouletteUpdateNotify, ExploreToolAllNotify,
PlayerExploreToolsData,
};
type Roulette = [i32; 8];
pub struct ExploreTools {
pub unlocked_explore_skills: HashSet<i32>,
pub active_explore_skill: i32,
pub roulette: Roulette,
}
impl ExploreTools {
pub fn build_save_data(&self) -> PlayerExploreToolsData {
PlayerExploreToolsData {
unlocked_skill_list: self.unlocked_explore_skills.iter().cloned().collect(),
active_skill_id: self.active_explore_skill,
roulette: self.roulette.iter().cloned().collect(),
}
}
pub fn load_from_save(data: PlayerExploreToolsData) -> Self {
Self {
unlocked_explore_skills: data.unlocked_skill_list.into_iter().collect(),
active_explore_skill: data.active_skill_id,
roulette: data
.roulette
.try_into()
.unwrap_or_else(|_| Self::default_roulette()),
}
}
pub fn build_explore_tool_all_notify(&self) -> ExploreToolAllNotify {
ExploreToolAllNotify {
skill_list: self.unlocked_explore_skills.iter().cloned().collect(),
explore_skill: self.active_explore_skill,
..Default::default()
}
}
pub fn build_roulette_update_notify(&self) -> ExploreSkillRouletteUpdateNotify {
ExploreSkillRouletteUpdateNotify {
roulette_info: vec![ExploreSkillRoulette {
skill_ids: self.roulette.iter().cloned().collect(),
extra_item_id: 0,
}],
}
}
fn default_roulette() -> Roulette {
let mut roulette = [0i32; 8];
explore_tools_data::iter()
.take(3)
.enumerate()
.for_each(|(i, e)| roulette[i] = e.phantom_skill_id);
roulette
}
}
impl Default for ExploreTools {
fn default() -> Self {
Self {
unlocked_explore_skills: explore_tools_data::iter()
.filter(|e| e.authorization.is_empty())
.map(|e| e.phantom_skill_id)
.collect(),
active_explore_skill: 1001,
roulette: Self::default_roulette(),
}
}
}

View file

@ -1,14 +1,23 @@
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,
EEntityType, EntityState, ERemoveEntityType, EntityAddNotify, EntityConfigType, EntityPb, EntityRemoveInfo,
EntityRemoveNotify, FightFormationNotifyInfo, FightRoleInfo, FightRoleInfos, FormationRoleInfo,
GroupFormation, ItemPkgOpenNotify, LivingStatus, PbGetRoleListNotify, PlayerBasicData,
PlayerFightFormations, PlayerRoleData, PlayerSaveData, ProtocolUnit, UpdateFormationNotify,
UpdateGroupFormationNotify,
};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use crate::logic::{
components::{
Attribute, EntityConfig, Equip, Movement, OwnerPlayer, PlayerEntityMarker, Position,
Visibility, VisionSkill,
},
ecs::component::ComponentContainer,
};
use crate::session::Session;
use super::{
@ -17,94 +26,145 @@ use super::{
};
mod basic_info;
mod explore_tools;
mod in_world_player;
mod location;
mod player_func;
use crate::create_player_entity_pb;
use crate::logic::ecs::world::WorldEntity;
use crate::logic::player::basic_info::PlayerBasicInfo;
use crate::logic::player::explore_tools::ExploreTools;
use crate::logic::player::location::PlayerLocation;
use crate::logic::player::player_func::PlayerFunc;
pub use in_world_player::InWorldPlayer;
use shorekeeper_data::base_property_data;
use shorekeeper_data::role_info_data;
use shorekeeper_protocol::message::Message;
pub struct Player {
session: Option<Arc<Session>>,
// Persistent
pub basic_info: PlayerBasicInfo,
pub role_list: Vec<Role>,
pub formation_list: Vec<RoleFormation>,
pub role_list: HashMap<i32, Role>,
pub formation_list: HashMap<i32, RoleFormation>,
pub cur_formation_id: i32,
pub location: PlayerLocation,
pub func: PlayerFunc,
pub explore_tools: ExploreTools,
// Runtime
pub world: Rc<RefCell<World>>,
pub last_save_time: u64,
pub quadrant_id: u64,
}
impl Player {
pub fn init(&mut self) {
if self.role_list.is_empty() {
self.on_first_enter();
if self.role_list.is_empty() || self.formation_list.is_empty() {
self.init_role_and_formation();
}
// 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();
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());
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.notify(ItemPkgOpenNotify {
open_pkg: (0..8).collect(),
});
self.location = PlayerLocation::default();
self.notify(self.build_update_formation_notify());
}
fn init_role_and_formation(&mut self) {
self.role_list.clear();
let mut role = match self.basic_info.sex {
0 => Role::new(Role::MAIN_CHARACTER_FEMALE_ID),
1 => Role::new(Role::MAIN_CHARACTER_MALE_ID),
_ => unreachable!(),
};
role.name = self.basic_info.name.clone();
self.role_list.insert(role.role_id, role);
let required_role_ids: Vec<i32> = role_info_data::iter()
.filter(|role_info| role_info.role_type == 1)
.map(|role_info| role_info.id)
.collect();
let formation = vec![1603, 1504, 1505];
required_role_ids.iter().for_each(|&role_id| {
if !self.role_list.keys().any(|&k| k == role_id) {
self.role_list.insert(role_id, Role::new(role_id));
}
});
self.formation_list.insert(
1,
RoleFormation {
id: 1,
cur_role: *formation.iter().next().unwrap(),
role_ids: formation,
is_current: true,
},
);
self.cur_formation_id = 1;
self.formation_list.values_mut().for_each(|formation| {
if formation.is_current && formation.id != 1 {
formation.is_current = false;
}
});
self.ensure_current_formation();
}
// 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 the list off formation is empty, add a default formation
if self.formation_list.is_empty() {
let role = &self.role_list[0];
let mut role_list_clone = self.role_list.iter().clone();
self.formation_list.push(RoleFormation {
id: 1,
cur_role: role.role_id,
role_id_set: HashSet::from([role.role_id]),
is_current: true,
});
self.formation_list.insert(
1,
RoleFormation {
id: 1,
cur_role: role_list_clone.next().unwrap().1.role_id,
role_ids: role_list_clone
.take(3)
.map(|(&role_id, _)| role_id)
.collect(),
is_current: true,
},
);
}
if !self.formation_list.iter().any(|rf| rf.is_current) {
self.formation_list[0].is_current = true;
// If there is no current formation, set the first formation as the current formation
if !self.formation_list.values().any(|rf| rf.is_current) {
self.formation_list.get_mut(&1).unwrap().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);
// Ensure that the set of character IDs for the current formation is not empty and that the current character ID is in the set
if let Some(rf) = self.formation_list.values_mut().find(|rf| rf.is_current) {
if rf.role_ids.is_empty() {
rf.role_ids
.push(self.role_list.iter().next().unwrap().1.role_id);
}
if !rf.role_id_set.contains(&rf.cur_role) {
rf.cur_role = *rf.role_id_set.iter().nth(0).unwrap();
if !rf.role_ids.contains(&rf.cur_role) {
rf.cur_role = *rf.role_ids.iter().nth(0).unwrap();
}
}
}
@ -119,24 +179,105 @@ impl Player {
}
}
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 build_player_entity_add_notify(
&self,
role_list: Vec<Role>,
world: &mut WorldEntity,
) -> EntityAddNotify {
create_player_entity_pb!(
role_list,
self.basic_info.cur_map_id,
world,
self.basic_info.id,
self.location.position.clone(),
self.explore_tools
)
}
pub fn get_cur_role_id(&self) -> i32 {
self.formation_list
.iter()
.find(|rf| rf.is_current)
.unwrap()
.cur_role
pub fn build_player_entity_remove_notify(
&self,
entities: Vec<i64>,
remove_type: ERemoveEntityType,
) -> EntityRemoveNotify {
EntityRemoveNotify {
remove_infos: entities
.iter()
.map(|&entity_id| EntityRemoveInfo {
entity_id,
r#type: remove_type.into(),
})
.collect(),
is_remove: true,
}
}
pub fn build_update_group_formation_notify(
&self,
cur_formation: RoleFormation,
world: &mut WorldEntity,
) -> UpdateGroupFormationNotify {
let group_type = 1;
UpdateGroupFormationNotify {
group_formation: vec![GroupFormation {
player_id: self.basic_info.id,
fight_role_infos: vec![FightRoleInfos {
group_type,
fight_role_infos: cur_formation
.role_ids
.iter()
.map(|&role_id| FightRoleInfo {
role_id,
entity_id: world.get_entity_id(role_id),
})
.collect(),
cur_role: cur_formation.cur_role,
is_retain: false,
living_status: LivingStatus::Alive.into(),
is_fixed_location: false,
}],
current_group_type: group_type,
}],
}
}
pub fn build_update_formation_notify(&self) -> UpdateFormationNotify {
let role_map: HashMap<i32, (i32, i32)> = self
.role_list
.values()
.map(|role| (role.role_id, (role.role_id, role.level)))
.collect();
UpdateFormationNotify {
players_formations: vec![PlayerFightFormations {
player_id: self.basic_info.id,
formations: self
.formation_list
.iter()
.map(|(&formation_id, formation)| FightFormationNotifyInfo {
formation_id,
cur_role: formation.cur_role,
role_infos: formation
.role_ids
.iter()
.map(|role_id| {
if !role_map.contains_key(role_id) {
tracing::warn!("Role {} not found in use role list", role_id);
return Default::default();
}
let &(role_id, level) = role_map.get(&role_id).unwrap();
FormationRoleInfo {
role_id,
max_hp: 0,
cur_hp: 0,
level,
}
})
.collect(),
is_current: formation.is_current,
})
.collect(),
}],
}
}
pub fn load_from_save(save_data: PlayerSaveData) -> Self {
@ -144,17 +285,20 @@ impl Player {
Self {
session: None,
basic_info: PlayerBasicInfo::load_from_save(save_data.basic_data.unwrap_or_default()),
basic_info: PlayerBasicInfo::load_from_save(
save_data.basic_data.clone().unwrap_or_default(),
),
role_list: role_data
.role_list
.into_iter()
.map(Role::load_from_save)
.collect(),
.collect::<HashMap<i32, Role>>(),
formation_list: role_data
.role_formation_list
.into_iter()
.map(RoleFormation::load_from_save)
.map(|(k, v)| (k, RoleFormation::load_from_save(v)))
.collect(),
cur_formation_id: role_data.cur_formation_id,
location: save_data
.location_data
.map(PlayerLocation::load_from_save)
@ -163,8 +307,13 @@ impl Player {
.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(),
quadrant_id: 0,
}
}
@ -172,15 +321,21 @@ impl Player {
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_list: self
.role_list
.iter()
.map(|(_, role)| role.build_save_data())
.collect(),
role_formation_list: self
.formation_list
.iter()
.map(|rf| rf.build_save_data())
.map(|(&k, v)| (k, v.build_save_data()))
.collect(),
cur_formation_id: self.cur_formation_id,
}),
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()),
}
}
@ -190,7 +345,11 @@ impl Player {
pub fn build_role_list_notify(&self) -> PbGetRoleListNotify {
PbGetRoleListNotify {
role_list: self.role_list.iter().map(|r| r.to_protobuf()).collect(),
role_list: self
.role_list
.iter()
.map(|(_, role)| role.to_protobuf())
.collect(),
}
}
@ -215,26 +374,23 @@ impl Player {
}
}
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!(),
pub fn create_default_save_data(id: i32, name: String, sex: i32) -> PlayerSaveData {
let role_id = match sex {
0 => Role::MAIN_CHARACTER_FEMALE_ID, // 1502
1 => Role::MAIN_CHARACTER_MALE_ID, // 1501
_ => Role::MAIN_CHARACTER_MALE_ID, // Default to male
};
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_photo: 1603,
head_frame: 80060009,
cur_map_id: 8,
role_show_list: vec![role_id],
..Default::default()
}),
..Default::default()

View file

@ -8,6 +8,10 @@ pub struct PlayerFunc {
}
impl PlayerFunc {
pub fn unlock(&mut self, id: i32) {
self.func_map.insert(id, 2);
}
pub fn load_from_save(data: PlayerFuncData) -> Self {
PlayerFunc {
func_map: data.func_map,

View file

@ -1,11 +1,9 @@
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 role_ids: Vec<i32>,
pub is_current: bool,
}
@ -14,7 +12,7 @@ impl RoleFormation {
Self {
id: data.formation_id,
cur_role: data.cur_role,
role_id_set: data.role_id_list.into_iter().collect(),
role_ids: data.role_id_list,
is_current: data.is_current,
}
}
@ -23,7 +21,7 @@ impl RoleFormation {
RoleFormationData {
formation_id: self.id,
cur_role: self.cur_role,
role_id_list: self.role_id_set.iter().cloned().collect(),
role_id_list: self.role_ids.iter().map(|&role_id| role_id).collect(),
is_current: self.is_current,
}
}

View file

@ -1,12 +1,12 @@
use std::collections::HashMap;
use crate::logic::utils::load_role_info::load_key_value;
use common::time_util;
use shorekeeper_data::role_info_data;
pub use formation::RoleFormation;
use shorekeeper_data::{base_property_data, role_info_data};
use shorekeeper_protocol::{ArrayIntInt, RoleData, RoleInfo};
mod formation;
pub use formation::RoleFormation;
pub struct Role {
pub role_id: i32,
pub name: String,
@ -17,6 +17,7 @@ pub struct Role {
pub star: i32,
pub favor: i32,
pub create_time: u32,
pub equip_weapon: i32,
}
impl Role {
@ -36,10 +37,17 @@ impl Role {
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 {
let base_prop: HashMap<i32, i32> = load_key_value(
base_property_data::iter()
.find(|d| d.id == self.role_id)
.unwrap(),
);
RoleInfo {
role_id: self.role_id,
name: self.name.clone(),
@ -54,22 +62,30 @@ impl Role {
.collect(),
star: self.star,
favor: self.favor,
base_prop: base_prop
.iter()
.map(|(&k, &v)| ArrayIntInt { key: k, value: v })
.collect(),
..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,
}
pub fn load_from_save(data: RoleData) -> (i32, Self) {
(
data.role_id,
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 {
@ -83,6 +99,7 @@ impl Role {
star: self.star,
favor: self.favor,
create_time: self.create_time,
equip_weapon: self.equip_weapon,
..Default::default()
}
}

View file

@ -15,8 +15,9 @@ pub(super) struct MovementSystem;
impl System for MovementSystem {
fn tick(&self, world: &mut World, players: &mut [RefMut<Player>]) {
let mut notify = MovePackageNotify::default();
let world_entity = world.get_world_entity();
for (entity, mut movement, mut position) in query_with!(world, Movement, Position) {
for (entity, mut movement, mut position) in query_with!(world_entity, Movement, Position) {
if movement.pending_movement_vec.is_empty() {
continue;
}
@ -46,9 +47,12 @@ impl System for MovementSystem {
notify.moving_entities.push(moving_entity_data);
if let (Some(_), Some(owner)) =
query_components!(world, i64::from(entity), PlayerEntityMarker, OwnerPlayer)
{
if let (Some(_), Some(owner)) = query_components!(
world_entity,
i64::from(entity),
PlayerEntityMarker,
OwnerPlayer
) {
if let Some(player) = players.iter_mut().find(|pl| pl.basic_info.id == owner.0) {
player.location.position = position.0.clone();
}

View file

@ -1,3 +1,10 @@
use common::time_util;
use shorekeeper_protocol::PlayerSaveData;
use shorekeeper_protocol::{
message::Message, AfterJoinSceneNotify, EnterGameResponse, JoinSceneNotify, JsPatchNotify,
TransitionOptionPb,
};
use std::collections::hash_map::Entry::Vacant;
use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
@ -10,16 +17,13 @@ use std::{
time::Duration,
};
use common::time_util;
use shorekeeper_protocol::{message::Message, JoinSceneNotify, TransitionOptionPb};
use shorekeeper_protocol::{AfterJoinSceneNotify, EnterGameResponse, PlayerSaveData};
use crate::{
player_save_task::{self, PlayerSaveReason},
session::Session,
};
use super::{ecs::world::World, player::Player, utils::world_util};
use crate::logic::ecs::world::WorldEntity;
use crate::{logic, player_save_task::{self, PlayerSaveReason}, session::Session};
const WATER_MASK: &str = include_str!("../../watermask-rr.js");
const UID_FIX: &str = include_str!("../../uidfix.js");
const CENSORSHIP_FIX: &str = include_str!("../../censorshipfix.js");
pub enum LogicInput {
AddPlayer {
@ -105,8 +109,7 @@ fn logic_thread_func(receiver: mpsc::Receiver<LogicInput>, load: Arc<AtomicUsize
let mut world = world.borrow_mut();
let mut players = world
.player_ids()
.map(|id| state.players.get(id).map(|pl| pl.borrow_mut()))
.flatten()
.flat_map(|id| state.players.get(id).map(|pl| pl.borrow_mut()))
.collect::<Box<_>>();
super::systems::tick_systems(&mut world, &mut players);
@ -135,16 +138,33 @@ fn handle_logic_input(state: &mut LogicState, input: LogicInput) {
session,
player_save_data,
} => {
let player = state
.players
.entry(player_id)
.or_insert(RefCell::new(Player::load_from_save(player_save_data)));
let (player, is_player) = if let Vacant(e) = state.players.entry(player_id) {
(
e.insert(RefCell::new(Player::load_from_save(player_save_data))),
true,
)
} else {
if let Some(player) = state.players.get_mut(&player_id) {
(player, false)
} else {
tracing::warn!("logic_thread: get player requested, but player {player_id} with data doesn't exist");
return;
}
};
let mut player = player.borrow_mut();
state.worlds.insert(player_id, player.world.clone());
if is_player {
player
.world
.borrow_mut()
.world_entitys
.insert(player.basic_info.cur_map_id, WorldEntity::default());
state.worlds.insert(player_id, player.world.clone());
}
player.init();
player.set_session(session);
player.respond(EnterGameResponse::default(), enter_rpc_id);
player.notify_general_data();
player
@ -152,21 +172,41 @@ fn handle_logic_input(state: &mut LogicState, input: LogicInput) {
.borrow_mut()
.set_in_world_player_data(player.build_in_world_player());
world_util::add_player_entities(&mut player.world.borrow_mut(), &player);
let scene_info = world_util::build_scene_information(
&player.world.borrow(),
player.location.instance_id,
player.basic_info.id,
);
world_util::add_player_entities(&player);
let scene_info = world_util::build_scene_information(&player);
player.notify(JoinSceneNotify {
max_entity_id: i64::MAX,
scene_info: Some(scene_info),
max_entity_id: i64::MAX,
transition_option: Some(TransitionOptionPb::default()),
});
player.respond(EnterGameResponse::default(), enter_rpc_id);
player.notify(AfterJoinSceneNotify::default());
// TODO: maybe move somewhere else?
player.notify(JsPatchNotify {
// TODO: Add the possibility to customize size and text from options
content: WATER_MASK.to_string(),
});
player.notify(JsPatchNotify {
content: UID_FIX
.replace("{PLAYER_USERNAME}", &player.basic_info.name)
.replace("{SELECTED_COLOR}", "50FC71"),
});
player.notify(JsPatchNotify {
content: CENSORSHIP_FIX.to_string(),
});
let map = logic::utils::quadrant_util::get_map(player.location.instance_id);
let quadrant_id = map.get_quadrant_id(
player.location.position.position.x * 100.0,
player.location.position.position.y * 100.0,
);
player.quadrant_id = quadrant_id;
let entities = map.get_initial_entities(quadrant_id);
world_util::add_entities(&player, &entities);
drop(player);
state
@ -191,6 +231,7 @@ fn handle_logic_input(state: &mut LogicState, input: LogicInput) {
let _ = state.worlds.remove(&player_id);
// TODO: kick co-op players from removed world
// TODO: Remove all entitie
player_save_task::push(
player_id,

View file

@ -1,26 +1,75 @@
use crate::logic::ecs::component::ComponentContainer;
use shorekeeper_protocol::{EntityPb, PlayerSceneAoiData};
use shorekeeper_protocol::{EEntityType, EntityPb, PlayerSceneAoiData};
use std::collections::HashSet;
use crate::{logic::ecs::world::World, query_with};
use crate::logic::components::Visibility;
use crate::logic::player::Player;
use crate::{modify_component, query_hn_with};
pub fn build_scene_add_on_init_data(world: &World) -> PlayerSceneAoiData {
let entities = query_with!(world, PlayerEntityMarker)
pub fn build_scene_add_on_init_data(player: &Player) -> PlayerSceneAoiData {
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
let entities = query_hn_with!(world, PlayerEntityMarker)
.into_iter()
.map(|(e, _)| e)
.collect::<Vec<_>>();
.map(|(entity_id, _)| {
let res_map: (EEntityType, i32);
match EEntityType::try_from(
world.get_entity(world.get_config_id(entity_id)).entity_type,
) {
Ok(EEntityType::Player) => {
res_map = (EEntityType::Player, entity_id);
}
_ => {
res_map = (EEntityType::default(), -1);
}
}
res_map
})
.collect::<HashSet<(EEntityType, i32)>>();
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));
entities
.iter()
.filter(|&&(_, entity_id)| entity_id != -1)
.for_each(|&(entity_type, entity_id)| {
match entity_type {
EEntityType::Player => {
let config_id = world.get_config_id(entity_id);
modify_component!(
world.get_entity_components(entity_id),
Visibility,
|vis: &mut Visibility| {
let cur_role_id = player
.formation_list
.get(&player.cur_formation_id)
.unwrap()
.cur_role;
vis.0 = if config_id == cur_role_id {
true
} else {
false
};
}
);
aoi_data.entities.push(pb);
}
if world.get_entity(config_id).entity_type == EEntityType::Player as i32 {
let mut pb = EntityPb {
id: entity_id as i64,
..Default::default()
};
world
.get_entity_components(entity_id)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
aoi_data.entities.push(pb);
}
}
_ => {}
};
});
aoi_data
}

View file

@ -0,0 +1,158 @@
use shorekeeper_data::BasePropertyData;
use shorekeeper_protocol::EAttributeType;
use std::collections::HashMap;
#[macro_export]
macro_rules! impl_base_prop {
($($name:ident),*) => {
pub fn load_key_value(base_property: &BasePropertyData) -> HashMap<i32, i32> {
HashMap::from([$(
::paste::paste!((EAttributeType::[<$name:camel>] as i32, base_property.$name as i32)),
)*])
}
pub fn attribute_from_data(base_property: &BasePropertyData) -> HashMap<EAttributeType, (i32, i32)> {
HashMap::from([$(
::paste::paste!((EAttributeType::[<$name:camel>], (base_property.$name, 0))),
)*])
}
};
}
impl_base_prop!(
lv,
life_max,
life,
sheild,
sheild_damage_change,
sheild_damage_reduce,
atk,
crit,
crit_damage,
def,
energy_efficiency,
cd_reduse,
element_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,2 +1,4 @@
pub mod entity_serializer;
pub mod load_role_info;
pub mod world_util;
pub mod quadrant_util;

View file

@ -0,0 +1,230 @@
use std::collections::HashMap;
use std::sync::OnceLock;
use shorekeeper_data::LevelEntityConfigData;
#[derive(Clone)]
struct MapBounds {
x_max: f32,
x_min: f32,
x_translate: f32,
y_max: f32,
y_min: f32,
y_translate: f32,
}
#[derive(Default)]
struct Quadrant {
entities: HashMap<i64, &'static LevelEntityConfigData>,
}
pub struct Map {
bounds: MapBounds,
width: u64,
height: u64,
quadrants: HashMap<u64, Quadrant>,
}
// TODO: Make it configurable?
const EDGE_SIZE: f32 = 1000000f32;
const EDGE_CHECK: f32 = EDGE_SIZE * 3.0f32;
pub(crate) static MAP_TABLE: OnceLock<HashMap<i32, Map>> = OnceLock::new();
impl MapBounds {
fn find_max_min(slice: &[&LevelEntityConfigData]) -> (Self, bool) {
let mut x_max = 0f32;
let mut x_min = 0f32;
let mut y_max = 0f32;
let mut y_min = 0f32;
// Find max and min coordinates
for entity in slice.iter() {
if entity.transform[0].x < x_min { x_min = entity.transform[0].x }
if entity.transform[0].x > x_max { x_max = entity.transform[0].x }
if entity.transform[0].y < y_min { y_min = entity.transform[0].y }
if entity.transform[0].y > y_max { y_max = entity.transform[0].y }
}
if (f32::abs(x_max - x_min) < EDGE_CHECK) || (f32::abs(y_max - y_min) < EDGE_CHECK) {
// TODO: Handle this special case, since all entities fit, no need for quadrant
// Move everything to positive coordinates to prevent corner cases
let (x_max, x_min, x_translate) = recenter_map(x_max, x_min);
let (y_max, y_min, y_translate) = recenter_map(y_max, y_min);
(MapBounds { x_max, x_min, x_translate, y_max, y_min, y_translate }, false)
} else {
// Round to edge
x_max = round_max_coordinate(x_max, EDGE_SIZE);
x_min = round_min_coordinate(x_min, EDGE_SIZE);
y_max = round_max_coordinate(y_max, EDGE_SIZE);
y_min = round_min_coordinate(y_min, EDGE_SIZE);
// Adding bounds to prevent OOB when moving
x_max += EDGE_SIZE;
x_min -= EDGE_SIZE;
y_max += EDGE_SIZE;
y_min -= EDGE_SIZE;
// Move everything to positive coordinates to prevent corner cases
let (x_max, x_min, x_translate) = recenter_map(x_max, x_min);
let (y_max, y_min, y_translate) = recenter_map(y_max, y_min);
(MapBounds { x_max, x_min, x_translate, y_max, y_min, y_translate }, true)
}
}
}
impl Quadrant {
fn insert_entity(&mut self, entity_id: i64, entity: &'static LevelEntityConfigData) {
self.entities.insert(entity_id, entity);
}
fn get_entities(&self) -> Vec<&LevelEntityConfigData> {
self.entities
.iter()
.map(|(_, v)| *v)
.collect()
}
}
impl Map {
fn insert_entity(&mut self, entity: &'static LevelEntityConfigData) {
let index = self.get_quadrant_id(entity.transform[0].x, entity.transform[0].y);
self.quadrants.entry(index).or_default().insert_entity(entity.entity_id, entity)
}
fn get_neighbour_cells(&self, quadrant_id: u64) -> [u64; 9] {
let x = quadrant_id % self.width;
let y = (quadrant_id - x) / self.width;
return [
(self.width * (y - 1)) + (x - 1),
(self.width * (y - 1)) + (x),
(self.width * (y - 1)) + (x + 1),
(self.width * (y)) + (x - 1),
(self.width * (y)) + (x),
(self.width * (y)) + (x + 1),
(self.width * (y + 1)) + (x - 1),
(self.width * (y + 1)) + (x),
(self.width * (y + 1)) + (x + 1),
];
}
fn collect_quadrant_differences(&self, discriminant: [u64; 9], discriminator: [u64; 9]) -> Vec<&LevelEntityConfigData> {
let mut output = Vec::new();
for quadrant in discriminant {
if !discriminator.contains(&quadrant) {
if let Some(quadrant) = &self.quadrants.get(&quadrant) {
output.extend_from_slice(&quadrant.get_entities())
}
}
}
output
}
pub fn get_quadrant_id(&self, x: f32, y: f32) -> u64 {
let width: u64 = unsafe {
f32::to_int_unchecked(
f32::trunc(
(self.bounds.x_max + self.bounds.x_translate - x) / EDGE_SIZE
)
)
};
let height: u64 = unsafe {
f32::to_int_unchecked(
f32::trunc(
(self.bounds.y_max + self.bounds.y_translate - y) / EDGE_SIZE
)
)
};
(self.width * height) + width
}
pub fn get_initial_entities(&self, quadrant_id: u64) -> Vec<&LevelEntityConfigData> {
let quadrants = self.get_neighbour_cells(quadrant_id);
let mut output = Vec::new();
for quadrant in quadrants {
if let Some(quadrant) = &self.quadrants.get(&quadrant) {
output.extend_from_slice(&quadrant.get_entities())
}
}
output
}
pub fn get_update_entities(&self, old_quadrant_id: u64, new_quadrant_id: u64) -> (Vec<&LevelEntityConfigData>, Vec<&LevelEntityConfigData>) {
let old_quadrants = self.get_neighbour_cells(old_quadrant_id);
let new_quadrants = self.get_neighbour_cells(new_quadrant_id);
let entities_to_remove = self.collect_quadrant_differences(old_quadrants, new_quadrants);
let entities_to_add = self.collect_quadrant_differences(new_quadrants, old_quadrants);
(entities_to_remove, entities_to_add)
}
}
pub fn maps_iter() -> std::collections::hash_map::Iter<'static, i32, Map> {
MAP_TABLE.get().unwrap().iter()
}
pub fn initialize_quadrant_system() {
let mut map_grouped_entities: HashMap<i32, Vec<&LevelEntityConfigData>> = HashMap::new();
for (_, entity) in shorekeeper_data::level_entity_config_data::iter() {
map_grouped_entities.entry(entity.map_id).or_default().push(entity);
}
let mut maps: HashMap<i32, Map> = HashMap::new();
for (map_id, entities) in map_grouped_entities {
let (bounds, _quadrant_enabled) = MapBounds::find_max_min(&entities[..]);
let width = unsafe { f32::to_int_unchecked((bounds.x_max - bounds.x_min) / EDGE_SIZE) };
let height = unsafe { f32::to_int_unchecked((bounds.y_max - bounds.y_min) / EDGE_SIZE) };
let map = maps.entry(map_id).or_insert(
Map {
bounds: bounds.clone(),
width,
height,
quadrants: HashMap::new(),
}
);
for entity in entities {
map.insert_entity(entity);
}
}
let _ = MAP_TABLE.set(maps);
}
pub fn get_map(map_id: i32) -> &'static Map {
// TODO: Error check for map id
MAP_TABLE.get().unwrap().get(&map_id).unwrap()
}
fn recenter_map(max: f32, min: f32) -> (f32, f32, f32) {
match min < 0.0 {
true => (max + f32::abs(min), 0.0, min),
false => (max, min, 0.0)
}
}
fn round_max_coordinate(coordinate: f32, round: f32) -> f32 {
let rounded = f32::round(coordinate);
let remainder = rounded % round;
if remainder != 0f32 {
rounded + (if rounded > 0.0 { round } else { 0.0 } - remainder)
} else {
rounded
}
}
fn round_min_coordinate(coordinate: f32, round: f32) -> f32 {
let rounded = f32::round(coordinate);
let remainder = rounded % round;
if remainder != 0f32 {
rounded + (remainder.signum() * (if rounded > 0.0 { 0.0 } else { round } - f32::abs(remainder)))
} else {
rounded
}
}

View file

@ -1,76 +1,161 @@
use shorekeeper_data::base_property_data;
use shorekeeper_protocol::{
EntityConfigType, FightRoleInfo, FightRoleInfos, LivingStatus, SceneInformation, SceneMode,
ScenePlayerInformation, SceneTimeInfo,
};
use std::cell::{BorrowMutError, RefMut};
use crate::{
logic::{
components::{
Attribute, EntityConfig, Movement, OwnerPlayer, PlayerEntityMarker, Position,
Visibility,
},
ecs::{component::ComponentContainer, world::World},
player::Player,
use shorekeeper_data::{base_property_data, LevelEntityConfigData};
use shorekeeper_protocol::{DFsm, EEntityType, EntityAddNotify, EntityConfigType, EntityPb,
EntityRemoveInfo, EntityRemoveNotify, EntityState, FightRoleInfo,
FightRoleInfos, LivingStatus, SceneInformation, SceneMode,
ScenePlayerInformation, SceneTimeInfo};
use crate::logic::{
components::{
Attribute, EntityConfig, Equip, Movement, OwnerPlayer, PlayerEntityMarker, Position,
Visibility, VisionSkill,
},
query_with,
ecs::component::ComponentContainer,
};
use crate::logic::components::{Fsm, MonsterAi};
use crate::logic::ecs::entity::Entity;
use crate::logic::ecs::world::{World, WorldEntity};
use crate::logic::math::Transform;
use crate::logic::player::Player;
use crate::logic::utils::entity_serializer;
use crate::query_with;
use super::entity_serializer;
#[macro_export]
macro_rules! create_player_entity_pb {
($role_list:expr, $cur_map_id:expr, $world:expr, $player_id:expr, $position:expr, $explore_tools:expr) => {{
let mut pbs = Vec::new();
pub fn add_player_entities(world: &mut World, player: &Player) {
let cur_role_id = player.get_cur_role_id();
for role in $role_list {
let role_id: i32 = role.role_id;
let base_property = base_property_data::iter()
.find(|d| d.id == role_id)
.expect("macro create_role_entity_pb: Base property data not found");
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();
let entity = $world
.create_entity(role_id, EEntityType::Player.into(), $cur_map_id)
.with(ComponentContainer::PlayerEntityMarker(PlayerEntityMarker))
.with(ComponentContainer::EntityConfig(EntityConfig {
config_id: role_id,
config_type: EntityConfigType::Character,
entity_type: EEntityType::Player.into(),
entity_state: EntityState::Default
}))
.with(ComponentContainer::OwnerPlayer(OwnerPlayer($player_id)))
.with(ComponentContainer::Position(Position($position)))
.with(ComponentContainer::Visibility(Visibility(
role_id == role_id,
)))
.with(ComponentContainer::Attribute(Attribute::from_data(
base_property,
)))
.with(ComponentContainer::Movement(Movement::default()))
.with(ComponentContainer::Equip(Equip {
weapon_id: role.equip_weapon,
weapon_breach_level: 90, // TODO: store this too
}))
.with(ComponentContainer::VisionSkill(VisionSkill {
skill_id: $explore_tools.active_explore_skill,
}))
.build();
tracing::debug!(
"created player entity, id: {}, role_id: {}",
i64::from(id),
role.role_id
);
let mut pb = EntityPb {
id: entity.entity_id as i64,
..Default::default()
};
$world
.get_entity_components(entity.entity_id)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
pbs.push(pb);
}
EntityAddNotify {
entity_pbs: pbs,
is_add: true,
}
}};
}
pub fn add_player_entities(player: &Player) {
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
let current_formation = player.formation_list.get(&player.cur_formation_id).unwrap();
let role_vec = current_formation
.role_ids
.iter()
.map(|role_id| player.role_list.get(&role_id).unwrap())
.collect::<Vec<_>>();
let cur_role_id = current_formation.cur_role;
if world.active_entity_empty() {
for role in role_vec {
let entity = world
.create_entity(
role.role_id,
EEntityType::Player.into(),
player.basic_info.cur_map_id,
)
.with(ComponentContainer::PlayerEntityMarker(PlayerEntityMarker))
.with(ComponentContainer::EntityConfig(EntityConfig {
config_id: role.role_id,
config_type: EntityConfigType::Character,
entity_type: EEntityType::Player.into(),
entity_state: EntityState::Default,
}))
.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: {}",
entity.entity_id,
role.role_id
);
}
}
}
pub fn build_scene_information(world: &World, instance_id: i32, owner_id: i32) -> SceneInformation {
pub fn build_scene_information(player: &Player) -> SceneInformation {
SceneInformation {
scene_id: String::new(),
instance_id,
owner_id,
instance_id: player.location.instance_id,
owner_id: player.basic_info.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),
aoi_data: Some(entity_serializer::build_scene_add_on_init_data(player)),
player_infos: build_player_info_list(&player.world.borrow_mut()),
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,
cur_context_id: player.basic_info.id as i64,
..Default::default()
}
}
@ -79,24 +164,33 @@ fn build_player_info_list(world: &World) -> Vec<ScenePlayerInformation> {
world
.players()
.map(|sp| {
let (cur_role_id, transform) = query_with!(
world,
let (cur_role_id, transform, _equip) = query_with!(
world.get_world_entity(),
PlayerEntityMarker,
OwnerPlayer,
Visibility,
EntityConfig,
Position
Position,
Equip
)
.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();
.into_iter()
.find_map(|(_, _, owner, visibility, conf, pos, equip)| {
(sp.player_id == owner.0 && visibility.0).then_some((
conf.config_id,
pos.0.clone(),
equip.weapon_id,
))
})
.unwrap_or_default();
let active_characters =
query_with!(world, PlayerEntityMarker, OwnerPlayer, EntityConfig)
.into_iter()
.filter(|(_, _, owner, _)| owner.0 == sp.player_id);
let active_characters = query_with!(
world.get_world_entity(),
PlayerEntityMarker,
OwnerPlayer,
EntityConfig
)
.into_iter()
.filter(|(_, _, owner, _)| owner.0 == sp.player_id);
ScenePlayerInformation {
cur_role: cur_role_id,
@ -125,3 +219,119 @@ fn build_player_info_list(world: &World) -> Vec<ScenePlayerInformation> {
})
.collect()
}
pub fn build_monster_entity(world: &mut WorldEntity, config_id: i32, map_id: i32, transform: Transform) -> Entity {
// TODO: Check for more components, AI and so
world.create_entity(config_id, EEntityType::Monster.into(), map_id)
.with(ComponentContainer::EntityConfig(EntityConfig {
config_id,
config_type: EntityConfigType::Level,
entity_type: EEntityType::Monster.into(),
entity_state: EntityState::Born,
}))
.with(ComponentContainer::Position(Position(transform)))
.with(ComponentContainer::Visibility(Visibility(true)))
.with(ComponentContainer::Attribute(Attribute::from_data(
base_property_data::iter()
.find(|d| d.id == 600000100) // TODO: Implement monster stats
.unwrap(),
)))
.with(ComponentContainer::MonsterAi(MonsterAi {
weapon_id: 0,
hatred_group_id: 0,
ai_team_init_id: 100,
combat_message_id: 0,
}))
.with(ComponentContainer::Fsm(Fsm {
fsms: vec![
DFsm {
fsm_id: 10007,
current_state: 10013,
flag: 0,
k_ts: 0,
},
DFsm {
fsm_id: 10007,
current_state: 10015,
flag: 0,
k_ts: 0,
},
DFsm {
fsm_id: 10007,
current_state: 10012,
flag: 0,
k_ts: 0,
},
],
hash_code: 0,
common_hash_code: 0,
black_board: vec![],
fsm_custom_blackboard_datas: None,
}))
.with(ComponentContainer::Movement(Movement::default()))
.build()
}
pub fn remove_entities(player: &Player, entities: &[&LevelEntityConfigData]) {
let mut removed_entities = Vec::with_capacity(entities.len());
// Enclose to drop borrow mut ASAP
{
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
for entity in entities {
let entity_id = entity.entity_id as i32; // TODO: Should be i64
if world.remove_entity(entity_id) {
removed_entities.push(world.get_entity_id(entity_id));
}
}
}
for entity_id in removed_entities {
player.notify(EntityRemoveNotify {
remove_infos: vec![EntityRemoveInfo { entity_id, r#type: 0 }],
is_remove: true,
});
}
}
pub fn add_entities(player: &Player, entities: &[&LevelEntityConfigData]) {
let mut added_entities = Vec::with_capacity(entities.len());
// Enclose to drop borrow mut ASAP
{
let mut world_ref = player.world.borrow_mut();
let world = world_ref.get_mut_world_entity();
for entity in entities {
// TODO: review other types
if entity.blueprint_type.contains("Monster") {
added_entities.push(build_monster_entity(
world,
entity.entity_id as i32, // TODO: Should be i64
entity.map_id,
Transform::from(&entity.transform[..]),
));
} else {
tracing::debug!("Unhandled entity to be added of type: {}", entity.blueprint_type);
}
}
}
let mut world_ref = player.world.borrow();
let world = world_ref.get_world_entity();
// Since kuro has issues, we can only send one
for entity in added_entities {
let mut pb = EntityPb {
id: entity.entity_id as i64, // TODO: Should be i64
..Default::default()
};
world.get_entity_components(entity.entity_id)
.into_iter()
.for_each(|comp| comp.set_pb_data(&mut pb));
player.notify(EntityAddNotify {
entity_pbs: vec![pb],
is_add: true,
});
}
}

View file

@ -20,7 +20,8 @@ async fn main() -> Result<()> {
::common::splash::print_splash();
::common::logging::init(::tracing::Level::DEBUG);
shorekeeper_data::load_json_data("assets/logic/json")?;
shorekeeper_data::load_all_json_data("assets/logic/BinData")?;
logic::utils::quadrant_util::initialize_quadrant_system();
let database = Arc::new(shorekeeper_database::connect_to(&CONFIG.database).await?);
shorekeeper_database::run_migrations(database.as_ref()).await?;

9
game-server/uidfix.js Normal file
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,41 @@
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.LOo = 0.15;
_a.yOo = 700;
_a.IOo = 700;
_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 = "NCSO @ 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

@ -867,7 +867,7 @@ impl<Output> Kcp<Output> {
}
let mut ts_flush = self.ts_flush;
let mut tm_packet = u32::max_value();
let mut tm_packet = u32::MAX;
if timediff(current, ts_flush) >= 10000 || timediff(current, ts_flush) < -10000 {
ts_flush = current;
@ -1142,7 +1142,7 @@ impl<Output: Write> Kcp<Output> {
let resent = if self.fastresend > 0 {
self.fastresend
} else {
u32::max_value()
u32::MAX
};
let rtomin = if !self.nodelay { self.rx_rto >> 3 } else { 0 };
@ -1381,7 +1381,7 @@ impl<Output: AsyncWrite + Unpin + Send> Kcp<Output> {
let resent = if self.fastresend > 0 {
self.fastresend
} else {
u32::max_value()
u32::MAX
};
let rtomin = if !self.nodelay { self.rx_rto >> 3 } else { 0 };

View file

@ -86,7 +86,7 @@ async fn on_login_request(
.flatten()
else {
tracing::debug!("login: account '{}' not found", &request.account);
response.code = ErrorCode::InvalidUserId.into();
response.error_code = ErrorCode::InvalidUserId.into();
return;
};
@ -100,7 +100,7 @@ async fn on_login_request(
&request.login_trace_id,
&account.user_id
);
response.code = ErrorCode::LoginRetry.into();
response.error_code = ErrorCode::LoginRetry.into();
return;
}
@ -113,7 +113,7 @@ async fn on_login_request(
ban_time_stamp,
ban_time_stamp - cur_time_stamp
);
response.code = ErrorCode::AccountIsBlocked.into();
response.error_code = ErrorCode::AccountIsBlocked.into();
return;
}
}
@ -134,13 +134,13 @@ async fn on_login_request(
"login: first login on account {}, awaiting create character request",
&account.user_id
);
response.code = ErrorCode::HaveNoCharacter.into();
response.error_code = ErrorCode::HaveNoCharacter.into();
return;
};
session.player_id = Some(player_id);
response.code = ErrorCode::Success.into();
response.timestamp = time_util::unix_timestamp() as i64;
response.error_code = ErrorCode::Success.into();
response.timestamp = time_util::unix_timestamp_ms() as i64;
tracing::info!(
"login success, user_id: {}, player_id: {}",

View file

@ -155,11 +155,11 @@ impl Session {
fn next_message(&mut self) -> Option<Message> {
self.decoder.pop_with(|buf| {
Message::decode(&buf)
Message::decode(buf)
.inspect_err(|err| {
tracing::error!(
"failed to decode a message, err: {err}, buf: {}",
hex::encode(&buf)
hex::encode(buf)
)
})
.ok()

View file

@ -15,8 +15,7 @@ impl LengthFieldBasedDecoder {
pub fn input(&mut self, data: &[u8]) {
self.ensure_capacity(data.len());
(&mut self.buffer[self.cur_index..self.cur_index + data.len()]).copy_from_slice(data);
self.buffer[self.cur_index..self.cur_index + data.len()].copy_from_slice(data);
self.cur_index += data.len();
}

View file

@ -64,7 +64,7 @@ impl UdpServer {
conv_id,
addr,
self.socket.clone(),
&self.protokey_helper,
self.protokey_helper,
self.db.clone(),
);
self.session_mgr.add(conv_id, session);

View file

@ -26,11 +26,12 @@ async fn main() -> Result<()> {
LazyLock::new(|| config_util::load_or_create("hotpatch.toml"));
::common::splash::print_splash();
::common::logging::init(::tracing::Level::DEBUG);
::common::logging::init_axum(::tracing::Level::DEBUG);
Application::new()
.get("/:env/client/:hash/:platform/config.json", get_config)
.with_encryption(&CONFIG.encryption)
.with_logger()
.serve(&CONFIG.network)
.await?;

View file

@ -11,13 +11,10 @@ pub async fn handle_login_api_call(
tracing::debug!("login requested");
let user_data = parameters.user_data;
let result = match login(&state, parameters).await {
Ok(result) => result,
Err(err) => {
tracing::warn!("login: internal error occurred {err:?}");
schema::LoginResult::error(-1, String::from("Internal server error"))
}
};
let result = login(&state, parameters).await.unwrap_or_else(|err| {
tracing::warn!("login: internal error occurred {err:?}");
schema::LoginResult::error(-1, String::from("Internal server error"))
});
Json(result.with_user_data(user_data))
}
@ -32,7 +29,7 @@ async fn login(state: &ServiceState, params: schema::LoginParameters) -> Result<
Some(account) => {
if let Some(ban_time_stamp) = account.ban_time_stamp {
if time_util::unix_timestamp() < ban_time_stamp as u64 {
return Ok(schema::LoginResult::banned(String::from("You're banned MF"), ban_time_stamp as i64));
return Ok(schema::LoginResult::banned(String::from("You're banned MF"), ban_time_stamp));
}
}

View file

@ -1,6 +1,7 @@
use std::{process, sync::LazyLock};
use anyhow::Result;
use config::{GatewayConfig, ServerConfig};
use shorekeeper_database::PgPool;
use shorekeeper_http::{Application, StatusCode};
@ -21,7 +22,7 @@ async fn main() -> Result<()> {
LazyLock::new(|| ::common::config_util::load_or_create("loginserver.toml"));
::common::splash::print_splash();
::common::logging::init(::tracing::Level::DEBUG);
::common::logging::init_axum(::tracing::Level::DEBUG);
let Ok(pool) = shorekeeper_database::connect_to(&CONFIG.database).await else {
tracing::error!(
@ -37,10 +38,11 @@ async fn main() -> Result<()> {
pool,
gateway: &CONFIG.gateway,
})
.get("/health", || async { StatusCode::OK })
.get("/api/login", handler::handle_login_api_call)
.serve(&CONFIG.network)
.await?;
.get("/health", || async { StatusCode::OK })
.get("/api/login", handler::handle_login_api_call)
.with_logger()
.serve(&CONFIG.network)
.await?;
Ok(())
}

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