Compare commits
496 Commits
nightly
...
1.6-pre-alpha
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fcf47cebd | |||
| 826e80c5d8 | |||
| 3944d41e85 | |||
| 114e59a3bd | |||
| db7146e00e | |||
| e6718cec58 | |||
| 6f2c0077cb | |||
| 5f1aadbdb5 | |||
| 7cd0fd22af | |||
| e6c6e5ceff | |||
| bda1dac90b | |||
| 0bc89a9bf3 | |||
| 7e019cc112 | |||
| 713e005975 | |||
| 0cb1098819 | |||
| ba8c2c741d | |||
| feb20c47f4 | |||
| 7f4aa47943 | |||
| 525bd495b4 | |||
| cd70256315 | |||
| 920b5d15ba | |||
| fd4b5f8afb | |||
| bdcdf75a8e | |||
| f100261e88 | |||
| cea49dae2b | |||
| f44715cdf4 | |||
| 57c9c49757 | |||
| df9efb6210 | |||
| cc92d1ad2b | |||
| 63f11053c7 | |||
| 11c7ee19a3 | |||
| 211ab4ee7c | |||
| 83f7cbc963 | |||
| 2451131e36 | |||
| 0e5271b322 | |||
| 6e2b4aff2f | |||
| e5f426690f | |||
| e4f432b593 | |||
| f555fef48d | |||
| ac07edfe68 | |||
| fb654f0e56 | |||
| 718814c491 | |||
| b4f86719ce | |||
| 6792559b5b | |||
| ffb31b0986 | |||
| f1b9bf0571 | |||
| d4df041c44 | |||
| a7ea28281e | |||
| 6a1ebcf9fe | |||
| a79f9f2a69 | |||
| 798776c822 | |||
| 9cb7b28d8f | |||
| cac2385c35 | |||
| 0112140f0e | |||
| 174f711e42 | |||
| 3a9068f060 | |||
| 4cc5054b91 | |||
| a9b05c62ff | |||
| 952ab4f7b3 | |||
| 598790dedd | |||
| 979be4cfa8 | |||
| 7dec978e9a | |||
| 78f0dfaebb | |||
| e6c8d4df46 | |||
| 279492e0bd | |||
| 478305112f | |||
| 745b212b42 | |||
| 50153d860a | |||
| 822a69573f | |||
| 23e4151b2e | |||
| 71195e93c2 | |||
| 8961eb102b | |||
| 2828b9b74f | |||
| 6b34ab6e36 | |||
| 69419d41e0 | |||
| a50ae062d8 | |||
| 9e0ce58bd2 | |||
| 806f1d194a | |||
| 4e6bab1b53 | |||
| 64692bc161 | |||
| 8d27c072ca | |||
| d7c0abdfc2 | |||
| d0b6bbaa3a | |||
| 411b199a98 | |||
| b48b15b652 | |||
| e83480b7f9 | |||
| d1b4f23992 | |||
| 42c0ef2159 | |||
| 8701ac703e | |||
| fe53e01a4a | |||
| 617dbc9812 | |||
| 1606d25fed | |||
| 5585876005 | |||
| 54c7f4ce92 | |||
| a062322dda | |||
| 4599c558e4 | |||
| 71f7f4b257 | |||
| 3f3b5fd2d0 | |||
| 9173e9a99e | |||
| 6e64c458df | |||
| 76f959abe9 | |||
| f2e85d66ab | |||
| e30ef1f4bd | |||
| 6e8f6e78f6 | |||
| f28c83ccbd | |||
| a83821b3d2 | |||
| 3b5436064e | |||
| c4b321324e | |||
| ba8d525ddf | |||
| 884e2f39f0 | |||
| 76c529f1d1 | |||
| df0064bd38 | |||
| f1fc96bbd7 | |||
| 23e9326f5f | |||
| 4621fb4987 | |||
| 6c82895fa7 | |||
| 84b040766f | |||
| f44558cd3b | |||
| accdbbcbc6 | |||
| 2d4107f35e | |||
| bac53fd769 | |||
| 8f63fee5b5 | |||
| 8d0ad0b600 | |||
| dc08c0c1f6 | |||
| faaa856aa5 | |||
| eab64f9254 | |||
| 6c936cb8f9 | |||
| 83a37f8556 | |||
| bb4eacce26 | |||
| d5b71db5d4 | |||
| 89ebd0305c | |||
| b209202292 | |||
| f199241ed8 | |||
| 50229116a7 | |||
| f192caeae1 | |||
| 6005b2ba11 | |||
| 6e213611cc | |||
| 347d65af62 | |||
| 636ee3a33f | |||
| 8ff08e0804 | |||
| 6d7fe58719 | |||
| c7e14ef191 | |||
| 39eb5ef80a | |||
| d25c2539e0 | |||
| eb31a33104 | |||
| 8b3f8ffd9b | |||
| cca09c4178 | |||
| 871bb7e64e | |||
| 02da3ce8af | |||
| 711ba62fc2 | |||
| 8f7d3135db | |||
| cb563a1bf8 | |||
| a84ab42a42 | |||
| b3e285f92f | |||
| 01433f0de6 | |||
| c034cd8aa9 | |||
| 363c2ccb00 | |||
| 7380c162be | |||
| f1c7192029 | |||
| 6720636d48 | |||
| 2af9482da9 | |||
| bf22c17bba | |||
| 01492a0c55 | |||
| c17b234057 | |||
| 739577bbf1 | |||
| 2d2da624e4 | |||
| 9e66762061 | |||
| 84d578366a | |||
| 03bc3a7742 | |||
| 5bfc7f836e | |||
| 54977c79f1 | |||
| 0352bfa328 | |||
| 6fbc386b45 | |||
| 203e3b2528 | |||
| c9aa995224 | |||
| c201b94897 | |||
| 87f873d1a5 | |||
| 6c355b109d | |||
| 2ef3012672 | |||
| 7229574ff4 | |||
| eff9fd50b1 | |||
| e2cbf26360 | |||
| b0f448fd5f | |||
| 721c4d3dee | |||
| 87dfc18d91 | |||
| fa4fe2c10f | |||
| 3a088ddbfc | |||
| 52cc9b647c | |||
| 1aa23901d5 | |||
| 52584d5774 | |||
| 62bf15dede | |||
| 722ca16f57 | |||
| 3e61498366 | |||
| 1ff2fa5691 | |||
| 5088973474 | |||
| b63378f4d0 | |||
| f234830c54 | |||
| 5075289657 | |||
| c20118aac1 | |||
| c4312faf4a | |||
| 7566423889 | |||
| be13e0d102 | |||
| 490802862a | |||
| e369a50b89 | |||
| 13a6431248 | |||
| cb73db7146 | |||
| 9dcd6bb363 | |||
| 842e1435f2 | |||
| 4d03f8e371 | |||
| e040d53cc2 | |||
| 22bfa43ec5 | |||
| 7123bea8e5 | |||
| 973f6873e4 | |||
| efb759b9f9 | |||
| ad3ff96f61 | |||
| e156b2195f | |||
| 73a99e033d | |||
| b44d722b5f | |||
| 307b6365f7 | |||
| a49ee19d3d | |||
| 9c5c1da4c9 | |||
| 0348296dc1 | |||
| adb9439226 | |||
| ae10da874b | |||
| cb91486848 | |||
| 3289c03fd2 | |||
| d4ffb0c929 | |||
| 3c089e2c47 | |||
| 7a4e4182b5 | |||
| aa6c45b20d | |||
| 0943a7992f | |||
| d30e1f6bb3 | |||
| 0c71d4dbaa | |||
| c78fbd89bc | |||
| dc83cd5be9 | |||
| b5f6fc578d | |||
| 4acf964f0a | |||
| 560f092304 | |||
| 8305d9997a | |||
| d68941b3ac | |||
| 67c4646198 | |||
| a7dab1e0e1 | |||
| 2b8a3db6bf | |||
| f9609afd8a | |||
| 609fd90561 | |||
| a7d64d241d | |||
| b436abf131 | |||
| c14221f1a3 | |||
| abbc871d1a | |||
| edb8838455 | |||
| 4cca75ee35 | |||
| 669b457275 | |||
| 2ef0060037 | |||
| 55649f26ae | |||
| 6bb7d5d0fd | |||
| 8f5deaa33b | |||
| 91b0afb6ef | |||
| 4dc3cc5c28 | |||
| f9a411833a | |||
| d24d786fd3 | |||
| edae7a075a | |||
| 85b028e3e7 | |||
| 36de46a2ce | |||
| 6e5f72ee70 | |||
| 8d450813e1 | |||
| 495e5dc6b0 | |||
| 0bd1d2c2a7 | |||
| 339f817c7e | |||
| b583618e6c | |||
| dc0fafbe9e | |||
| b9e22a880f | |||
| cb7f0290ec | |||
| d70c739208 | |||
| 29202906e1 | |||
| 1d3e53f734 | |||
| a26a799a9f | |||
| 033d0a3f2e | |||
| 95e302a698 | |||
| ff9e16bd5e | |||
| 01c668b159 | |||
| 74e51c8070 | |||
| 01524b1796 | |||
| 83d79b0a17 | |||
| c3d7c2b335 | |||
| 6b6125eb09 | |||
| 09bb121ffe | |||
| a50be5b869 | |||
| ca5ef8cf4c | |||
| 4e33956acd | |||
| 9aae315e11 | |||
| 742e7ea998 | |||
| 69bd4c837c | |||
| 8c10d24842 | |||
| 6b681afdee | |||
| 6186497999 | |||
| 2571da594e | |||
| 7c52cad569 | |||
| 303bc940f2 | |||
| eda903abde | |||
| 5d5bc668eb | |||
| 6e5653a168 | |||
| 87343b8255 | |||
| cb21291983 | |||
| 8dd2241590 | |||
| 858fa81ea2 | |||
| 8d6c70b75e | |||
| ffd6ce23c3 | |||
| 5a882d0bc8 | |||
| cb1b4f4970 | |||
| 0de040548d | |||
| 40f4cf88c9 | |||
| 275b39fb1e | |||
| fe5da3cf90 | |||
| 4ed4e37604 | |||
| 10552d119f | |||
| d4e0e6f0ab | |||
| e2fce3cf9b | |||
| ad5ffc22ac | |||
| f980790bd7 | |||
| df8519961a | |||
| 171a6be736 | |||
| 91035d9a3f | |||
| 60e7d91409 | |||
| d05d1dac85 | |||
| 8184b4d155 | |||
| a0d5f9d2df | |||
| 49f7f9e3e1 | |||
| 911d8d5d64 | |||
| 3015ffe3ba | |||
| eb74bcf987 | |||
| b63898d8b9 | |||
| 459cfccff5 | |||
| fdb66cba6d | |||
| 93d705e402 | |||
| c8bbdc69d0 | |||
| 9b1587f334 | |||
| 3e67cc519e | |||
| eee6f614f1 | |||
| 62e5bad6ac | |||
| 163f342d7a | |||
| 370247eef2 | |||
| f22ecb03f2 | |||
| 577857ab5a | |||
| 095d16a57e | |||
| 862c2c0a71 | |||
| d7e9ce8ff1 | |||
| e657a02f7a | |||
| 182ff877f0 | |||
| 521358abb5 | |||
| b62aed5c5a | |||
| 5c4e31806d | |||
| 9f8d695d45 | |||
| e0e5f52d36 | |||
| 9fda563438 | |||
| 6d5bb285c9 | |||
| 7ac405b8f9 | |||
| 45d53bd38c | |||
| cc52f66f5d | |||
| e072c2aee8 | |||
| e52135c343 | |||
| cb53e8a664 | |||
| 44a8445b54 | |||
| b0a197cd18 | |||
| 9ba52b332c | |||
| ecfc77e57a | |||
| 2ba3e33d39 | |||
| 71ecdbd5b9 | |||
| bbe0b21aa4 | |||
| 51dd445a31 | |||
| 264158957c | |||
| 9d8963222c | |||
| c1a35dbab3 | |||
| d3931cc57c | |||
| 3795ced530 | |||
| 1372a5c33f | |||
| 9527da9785 | |||
| 3072db0e21 | |||
| 444e6d7ffa | |||
| 809c935d37 | |||
| 10bd54a785 | |||
| ece899f2a1 | |||
| 5d1a182e47 | |||
| 228ab9e727 | |||
| 2605aafc4f | |||
| 582bb2a7d7 | |||
| 0a3dbb1f59 | |||
| 5d1832fcef | |||
| 30ed509c55 | |||
| 2ad91011c3 | |||
| e40d4695ea | |||
| e4c03f8ccc | |||
| e10a0f5415 | |||
| 2a29011015 | |||
| a37e8869ac | |||
| 038860f158 | |||
| 51e678d760 | |||
| 1e2a9992d8 | |||
| 5b7bb917ef | |||
| 72aff78718 | |||
| cdeef7279c | |||
| 9cb5c557c5 | |||
| 1e1259d0a5 | |||
| 20b326d396 | |||
| b7cad3b8f7 | |||
| cf8b54a3b5 | |||
| edca1e2b98 | |||
| 89410b8b46 | |||
| a9397690e2 | |||
| ea678ece94 | |||
| f28f23f560 | |||
| b2911017d0 | |||
| cda4f702a1 | |||
| b6a0ae7f4a | |||
| b6f3508680 | |||
| ad0368b4c0 | |||
| b1d67bd1c4 | |||
| cda3d37f37 | |||
| abed0f146f | |||
| bdeaeee293 | |||
| b37ce4d0c1 | |||
| e3ff62bdc7 | |||
| eab6c79e15 | |||
| 260678c1bc | |||
| b2d300784f | |||
| 8d1d4f5eb7 | |||
| 25a03c7e27 | |||
| 27ab63ae7a | |||
| 9567e97b31 | |||
| b10a8b47d5 | |||
| 1bc274e2d0 | |||
| dca3420071 | |||
| bc3fc70a95 | |||
| eee983dfd2 | |||
| fbec69cd71 | |||
| 615c49e395 | |||
| 462d68df6b | |||
| 0224abc9bd | |||
| 16fe2fe1ab | |||
| 61c4486551 | |||
| 17ef088492 | |||
| 1111879c4d | |||
| e9d0b73296 | |||
| 8eadbf6fee | |||
| 8ae491d0d5 | |||
| 3dd125f861 | |||
| 01b4753dc6 | |||
| 5a5d78ac27 | |||
| a05eedd68e | |||
| 05304f9461 | |||
| fff5ec4534 | |||
| cf7641cee0 | |||
| a77e0c1fc9 | |||
| f374de2064 | |||
| 8e0620bedb | |||
| 8bb39b1bfa | |||
| 2450956fda | |||
| 56cd17401b | |||
| a53b514fec | |||
| de93af73f2 | |||
| 44f106fd0c | |||
| 8ac5cc40c1 | |||
| 8d7eebd390 | |||
| 0a5bb67c54 | |||
| f866151cc5 | |||
| 08e17b1714 | |||
| fa96f4c073 | |||
| dc5cf2b965 | |||
| f47f0570f9 | |||
| e0c96cc200 | |||
| 17e1ae9ea9 | |||
| 8401d616e0 | |||
| d8ac486984 | |||
| 335ea2ca38 | |||
| c73ff96690 | |||
| 15b23e3a4e | |||
| 47f86c098d | |||
| 6c2c7e881f | |||
| 74a2265200 | |||
| 0b2ec1d626 | |||
| 8ee188cf19 | |||
| eb5b23ae17 | |||
| 77999fe8f3 | |||
| 64fda51917 | |||
| 4165b757a5 | |||
| d31799ab7b | |||
| 8a46bd6b68 | |||
| 58362c2613 | |||
| 7d6c5fe3bd | |||
| f9b9d78b79 | |||
| 7670af88a2 | |||
| 0800361b40 | |||
| cdf3f41127 | |||
| 552bf8d61c | |||
| 2f58e48f42 | |||
| 67035f5d2f | |||
| b48206d942 |
@@ -0,0 +1,4 @@
|
||||
[*.cs]
|
||||
|
||||
# CS0649: Field 'VehicleInfo.Name' is never assigned to, and will always have its default value null
|
||||
dotnet_diagnostic.CS0649.severity = silent
|
||||
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [RAGECOOP] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
#patreon: # Replace with a single Patreon username
|
||||
#open_collective: # Replace with a single Open Collective username
|
||||
#ko_fi: # Replace with a single Ko-fi username
|
||||
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
#liberapay: # Replace with a single Liberapay username
|
||||
#issuehunt: # Replace with a single IssueHunt username
|
||||
#otechie: # Replace with a single Otechie username
|
||||
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://www.buymeacoffee.com/xEntenKoeniqx', 'https://patreon.com/Sardelka']
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Build test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**' # matches every branch
|
||||
- '!main' # excludes main
|
||||
- '!dev-nightly' # excludes nightly
|
||||
|
||||
pull_request:
|
||||
branches: [ "main", "dev-nightly" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dotnet-version: ['7.0.x']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ matrix.dotnet-version }}
|
||||
|
||||
- name: Initialise submodules
|
||||
run: git submodule update --init
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Restore nuget packages
|
||||
run: nuget restore
|
||||
|
||||
- name: Build
|
||||
run: .\build.cmd
|
||||
|
||||
- name: Upload server
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: RageCoop.Server
|
||||
path: bin/Release/Server
|
||||
|
||||
- name: Upload Client
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: RageCoop.Client
|
||||
path: bin/Release/Client
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -2,9 +2,7 @@ name: Nightly-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: [ "dev-nightly" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -12,7 +10,7 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dotnet-version: ['6.0.x']
|
||||
dotnet-version: ['7.0.x']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -20,31 +18,72 @@ jobs:
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ matrix.dotnet-version }}
|
||||
|
||||
- name: Initialise submodules
|
||||
run: git submodule update --init
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build client
|
||||
run: dotnet build RageCoop.Client/RageCoop.Client.csproj --no-restore --configuration Release -o RageCoop.Client/bin/RageCoop
|
||||
- name: Build server win-x64
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r win-x64 -o RageCoop.Server/bin/win-x64 -c Release
|
||||
- name: Build server linux-x64
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-x64 -o RageCoop.Server/bin/linux-x64 -c Release
|
||||
- name: Build server linux-arm
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-arm -o RageCoop.Server/bin/linux-arm -c Release
|
||||
- uses: actions/upload-artifact@v3
|
||||
|
||||
- name: Restore nuget packages
|
||||
run: nuget restore
|
||||
|
||||
- name: Build artifacts
|
||||
run: .\build.cmd
|
||||
|
||||
- name: Build server for different platforms
|
||||
run: .\publish-server.cmd
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Client
|
||||
path: RageCoop.Client/bin
|
||||
- uses: actions/upload-artifact@v3
|
||||
files: bin/Release/Client
|
||||
dest: RageCoop.Client.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Server-win-x64
|
||||
path: RageCoop.Server/bin/win-x64
|
||||
- uses: actions/upload-artifact@v3
|
||||
files: bin/API
|
||||
dest: bin/Artifacts/SDK.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Server-linux-x64
|
||||
path: RageCoop.Server/bin/linux-x64
|
||||
- uses: actions/upload-artifact@v3
|
||||
files: bin/Release/Server/win-x64
|
||||
dest: bin/Artifacts/RageCoop.Server-win-x64.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Server-linux-arm
|
||||
path: RageCoop.Server/bin/linux-arm
|
||||
files: bin/Release/Server/win-x86
|
||||
dest: bin/Artifacts/RageCoop.Server-win-x86.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Server/linux-x64
|
||||
dest: bin/Artifacts/RageCoop.Server-linux-x64.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Server/linux-arm
|
||||
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Server/linux-arm
|
||||
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Server/linux-arm64
|
||||
dest: bin/Artifacts/RageCoop.Server-linux-arm64.zip
|
||||
|
||||
- name: Deploy binaries
|
||||
uses: Sardelka9515/deploy-nightly@v0.7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/RAGECOOP/GTAV-RESOURCES/releases/75463254/assets{?name,label}
|
||||
release_id: 75463254
|
||||
asset_path: bin/Artifacts
|
||||
asset_content_type: application/zip
|
||||
max_releases: 7
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
**/bin
|
||||
**/obj
|
||||
**/packages
|
||||
**/.vs
|
||||
**/.vs
|
||||
**/.vscode
|
||||
@@ -0,0 +1,6 @@
|
||||
[submodule "libs/Lidgren.Network"]
|
||||
path = libs/Lidgren.Network
|
||||
url = https://github.com/RAGECOOP/lidgren-network-gen3
|
||||
[submodule "libs/ScriptHookVDotNetCore"]
|
||||
path = libs/ScriptHookVDotNetCore
|
||||
url = https://github.com/Sardelka9515/scripthookvdotnetcore
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,314 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using System.Runtime.Remoting;
|
||||
using System.Runtime.Remoting.Channels;
|
||||
using System.Runtime.Remoting.Channels.Ipc;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using CefSharp.OffScreen;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace RageCoop.Client.CefHost
|
||||
{
|
||||
public enum BufferMode
|
||||
{
|
||||
Full = 1,
|
||||
Dirty = 2
|
||||
}
|
||||
|
||||
public enum MouseButton
|
||||
{
|
||||
Left,
|
||||
Middle,
|
||||
Right
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hosted by CefHost for managing cef instances.
|
||||
/// </summary>
|
||||
public class CefController : MarshalByRefObject, IDisposable
|
||||
{
|
||||
private static Process _host;
|
||||
private static ActivatedClientTypeEntry _controllerEntry;
|
||||
private static IpcChannel _adapterChannel;
|
||||
public static Action<string> OnCefMessage;
|
||||
private ChromiumWebBrowser _browser;
|
||||
private MemoryMappedFile _mmf;
|
||||
private string _mmfName;
|
||||
private SafeMemoryMappedViewHandle _mmfView;
|
||||
private BufferMode _mode;
|
||||
private CefProcessor _processor;
|
||||
public IntPtr PtrBuffer { get; private set; }
|
||||
|
||||
public int FrameRate
|
||||
{
|
||||
get => _browser.GetBrowserHost().WindowlessFrameRate;
|
||||
set => _browser.GetBrowserHost().WindowlessFrameRate = value;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_browser?.Dispose();
|
||||
|
||||
_mmf?.Dispose();
|
||||
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
|
||||
_mmfView?.Dispose();
|
||||
|
||||
PtrBuffer = IntPtr.Zero;
|
||||
_mmf = null;
|
||||
_mmfView = null;
|
||||
}
|
||||
|
||||
|
||||
public static void Initialize(string fileName = "RageCoop.Client.CefHost.exe")
|
||||
{
|
||||
_host = new Process();
|
||||
_host.StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
_host.EnableRaisingEvents = true;
|
||||
_host.Start();
|
||||
RegisterChannels(_host.StandardOutput.ReadLine());
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (_host?.HasExited == false) OnCefMessage?.Invoke("[CEF]: " + _host.StandardOutput.ReadLine());
|
||||
});
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (_host?.HasExited == false)
|
||||
OnCefMessage?.Invoke("[CEF][ERROR]: " + _host.StandardError.ReadLine());
|
||||
});
|
||||
}
|
||||
|
||||
public static void ShutDown()
|
||||
{
|
||||
if (_host == null) return;
|
||||
_host.StandardInput.WriteLine("exit");
|
||||
_host.WaitForExit(1000);
|
||||
_host.Kill();
|
||||
_host = null;
|
||||
}
|
||||
|
||||
private static void RegisterChannels(string hostChannel)
|
||||
{
|
||||
var service = Guid.NewGuid().ToString();
|
||||
Console.WriteLine("Registering adapter channel: " + service);
|
||||
_adapterChannel = new IpcChannel(service);
|
||||
ChannelServices.RegisterChannel(_adapterChannel, false);
|
||||
|
||||
_controllerEntry = new ActivatedClientTypeEntry(typeof(CefController), "ipc://" + hostChannel);
|
||||
RemotingConfiguration.RegisterActivatedClientType(_controllerEntry);
|
||||
Console.WriteLine("Registered controller entry: " + "ipc://" + hostChannel);
|
||||
|
||||
|
||||
RemotingConfiguration.RegisterActivatedServiceType(typeof(CefAdapter));
|
||||
Console.WriteLine("Registered service: " + nameof(CefAdapter));
|
||||
_host.StandardInput.WriteLine("ipc://" + service);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called inside client process
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="adapter"></param>
|
||||
/// <param name="bufferMode"></param>
|
||||
/// <param name="bufferSize"></param>
|
||||
/// <returns></returns>
|
||||
public static CefController Create(int id, Size size, out CefAdapter adapter, BufferMode bufferMode,
|
||||
long bufferSize = 1024 * 1024 * 16)
|
||||
{
|
||||
if (RemotingConfiguration.IsRemotelyActivatedClientType(typeof(CefController)) == null)
|
||||
throw new RemotingException();
|
||||
var controller = new CefController();
|
||||
controller.Activate(id, size, bufferMode, bufferSize);
|
||||
adapter = CefAdapter.Adapters[id];
|
||||
controller.Ping();
|
||||
return controller;
|
||||
}
|
||||
|
||||
public unsafe void Activate(int id, Size size, BufferMode mode = BufferMode.Dirty,
|
||||
long sharedMemorySize = 1024 * 1024 * 16)
|
||||
{
|
||||
_mode = mode;
|
||||
_mmfName = Guid.NewGuid().ToString();
|
||||
|
||||
// Set up shared memory
|
||||
_mmf = MemoryMappedFile.CreateNew(_mmfName, sharedMemorySize);
|
||||
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
|
||||
byte* pBuf = null;
|
||||
try
|
||||
{
|
||||
_mmfView.AcquirePointer(ref pBuf);
|
||||
PtrBuffer = (IntPtr)pBuf;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
var adapter = new CefAdapter();
|
||||
adapter.Register(id, mode, _mmfName);
|
||||
_browser = new ChromiumWebBrowser();
|
||||
_browser.RenderHandler = _processor = new CefProcessor(size, adapter, PtrBuffer, mode);
|
||||
while (_browser.GetBrowserHost() == null) Thread.Sleep(20); // Wait till the browser is actually created
|
||||
Console.WriteLine("CefController created: " + size);
|
||||
}
|
||||
|
||||
|
||||
public void LoadUrl(string url)
|
||||
{
|
||||
_browser.LoadUrl(url);
|
||||
}
|
||||
|
||||
public void Resize(Size size)
|
||||
{
|
||||
_browser.Size = size;
|
||||
_processor.Size = size;
|
||||
}
|
||||
|
||||
public void SendMouseClick(int x, int y, int modifiers, MouseButton button, bool mouseUp, int clicks)
|
||||
{
|
||||
var e = new MouseEvent(x, y, (CefEventFlags)modifiers);
|
||||
_browser.GetBrowserHost()
|
||||
?.SendMouseClickEvent(e, (MouseButtonType)button, mouseUp, clicks);
|
||||
}
|
||||
|
||||
public void SendMouseMove(int x, int y, bool leave = false)
|
||||
{
|
||||
var e = new MouseEvent(x, y, 0);
|
||||
|
||||
_browser.GetBrowserHost()?.SendMouseMoveEvent(e, leave);
|
||||
}
|
||||
|
||||
public DateTime Ping()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
;
|
||||
}
|
||||
|
||||
public override object InitializeLifetimeService()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hosted by client for receiving rendering data
|
||||
/// </summary>
|
||||
public class CefAdapter : MarshalByRefObject, IDisposable
|
||||
{
|
||||
public delegate void PaintDelegate(int bufferSize, Rectangle dirtyRect);
|
||||
|
||||
public delegate void ResizeDelegate(Size newSize);
|
||||
|
||||
public static Dictionary<int, CefAdapter> Adapters = new Dictionary<int, CefAdapter>();
|
||||
|
||||
private MemoryMappedFile _mmf;
|
||||
private SafeMemoryMappedViewHandle _mmfView;
|
||||
public int Id;
|
||||
|
||||
public CefAdapter()
|
||||
{
|
||||
Console.WriteLine("Adapter created");
|
||||
}
|
||||
|
||||
public Size Size { get; private set; }
|
||||
|
||||
public IntPtr PtrBuffer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum buffer size for a paint event, use this property to allocate memory.
|
||||
/// </summary>
|
||||
/// <remarks>Value is equal to <see cref="Size" />*4, therefore will change upon resize</remarks>
|
||||
public int MaxBufferSize => Size.Height * Size.Width * 4;
|
||||
|
||||
public BufferMode BufferMode { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_mmf?.Dispose();
|
||||
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
|
||||
_mmfView?.Dispose();
|
||||
|
||||
PtrBuffer = IntPtr.Zero;
|
||||
_mmf = null;
|
||||
_mmfView = null;
|
||||
|
||||
lock (Adapters)
|
||||
{
|
||||
if (Adapters.ContainsKey(Id)) Adapters.Remove(Id);
|
||||
}
|
||||
}
|
||||
|
||||
public event PaintDelegate OnPaint;
|
||||
public event ResizeDelegate OnResize;
|
||||
|
||||
public void Resized(Size newSize)
|
||||
{
|
||||
Size = newSize;
|
||||
OnResize?.Invoke(newSize);
|
||||
}
|
||||
|
||||
public void Paint(Rectangle dirtyRect)
|
||||
{
|
||||
var size = BufferMode == BufferMode.Dirty
|
||||
? dirtyRect.Width * dirtyRect.Height * 4
|
||||
: Size.Width * Size.Height * 4;
|
||||
OnPaint?.Invoke(size, dirtyRect);
|
||||
}
|
||||
|
||||
public override object InitializeLifetimeService()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public unsafe void Register(int id, BufferMode mode, string mmfName)
|
||||
{
|
||||
lock (Adapters)
|
||||
{
|
||||
if (Adapters.ContainsKey(id)) throw new ArgumentException("Specified id is already used", nameof(id));
|
||||
|
||||
// Set up shared memory
|
||||
_mmf = MemoryMappedFile.OpenExisting(mmfName);
|
||||
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
|
||||
byte* pBuf = null;
|
||||
try
|
||||
{
|
||||
_mmfView.AcquirePointer(ref pBuf);
|
||||
PtrBuffer = (IntPtr)pBuf;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
Id = id;
|
||||
BufferMode = mode;
|
||||
Adapters.Add(id, this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure ipc connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public DateTime Ping()
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using BitmapUtil;
|
||||
using CefSharp;
|
||||
using CefSharp.Enums;
|
||||
using CefSharp.OffScreen;
|
||||
using CefSharp.Structs;
|
||||
using Size = System.Drawing.Size;
|
||||
|
||||
namespace RageCoop.Client.CefHost
|
||||
{
|
||||
internal class CefProcessor : IRenderHandler
|
||||
{
|
||||
private readonly CefAdapter _adapter;
|
||||
private readonly BufferMode _mode;
|
||||
private readonly IntPtr _pSharedBuffer;
|
||||
private Rect _rect;
|
||||
|
||||
public CefProcessor(Size size, CefAdapter adapter, IntPtr pSharedBuffer, BufferMode mode)
|
||||
{
|
||||
_adapter = adapter;
|
||||
_rect = new Rect(0, 0, size.Width, size.Height);
|
||||
_pSharedBuffer = pSharedBuffer;
|
||||
_mode = mode;
|
||||
_adapter?.Resized(size);
|
||||
}
|
||||
|
||||
public Size Size
|
||||
{
|
||||
get => new Size(_rect.Width, _rect.Height);
|
||||
set
|
||||
{
|
||||
_rect = new Rect(0, 0, value.Width, value.Height);
|
||||
_adapter?.Resized(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public ScreenInfo? GetScreenInfo()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Rect GetViewRect()
|
||||
{
|
||||
return _rect;
|
||||
}
|
||||
|
||||
public bool GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY)
|
||||
{
|
||||
screenX = viewX;
|
||||
screenY = viewY;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, IntPtr sharedHandle)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height)
|
||||
{
|
||||
var dirty = new Rectangle
|
||||
{
|
||||
Width = dirtyRect.Width,
|
||||
Height = dirtyRect.Height,
|
||||
X = dirtyRect.X,
|
||||
Y = dirtyRect.Y
|
||||
};
|
||||
var source = new BitmapInfo
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
BytesPerPixel = 4,
|
||||
Scan0 = buffer
|
||||
};
|
||||
|
||||
switch (_mode)
|
||||
{
|
||||
case BufferMode.Dirty:
|
||||
Unsafe.CopyRegion(source, _pSharedBuffer, dirty);
|
||||
break;
|
||||
case BufferMode.Full:
|
||||
{
|
||||
var target = source;
|
||||
target.Scan0 = _pSharedBuffer;
|
||||
Unsafe.UpdateRegion(source, target, dirty, dirty.Location);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_adapter?.Paint(dirty);
|
||||
}
|
||||
|
||||
public void OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public bool StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdateDragCursor(DragOperationsMask operation)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnPopupShow(bool show)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnPopupSize(Rect rect)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
public void OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode)
|
||||
{
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Remoting;
|
||||
using System.Runtime.Remoting.Channels;
|
||||
using System.Runtime.Remoting.Channels.Ipc;
|
||||
using System.Security.Permissions;
|
||||
using System.Threading.Tasks;
|
||||
using CefSharp;
|
||||
using CefSharp.OffScreen;
|
||||
|
||||
namespace RageCoop.Client.CefHost
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
[SecurityPermission(SecurityAction.Demand)]
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
Cef.Initialize(new CefSettings
|
||||
{
|
||||
BackgroundColor = 0x00
|
||||
});
|
||||
|
||||
var name = Guid.NewGuid().ToString();
|
||||
|
||||
var channel = new IpcChannel(name);
|
||||
ChannelServices.RegisterChannel(channel, false);
|
||||
RemotingConfiguration.RegisterActivatedServiceType(typeof(CefController));
|
||||
|
||||
|
||||
// Write to stdout so it can be read by the client
|
||||
Console.WriteLine(name);
|
||||
|
||||
var adapterUrl = Console.ReadLine();
|
||||
var adapterEntry = new ActivatedClientTypeEntry(typeof(CefAdapter), adapterUrl);
|
||||
Console.WriteLine("Registered adapter entry: " + adapterUrl);
|
||||
RemotingConfiguration.RegisterActivatedClientType(adapterEntry);
|
||||
|
||||
var channelData = (ChannelDataStore)channel.ChannelData;
|
||||
foreach (var uri in channelData.ChannelUris) Console.WriteLine("Channel URI: {0}", uri);
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Util.GetParentProcess().WaitForExit();
|
||||
Console.WriteLine("Parent process terminated, exiting...");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
}
|
||||
});
|
||||
|
||||
while (true)
|
||||
switch (Console.ReadLine())
|
||||
{
|
||||
case "exit":
|
||||
Cef.Shutdown();
|
||||
Environment.Exit(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A utility class to determine a process parent.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Util
|
||||
{
|
||||
// These members must match PROCESS_BASIC_INFORMATION
|
||||
internal IntPtr Reserved1;
|
||||
internal IntPtr PebBaseAddress;
|
||||
internal IntPtr Reserved2_0;
|
||||
internal IntPtr Reserved2_1;
|
||||
internal IntPtr UniqueProcessId;
|
||||
internal IntPtr InheritedFromUniqueProcessId;
|
||||
|
||||
[DllImport("ntdll.dll")]
|
||||
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass,
|
||||
ref Util processInformation, int processInformationLength, out int returnLength);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent process of the current process.
|
||||
/// </summary>
|
||||
/// <returns>An instance of the Process class.</returns>
|
||||
public static Process GetParentProcess()
|
||||
{
|
||||
return GetParentProcess(Process.GetCurrentProcess().Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent process of specified process.
|
||||
/// </summary>
|
||||
/// <param name="id">The process id.</param>
|
||||
/// <returns>An instance of the Process class.</returns>
|
||||
public static Process GetParentProcess(int id)
|
||||
{
|
||||
var process = Process.GetProcessById(id);
|
||||
return GetParentProcess(process.Handle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent process of a specified process.
|
||||
/// </summary>
|
||||
/// <param name="handle">The process handle.</param>
|
||||
/// <returns>An instance of the Process class.</returns>
|
||||
public static Process GetParentProcess(IntPtr handle)
|
||||
{
|
||||
var pbi = new Util();
|
||||
int returnLength;
|
||||
var status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength);
|
||||
if (status != 0)
|
||||
throw new Win32Exception(status);
|
||||
|
||||
try
|
||||
{
|
||||
return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// not found
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("RageCoop.Client.CefHost")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("RageCoop.Client.CefHost")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ba750e08-5e41-4b56-8ad5-875716d2ccea")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props"
|
||||
Condition="Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props')" />
|
||||
<Import Project="..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props"
|
||||
Condition="Exists('..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props')" />
|
||||
<Import Project="..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props"
|
||||
Condition="Exists('..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
|
||||
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{BA750E08-5E41-4B56-8AD5-875716D2CCEA}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>RageCoop.Client.CefHost</RootNamespace>
|
||||
<AssemblyName>RageCoop.Client.CefHost</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
<OutDir>..\..\bin\$(Configuration)\Client\SubProcess</OutDir>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BitmapUtil">
|
||||
<HintPath>..\..\libs\BitmapUtil.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference
|
||||
Include="CefSharp, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
|
||||
PrivateAssets="All">
|
||||
<HintPath>..\..\packages\CefSharp.Common.106.0.290\lib\net452\CefSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference
|
||||
Include="CefSharp.Core, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
|
||||
PrivateAssets="All">
|
||||
<HintPath>..\..\packages\CefSharp.Common.106.0.290\lib\net452\CefSharp.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference
|
||||
Include="CefSharp.OffScreen, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
|
||||
PrivateAssets="All">
|
||||
<HintPath>..\..\packages\CefSharp.OffScreen.106.0.290\lib\net462\CefSharp.OffScreen.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Runtime.Remoting" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CefController.cs" />
|
||||
<Compile Include="CefProcessor.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props')"
|
||||
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props')"
|
||||
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props')"
|
||||
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets')"
|
||||
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets"
|
||||
Condition="Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets')" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>
|
||||
if not exist "ref" mkdir "ref"
|
||||
copy "$(TargetFileName)" "ref\$(TargetName).dll" /y
|
||||
copy "CefSharp.OffScreen.dll" "ref\CefSharp.OffScreen.dll" /y
|
||||
copy "CefSharp.dll" "ref\CefSharp.dll" /y
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
|
||||
<StartAction>Program</StartAction>
|
||||
<StartProgram>M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Client\SubProcess\RageCoop.Client.CefHost.exe</StartProgram>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<packages>
|
||||
<package id="cef.redist.x64" version="106.0.29" targetFramework="net48" />
|
||||
<package id="cef.redist.x86" version="106.0.29" targetFramework="net48" />
|
||||
<package id="CefSharp.Common" version="106.0.290" targetFramework="net48" />
|
||||
<package id="CefSharp.OffScreen" version="106.0.290" targetFramework="net48" />
|
||||
</packages>
|
||||
+335214
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"Bullet": {
|
||||
"2861067768": "VEHICLE_WEAPON_INSURGENT_MINIGUN",
|
||||
"3683206664": "VEHICLE_WEAPON_TECHNICAL_MINIGUN",
|
||||
"3048454573": "VEHICLE_WEAPON_AKULA_TURRET_SINGLE",
|
||||
"476907586": "VEHICLE_WEAPON_AKULA_TURRET_DUAL",
|
||||
"431576697": "VEHICLE_WEAPON_AKULA_MINIGUN",
|
||||
"3405172033": "VEHICLE_WEAPON_ANNIHILATOR2_MINI",
|
||||
"1000258817": "VEHICLE_WEAPON_BARRAGE_TOP_MINIGUN",
|
||||
"525623141": "VEHICLE_WEAPON_BARRAGE_REAR_MINIGUN",
|
||||
"1338760315": "VEHICLE_WEAPON_CARACARA_MINIGUN",
|
||||
"490982948": "VEHICLE_WEAPON_DEATHBIKE_DUALMINIGUN",
|
||||
"3909880809": "VEHICLE_WEAPON_DEATHBIKE2_MINIGUN_LASER",
|
||||
"2600428406": "VEHICLE_WEAPON_DELUXO_MG",
|
||||
"1416047217": "VEHICLE_WEAPON_DUNE_MINIGUN",
|
||||
"3003147322": "VEHICLE_WEAPON_FLAMETHROWER",
|
||||
"2182329506": "VEHICLE_WEAPON_FLAMETHROWER_SCIFI",
|
||||
"855547631": "VEHICLE_WEAPON_HAVOK_MINIGUN",
|
||||
"2263283790": "VEHICLE_WEAPON_POUNDER2_MINI",
|
||||
"2431961420": "VEHICLE_WEAPON_SEASPARROW2_MINIGUN",
|
||||
"2667462330": "VEHICLE_WEAPON_SPEEDO4_TURRET_MINI",
|
||||
"3670375085": "VEHICLE_WEAPON_TAMPA_FIXEDMINIGUN",
|
||||
"1744687076": "VEHICLE_WEAPON_TAMPA_DUALMINIGUN",
|
||||
"1697521053": "VEHICLE_WEAPON_THRUSTER_MG",
|
||||
"376489128": "VEHICLE_WEAPON_TULA_MINIGUN",
|
||||
"4109257098": "VEHICLE_WEAPON_RCTANK_FLAME",
|
||||
"3959029566": "VEHICLE_WEAPON_CANNON_BLAZER",
|
||||
"1119849093": "WEAPON_MINIGUN",
|
||||
"4256881901": "WEAPON_DIGISCANNER",
|
||||
"1186503822": "VEHICLE_WEAPON_PLAYER_BUZZARD",
|
||||
"3056410471": "WEAPON_RAYMINIGUN",
|
||||
"729375873": "VEHICLE_WEAPON_TURRET_LIMO",
|
||||
"2756787765": "VEHICLE_WEAPON_TURRET_VALKYRIE",
|
||||
"50118905": "VEHICLE_WEAPON_RUINER_BULLET"
|
||||
},
|
||||
"Lazer": {
|
||||
"955522731": "VEHICLE_WEAPON_STRIKEFORCE_CANNON",
|
||||
"539292904": "WEAPON_EXPLOSION",
|
||||
"1638077257": "VEHICLE_WEAPON_PLAYER_SAVAGE"
|
||||
},
|
||||
"Others": {
|
||||
"3441901897": "WEAPON_BATTLEAXE",
|
||||
"4192643659": "WEAPON_BOTTLE",
|
||||
"2460120199": "WEAPON_DAGGER",
|
||||
"2343591895": "WEAPON_FLASHLIGHT",
|
||||
"3794977420": "WEAPON_GARBAGEBAG",
|
||||
"3494679629": "WEAPON_HANDCUFFS",
|
||||
"4191993645": "WEAPON_HATCHET",
|
||||
"3638508604": "WEAPON_KNUCKLE",
|
||||
"3713923289": "WEAPON_MACHETE",
|
||||
"2484171525": "WEAPON_POOLCUE",
|
||||
"2725352035": "WEAPON_UNARMED",
|
||||
"4194021054": "WEAPON_ANIMAL",
|
||||
"148160082": "WEAPON_COUGAR",
|
||||
"2578778090": "WEAPON_KNIFE",
|
||||
"1737195953": "WEAPON_NIGHTSTICK",
|
||||
"1317494643": "WEAPON_HAMMER",
|
||||
"2508868239": "WEAPON_BAT",
|
||||
"1141786504": "WEAPON_GOLFCLUB",
|
||||
"2227010557": "WEAPON_CROWBAR",
|
||||
"101631238": "WEAPON_FIREEXTINGUISHER",
|
||||
"883325847": "WEAPON_PETROLCAN",
|
||||
"2294779575": "WEAPON_BRIEFCASE",
|
||||
"28811031": "WEAPON_BRIEFCASE_02",
|
||||
"3450622333": "VEHICLE_WEAPON_SEARCHLIGHT",
|
||||
"3530961278": "VEHICLE_WEAPON_RADAR",
|
||||
"1223143800": "WEAPON_BARBED_WIRE",
|
||||
"4284007675": "WEAPON_DROWNING",
|
||||
"1936677264": "WEAPON_DROWNING_IN_VEHICLE",
|
||||
"2339582971": "WEAPON_BLEEDING",
|
||||
"2461879995": "WEAPON_ELECTRIC_FENCE",
|
||||
"3452007600": "WEAPON_FALL",
|
||||
"910830060": "WEAPON_EXHAUSTION",
|
||||
"3425972830": "WEAPON_HIT_BY_WATER_CANNON",
|
||||
"133987706": "WEAPON_RAMMED_BY_CAR",
|
||||
"2741846334": "WEAPON_RUN_OVER_BY_CAR",
|
||||
"341774354": "WEAPON_HELI_CRASH",
|
||||
"2971687502": "VEHICLE_WEAPON_ROTORS",
|
||||
"3750660587": "WEAPON_FIRE",
|
||||
"3854032506": "WEAPON_ANIMAL_RETRIEVER",
|
||||
"3146768957": "WEAPON_SMALL_DOG",
|
||||
"743550225": "WEAPON_TIGER_SHARK",
|
||||
"3030980043": "WEAPON_HAMMERHEAD_SHARK",
|
||||
"4198358245": "WEAPON_KILLER_WHALE",
|
||||
"861723357": "WEAPON_BOAR",
|
||||
"1205296881": "WEAPON_PIG",
|
||||
"1161062353": "WEAPON_COYOTE",
|
||||
"4106648222": "WEAPON_DEER",
|
||||
"955837630": "WEAPON_HEN",
|
||||
"2793925639": "WEAPON_RABBIT",
|
||||
"3799318422": "WEAPON_CAT",
|
||||
"94548753": "WEAPON_COW",
|
||||
"940833800": "WEAPON_STONE_HATCHET",
|
||||
"3756226112": "WEAPON_SWITCHBLADE",
|
||||
"419712736": "WEAPON_WRENCH",
|
||||
"406929569": "WEAPON_FERTILIZERCAN",
|
||||
"3126027122": "WEAPON_HAZARDCAN"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
public enum WeaponHash : uint
|
||||
{
|
||||
Advancedrifle = 0xAF113F99,
|
||||
AirDefenceGun = 0x2C082D7D,
|
||||
AirstrikeRocket = 0x13579279,
|
||||
Animal = 0xF9FBAEBE,
|
||||
AnimalRetriever = 0xE5B7DE7A,
|
||||
Appistol = 0x22D8FE39,
|
||||
ArenaHomingMissile = 0x648A81D0,
|
||||
ArenaMachineGun = 0x34FDFF66,
|
||||
Assaultrifle = 0xBFEFFF6D,
|
||||
AssaultrifleMk2 = 0x394F415C,
|
||||
Assaultshotgun = 0xE284C527,
|
||||
Assaultsmg = 0xEFE7E2DF,
|
||||
Autoshotgun = 0x12E82D3D,
|
||||
Ball = 0x23C9F95C,
|
||||
BarbedWire = 0x48E7B178,
|
||||
Bat = 0x958A4A8F,
|
||||
Battleaxe = 0xCD274149,
|
||||
BirdCrap = 0x6D5E2801,
|
||||
Bleeding = 0x8B7333FB,
|
||||
Boar = 0x335CDADD,
|
||||
Bottle = 0xF9E6AA4B,
|
||||
Briefcase = 0x88C78EB7,
|
||||
Briefcase02 = 0x1B79F17,
|
||||
Bullpuprifle = 0x7F229F94,
|
||||
BullpuprifleMk2 = 0x84D6FAFD,
|
||||
Bullpupshotgun = 0x9D61E50F,
|
||||
Bzgas = 0xA0973D5E,
|
||||
Carbinerifle = 0x83BF0278,
|
||||
CarbinerifleMk2 = 0xFAD1F1C9,
|
||||
Cat = 0xE274FF96,
|
||||
Ceramicpistol = 0x2B5EF5EC,
|
||||
Combatmg = 0x7FD62962,
|
||||
CombatmgMk2 = 0xDBBD7280,
|
||||
Combatpdw = 0xA3D4D34,
|
||||
Combatpistol = 0x5EF9FEC4,
|
||||
Combatshotgun = 0x5A96BA4,
|
||||
Compactlauncher = 0x781FE4A,
|
||||
Compactrifle = 0x624FE830,
|
||||
Cougar = 0x8D4BE52,
|
||||
Cow = 0x5A2B311,
|
||||
Coyote = 0x453467D1,
|
||||
Crowbar = 0x84BD7BFD,
|
||||
Dagger = 0x92A27487,
|
||||
Dbshotgun = 0xEF951FBB,
|
||||
Deer = 0xF4C67A9E,
|
||||
Digiscanner = 0xFDBADCED,
|
||||
Doubleaction = 0x97EA20B8,
|
||||
Drowning = 0xFF58C4FB,
|
||||
DrowningInVehicle = 0x736F5990,
|
||||
ElectricFence = 0x92BD4EBB,
|
||||
Emplauncher = 0xDB26713A,
|
||||
Exhaustion = 0x364A29EC,
|
||||
Explosion = 0x2024F4E8,
|
||||
Fall = 0xCDC174B0,
|
||||
Fertilizercan = 0x184140A1,
|
||||
Fire = 0xDF8E89EB,
|
||||
Fireextinguisher = 0x60EC506,
|
||||
Firework = 0x7F7497E5,
|
||||
Flare = 0x497FACC3,
|
||||
Flaregun = 0x47757124,
|
||||
Flashlight = 0x8BB05FD7,
|
||||
Gadgetpistol = 0x57A4368C,
|
||||
Garbagebag = 0xE232C28C,
|
||||
Golfclub = 0x440E4788,
|
||||
Grenade = 0x93E220BD,
|
||||
Grenadelauncher = 0xA284510B,
|
||||
GrenadelauncherSmoke = 0x4DD2DC56,
|
||||
Gusenberg = 0x61012683,
|
||||
Hammer = 0x4E875F73,
|
||||
HammerheadShark = 0xB4A915CB,
|
||||
Handcuffs = 0xD04C944D,
|
||||
Hatchet = 0xF9DCBF2D,
|
||||
Hazardcan = 0xBA536372,
|
||||
Heavypistol = 0xD205520E,
|
||||
Heavyrifle = 0xC78D71B4,
|
||||
Heavyshotgun = 0x3AABBBAA,
|
||||
Heavysniper = 0xC472FE2,
|
||||
HeavysniperMk2 = 0xA914799,
|
||||
HeliCrash = 0x145F1012,
|
||||
Hen = 0x38F8ECBE,
|
||||
HitByWaterCannon = 0xCC34325E,
|
||||
Hominglauncher = 0x63AB0442,
|
||||
KillerWhale = 0xFA3DDCE5,
|
||||
Knife = 0x99B507EA,
|
||||
Knuckle = 0xD8DF3C3C,
|
||||
Machete = 0xDD5DF8D9,
|
||||
Machinepistol = 0xDB1AA450,
|
||||
Marksmanpistol = 0xDC4DB296,
|
||||
Marksmanrifle = 0xC734385A,
|
||||
MarksmanrifleMk2 = 0x6A6C02E0,
|
||||
Mg = 0x9D07F764,
|
||||
Microsmg = 0x13532244,
|
||||
Militaryrifle = 0x9D1F17E6,
|
||||
Minigun = 0x42BF8A85,
|
||||
Minismg = 0xBD248B55,
|
||||
Molotov = 0x24B17070,
|
||||
Musket = 0xA89CB99E,
|
||||
Navyrevolver = 0x917F6C8C,
|
||||
Nightstick = 0x678B81B1,
|
||||
PassengerRocket = 0x166218FF,
|
||||
Petrolcan = 0x34A67B97,
|
||||
Pig = 0x47D75EF1,
|
||||
Pipebomb = 0xBA45E8B8,
|
||||
Pistol = 0x1B06D571,
|
||||
Pistol50 = 0x99AEEB3B,
|
||||
PistolMk2 = 0xBFE256D4,
|
||||
Poolcue = 0x94117305,
|
||||
Proxmine = 0xAB564B93,
|
||||
Pumpshotgun = 0x1D073A89,
|
||||
PumpshotgunMk2 = 0x555AF99A,
|
||||
Rabbit = 0xA687EC07,
|
||||
Railgun = 0x6D544C99,
|
||||
RammedByCar = 0x7FC7D7A,
|
||||
Raycarbine = 0x476BF155,
|
||||
Rayminigun = 0xB62D1F67,
|
||||
Raypistol = 0xAF3696A1,
|
||||
Remotesniper = 0x33058E22,
|
||||
Revolver = 0xC1B3C3D1,
|
||||
RevolverMk2 = 0xCB96392F,
|
||||
Rpg = 0xB1CA77B1,
|
||||
RunOverByCar = 0xA36D413E,
|
||||
Sawnoffshotgun = 0x7846A318,
|
||||
SmallDog = 0xBB8FE23D,
|
||||
Smg = 0x2BE6766B,
|
||||
SmgMk2 = 0x78A97CD0,
|
||||
Smokegrenade = 0xFDBC8A50,
|
||||
Sniperrifle = 0x5FC3C11,
|
||||
Snowball = 0x787F0BB,
|
||||
Snspistol = 0xBFD21232,
|
||||
SnspistolMk2 = 0x88374054,
|
||||
Specialcarbine = 0xC0A3098D,
|
||||
SpecialcarbineMk2 = 0x969C3D67,
|
||||
Stickybomb = 0x2C3731D9,
|
||||
Stinger = 0x687652CE,
|
||||
StoneHatchet = 0x3813FC08,
|
||||
Stungun = 0x3656C8C1,
|
||||
StungunMp = 0x45CD9CF3,
|
||||
Switchblade = 0xDFE37640,
|
||||
TigerShark = 0x2C51AD11,
|
||||
Tranquilizer = 0x32A888BD,
|
||||
Unarmed = 0xA2719263,
|
||||
VehicleRocket = 0xBEFDC581,
|
||||
Vintagepistol = 0x83839C4,
|
||||
Wrench = 0x19044EE0,
|
||||
}
|
||||
|
||||
public enum VehicleWeaponHash : uint
|
||||
{
|
||||
Invalid = 0xFFFFFFFF,
|
||||
AkulaBarrage = 0x880D14F2,
|
||||
AkulaMinigun = 0x19B95679,
|
||||
AkulaMissile = 0x7CBE304C,
|
||||
AkulaTurretDual = 0x1C6D0842,
|
||||
AkulaTurretSingle = 0xB5B3B9AD,
|
||||
Annihilator2Barrage = 0x35D8CC90,
|
||||
Annihilator2Mini = 0xCAF6CD41,
|
||||
Annihilator2Missile = 0xC76BC6B7,
|
||||
ApcCannon = 0x138F71D8,
|
||||
ApcMg = 0xB56E4E4,
|
||||
ApcMissile = 0x44A56189,
|
||||
ArdentMg = 0xC44E4341,
|
||||
AvengerCannon = 0x9867203B,
|
||||
BarrageRearGl = 0xA44C228D,
|
||||
BarrageRearMg = 0x47894765,
|
||||
BarrageRearMinigun = 0x1F545F65,
|
||||
BarrageTopMg = 0xF7498994,
|
||||
BarrageTopMinigun = 0x3B9EBD01,
|
||||
Bomb = 0x9AF0B90C,
|
||||
BombCluster = 0xD28BCA3,
|
||||
BombGas = 0x5540A91E,
|
||||
BombIncendiary = 0x6AF7A717,
|
||||
BombStandardWide = 0x6EA548D0,
|
||||
BombushkaCannon = 0xD8443A59,
|
||||
BombushkaDualmg = 0x2C2B2D58,
|
||||
BombWater = 0xF444C4C8,
|
||||
Bruiser250calLaser = 0x3D6A0196,
|
||||
Bruiser50cal = 0xD73DC601,
|
||||
Brutus250calLaser = 0x68C7A4C3,
|
||||
Brutus50cal = 0xEB5E5C0A,
|
||||
CannonBlazer = 0xEBF9FF3E,
|
||||
CaracaraMg = 0x6C516BA8,
|
||||
CaracaraMinigun = 0x4FCBDC7B,
|
||||
ChernoMissile = 0xA247D03E,
|
||||
CometMg = 0xEAA835F3,
|
||||
Deathbike2MinigunLaser = 0xE90C0BE9,
|
||||
DeathbikeDualminigun = 0x1D43CE24,
|
||||
DeluxoMg = 0x9AFF6376,
|
||||
DeluxoMissile = 0xB4F96934,
|
||||
DogfighterMg = 0x5F1834E2,
|
||||
DogfighterMissile = 0xCA46F87D,
|
||||
Dominator450cal = 0xF80C9B0F,
|
||||
Dominator550calLaser = 0xB4246A5F,
|
||||
DuneGrenadelauncher = 0xA0FC710D,
|
||||
DuneMg = 0xD11507CF,
|
||||
DuneMinigun = 0x54672A71,
|
||||
EnemyLaser = 0x5D6660AB,
|
||||
Flamethrower = 0xB300643A,
|
||||
FlamethrowerScifi = 0x8213B4A2,
|
||||
Granger2Mg = 0xEAE2E19A,
|
||||
HackerMissile = 0x766FF7B1,
|
||||
HackerMissileHoming = 0x77EACF96,
|
||||
HalftrackDualmg = 0x4F6384FB,
|
||||
HalftrackQuadmg = 0x491B2E74,
|
||||
HavokMinigun = 0x32FE9EEF,
|
||||
HunterBarrage = 0x2ED14835,
|
||||
HunterCannon = 0x2A00AB1A,
|
||||
HunterMg = 0x42BA80A7,
|
||||
HunterMissile = 0x924A5F5,
|
||||
Impaler250cal = 0x5F565C09,
|
||||
Impaler350calLaser = 0x8CBDFC88,
|
||||
Imperator250calLaser = 0x7817C526,
|
||||
Imperator50cal = 0xB662C67B,
|
||||
InsurgentMinigun = 0xAA886DF8,
|
||||
Issi450cal = 0x7648E34D,
|
||||
Issi550calLaser = 0x767F6925,
|
||||
Jb700Mg = 0x373AD53C,
|
||||
KhanjaliCannon = 0x1E3ACFA0,
|
||||
KhanjaliCannonHeavy = 0x838B716D,
|
||||
KhanjaliGl = 0x178605E2,
|
||||
KhanjaliMg = 0x2A6F8E1D,
|
||||
KosatkaTorpedo = 0x62E2140E,
|
||||
MenacerMg = 0xDFCAF8A4,
|
||||
MicrolightMg = 0xC4E0216C,
|
||||
Mine = 0x59EAE9A4,
|
||||
MineEmp = 0x69E10D60,
|
||||
MineEmpRc = 0x5454B4C6,
|
||||
MineKinetic = 0x3C09584E,
|
||||
MineKineticRc = 0x252AF560,
|
||||
MineSlick = 0x56FACAC7,
|
||||
MineSlickRc = 0x84E87B17,
|
||||
MineSpike = 0xD96DA06C,
|
||||
MineSpikeRc = 0x7C2AFE51,
|
||||
MineTar = 0xF4418BA0,
|
||||
MineTarRc = 0x7D3474D6,
|
||||
MobileopsCannon = 0xE53E69A4,
|
||||
MogulDualnose = 0xE5F3AE2F,
|
||||
MogulDualturret = 0xBA277C01,
|
||||
MogulNose = 0xF6189F4A,
|
||||
MogulTurret = 0xE2FD135E,
|
||||
Monster3Glkin = 0xE5AE53DD,
|
||||
MortarExplosive = 0xA1A8CCD2,
|
||||
MortarKinetic = 0x632A22FD,
|
||||
Mule4Mg = 0x84558727,
|
||||
Mule4Missile = 0x4772F84B,
|
||||
Mule4TurretGl = 0xDD124A65,
|
||||
NightsharkMg = 0xA61AC574,
|
||||
NoseTurretValkyrie = 0x4170E491,
|
||||
Oppressor2Cannon = 0xD64D3469,
|
||||
Oppressor2Mg = 0xE2451DD6,
|
||||
Oppressor2Missile = 0x753A78F1,
|
||||
OppressorMg = 0xD9322EDD,
|
||||
OppressorMissile = 0x8BB7C63E,
|
||||
Paragon2Mg = 0x2CAC4286,
|
||||
PatrolboatDualmg = 0x4C2FB4E9,
|
||||
PlaneRocket = 0xCF0896E0,
|
||||
PlayerBullet = 0x4B139B2D,
|
||||
PlayerBuzzard = 0x46B89C8E,
|
||||
PlayerHunter = 0x9F1A91DE,
|
||||
PlayerLaser = 0xEFFD014B,
|
||||
PlayerLazer = 0xE2822A29,
|
||||
PlayerSavage = 0x61A31349,
|
||||
Pounder2Barrage = 0x926B8CE4,
|
||||
Pounder2Gl = 0x9318FF16,
|
||||
Pounder2Mini = 0x86E6F84E,
|
||||
Pounder2Missile = 0x9A8EA9A,
|
||||
Radar = 0xD276317E,
|
||||
RctankFlame = 0xF4EE498A,
|
||||
RctankGun = 0x52FCA619,
|
||||
RctankLazer = 0x57F22C50,
|
||||
RctankRocket = 0x76F744CB,
|
||||
RevolterMg = 0xBD5E626A,
|
||||
RogueCannon = 0xE72ABBC2,
|
||||
RogueMg = 0x97273CD,
|
||||
RogueMissile = 0x6C88E47D,
|
||||
Rotors = 0xB1205A4E,
|
||||
RuinerBullet = 0x2FCC0F9,
|
||||
RuinerRocket = 0x50DC6AB,
|
||||
SavestraMg = 0xEB41E84E,
|
||||
Scarab250calLaser = 0xE22DEDCC,
|
||||
Scarab50cal = 0x217FEF28,
|
||||
ScramjetMg = 0xDCE6112,
|
||||
ScramjetMissile = 0xBCE908DB,
|
||||
SeabreezeMg = 0x51B8D4E8,
|
||||
Searchlight = 0xCDAC517D,
|
||||
Seasparrow2Minigun = 0x90F4C94C,
|
||||
Slamvan450cal = 0x3AAB6E6B,
|
||||
Slamvan550calLaser = 0x519543AE,
|
||||
SpaceRocket = 0xF8A3939F,
|
||||
Speedo4Mg = 0xC7FCF93C,
|
||||
Speedo4TurretMg = 0xD6561141,
|
||||
Speedo4TurretMini = 0x9EFE3EBA,
|
||||
StrikeforceBarrage = 0x39BC6683,
|
||||
StrikeforceCannon = 0x38F41EAB,
|
||||
StrikeforceMissile = 0x1EF01D8A,
|
||||
SubcarMg = 0x461DDDB0,
|
||||
SubcarMissile = 0xD4897C0E,
|
||||
SubcarTorpedo = 0xE783C3BA,
|
||||
SubMissileHoming = 0xAAE74AC1,
|
||||
TampaDualminigun = 0x67FDCFE4,
|
||||
TampaFixedminigun = 0xDAC57AAD,
|
||||
TampaMissile = 0x9E5840A2,
|
||||
TampaMortar = 0x3C83C410,
|
||||
Tank = 0x73F7C04B,
|
||||
TechnicalMinigun = 0xDB894608,
|
||||
ThrusterMg = 0x652E1D9D,
|
||||
ThrusterMissile = 0x4635DD15,
|
||||
TrailerDualaa = 0x808C4D4C,
|
||||
TrailerMissile = 0x145599F7,
|
||||
TrailerQuadmg = 0x4711B02C,
|
||||
TulaDualmg = 0xB0D15C0B,
|
||||
TulaMg = 0x488BD081,
|
||||
TulaMinigun = 0x1670C4A8,
|
||||
TulaNosemg = 0x419D8E15,
|
||||
TurretBoxville = 0xB54F4918,
|
||||
TurretDinghy550cal = 0xB3B155FD,
|
||||
TurretInsurgent = 0x44DB5498,
|
||||
TurretLimo = 0x2B796481,
|
||||
TurretPatrolboat50cal = 0xDF4EA041,
|
||||
TurretTechnical = 0x7FD2EA0B,
|
||||
TurretValkyrie = 0xA4513E35,
|
||||
VigilanteMg = 0xF4077EE7,
|
||||
VigilanteMissile = 0x504DA665,
|
||||
ViserisMg = 0x87A02E06,
|
||||
VolatolDualmg = 0x4497AC40,
|
||||
Zr380250calLaser = 0x220093BC,
|
||||
Zr38050cal = 0x6AB93C82,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
<Application x:Class="RageCoop.Client.Installer.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace RageCoop.Client.Installer
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows;
|
||||
|
||||
[assembly: ThemeInfo(
|
||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||
//(used if a resource is not found in the page,
|
||||
// or application resource dictionaries)
|
||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||
//(used if a resource is not found in the page,
|
||||
// app, or any theme specific resource dictionaries)
|
||||
)]
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -0,0 +1,15 @@
|
||||
<Window x:Class="RageCoop.Client.Installer.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="376" Width="617" Background="#FFBABABA">
|
||||
<Grid>
|
||||
<Grid.Background>
|
||||
<ImageBrush ImageSource="/bg.png" Opacity="0.2" />
|
||||
</Grid.Background>
|
||||
<Label x:Name="Status" FontSize="20" Foreground="#FF232323" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,200 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using RageCoop.Core;
|
||||
using static RageCoop.Client.Shared;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||
|
||||
namespace RageCoop.Client.Installer
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Choose();
|
||||
}
|
||||
|
||||
private void Choose()
|
||||
{
|
||||
var od = new OpenFileDialog
|
||||
{
|
||||
Filter = "GTA 5 executable |GTA5.exe;PlayGTAV.exe",
|
||||
Title = "Select you GTAV executable"
|
||||
};
|
||||
if (od.ShowDialog() ?? !true)
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Install(Directory.GetParent(od.FileName).FullName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Installation failed: " + ex);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
});
|
||||
else
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private void Install(string root)
|
||||
{
|
||||
UpdateStatus("Checking requirements");
|
||||
var shvPath = Path.Combine(root, "ScriptHookV.dll");
|
||||
var shvdnPath = Path.Combine(root, "ScriptHookVDotNetCore.dll");
|
||||
var scriptsPath = Path.Combine(root, "Scripts");
|
||||
var installPath = Path.Combine(root, "CoreScripts");
|
||||
var legacyPath = Path.Combine(scriptsPath, "RageCoop");
|
||||
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName.StartsWith(installPath))
|
||||
throw new InvalidOperationException(
|
||||
"The installer is not meant to be run in the game folder, please extract the zip to somewhere else and run again.");
|
||||
if (!File.Exists(shvPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookV first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(installPath);
|
||||
|
||||
if (!File.Exists(shvdnPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookVDotNetCore first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
var shvdnVer = GetVer(shvdnPath);
|
||||
if (shvdnVer < new Version(1, 2, 1))
|
||||
{
|
||||
MessageBox.Show("Please update ScriptHookVDotNetCore to latest version!" +
|
||||
$"\nCurrent version is {shvdnVer}, 1.2.1 or higher is required");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
|
||||
UpdateStatus("Removing old versions");
|
||||
|
||||
if (Directory.Exists(scriptsPath))
|
||||
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
|
||||
File.Delete(f);
|
||||
|
||||
// <= 1.5 installation check
|
||||
if (Directory.Exists(legacyPath)) Directory.Delete(legacyPath, true);
|
||||
|
||||
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories)) File.Delete(f);
|
||||
|
||||
if (File.Exists("Scripts/RageCoop.Core.dll") && File.Exists("Scripts/RageCoop.Client.dll"))
|
||||
{
|
||||
UpdateStatus("Installing...");
|
||||
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()),
|
||||
new DirectoryInfo(installPath));
|
||||
Finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception(
|
||||
"Required files are missing, please re-download the installer from official website");
|
||||
}
|
||||
|
||||
void Finish()
|
||||
{
|
||||
checkKeys:
|
||||
UpdateStatus("Checking conflicts");
|
||||
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
|
||||
var settingsPath = Path.Combine(installPath, "Data", "Setting.json");
|
||||
ClientSettings settings = null;
|
||||
try
|
||||
{
|
||||
settings = Util.ReadSettings(settingsPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
settings = new();
|
||||
}
|
||||
|
||||
if (File.Exists(menyooConfig))
|
||||
{
|
||||
var lines = File.ReadAllLines(menyooConfig)
|
||||
.Where(x => !x.StartsWith(";") && x.EndsWith(" = " + (int)settings.MenuKey));
|
||||
if (lines.Any())
|
||||
if (MessageBox.Show("Following menyoo config value will conflict with RAGECOOP menu key\n" +
|
||||
string.Join("\n", lines)
|
||||
+ "\nDo you wish to change the Menu Key?", "Warning!",
|
||||
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
var ae = new AutoResetEvent(false);
|
||||
UpdateStatus("Press the key you wish to change to");
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
KeyDown += (s, e) =>
|
||||
{
|
||||
settings.MenuKey = (GTA.Keys)KeyInterop.VirtualKeyFromKey(e.Key);
|
||||
ae.Set();
|
||||
}));
|
||||
ae.WaitOne();
|
||||
if (!Util.SaveSettings(settingsPath, settings))
|
||||
{
|
||||
MessageBox.Show("Error occurred when saving settings");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
MessageBox.Show("Menu key changed to " + settings.MenuKey);
|
||||
goto checkKeys;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatus("Checking ZeroTier");
|
||||
try
|
||||
{
|
||||
ZeroTierHelper.Check();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (MessageBox.Show(
|
||||
"You can't join ZeroTier server unless ZeroTier is installed, do you want to download and install it?",
|
||||
"Install ZeroTier", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
|
||||
{
|
||||
var url = "https://download.zerotier.com/dist/ZeroTier%20One.msi";
|
||||
UpdateStatus("Downloading ZeroTier from " + url);
|
||||
try
|
||||
{
|
||||
HttpHelper.DownloadFile(url, "ZeroTier.msi",
|
||||
p => UpdateStatus("Downloading ZeroTier " + p + "%"));
|
||||
UpdateStatus("Installing ZeroTier");
|
||||
Process.Start("ZeroTier.msi").WaitForExit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show("Failed to download ZeroTier, please download it from official website");
|
||||
Process.Start(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatus("Completed!");
|
||||
MessageBox.Show("Installation successful!");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStatus(string status)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() => Status.Content = status));
|
||||
}
|
||||
|
||||
private Version GetVer(string location)
|
||||
{
|
||||
return Version.Parse(FileVersionInfo.GetVersionInfo(location).FileVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<NoAotCompile>true</NoAotCompile>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<AllowedReferenceRelatedFileExtensions>
|
||||
-
|
||||
</AllowedReferenceRelatedFileExtensions>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<OutDir>..\..\bin\Debug\Client</OutDir>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<OutDir>..\..\bin\Release\Client</OutDir>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Scripts\RageCoop.Client.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="bg.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Resource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resource.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Costura.Fody" Version="5.7.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>compile; runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resource.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,72 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<AssemblyName>RageCoop.Client.Installer</AssemblyName>
|
||||
<IntermediateOutputPath>obj\Debug\</IntermediateOutputPath>
|
||||
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
|
||||
<MSBuildProjectExtensionsPath>M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\</MSBuildProjectExtensionsPath>
|
||||
<_TargetAssemblyProjectName>RageCoop.Client.Installer</_TargetAssemblyProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="bg.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RageCoop.Client\RageCoop.Client.csproj" />
|
||||
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Resource.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resource.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resource.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\sharpziplib\1.3.3\lib\net45\ICSharpCode.SharpZipLib.dll" />
|
||||
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\microsoft.extensions.objectpool\6.0.8\lib\net461\Microsoft.Extensions.ObjectPool.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\mscorlib.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationCore.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationFramework.dll" />
|
||||
<ReferencePath Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Client\RageCoop.Client.dll" />
|
||||
<ReferencePath Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Core\RageCoop.Core.dll" />
|
||||
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\system.buffers\4.5.1\ref\net45\System.Buffers.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Core.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Data.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Drawing.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.IO.Compression.FileSystem.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Numerics.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Runtime.Serialization.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Windows.Controls.Ribbon.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Windows.Forms.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xaml.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.Linq.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClient.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClientsideProviders.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationProvider.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationTypes.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\WindowsBase.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\WindowsFormsIntegration.dll" />
|
||||
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\Facades\netstandard.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\Debug\net48\MainWindow.g.cs" />
|
||||
<Compile Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\Debug\net48\App.g.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
|
||||
</Project>
|
||||
Generated
+63
@@ -0,0 +1,63 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace RageCoop.Client.Installer {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resource {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resource() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RageCoop.Client.Installer.Resource", typeof(Resource).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
</root>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace GTA
|
||||
{
|
||||
internal class Console
|
||||
{
|
||||
private static readonly SHVDN.Console console = AppDomain.CurrentDomain.GetData("Console") as SHVDN.Console;
|
||||
|
||||
public static void Warning(object format, params object[] objects)
|
||||
{
|
||||
console.PrintInfo("[~o~WARNING~w~] ", format.ToString(), objects);
|
||||
}
|
||||
|
||||
public static void Error(object format, params object[] objects)
|
||||
{
|
||||
console.PrintError("[~r~ERROR~w~] ", format.ToString(), objects);
|
||||
}
|
||||
|
||||
public static void Info(object format, params object[] objects)
|
||||
{
|
||||
console.PrintWarning("[~b~INFO~w~] ", format.ToString(), objects);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using GTA.UI;
|
||||
using SHVDN;
|
||||
using Console = GTA.Console;
|
||||
|
||||
namespace RageCoop.Client.Loader
|
||||
{
|
||||
public class LoaderContext : MarshalByRefObject, IDisposable
|
||||
{
|
||||
#region PRIMARY-LOADING-LOGIC
|
||||
|
||||
public static ConcurrentDictionary<string, LoaderContext> LoadedDomains =>
|
||||
new ConcurrentDictionary<string, LoaderContext>(_loadedDomains);
|
||||
|
||||
private static readonly ConcurrentDictionary<string, LoaderContext> _loadedDomains =
|
||||
new ConcurrentDictionary<string, LoaderContext>();
|
||||
|
||||
public bool UnloadRequested;
|
||||
public string BaseDirectory => AppDomain.CurrentDomain.BaseDirectory;
|
||||
private ScriptDomain CurrentDomain => ScriptDomain.CurrentDomain;
|
||||
|
||||
public static void CheckForUnloadRequest()
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
foreach (var p in _loadedDomains.Values)
|
||||
if (p.UnloadRequested)
|
||||
Unload(p);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsLoaded(string dir)
|
||||
{
|
||||
return _loadedDomains.ContainsKey(Path.GetFullPath(dir).ToLower());
|
||||
}
|
||||
|
||||
public static LoaderContext Load(string dir)
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
dir = Path.GetFullPath(dir).ToLower();
|
||||
if (IsLoaded(dir)) throw new Exception("Already loaded");
|
||||
ScriptDomain newDomain = null;
|
||||
try
|
||||
{
|
||||
dir = Path.GetFullPath(dir).ToLower();
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
// Delete API assemblies
|
||||
Directory.GetFiles(dir, "ScriptHookVDotNet*", SearchOption.AllDirectories).ToList()
|
||||
.ForEach(x => File.Delete(x));
|
||||
|
||||
|
||||
newDomain = ScriptDomain.Load(
|
||||
Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName, dir);
|
||||
|
||||
newDomain.AppDomain.SetData("Primary", ScriptDomain.CurrentDomain);
|
||||
|
||||
newDomain.AppDomain.SetData("Console",
|
||||
ScriptDomain.CurrentDomain.AppDomain.GetData("Console"));
|
||||
|
||||
// Copy to target domain base directory
|
||||
// Delete loader assembly
|
||||
var loaderPath = Path.Combine(dir, Path.GetFileName(typeof(LoaderContext).Assembly.Location));
|
||||
if (File.Exists(loaderPath)) File.Delete(loaderPath);
|
||||
|
||||
var context = (LoaderContext)newDomain.AppDomain.CreateInstanceFromAndUnwrap(
|
||||
typeof(LoaderContext).Assembly.Location,
|
||||
typeof(LoaderContext).FullName, false,
|
||||
BindingFlags.Instance | BindingFlags.NonPublic,
|
||||
null,
|
||||
new object[] { }
|
||||
, null, null);
|
||||
|
||||
newDomain.AppDomain.SetData("RageCoop.Client.LoaderContext", context);
|
||||
newDomain.Start();
|
||||
|
||||
_loadedDomains.TryAdd(dir, context);
|
||||
return _loadedDomains[dir];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Notification.Show(ex.ToString());
|
||||
Console.Error(ex);
|
||||
if (newDomain != null) ScriptDomain.Unload(newDomain);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Assembly ResolveLoader(object sender, ResolveEventArgs args)
|
||||
{
|
||||
Log.Message(Log.Level.Debug, "resolving assembly " + args.Name);
|
||||
if (args.Name == typeof(LoaderContext).Assembly.GetName().Name) return typeof(LoaderContext).Assembly;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Unload(LoaderContext domain)
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
var name = domain.CurrentDomain.Name;
|
||||
Console.Info("Unloading domain: " + name);
|
||||
if (!_loadedDomains.TryRemove(domain.BaseDirectory.ToLower(), out _))
|
||||
throw new Exception("Failed to remove domain from list");
|
||||
domain.Dispose();
|
||||
ScriptDomain.Unload(domain.CurrentDomain);
|
||||
Console.Info("Unloaded domain: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unload(string dir)
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
Unload(_loadedDomains[Path.GetFullPath(dir).ToLower()]);
|
||||
}
|
||||
}
|
||||
|
||||
public static void TickAll()
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
foreach (var c in _loadedDomains.Values) c.DoTick();
|
||||
}
|
||||
}
|
||||
|
||||
public static void KeyEventAll(Keys keys, bool status)
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
foreach (var c in _loadedDomains.Values) c.DoKeyEvent(keys, status);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnloadAll()
|
||||
{
|
||||
lock (_loadedDomains)
|
||||
{
|
||||
foreach (var d in _loadedDomains.Values.ToArray()) Unload(d);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LOAD-CONTEXT
|
||||
|
||||
private readonly Action _domainDoTick;
|
||||
private readonly Action<Keys, bool> _domainDoKeyEvent;
|
||||
|
||||
private LoaderContext()
|
||||
{
|
||||
AppDomain.CurrentDomain.DomainUnload += (s, e) => Dispose();
|
||||
|
||||
var tickMethod = typeof(ScriptDomain).GetMethod("DoTick", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var doKeyEventMethod =
|
||||
typeof(ScriptDomain).GetMethod("DoKeyEvent", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
// Create delegates to avoid using reflection to call method each time, which is slow
|
||||
_domainDoTick = (Action)Delegate.CreateDelegate(typeof(Action), CurrentDomain, tickMethod);
|
||||
_domainDoKeyEvent =
|
||||
(Action<Keys, bool>)Delegate.CreateDelegate(typeof(Action<Keys, bool>), CurrentDomain,
|
||||
doKeyEventMethod);
|
||||
|
||||
Console.Info(
|
||||
$"Loaded domain: {AppDomain.CurrentDomain.FriendlyName}, {AppDomain.CurrentDomain.BaseDirectory}");
|
||||
}
|
||||
|
||||
public static ScriptDomain PrimaryDomain => AppDomain.CurrentDomain.GetData("Primary") as ScriptDomain;
|
||||
|
||||
public static LoaderContext CurrentContext =>
|
||||
(LoaderContext)AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext");
|
||||
|
||||
/// <summary>
|
||||
/// Request the current domain to be unloaded
|
||||
/// </summary>
|
||||
public static void RequestUnload()
|
||||
{
|
||||
if (PrimaryDomain == null)
|
||||
throw new NotSupportedException(
|
||||
"Current domain not loaded by the loader therefore cannot be unloaded automatically");
|
||||
CurrentContext.UnloadRequested = true;
|
||||
}
|
||||
|
||||
public override object InitializeLifetimeService()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void DoTick()
|
||||
{
|
||||
_domainDoTick();
|
||||
}
|
||||
|
||||
public void DoKeyEvent(Keys key, bool status)
|
||||
{
|
||||
_domainDoKeyEvent(key, status);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (PrimaryDomain == null) return;
|
||||
AppDomain.CurrentDomain.SetData("Primary", null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using GTA;
|
||||
using GTA.UI;
|
||||
using SHVDN;
|
||||
using Script = GTA.Script;
|
||||
|
||||
namespace RageCoop.Client.Loader
|
||||
{
|
||||
[ScriptAttributes(Author = "RageCoop", NoScriptThread = true,
|
||||
SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
|
||||
public class Main : Script
|
||||
{
|
||||
private static readonly string GameDir = Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName;
|
||||
private static readonly string ScriptsLocation = Path.Combine(GameDir, "RageCoop", "Scripts");
|
||||
private bool _loaded;
|
||||
|
||||
public Main()
|
||||
{
|
||||
if (LoaderContext.PrimaryDomain != null) return;
|
||||
Tick += OnTick;
|
||||
KeyDown += (s, e) => LoaderContext.KeyEventAll(e.KeyCode, true);
|
||||
KeyUp += (s, e) => LoaderContext.KeyEventAll(e.KeyCode, false);
|
||||
Aborted += (s, e) => LoaderContext.UnloadAll();
|
||||
}
|
||||
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
if (!_loaded)
|
||||
{
|
||||
_loaded = !Game.IsLoading;
|
||||
return;
|
||||
}
|
||||
|
||||
LoaderContext.CheckForUnloadRequest();
|
||||
if (!LoaderContext.IsLoaded(ScriptsLocation))
|
||||
{
|
||||
if (!File.Exists(Path.Combine(ScriptsLocation, "RageCoop.Client.dll")))
|
||||
{
|
||||
Notification.Show("~r~Main assembly is missing, please re-install the client");
|
||||
Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
LoaderContext.Load(ScriptsLocation);
|
||||
}
|
||||
|
||||
LoaderContext.TickAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("RageCoop.Client.Loader")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("RageCoop.Client.Loader")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("fc8cbdbb-6dc3-43af-b34d-092e476410a5")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>RageCoop.Client.Loader</RootNamespace>
|
||||
<AssemblyName>RageCoop.Client.Loader</AssemblyName>
|
||||
<OutPutPath>..\..\bin\$(Configuration)\Client\Loader</OutPutPath>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="ScriptHookVDotNet3, Version=3.5.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>True</SpecificVersion>
|
||||
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
|
||||
</Reference>
|
||||
|
||||
<Reference Include="ScriptHookVDotNet">
|
||||
<HintPath>..\..\libs\ScriptHookVDotNet.asi</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Console.cs" />
|
||||
<Compile Include="LoaderContext.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public static unsafe partial class APIBridge
|
||||
{
|
||||
|
||||
public static class Config
|
||||
{
|
||||
public static System.String Username => GetConfig<System.String>("Username");
|
||||
public static System.Boolean EnableAutoRespawn => GetConfig<System.Boolean>("EnableAutoRespawn");
|
||||
public static GTA.BlipColor BlipColor => GetConfig<GTA.BlipColor>("BlipColor");
|
||||
public static GTA.BlipSprite BlipSprite => GetConfig<GTA.BlipSprite>("BlipSprite");
|
||||
public static System.Single BlipScale => GetConfig<System.Single>("BlipScale");
|
||||
public static System.Boolean ShowPlayerNameTag => GetConfig<System.Boolean>("ShowPlayerNameTag");
|
||||
|
||||
}
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
public static System.Int32 LocalPlayerID => GetProperty<System.Int32>("LocalPlayerID");
|
||||
public static System.Boolean IsOnServer => GetProperty<System.Boolean>("IsOnServer");
|
||||
public static System.Net.IPEndPoint ServerEndPoint => GetProperty<System.Net.IPEndPoint>("ServerEndPoint");
|
||||
public static System.Boolean IsMenuVisible => GetProperty<System.Boolean>("IsMenuVisible");
|
||||
public static System.Boolean IsChatFocused => GetProperty<System.Boolean>("IsChatFocused");
|
||||
public static System.Boolean IsPlayerListVisible => GetProperty<System.Boolean>("IsPlayerListVisible");
|
||||
public static System.Version CurrentVersion => GetProperty<System.Version>("CurrentVersion");
|
||||
public static System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo> Players => GetProperty<System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo>>("Players");
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region FUNCTIONS
|
||||
|
||||
public static void Connect(System.String address) => InvokeCommand("Connect", address);
|
||||
public static void Disconnect() => InvokeCommand("Disconnect");
|
||||
public static System.Collections.Generic.List<RageCoop.Core.ServerInfo> ListServers() => InvokeCommand<System.Collections.Generic.List<RageCoop.Core.ServerInfo>>("ListServers");
|
||||
public static void LocalChatMessage(System.String from, System.String message) => InvokeCommand("LocalChatMessage", from, message);
|
||||
public static void SendChatMessage(System.String message) => InvokeCommand("SendChatMessage", message);
|
||||
public static RageCoop.Client.Scripting.ClientResource GetResource(System.String name) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResource", name);
|
||||
public static RageCoop.Client.Scripting.ClientResource GetResourceFromPath(System.String path) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResourceFromPath", path);
|
||||
public static System.Object GetConfig(System.String name) => InvokeCommand<System.Object>("GetConfig", name);
|
||||
public static void SetConfig(System.String name, System.String jsonVal) => InvokeCommand("SetConfig", name, jsonVal);
|
||||
public static void RegisterCustomEventHandler(RageCoop.Core.Scripting.CustomEventHash hash, RageCoop.Core.Scripting.CustomEventHandler handler) => InvokeCommand("RegisterCustomEventHandler", hash, handler);
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client")] // For debugging
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public static unsafe partial class APIBridge
|
||||
{
|
||||
static readonly ThreadLocal<char[]> _resultBuf = new(() => new char[4096]);
|
||||
static readonly List<CustomEventHandler> _handlers = new();
|
||||
|
||||
static APIBridge()
|
||||
{
|
||||
if (SHVDN.Core.GetPtr == null)
|
||||
throw new InvalidOperationException("Game not running");
|
||||
|
||||
foreach(var fd in typeof(APIBridge).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
{
|
||||
var importAttri = fd.GetCustomAttribute<ApiImportAttribute>();
|
||||
if (importAttri == null)
|
||||
continue;
|
||||
importAttri.EntryPoint ??= fd.Name;
|
||||
var key = $"RageCoop.Client.Scripting.API.{importAttri.EntryPoint}";
|
||||
var fptr = SHVDN.Core.GetPtr(key);
|
||||
if (fptr == default)
|
||||
throw new KeyNotFoundException($"Failed to find function pointer: {key}");
|
||||
|
||||
fd.SetValue(null,fptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy content of string to a sequential block of memory
|
||||
/// </summary>
|
||||
/// <param name="strs"></param>
|
||||
/// <returns>Pointer to the start of the block, can be used as argv</returns>
|
||||
/// <remarks>Call <see cref="Marshal.FreeHGlobal(nint)"/> with the returned pointer when finished using</remarks>
|
||||
internal static char** StringArrayToMemory(string[] strs)
|
||||
{
|
||||
var argc = strs.Length;
|
||||
var cbSize = sizeof(IntPtr) * argc + strs.Sum(s => (s.Length + 1) * sizeof(char));
|
||||
var result = (char**)Marshal.AllocHGlobal(cbSize);
|
||||
var pCurStr = (char*)(result + argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
result[i] = pCurStr;
|
||||
var len = strs[i].Length;
|
||||
var cbStrSize = (len + 1) * sizeof(char); // null terminator
|
||||
fixed (char* pStr = strs[i])
|
||||
{
|
||||
System.Buffer.MemoryCopy(pStr, pCurStr, cbStrSize, cbStrSize);
|
||||
}
|
||||
pCurStr += len + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static void InvokeCommand(string name, params object[] args)
|
||||
=> InvokeCommandAsJson(name, args);
|
||||
internal static T InvokeCommand<T>(string name, params object[] args)
|
||||
=> JsonDeserialize<T>(InvokeCommandAsJson(name, args));
|
||||
|
||||
/// <summary>
|
||||
/// Invoke command and get the return value as json
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns>The json representation of returned object</returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
internal static string InvokeCommandAsJson(string name, params object[] args)
|
||||
{
|
||||
var argc = args.Length;
|
||||
var argv = StringArrayToMemory(args.Select(JsonSerialize).ToArray());
|
||||
try
|
||||
{
|
||||
fixed(char* pName = name)
|
||||
{
|
||||
var resultLen = InvokeCommandAsJsonUnsafe(pName, argc, argv);
|
||||
if (resultLen == 0)
|
||||
throw new Exception(GetLastResult());
|
||||
return GetLastResult();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal((IntPtr)argv);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLastResult()
|
||||
{
|
||||
var countCharsRequired = GetLastResultLenInChars() + 1;
|
||||
if (countCharsRequired > _resultBuf.Value.Length)
|
||||
{
|
||||
_resultBuf.Value = new char[countCharsRequired];
|
||||
}
|
||||
var cbBufSize = _resultBuf.Value.Length * sizeof(char);
|
||||
fixed (char* pBuf = _resultBuf.Value)
|
||||
{
|
||||
if (GetLastResultUnsafe(pBuf, cbBufSize) > 0)
|
||||
{
|
||||
return new string(pBuf);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SendCustomEvent(CustomEventHash hash, params object[] args)
|
||||
=> SendCustomEvent(CustomEventFlags.None, hash, args);
|
||||
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash hash, params object[] args)
|
||||
{
|
||||
var writer = GetWriter();
|
||||
CustomEvents.WriteObjects(writer, args);
|
||||
SendCustomEventUnsafe(flags, hash, writer.Address, writer.Position);
|
||||
}
|
||||
|
||||
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
|
||||
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
|
||||
|
||||
internal static string GetPropertyAsJson(string name) => InvokeCommandAsJson("GetProperty", name);
|
||||
internal static string GetConfigAsJson(string name) => InvokeCommandAsJson("GetConfig", name);
|
||||
|
||||
internal static T GetProperty<T>(string name) => JsonDeserialize<T>(GetPropertyAsJson(name));
|
||||
internal static T GetConfig<T>(string name) => JsonDeserialize<T>(GetConfigAsJson(name));
|
||||
|
||||
internal static void SetProperty(string name, object val) => InvokeCommand("SetProperty", name, val);
|
||||
internal static void SetConfig(string name, object val) => InvokeCommand("SetConfig", name, val);
|
||||
|
||||
|
||||
[ApiImport]
|
||||
public static delegate* unmanaged<char*, CustomEventHash> GetEventHash;
|
||||
|
||||
[ApiImport]
|
||||
private static delegate* unmanaged<char*,void> SetLastResult;
|
||||
|
||||
[ApiImport(EntryPoint = "GetLastResult")]
|
||||
private static delegate* unmanaged<char*, int, int> GetLastResultUnsafe;
|
||||
|
||||
[ApiImport(EntryPoint = "InvokeCommand")]
|
||||
private static delegate* unmanaged<char*, int, char**, int> InvokeCommandAsJsonUnsafe;
|
||||
|
||||
[ApiImport(EntryPoint = "SendCustomEvent")]
|
||||
private static delegate* unmanaged<CustomEventFlags, int, byte*, int, void> SendCustomEventUnsafe;
|
||||
|
||||
[ApiImport]
|
||||
private static delegate* unmanaged<int> GetLastResultLenInChars;
|
||||
|
||||
[ApiImport]
|
||||
public static delegate* unmanaged<LogLevel, char*, void> LogEnqueue;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
class ApiImportAttribute : Attribute
|
||||
{
|
||||
public string EntryPoint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public class ClientFile : ResourceFile
|
||||
{
|
||||
public ClientFile() {
|
||||
GetStream = GetStreamMethod;
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
public string FullPath { get; internal set; }
|
||||
Stream GetStreamMethod()
|
||||
{
|
||||
if (IsDirectory)
|
||||
{
|
||||
return File.Open(FullPath, FileMode.Open);
|
||||
}
|
||||
throw new InvalidOperationException("Cannot open directory as file");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public class ClientResource
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the resource
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Directory where the scripts is loaded from
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public string ScriptsDirectory { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A resource-specific folder that can be used to store your files.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public string DataFolder { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ClientFile" /> where this script is loaded from.
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public Dictionary<string, ClientFile> Files { get; internal set; } = new Dictionary<string, ClientFile>();
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ResourceLogger Logger => ResourceLogger.Default;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using GTA;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
[JsonDontSerialize]
|
||||
[ScriptAttributes(NoDefaultInstance = true)]
|
||||
public abstract class ClientScript : Script
|
||||
{
|
||||
readonly ConcurrentQueue<Func<bool>> _jobQueue = new();
|
||||
readonly Queue<Func<bool>> _reAdd = new();
|
||||
public ClientScript()
|
||||
{
|
||||
var dir = SHVDN.Core.CurrentDirectory;
|
||||
CurrentResource = APIBridge.GetResourceFromPath(dir);
|
||||
if (CurrentResource == null)
|
||||
throw new Exception("No resource associated with this script is found");
|
||||
|
||||
CurrentFile = CurrentResource.Files.Values.FirstOrDefault(x => x?.FullPath?.ToLower() == FilePath?.ToLower());
|
||||
if (CurrentFile == null)
|
||||
{
|
||||
Logger.Warning("No file associated with curent script was found");
|
||||
}
|
||||
}
|
||||
protected void QueueAction(Func<bool> action) => _jobQueue.Enqueue(action);
|
||||
protected void QueueAction(Action action) => QueueAction(() => { action(); return true; });
|
||||
protected override void OnTick()
|
||||
{
|
||||
base.OnTick();
|
||||
DoQueuedJobs();
|
||||
}
|
||||
private void DoQueuedJobs()
|
||||
{
|
||||
while (_reAdd.TryDequeue(out var toAdd))
|
||||
_jobQueue.Enqueue(toAdd);
|
||||
while (_jobQueue.TryDequeue(out var job))
|
||||
{
|
||||
if (!job())
|
||||
_reAdd.Enqueue(job);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ClientFile" /> instance where this script is loaded from.
|
||||
/// </summary>
|
||||
public ClientFile CurrentFile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ClientResource" /> that this script belongs to.
|
||||
/// </summary>
|
||||
public ClientResource CurrentResource { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
|
||||
/// </summary>
|
||||
public ResourceLogger Logger => CurrentResource.Logger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public class PlayerInfo
|
||||
{
|
||||
public byte HolePunchStatus { get; internal set; }
|
||||
public bool IsHost { get; internal set; }
|
||||
public string Username { get; internal set; }
|
||||
public int ID { get; internal set; }
|
||||
public int EntityHandle { get; internal set; }
|
||||
public IPEndPoint InternalEndPoint { get; internal set; }
|
||||
public IPEndPoint ExternalEndPoint { get; internal set; }
|
||||
public float Ping { get; internal set; }
|
||||
public float PacketTravelTime { get; internal set; }
|
||||
public bool DisplayNameTag { get; internal set; }
|
||||
public bool HasDirectConnection { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<OutDir>..\..\bin\API</OutDir>
|
||||
<DocumentationFile>..\..\bin\API\RageCoop.Client.Scripting.xml</DocumentationFile>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public class ResourceLogger : Core.Logger
|
||||
{
|
||||
public static readonly ResourceLogger Default = new();
|
||||
public ResourceLogger()
|
||||
{
|
||||
FlushImmediately = true;
|
||||
OnFlush += FlushToMainModule;
|
||||
}
|
||||
|
||||
private unsafe void FlushToMainModule(LogLine line, string fomatted)
|
||||
{
|
||||
fixed (char* pMsg = line.Message)
|
||||
{
|
||||
APIBridge.LogEnqueue(line.LogLevel, pMsg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
global using static RageCoop.Core.Shared;
|
||||
global using static RageCoop.Client.Scripting.Shared;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal class Shared
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public class ClientSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// LogLevel for RageCoop.
|
||||
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
|
||||
/// </summary>
|
||||
public int LogLevel = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public string Username { get; set; } = "Player";
|
||||
|
||||
/// <summary>
|
||||
/// The password used to authenticate when connecting to a server.
|
||||
/// </summary>
|
||||
public string Password { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public string LastServerAddress { get; set; } = "127.0.0.1:4499";
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public string MasterServer { get; set; } = "https://test.ragecoop.com/";
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public bool FlipMenu { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public bool Voice { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The key to open menu
|
||||
/// </summary>
|
||||
public Keys MenuKey { get; set; } = Keys.F7;
|
||||
|
||||
/// <summary>
|
||||
/// The key to enter a vehicle as passenger.
|
||||
/// </summary>
|
||||
public Keys PassengerKey { get; set; } = Keys.G;
|
||||
|
||||
/// <summary>
|
||||
/// Disable world NPC traffic, mission entities won't be affected
|
||||
/// </summary>
|
||||
public bool DisableTraffic { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Bring up pause menu but don't freeze time when FrontEndPauseAlternate(Esc) is pressed.
|
||||
/// </summary>
|
||||
public bool DisableAlternatePause { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
|
||||
/// </summary>
|
||||
public int WorldVehicleSoftLimit { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
|
||||
/// </summary>
|
||||
public int WorldPedSoftLimit { get; set; } = 30;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Show the owner name of the entity you're aiming at
|
||||
/// </summary>
|
||||
public bool ShowEntityOwnerName { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Show other player's nametag on your screen, only effective if server didn't disable nametag display
|
||||
/// </summary>
|
||||
public bool ShowPlayerNameTag { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Show other player's blip on map, can be overridden by server resource
|
||||
/// </summary>
|
||||
public bool ShowPlayerBlip { get; set; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using GTA;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
|
||||
internal class DevTool : Script
|
||||
{
|
||||
public static Vehicle ToMark;
|
||||
public static Script Instance;
|
||||
|
||||
public DevTool()
|
||||
{
|
||||
Instance = this;
|
||||
Pause();
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
base.OnTick();
|
||||
|
||||
foreach (var p in World.GetAllPeds()) DrawWeaponBone(p);
|
||||
if (ToMark == null) return;
|
||||
|
||||
if (WeaponUtil.VehicleWeapons.TryGetValue((uint)(int)ToMark.Model, out var info))
|
||||
foreach (var ws in info.Weapons)
|
||||
foreach (var w in ws.Value.Bones)
|
||||
DrawBone(w.BoneName, ws.Value.Name + ":" + ws.Key.ToHex());
|
||||
var P = Game.Player.Character;
|
||||
var b = ToMark.GetMuzzleBone(P.VehicleWeapon);
|
||||
if (b != null) World.DrawLine(b.Position, b.Position + b.ForwardVector * 5, Color.Brown);
|
||||
}
|
||||
|
||||
public static void DrawWeaponBone(Ped p)
|
||||
{
|
||||
var wb = p.Weapons?.CurrentWeaponObject?.Bones["gun_muzzle"];
|
||||
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.RightVector, Color.Blue);
|
||||
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.ForwardVector, Color.Red);
|
||||
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.UpVector, Color.Green);
|
||||
|
||||
}
|
||||
private void FindAndDraw()
|
||||
{
|
||||
DrawBone("weapon_1a");
|
||||
DrawBone("weapon_1b");
|
||||
DrawBone("weapon_1c");
|
||||
DrawBone("weapon_1d");
|
||||
DrawBone("weapon_2a");
|
||||
DrawBone("weapon_2b");
|
||||
DrawBone("weapon_2c");
|
||||
DrawBone("weapon_2d");
|
||||
DrawBone("weapon_3a");
|
||||
DrawBone("weapon_3b");
|
||||
DrawBone("weapon_3c");
|
||||
DrawBone("weapon_3d");
|
||||
DrawBone("weapon_4a");
|
||||
DrawBone("weapon_4b");
|
||||
DrawBone("weapon_4c");
|
||||
DrawBone("weapon_4d");
|
||||
DrawBone("weapon_1e");
|
||||
DrawBone("weapon_1f");
|
||||
DrawBone("weapon_1g");
|
||||
DrawBone("weapon_1h");
|
||||
DrawBone("weapon_2e");
|
||||
DrawBone("weapon_2f");
|
||||
DrawBone("weapon_2g");
|
||||
DrawBone("weapon_2h");
|
||||
DrawBone("weapon_3e");
|
||||
DrawBone("weapon_3f");
|
||||
DrawBone("weapon_3g");
|
||||
DrawBone("weapon_3h");
|
||||
DrawBone("weapon_4e");
|
||||
DrawBone("weapon_4f");
|
||||
DrawBone("weapon_4g");
|
||||
DrawBone("weapon_4h");
|
||||
}
|
||||
|
||||
private void DrawBone(string name, string text = null)
|
||||
{
|
||||
text = text ?? name;
|
||||
var b = ToMark.Bones[name];
|
||||
if (b.IsValid)
|
||||
{
|
||||
var start = b.Position;
|
||||
var end = b.Position + b.ForwardVector * 5;
|
||||
World.DrawLine(start, end, Color.AliceBlue);
|
||||
Util.DrawTextFromCoord(end, text, 0.35f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using DXHook.Hook.Common;
|
||||
using GTA;
|
||||
using RageCoop.Client.CefHost;
|
||||
using RageCoop.Client.Scripting;
|
||||
|
||||
namespace RageCoop.Client.GUI
|
||||
{
|
||||
public class CefClient
|
||||
{
|
||||
public readonly int Id;
|
||||
internal CefAdapter Adapter;
|
||||
internal CefController Controller;
|
||||
internal ImageElement MainFrame;
|
||||
|
||||
internal CefClient(int id, Size size)
|
||||
{
|
||||
Id = id;
|
||||
Controller = CefController.Create(id, size, out Adapter, BufferMode.Full);
|
||||
MainFrame = new ImageElement(size.Width, size.Height, 4, Adapter.PtrBuffer);
|
||||
Adapter.OnPaint += (len, dirty) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Image is using same shared buffer, so just need to make it re-copied to GPU
|
||||
MainFrame.Invalidate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
API.Logger.Error(ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
internal void Destroy()
|
||||
{
|
||||
Controller.Dispose();
|
||||
Adapter.Dispose();
|
||||
MainFrame.Dispose();
|
||||
}
|
||||
|
||||
public Point GetLocationInFrame(Point screenPos)
|
||||
{
|
||||
screenPos.X -= MainFrame.Location.X;
|
||||
screenPos.Y -= MainFrame.Location.Y;
|
||||
return screenPos;
|
||||
}
|
||||
|
||||
public Point GetLocationInCef(Point screenPos)
|
||||
{
|
||||
var p = GetLocationInFrame(screenPos);
|
||||
p.X = (int)(p.X / Scale);
|
||||
p.Y = (int)(p.Y / Scale);
|
||||
return p;
|
||||
}
|
||||
|
||||
internal bool PointInArea(Point screen)
|
||||
{
|
||||
screen = GetLocationInFrame(screen);
|
||||
return screen.X.IsBetween(0, Width) && screen.Y.IsBetween(0, Height);
|
||||
}
|
||||
|
||||
internal void Tick()
|
||||
{
|
||||
var mousePos = Util.CursorPosition;
|
||||
if (!PointInArea(mousePos)) return;
|
||||
var pos = GetLocationInCef(mousePos);
|
||||
if (Game.IsControlJustPressed(Control.CursorAccept))
|
||||
Controller.SendMouseClick(pos.X, pos.Y, 0, MouseButton.Left, false, 1);
|
||||
else if (Game.IsControlJustReleased(Control.CursorAccept))
|
||||
Controller.SendMouseClick(pos.X, pos.Y, 0, MouseButton.Left, true, 1);
|
||||
}
|
||||
|
||||
#region FRAME-APPERANCE
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get => MainFrame.Scale;
|
||||
set => MainFrame.Scale = value;
|
||||
}
|
||||
|
||||
public Color Tint
|
||||
{
|
||||
get => MainFrame.Tint;
|
||||
set => MainFrame.Tint = value;
|
||||
}
|
||||
|
||||
public byte Opacity
|
||||
{
|
||||
get => MainFrame.Opacity;
|
||||
set => MainFrame.Opacity = value;
|
||||
}
|
||||
|
||||
public Point Location
|
||||
{
|
||||
get => MainFrame.Location;
|
||||
set => MainFrame.Location = value;
|
||||
}
|
||||
|
||||
public int Width => MainFrame.Width;
|
||||
public int Height => MainFrame.Height;
|
||||
|
||||
public bool Hidden
|
||||
{
|
||||
get => MainFrame.Hidden;
|
||||
set => MainFrame.Hidden = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using RageCoop.Client.CefHost;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using static RageCoop.Client.Shared;
|
||||
|
||||
namespace RageCoop.Client.GUI
|
||||
{
|
||||
internal static class CefManager
|
||||
{
|
||||
private static readonly ConcurrentDictionary<int, CefClient> Clients =
|
||||
new ConcurrentDictionary<int, CefClient>();
|
||||
|
||||
private static readonly Overlay CefOverlay = new Overlay
|
||||
{
|
||||
Elements = new List<IOverlayElement>(),
|
||||
Hidden = false
|
||||
};
|
||||
|
||||
static CefManager()
|
||||
{
|
||||
Main.CefRunning = true;
|
||||
HookManager.Initialize();
|
||||
CefController.Initialize(CefSubProcessPath);
|
||||
CefController.OnCefMessage = m => API.Logger.Debug(m);
|
||||
HookManager.AddOverLay(CefOverlay);
|
||||
}
|
||||
|
||||
public static CefClient ActiveClient { get; set; }
|
||||
|
||||
public static void Tick()
|
||||
{
|
||||
if (ActiveClient != null)
|
||||
{
|
||||
Game.DisableAllControlsThisFrame();
|
||||
Function.Call(Hash.SET_MOUSE_CURSOR_THIS_FRAME);
|
||||
ActiveClient.Tick();
|
||||
}
|
||||
}
|
||||
|
||||
public static void KeyDown(Keys key)
|
||||
{
|
||||
}
|
||||
|
||||
public static void KeyUp(Keys key)
|
||||
{
|
||||
}
|
||||
|
||||
public static bool DestroyClient(CefClient client)
|
||||
{
|
||||
lock (Clients)
|
||||
{
|
||||
if (Clients.TryRemove(client.Id, out var c) && client == c)
|
||||
{
|
||||
client.Destroy();
|
||||
CefOverlay.Elements.Remove(client.MainFrame);
|
||||
if (ActiveClient == client) ActiveClient = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static CefClient CreateClient(Size size)
|
||||
{
|
||||
lock (Clients)
|
||||
{
|
||||
var id = 0;
|
||||
while (id == 0 || Clients.ContainsKey(id)) id = CoreUtils.RandInt(0, int.MaxValue);
|
||||
var client = new CefClient(id, size);
|
||||
if (Clients.TryAdd(id, client))
|
||||
{
|
||||
CefOverlay.Elements.Add(client.MainFrame);
|
||||
return client;
|
||||
}
|
||||
|
||||
API.Logger.Warning("Failed to create CefClient");
|
||||
client.Destroy();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CleanUp()
|
||||
{
|
||||
Main.CefRunning = false;
|
||||
ActiveClient = null;
|
||||
try
|
||||
{
|
||||
lock (Clients)
|
||||
{
|
||||
foreach (var c in Clients.Values) DestroyClient(c);
|
||||
|
||||
Clients.Clear();
|
||||
}
|
||||
|
||||
CefController.ShutDown();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
API.Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using DXHook.Hook;
|
||||
using DXHook.Hook.Common;
|
||||
using DXHook.Interface;
|
||||
using RageCoop.Client.Scripting;
|
||||
|
||||
namespace RageCoop.Client.GUI
|
||||
{
|
||||
internal static class HookManager
|
||||
{
|
||||
public static readonly CaptureInterface Interface = new CaptureInterface();
|
||||
private static DXHookD3D11 _hook;
|
||||
public static Overlay DefaultOverlay = new Overlay();
|
||||
public static bool Hooked => _hook != null;
|
||||
|
||||
public static void GetOverlays()
|
||||
{
|
||||
new List<IOverlay>(_hook.Overlays);
|
||||
}
|
||||
|
||||
public static void AddOverLay(IOverlay ol)
|
||||
{
|
||||
_hook.Overlays.Add(ol);
|
||||
_hook.IsOverlayUpdatePending = true;
|
||||
}
|
||||
|
||||
public static void RemoveOverlay(IOverlay ol)
|
||||
{
|
||||
_hook.Overlays.Remove(ol);
|
||||
_hook.IsOverlayUpdatePending = true;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (_hook != null) return;
|
||||
_hook = new DXHookD3D11(Interface);
|
||||
_hook.Config = new CaptureConfig
|
||||
{
|
||||
Direct3DVersion = Direct3DVersion.Direct3D11,
|
||||
ShowOverlay = true
|
||||
};
|
||||
_hook.Overlays = new List<IOverlay>();
|
||||
_hook.Hook();
|
||||
_hook.OnPresent += Present;
|
||||
DefaultOverlay.Elements = new List<IOverlayElement>();
|
||||
AddOverLay(DefaultOverlay);
|
||||
Interface.RemoteMessage += m => { API.Logger.Debug("DXHook: " + m.Message); };
|
||||
API.Logger.Debug("Hooked DX3D11");
|
||||
}
|
||||
|
||||
private static void Present()
|
||||
{
|
||||
}
|
||||
|
||||
public static void CleanUp()
|
||||
{
|
||||
_hook?.Cleanup();
|
||||
_hook?.Dispose();
|
||||
_hook = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using LemonUI.Elements;
|
||||
using LemonUI.Menus;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using Control = GTA.Control;
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V", NoScriptThread = true)]
|
||||
internal class Main : Script
|
||||
{
|
||||
internal static Version ModVersion = typeof(Main).Assembly.GetName().Version;
|
||||
|
||||
internal static int LocalPlayerID = 0;
|
||||
|
||||
internal static RelationshipGroup SyncedPedsGroup;
|
||||
|
||||
internal static ClientSettings Settings = null;
|
||||
internal static Chat MainChat = null;
|
||||
internal static Stopwatch Counter = new();
|
||||
internal static Logger Log = null;
|
||||
internal static ulong Ticked = 0;
|
||||
internal static Vector3 PlayerPosition;
|
||||
internal static Resources MainRes = null;
|
||||
|
||||
public static Ped P;
|
||||
public static Vehicle V;
|
||||
public static Vehicle LastV;
|
||||
public static float FPS;
|
||||
private static bool _lastDead;
|
||||
public static bool CefRunning;
|
||||
public static bool IsUnloading { get; private set; }
|
||||
public static Script Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public Main()
|
||||
{
|
||||
Instance = this;
|
||||
Directory.CreateDirectory(DataPath);
|
||||
try
|
||||
{
|
||||
Settings = Util.ReadSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Notification.Show("Malformed configuration, overwriting with default values...");
|
||||
Settings = new();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
|
||||
Log = new Logger()
|
||||
{
|
||||
FlushImmediately = true,
|
||||
Writers = null,
|
||||
#if DEBUG
|
||||
LogLevel = 0,
|
||||
#else
|
||||
LogLevel = Settings.LogLevel,
|
||||
#endif
|
||||
};
|
||||
Log.OnFlush += (line, formatted) =>
|
||||
{
|
||||
SHVDN.Logger.Write($"[RageCoop] {line.Message}", (uint)line.LogLevel);
|
||||
};
|
||||
|
||||
// Run static constructor to register all function pointers and remoting entries
|
||||
RuntimeHelpers.RunClassConstructor(typeof(API).TypeHandle);
|
||||
}
|
||||
|
||||
protected override void OnAborted(AbortedEventArgs e)
|
||||
{
|
||||
base.OnAborted(e);
|
||||
try
|
||||
{
|
||||
IsUnloading = e.IsUnloading;
|
||||
CleanUp("Abort");
|
||||
WorldThread.DoQueuedActions();
|
||||
if (IsUnloading)
|
||||
{
|
||||
ThreadManager.OnUnload();
|
||||
Log.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
}
|
||||
protected override void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
|
||||
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
|
||||
{
|
||||
throw new NotSupportedException("Please update your GTA5 to v1.0.1290 or newer!");
|
||||
}
|
||||
|
||||
MainRes = new();
|
||||
|
||||
|
||||
|
||||
Log.Info(
|
||||
$"Main script initialized");
|
||||
|
||||
BaseScript.OnStart();
|
||||
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
|
||||
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral,
|
||||
true);
|
||||
MainChat = new Chat();
|
||||
|
||||
Util.NativeMemory();
|
||||
Counter.Restart();
|
||||
|
||||
}
|
||||
protected override void OnTick()
|
||||
{
|
||||
base.OnTick();
|
||||
|
||||
var lastVehicleHandle = Call<int>(GET_PLAYERS_LAST_VEHICLE);
|
||||
var playerHandle = Call<int>(PLAYER_PED_ID);
|
||||
|
||||
if (LastV?.Handle != lastVehicleHandle)
|
||||
LastV = Entity.FromHandle(lastVehicleHandle) as Vehicle;
|
||||
|
||||
if (P?.Handle != playerHandle)
|
||||
P = (Ped)Entity.FromHandle(playerHandle);
|
||||
|
||||
var playerVehHandle = Call<int>(GET_VEHICLE_PED_IS_IN, P.Handle, false);
|
||||
if (V?.Handle != playerVehHandle)
|
||||
V = Entity.FromHandle(playerVehHandle) as Vehicle;
|
||||
|
||||
PlayerPosition = P.ReadPosition();
|
||||
FPS = Game.FPS;
|
||||
|
||||
#if CEF
|
||||
if (CefRunning)
|
||||
{
|
||||
CefManager.Tick();
|
||||
}
|
||||
#endif
|
||||
if (!Networking.IsOnServer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
EntityPool.DoSync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
|
||||
|
||||
if (Game.TimeScale != 1.0f)
|
||||
{
|
||||
Game.TimeScale = 1;
|
||||
}
|
||||
|
||||
if (Networking.ShowNetworkInfo)
|
||||
{
|
||||
new ScaledText(new PointF(200, 0),
|
||||
$"L: {Networking.Latency * 1000:N0}ms", 0.5f)
|
||||
{ Alignment = Alignment.Center }.Draw();
|
||||
new ScaledText(new PointF(200, 30),
|
||||
$"R: {NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/s", 0.5f)
|
||||
{ Alignment = Alignment.Center }.Draw();
|
||||
new ScaledText(new PointF(200, 60),
|
||||
$"S: {NetUtility.ToHumanReadable(Statistics.BytesUpPerSecond)}/s", 0.5f)
|
||||
{ Alignment = Alignment.Center }.Draw();
|
||||
}
|
||||
|
||||
MainChat.Tick();
|
||||
PlayerList.Tick();
|
||||
if (!API.Config.EnableAutoRespawn)
|
||||
{
|
||||
Call(PAUSE_DEATH_ARREST_RESTART, true);
|
||||
Call(IGNORE_NEXT_RESTART, true);
|
||||
Call(FORCE_GAME_STATE_PLAYING);
|
||||
Call(TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
|
||||
if (P.IsDead)
|
||||
{
|
||||
Call(SET_FADE_OUT_AFTER_DEATH, false);
|
||||
|
||||
if (P.Health != 1)
|
||||
{
|
||||
P.Health = 1;
|
||||
Game.Player.WantedLevel = 0;
|
||||
Log.Debug("Player died.");
|
||||
API.Events.InvokePlayerDied();
|
||||
}
|
||||
|
||||
Screen.StopEffects();
|
||||
}
|
||||
else
|
||||
{
|
||||
Call(DISPLAY_HUD, true);
|
||||
}
|
||||
}
|
||||
else if (P.IsDead && !_lastDead)
|
||||
{
|
||||
API.Events.InvokePlayerDied();
|
||||
}
|
||||
|
||||
_lastDead = P.IsDead;
|
||||
Ticked++;
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(GTA.KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyUp(e);
|
||||
#if CEF
|
||||
if (CefRunning)
|
||||
{
|
||||
CefManager.KeyUp(e.KeyCode);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
protected unsafe override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
if (MainChat.Focused)
|
||||
{
|
||||
MainChat.OnKeyDown(e.KeyCode);
|
||||
return;
|
||||
}
|
||||
#if CEF
|
||||
if (CefRunning)
|
||||
{
|
||||
CefManager.KeyDown(e.KeyCode);
|
||||
}
|
||||
#endif
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
if (Voice.WasInitialized())
|
||||
{
|
||||
if (Game.IsControlPressed(Control.PushToTalk))
|
||||
{
|
||||
Voice.StartRecording();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Voice.IsRecording())
|
||||
{
|
||||
Voice.StopRecording();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Game.IsControlPressed(Control.FrontendPause))
|
||||
{
|
||||
Call(ACTIVATE_FRONTEND_MENU,
|
||||
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Game.IsControlPressed(Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
|
||||
{
|
||||
Call(ACTIVATE_FRONTEND_MENU,
|
||||
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.KeyCode == Settings.MenuKey)
|
||||
{
|
||||
if (CoopMenu.MenuPool.AreAnyVisible)
|
||||
{
|
||||
CoopMenu.MenuPool.ForEach<NativeMenu>(x =>
|
||||
{
|
||||
if (x.Visible)
|
||||
{
|
||||
CoopMenu.LastMenu = x;
|
||||
x.Visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
CoopMenu.LastMenu.Visible = true;
|
||||
}
|
||||
}
|
||||
else if (Game.IsControlJustPressed(Control.MpTextChatAll))
|
||||
{
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
MainChat.Focused = true;
|
||||
}
|
||||
}
|
||||
else if (MainChat.Focused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (Game.IsControlJustPressed(Control.MultiplayerInfo))
|
||||
{
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
ulong currentTimestamp = Util.GetTickCount64();
|
||||
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000
|
||||
? (currentTimestamp - 6000)
|
||||
: currentTimestamp;
|
||||
}
|
||||
}
|
||||
else if (e.KeyCode == Settings.PassengerKey)
|
||||
{
|
||||
if (P == null || P.IsInVehicle())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
|
||||
{
|
||||
P.Task.ClearAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
var V = World.GetClosestVehicle(P.ReadPosition(), 15);
|
||||
|
||||
if (V != null)
|
||||
{
|
||||
var seat = P.GetNearestSeat(V);
|
||||
var p = V.GetPedOnSeat(seat);
|
||||
if (p != null && !p.IsDead)
|
||||
{
|
||||
for (int i = -1; i < V.PassengerCapacity; i++)
|
||||
{
|
||||
seat = (VehicleSeat)i;
|
||||
p = V.GetPedOnSeat(seat);
|
||||
if (p == null || p.IsDead)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
P.Task.EnterVehicle(V, seat, -1, 5, EnterVehicleFlags.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Connected()
|
||||
{
|
||||
Memory.ApplyPatches();
|
||||
if (Settings.Voice && !Voice.WasInitialized())
|
||||
{
|
||||
Voice.Init();
|
||||
}
|
||||
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
WorldThread.Traffic(!Settings.DisableTraffic);
|
||||
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
|
||||
CoopMenu.ConnectedMenuSetting();
|
||||
MainChat.Init();
|
||||
Notification.Show("~g~Connected!");
|
||||
});
|
||||
|
||||
Log.Info(">> Connected <<");
|
||||
}
|
||||
|
||||
private static readonly object _cleanupLock = new();
|
||||
public static void CleanUp(string reason)
|
||||
{
|
||||
lock (_cleanupLock)
|
||||
{
|
||||
|
||||
if (reason != "Abort")
|
||||
{
|
||||
Log.Info($">> Disconnected << reason: {reason}");
|
||||
Notification.Show("~r~Disconnected: " + reason);
|
||||
}
|
||||
|
||||
if (MainChat.Focused)
|
||||
{
|
||||
MainChat.Focused = false;
|
||||
}
|
||||
|
||||
PlayerList.Cleanup();
|
||||
MainChat.Clear();
|
||||
EntityPool.Cleanup();
|
||||
WorldThread.Traffic(true);
|
||||
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
|
||||
CoopMenu.DisconnectedMenuSetting();
|
||||
LocalPlayerID = default;
|
||||
MainRes.Unload();
|
||||
Memory.RestorePatches();
|
||||
#if CEF
|
||||
if (CefRunning)
|
||||
{
|
||||
CefManager.CleanUp();
|
||||
}
|
||||
#endif
|
||||
|
||||
DownloadManager.Cleanup();
|
||||
Voice.ClearAll();
|
||||
Networking.Peer?.Dispose();
|
||||
Networking.Peer = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,53 @@
|
||||
using GTA;
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using LemonUI;
|
||||
using LemonUI.Elements;
|
||||
using LemonUI.Menus;
|
||||
using LemonUI.Scaleform;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
internal static class CoopMenu
|
||||
{
|
||||
public static ObjectPool MenuPool = new ObjectPool();
|
||||
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "MAIN")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
public static PopUp PopUp=new PopUp()
|
||||
|
||||
public static PopUp PopUp = new PopUp
|
||||
{
|
||||
Title="",
|
||||
Prompt="",
|
||||
Title = "",
|
||||
Prompt = "",
|
||||
Subtitle = "",
|
||||
Error="",
|
||||
Error = "",
|
||||
ShowBackground = true,
|
||||
Visible=false,
|
||||
Visible = false
|
||||
};
|
||||
public static NativeMenu LastMenu { get; set; } = Menu;
|
||||
#region ITEMS
|
||||
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
|
||||
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*',Main.Settings.Password.Length) };
|
||||
|
||||
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
|
||||
private static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
|
||||
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
|
||||
"https://github.com/RAGECOOP~n~" +
|
||||
"~y~VERSION~s~~n~" +
|
||||
Main.CurrentVersion.Replace("_", ".")) { LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
static CoopMenu()
|
||||
{
|
||||
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
_usernameItem.Activated += UsernameActivated;
|
||||
_passwordItem.Activated+=_passwordActivated;
|
||||
_passwordItem.Activated += _passwordActivated;
|
||||
ServerIpItem.Activated += ServerIpActivated;
|
||||
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
|
||||
_serverConnectItem.Activated += (sender, item) =>
|
||||
{
|
||||
Networking.ToggleConnection(Settings.LastServerAddress);
|
||||
};
|
||||
|
||||
|
||||
Menu.AddSubMenu(ServersMenu.Menu);
|
||||
@@ -69,72 +61,89 @@ namespace RageCoop.Client.Menus
|
||||
Menu.AddSubMenu(DevToolMenu.Menu);
|
||||
Menu.AddSubMenu(DebugMenu.Menu);
|
||||
|
||||
|
||||
MenuPool.Add(Menu);
|
||||
MenuPool.Add(SettingsMenu.Menu);
|
||||
MenuPool.Add(DevToolMenu.Menu);
|
||||
MenuPool.Add(DebugMenu.Menu);
|
||||
MenuPool.Add(DebugMenu.DiagnosticMenu);
|
||||
MenuPool.Add(DebugMenu.TuneMenu);
|
||||
MenuPool.Add(ServersMenu.Menu);
|
||||
MenuPool.Add(PopUp);
|
||||
|
||||
Menu.Add(_aboutItem);
|
||||
}
|
||||
|
||||
public static NativeMenu LastMenu { get; set; } = Menu;
|
||||
|
||||
public static bool ShowPopUp(string prompt, string title,string subtitle,string error,bool showbackground)
|
||||
|
||||
public static bool ShowPopUp(string prompt, string title, string subtitle, string error, bool showbackground)
|
||||
{
|
||||
PopUp.Prompt=prompt;
|
||||
PopUp.Title=title;
|
||||
PopUp.Subtitle=subtitle;
|
||||
PopUp.Error=error;
|
||||
PopUp.ShowBackground=showbackground;
|
||||
PopUp.Visible=true;
|
||||
PopUp.Prompt = prompt;
|
||||
PopUp.Title = title;
|
||||
PopUp.Subtitle = subtitle;
|
||||
PopUp.Error = error;
|
||||
PopUp.ShowBackground = showbackground;
|
||||
PopUp.Visible = true;
|
||||
Script.Yield();
|
||||
while (true)
|
||||
{
|
||||
Game.DisableAllControlsThisFrame();
|
||||
MenuPool.Process();
|
||||
|
||||
var scaleform = new Scaleform("instructional_buttons");
|
||||
scaleform.CallFunction("CLEAR_ALL");
|
||||
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
|
||||
scaleform.CallFunction("CREATE_CONTAINER");
|
||||
|
||||
scaleform.CallFunction("SET_DATA_SLOT", 0,
|
||||
Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
|
||||
scaleform.CallFunction("SET_DATA_SLOT", 1,
|
||||
Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
|
||||
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
|
||||
scaleform.Render2D();
|
||||
if (Game.IsControlJustPressed(Control.FrontendAccept))
|
||||
{
|
||||
PopUp.Visible=false;
|
||||
PopUp.Visible = false;
|
||||
return true;
|
||||
}
|
||||
else if (Game.IsControlJustPressed(Control.FrontendCancel))
|
||||
|
||||
if (Game.IsControlJustPressed(Control.FrontendCancel))
|
||||
{
|
||||
PopUp.Visible = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Script.Yield();
|
||||
Game.DisableAllControlsThisFrame();
|
||||
}
|
||||
}
|
||||
public static void UsernameActivated(object a, System.EventArgs b)
|
||||
|
||||
public static void UsernameActivated(object a, EventArgs b)
|
||||
{
|
||||
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
|
||||
var newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
|
||||
if (!string.IsNullOrWhiteSpace(newUsername))
|
||||
{
|
||||
Main.Settings.Username = newUsername;
|
||||
Settings.Username = newUsername;
|
||||
Util.SaveSettings();
|
||||
|
||||
_usernameItem.AltTitle = newUsername;
|
||||
}
|
||||
}
|
||||
|
||||
private static void _passwordActivated(object sender, System.EventArgs e)
|
||||
private static void _passwordActivated(object sender, EventArgs e)
|
||||
{
|
||||
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
|
||||
if (!string.IsNullOrWhiteSpace(newPass))
|
||||
{
|
||||
Main.Settings.Password = newPass;
|
||||
Util.SaveSettings();
|
||||
_passwordItem.AltTitle = new string('*', newPass.Length);
|
||||
}
|
||||
var newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
|
||||
Settings.Password = newPass;
|
||||
Util.SaveSettings();
|
||||
_passwordItem.AltTitle = new string('*', newPass.Length);
|
||||
}
|
||||
public static void ServerIpActivated(object a, System.EventArgs b)
|
||||
|
||||
public static void ServerIpActivated(object a, EventArgs b)
|
||||
{
|
||||
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
|
||||
var newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
|
||||
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
|
||||
{
|
||||
Main.Settings.LastServerAddress = newServerIp;
|
||||
Settings.LastServerAddress = newServerIp;
|
||||
Util.SaveSettings();
|
||||
|
||||
ServerIpItem.AltTitle = newServerIp;
|
||||
@@ -162,5 +171,26 @@ namespace RageCoop.Client.Menus
|
||||
_serverConnectItem.Enabled = true;
|
||||
_serverConnectItem.Title = "Connect";
|
||||
}
|
||||
|
||||
#region ITEMS
|
||||
|
||||
private static readonly NativeItem _usernameItem = new NativeItem("Username")
|
||||
{ AltTitle = Settings.Username };
|
||||
|
||||
private static readonly NativeItem _passwordItem = new NativeItem("Password")
|
||||
{ AltTitle = new string('*', Settings.Password.Length) };
|
||||
|
||||
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP")
|
||||
{ AltTitle = Settings.LastServerAddress };
|
||||
|
||||
internal static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
|
||||
|
||||
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
|
||||
"https://github.com/RAGECOOP~n~" +
|
||||
"~y~VERSION~s~~n~" +
|
||||
Main.ModVersion)
|
||||
{ LeftBadge = new ScaledTexture("commonmenu", "shop_new_star") };
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DebugMenu
|
||||
{
|
||||
public static NativeMenu Menu = new("RAGECOOP", "Debug", "Debug settings")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
public static NativeMenu DiagnosticMenu = new("RAGECOOP", "Diagnostic", "Performence and Diagnostic")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
|
||||
public static NativeMenu TuneMenu = new("RAGECOOP", "Change tunable values")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
public static NativeItem SimulatedLatencyItem =
|
||||
new("Simulated network latency", "Simulated network latency in ms (one way)", "0");
|
||||
|
||||
public static NativeCheckboxItem ShowOwnerItem = new("Show entity owner",
|
||||
"Show the owner name of the entity you're aiming at", false);
|
||||
|
||||
private static readonly NativeCheckboxItem ShowNetworkInfoItem =
|
||||
new("Show Network Info", Networking.ShowNetworkInfo);
|
||||
|
||||
static DebugMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
TuneMenu.Opening += (s, e) =>
|
||||
{
|
||||
TuneMenu.Clear();
|
||||
foreach (var t in typeof(Main).Assembly.GetTypes())
|
||||
{
|
||||
foreach (var field in t.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic))
|
||||
{
|
||||
var attri = field.GetCustomAttribute<DebugTunableAttribute>();
|
||||
if (attri == null)
|
||||
continue;
|
||||
var item = new NativeItem(field.Name);
|
||||
item.AltTitle = field.GetValue(null).ToString();
|
||||
item.Activated += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
field.SetValue(null, Convert.ChangeType(Game.GetUserInput(), field.FieldType));
|
||||
item.AltTitle = field.GetValue(null).ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
};
|
||||
TuneMenu.Add(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
DiagnosticMenu.Opening += (sender, e) =>
|
||||
{
|
||||
DiagnosticMenu.Clear();
|
||||
DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug()));
|
||||
// foreach (var pair in Debug.TimeStamps)
|
||||
// DiagnosticMenu.Add(
|
||||
// new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
|
||||
};
|
||||
ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
|
||||
{
|
||||
Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked;
|
||||
};
|
||||
ShowOwnerItem.CheckboxChanged += (s, e) =>
|
||||
{
|
||||
Settings.ShowEntityOwnerName = ShowOwnerItem.Checked;
|
||||
Util.SaveSettings();
|
||||
};
|
||||
#if DEBUG
|
||||
SimulatedLatencyItem.Activated += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SimulatedLatencyItem.AltTitle =
|
||||
((Networking.SimulatedLatency =
|
||||
int.Parse(Game.GetUserInput(SimulatedLatencyItem.AltTitle)) * 0.002f) * 500).ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
};
|
||||
Menu.Add(SimulatedLatencyItem);
|
||||
#endif
|
||||
Menu.Add(ShowNetworkInfoItem);
|
||||
Menu.Add(ShowOwnerItem);
|
||||
Menu.AddSubMenu(DiagnosticMenu);
|
||||
Menu.AddSubMenu(TuneMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using RageCoop.Core;
|
||||
|
||||
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DevToolMenu
|
||||
{
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "DevTool", "Internal testing tools")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
private static readonly NativeCheckboxItem enableItem = new NativeCheckboxItem("Show weapon bones");
|
||||
public static readonly NativeItem DumpFixItem = new NativeItem("Dump weapon fixes");
|
||||
public static readonly NativeItem GetAnimItem = new NativeItem("Get current animation");
|
||||
public static readonly NativeItem DumpVwHashItem = new NativeItem("Dump VehicleWeaponHash.cs");
|
||||
|
||||
static DevToolMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
enableItem.Activated += ShowBones;
|
||||
enableItem.Checked = false;
|
||||
DumpFixItem.Activated += (s, e) => WeaponUtil.DumpWeaponFix(WeaponFixDataPath);
|
||||
GetAnimItem.Activated += (s, e) =>
|
||||
{
|
||||
if (File.Exists(AnimationsDataPath))
|
||||
{
|
||||
var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(AnimationsDataPath));
|
||||
foreach (var anim in anims)
|
||||
foreach (var a in anim.Animations)
|
||||
if (Call<bool>(IS_ENTITY_PLAYING_ANIM, P, anim.DictionaryName, a, 3))
|
||||
{
|
||||
Console.PrintInfo(anim.DictionaryName + " : " + a);
|
||||
Notification.Show(anim.DictionaryName + " : " + a);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Notification.Show($"~r~{AnimationsDataPath} not found");
|
||||
}
|
||||
};
|
||||
|
||||
Menu.Add(enableItem);
|
||||
Menu.Add(DumpVwHashItem);
|
||||
Menu.Add(DumpFixItem);
|
||||
Menu.Add(GetAnimItem);
|
||||
}
|
||||
|
||||
private static void ShowBones(object sender, EventArgs e)
|
||||
{
|
||||
if (enableItem.Checked)
|
||||
{
|
||||
DevTool.ToMark = Game.Player.Character.CurrentVehicle;
|
||||
DevTool.Instance.Resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
DevTool.Instance.Pause();
|
||||
DevTool.ToMark = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
-52
@@ -1,71 +1,49 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Drawing;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using LemonUI.Menus;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
internal class ServerListClass
|
||||
{
|
||||
[JsonProperty("address")]
|
||||
public string Address { get; set; }
|
||||
|
||||
[JsonProperty("port")]
|
||||
public string Port { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
|
||||
[JsonProperty("players")]
|
||||
public int Players { get; set; }
|
||||
|
||||
[JsonProperty("maxPlayers")]
|
||||
public int MaxPlayers { get; set; }
|
||||
|
||||
[JsonProperty("country")]
|
||||
public string Country { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
internal static class ServersMenu
|
||||
{
|
||||
private static Thread GetServersThread;
|
||||
|
||||
internal static NativeMenu Menu = new NativeMenu("RAGECOOP", "Servers", "Go to the server list")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
internal static NativeItem ResultItem = null;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
static ServersMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) =>
|
||||
Menu.Opening += (object sender, CancelEventArgs e) =>
|
||||
{
|
||||
CleanUpList();
|
||||
Menu.Add(ResultItem = new NativeItem("Loading..."));
|
||||
|
||||
// Prevent freezing
|
||||
GetServersThread=new Thread(()=> GetAllServers());
|
||||
GetServersThread.Start();
|
||||
};
|
||||
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
|
||||
{
|
||||
CleanUpList();
|
||||
GetServersThread = ThreadManager.CreateThread(() => GetAllServers(),"GetServers");
|
||||
};
|
||||
Menu.Closing += (object sender, CancelEventArgs e) => { CleanUpList(); };
|
||||
}
|
||||
|
||||
private static void CleanUpList()
|
||||
@@ -76,52 +54,74 @@ namespace RageCoop.Client.Menus
|
||||
|
||||
private static void GetAllServers()
|
||||
{
|
||||
List<ServerListClass> serverList = null;
|
||||
var realUrl = Main.Settings.MasterServer=="[AUTO]" ? DownloadString("https://ragecoop.online/stuff/masterserver") : Main.Settings.MasterServer;
|
||||
serverList = JsonConvert.DeserializeObject<List<ServerListClass>>(DownloadString(realUrl));
|
||||
|
||||
List<ServerInfo> serverList = null;
|
||||
var realUrl = Settings.MasterServer;
|
||||
serverList = null;
|
||||
try { serverList = JsonDeserialize<List<ServerInfo>>(DownloadString(realUrl)); }
|
||||
catch (Exception ex) { Log.Error(ex); }
|
||||
|
||||
// Need to be processed in main thread
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
if (serverList == null)
|
||||
{
|
||||
ResultItem.Title = "Something went wrong!";
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverList.Count == 0)
|
||||
{
|
||||
ResultItem.Title = "No server was found!";
|
||||
return;
|
||||
}
|
||||
|
||||
CleanUpList();
|
||||
foreach (ServerListClass server in serverList)
|
||||
foreach (ServerInfo server in serverList)
|
||||
{
|
||||
string address = $"{server.Address}:{server.Port}";
|
||||
NativeItem tmpItem = new NativeItem($"[{server.Country}] {server.Name}", $"~b~{address}~s~~n~~g~Version {server.Version}.x~s~") { AltTitle = $"[{server.Players}/{server.MaxPlayers}]" };
|
||||
string address = $"{server.address}:{server.port}";
|
||||
NativeItem tmpItem =
|
||||
new NativeItem($"[{server.country}] {server.name}",
|
||||
$"~b~{address}~s~~n~~g~Version {server.version}.x~s~")
|
||||
{ AltTitle = $"[{server.players}/{server.maxPlayers}]" };
|
||||
tmpItem.Activated += (object sender, EventArgs e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Menu.Visible = false;
|
||||
if (server.useZT)
|
||||
{
|
||||
address = $"{server.ztAddress}:{server.port}";
|
||||
Notification.Show($"~y~Joining ZeroTier network... {server.ztID}");
|
||||
if (ZeroTierHelper.Join(server.ztID) == null)
|
||||
{
|
||||
throw new Exception("Failed to obtain ZeroTier network IP");
|
||||
}
|
||||
}
|
||||
|
||||
Networking.ToggleConnection(address);
|
||||
Networking.ToggleConnection(address, null, null, PublicKey.FromServerInfo(server));
|
||||
#if !NON_INTERACTIVE
|
||||
CoopMenu.ServerIpItem.AltTitle = address;
|
||||
|
||||
CoopMenu.Menu.Visible = true;
|
||||
#endif
|
||||
Main.Settings.LastServerAddress = address;
|
||||
Settings.LastServerAddress = address;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GTA.UI.Notification.Show($"~r~{ex.Message}");
|
||||
Notification.Show($"~r~{ex.Message}");
|
||||
if (server.useZT)
|
||||
{
|
||||
Notification.Show(
|
||||
$"Make sure ZeroTier is correctly installed, download it from https://www.zerotier.com/");
|
||||
}
|
||||
}
|
||||
};
|
||||
Menu.Add(tmpItem);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static string DownloadString(string url)
|
||||
{
|
||||
try
|
||||
@@ -131,12 +131,12 @@ namespace RageCoop.Client.Menus
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
|
||||
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
|
||||
|
||||
WebClient client = new WebClient();
|
||||
return client.DownloadString(url);
|
||||
var client = new HttpClient();
|
||||
return client.GetStringAsync(url).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.QueueAction(() =>
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
ResultItem.Title = "Download failed!";
|
||||
ResultItem.Description = ex.Message;
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
using GTA;
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
internal static class SettingsMenu
|
||||
{
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
private static readonly NativeCheckboxItem _disableTrafficItem =
|
||||
new("Disable Traffic (NPCs/Vehicles)", "Local traffic only",
|
||||
Settings.DisableTraffic);
|
||||
|
||||
private static readonly NativeCheckboxItem _flipMenuItem =
|
||||
new("Flip menu", Settings.FlipMenu);
|
||||
|
||||
private static readonly NativeCheckboxItem _disablePauseAlt = new("Disable Alternate Pause",
|
||||
"Don't freeze game time when Esc pressed", Settings.DisableAlternatePause);
|
||||
|
||||
private static readonly NativeCheckboxItem _disableVoice = new("Enable voice",
|
||||
"Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends",
|
||||
Settings.Voice);
|
||||
|
||||
private static readonly NativeCheckboxItem _showBlip = new("Show player blip",
|
||||
"Show other player's blip on map, can be overridden by server resource ",
|
||||
Settings.ShowPlayerBlip);
|
||||
|
||||
private static readonly NativeCheckboxItem _showNametag = new("Show player nametag",
|
||||
"Show other player's nametag on your screen, only effective if server didn't disable nametag display",
|
||||
Settings.ShowPlayerNameTag);
|
||||
|
||||
private static readonly NativeItem _menuKey =
|
||||
new("Menu Key", "The key to open menu", Settings.MenuKey.ToString());
|
||||
|
||||
private static readonly NativeItem _passengerKey = new("Passenger Key",
|
||||
"The key to enter a vehicle as passenger", Settings.PassengerKey.ToString());
|
||||
|
||||
private static readonly NativeItem _vehicleSoftLimit = new("Vehicle limit (soft)",
|
||||
"The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).",
|
||||
Settings.WorldVehicleSoftLimit.ToString());
|
||||
|
||||
static SettingsMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
_disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged;
|
||||
_disablePauseAlt.CheckboxChanged += DisablePauseAltCheckboxChanged;
|
||||
_disableVoice.CheckboxChanged += DisableVoiceCheckboxChanged;
|
||||
_flipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged;
|
||||
_menuKey.Activated += ChaneMenuKey;
|
||||
_passengerKey.Activated += ChangePassengerKey;
|
||||
_vehicleSoftLimit.Activated += VehicleSoftLimitActivated;
|
||||
_showBlip.Activated += (s, e) =>
|
||||
{
|
||||
Settings.ShowPlayerBlip = _showBlip.Checked;
|
||||
Util.SaveSettings();
|
||||
};
|
||||
_showNametag.Activated += (s, e) =>
|
||||
{
|
||||
API.Config.ShowPlayerNameTag = _showNametag.Checked;
|
||||
};
|
||||
|
||||
Menu.Add(_disableTrafficItem);
|
||||
Menu.Add(_disablePauseAlt);
|
||||
Menu.Add(_flipMenuItem);
|
||||
Menu.Add(_disableVoice);
|
||||
Menu.Add(_menuKey);
|
||||
Menu.Add(_passengerKey);
|
||||
Menu.Add(_vehicleSoftLimit);
|
||||
Menu.Add(_showBlip);
|
||||
Menu.Add(_showNametag);
|
||||
}
|
||||
|
||||
private static void DisableVoiceCheckboxChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_disableVoice.Checked)
|
||||
{
|
||||
if (Networking.IsOnServer && !Voice.WasInitialized()) Voice.Init();
|
||||
}
|
||||
else
|
||||
{
|
||||
Voice.ClearAll();
|
||||
}
|
||||
|
||||
Settings.Voice = _disableVoice.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
|
||||
private static void DisablePauseAltCheckboxChanged(object sender, EventArgs e)
|
||||
{
|
||||
Settings.DisableAlternatePause = _disablePauseAlt.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
|
||||
private static void VehicleSoftLimitActivated(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Settings.WorldVehicleSoftLimit = int.Parse(
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Settings.WorldVehicleSoftLimit.ToString(), 20));
|
||||
_menuKey.AltTitle = Settings.WorldVehicleSoftLimit.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void ChaneMenuKey(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Settings.MenuKey = (Keys)Enum.Parse(
|
||||
typeof(Keys),
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Settings.MenuKey.ToString(), 20));
|
||||
_menuKey.AltTitle = Settings.MenuKey.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static void ChangePassengerKey(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Settings.PassengerKey = (Keys)Enum.Parse(
|
||||
typeof(Keys),
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Settings.PassengerKey.ToString(), 20));
|
||||
_passengerKey.AltTitle = Settings.PassengerKey.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static void DisableTrafficCheckboxChanged(object a, EventArgs b)
|
||||
{
|
||||
WorldThread.Traffic(!_disableTrafficItem.Checked);
|
||||
Settings.DisableTraffic = _disableTrafficItem.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
|
||||
public static void FlipMenuCheckboxChanged(object a, EventArgs b)
|
||||
{
|
||||
CoopMenu.Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
|
||||
|
||||
Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
|
||||
Settings.FlipMenu = _flipMenuItem.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
|
||||
@@ -12,18 +10,21 @@ namespace RageCoop.Client
|
||||
{
|
||||
private readonly Scaleform MainScaleForm;
|
||||
|
||||
public Chat()
|
||||
{
|
||||
MainScaleForm = new Scaleform("multiplayer_chat");
|
||||
}
|
||||
|
||||
public string CurrentInput { get; set; }
|
||||
|
||||
private bool CurrentFocused { get; set; }
|
||||
|
||||
public bool Focused
|
||||
{
|
||||
get { return CurrentFocused; }
|
||||
get => CurrentFocused;
|
||||
set
|
||||
{
|
||||
if (value && Hidden)
|
||||
{
|
||||
Hidden = false;
|
||||
}
|
||||
if (value && Hidden) Hidden = false;
|
||||
|
||||
MainScaleForm.CallFunction("SET_FOCUS", value ? 2 : 1, 2, "ALL");
|
||||
|
||||
@@ -34,17 +35,15 @@ namespace RageCoop.Client
|
||||
private ulong LastMessageTime { get; set; }
|
||||
|
||||
private bool CurrentHidden { get; set; }
|
||||
|
||||
private bool Hidden
|
||||
{
|
||||
get { return CurrentHidden; }
|
||||
get => CurrentHidden;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (!CurrentHidden)
|
||||
{
|
||||
MainScaleForm.CallFunction("hide");
|
||||
}
|
||||
if (!CurrentHidden) MainScaleForm.CallFunction("hide");
|
||||
}
|
||||
else if (CurrentHidden)
|
||||
{
|
||||
@@ -55,11 +54,6 @@ namespace RageCoop.Client
|
||||
}
|
||||
}
|
||||
|
||||
public Chat()
|
||||
{
|
||||
MainScaleForm = new Scaleform("multiplayer_chat");
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
MainScaleForm.CallFunction("SET_FOCUS", 2, 2, "ALL");
|
||||
@@ -73,22 +67,13 @@ namespace RageCoop.Client
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if ((Util.GetTickCount64() - LastMessageTime) > 15000 && !Focused && !Hidden)
|
||||
{
|
||||
Hidden = true;
|
||||
}
|
||||
if (Util.GetTickCount64() - LastMessageTime > 15000 && !Focused && !Hidden) Hidden = true;
|
||||
|
||||
if (!Hidden)
|
||||
{
|
||||
MainScaleForm.Render2D();
|
||||
}
|
||||
if (!Hidden) MainScaleForm.Render2D();
|
||||
|
||||
if (!CurrentFocused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!CurrentFocused) return;
|
||||
|
||||
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, 0);
|
||||
Call(DISABLE_ALL_CONTROL_ACTIONS, 0);
|
||||
}
|
||||
|
||||
public void AddMessage(string sender, string msg)
|
||||
@@ -106,22 +91,14 @@ namespace RageCoop.Client
|
||||
CurrentInput = "";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (key == Keys.PageUp)
|
||||
{
|
||||
MainScaleForm.CallFunction("PAGE_UP");
|
||||
}
|
||||
else if (key == Keys.PageDown)
|
||||
{
|
||||
MainScaleForm.CallFunction("PAGE_DOWN");
|
||||
}
|
||||
else if (key == Keys.PageDown) MainScaleForm.CallFunction("PAGE_DOWN");
|
||||
|
||||
string keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
|
||||
var keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
|
||||
|
||||
if (keyChar.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (keyChar.Length == 0) return;
|
||||
|
||||
switch (keyChar[0])
|
||||
{
|
||||
@@ -131,14 +108,12 @@ namespace RageCoop.Client
|
||||
CurrentInput = CurrentInput.Remove(CurrentInput.Length - 1);
|
||||
MainScaleForm.CallFunction("DELETE_TEXT");
|
||||
}
|
||||
|
||||
return;
|
||||
case (char)13:
|
||||
MainScaleForm.CallFunction("ADD_TEXT", "ENTER");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(CurrentInput))
|
||||
{
|
||||
Networking.SendChatMessage(CurrentInput);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(CurrentInput)) Networking.SendChatMessage(CurrentInput);
|
||||
|
||||
Focused = false;
|
||||
CurrentInput = "";
|
||||
@@ -152,19 +127,19 @@ namespace RageCoop.Client
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int ToUnicodeEx(uint virtualKeyCode, uint scanCode, byte[] keyboardState,
|
||||
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
|
||||
[Out] [MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
|
||||
StringBuilder receivingBuffer,
|
||||
int bufferSize, uint flags, IntPtr kblayout);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr GetKeyboardLayout(uint idThread);
|
||||
|
||||
public static string GetCharFromKey(Keys key, bool shift, bool altGr)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(256);
|
||||
byte[] keyboardState = new byte[256];
|
||||
var buf = new StringBuilder(256);
|
||||
var keyboardState = new byte[256];
|
||||
|
||||
if (shift)
|
||||
{
|
||||
keyboardState[(int)Keys.ShiftKey] = 0xff;
|
||||
}
|
||||
if (shift) keyboardState[(int)Keys.ShiftKey] = 0xff;
|
||||
|
||||
if (altGr)
|
||||
{
|
||||
@@ -172,8 +147,8 @@ namespace RageCoop.Client
|
||||
keyboardState[(int)Keys.Menu] = 0xff;
|
||||
}
|
||||
|
||||
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, InputLanguage.CurrentInputLanguage.Handle);
|
||||
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, GetKeyboardLayout(0));
|
||||
return buf.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using static RageCoop.Client.Shared;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DownloadManager
|
||||
{
|
||||
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
|
||||
private static readonly HashSet<string> _resources = new HashSet<string>();
|
||||
|
||||
static DownloadManager()
|
||||
{
|
||||
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, data =>
|
||||
{
|
||||
var fr = new Packets.FileTransferRequest();
|
||||
fr.Deserialize(data);
|
||||
if (fr.Name.EndsWith(".res")) _resources.Add(fr.Name);
|
||||
return new Packets.FileTransferResponse
|
||||
{
|
||||
ID = fr.ID,
|
||||
Response = AddFile(fr.ID, fr.Name, fr.FileLength)
|
||||
? FileResponse.NeedToDownload
|
||||
: FileResponse.AlreadyExists
|
||||
};
|
||||
});
|
||||
Networking.RequestHandlers.Add(PacketType.FileTransferComplete, data =>
|
||||
{
|
||||
var packet = new Packets.FileTransferComplete();
|
||||
packet.Deserialize(data);
|
||||
|
||||
Log.Debug($"Finalizing download:{packet.ID}");
|
||||
Complete(packet.ID);
|
||||
|
||||
// Inform the server that the download is completed
|
||||
return new Packets.FileTransferResponse
|
||||
{
|
||||
ID = packet.ID,
|
||||
Response = FileResponse.Completed
|
||||
};
|
||||
});
|
||||
Networking.RequestHandlers.Add(PacketType.AllResourcesSent, data =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(ResourceFolder);
|
||||
MainRes.Load(ResourceFolder, _resources.ToArray());
|
||||
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.Loaded };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Error occurred when loading server resource");
|
||||
Log.Error(ex);
|
||||
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.LoadFailed };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static string ResourceFolder => Path.GetFullPath(Path.Combine(DataPath, "Resources",
|
||||
API.ServerEndPoint.ToString().Replace(":", ".")));
|
||||
|
||||
public static event EventHandler<string> DownloadCompleted;
|
||||
|
||||
public static bool AddFile(int id, string name, long length)
|
||||
{
|
||||
var path = $"{ResourceFolder}\\{name}";
|
||||
Log.Debug($"Downloading file to {path} , id:{id}");
|
||||
if (!Directory.Exists(Directory.GetParent(path).FullName))
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
|
||||
if (FileAlreadyExists(ResourceFolder, name, length))
|
||||
{
|
||||
Log.Debug($"File already exists! canceling download:{name}");
|
||||
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
if (!name.EndsWith(".zip"))
|
||||
{
|
||||
Log.Error($"File download blocked! [{name}]");
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
InProgressDownloads.Add(id, new DownloadFile
|
||||
{
|
||||
FileID = id,
|
||||
FileName = name,
|
||||
FileLength = length,
|
||||
Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the file already exists and if the size correct otherwise delete this file
|
||||
/// </summary>
|
||||
/// <param name="folder"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="length"></param>
|
||||
/// <returns></returns>
|
||||
private static bool FileAlreadyExists(string folder, string name, long length)
|
||||
{
|
||||
var filePath = $"{folder}\\{name}";
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
if (new FileInfo(filePath).Length == length) return true;
|
||||
// Delete the file because the length is wrong (maybe the file was updated)
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Write(int id, byte[] chunk)
|
||||
{
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
if (InProgressDownloads.TryGetValue(id, out var file))
|
||||
file.Stream.Write(chunk, 0, chunk.Length);
|
||||
else
|
||||
Log.Trace($"Received unhandled file chunk:{id}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Complete(int id)
|
||||
{
|
||||
if (InProgressDownloads.TryGetValue(id, out var f))
|
||||
{
|
||||
InProgressDownloads.Remove(id);
|
||||
f.Dispose();
|
||||
Log.Info($"Download finished:{f.FileName}");
|
||||
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Download not found! {id}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cleanup()
|
||||
{
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
foreach (var file in InProgressDownloads.Values) file.Dispose();
|
||||
InProgressDownloads.Clear();
|
||||
}
|
||||
|
||||
_resources.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal class DownloadFile : IDisposable
|
||||
{
|
||||
public int FileID { get; set; }
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public long FileLength { get; set; }
|
||||
public long FileWritten { get; set; } = 0;
|
||||
public FileStream Stream { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Stream != null)
|
||||
{
|
||||
Stream.Flush();
|
||||
Stream.Close();
|
||||
Stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Timers;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class HolePunch
|
||||
{
|
||||
static HolePunch()
|
||||
{
|
||||
// Periodically send hole punch message as needed
|
||||
var timer = new Timer(1000);
|
||||
timer.Elapsed += DoPunch;
|
||||
timer.Enabled = true;
|
||||
}
|
||||
|
||||
private static void DoPunch(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Networking.IsOnServer) return;
|
||||
foreach (var p in PlayerList.Players.Values.ToArray())
|
||||
if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null ||
|
||||
p.Connection.Status == NetConnectionStatus.Disconnected))
|
||||
{
|
||||
Log.Trace(
|
||||
$"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.ID}");
|
||||
var msg = Networking.Peer.CreateMessage();
|
||||
new Packets.HolePunch
|
||||
{
|
||||
Puncher = LocalPlayerID,
|
||||
Status = p.HolePunchStatus
|
||||
}.Pack(msg);
|
||||
Networking.Peer.SendUnconnectedMessage(msg,
|
||||
new List<IPEndPoint> { p.InternalEndPoint, p.ExternalEndPoint });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(Packets.HolePunchInit p)
|
||||
{
|
||||
if (PlayerList.Players.TryGetValue(p.TargetID, out var player))
|
||||
{
|
||||
Log.Debug($"{p.TargetID},{player.Username} added to HolePunch target");
|
||||
player.InternalEndPoint = CoreUtils.StringToEndPoint(p.TargetInternal);
|
||||
player.ExternalEndPoint = CoreUtils.StringToEndPoint(p.TargetExternal);
|
||||
player.ConnectWhenPunched = p.Connect;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("No player with specified TargetID found for hole punching:" + p.TargetID);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Punched(Packets.HolePunch p, IPEndPoint from)
|
||||
{
|
||||
Log.Debug($"HolePunch message received from:{from}, status:{p.Status}");
|
||||
if (PlayerList.Players.TryGetValue(p.Puncher, out var puncher))
|
||||
{
|
||||
Log.Debug("Puncher identified as: " + puncher.Username);
|
||||
puncher.HolePunchStatus = (byte)(p.Status + 1);
|
||||
if (p.Status >= 3)
|
||||
{
|
||||
Log.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
|
||||
if (puncher.ConnectWhenPunched && (puncher.Connection == null ||
|
||||
puncher.Connection.Status == NetConnectionStatus.Disconnected))
|
||||
{
|
||||
Log.Debug("Connecting to peer: " + from);
|
||||
var msg = Networking.Peer.CreateMessage();
|
||||
new Packets.P2PConnect { ID = LocalPlayerID }.Pack(msg);
|
||||
puncher.Connection = Networking.Peer.Connect(from, msg);
|
||||
Networking.Peer.FlushSendQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GTA.UI;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
public static IPEndPoint _targetServerEP;
|
||||
|
||||
public static CoopPeer Peer;
|
||||
public static bool ShowNetworkInfo = false;
|
||||
public static Security Security = new();
|
||||
public static NetConnection ServerConnection;
|
||||
|
||||
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new();
|
||||
|
||||
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new();
|
||||
|
||||
internal static float SimulatedLatency = 0;
|
||||
|
||||
public static float Latency => ServerConnection.AverageRoundtripTime / 2;
|
||||
public static bool IsConnecting { get; private set; }
|
||||
public static bool IsOnServer => ServerConnection?.Status == NetConnectionStatus.Connected;
|
||||
|
||||
public static void ToggleConnection(string address, string username = null, string password = null,
|
||||
PublicKey publicKey = null)
|
||||
{
|
||||
CoopMenu.Menu.Visible = false;
|
||||
|
||||
if (IsConnecting)
|
||||
{
|
||||
_publicKeyReceived.Set();
|
||||
IsConnecting = false;
|
||||
API.QueueAction(() =>
|
||||
Notification.Show("Connection has been canceled"));
|
||||
|
||||
Peer.Shutdown("bye");
|
||||
}
|
||||
else if (IsOnServer)
|
||||
{
|
||||
Peer.Shutdown("bye");
|
||||
}
|
||||
else
|
||||
{
|
||||
IsConnecting = true;
|
||||
password ??= Settings.Password;
|
||||
username ??= Settings.Username;
|
||||
|
||||
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
|
||||
var config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
|
||||
{
|
||||
AutoFlushSendQueue = false,
|
||||
AcceptIncomingConnections = true,
|
||||
MaximumConnections = 32,
|
||||
PingInterval = 5
|
||||
};
|
||||
#if DEBUG
|
||||
config.SimulatedMinimumLatency = SimulatedLatency;
|
||||
config.SimulatedRandomLatency = 0;
|
||||
#endif
|
||||
|
||||
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
|
||||
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
|
||||
|
||||
var ip = new string[2];
|
||||
|
||||
var idx = address.LastIndexOf(':');
|
||||
if (idx != -1)
|
||||
{
|
||||
ip[0] = address.Substring(0, idx);
|
||||
ip[1] = address.Substring(idx + 1);
|
||||
}
|
||||
|
||||
if (ip.Length != 2) throw new Exception("Malformed URL");
|
||||
|
||||
PlayerList.Cleanup();
|
||||
EntityPool.AddPlayer();
|
||||
if (publicKey == null && !string.IsNullOrEmpty(password) && !CoopMenu.ShowPopUp("", "WARNING",
|
||||
"Server's IP can be spoofed when using direct connection, do you wish to continue?", "", true))
|
||||
{
|
||||
IsConnecting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadManager.CreateThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_targetServerEP = CoreUtils.StringToEndPoint(address);
|
||||
|
||||
// Ensure static constructor invocation
|
||||
DownloadManager.Cleanup();
|
||||
Peer = new CoopPeer(config,Log);
|
||||
Peer.OnMessageReceived += (s, m) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessMessage(m);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
};
|
||||
API.QueueAction(() => { Notification.Show("~y~Trying to connect..."); });
|
||||
CoopMenu._serverConnectItem.Enabled = false;
|
||||
Security.Regen();
|
||||
if (publicKey == null)
|
||||
{
|
||||
if (!GetServerPublicKey(ip[0], int.Parse(ip[1])))
|
||||
{
|
||||
CoopMenu._serverConnectItem.Enabled = true;
|
||||
throw new TimeoutException("Failed to retrive server's public key");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Security.SetServerPublicKey(publicKey.Modulus, publicKey.Exponent);
|
||||
}
|
||||
|
||||
// Send handshake packet
|
||||
var outgoingMessage = Peer.CreateMessage();
|
||||
var handshake = new Packets.Handshake
|
||||
{
|
||||
PedID = LocalPlayerID,
|
||||
Username = username,
|
||||
ModVersion = Main.ModVersion.ToString(),
|
||||
PasswordEncrypted = Security.Encrypt(password.GetBytes()),
|
||||
InternalEndPoint = new IPEndPoint(CoreUtils.GetLocalAddress(ip[0]), Peer.Port)
|
||||
};
|
||||
|
||||
Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted, out handshake.AesIVCrypted);
|
||||
handshake.Pack(outgoingMessage);
|
||||
ServerConnection = Peer.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Cannot connect to server: ", ex);
|
||||
API.QueueAction(() => Notification.Show("~r~Cannot connect to server: " + ex.Message));
|
||||
}
|
||||
|
||||
IsConnecting = false;
|
||||
}, "Connect");
|
||||
}
|
||||
}
|
||||
|
||||
private static int NewRequestID()
|
||||
{
|
||||
var ID = 0;
|
||||
while (ID == 0 || PendingResponses.ContainsKey(ID))
|
||||
{
|
||||
var rngBytes = new byte[4];
|
||||
|
||||
RandomNumberGenerator.Create().GetBytes(rngBytes);
|
||||
|
||||
// Convert the bytes into an integer
|
||||
ID = BitConverter.ToInt32(rngBytes, 0);
|
||||
}
|
||||
|
||||
return ID;
|
||||
}
|
||||
|
||||
#region -- PLAYER --
|
||||
|
||||
private static void PlayerConnect(Packets.PlayerConnect packet)
|
||||
{
|
||||
var p = new Player
|
||||
{
|
||||
ID = packet.PedID,
|
||||
Username = packet.Username
|
||||
};
|
||||
PlayerList.SetPlayer(packet.PedID, packet.Username);
|
||||
|
||||
Log.Debug($"player connected:{p.Username}");
|
||||
API.QueueAction(() =>
|
||||
Notification.Show($"~h~{p.Username}~h~ connected."));
|
||||
}
|
||||
|
||||
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
|
||||
{
|
||||
var player = PlayerList.GetPlayer(packet.PedID);
|
||||
if (player == null) return;
|
||||
PlayerList.RemovePlayer(packet.PedID);
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
EntityPool.RemoveAllFromPlayer(packet.PedID);
|
||||
Notification.Show($"~h~{player.Username}~h~ left.");
|
||||
});
|
||||
}
|
||||
|
||||
#endregion // -- PLAYER --
|
||||
|
||||
#region -- GET --
|
||||
|
||||
private static bool GetServerPublicKey(string host, int port, int timeout = 10000)
|
||||
{
|
||||
Security.ServerRSA = null;
|
||||
var msg = Peer.CreateMessage();
|
||||
new Packets.PublicKeyRequest().Pack(msg);
|
||||
Peer.SendUnconnectedMessage(msg, host, port);
|
||||
return _publicKeyReceived.WaitOne(timeout) && Security.ServerRSA != null;
|
||||
}
|
||||
|
||||
public static void GetResponse<T>(Packet request, Action<T> callback,
|
||||
ConnectionChannel channel = ConnectionChannel.RequestResponse) where T : Packet, new()
|
||||
{
|
||||
var received = new AutoResetEvent(false);
|
||||
var id = NewRequestID();
|
||||
PendingResponses.Add(id, (type, p) =>
|
||||
{
|
||||
var result = new T();
|
||||
result.Deserialize(p);
|
||||
callback(result);
|
||||
});
|
||||
var msg = Peer.CreateMessage();
|
||||
msg.Write((byte)PacketType.Request);
|
||||
msg.Write(id);
|
||||
request.Pack(msg);
|
||||
Peer.SendMessage(msg, ServerConnection, NetDeliveryMethod.ReliableOrdered, (int)channel);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using GTA;
|
||||
using GTA.UI;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
|
||||
|
||||
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
|
||||
|
||||
public static void ProcessMessage(NetIncomingMessage message)
|
||||
{
|
||||
if (message == null) return;
|
||||
var _recycle = true;
|
||||
switch (message.MessageType)
|
||||
{
|
||||
case NetIncomingMessageType.StatusChanged:
|
||||
var status = (NetConnectionStatus)message.ReadByte();
|
||||
var reason = message.ReadString();
|
||||
switch (status)
|
||||
{
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
if (message.SenderConnection == ServerConnection) CoopMenu.InitiateConnectionMenuSetting();
|
||||
break;
|
||||
case NetConnectionStatus.Connected:
|
||||
if (message.SenderConnection == ServerConnection)
|
||||
{
|
||||
var response = message.SenderConnection.RemoteHailMessage;
|
||||
if ((PacketType)response.ReadByte() != PacketType.HandshakeSuccess)
|
||||
throw new Exception("Invalid handshake response!");
|
||||
var p = new Packets.HandshakeSuccess();
|
||||
p.Deserialize(response);
|
||||
foreach (var player in p.Players) PlayerList.SetPlayer(player.ID, player.Username);
|
||||
Connected();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Self-initiated connection
|
||||
if (message.SenderConnection.RemoteHailMessage == null) return;
|
||||
|
||||
var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>();
|
||||
if (PlayerList.Players.TryGetValue(p.ID, out var player))
|
||||
{
|
||||
player.Connection = message.SenderConnection;
|
||||
Log.Debug($"Direct connection to {player.Username} established");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Info(
|
||||
$"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
|
||||
message.SenderConnection.Disconnect("eat poop");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case NetConnectionStatus.Disconnected:
|
||||
if (message.SenderConnection == ServerConnection) API.QueueAction(() => CleanUp(reason));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case NetIncomingMessageType.Data:
|
||||
{
|
||||
if (message.LengthBytes == 0) break;
|
||||
var packetType = PacketType.Unknown;
|
||||
try
|
||||
{
|
||||
// Get packet type
|
||||
packetType = (PacketType)message.ReadByte();
|
||||
switch (packetType)
|
||||
{
|
||||
case PacketType.Response:
|
||||
{
|
||||
var id = message.ReadInt32();
|
||||
if (PendingResponses.TryGetValue(id, out var callback))
|
||||
{
|
||||
callback((PacketType)message.ReadByte(), message);
|
||||
PendingResponses.Remove(id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case PacketType.Request:
|
||||
{
|
||||
var id = message.ReadInt32();
|
||||
var realType = (PacketType)message.ReadByte();
|
||||
if (RequestHandlers.TryGetValue(realType, out var handler))
|
||||
{
|
||||
var response = Peer.CreateMessage();
|
||||
response.Write((byte)PacketType.Response);
|
||||
response.Write(id);
|
||||
handler(message).Pack(response);
|
||||
Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered,
|
||||
message.SequenceChannel);
|
||||
Peer.FlushSendQueue();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("Did not find a request handler of type: " + realType);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
Notification.Show($"~r~~h~Packet Error {ex.Message}");
|
||||
return true;
|
||||
});
|
||||
Log.Error($"[{packetType}] {ex.Message}");
|
||||
Log.Error(ex);
|
||||
Peer.Shutdown($"Packet Error [{packetType}]");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case NetIncomingMessageType.UnconnectedData:
|
||||
{
|
||||
var packetType = (PacketType)message.ReadByte();
|
||||
switch (packetType)
|
||||
{
|
||||
case PacketType.HolePunch:
|
||||
{
|
||||
HolePunch.Punched(message.GetPacket<Packets.HolePunch>(), message.SenderEndPoint);
|
||||
break;
|
||||
}
|
||||
case PacketType.PublicKeyResponse:
|
||||
{
|
||||
if (message.SenderEndPoint.ToString() != _targetServerEP.ToString() || !IsConnecting) break;
|
||||
var packet = message.GetPacket<Packets.PublicKeyResponse>();
|
||||
Security.SetServerPublicKey(packet.Modulus, packet.Exponent);
|
||||
_publicKeyReceived.Set();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case NetIncomingMessageType.DebugMessage:
|
||||
case NetIncomingMessageType.ErrorMessage:
|
||||
case NetIncomingMessageType.WarningMessage:
|
||||
case NetIncomingMessageType.VerboseDebugMessage:
|
||||
Log.Trace(message.ReadString());
|
||||
break;
|
||||
}
|
||||
|
||||
if (_recycle) Peer.Recycle(message);
|
||||
}
|
||||
|
||||
private static void HandlePacket(PacketType packetType, NetIncomingMessage msg, NetConnection senderConnection,
|
||||
ref bool recycle)
|
||||
{
|
||||
switch (packetType)
|
||||
{
|
||||
case PacketType.HolePunchInit:
|
||||
HolePunch.Add(msg.GetPacket<Packets.HolePunchInit>());
|
||||
break;
|
||||
|
||||
case PacketType.PlayerConnect:
|
||||
PlayerConnect(msg.GetPacket<Packets.PlayerConnect>());
|
||||
break;
|
||||
|
||||
case PacketType.PlayerDisconnect:
|
||||
PlayerDisconnect(msg.GetPacket<Packets.PlayerDisconnect>());
|
||||
break;
|
||||
|
||||
case PacketType.PlayerInfoUpdate:
|
||||
PlayerList.UpdatePlayer(msg.GetPacket<Packets.PlayerInfoUpdate>());
|
||||
break;
|
||||
|
||||
case PacketType.VehicleSync:
|
||||
ReceivedPackets.VehicelPacket.Deserialize(msg);
|
||||
VehicleSync(ReceivedPackets.VehicelPacket);
|
||||
break;
|
||||
|
||||
case PacketType.PedSync:
|
||||
ReceivedPackets.PedPacket.Deserialize(msg);
|
||||
PedSync(ReceivedPackets.PedPacket);
|
||||
break;
|
||||
case PacketType.ProjectileSync:
|
||||
ReceivedPackets.ProjectilePacket.Deserialize(msg);
|
||||
ProjectileSync(ReceivedPackets.ProjectilePacket);
|
||||
break;
|
||||
|
||||
case PacketType.ChatMessage:
|
||||
{
|
||||
var packet = new Packets.ChatMessage(b => Security.Decrypt(b));
|
||||
packet.Deserialize(msg);
|
||||
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
MainChat.AddMessage(packet.Username, packet.Message);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.Voice:
|
||||
{
|
||||
if (Settings.Voice)
|
||||
{
|
||||
var packet = new Packets.Voice();
|
||||
packet.Deserialize(msg);
|
||||
|
||||
|
||||
var player = EntityPool.GetPedByID(packet.ID);
|
||||
player.IsSpeaking = true;
|
||||
player.LastSpeakingTime = Ticked;
|
||||
|
||||
Voice.AddVoiceData(packet.Buffer, packet.Recorded);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.CustomEvent:
|
||||
{
|
||||
var packet = new Packets.CustomEvent();
|
||||
if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
|
||||
{
|
||||
recycle = false;
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
packet.Deserialize(msg);
|
||||
API.Events.InvokeCustomEventReceived(packet);
|
||||
Peer.Recycle(msg);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
packet.Deserialize(msg);
|
||||
API.Events.InvokeCustomEventReceived(packet);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.FileTransferChunk:
|
||||
{
|
||||
var packet = new Packets.FileTransferChunk();
|
||||
packet.Deserialize(msg);
|
||||
DownloadManager.Write(packet.ID, packet.FileChunk);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (packetType.IsSyncEvent())
|
||||
{
|
||||
recycle = false;
|
||||
// Dispatch to script thread
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
SyncEvents.HandleEvent(packetType, msg);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PedSync(Packets.PedSync packet)
|
||||
{
|
||||
var c = EntityPool.GetPedByID(packet.ID);
|
||||
if (c == null)
|
||||
// Log.Debug($"Creating character for incoming sync:{packet.ID}");
|
||||
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
|
||||
var flags = packet.Flags;
|
||||
c.ID = packet.ID;
|
||||
c.OwnerID = packet.OwnerID;
|
||||
c.Health = packet.Health;
|
||||
c.Rotation = packet.Rotation;
|
||||
c.Velocity = packet.Velocity;
|
||||
c.Speed = packet.Speed;
|
||||
c.Flags = packet.Flags;
|
||||
c.Heading = packet.Heading;
|
||||
c.Position = packet.Position;
|
||||
if (c.IsRagdoll)
|
||||
{
|
||||
c.HeadPosition = packet.HeadPosition;
|
||||
c.RightFootPosition = packet.RightFootPosition;
|
||||
c.LeftFootPosition = packet.LeftFootPosition;
|
||||
}
|
||||
else if (c.Speed >= 4)
|
||||
{
|
||||
c.VehicleID = packet.VehicleID;
|
||||
c.Seat = packet.Seat;
|
||||
}
|
||||
|
||||
if (c.IsAiming) c.AimCoords = packet.AimCoords;
|
||||
bool full = packet.Flags.HasPedFlag(PedDataFlags.IsFullSync);
|
||||
if (full)
|
||||
{
|
||||
if (packet.Speed == 4)
|
||||
c.VehicleWeapon = packet.VehicleWeapon;
|
||||
c.CurrentWeapon = packet.CurrentWeapon;
|
||||
c.Clothes = packet.Clothes;
|
||||
c.WeaponComponents = packet.WeaponComponents;
|
||||
c.WeaponTint = packet.WeaponTint;
|
||||
c.Model = packet.ModelHash;
|
||||
c.BlipColor = packet.BlipColor;
|
||||
c.BlipSprite = packet.BlipSprite;
|
||||
c.BlipScale = packet.BlipScale;
|
||||
}
|
||||
c.SetLastSynced(full);
|
||||
}
|
||||
|
||||
private static void VehicleSync(Packets.VehicleSync packet)
|
||||
{
|
||||
var v = EntityPool.GetVehicleByID(packet.ID);
|
||||
if (v == null) EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
|
||||
if (v.IsLocal) return;
|
||||
v.ID = packet.ID;
|
||||
v.OwnerID = packet.OwnerID;
|
||||
v.Flags = packet.Flags;
|
||||
v.Position = packet.Position;
|
||||
v.Quaternion = packet.Quaternion;
|
||||
v.SteeringAngle = packet.SteeringAngle;
|
||||
v.ThrottlePower = packet.ThrottlePower;
|
||||
v.BrakePower = packet.BrakePower;
|
||||
v.Velocity = packet.Velocity;
|
||||
v.RotationVelocity = packet.RotationVelocity;
|
||||
v.DeluxoWingRatio = packet.DeluxoWingRatio;
|
||||
bool full = packet.Flags.HasVehFlag(VehicleDataFlags.IsFullSync);
|
||||
if (full)
|
||||
{
|
||||
v.DamageModel = packet.DamageModel;
|
||||
v.EngineHealth = packet.EngineHealth;
|
||||
v.Mods = packet.Mods;
|
||||
v.ToggleModsMask = packet.ToggleModsMask;
|
||||
v.Model = packet.ModelHash;
|
||||
v.Colors = packet.Colors;
|
||||
v.LandingGear = packet.LandingGear;
|
||||
v.RoofState = (VehicleRoofState)packet.RoofState;
|
||||
v.LockStatus = packet.LockStatus;
|
||||
v.RadioStation = packet.RadioStation;
|
||||
v.LicensePlate = packet.LicensePlate;
|
||||
v.Livery = packet.Livery;
|
||||
v.HeadlightColor = packet.HeadlightColor;
|
||||
v.ExtrasMask = packet.ExtrasMask;
|
||||
}
|
||||
v.SetLastSynced(full);
|
||||
}
|
||||
|
||||
private static void ProjectileSync(Packets.ProjectileSync packet)
|
||||
{
|
||||
var p = EntityPool.GetProjectileByID(packet.ID);
|
||||
if (p == null)
|
||||
{
|
||||
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) return;
|
||||
// Log.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
|
||||
EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID));
|
||||
}
|
||||
|
||||
p.Flags = packet.Flags;
|
||||
p.Position = packet.Position;
|
||||
p.Rotation = packet.Rotation;
|
||||
p.Velocity = packet.Velocity;
|
||||
p.WeaponHash = (WeaponHash)packet.WeaponHash;
|
||||
p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle)
|
||||
? (SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID)
|
||||
: EntityPool.GetPedByID(packet.ShooterID);
|
||||
p.SetLastSynced(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduce GC pressure by reusing frequently used packets
|
||||
/// </summary>
|
||||
private static class ReceivedPackets
|
||||
{
|
||||
public static readonly Packets.PedSync PedPacket = new Packets.PedSync();
|
||||
public static readonly Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
|
||||
public static readonly Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
public static int SyncInterval = 30;
|
||||
public static List<NetConnection> Targets = new List<NetConnection>();
|
||||
|
||||
public static void SendSync(Packet p, ConnectionChannel channel = ConnectionChannel.Default,
|
||||
NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
|
||||
{
|
||||
Peer.SendTo(p, Targets, channel, method);
|
||||
}
|
||||
|
||||
public static void SendPed(SyncedPed sp, bool full)
|
||||
{
|
||||
if (sp.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
|
||||
var ped = sp.MainPed;
|
||||
var p = SendPackets.PedPacket;
|
||||
p.ID = sp.ID;
|
||||
p.OwnerID = sp.OwnerID;
|
||||
p.Health = ped.Health;
|
||||
p.Rotation = ped.ReadRotation();
|
||||
p.Velocity = ped.ReadVelocity();
|
||||
p.Speed = ped.GetPedSpeed();
|
||||
p.Flags = ped.GetPedFlags();
|
||||
p.Heading = ped.Heading;
|
||||
if (p.Flags.HasPedFlag(PedDataFlags.IsAiming)) p.AimCoords = ped.GetAimCoord();
|
||||
if (p.Flags.HasPedFlag(PedDataFlags.IsRagdoll))
|
||||
{
|
||||
p.HeadPosition = ped.Bones[Bone.SkelHead].Position;
|
||||
p.RightFootPosition = ped.Bones[Bone.SkelRightFoot].Position;
|
||||
p.LeftFootPosition = ped.Bones[Bone.SkelLeftFoot].Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Seat sync
|
||||
if (p.Speed >= 4)
|
||||
{
|
||||
var veh = ped.CurrentVehicle?.GetSyncEntity() ??
|
||||
ped.VehicleTryingToEnter?.GetSyncEntity() ?? ped.LastVehicle?.GetSyncEntity();
|
||||
p.VehicleID = veh?.ID ?? 0;
|
||||
if (p.VehicleID == 0) Log.Error("Invalid vehicle");
|
||||
if (p.Speed == 5)
|
||||
p.Seat = ped.GetSeatTryingToEnter();
|
||||
else
|
||||
p.Seat = ped.SeatIndex;
|
||||
if (!veh.IsLocal && p.Speed == 4 && p.Seat == VehicleSeat.Driver)
|
||||
{
|
||||
veh.OwnerID = LocalPlayerID;
|
||||
SyncEvents.TriggerChangeOwner(veh.ID, LocalPlayerID);
|
||||
}
|
||||
}
|
||||
|
||||
p.Position = ped.ReadPosition();
|
||||
}
|
||||
|
||||
sp.LastSentStopWatch.Restart();
|
||||
if (full)
|
||||
{
|
||||
if (p.Speed == 4)
|
||||
p.VehicleWeapon = ped.VehicleWeapon;
|
||||
p.CurrentWeapon = ped.Weapons.Current.Hash;
|
||||
p.Flags |= PedDataFlags.IsFullSync;
|
||||
p.Clothes = ped.GetPedClothes();
|
||||
p.ModelHash = ped.Model.Hash;
|
||||
p.WeaponComponents = ped.Weapons.Current.GetWeaponComponents();
|
||||
p.WeaponTint = (byte)Call<int>(GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
|
||||
|
||||
Blip b;
|
||||
if (sp.IsPlayer)
|
||||
{
|
||||
p.BlipColor = API.Config.BlipColor;
|
||||
p.BlipSprite = API.Config.BlipSprite;
|
||||
p.BlipScale = API.Config.BlipScale;
|
||||
}
|
||||
else if ((b = ped.AttachedBlip) != null)
|
||||
{
|
||||
p.BlipColor = b.Color;
|
||||
p.BlipSprite = b.Sprite;
|
||||
|
||||
if (p.BlipSprite == BlipSprite.PoliceOfficer || p.BlipSprite == BlipSprite.PoliceOfficer2)
|
||||
p.BlipScale = 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.BlipColor = (BlipColor)255;
|
||||
}
|
||||
}
|
||||
|
||||
SendSync(p, ConnectionChannel.PedSync);
|
||||
}
|
||||
|
||||
public static void SendVehicle(SyncedVehicle v, bool full)
|
||||
{
|
||||
if (v.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
|
||||
var veh = v.MainVehicle;
|
||||
var packet = SendPackets.VehicelPacket;
|
||||
packet.ID = v.ID;
|
||||
packet.OwnerID = v.OwnerID;
|
||||
packet.Flags = v.GetVehicleFlags();
|
||||
packet.SteeringAngle = veh.SteeringAngle;
|
||||
packet.Position = veh.ReadPosition();
|
||||
packet.Velocity = veh.Velocity;
|
||||
packet.Quaternion = veh.ReadQuaternion();
|
||||
packet.RotationVelocity = veh.WorldRotationVelocity;
|
||||
packet.ThrottlePower = veh.ThrottlePower;
|
||||
packet.BrakePower = veh.BrakePower;
|
||||
v.LastSentStopWatch.Restart();
|
||||
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
|
||||
packet.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio();
|
||||
if (full)
|
||||
{
|
||||
byte primaryColor = 0;
|
||||
byte secondaryColor = 0;
|
||||
unsafe
|
||||
{
|
||||
Call<byte>(GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
|
||||
}
|
||||
|
||||
packet.Flags |= VehicleDataFlags.IsFullSync;
|
||||
packet.Colors = (primaryColor, secondaryColor);
|
||||
packet.DamageModel = veh.GetVehicleDamageModel();
|
||||
packet.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
|
||||
packet.RoofState = (byte)veh.RoofState;
|
||||
packet.Mods = v.GetVehicleMods(out packet.ToggleModsMask);
|
||||
packet.ModelHash = veh.Model.Hash;
|
||||
packet.EngineHealth = veh.EngineHealth;
|
||||
packet.LockStatus = veh.LockStatus;
|
||||
packet.LicensePlate = Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
|
||||
packet.Livery = Call<int>(GET_VEHICLE_LIVERY, veh);
|
||||
packet.HeadlightColor = (byte)Call<int>(GET_VEHICLE_XENON_LIGHT_COLOR_INDEX, veh);
|
||||
packet.ExtrasMask = v.GetVehicleExtras();
|
||||
packet.RadioStation = v.MainVehicle == LastV
|
||||
? Util.GetPlayerRadioIndex() : byte.MaxValue;
|
||||
if (packet.EngineHealth > v.LastEngineHealth) packet.Flags |= VehicleDataFlags.Repaired;
|
||||
v.LastEngineHealth = packet.EngineHealth;
|
||||
}
|
||||
|
||||
SendSync(packet, ConnectionChannel.VehicleSync);
|
||||
}
|
||||
|
||||
public static void SendProjectile(SyncedProjectile sp)
|
||||
{
|
||||
sp.ExtractData(ref SendPackets.ProjectilePacket);
|
||||
if (sp.MainProjectile.IsDead) EntityPool.RemoveProjectile(sp.ID, "Dead");
|
||||
SendSync(SendPackets.ProjectilePacket, ConnectionChannel.ProjectileSync);
|
||||
}
|
||||
|
||||
public static void SendChatMessage(string message)
|
||||
{
|
||||
Peer.SendTo(new Packets.ChatMessage(s => Security.Encrypt(s.GetBytes()))
|
||||
{ Username = Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat,
|
||||
NetDeliveryMethod.ReliableOrdered);
|
||||
Peer.FlushSendQueue();
|
||||
}
|
||||
|
||||
public static void SendVoiceMessage(byte[] buffer, int recorded)
|
||||
{
|
||||
SendSync(new Packets.Voice { ID = LocalPlayerID, Buffer = buffer, Recorded = recorded },
|
||||
ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduce GC pressure by reusing frequently used packets
|
||||
/// </summary>
|
||||
private static class SendPackets
|
||||
{
|
||||
public static readonly Packets.PedSync PedPacket = new Packets.PedSync();
|
||||
public static readonly Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
|
||||
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
|
||||
}
|
||||
|
||||
|
||||
#region SYNC EVENTS
|
||||
|
||||
public static void SendBullet(int ownerID, uint weapon, Vector3 end)
|
||||
{
|
||||
SendSync(new Packets.BulletShot
|
||||
{
|
||||
EndPosition = end,
|
||||
OwnerID = ownerID,
|
||||
WeaponHash = weapon
|
||||
}, ConnectionChannel.SyncEvents);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class Statistics
|
||||
{
|
||||
static Statistics()
|
||||
{
|
||||
ThreadManager.CreateThread(() =>
|
||||
{
|
||||
while (!IsUnloading)
|
||||
{
|
||||
var bu = Networking.Peer.Statistics.SentBytes;
|
||||
var bd = Networking.Peer.Statistics.ReceivedBytes;
|
||||
Thread.Sleep(1000);
|
||||
BytesUpPerSecond = Networking.Peer.Statistics.SentBytes - bu;
|
||||
BytesDownPerSecond = Networking.Peer.Statistics.ReceivedBytes - bd;
|
||||
}
|
||||
},"Statistics");
|
||||
}
|
||||
|
||||
public static int BytesDownPerSecond { get; private set; }
|
||||
public static int BytesUpPerSecond { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json.Serialization;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class PlayerList
|
||||
{
|
||||
private const float LEFT_POSITION = 0.122f;
|
||||
private const float RIGHT_POSITION = 0.9f;
|
||||
private static readonly Scaleform _mainScaleform = new Scaleform("mp_mm_card_freemode");
|
||||
private static ulong _lastUpdate = Util.GetTickCount64();
|
||||
|
||||
public static bool LeftAlign = true;
|
||||
public static Dictionary<int, Player> Players = new Dictionary<int, Player>();
|
||||
public static ulong Pressed { get; set; }
|
||||
|
||||
public static void Tick()
|
||||
{
|
||||
if (!Networking.IsOnServer) return;
|
||||
|
||||
if (Util.GetTickCount64() - _lastUpdate >= 1000) Update();
|
||||
|
||||
if (Util.GetTickCount64() - Pressed < 5000 && !MainChat.Focused
|
||||
#if !NON_INTERACTIVE
|
||||
&& !CoopMenu.MenuPool.AreAnyVisible
|
||||
#endif
|
||||
)
|
||||
Call(DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
|
||||
LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f,
|
||||
0.28f, 0.6f,
|
||||
255, 255, 255, 255, 0);
|
||||
}
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
_lastUpdate = Util.GetTickCount64();
|
||||
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var player in Players.Values)
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms",
|
||||
player.Username + (player.IsHost ? " (Host)" : ""), 116, 0, i - 1, "", "", 2, "", "", ' ');
|
||||
|
||||
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players");
|
||||
_mainScaleform.CallFunction("DISPLAY_VIEW");
|
||||
}
|
||||
|
||||
public static void SetPlayer(int id, string username, float latency = 0)
|
||||
{
|
||||
Log.Debug($"{id},{username},{latency}");
|
||||
if (Players.TryGetValue(id, out var p))
|
||||
{
|
||||
p.Username = username;
|
||||
p.ID = id;
|
||||
p._latencyToServer = latency;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = new Player { ID = id, Username = username, _latencyToServer = latency };
|
||||
Players.Add(id, p);
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdatePlayer(Packets.PlayerInfoUpdate packet)
|
||||
{
|
||||
var p = GetPlayer(packet.PedID);
|
||||
if (p != null)
|
||||
{
|
||||
p._latencyToServer = packet.Latency;
|
||||
p.Position = packet.Position;
|
||||
p.IsHost = packet.IsHost;
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
if (p.FakeBlip?.Exists() != true) p.FakeBlip = World.CreateBlip(p.Position);
|
||||
if (EntityPool.PedExists(p.ID))
|
||||
{
|
||||
p.FakeBlip.DisplayType = BlipDisplayType.NoDisplay;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.FakeBlip.Color = API.Config.BlipColor;
|
||||
p.FakeBlip.Scale = API.Config.BlipScale;
|
||||
p.FakeBlip.Sprite = API.Config.BlipSprite;
|
||||
p.FakeBlip.DisplayType = BlipDisplayType.Default;
|
||||
p.FakeBlip.Position = p.Position;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static Player GetPlayer(int id)
|
||||
{
|
||||
Players.TryGetValue(id, out var p);
|
||||
return p;
|
||||
}
|
||||
|
||||
public static Player GetPlayer(SyncedPed p)
|
||||
{
|
||||
var player = GetPlayer(p.ID);
|
||||
if (player != null) player.Character = p;
|
||||
return player;
|
||||
}
|
||||
|
||||
public static void RemovePlayer(int id)
|
||||
{
|
||||
if (Players.TryGetValue(id, out var player))
|
||||
{
|
||||
Players.Remove(id);
|
||||
API.QueueAction(() => player.FakeBlip?.Delete());
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cleanup()
|
||||
{
|
||||
foreach (var p in Players.Values.ToArray()) p.FakeBlip?.Delete();
|
||||
Players = new Dictionary<int, Player>();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Player
|
||||
{
|
||||
internal float _latencyToServer;
|
||||
internal bool ConnectWhenPunched { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Blip FakeBlip { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public Vector3 Position { get; internal set; }
|
||||
[JsonIgnore]
|
||||
public SyncedPed Character { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public NetConnection Connection { get; internal set; }
|
||||
|
||||
public byte HolePunchStatus { get; internal set; } = 1;
|
||||
public bool IsHost { get; internal set; }
|
||||
public string Username { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Universal ped ID.
|
||||
/// </summary>
|
||||
public int ID { get; internal set; }
|
||||
|
||||
public int EntityHandle => Character?.MainPed?.Handle ?? 0;
|
||||
|
||||
public IPEndPoint InternalEndPoint { get; internal set; }
|
||||
public IPEndPoint ExternalEndPoint { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Player round-trip time in seconds, will be the rtt to server if not using P2P connection.
|
||||
/// </summary>
|
||||
public float Ping => LocalPlayerID == ID ? Networking.Latency * 2 :
|
||||
HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2;
|
||||
|
||||
public float PacketTravelTime => HasDirectConnection
|
||||
? Connection.AverageRoundtripTime / 2
|
||||
: Networking.Latency + _latencyToServer;
|
||||
|
||||
public bool DisplayNameTag { get; set; } = true;
|
||||
public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Resources;
|
||||
|
||||
// General Information
|
||||
[assembly: AssemblyTitle("RageCoop.Client")]
|
||||
[assembly: AssemblyDescription("RageCoop.Client")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("RAGECOOP")]
|
||||
[assembly: AssemblyProduct("RageCoop.Client")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("RAGECOOP")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
|
||||
// Version information
|
||||
[assembly: AssemblyVersion("1.6.0.58")]
|
||||
[assembly: AssemblyFileVersion("1.6.0.58")]
|
||||
[assembly: NeutralResourcesLanguage( "en-US" )]
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<NoAotCompile>false</NoAotCompile>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<OutputType>Library</OutputType>
|
||||
<OutDir>..\..\bin\$(Configuration)\Client\Scripts</OutDir>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<AllowedReferenceRelatedFileExtensions>
|
||||
.dll
|
||||
.pdb
|
||||
</AllowedReferenceRelatedFileExtensions>
|
||||
<NoWarn>CS1591</NoWarn>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>False</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<TrimmerRootDescriptor Include="Trimming.xml" />
|
||||
<Compile Remove="GUI\**" />
|
||||
<EmbeddedResource Remove="GUI\**" />
|
||||
<None Remove="GUI\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Scripting\ClientScript.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="LemonUI.SHVDNC">
|
||||
<HintPath>..\..\libs\LemonUI.SHVDNC.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
|
||||
<ProjectReference Include="..\Scripting\RageCoop.Client.Scripting.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NAudio" Version="2.1.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.0" />
|
||||
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(SolutionDir)' != '*Undefined*' and '$(Configuration)' != 'API'">
|
||||
<Exec Command="xcopy "$(SolutionDir)Client\Data" "$(OutDir)..\Data" /i /s /y" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shared/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=util/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,140 @@
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.InteropServices;
|
||||
using static RageCoop.Core.Scripting.CustomEvents;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal static unsafe partial class API
|
||||
{
|
||||
static API()
|
||||
{
|
||||
RegisterFunctionPointers();
|
||||
}
|
||||
|
||||
static void RegisterFunctionPointers()
|
||||
{
|
||||
foreach (var method in typeof(API).GetMethods(BindingFlags.Public | BindingFlags.Static))
|
||||
{
|
||||
var attri = method.GetCustomAttribute<ApiExportAttribute>();
|
||||
if (attri == null) continue;
|
||||
attri.EntryPoint ??= method.Name;
|
||||
SHVDN.Core.SetPtr($"{typeof(API).FullName}.{attri.EntryPoint}", method.MethodHandle.GetFunctionPointer());
|
||||
Log.Debug($"Registered function pointer for {method.DeclaringType}.{method.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
[ThreadStatic]
|
||||
static string _lastResult;
|
||||
|
||||
[ApiExportAttribute(EntryPoint = nameof(GetLastResult))]
|
||||
public static int GetLastResult(char* buf, int cbBufSize)
|
||||
{
|
||||
if (_lastResult == null)
|
||||
return 0;
|
||||
|
||||
fixed (char* pErr = _lastResult)
|
||||
{
|
||||
var cbToCopy = sizeof(char) * (_lastResult.Length + 1);
|
||||
System.Buffer.MemoryCopy(pErr, buf, cbToCopy, Math.Min(cbToCopy, cbBufSize));
|
||||
if (cbToCopy > cbBufSize && cbBufSize > 0)
|
||||
{
|
||||
buf[cbBufSize / sizeof(char) - 1] = '\0'; // Always add null terminator
|
||||
}
|
||||
return _lastResult.Length;
|
||||
}
|
||||
}
|
||||
public static void SetLastResult(string msg) => _lastResult = msg;
|
||||
|
||||
[ApiExportAttribute(EntryPoint = nameof(SetLastResult))]
|
||||
public static void SetLastResult(char* msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetLastResult(msg == null ? null : new string(msg));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SHVDN.PInvoke.MessageBoxA(default, ex.ToString(), "error", default);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiExportAttribute(EntryPoint = nameof(GetEventHash))]
|
||||
public static CustomEventHash GetEventHash(char* name) => new string(name);
|
||||
|
||||
[ApiExportAttribute(EntryPoint = nameof(SendCustomEvent))]
|
||||
public static void SendCustomEvent(CustomEventFlags flags, int hash, byte* data, int cbData)
|
||||
{
|
||||
var payload = new byte[cbData];
|
||||
Marshal.Copy((IntPtr)data, payload, 0, cbData);
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent()
|
||||
{
|
||||
Flags = flags,
|
||||
Payload = payload,
|
||||
Hash = hash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
[ApiExportAttribute(EntryPoint = nameof(InvokeCommand))]
|
||||
public static int InvokeCommand(char* name, int argc, char** argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
var args = new string[argc];
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
args[i] = new(argv[i]);
|
||||
}
|
||||
_lastResult = _invokeCommand(new string(name), args);
|
||||
return _lastResult.Length;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
SetLastResult(ex.ToString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
[ApiExportAttribute(EntryPoint = nameof(GetLastResultLenInChars))]
|
||||
public static int GetLastResultLenInChars() => _lastResult?.Length ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Convert Entity ID to handle
|
||||
/// </summary>
|
||||
[ApiExportAttribute(EntryPoint = nameof(IdToHandle))]
|
||||
public static int IdToHandle(byte type, int id)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
T_ID_PROP => EntityPool.GetPropByID(id)?.MainProp?.Handle ?? 0,
|
||||
T_ID_PED => EntityPool.GetPedByID(id)?.MainPed?.Handle ?? 0,
|
||||
T_ID_VEH => EntityPool.GetVehicleByID(id)?.MainVehicle?.Handle ?? 0,
|
||||
T_ID_BLIP => EntityPool.GetBlipByID(id)?.Handle ?? 0,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueue a message to the main logger
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <param name="msg"></param>
|
||||
[ApiExportAttribute(EntryPoint = nameof(LogEnqueue))]
|
||||
public static void LogEnqueue(LogLevel level, char* msg)
|
||||
{
|
||||
Log.Enqueue((int)level, new(msg));
|
||||
}
|
||||
|
||||
class ApiExportAttribute : Attribute
|
||||
{
|
||||
public string EntryPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
internal class RemotingAttribute : Attribute
|
||||
{
|
||||
public bool GenBridge = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some local remoting implementation with json-based serialization, somewhar slow, but convenient
|
||||
/// </summary>
|
||||
internal static unsafe partial class API
|
||||
{
|
||||
static readonly MethodInfo[] _apiEntries =
|
||||
typeof(API).GetMethods(BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
static readonly Dictionary<string, MethodInfo> _commands =
|
||||
new(typeof(API).GetMethods().
|
||||
Where(md => md.CustomAttributes.
|
||||
Any(attri => attri.AttributeType == typeof(RemotingAttribute))).
|
||||
Select(x => new KeyValuePair<string, MethodInfo>(x.Name, x)));
|
||||
|
||||
static string _invokeCommand(string name, string[] argsJson)
|
||||
{
|
||||
if (_commands.TryGetValue(name, out var method))
|
||||
{
|
||||
var ps = method.GetParameters();
|
||||
|
||||
if (argsJson.Length != ps.Length)
|
||||
throw new ArgumentException($"Parameter count mismatch, expecting {ps.Length} parameters, got {argsJson.Length}", nameof(argsJson));
|
||||
|
||||
object[] args = new object[ps.Length];
|
||||
for (int i = 0; i < ps.Length; i++)
|
||||
{
|
||||
args[i] = JsonDeserialize(argsJson[i], ps[i].ParameterType);
|
||||
}
|
||||
var result = method.Invoke(null, args);
|
||||
if (method.ReturnType == typeof(void))
|
||||
{
|
||||
return "void";
|
||||
}
|
||||
return JsonSerialize(result);
|
||||
}
|
||||
throw new KeyNotFoundException($"Command {name} was not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
#undef DEBUG
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
[assembly: InternalsVisibleTo("CodeGen")] // For generating api bridge
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides vital functionality to interact with RAGECOOP
|
||||
/// </summary>
|
||||
internal static unsafe partial class API
|
||||
{
|
||||
#region INTERNAL
|
||||
|
||||
internal static Dictionary<int, List<CustomEventHandler>> CustomEventHandlers =
|
||||
new();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Client configuration, this will conflict with server-side config.
|
||||
/// </summary>
|
||||
public static class Config
|
||||
{
|
||||
/// <summary>
|
||||
/// Get or set local player's username, set won't be effective if already connected to a server.
|
||||
/// </summary>
|
||||
public static string Username
|
||||
{
|
||||
get => Settings.Username;
|
||||
set
|
||||
{
|
||||
if (Networking.IsOnServer || string.IsNullOrEmpty(value)) return;
|
||||
Settings.Username = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable automatic respawn for this player.
|
||||
/// </summary>
|
||||
public static bool EnableAutoRespawn { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set player's blip color
|
||||
/// </summary>
|
||||
public static BlipColor BlipColor { get; set; } = BlipColor.White;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set player's blip sprite
|
||||
/// </summary>
|
||||
public static BlipSprite BlipSprite { get; set; } = BlipSprite.Standard;
|
||||
|
||||
/// <summary>
|
||||
/// Get or set scale of player's blip
|
||||
/// </summary>
|
||||
public static float BlipScale { get; set; } = 1;
|
||||
|
||||
public static bool ShowPlayerNameTag
|
||||
{
|
||||
get => Settings.ShowPlayerNameTag;
|
||||
set
|
||||
{
|
||||
if (value == ShowPlayerNameTag) return;
|
||||
Settings.ShowPlayerNameTag = value;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base events for RageCoop
|
||||
/// </summary>
|
||||
public static class Events
|
||||
{
|
||||
/// <summary>
|
||||
/// The local player is dead
|
||||
/// </summary>
|
||||
public static event EmptyEvent OnPlayerDied;
|
||||
|
||||
/// <summary>
|
||||
/// A local vehicle is spawned
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedVehicle> OnVehicleSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// A local vehicle is deleted
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedVehicle> OnVehicleDeleted;
|
||||
|
||||
/// <summary>
|
||||
/// A local ped is spawned
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedPed> OnPedSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// A local ped is deleted
|
||||
/// </summary>
|
||||
public static event EventHandler<SyncedPed> OnPedDeleted;
|
||||
|
||||
#region DELEGATES
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public delegate void EmptyEvent();
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="args"></param>
|
||||
public delegate void CustomEvent(int hash, List<object> args);
|
||||
|
||||
#endregion
|
||||
|
||||
#region INVOKE
|
||||
|
||||
internal static void InvokeVehicleSpawned(SyncedVehicle v)
|
||||
{
|
||||
OnVehicleSpawned?.Invoke(null, v);
|
||||
}
|
||||
|
||||
internal static void InvokeVehicleDeleted(SyncedVehicle v)
|
||||
{
|
||||
OnVehicleDeleted?.Invoke(null, v);
|
||||
}
|
||||
|
||||
internal static void InvokePedSpawned(SyncedPed p)
|
||||
{
|
||||
OnPedSpawned?.Invoke(null, p);
|
||||
}
|
||||
|
||||
internal static void InvokePedDeleted(SyncedPed p)
|
||||
{
|
||||
OnPedDeleted?.Invoke(null, p);
|
||||
}
|
||||
|
||||
internal static void InvokePlayerDied()
|
||||
{
|
||||
OnPlayerDied?.Invoke();
|
||||
}
|
||||
|
||||
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
|
||||
{
|
||||
|
||||
// Log.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
|
||||
|
||||
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers))
|
||||
{
|
||||
fixed (byte* pData = p.Payload)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.Invoke(p.Hash, pData, p.Payload.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("InvokeCustomEvent", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region PROPERTIES
|
||||
|
||||
/// <summary>
|
||||
/// Get the local player's ID
|
||||
/// </summary>
|
||||
/// <returns>PlayerID</returns>
|
||||
public static int LocalPlayerID => Main.LocalPlayerID;
|
||||
|
||||
/// <summary>
|
||||
/// Check if player is connected to a server
|
||||
/// </summary>
|
||||
public static bool IsOnServer => Networking.IsOnServer;
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="System.Net.IPEndPoint" /> that the player is currently connected to, or null if not connected to
|
||||
/// the server
|
||||
/// </summary>
|
||||
public static IPEndPoint ServerEndPoint =>
|
||||
Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null;
|
||||
|
||||
/// <summary>
|
||||
/// Check if a RAGECOOP menu is visible
|
||||
/// </summary>
|
||||
public static bool IsMenuVisible => CoopMenu.MenuPool.AreAnyVisible;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP chat is visible
|
||||
/// </summary>
|
||||
public static bool IsChatFocused => MainChat.Focused;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP list of players is visible
|
||||
/// </summary>
|
||||
public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of RAGECOOP
|
||||
/// </summary>
|
||||
public static Version CurrentVersion => Main.ModVersion;
|
||||
|
||||
/// <summary>
|
||||
/// Get all players indexed by their ID
|
||||
/// </summary>
|
||||
public static Dictionary<int, Player> Players => new(PlayerList.Players);
|
||||
|
||||
#endregion
|
||||
|
||||
#region FUNCTIONS
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
public static void QueueAction(Action a)
|
||||
{
|
||||
WorldThread.QueueAction(a);
|
||||
}
|
||||
|
||||
public static void QueueActionAndWait(Action a, int timeout = 15000)
|
||||
{
|
||||
var done = new AutoResetEvent(false);
|
||||
Exception e = null;
|
||||
QueueAction(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
a();
|
||||
done.Set();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
e = ex;
|
||||
}
|
||||
});
|
||||
if (e != null)
|
||||
throw e;
|
||||
if (!done.WaitOne(timeout)) throw new TimeoutException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a">
|
||||
/// An <see cref="Func{T, TResult}" /> to be executed with a return value indicating whether it can be
|
||||
/// removed after execution.
|
||||
/// </param>
|
||||
public static void QueueAction(Func<bool> a)
|
||||
{
|
||||
WorldThread.QueueAction(a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send an event and data to the server.
|
||||
/// </summary>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">
|
||||
/// The objects conataing your data, see <see cref="CustomEventReceivedArgs" /> for a list of supported
|
||||
/// types
|
||||
/// </param>
|
||||
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
|
||||
=> SendCustomEvent(CustomEventFlags.None, eventHash, args);
|
||||
|
||||
/// <summary>
|
||||
/// Send an event and data to the server
|
||||
/// </summary>
|
||||
/// <param name="flags"></param>
|
||||
/// <param name="eventHash">An unique identifier of the event</param>
|
||||
/// <param name="args">
|
||||
/// The objects conataing your data, see <see cref="CustomEventReceivedArgs" /> for a list of supported
|
||||
/// types
|
||||
/// </param>
|
||||
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args)
|
||||
{
|
||||
var writer = GetWriter();
|
||||
CustomEvents.WriteObjects(writer, args);
|
||||
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
|
||||
{
|
||||
|
||||
Payload = writer.ToByteArray(writer.Position),
|
||||
Hash = eventHash
|
||||
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
|
||||
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
|
||||
/// </summary>
|
||||
/// <param name="hash">
|
||||
/// An unique identifier of the event
|
||||
/// </param>
|
||||
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
|
||||
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
|
||||
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static void RequestSharedFile(string name, Action<string> callback)
|
||||
{
|
||||
EventHandler<string> handler = (s, e) =>
|
||||
{
|
||||
if (e.EndsWith(name)) callback(e);
|
||||
};
|
||||
DownloadManager.DownloadCompleted += handler;
|
||||
Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest
|
||||
{
|
||||
Name = name
|
||||
},
|
||||
p =>
|
||||
{
|
||||
if (p.Response != FileResponse.Loaded)
|
||||
{
|
||||
DownloadManager.DownloadCompleted -= handler;
|
||||
throw new ArgumentException("Requested file was not found on the server: " + name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Connect to a server
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
|
||||
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
|
||||
[Remoting]
|
||||
public static void Connect(string address)
|
||||
{
|
||||
if (Networking.IsOnServer || Networking.IsConnecting)
|
||||
throw new InvalidOperationException("Cannot connect to server when another connection is active");
|
||||
Networking.ToggleConnection(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from current server or cancel the connection attempt.
|
||||
/// </summary>
|
||||
[Remoting]
|
||||
public static void Disconnect()
|
||||
{
|
||||
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all servers from master server address
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Remoting]
|
||||
public static List<ServerInfo> ListServers()
|
||||
{
|
||||
return JsonDeserialize<List<ServerInfo>>(
|
||||
HttpHelper.DownloadString(Settings.MasterServer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a local chat message to this player
|
||||
/// </summary>
|
||||
/// <param name="from">Name of the sender</param>
|
||||
/// <param name="message">The player's message</param>
|
||||
[Remoting]
|
||||
public static void LocalChatMessage(string from, string message)
|
||||
{
|
||||
MainChat.AddMessage(from, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message or command to server/other players
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
[Remoting]
|
||||
public static void SendChatMessage(string message)
|
||||
{
|
||||
if (!IsOnServer)
|
||||
throw new InvalidOperationException("Not on server");
|
||||
Networking.SendChatMessage(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ClientResource"/> with this name
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
[Remoting]
|
||||
public static ClientResource GetResource(string name)
|
||||
{
|
||||
if (MainRes.LoadedResources.TryGetValue(name, out var resource))
|
||||
return resource;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get <see cref="ClientResource"/> that contains the specified file or directory
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Remoting]
|
||||
public static ClientResource GetResourceFromPath(string path)
|
||||
{
|
||||
path = Path.GetFullPath(path).ToLower();
|
||||
foreach (var res in MainRes.LoadedResources.Values)
|
||||
{
|
||||
if (res.ScriptsDirectory.ToLower() == path || res.Files.Any(file => file.Value.FullPath.ToLower() == path))
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
[Remoting(GenBridge = false)]
|
||||
public static object GetProperty(string name)
|
||||
=> typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
|
||||
|
||||
[Remoting(GenBridge = false)]
|
||||
public static void SetProperty(string name, string jsonVal)
|
||||
{
|
||||
var prop = typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public); ;
|
||||
if (prop == null)
|
||||
throw new KeyNotFoundException($"Property {name} was not found");
|
||||
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
|
||||
}
|
||||
|
||||
[Remoting]
|
||||
public static object GetConfig(string name)
|
||||
=> typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
|
||||
|
||||
[Remoting]
|
||||
public static void SetConfig(string name, string jsonVal)
|
||||
{
|
||||
var prop = typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public);
|
||||
if (prop == null)
|
||||
throw new KeyNotFoundException($"Property {name} was not found");
|
||||
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
|
||||
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
|
||||
/// </summary>
|
||||
/// <param name="hash">
|
||||
/// An unique identifier of the event
|
||||
/// </param>
|
||||
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
|
||||
[Remoting]
|
||||
public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler)
|
||||
{
|
||||
if (handler.Directory == default)
|
||||
throw new ArgumentException("Script directory not specified");
|
||||
|
||||
if (handler.FunctionPtr == default)
|
||||
throw new ArgumentException("Function pointer not specified");
|
||||
|
||||
lock (CustomEventHandlers)
|
||||
{
|
||||
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
|
||||
CustomEventHandlers.Add(hash, handlers = new());
|
||||
handlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal static class BaseScript
|
||||
{
|
||||
private static bool _isHost;
|
||||
|
||||
public static void OnStart()
|
||||
{
|
||||
API.Events.OnPedDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
|
||||
API.Events.OnVehicleDeleted += (s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); };
|
||||
API.Events.OnPlayerDied += () => { API.SendCustomEvent(CustomEvents.OnPlayerDied); };
|
||||
|
||||
API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn, SetAutoRespawn);
|
||||
API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag, SetDisplayNameTag);
|
||||
API.RegisterCustomEventHandler(CustomEvents.NativeCall, NativeCall);
|
||||
API.RegisterCustomEventHandler(CustomEvents.ServerPropSync, ServerObjectSync);
|
||||
API.RegisterCustomEventHandler(CustomEvents.DeleteServerProp, DeleteServerProp);
|
||||
API.RegisterCustomEventHandler(CustomEvents.DeleteEntity, DeleteEntity);
|
||||
API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag, SetNameTag);
|
||||
API.RegisterCustomEventHandler(CustomEvents.ServerBlipSync, ServerBlipSync);
|
||||
API.RegisterCustomEventHandler(CustomEvents.DeleteServerBlip, DeleteServerBlip);
|
||||
API.RegisterCustomEventHandler(CustomEvents.CreateVehicle, CreateVehicle);
|
||||
API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip);
|
||||
API.RegisterCustomEventHandler(CustomEvents.IsHost, e => { _isHost = (bool)e.Args[0]; });
|
||||
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
|
||||
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied,
|
||||
e => { Notification.Show($"~h~{e.Args[0]}~h~ died."); });
|
||||
ThreadManager.CreateThread(() =>
|
||||
{
|
||||
while (!IsUnloading)
|
||||
{
|
||||
if (Networking.IsOnServer && _isHost)
|
||||
API.QueueAction(() =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var time = World.CurrentTimeOfDay;
|
||||
var weather1 = default(int);
|
||||
var weather2 = default(int);
|
||||
var percent2 = default(float);
|
||||
Call(GET_CURR_WEATHER_STATE, &weather1, &weather2, &percent2);
|
||||
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes,
|
||||
time.Seconds, weather1, weather2, percent2);
|
||||
}
|
||||
});
|
||||
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}, "BaseScript");
|
||||
}
|
||||
|
||||
private static void WeatherTimeSync(CustomEventReceivedArgs e)
|
||||
{
|
||||
World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
|
||||
Call(SET_CURR_WEATHER_STATE, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
|
||||
}
|
||||
|
||||
private static void SetDisplayNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null) p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
|
||||
private static void UpdatePedBlip(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = Entity.FromHandle((int)e.Args[0]);
|
||||
if (p == null) return;
|
||||
if (p.Handle == Game.Player.Character.Handle)
|
||||
{
|
||||
API.Config.BlipColor = (BlipColor)(byte)e.Args[1];
|
||||
API.Config.BlipSprite = (BlipSprite)(ushort)e.Args[2];
|
||||
API.Config.BlipScale = (float)e.Args[3];
|
||||
}
|
||||
else
|
||||
{
|
||||
var b = p.AttachedBlip;
|
||||
if (b == null) b = p.AddBlip();
|
||||
b.Color = (BlipColor)(byte)e.Args[1];
|
||||
b.Sprite = (BlipSprite)(ushort)e.Args[2];
|
||||
b.Scale = (float)e.Args[3];
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateVehicle(CustomEventReceivedArgs e)
|
||||
{
|
||||
var vehicleModel = (Model)e.Args[1];
|
||||
vehicleModel.Request(1000);
|
||||
Vehicle veh;
|
||||
while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
|
||||
Thread.Sleep(10);
|
||||
veh.CanPretendOccupants = false;
|
||||
var v = new SyncedVehicle
|
||||
{
|
||||
ID = (int)e.Args[0],
|
||||
MainVehicle = veh,
|
||||
OwnerID = LocalPlayerID
|
||||
};
|
||||
EntityPool.Add(v);
|
||||
}
|
||||
|
||||
private static void DeleteServerBlip(CustomEventReceivedArgs e)
|
||||
{
|
||||
if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip))
|
||||
{
|
||||
EntityPool.ServerBlips.Remove((int)e.Args[0]);
|
||||
blip?.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ServerBlipSync(CustomEventReceivedArgs obj)
|
||||
{
|
||||
var id = (int)obj.Args[0];
|
||||
var sprite = (BlipSprite)(ushort)obj.Args[1];
|
||||
var color = (BlipColor)(byte)obj.Args[2];
|
||||
var scale = (float)obj.Args[3];
|
||||
var pos = (Vector3)obj.Args[4];
|
||||
var rot = (int)obj.Args[5];
|
||||
var name = (string)obj.Args[6];
|
||||
if (!EntityPool.ServerBlips.TryGetValue(id, out var blip))
|
||||
EntityPool.ServerBlips.Add(id, blip = World.CreateBlip(pos));
|
||||
blip.Sprite = sprite;
|
||||
blip.Color = color;
|
||||
blip.Scale = scale;
|
||||
blip.Position = pos;
|
||||
blip.Rotation = rot;
|
||||
blip.Name = name;
|
||||
}
|
||||
|
||||
|
||||
private static void DeleteEntity(CustomEventReceivedArgs e)
|
||||
{
|
||||
Entity.FromHandle((int)e.Args[0])?.Delete();
|
||||
}
|
||||
|
||||
private static void SetNameTag(CustomEventReceivedArgs e)
|
||||
{
|
||||
var p = PlayerList.GetPlayer((int)e.Args[0]);
|
||||
if (p != null) p.DisplayNameTag = (bool)e.Args[1];
|
||||
}
|
||||
|
||||
private static void SetAutoRespawn(CustomEventReceivedArgs args)
|
||||
{
|
||||
API.Config.EnableAutoRespawn = (bool)args.Args[0];
|
||||
}
|
||||
|
||||
private static void DeleteServerProp(CustomEventReceivedArgs e)
|
||||
{
|
||||
var id = (int)e.Args[0];
|
||||
if (EntityPool.ServerProps.TryGetValue(id, out var prop))
|
||||
{
|
||||
EntityPool.ServerProps.Remove(id);
|
||||
prop?.MainProp?.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ServerObjectSync(CustomEventReceivedArgs e)
|
||||
{
|
||||
SyncedProp prop;
|
||||
var id = (int)e.Args[0];
|
||||
lock (EntityPool.PropsLock)
|
||||
{
|
||||
if (!EntityPool.ServerProps.TryGetValue(id, out prop))
|
||||
EntityPool.ServerProps.Add(id, prop = new SyncedProp(id));
|
||||
}
|
||||
|
||||
prop.LastSynced = Ticked + 1;
|
||||
prop.Model = (Model)e.Args[1];
|
||||
prop.Position = (Vector3)e.Args[2];
|
||||
prop.Rotation = (Vector3)e.Args[3];
|
||||
prop.Model.Request(1000);
|
||||
prop.Update();
|
||||
}
|
||||
|
||||
private static void NativeCall(CustomEventReceivedArgs e)
|
||||
{
|
||||
var arguments = new List<InputArgument>();
|
||||
int i;
|
||||
var ty = (byte)e.Args[0];
|
||||
var returnType = (TypeCode)ty;
|
||||
i = returnType == TypeCode.Empty ? 1 : 2;
|
||||
var hash = (Hash)e.Args[i++];
|
||||
for (; i < e.Args.Length; i++) arguments.Add(GetInputArgument(e.Args[i]));
|
||||
|
||||
if (returnType == TypeCode.Empty)
|
||||
{
|
||||
Call(hash, arguments.ToArray());
|
||||
return;
|
||||
}
|
||||
|
||||
var id = (int)e.Args[1];
|
||||
|
||||
|
||||
switch (returnType)
|
||||
{
|
||||
case TypeCode.Boolean:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<bool>(hash, arguments.ToArray()));
|
||||
break;
|
||||
case TypeCode.Byte:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<byte>(hash, arguments.ToArray()));
|
||||
break;
|
||||
case TypeCode.Char:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<char>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Single:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<float>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Double:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<double>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int16:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<short>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int32:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id, Call<int>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.Int64:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<long>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.String:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<string>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt16:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<ushort>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt32:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<uint>(hash, arguments.ToArray()));
|
||||
break;
|
||||
|
||||
case TypeCode.UInt64:
|
||||
API.SendCustomEvent(CustomEvents.NativeResponse, id,
|
||||
Call<ulong>(hash, arguments.ToArray()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static InputArgument GetInputArgument(object obj)
|
||||
{
|
||||
// Implicit conversion
|
||||
switch (obj)
|
||||
{
|
||||
case byte stuff:
|
||||
return stuff;
|
||||
case short stuff:
|
||||
return stuff;
|
||||
case ushort stuff:
|
||||
return stuff;
|
||||
case int stuff:
|
||||
return stuff;
|
||||
case uint stuff:
|
||||
return stuff;
|
||||
case long stuff:
|
||||
return stuff;
|
||||
case ulong stuff:
|
||||
return stuff;
|
||||
case float stuff:
|
||||
return stuff;
|
||||
case bool stuff:
|
||||
return stuff;
|
||||
case string stuff:
|
||||
return stuff;
|
||||
default:
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using GTA;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
public abstract class ClientScript : Script
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="ResourceFile" /> instance where this script is loaded from.
|
||||
/// </summary>
|
||||
public ResourceFile CurrentFile { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ClientResource" /> that this script belongs to.
|
||||
/// </summary>
|
||||
public ClientResource CurrentResource { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
|
||||
/// </summary>
|
||||
public Logger Logger => CurrentResource.Logger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using SHVDN;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal class Resources
|
||||
{
|
||||
public static string TempPath = Path.Combine(Path.GetTempPath(), "RageCoop");
|
||||
|
||||
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new();
|
||||
|
||||
static Resources()
|
||||
{
|
||||
if (Directory.Exists(TempPath))
|
||||
try
|
||||
{
|
||||
Directory.Delete(TempPath, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
TempPath = CoreUtils.GetTempDirectory(TempPath);
|
||||
Directory.CreateDirectory(TempPath);
|
||||
}
|
||||
|
||||
|
||||
public unsafe void Load(string path, string[] zips)
|
||||
{
|
||||
LoadedResources.Clear();
|
||||
foreach (var zip in zips)
|
||||
{
|
||||
var zipPath = Path.Combine(path, zip);
|
||||
Log?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
|
||||
Unpack(zipPath, Path.Combine(path, "Data"));
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
var dirs = LoadedResources.Values.Select(x => x.ScriptsDirectory);
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
SHVDN.Core.RuntimeController.RequestUnload(dir);
|
||||
}
|
||||
|
||||
// Unregister associated handler
|
||||
foreach (var handlers in API.CustomEventHandlers.Values)
|
||||
{
|
||||
foreach (var handler in handlers.ToArray())
|
||||
{
|
||||
if (dirs.Contains(handler.Directory, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
handlers.Remove(handler);
|
||||
Log.Debug($"Unregistered handler from script directory {handler.Directory}");
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadedResources.Clear();
|
||||
}
|
||||
|
||||
private unsafe ClientResource Unpack(string zipPath, string dataFolderRoot)
|
||||
{
|
||||
var r = new ClientResource
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(zipPath),
|
||||
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
|
||||
ScriptsDirectory = Path.Combine(TempPath, Path.GetFileNameWithoutExtension(zipPath))
|
||||
};
|
||||
Directory.CreateDirectory(r.DataFolder);
|
||||
var scriptsDir = r.ScriptsDirectory;
|
||||
if (Directory.Exists(scriptsDir))
|
||||
Directory.Delete(scriptsDir, true);
|
||||
else if (File.Exists(scriptsDir)) File.Delete(scriptsDir);
|
||||
Directory.CreateDirectory(scriptsDir);
|
||||
|
||||
new FastZip().ExtractZip(zipPath, scriptsDir, null);
|
||||
|
||||
|
||||
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
|
||||
r.Files.Add(dir, new ClientFile
|
||||
{
|
||||
IsDirectory = true,
|
||||
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/'),
|
||||
FullPath = dir
|
||||
});
|
||||
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
|
||||
var rfile = new ClientFile
|
||||
{
|
||||
IsDirectory = false,
|
||||
Name = relativeName,
|
||||
FullPath = file
|
||||
};
|
||||
r.Files.Add(relativeName, rfile);
|
||||
}
|
||||
SHVDN.Core.RuntimeController.RequestLoad(scriptsDir);
|
||||
LoadedResources.TryAdd(r.Name, r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.IO;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class Security
|
||||
{
|
||||
public RSA ServerRSA { get; set; }
|
||||
public Aes ClientAes { get; set; }=Aes.Create();
|
||||
private Logger Logger;
|
||||
public Security(Logger logger)
|
||||
|
||||
public Security()
|
||||
{
|
||||
Logger = logger;
|
||||
ClientAes.GenerateKey();
|
||||
ClientAes.GenerateIV();
|
||||
}
|
||||
public void GetSymmetricKeysCrypted(out byte[] cryptedKey,out byte[] cryptedIV)
|
||||
|
||||
public RSA ServerRSA { get; set; }
|
||||
public Aes ClientAes { get; set; } = Aes.Create();
|
||||
|
||||
public void GetSymmetricKeysCrypted(out byte[] cryptedKey, out byte[] cryptedIV)
|
||||
{
|
||||
// Logger?.Debug($"Aes.Key:{ClientAes.Key.Dump()}, Aes.IV:{ClientAes.IV.Dump()}");
|
||||
cryptedKey =ServerRSA.Encrypt(ClientAes.Key, RSAEncryptionPadding.Pkcs1);
|
||||
cryptedIV =ServerRSA.Encrypt(ClientAes.IV,RSAEncryptionPadding.Pkcs1);
|
||||
cryptedKey = ServerRSA.Encrypt(ClientAes.Key, RSAEncryptionPadding.Pkcs1);
|
||||
cryptedIV = ServerRSA.Encrypt(ClientAes.IV, RSAEncryptionPadding.Pkcs1);
|
||||
}
|
||||
|
||||
public byte[] Encrypt(byte[] data)
|
||||
{
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateEncryptor(), CryptoStreamMode.Read).ReadToEnd();
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateEncryptor(), CryptoStreamMode.Read)
|
||||
.ReadToEnd();
|
||||
}
|
||||
public void SetServerPublicKey(byte[] modulus,byte[] exponent)
|
||||
|
||||
public byte[] Decrypt(byte[] data)
|
||||
{
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateDecryptor(), CryptoStreamMode.Read)
|
||||
.ReadToEnd();
|
||||
}
|
||||
|
||||
public void SetServerPublicKey(byte[] modulus, byte[] exponent)
|
||||
{
|
||||
var para = new RSAParameters();
|
||||
para.Modulus = modulus;
|
||||
para.Exponent = exponent;
|
||||
ServerRSA=RSA.Create(para);
|
||||
ServerRSA = RSA.Create(para);
|
||||
}
|
||||
|
||||
public void Regen()
|
||||
{
|
||||
ClientAes = Aes.Create();
|
||||
ClientAes.GenerateKey();
|
||||
ClientAes.GenerateIV();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
global using GTA;
|
||||
global using GTA.Native;
|
||||
global using static GTA.Native.Function;
|
||||
global using static GTA.Native.Hash;
|
||||
global using static RageCoop.Client.Shared;
|
||||
global using static RageCoop.Client.Main;
|
||||
global using Console = GTA.Console;
|
||||
global using static RageCoop.Core.Shared;
|
||||
using System.IO;
|
||||
using System;
|
||||
using SHVDN;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
class DebugTunableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
internal static class Shared
|
||||
{
|
||||
private static unsafe string GetBasePath()
|
||||
{
|
||||
using var fs = File.OpenRead(Instance.FilePath);
|
||||
var buf = stackalloc char[1024];
|
||||
GetFinalPathNameByHandleW(fs.SafeFileHandle.DangerousGetHandle(), buf, 1024, 0);
|
||||
ErrorCheck32();
|
||||
var scriptDir = Directory.GetParent(Marshal.PtrToStringUni((IntPtr)buf)).FullName;
|
||||
if (Path.GetFileName(scriptDir).ToLower() != "scripts")
|
||||
throw new Exception("Unexpected script location");
|
||||
|
||||
var result = Directory.GetParent(scriptDir).FullName;
|
||||
Logger.Debug($"Base path is: {result}");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string BasePath = GetBasePath();
|
||||
public static string DataPath = Path.Combine(BasePath, "Data");
|
||||
public static string SettingsPath = Path.Combine(DataPath, "Setting.json");
|
||||
|
||||
public static string VehicleWeaponDataPath = Path.Combine(DataPath, "VehicleWeapons.json");
|
||||
public static string WeaponFixDataPath = Path.Combine(DataPath, "WeaponFixes.json");
|
||||
public static string WeaponInfoDataPath = Path.Combine(DataPath, "Weapons.json");
|
||||
public static string AnimationsDataPath = Path.Combine(DataPath, "Animations.json");
|
||||
|
||||
public static string CefSubProcessPath = Path.Combine(BasePath, "SubProcess", "RageCoop.Client.CefHost.exe");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
public partial class SyncedPed
|
||||
{
|
||||
private void DisplaySpeaking(bool speaking)
|
||||
{
|
||||
if (!MainPed.IsHuman)
|
||||
return;
|
||||
|
||||
if (speaking)
|
||||
{
|
||||
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mic_chatter", "mp_facial");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (MainPed.Gender)
|
||||
{
|
||||
case Gender.Male:
|
||||
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
|
||||
"facials@gen_male@variations@normal");
|
||||
break;
|
||||
case Gender.Female:
|
||||
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
|
||||
"facials@gen_female@variations@normal");
|
||||
break;
|
||||
default:
|
||||
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
|
||||
"facials@mime@variations@normal");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayInCover()
|
||||
{
|
||||
var ourAnim = GetCoverAnim();
|
||||
var animDict = GetCoverIdleAnimDict();
|
||||
|
||||
if (ourAnim != null && animDict != null)
|
||||
{
|
||||
var flag = AnimationFlags.Loop;
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
|
||||
{
|
||||
if (LoadAnim(animDict) == null) return;
|
||||
|
||||
MainPed.Task.ClearAll();
|
||||
Call(TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal string GetCoverAnim()
|
||||
{
|
||||
if (IsInCover)
|
||||
{
|
||||
if (IsBlindFiring)
|
||||
{
|
||||
if (IsInCover)
|
||||
return IsInCoverFacingLeft ? "blindfire_low_l_aim_med" : "blindfire_low_r_aim_med";
|
||||
return IsInCoverFacingLeft ? "blindfire_hi_l_aim_med" : "blindfire_hi_r_aim_med";
|
||||
}
|
||||
|
||||
return IsInCoverFacingLeft ? "idle_l_corner" : "idle_r_corner";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal string GetCoverIdleAnimDict()
|
||||
{
|
||||
if (!IsInCover) return "";
|
||||
var altitude = IsInLowCover ? "low" : "high";
|
||||
|
||||
var hands = GetWeaponHandsHeld((uint)CurrentWeapon);
|
||||
if (IsBlindFiring)
|
||||
{
|
||||
if (hands == 1) return "cover@weapon@1h";
|
||||
if (hands == 2 || hands == 5) return "cover@weapon@2h";
|
||||
}
|
||||
|
||||
if (hands == 1) return "cover@idles@1h@" + altitude + "@_a";
|
||||
if (hands == 2 || hands == 5) return "cover@idles@2h@" + altitude + "@_a";
|
||||
if (hands == 3 || hands == 4 || hands == 0) return "cover@idles@unarmed@" + altitude + "@_a";
|
||||
return "";
|
||||
}
|
||||
|
||||
internal int GetWeaponHandsHeld(uint weapon)
|
||||
{
|
||||
switch (weapon)
|
||||
{
|
||||
case (uint)WeaponHash.Unarmed:
|
||||
return 0;
|
||||
|
||||
case (uint)WeaponHash.RPG:
|
||||
case (uint)WeaponHash.HomingLauncher:
|
||||
case (uint)WeaponHash.Firework:
|
||||
return 5;
|
||||
|
||||
case (uint)WeaponHash.Minigun:
|
||||
return 5;
|
||||
|
||||
case (uint)WeaponHash.GolfClub:
|
||||
case (uint)WeaponHash.PoolCue:
|
||||
case (uint)WeaponHash.Bat:
|
||||
return 4;
|
||||
|
||||
case (uint)WeaponHash.Knife:
|
||||
case (uint)WeaponHash.Nightstick:
|
||||
case (uint)WeaponHash.Hammer:
|
||||
case (uint)WeaponHash.Crowbar:
|
||||
case (uint)WeaponHash.Wrench:
|
||||
case (uint)WeaponHash.BattleAxe:
|
||||
case (uint)WeaponHash.Dagger:
|
||||
case (uint)WeaponHash.Hatchet:
|
||||
case (uint)WeaponHash.KnuckleDuster:
|
||||
case unchecked((uint)-581044007):
|
||||
case unchecked((uint)-102323637):
|
||||
case unchecked((uint)-538741184):
|
||||
return 3;
|
||||
|
||||
case unchecked((uint)-1357824103):
|
||||
case unchecked((uint)-1074790547):
|
||||
case 2132975508:
|
||||
case unchecked((uint)-2084633992):
|
||||
case unchecked((uint)-952879014):
|
||||
case 100416529:
|
||||
case (uint)WeaponHash.Gusenberg:
|
||||
case (uint)WeaponHash.MG:
|
||||
case (uint)WeaponHash.CombatMG:
|
||||
case (uint)WeaponHash.CombatPDW:
|
||||
case (uint)WeaponHash.AssaultSMG:
|
||||
case (uint)WeaponHash.SMG:
|
||||
case (uint)WeaponHash.HeavySniper:
|
||||
case (uint)WeaponHash.PumpShotgun:
|
||||
case (uint)WeaponHash.HeavyShotgun:
|
||||
case (uint)WeaponHash.Musket:
|
||||
case (uint)WeaponHash.AssaultShotgun:
|
||||
case (uint)WeaponHash.BullpupShotgun:
|
||||
case (uint)WeaponHash.SawnOffShotgun:
|
||||
case (uint)WeaponHash.SweeperShotgun:
|
||||
case (uint)WeaponHash.CompactRifle:
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private string LoadAnim(string anim)
|
||||
{
|
||||
if (!Call<bool>(HAS_ANIM_DICT_LOADED, anim))
|
||||
{
|
||||
Call(REQUEST_ANIM_DICT, anim);
|
||||
return null;
|
||||
}
|
||||
|
||||
return anim;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using LemonUI.Elements;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
public partial class SyncedPed : SyncedEntity
|
||||
{
|
||||
private readonly string[] _currentAnimation = new string[2] { "", "" };
|
||||
private byte[] _lastClothes;
|
||||
private bool _lastDriveBy;
|
||||
private bool _lastInCover;
|
||||
private bool _lastIsJumping;
|
||||
private WeaponHash _lastWeaponHash;
|
||||
private bool _lastMoving;
|
||||
private bool _lastRagdoll;
|
||||
private ulong _lastRagdollTime;
|
||||
private Dictionary<uint, bool> _lastWeaponComponents;
|
||||
private ScaledText _nameTag;
|
||||
internal Entity WeaponObj;
|
||||
internal BlipColor BlipColor = (BlipColor)255;
|
||||
internal float BlipScale = 1;
|
||||
internal BlipSprite BlipSprite = 0;
|
||||
internal PedDataFlags Flags;
|
||||
internal Blip PedBlip = null;
|
||||
internal VehicleSeat Seat;
|
||||
|
||||
internal int VehicleID
|
||||
{
|
||||
get => CurrentVehicle?.ID ?? 0;
|
||||
set
|
||||
{
|
||||
if (CurrentVehicle == null || value != CurrentVehicle?.ID)
|
||||
CurrentVehicle = EntityPool.GetVehicleByID(value);
|
||||
}
|
||||
}
|
||||
|
||||
internal SyncedVehicle CurrentVehicle { get; private set; }
|
||||
public bool IsPlayer => OwnerID == ID && ID != 0;
|
||||
public Ped MainPed { get; internal set; }
|
||||
internal int Health;
|
||||
|
||||
internal Vector3 HeadPosition;
|
||||
internal Vector3 RightFootPosition;
|
||||
internal Vector3 LeftFootPosition;
|
||||
|
||||
internal byte WeaponTint;
|
||||
internal byte[] Clothes;
|
||||
|
||||
internal float Heading;
|
||||
|
||||
internal ulong LastSpeakingTime { get; set; } = 0;
|
||||
internal bool IsSpeaking { get; set; } = false;
|
||||
public byte Speed { get; set; }
|
||||
|
||||
internal bool IsAiming => Flags.HasPedFlag(PedDataFlags.IsAiming);
|
||||
internal bool IsReloading => Flags.HasPedFlag(PedDataFlags.IsReloading);
|
||||
internal bool IsJumping => Flags.HasPedFlag(PedDataFlags.IsJumping);
|
||||
internal bool IsRagdoll => Flags.HasPedFlag(PedDataFlags.IsRagdoll);
|
||||
internal bool IsOnFire => Flags.HasPedFlag(PedDataFlags.IsOnFire);
|
||||
internal bool IsInParachuteFreeFall => Flags.HasPedFlag(PedDataFlags.IsInParachuteFreeFall);
|
||||
internal bool IsParachuteOpen => Flags.HasPedFlag(PedDataFlags.IsParachuteOpen);
|
||||
internal bool IsOnLadder => Flags.HasPedFlag(PedDataFlags.IsOnLadder);
|
||||
internal bool IsVaulting => Flags.HasPedFlag(PedDataFlags.IsVaulting);
|
||||
internal bool IsInCover => Flags.HasPedFlag(PedDataFlags.IsInCover);
|
||||
internal bool IsInLowCover => Flags.HasPedFlag(PedDataFlags.IsInLowCover);
|
||||
internal bool IsInCoverFacingLeft => Flags.HasPedFlag(PedDataFlags.IsInCoverFacingLeft);
|
||||
internal bool IsBlindFiring => Flags.HasPedFlag(PedDataFlags.IsBlindFiring);
|
||||
internal bool IsInStealthMode => Flags.HasPedFlag(PedDataFlags.IsInStealthMode);
|
||||
internal Prop ParachuteProp = null;
|
||||
internal WeaponHash CurrentWeapon = WeaponHash.Unarmed;
|
||||
internal VehicleWeaponHash VehicleWeapon = VehicleWeaponHash.Invalid;
|
||||
internal Dictionary<uint, bool> WeaponComponents = null;
|
||||
internal Vector3 AimCoords;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,711 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.NaturalMotion;
|
||||
using GTA.UI;
|
||||
using LemonUI.Elements;
|
||||
using RageCoop.Core;
|
||||
using Font = GTA.UI.Font;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
public partial class SyncedPed : SyncedEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a local entity (outgoing sync)
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
internal SyncedPed(Ped p)
|
||||
{
|
||||
ID = EntityPool.RequestNewID();
|
||||
p.CanWrithe = false;
|
||||
p.IsOnlyDamagedByPlayer = false;
|
||||
MainPed = p;
|
||||
OwnerID = LocalPlayerID;
|
||||
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty character with ID
|
||||
/// </summary>
|
||||
internal SyncedPed(int id)
|
||||
{
|
||||
ID = id;
|
||||
LastSynced = Ticked;
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
if (Owner == null)
|
||||
{
|
||||
OwnerID = OwnerID;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPlayer) RenderNameTag();
|
||||
|
||||
// Check if all data available
|
||||
if (!IsReady) return;
|
||||
|
||||
// Skip update if no new sync message has arrived.
|
||||
if (!NeedUpdate) return;
|
||||
|
||||
if (MainPed == null || !MainPed.Exists())
|
||||
if (!CreateCharacter())
|
||||
return;
|
||||
|
||||
if (LastFullSynced >= LastUpdated)
|
||||
{
|
||||
if (MainPed != null && Model != MainPed.Model.Hash)
|
||||
if (!CreateCharacter())
|
||||
return;
|
||||
|
||||
if (!Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255;
|
||||
if ((byte)BlipColor == 255 && PedBlip != null)
|
||||
{
|
||||
PedBlip.Delete();
|
||||
PedBlip = null;
|
||||
}
|
||||
else if ((byte)BlipColor != 255 && PedBlip == null)
|
||||
{
|
||||
PedBlip = MainPed.AddBlip();
|
||||
|
||||
|
||||
PedBlip.Color = BlipColor;
|
||||
PedBlip.Sprite = BlipSprite;
|
||||
PedBlip.Scale = BlipScale;
|
||||
}
|
||||
|
||||
if (PedBlip != null)
|
||||
{
|
||||
if (PedBlip.Color != BlipColor) PedBlip.Color = BlipColor;
|
||||
if (PedBlip.Sprite != BlipSprite) PedBlip.Sprite = BlipSprite;
|
||||
if (IsPlayer) PedBlip.Name = Owner.Username;
|
||||
}
|
||||
|
||||
if (!Clothes.SequenceEqual(_lastClothes)) SetClothes();
|
||||
|
||||
CheckCurrentWeapon();
|
||||
}
|
||||
|
||||
if (MainPed.IsDead)
|
||||
{
|
||||
if (Health > 0)
|
||||
{
|
||||
if (IsPlayer)
|
||||
MainPed.Resurrect();
|
||||
else
|
||||
SyncEvents.TriggerPedKilled(this);
|
||||
}
|
||||
}
|
||||
else if (IsPlayer && MainPed.Health != Health)
|
||||
{
|
||||
MainPed.Health = Health;
|
||||
|
||||
if (Health <= 0 && !MainPed.IsDead)
|
||||
{
|
||||
MainPed.IsInvincible = false;
|
||||
MainPed.Kill();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Speed >= 4)
|
||||
{
|
||||
DisplayInVehicle();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MainPed.IsInVehicle())
|
||||
{
|
||||
MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut);
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayOnFoot();
|
||||
}
|
||||
|
||||
if (IsSpeaking)
|
||||
{
|
||||
if (Ticked - LastSpeakingTime < 10)
|
||||
{
|
||||
DisplaySpeaking(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplaySpeaking(false);
|
||||
|
||||
IsSpeaking = false;
|
||||
LastSpeakingTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
private void RenderNameTag()
|
||||
{
|
||||
if (!Owner.DisplayNameTag || !Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible)
|
||||
return;
|
||||
|
||||
float dist = Call<Vector3>(GET_GAMEPLAY_CAM_COORD).DistanceToSquared(MainPed.Position);
|
||||
if (dist > 10 * 10f)
|
||||
return;
|
||||
|
||||
float scale = 1f - (0.8f * (float)Math.Sqrt(dist)) / 20f;
|
||||
float fontSize = 0.6f * scale;
|
||||
float frameTime = Call<float>(GET_FRAME_TIME);
|
||||
Vector3 headPos = MainPed.Bones[Bone.IKHead].Position;
|
||||
headPos.Z += 0.5f;
|
||||
headPos += Velocity * frameTime;
|
||||
|
||||
Point toDraw = default;
|
||||
if (Util.WorldToScreen(headPos, ref toDraw))
|
||||
{
|
||||
_nameTag ??= new ScaledText(toDraw, Owner.Username, fontSize, Font.ChaletLondon)
|
||||
{
|
||||
Alignment = Alignment.Center,
|
||||
Outline = true
|
||||
};
|
||||
_nameTag.Position = toDraw;
|
||||
_nameTag.Scale = fontSize;
|
||||
_nameTag.Color = Owner.HasDirectConnection ? Color.FromArgb(179, 229, 252) : Color.White;
|
||||
_nameTag.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateCharacter()
|
||||
{
|
||||
if (MainPed != null)
|
||||
{
|
||||
if (MainPed.Exists())
|
||||
{
|
||||
// Log.Debug($"Removing ped {ID}. Reason:CreateCharacter");
|
||||
MainPed.Kill();
|
||||
MainPed.MarkAsNoLongerNeeded();
|
||||
MainPed.Delete();
|
||||
}
|
||||
|
||||
MainPed = null;
|
||||
}
|
||||
|
||||
if (PedBlip != null && PedBlip.Exists())
|
||||
{
|
||||
PedBlip.Delete();
|
||||
PedBlip = null;
|
||||
}
|
||||
|
||||
if (!Model.IsLoaded)
|
||||
{
|
||||
Model.Request();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((MainPed = Util.CreatePed(Model, Position)) == null) return false;
|
||||
|
||||
Model.MarkAsNoLongerNeeded();
|
||||
|
||||
MainPed.BlockPermanentEvents = true;
|
||||
MainPed.CanWrithe = false;
|
||||
MainPed.CanBeDraggedOutOfVehicle = true;
|
||||
MainPed.IsOnlyDamagedByPlayer = false;
|
||||
MainPed.RelationshipGroup = SyncedPedsGroup;
|
||||
MainPed.IsFireProof = false;
|
||||
MainPed.IsExplosionProof = false;
|
||||
|
||||
Call(SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
|
||||
Call(SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
|
||||
Call(SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
|
||||
Call(SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
|
||||
Call(SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
|
||||
// Call(_SET_PED_CAN_PLAY_INJURED_ANIMS, false);
|
||||
Call(SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
|
||||
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater, false);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableExplosionReactions, true);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_AvoidTearGas, false);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_IgnoreBeingOnFire, true);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableEvasiveDives, true);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisablePanicInVehicle, true);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_BlockNonTemporaryEvents, true);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableShockingEvents, true);
|
||||
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
|
||||
|
||||
SetClothes();
|
||||
|
||||
if (IsPlayer) MainPed.IsInvincible = true;
|
||||
if (IsInvincible) MainPed.IsInvincible = true;
|
||||
|
||||
lock (EntityPool.PedsLock)
|
||||
{
|
||||
// Add to EntityPool so this Character can be accessed by handle.
|
||||
EntityPool.Add(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetClothes()
|
||||
{
|
||||
for (byte i = 0; i < 12; i++)
|
||||
Call(SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i],
|
||||
(int)Clothes[i + 12], (int)Clothes[i + 24]);
|
||||
_lastClothes = Clothes;
|
||||
}
|
||||
|
||||
|
||||
private void DisplayOnFoot()
|
||||
{
|
||||
if (IsInParachuteFreeFall)
|
||||
{
|
||||
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
|
||||
MainPed.Rotation = Rotation;
|
||||
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
|
||||
{
|
||||
// Skip update if animation is not loaded
|
||||
var dict = LoadAnim("skydive@base");
|
||||
if (dict == null) return;
|
||||
Call(TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsParachuteOpen)
|
||||
{
|
||||
if (ParachuteProp == null)
|
||||
{
|
||||
Model model = 1740193300;
|
||||
model.Request(1000);
|
||||
if (model != null)
|
||||
{
|
||||
ParachuteProp = World.CreateProp(model, MainPed.ReadPosition(), MainPed.ReadRotation(), false,
|
||||
false);
|
||||
model.MarkAsNoLongerNeeded();
|
||||
ParachuteProp.IsPositionFrozen = true;
|
||||
ParachuteProp.IsCollisionEnabled = false;
|
||||
|
||||
ParachuteProp.AttachTo(MainPed.Bones[Bone.SkelSpine2], new Vector3(3.6f, 0f, 0f),
|
||||
new Vector3(0f, 90f, 0f));
|
||||
}
|
||||
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
MainPed.Task.ClearSecondary();
|
||||
}
|
||||
|
||||
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
|
||||
MainPed.Rotation = Rotation;
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person",
|
||||
"chute_idle_right", 3))
|
||||
{
|
||||
var dict = LoadAnim("skydive@parachute@first_person");
|
||||
if (dict == null) return;
|
||||
Call(TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ParachuteProp != null)
|
||||
{
|
||||
if (ParachuteProp.Exists()) ParachuteProp.Delete();
|
||||
ParachuteProp = null;
|
||||
}
|
||||
|
||||
if (IsOnLadder)
|
||||
{
|
||||
if (Velocity.Z < 0)
|
||||
{
|
||||
var anim = Velocity.Z < -2f ? "slide_climb_down" : "climb_down";
|
||||
if (_currentAnimation[1] != anim)
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_currentAnimation[1] = anim;
|
||||
}
|
||||
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", anim, 3))
|
||||
MainPed.Task.PlayAnimation("laddersbase", anim, 8f, -1, AnimationFlags.Loop);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Abs(Velocity.Z) < 0.5)
|
||||
{
|
||||
if (_currentAnimation[1] != "base_left_hand_up")
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_currentAnimation[1] = "base_left_hand_up";
|
||||
}
|
||||
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase",
|
||||
"base_left_hand_up", 3))
|
||||
MainPed.Task.PlayAnimation("laddersbase", "base_left_hand_up", 8f, -1, AnimationFlags.Loop);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_currentAnimation[1] != "climb_up")
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_currentAnimation[1] = "climb_up";
|
||||
}
|
||||
|
||||
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up",
|
||||
3)) MainPed.Task.PlayAnimation("laddersbase", "climb_up", 8f, -1, AnimationFlags.Loop);
|
||||
}
|
||||
}
|
||||
|
||||
SmoothTransition();
|
||||
return;
|
||||
}
|
||||
|
||||
if (MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_currentAnimation[1] = "";
|
||||
}
|
||||
|
||||
if (IsVaulting)
|
||||
{
|
||||
if (!MainPed.IsVaulting) MainPed.Task.Climb();
|
||||
|
||||
SmoothTransition();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsVaulting && MainPed.IsVaulting) MainPed.Task.ClearAllImmediately();
|
||||
|
||||
if (IsOnFire && !MainPed.IsOnFire)
|
||||
Call(START_ENTITY_FIRE, MainPed);
|
||||
else if (!IsOnFire && MainPed.IsOnFire) Call(STOP_ENTITY_FIRE, MainPed);
|
||||
|
||||
if (IsJumping)
|
||||
{
|
||||
if (!_lastIsJumping)
|
||||
{
|
||||
_lastIsJumping = true;
|
||||
MainPed.Task.Jump();
|
||||
}
|
||||
|
||||
SmoothTransition();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastIsJumping = false;
|
||||
|
||||
if (IsRagdoll || Health == 0)
|
||||
{
|
||||
if (!MainPed.IsRagdoll) MainPed.Ragdoll();
|
||||
SmoothTransition();
|
||||
if (!_lastRagdoll)
|
||||
{
|
||||
_lastRagdoll = true;
|
||||
_lastRagdollTime = Ticked;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (MainPed.IsRagdoll)
|
||||
{
|
||||
if (Speed == 0)
|
||||
MainPed.CancelRagdoll();
|
||||
else
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_lastRagdoll = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsReloading)
|
||||
{
|
||||
if (!MainPed.IsTaskActive(TaskType.CTaskReloadGun)) MainPed.Task.ReloadWeapon();
|
||||
/*
|
||||
if (!_isPlayingAnimation)
|
||||
{
|
||||
string[] reloadingAnim = MainPed.GetReloadingAnimation();
|
||||
if (reloadingAnim != null)
|
||||
{
|
||||
_isPlayingAnimation = true;
|
||||
_currentAnimation = reloadingAnim;
|
||||
MainPed.Task.PlayAnimation(_currentAnimation[0], _currentAnimation[1], 8f, -1, AnimationFlags.AllowRotation | AnimationFlags.UpperBodyOnly);
|
||||
}
|
||||
}
|
||||
*/
|
||||
SmoothTransition();
|
||||
}
|
||||
else if (IsInCover)
|
||||
{
|
||||
if (!_lastInCover) Call(TASK_STAY_IN_COVER, MainPed.Handle);
|
||||
|
||||
_lastInCover = true;
|
||||
if (IsAiming)
|
||||
{
|
||||
DisplayAiming();
|
||||
_lastInCover = false;
|
||||
}
|
||||
else if (MainPed.IsInCover)
|
||||
{
|
||||
SmoothTransition();
|
||||
}
|
||||
}
|
||||
else if (_lastInCover)
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
_lastInCover = false;
|
||||
}
|
||||
else if (IsAiming)
|
||||
{
|
||||
DisplayAiming();
|
||||
}
|
||||
else if (MainPed.IsShooting)
|
||||
{
|
||||
MainPed.Task.ClearAllImmediately();
|
||||
}
|
||||
else
|
||||
{
|
||||
WalkTo();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckCurrentWeapon()
|
||||
{
|
||||
if (MainPed.VehicleWeapon != VehicleWeapon) MainPed.VehicleWeapon = VehicleWeapon;
|
||||
var compChanged = WeaponComponents != null && WeaponComponents.Count != 0 && WeaponComponents != _lastWeaponComponents && !WeaponComponents.Compare(_lastWeaponComponents);
|
||||
if (_lastWeaponHash != CurrentWeapon || compChanged)
|
||||
{
|
||||
if (_lastWeaponHash == WeaponHash.Unarmed && WeaponObj?.Exists() == true)
|
||||
{
|
||||
WeaponObj.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
var model = Call<uint>(GET_WEAPONTYPE_MODEL, CurrentWeapon);
|
||||
if (!Call<bool>(HAS_MODEL_LOADED, model))
|
||||
{
|
||||
Call(REQUEST_MODEL, model);
|
||||
return;
|
||||
}
|
||||
if (WeaponObj?.Exists() == true)
|
||||
WeaponObj.Delete();
|
||||
MainPed.Weapons.RemoveAll();
|
||||
WeaponObj = Entity.FromHandle(Call<int>(CREATE_WEAPON_OBJECT, CurrentWeapon, -1, Position.X, Position.Y, Position.Z, true, 0, 0));
|
||||
}
|
||||
|
||||
if (compChanged)
|
||||
{
|
||||
foreach (var comp in WeaponComponents)
|
||||
{
|
||||
if (comp.Value)
|
||||
{
|
||||
Call(GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, WeaponObj.Handle, comp.Key);
|
||||
}
|
||||
}
|
||||
_lastWeaponComponents = WeaponComponents;
|
||||
}
|
||||
Call(GIVE_WEAPON_OBJECT_TO_PED, WeaponObj.Handle, MainPed.Handle);
|
||||
_lastWeaponHash = CurrentWeapon;
|
||||
}
|
||||
|
||||
if (Call<int>(GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon) != WeaponTint)
|
||||
Call<int>(SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon, WeaponTint);
|
||||
}
|
||||
|
||||
private void DisplayAiming()
|
||||
{
|
||||
if (Velocity == default)
|
||||
MainPed.Task.AimAt(AimCoords, 1000);
|
||||
else
|
||||
Call(TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
|
||||
Position.X + Velocity.X, Position.Y + Velocity.Y, Position.Z + Velocity.Z,
|
||||
AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, false, 0x3F000000, 0x40800000, false, 512, false, 0);
|
||||
SmoothTransition();
|
||||
}
|
||||
|
||||
private void WalkTo()
|
||||
{
|
||||
Call(SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
|
||||
var predictPosition = Predict(Position) + Velocity;
|
||||
var range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
|
||||
|
||||
switch (Speed)
|
||||
{
|
||||
case 1:
|
||||
if (!MainPed.IsWalking || range > 0.25f)
|
||||
{
|
||||
var nrange = range * 2;
|
||||
if (nrange > 1.0f) nrange = 1.0f;
|
||||
|
||||
MainPed.Task.GoStraightTo(predictPosition);
|
||||
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, nrange);
|
||||
}
|
||||
|
||||
_lastMoving = true;
|
||||
break;
|
||||
case 2:
|
||||
if (!MainPed.IsRunning || range > 0.50f)
|
||||
{
|
||||
MainPed.Task.RunTo(predictPosition, true);
|
||||
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
|
||||
}
|
||||
|
||||
_lastMoving = true;
|
||||
break;
|
||||
case 3:
|
||||
if (!MainPed.IsSprinting || range > 0.75f)
|
||||
{
|
||||
Call(TASK_GO_STRAIGHT_TO_COORD, MainPed.Handle, predictPosition.X,
|
||||
predictPosition.Y, predictPosition.Z, 3.0f, -1, 0.0f, 0.0f);
|
||||
Call(SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, MainPed.Handle, 1.49f);
|
||||
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
|
||||
}
|
||||
|
||||
_lastMoving = true;
|
||||
break;
|
||||
default:
|
||||
if (_lastMoving)
|
||||
{
|
||||
MainPed.Task.StandStill(2000);
|
||||
_lastMoving = false;
|
||||
}
|
||||
|
||||
if (MainPed.IsTaskActive(TaskType.CTaskDiveToGround)) MainPed.Task.ClearAll();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
SmoothTransition();
|
||||
}
|
||||
|
||||
private void SmoothTransition()
|
||||
{
|
||||
var localRagdoll = MainPed.IsRagdoll;
|
||||
var predicted = Predict(Position);
|
||||
var dist = predicted.DistanceTo(MainPed.ReadPosition());
|
||||
if (IsOff(dist))
|
||||
{
|
||||
MainPed.PositionNoOffset = predicted;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(localRagdoll || MainPed.IsDead))
|
||||
{
|
||||
if (!IsAiming && !MainPed.IsGettingUp)
|
||||
{
|
||||
var cur = MainPed.Heading;
|
||||
var diff = Heading - cur;
|
||||
if (diff > 180)
|
||||
diff -= 360;
|
||||
else if (diff < -180) diff += 360;
|
||||
|
||||
MainPed.Heading = cur + diff / 2;
|
||||
}
|
||||
|
||||
MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
|
||||
}
|
||||
else if (Ticked - _lastRagdollTime < 10)
|
||||
{
|
||||
}
|
||||
else if (IsRagdoll)
|
||||
{
|
||||
var helper = new ApplyImpulseHelper(MainPed);
|
||||
var head = MainPed.Bones[Bone.SkelHead];
|
||||
var rightFoot = MainPed.Bones[Bone.SkelRightFoot];
|
||||
var leftFoot = MainPed.Bones[Bone.SkelLeftFoot];
|
||||
Vector3 amount;
|
||||
// 20:head, 3:left foot, 6:right foot, 17:right hand,
|
||||
|
||||
amount = 20 * (Predict(HeadPosition) - head.Position);
|
||||
if (amount.Length() > 50) amount = amount.Normalized * 50;
|
||||
helper.EqualizeAmount = 1;
|
||||
helper.PartIndex = 20;
|
||||
helper.Impulse = amount;
|
||||
helper.Start();
|
||||
helper.Stop();
|
||||
|
||||
amount = 20 * (Predict(RightFootPosition) - rightFoot.Position);
|
||||
if (amount.Length() > 50) amount = amount.Normalized * 50;
|
||||
helper.EqualizeAmount = 1;
|
||||
helper.PartIndex = 6;
|
||||
helper.Impulse = amount;
|
||||
helper.Start();
|
||||
helper.Stop();
|
||||
|
||||
amount = 20 * (Predict(LeftFootPosition) - leftFoot.Position);
|
||||
if (amount.Length() > 50) amount = amount.Normalized * 50;
|
||||
helper.EqualizeAmount = 1;
|
||||
helper.PartIndex = 3;
|
||||
helper.Impulse = amount;
|
||||
helper.Start();
|
||||
helper.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
// localRagdoll
|
||||
var force = Velocity - MainPed.Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
|
||||
if (force.Length() > 20) force = force.Normalized * 20;
|
||||
MainPed.ApplyWorldForceCenterOfMass(force, ForceType.InternalImpulse, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayInVehicle()
|
||||
{
|
||||
if (CurrentVehicle?.MainVehicle == null) return;
|
||||
switch (Speed)
|
||||
{
|
||||
// In vehicle
|
||||
case 4:
|
||||
if (MainPed.CurrentVehicle != CurrentVehicle.MainVehicle || MainPed.SeatIndex != Seat ||
|
||||
(!MainPed.IsSittingInVehicle() && !MainPed.IsBeingJacked))
|
||||
MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat);
|
||||
if (MainPed.IsOnTurretSeat())
|
||||
Call(TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y,
|
||||
AimCoords.Z);
|
||||
|
||||
// Drive-by
|
||||
if (VehicleWeapon == VehicleWeaponHash.Invalid)
|
||||
{
|
||||
if (IsAiming)
|
||||
{
|
||||
Call(SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y,
|
||||
AimCoords.Z);
|
||||
if (!_lastDriveBy)
|
||||
{
|
||||
_lastDriveBy = true;
|
||||
Call(TASK_DRIVE_BY, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z,
|
||||
100000, 100, 1, FiringPattern.SingleShot);
|
||||
}
|
||||
}
|
||||
else if (_lastDriveBy || MainPed.IsTaskActive(TaskType.CTaskAimGunVehicleDriveBy))
|
||||
{
|
||||
MainPed.Task.ClearAll();
|
||||
_lastDriveBy = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Entering vehicle
|
||||
case 5:
|
||||
if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle ||
|
||||
MainPed.GetSeatTryingToEnter() != Seat)
|
||||
MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5,
|
||||
EnterVehicleFlags.JackAnyone);
|
||||
break;
|
||||
|
||||
// Leaving vehicle
|
||||
case 6:
|
||||
if (!MainPed.IsTaskActive(TaskType.CTaskExitVehicle))
|
||||
MainPed.Task.LeaveVehicle(CurrentVehicle.Velocity.LengthSquared() > 5 * 5
|
||||
? LeaveVehicleFlags.BailOut
|
||||
: LeaveVehicleFlags.None);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using System.Diagnostics;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public abstract class SyncedEntity
|
||||
{
|
||||
private float _accumulatedOff;
|
||||
|
||||
|
||||
private int _ownerID;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current player is responsible for syncing this entity.
|
||||
/// </summary>
|
||||
public bool IsLocal => OwnerID == LocalPlayerID;
|
||||
|
||||
/// <summary>
|
||||
/// Network ID for this entity
|
||||
/// </summary>
|
||||
public int ID { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public int OwnerID
|
||||
{
|
||||
get => _ownerID;
|
||||
internal set
|
||||
{
|
||||
if (value == _ownerID && Owner != null) return;
|
||||
_ownerID = value;
|
||||
Owner = PlayerList.GetPlayer(value);
|
||||
if (this is SyncedPed && Owner != null) Owner.Character = (SyncedPed)this;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual Player Owner { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public bool IsOutOfSync => Ticked - LastSynced > 200 && ID != 0;
|
||||
|
||||
internal bool IsReady => LastSynced > 0 || LastFullSynced == 0;
|
||||
|
||||
internal bool IsInvincible { get; set; } = false;
|
||||
|
||||
internal bool NeedUpdate => LastSynced >= LastUpdated;
|
||||
|
||||
public bool SendNextFrame { get; set; } = false;
|
||||
public bool SendFullNextFrame { get; set; } = false;
|
||||
internal Model Model { get; set; }
|
||||
internal Vector3 Position { get; set; }
|
||||
internal Vector3 Rotation { get; set; }
|
||||
internal Quaternion Quaternion { get; set; }
|
||||
internal Vector3 Velocity { get; set; }
|
||||
internal abstract void Update();
|
||||
|
||||
internal void PauseUpdate(ulong frames)
|
||||
{
|
||||
LastUpdated = Ticked + frames;
|
||||
}
|
||||
|
||||
protected Vector3 Predict(Vector3 input)
|
||||
{
|
||||
return Diff() + input;
|
||||
}
|
||||
protected Vector3 Diff()
|
||||
{
|
||||
return (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Velocity;
|
||||
}
|
||||
|
||||
protected bool IsOff(float thisOff, float tolerance = 3, float limit = 30)
|
||||
{
|
||||
_accumulatedOff += thisOff - tolerance;
|
||||
if (_accumulatedOff < 0)
|
||||
{
|
||||
_accumulatedOff = 0;
|
||||
}
|
||||
else if (_accumulatedOff >= limit)
|
||||
{
|
||||
_accumulatedOff = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region LAST STATE
|
||||
|
||||
public void SetLastSynced(bool full)
|
||||
{
|
||||
LastSyncInterval = LastSentStopWatch.ElapsedMilliseconds;
|
||||
LastSynced = Ticked;
|
||||
if (full)
|
||||
{
|
||||
LastFullSynced = Ticked;
|
||||
}
|
||||
LastSyncedStopWatch.Restart();
|
||||
}
|
||||
|
||||
public Stopwatch LastSyncedStopWatch = new Stopwatch();
|
||||
|
||||
/// <summary>
|
||||
/// The interval between last sync and the earlier one
|
||||
/// </summary>
|
||||
public long LastSyncInterval;
|
||||
|
||||
/// <summary>
|
||||
/// Last time a new sync message arrived.
|
||||
/// </summary>
|
||||
public ulong LastSynced { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Last time a new sync message arrived.
|
||||
/// </summary>
|
||||
public ulong LastFullSynced { get; internal set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Last time the local entity has been updated,
|
||||
/// </summary>
|
||||
public ulong LastUpdated { get; set; }
|
||||
|
||||
|
||||
internal Stopwatch LastSentStopWatch { get; set; } = Stopwatch.StartNew();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class SyncedProjectile : SyncedEntity
|
||||
{
|
||||
public readonly Vector3 Origin;
|
||||
private bool _firstSend;
|
||||
|
||||
public SyncedProjectile(Projectile p)
|
||||
{
|
||||
var owner = p.OwnerEntity;
|
||||
if (owner == null)
|
||||
{
|
||||
IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
ID = EntityPool.RequestNewID();
|
||||
MainProjectile = p;
|
||||
Origin = p.Position;
|
||||
if (EntityPool.PedsByHandle.TryGetValue(owner.Handle, out var shooter))
|
||||
{
|
||||
if (shooter.MainPed != null
|
||||
&& (p.AttachedEntity == shooter.MainPed.Weapons.CurrentWeaponObject
|
||||
|| p.AttachedEntity == shooter.MainPed))
|
||||
{
|
||||
// Reloading
|
||||
IsValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Shooter = shooter;
|
||||
IsLocal = shooter.IsLocal;
|
||||
}
|
||||
else if (EntityPool.VehiclesByHandle.TryGetValue(owner.Handle, out var shooterVeh))
|
||||
{
|
||||
Shooter = shooterVeh;
|
||||
IsLocal = shooterVeh.IsLocal;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
public SyncedProjectile(int id)
|
||||
{
|
||||
ID = id;
|
||||
IsLocal = false;
|
||||
}
|
||||
|
||||
public ProjectileDataFlags Flags { private get; set; } = ProjectileDataFlags.None;
|
||||
|
||||
|
||||
public bool IsValid { get; } = true;
|
||||
public new bool IsLocal { get; }
|
||||
public Projectile MainProjectile { get; set; }
|
||||
public SyncedEntity Shooter { get; set; }
|
||||
public bool Exploded => Flags.HasProjDataFlag(ProjectileDataFlags.Exploded);
|
||||
|
||||
internal override Player Owner => Shooter.Owner;
|
||||
|
||||
/// <summary>
|
||||
/// Invalid property for projectile.
|
||||
/// </summary>
|
||||
private new int OwnerID
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
public WeaponHash WeaponHash { get; set; }
|
||||
private WeaponAsset Asset { get; set; }
|
||||
|
||||
public void ExtractData(ref Packets.ProjectileSync p)
|
||||
{
|
||||
p.Position = MainProjectile.Position;
|
||||
p.Velocity = MainProjectile.Velocity;
|
||||
p.Rotation = MainProjectile.Rotation;
|
||||
p.ID = ID;
|
||||
p.ShooterID = Shooter.ID;
|
||||
p.WeaponHash = (uint)MainProjectile.WeaponHash;
|
||||
p.Flags = ProjectileDataFlags.None;
|
||||
if (MainProjectile.IsDead) p.Flags |= ProjectileDataFlags.Exploded;
|
||||
if (MainProjectile.AttachedEntity != null) p.Flags |= ProjectileDataFlags.IsAttached;
|
||||
if (Shooter is SyncedVehicle) p.Flags |= ProjectileDataFlags.IsShotByVehicle;
|
||||
if (_firstSend)
|
||||
{
|
||||
p.Flags |= ProjectileDataFlags.IsAttached;
|
||||
_firstSend = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
// Skip update if no new sync message has arrived.
|
||||
if (!NeedUpdate) return;
|
||||
|
||||
if (MainProjectile == null || !MainProjectile.Exists())
|
||||
{
|
||||
CreateProjectile();
|
||||
return;
|
||||
}
|
||||
|
||||
MainProjectile.Velocity = Velocity + 10 * (Predict(Position) - MainProjectile.Position);
|
||||
MainProjectile.Rotation = Rotation;
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
|
||||
private void CreateProjectile()
|
||||
{
|
||||
Asset = new WeaponAsset(WeaponHash);
|
||||
if (!Asset.IsLoaded)
|
||||
{
|
||||
Asset.Request();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Shooter == null) return;
|
||||
Entity owner;
|
||||
owner = (Shooter as SyncedPed)?.MainPed ?? (Entity)(Shooter as SyncedVehicle)?.MainVehicle;
|
||||
Position = (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Shooter.Velocity +
|
||||
Position;
|
||||
var end = Position + Velocity;
|
||||
Call(SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY, Position.X, Position.Y, Position.Z,
|
||||
end.X, end.Y, end.Z, 0, 1, WeaponHash, owner?.Handle ?? 0, 1, 0, -1);
|
||||
var ps = World.GetAllProjectiles();
|
||||
MainProjectile = ps[ps.Length - 1];
|
||||
MainProjectile.Position = Position;
|
||||
MainProjectile.Rotation = Rotation;
|
||||
MainProjectile.Velocity = Velocity;
|
||||
EntityPool.Add(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using GTA;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Synchronized prop, mostly owned by server
|
||||
/// </summary>
|
||||
public class SyncedProp : SyncedEntity
|
||||
{
|
||||
internal SyncedProp(int id)
|
||||
{
|
||||
ID = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The real entity
|
||||
/// </summary>
|
||||
public Prop MainProp { get; set; }
|
||||
|
||||
internal new int OwnerID =>
|
||||
// alwayse owned by server
|
||||
0;
|
||||
|
||||
internal override void Update()
|
||||
{
|
||||
if (!NeedUpdate) return;
|
||||
if (!Model.IsLoaded)
|
||||
{
|
||||
Model.Request();
|
||||
return;
|
||||
}
|
||||
|
||||
if (MainProp == null || !MainProp.Exists())
|
||||
{
|
||||
MainProp = World.CreateProp(Model, Position, Rotation, false, false);
|
||||
MainProp.IsInvincible = true;
|
||||
}
|
||||
|
||||
MainProp.Position = Position;
|
||||
MainProp.Rotation = Rotation;
|
||||
MainProp.SetFrozen(true);
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
public partial class SyncedVehicle
|
||||
{
|
||||
public Vehicle MainVehicle { get; internal set; }
|
||||
|
||||
|
||||
#region -- SYNC DATA --
|
||||
|
||||
internal Vector3 RotationVelocity { get; set; }
|
||||
internal float SteeringAngle { get; set; }
|
||||
internal float ThrottlePower { get; set; }
|
||||
internal float BrakePower { get; set; }
|
||||
internal float DeluxoWingRatio { get; set; } = -1;
|
||||
|
||||
|
||||
internal byte LandingGear { get; set; }
|
||||
internal VehicleRoofState RoofState { get; set; }
|
||||
internal VehicleDamageModel DamageModel { get; set; }
|
||||
internal (byte, byte) Colors { get; set; }
|
||||
internal (int, int)[] Mods { get; set; }
|
||||
internal float EngineHealth { get; set; }
|
||||
internal VehicleLockStatus LockStatus { get; set; }
|
||||
internal byte RadioStation = 255;
|
||||
internal string LicensePlate { get; set; }
|
||||
internal int Livery { get; set; } = -1;
|
||||
internal byte HeadlightColor { get; set; } = 255;
|
||||
internal VehicleDataFlags Flags { get; set; }
|
||||
internal ushort ExtrasMask;
|
||||
internal byte ToggleModsMask;
|
||||
|
||||
#endregion
|
||||
|
||||
#region FLAGS
|
||||
|
||||
internal bool EngineRunning => Flags.HasVehFlag(VehicleDataFlags.IsEngineRunning);
|
||||
internal bool Transformed => Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
|
||||
internal bool HornActive => Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
|
||||
internal bool LightsOn => Flags.HasVehFlag(VehicleDataFlags.AreLightsOn);
|
||||
internal bool BrakeLightsOn => Flags.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
|
||||
internal bool HighBeamsOn => Flags.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
|
||||
internal bool SireneActive => Flags.HasVehFlag(VehicleDataFlags.IsSirenActive);
|
||||
internal bool IsDead => Flags.HasVehFlag(VehicleDataFlags.IsDead);
|
||||
internal bool IsDeluxoHovering => Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering);
|
||||
|
||||
#endregion
|
||||
|
||||
#region FIXED-DATA
|
||||
|
||||
internal bool IsMotorcycle;
|
||||
internal bool IsAircraft;
|
||||
internal bool HasRocketBoost;
|
||||
internal bool HasParachute;
|
||||
internal bool HasRoof;
|
||||
internal bool IsSubmarineCar;
|
||||
internal bool IsDeluxo;
|
||||
internal bool IsTrain;
|
||||
internal ushort AvalibleExtras;
|
||||
|
||||
[DebugTunable]
|
||||
static float RotCalMult = 10f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region PRIVATE
|
||||
private byte _lastToggleMods;
|
||||
private (byte, byte) _lastVehicleColors;
|
||||
private ushort _lastExtras;
|
||||
private (int, int)[] _lastVehicleMods = Array.Empty<(int, int)>();
|
||||
private bool _lastHornActive;
|
||||
private bool _lastTransformed;
|
||||
private int _lastLivery = -1;
|
||||
private byte _lastHeadlightColor = 255;
|
||||
private Vector3 _predictedPosition;
|
||||
#endregion
|
||||
|
||||
#region OUTGOING
|
||||
|
||||
internal float LastNozzleAngle { get; set; }
|
||||
|
||||
internal float LastEngineHealth { get; set; }
|
||||
internal Vector3 LastVelocity { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using RageCoop.Core;
|
||||
using static ICSharpCode.SharpZipLib.Zip.ExtendedUnixData;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// A synchronized vehicle instance
|
||||
/// </summary>
|
||||
public partial class SyncedVehicle : SyncedEntity
|
||||
{
|
||||
internal override void Update()
|
||||
{
|
||||
// Check if all data available
|
||||
if (!IsReady || Owner == null) return;
|
||||
|
||||
// Check existence
|
||||
if (MainVehicle == null || !MainVehicle.Exists() || MainVehicle.Model != Model)
|
||||
if (!CreateVehicle())
|
||||
return;
|
||||
|
||||
|
||||
DisplayVehicle();
|
||||
// Skip update if no new sync message has arrived.
|
||||
if (!NeedUpdate) return;
|
||||
|
||||
if (SteeringAngle != MainVehicle.SteeringAngle)
|
||||
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * SteeringAngle);
|
||||
MainVehicle.ThrottlePower = ThrottlePower;
|
||||
MainVehicle.BrakePower = BrakePower;
|
||||
|
||||
if (IsDead)
|
||||
{
|
||||
if (MainVehicle.IsDead) return;
|
||||
|
||||
MainVehicle.Explode();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MainVehicle.IsDead)
|
||||
WorldThread.Delay(() =>
|
||||
{
|
||||
if (MainVehicle.IsDead && !IsDead) MainVehicle.Repair();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
if (MainVehicle.IsOnFire)
|
||||
{
|
||||
if (!Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
|
||||
}
|
||||
else if (Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
|
||||
{
|
||||
Call(START_ENTITY_FIRE, MainVehicle);
|
||||
}
|
||||
|
||||
if (EngineRunning != MainVehicle.IsEngineRunning) MainVehicle.IsEngineRunning = EngineRunning;
|
||||
|
||||
if (LightsOn != MainVehicle.AreLightsOn) MainVehicle.AreLightsOn = LightsOn;
|
||||
|
||||
if (HighBeamsOn != MainVehicle.AreHighBeamsOn) MainVehicle.AreHighBeamsOn = HighBeamsOn;
|
||||
|
||||
if (IsAircraft)
|
||||
{
|
||||
if (LandingGear != (byte)MainVehicle.LandingGearState)
|
||||
MainVehicle.LandingGearState = (VehicleLandingGearState)LandingGear;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MainVehicle.HasSiren && SireneActive != MainVehicle.IsSirenActive)
|
||||
MainVehicle.IsSirenActive = SireneActive;
|
||||
|
||||
if (HornActive)
|
||||
{
|
||||
if (!_lastHornActive)
|
||||
{
|
||||
_lastHornActive = true;
|
||||
MainVehicle.SoundHorn(99999);
|
||||
}
|
||||
}
|
||||
else if (_lastHornActive)
|
||||
{
|
||||
_lastHornActive = false;
|
||||
MainVehicle.SoundHorn(1);
|
||||
}
|
||||
|
||||
if (HasRoof && MainVehicle.RoofState != RoofState) MainVehicle.RoofState = RoofState;
|
||||
|
||||
if (HasRocketBoost && Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) !=
|
||||
MainVehicle.IsRocketBoostActive)
|
||||
MainVehicle.IsRocketBoostActive = Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive);
|
||||
if (HasParachute && Flags.HasFlag(VehicleDataFlags.IsParachuteActive) &&
|
||||
!MainVehicle.IsParachuteDeployed)
|
||||
MainVehicle.StartParachuting(false);
|
||||
if (IsSubmarineCar)
|
||||
{
|
||||
if (Transformed)
|
||||
{
|
||||
if (!_lastTransformed)
|
||||
{
|
||||
_lastTransformed = true;
|
||||
Call(TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
|
||||
}
|
||||
}
|
||||
else if (_lastTransformed)
|
||||
{
|
||||
_lastTransformed = false;
|
||||
Call(TRANSFORM_TO_CAR, MainVehicle.Handle, false);
|
||||
}
|
||||
}
|
||||
else if (IsDeluxo)
|
||||
{
|
||||
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
|
||||
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
|
||||
}
|
||||
|
||||
Call(SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
|
||||
}
|
||||
|
||||
MainVehicle.LockStatus = LockStatus;
|
||||
|
||||
if (LastFullSynced >= LastUpdated)
|
||||
{
|
||||
if (Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
|
||||
if (Colors != _lastVehicleColors)
|
||||
{
|
||||
Call(SET_VEHICLE_COLOURS, MainVehicle, Colors.Item1, Colors.Item2);
|
||||
|
||||
_lastVehicleColors = Colors;
|
||||
}
|
||||
|
||||
MainVehicle.EngineHealth = EngineHealth;
|
||||
if (Mods != null && !Mods.SequenceEqual(_lastVehicleMods))
|
||||
{
|
||||
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
|
||||
|
||||
foreach (var mod in Mods) MainVehicle.Mods[(VehicleModType)mod.Item1].Index = mod.Item2;
|
||||
|
||||
_lastVehicleMods = Mods;
|
||||
}
|
||||
if (ToggleModsMask != _lastToggleMods)
|
||||
{
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
Call(TOGGLE_VEHICLE_MOD, MainVehicle.Handle, i + 17, (ToggleModsMask & (1 << i)) != 0);
|
||||
}
|
||||
_lastToggleMods = ToggleModsMask;
|
||||
}
|
||||
|
||||
if (Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle) != LicensePlate)
|
||||
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, LicensePlate);
|
||||
|
||||
if (_lastLivery != Livery)
|
||||
{
|
||||
Call(SET_VEHICLE_LIVERY, MainVehicle, Livery);
|
||||
_lastLivery = Livery;
|
||||
}
|
||||
|
||||
if (_lastHeadlightColor != HeadlightColor)
|
||||
{
|
||||
Call(SET_VEHICLE_XENON_LIGHT_COLOR_INDEX, MainVehicle.Handle, HeadlightColor);
|
||||
_lastHeadlightColor = HeadlightColor;
|
||||
}
|
||||
MainVehicle.SetDamageModel(DamageModel);
|
||||
|
||||
if (MainVehicle.Handle == V?.Handle && Util.GetPlayerRadioIndex() != RadioStation)
|
||||
Util.SetPlayerRadioIndex(MainVehicle.Handle, RadioStation);
|
||||
|
||||
if (_lastExtras != ExtrasMask)
|
||||
{
|
||||
for (int i = 1; i < 15; i++)
|
||||
{
|
||||
var flag = (ushort)(1 << i);
|
||||
var hasExtra = (AvalibleExtras & (ushort)(1 << i)) != 0;
|
||||
if (!hasExtra)
|
||||
continue;
|
||||
|
||||
var on = (ExtrasMask & flag) != 0;
|
||||
Call(SET_VEHICLE_EXTRA, MainVehicle.Handle, i, !on);
|
||||
}
|
||||
_lastExtras = ExtrasMask;
|
||||
}
|
||||
}
|
||||
|
||||
LastUpdated = Ticked;
|
||||
}
|
||||
|
||||
private void DisplayVehicle()
|
||||
{
|
||||
_predictedPosition = Predict(Position);
|
||||
var current = MainVehicle.ReadPosition();
|
||||
var distSquared = current.DistanceToSquared(_predictedPosition);
|
||||
var cali = _predictedPosition - current;
|
||||
if (!IsTrain) cali += 0.5f * (Velocity - MainVehicle.Velocity);
|
||||
if (distSquared > 10 * 10)
|
||||
{
|
||||
MainVehicle.Position = _predictedPosition;
|
||||
MainVehicle.Velocity = Velocity;
|
||||
MainVehicle.Quaternion = Quaternion;
|
||||
return;
|
||||
}
|
||||
|
||||
var stopped = Velocity == Vector3.Zero;
|
||||
|
||||
// Calibrate position
|
||||
if (distSquared > 0.03 * 0.03)
|
||||
{
|
||||
if (IsTrain || distSquared > 20 * 20) MainVehicle.Velocity = Velocity + cali;
|
||||
else MainVehicle.ApplyWorldForceCenterOfMass(cali, ForceType.InternalImpulse, true);
|
||||
}
|
||||
|
||||
if (NeedUpdate || stopped)
|
||||
{
|
||||
// Calibrate rotation
|
||||
var diff = Quaternion.Diff(MainVehicle.ReadQuaternion());
|
||||
MainVehicle.WorldRotationVelocity = diff.ToEulerAngles() * RotCalMult;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateVehicle()
|
||||
{
|
||||
MainVehicle?.Delete();
|
||||
MainVehicle = Util.CreateVehicle(Model, Position);
|
||||
if (!Model.IsInCdImage)
|
||||
// GTA.UI.Notification.Show($"~r~(Vehicle)Model ({CurrentVehicleModelHash}) cannot be loaded!");
|
||||
return false;
|
||||
if (MainVehicle == null)
|
||||
{
|
||||
Model.Request();
|
||||
return false;
|
||||
}
|
||||
|
||||
lock (EntityPool.VehiclesLock)
|
||||
{
|
||||
EntityPool.Add(this);
|
||||
}
|
||||
|
||||
MainVehicle.Quaternion = Quaternion;
|
||||
if (MainVehicle.HasRoof) MainVehicle.RoofState = RoofState;
|
||||
foreach (var w in MainVehicle.Wheels) w.Fix();
|
||||
if (IsInvincible) MainVehicle.IsInvincible = true;
|
||||
SetUpFixedData();
|
||||
Model.MarkAsNoLongerNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
#region -- CONSTRUCTORS --
|
||||
|
||||
/// <summary>
|
||||
/// Create a local entity (outgoing sync)
|
||||
/// </summary>
|
||||
/// <param name="v"></param>
|
||||
internal SyncedVehicle(Vehicle v)
|
||||
{
|
||||
ID = EntityPool.RequestNewID();
|
||||
MainVehicle = v;
|
||||
MainVehicle.CanPretendOccupants = false;
|
||||
OwnerID = LocalPlayerID;
|
||||
SetUpFixedData();
|
||||
}
|
||||
|
||||
internal void SetUpFixedData()
|
||||
{
|
||||
if (MainVehicle == null) return;
|
||||
|
||||
IsAircraft = MainVehicle.IsAircraft;
|
||||
IsMotorcycle = MainVehicle.IsMotorcycle;
|
||||
HasRocketBoost = MainVehicle.HasRocketBoost;
|
||||
HasParachute = MainVehicle.HasParachute;
|
||||
HasRoof = MainVehicle.HasRoof;
|
||||
IsSubmarineCar = MainVehicle.IsSubmarineCar;
|
||||
IsDeluxo = MainVehicle.Model == 1483171323;
|
||||
IsTrain = MainVehicle.IsTrain;
|
||||
AvalibleExtras = 0;
|
||||
for (int i = 1; i < 15; i++)
|
||||
{
|
||||
if (Call<bool>(DOES_EXTRA_EXIST, MainVehicle.Handle, i))
|
||||
AvalibleExtras |= (ushort)(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty VehicleEntity
|
||||
/// </summary>
|
||||
internal SyncedVehicle()
|
||||
{
|
||||
}
|
||||
|
||||
internal SyncedVehicle(int id)
|
||||
{
|
||||
ID = id;
|
||||
LastSynced = Ticked;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region -- PEDALING --
|
||||
|
||||
/*
|
||||
* Thanks to @oldnapalm.
|
||||
*/
|
||||
|
||||
private string PedalingAnimDict()
|
||||
{
|
||||
switch ((VehicleHash)Model)
|
||||
{
|
||||
case VehicleHash.Bmx:
|
||||
return "veh@bicycle@bmx@front@base";
|
||||
case VehicleHash.Cruiser:
|
||||
return "veh@bicycle@cruiserfront@base";
|
||||
case VehicleHash.Scorcher:
|
||||
return "veh@bicycle@mountainfront@base";
|
||||
default:
|
||||
return "veh@bicycle@roadfront@base";
|
||||
}
|
||||
}
|
||||
|
||||
private string PedalingAnimName(bool fast)
|
||||
{
|
||||
return fast ? "fast_pedal_char" : "cruise_pedal_char";
|
||||
}
|
||||
|
||||
private void StartPedalingAnim(bool fast)
|
||||
{
|
||||
MainVehicle.Driver?.Task.PlayAnimation(PedalingAnimDict(), PedalingAnimName(fast), 8.0f, -8.0f, -1,
|
||||
AnimationFlags.Loop | AnimationFlags.Secondary, 1.0f);
|
||||
}
|
||||
|
||||
private void StopPedalingAnim(bool fast)
|
||||
{
|
||||
MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Security.Cryptography;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using SHVDN;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static unsafe class EntityPool
|
||||
{
|
||||
public static object PedsLock = new object();
|
||||
|
||||
#region ACTIVE INSTANCES
|
||||
|
||||
public static Dictionary<int, SyncedPed> PedsByID = new Dictionary<int, SyncedPed>();
|
||||
public static Dictionary<int, SyncedPed> PedsByHandle = new Dictionary<int, SyncedPed>();
|
||||
|
||||
|
||||
public static Dictionary<int, SyncedVehicle> VehiclesByID = new Dictionary<int, SyncedVehicle>();
|
||||
public static Dictionary<int, SyncedVehicle> VehiclesByHandle = new Dictionary<int, SyncedVehicle>();
|
||||
|
||||
public static Dictionary<int, SyncedProjectile> ProjectilesByID = new Dictionary<int, SyncedProjectile>();
|
||||
public static Dictionary<int, SyncedProjectile> ProjectilesByHandle = new Dictionary<int, SyncedProjectile>();
|
||||
|
||||
public static Dictionary<int, SyncedProp> ServerProps = new Dictionary<int, SyncedProp>();
|
||||
public static Dictionary<int, Blip> ServerBlips = new Dictionary<int, Blip>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region LOCKS
|
||||
|
||||
public static object VehiclesLock = new object();
|
||||
public static object ProjectilesLock = new object();
|
||||
public static object PropsLock = new object();
|
||||
public static object BlipsLock = new object();
|
||||
|
||||
#endregion
|
||||
|
||||
public static void Cleanup(bool keepPlayer = true, bool keepMine = true)
|
||||
{
|
||||
foreach (var ped in PedsByID.Values.ToArray())
|
||||
{
|
||||
if ((keepPlayer && ped.ID == LocalPlayerID) ||
|
||||
(keepMine && ped.OwnerID == LocalPlayerID)) continue;
|
||||
RemovePed(ped.ID);
|
||||
}
|
||||
|
||||
PedsByID.Clear();
|
||||
PedsByHandle.Clear();
|
||||
|
||||
foreach (var id in VehiclesByID.Keys.ToArray())
|
||||
{
|
||||
if (keepMine && VehiclesByID[id].OwnerID == LocalPlayerID) continue;
|
||||
RemoveVehicle(id);
|
||||
}
|
||||
|
||||
VehiclesByID.Clear();
|
||||
VehiclesByHandle.Clear();
|
||||
|
||||
foreach (var p in ProjectilesByID.Values.ToArray())
|
||||
if (p.Shooter.ID != LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
|
||||
p.MainProjectile.Delete();
|
||||
ProjectilesByID.Clear();
|
||||
ProjectilesByHandle.Clear();
|
||||
|
||||
foreach (var p in ServerProps.Values) p?.MainProp?.Delete();
|
||||
ServerProps.Clear();
|
||||
|
||||
foreach (var b in ServerBlips.Values)
|
||||
if (b.Exists())
|
||||
b.Delete();
|
||||
ServerBlips.Clear();
|
||||
}
|
||||
|
||||
#region PEDS
|
||||
|
||||
public static SyncedPed GetPedByID(int id)
|
||||
=> PedsByID.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
public static SyncedPed GetPedByHandle(int handle)
|
||||
=> PedsByHandle.TryGetValue(handle, out var p) ? p : null;
|
||||
|
||||
public static List<int> GetPedIDs()
|
||||
{
|
||||
return new List<int>(PedsByID.Keys);
|
||||
}
|
||||
|
||||
public static bool AddPlayer()
|
||||
{
|
||||
var p = Game.Player.Character;
|
||||
// var clipset=p.Gender==Gender.Male? "MOVE_M@TOUGH_GUY@" : "MOVE_F@TOUGH_GUY@";
|
||||
// Call(SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
|
||||
var player = GetPedByID(LocalPlayerID);
|
||||
if (player == null)
|
||||
{
|
||||
Log.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
|
||||
var c = new SyncedPed(p);
|
||||
LocalPlayerID = c.OwnerID = c.ID;
|
||||
Add(c);
|
||||
Log.Debug($"Local player ID is:{c.ID}");
|
||||
PlayerList.SetPlayer(c.ID, Settings.Username);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (player.MainPed != p)
|
||||
{
|
||||
// Player model changed
|
||||
player.MainPed = p;
|
||||
|
||||
// Remove it from Handle_Characters
|
||||
var pairs = PedsByHandle.Where(x => x.Value == player);
|
||||
if (pairs.Any())
|
||||
{
|
||||
var pair = pairs.First();
|
||||
|
||||
// Re-add
|
||||
PedsByHandle.Remove(pair.Key);
|
||||
if (PedsByHandle.ContainsKey(p.Handle)) RemovePed(PedsByHandle[p.Handle].ID);
|
||||
PedsByHandle.Add(p.Handle, player);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Add(SyncedPed c)
|
||||
{
|
||||
if (PedsByID.ContainsKey(c.ID))
|
||||
PedsByID[c.ID] = c;
|
||||
else
|
||||
PedsByID.Add(c.ID, c);
|
||||
if (c.MainPed == null) return;
|
||||
if (PedsByHandle.ContainsKey(c.MainPed.Handle))
|
||||
PedsByHandle[c.MainPed.Handle] = c;
|
||||
else
|
||||
PedsByHandle.Add(c.MainPed.Handle, c);
|
||||
if (c.IsLocal) API.Events.InvokePedSpawned(c);
|
||||
}
|
||||
|
||||
public static void RemovePed(int id, string reason = "Cleanup")
|
||||
{
|
||||
if (PedsByID.ContainsKey(id))
|
||||
{
|
||||
var c = PedsByID[id];
|
||||
var p = c.MainPed;
|
||||
if (p != null)
|
||||
{
|
||||
if (PedsByHandle.ContainsKey(p.Handle)) PedsByHandle.Remove(p.Handle);
|
||||
// Log.Debug($"Removing ped {c.ID}. Reason:{reason}");
|
||||
p.AttachedBlip?.Delete();
|
||||
p.Kill();
|
||||
p.Model.MarkAsNoLongerNeeded();
|
||||
p.MarkAsNoLongerNeeded();
|
||||
p.Delete();
|
||||
}
|
||||
|
||||
c.PedBlip?.Delete();
|
||||
c.ParachuteProp?.Delete();
|
||||
PedsByID.Remove(id);
|
||||
if (c.IsLocal) API.Events.InvokePedDeleted(c);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region VEHICLES
|
||||
|
||||
public static SyncedVehicle GetVehicleByID(int id)
|
||||
=> VehiclesByID.TryGetValue(id, out var v) ? v : null;
|
||||
|
||||
public static SyncedVehicle GetVehicleByHandle(int handle)
|
||||
=> VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
|
||||
|
||||
public static List<int> GetVehicleIDs()
|
||||
{
|
||||
return new List<int>(VehiclesByID.Keys);
|
||||
}
|
||||
|
||||
public static void Add(SyncedVehicle v)
|
||||
{
|
||||
if (VehiclesByID.ContainsKey(v.ID))
|
||||
VehiclesByID[v.ID] = v;
|
||||
else
|
||||
VehiclesByID.Add(v.ID, v);
|
||||
if (v.MainVehicle == null) return;
|
||||
if (VehiclesByHandle.ContainsKey(v.MainVehicle.Handle))
|
||||
VehiclesByHandle[v.MainVehicle.Handle] = v;
|
||||
else
|
||||
VehiclesByHandle.Add(v.MainVehicle.Handle, v);
|
||||
if (v.IsLocal) API.Events.InvokeVehicleSpawned(v);
|
||||
}
|
||||
|
||||
public static void RemoveVehicle(int id, string reason = "Cleanup")
|
||||
{
|
||||
if (VehiclesByID.ContainsKey(id))
|
||||
{
|
||||
var v = VehiclesByID[id];
|
||||
var veh = v.MainVehicle;
|
||||
if (veh != null)
|
||||
{
|
||||
if (VehiclesByHandle.ContainsKey(veh.Handle)) VehiclesByHandle.Remove(veh.Handle);
|
||||
// Log.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
|
||||
veh.AttachedBlip?.Delete();
|
||||
veh.Model.MarkAsNoLongerNeeded();
|
||||
veh.MarkAsNoLongerNeeded();
|
||||
veh.Delete();
|
||||
}
|
||||
|
||||
VehiclesByID.Remove(id);
|
||||
if (v.IsLocal) API.Events.InvokeVehicleDeleted(v);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PROJECTILES
|
||||
|
||||
public static SyncedProjectile GetProjectileByID(int id)
|
||||
=> ProjectilesByID.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
public static void Add(SyncedProjectile p)
|
||||
{
|
||||
if (!p.IsValid) return;
|
||||
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank)
|
||||
{
|
||||
Networking.SendBullet(((SyncedVehicle)p.Shooter).MainVehicle.Driver.GetSyncEntity().ID, (uint)VehicleWeaponHash.Tank, p.Position + p.Velocity);
|
||||
return;
|
||||
}
|
||||
if (ProjectilesByID.ContainsKey(p.ID))
|
||||
ProjectilesByID[p.ID] = p;
|
||||
else
|
||||
ProjectilesByID.Add(p.ID, p);
|
||||
if (p.MainProjectile == null) return;
|
||||
if (ProjectilesByHandle.ContainsKey(p.MainProjectile.Handle))
|
||||
ProjectilesByHandle[p.MainProjectile.Handle] = p;
|
||||
else
|
||||
ProjectilesByHandle.Add(p.MainProjectile.Handle, p);
|
||||
}
|
||||
|
||||
public static void RemoveProjectile(int id, string reason)
|
||||
{
|
||||
if (ProjectilesByID.ContainsKey(id))
|
||||
{
|
||||
var sp = ProjectilesByID[id];
|
||||
var p = sp.MainProjectile;
|
||||
if (p != null)
|
||||
{
|
||||
if (ProjectilesByHandle.ContainsKey(p.Handle)) ProjectilesByHandle.Remove(p.Handle);
|
||||
Log.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
|
||||
p.Explode();
|
||||
}
|
||||
|
||||
ProjectilesByID.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool PedExists(int id)
|
||||
{
|
||||
return PedsByID.ContainsKey(id);
|
||||
}
|
||||
|
||||
public static bool VehicleExists(int id)
|
||||
{
|
||||
return VehiclesByID.ContainsKey(id);
|
||||
}
|
||||
|
||||
public static bool ProjectileExists(int id)
|
||||
{
|
||||
return ProjectilesByID.ContainsKey(id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SERVER OBJECTS
|
||||
|
||||
public static SyncedProp GetPropByID(int id)
|
||||
=> ServerProps.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
public static Blip GetBlipByID(int id)
|
||||
=> ServerBlips.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
#endregion
|
||||
|
||||
private static int vehStateIndex;
|
||||
private static int pedStateIndex;
|
||||
private static int vehStatesPerFrame;
|
||||
private static int pedStatesPerFrame;
|
||||
private static int i;
|
||||
|
||||
public static void DoSync()
|
||||
{
|
||||
UpdateTargets();
|
||||
|
||||
var allPeds = NativeMemory.GetPedHandles();
|
||||
var allVehicles = NativeMemory.GetVehicleHandles();
|
||||
var allProjectiles = NativeMemory.GetProjectileHandles();
|
||||
vehStatesPerFrame = allVehicles.Length * 2 / (int)FPS + 1;
|
||||
pedStatesPerFrame = allPeds.Length * 2 / (int)FPS + 1;
|
||||
|
||||
|
||||
|
||||
lock (ProjectilesLock)
|
||||
{
|
||||
foreach (var p in allProjectiles)
|
||||
if (!ProjectilesByHandle.ContainsKey(p))
|
||||
Add(new SyncedProjectile(Projectile.FromHandle(p)));
|
||||
|
||||
foreach (var p in ProjectilesByID.Values.ToArray())
|
||||
// Outgoing sync
|
||||
if (p.IsLocal)
|
||||
{
|
||||
if (p.MainProjectile.AttachedEntity == null)
|
||||
{
|
||||
// Prevent projectiles from exploding next to vehicle
|
||||
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank ||
|
||||
(p.MainProjectile.OwnerEntity?.EntityType == EntityType.Vehicle &&
|
||||
p.MainProjectile.Position.DistanceTo(p.Origin) < 2)) continue;
|
||||
Networking.SendProjectile(p);
|
||||
}
|
||||
}
|
||||
else // Incoming sync
|
||||
{
|
||||
if (p.Exploded || p.IsOutOfSync)
|
||||
RemoveProjectile(p.ID, "OutOfSync | Exploded");
|
||||
else
|
||||
p.Update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
i = -1;
|
||||
|
||||
lock (PedsLock)
|
||||
{
|
||||
AddPlayer();
|
||||
|
||||
foreach (var p in allPeds)
|
||||
{
|
||||
var c = GetPedByHandle(p);
|
||||
if (c == null && p != Game.Player.Character.Handle)
|
||||
{
|
||||
var type = Util.GetPopulationType(p);
|
||||
if (allPeds.Length > Settings.WorldPedSoftLimit
|
||||
&& type == EntityPopulationType.RandomAmbient
|
||||
&& !Call<bool>(IS_PED_IN_ANY_VEHICLE, p, 0))
|
||||
{
|
||||
Util.DeleteEntity(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Log.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
|
||||
c = new SyncedPed((Ped)Entity.FromHandle(p));
|
||||
|
||||
Add(c);
|
||||
}
|
||||
}
|
||||
var ps = PedsByID.Values.ToArray();
|
||||
pedStateIndex += pedStatesPerFrame;
|
||||
if (pedStateIndex >= ps.Length) pedStateIndex = 0;
|
||||
|
||||
foreach (var c in ps)
|
||||
{
|
||||
i++;
|
||||
if (c.MainPed != null && !c.MainPed.Exists())
|
||||
{
|
||||
RemovePed(c.ID, "non-existent");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Outgoing sync
|
||||
if (c.IsLocal)
|
||||
{
|
||||
// event check
|
||||
SyncEvents.Check(c);
|
||||
|
||||
Networking.SendPed(c, i - pedStateIndex < pedStatesPerFrame);
|
||||
}
|
||||
else // Incoming sync
|
||||
{
|
||||
c.Update();
|
||||
if (c.IsOutOfSync) RemovePed(c.ID, "OutOfSync");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var check = Ticked % 100 == 0;
|
||||
i = -1;
|
||||
lock (VehiclesLock)
|
||||
{
|
||||
foreach (var veh in allVehicles)
|
||||
if (!VehiclesByHandle.ContainsKey(veh))
|
||||
{
|
||||
var cveh = (Vehicle)Entity.FromHandle(veh);
|
||||
if (cveh == null)
|
||||
continue;
|
||||
if (allVehicles.Length > Settings.WorldVehicleSoftLimit)
|
||||
{
|
||||
var type = cveh.PopulationType;
|
||||
if (type == EntityPopulationType.RandomAmbient
|
||||
|| type == EntityPopulationType.RandomParked)
|
||||
{
|
||||
foreach (var p in cveh.Occupants)
|
||||
{
|
||||
p.Delete();
|
||||
var c = GetPedByHandle(p.Handle);
|
||||
if (c != null) RemovePed(c.ID, "ThrottleTraffic");
|
||||
}
|
||||
|
||||
cveh.Delete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Log.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
|
||||
|
||||
Add(new SyncedVehicle(cveh));
|
||||
}
|
||||
var vs = VehiclesByID.Values.ToArray();
|
||||
vehStateIndex += vehStatesPerFrame;
|
||||
if (vehStateIndex >= vs.Length) vehStateIndex = 0;
|
||||
|
||||
foreach (var v in vs)
|
||||
{
|
||||
i++;
|
||||
if (v.MainVehicle != null && !v.MainVehicle.Exists())
|
||||
{
|
||||
RemoveVehicle(v.ID, "non-existent");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (check) v.SetUpFixedData();
|
||||
// Outgoing sync
|
||||
if (v.IsLocal)
|
||||
{
|
||||
if (!v.MainVehicle.IsVisible) continue;
|
||||
SyncEvents.Check(v);
|
||||
|
||||
Networking.SendVehicle(v, i - vehStateIndex < vehStatesPerFrame);
|
||||
}
|
||||
else // Incoming sync
|
||||
{
|
||||
v.Update();
|
||||
if (v.IsOutOfSync) RemoveVehicle(v.ID, "OutOfSync");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Networking.Peer.FlushSendQueue();
|
||||
}
|
||||
|
||||
private static void UpdateTargets()
|
||||
{
|
||||
Networking.Targets = new List<NetConnection>(PlayerList.Players.Count) { Networking.ServerConnection };
|
||||
foreach (var p in PlayerList.Players.Values.ToArray())
|
||||
if (p.HasDirectConnection && p.Position.DistanceTo(PlayerPosition) < 500)
|
||||
Networking.Targets.Add(p.Connection);
|
||||
}
|
||||
|
||||
public static void RemoveAllFromPlayer(int playerPedId)
|
||||
{
|
||||
foreach (var p in PedsByID.Values.ToArray())
|
||||
if (p.OwnerID == playerPedId)
|
||||
RemovePed(p.ID);
|
||||
|
||||
foreach (var v in VehiclesByID.Values.ToArray())
|
||||
if (v.OwnerID == playerPedId)
|
||||
RemoveVehicle(v.ID);
|
||||
}
|
||||
|
||||
public static int RequestNewID()
|
||||
{
|
||||
var ID = 0;
|
||||
while (ID == 0 || PedsByID.ContainsKey(ID) || VehiclesByID.ContainsKey(ID) ||
|
||||
ProjectilesByID.ContainsKey(ID))
|
||||
{
|
||||
var rngBytes = new byte[4];
|
||||
|
||||
RandomNumberGenerator.Create().GetBytes(rngBytes);
|
||||
|
||||
// Convert the bytes into an integer
|
||||
ID = BitConverter.ToInt32(rngBytes, 0);
|
||||
}
|
||||
|
||||
return ID;
|
||||
}
|
||||
|
||||
public static string DumpDebug()
|
||||
{
|
||||
return $"\nID_Peds: {PedsByID.Count}" +
|
||||
$"\nHandle_Peds: {PedsByHandle.Count}" +
|
||||
$"\nID_Vehicles: {VehiclesByID.Count}" +
|
||||
$"\nHandle_vehicles: {VehiclesByHandle.Count}" +
|
||||
$"\nID_Projectiles: {ProjectilesByID.Count}" +
|
||||
$"\nHandle_Projectiles: {ProjectilesByHandle.Count}" +
|
||||
$"\npedStatesPerFrame: {pedStatesPerFrame}" +
|
||||
$"\nvehStatesPerFrame: {vehStatesPerFrame}";
|
||||
}
|
||||
|
||||
public static class ThreadSafe
|
||||
{
|
||||
public static void Add(SyncedVehicle v)
|
||||
{
|
||||
lock (VehiclesLock)
|
||||
{
|
||||
EntityPool.Add(v);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(SyncedPed p)
|
||||
{
|
||||
lock (PedsLock)
|
||||
{
|
||||
EntityPool.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(SyncedProjectile sp)
|
||||
{
|
||||
lock (ProjectilesLock)
|
||||
{
|
||||
EntityPool.Add(sp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class SyncEvents
|
||||
{
|
||||
#region TRIGGER
|
||||
|
||||
public static void TriggerPedKilled(SyncedPed victim)
|
||||
{
|
||||
Networking.SendSync(new Packets.PedKilled { VictimID = victim.ID }, ConnectionChannel.SyncEvents);
|
||||
}
|
||||
|
||||
public static void TriggerChangeOwner(int vehicleID, int newOwnerID)
|
||||
{
|
||||
Networking.SendSync(new Packets.OwnerChanged
|
||||
{
|
||||
ID = vehicleID,
|
||||
NewOwnerID = newOwnerID
|
||||
}, ConnectionChannel.SyncEvents, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
|
||||
public static void TriggerNozzleTransform(int vehID, bool hover)
|
||||
{
|
||||
Networking.SendSync(new Packets.NozzleTransform { VehicleID = vehID, Hover = hover },
|
||||
ConnectionChannel.SyncEvents);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HANDLE
|
||||
|
||||
public static ParticleEffectAsset CorePFXAsset = new ParticleEffectAsset("core");
|
||||
|
||||
private static void HandlePedKilled(Packets.PedKilled p)
|
||||
{
|
||||
EntityPool.GetPedByID(p.VictimID)?.MainPed?.Kill();
|
||||
}
|
||||
|
||||
private static void HandleOwnerChanged(Packets.OwnerChanged p)
|
||||
{
|
||||
var v = EntityPool.GetVehicleByID(p.ID);
|
||||
if (v == null) return;
|
||||
v.OwnerID = p.NewOwnerID;
|
||||
v.SetLastSynced(true);
|
||||
v.Position = v.MainVehicle.Position;
|
||||
v.Quaternion = v.MainVehicle.Quaternion;
|
||||
}
|
||||
|
||||
private static void HandleNozzleTransform(Packets.NozzleTransform p)
|
||||
{
|
||||
EntityPool.GetVehicleByID(p.VehicleID)?.MainVehicle?.SetNozzleAngel(p.Hover ? 1 : 0);
|
||||
}
|
||||
|
||||
private static void HandleBulletShot(int ownerID, uint weaponHash, Vector3 end)
|
||||
{
|
||||
var c = EntityPool.GetPedByID(ownerID);
|
||||
var p = c?.MainPed;
|
||||
if (p == null)
|
||||
{
|
||||
return;
|
||||
// p = Game.Player.Character;
|
||||
// Log.Warning("Failed to find owner for bullet");
|
||||
}
|
||||
|
||||
var damage = (int)p.GetWeaponDamage(weaponHash);
|
||||
|
||||
// Some weapon hash has some firing issue, so we need to replace it with known good ones
|
||||
weaponHash = WeaponUtil.GetWeaponFix(weaponHash);
|
||||
|
||||
// Request asset for muzzle flash
|
||||
if (!CorePFXAsset.IsLoaded) CorePFXAsset.Request();
|
||||
|
||||
// Request asset for materialising the bullet
|
||||
var asset = new WeaponAsset(weaponHash);
|
||||
if (!asset.IsLoaded) asset.Request();
|
||||
|
||||
var vehWeap = p.VehicleWeapon;
|
||||
bool isVeh = vehWeap != VehicleWeaponHash.Invalid;
|
||||
var bone = isVeh ? c.MainPed.CurrentVehicle.GetMuzzleBone(vehWeap) : c.MainPed.GetMuzzleBone();
|
||||
if (bone == null)
|
||||
{
|
||||
Log.Warning($"Failed to find muzzle bone for {(isVeh ? vehWeap : (WeaponHash)weaponHash)}, {(isVeh ? p.CurrentVehicle.DisplayName : "")}");
|
||||
return;
|
||||
}
|
||||
World.ShootBullet(bone.Position, end, p, asset, damage);
|
||||
|
||||
World.CreateParticleEffectNonLooped(CorePFXAsset,
|
||||
!isVeh && p.Weapons.Current.Components.GetSuppressorComponent().Active
|
||||
? "muz_pistol_silencer"
|
||||
: ((WeaponHash)weaponHash).GetFlashFX(isVeh), bone.Position, isVeh ? bone.GetRotation() : bone.Owner.Rotation);
|
||||
}
|
||||
|
||||
public static void HandleEvent(PacketType type, NetIncomingMessage msg)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case PacketType.BulletShot:
|
||||
{
|
||||
var p = msg.GetPacket<Packets.BulletShot>();
|
||||
HandleBulletShot(p.OwnerID, p.WeaponHash, p.EndPosition);
|
||||
break;
|
||||
}
|
||||
case PacketType.OwnerChanged:
|
||||
{
|
||||
HandleOwnerChanged(msg.GetPacket<Packets.OwnerChanged>());
|
||||
}
|
||||
break;
|
||||
case PacketType.PedKilled:
|
||||
{
|
||||
HandlePedKilled(msg.GetPacket<Packets.PedKilled>());
|
||||
}
|
||||
break;
|
||||
case PacketType.NozzleTransform:
|
||||
{
|
||||
HandleNozzleTransform(msg.GetPacket<Packets.NozzleTransform>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Networking.Peer.Recycle(msg);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CHECK EVENTS
|
||||
|
||||
public static void Check(SyncedPed c)
|
||||
{
|
||||
var subject = c.MainPed;
|
||||
|
||||
// Check bullets
|
||||
if (subject.IsShooting && !subject.IsUsingProjectileWeapon())
|
||||
{
|
||||
|
||||
var i = 0;
|
||||
|
||||
// Some weapon is not instant hit, so we may need to wait a few ticks to get the impact position
|
||||
bool getBulletImpact()
|
||||
{
|
||||
var endPos = subject.LastWeaponImpactPosition;
|
||||
var vehWeap = subject.VehicleWeapon;
|
||||
if (vehWeap == VehicleWeaponHash.Invalid)
|
||||
{
|
||||
// Ped weapon sync
|
||||
var pedWeap = subject.Weapons.Current.Hash;
|
||||
if (endPos != default)
|
||||
{
|
||||
Networking.SendBullet(c.ID, (uint)pedWeap, endPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get impact in next tick
|
||||
if (++i <= 5) return false;
|
||||
|
||||
// Exceeded maximum wait of 5 ticks, return (inaccurate) aim coordinate
|
||||
endPos = subject.GetAimCoord();
|
||||
Networking.SendBullet(c.ID, (uint)pedWeap, endPos);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Veh weapon sync
|
||||
if (endPos == default)
|
||||
{
|
||||
var veh = c.MainPed.CurrentVehicle;
|
||||
var b = veh.GetMuzzleBone(vehWeap);
|
||||
if (b == null)
|
||||
{
|
||||
Log.Warning($"Failed to find muzzle bone for {vehWeap}, {veh.DisplayName}");
|
||||
return true;
|
||||
}
|
||||
endPos = b.Position + b.ForwardVector * 200;
|
||||
}
|
||||
Networking.SendBullet(c.ID, (uint)vehWeap, endPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!getBulletImpact()) API.QueueAction(getBulletImpact);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Check(SyncedVehicle v)
|
||||
{
|
||||
if (v.MainVehicle == null || !v.MainVehicle.HasNozzle()) return;
|
||||
|
||||
if (v.LastNozzleAngle == 1 && v.MainVehicle.GetNozzleAngel() != 1)
|
||||
TriggerNozzleTransform(v.ID, false);
|
||||
else if (v.LastNozzleAngle == 0 && v.MainVehicle.GetNozzleAngel() != 0) TriggerNozzleTransform(v.ID, true);
|
||||
v.LastNozzleAngle = v.MainVehicle.GetNozzleAngel();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System.Threading;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class Voice
|
||||
{
|
||||
private static bool _stopping = false;
|
||||
private static WaveInEvent _waveIn;
|
||||
|
||||
private static readonly BufferedWaveProvider _waveProvider =
|
||||
new BufferedWaveProvider(new WaveFormat(16000, 16, 1));
|
||||
|
||||
private static Thread _thread;
|
||||
|
||||
public static bool WasInitialized()
|
||||
{
|
||||
return _thread != null;
|
||||
}
|
||||
|
||||
public static bool IsRecording()
|
||||
{
|
||||
return _waveIn != null;
|
||||
}
|
||||
|
||||
public static void ClearAll()
|
||||
{
|
||||
_waveProvider.ClearBuffer();
|
||||
|
||||
StopRecording();
|
||||
|
||||
if (_thread != null && _thread.IsAlive)
|
||||
{
|
||||
_stopping = true;
|
||||
_thread.Join();
|
||||
_thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void StopRecording()
|
||||
{
|
||||
if (!IsRecording())
|
||||
return;
|
||||
|
||||
_waveIn.StopRecording();
|
||||
_waveIn.Dispose();
|
||||
_waveIn = null;
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
if (WasInitialized())
|
||||
return;
|
||||
|
||||
// I tried without thread but the game will lag without
|
||||
_thread = ThreadManager.CreateThread(() =>
|
||||
{
|
||||
while (!_stopping && !IsUnloading)
|
||||
using (var wo = new WaveOutEvent())
|
||||
{
|
||||
wo.Init(_waveProvider);
|
||||
wo.Play();
|
||||
|
||||
while (wo.PlaybackState == PlaybackState.Playing) Thread.Sleep(100);
|
||||
}
|
||||
},"Voice");
|
||||
}
|
||||
|
||||
public static void StartRecording()
|
||||
{
|
||||
if (IsRecording())
|
||||
return;
|
||||
|
||||
_waveIn = new WaveInEvent
|
||||
{
|
||||
DeviceNumber = 0,
|
||||
BufferMilliseconds = 20,
|
||||
NumberOfBuffers = 1,
|
||||
WaveFormat = _waveProvider.WaveFormat
|
||||
};
|
||||
_waveIn.DataAvailable += WaveInDataAvailable;
|
||||
|
||||
_waveIn.StartRecording();
|
||||
}
|
||||
|
||||
public static void AddVoiceData(byte[] buffer, int recorded)
|
||||
{
|
||||
_waveProvider.AddSamples(buffer, 0, recorded);
|
||||
}
|
||||
|
||||
private static void WaveInDataAvailable(object sender, WaveInEventArgs e)
|
||||
{
|
||||
if (!IsRecording())
|
||||
return;
|
||||
|
||||
Networking.SendVoiceMessage(e.Buffer, e.BytesRecorded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Needed to properly stop all thread when the module unloads
|
||||
/// </summary>
|
||||
internal static class ThreadManager
|
||||
{
|
||||
private static readonly List<Thread> _threads = new();
|
||||
private static readonly Thread _watcher = new(RemoveStopped);
|
||||
static ThreadManager()
|
||||
{
|
||||
_watcher.Start();
|
||||
}
|
||||
private static void RemoveStopped()
|
||||
{
|
||||
while (!IsUnloading)
|
||||
{
|
||||
lock (_threads)
|
||||
{
|
||||
_threads.RemoveAll(t => !t.IsAlive);
|
||||
}
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
public static Thread CreateThread(Action callback, string name = "CoopThread", bool startNow = true)
|
||||
{
|
||||
lock (_threads)
|
||||
{
|
||||
var created = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
callback();
|
||||
}
|
||||
catch (ThreadInterruptedException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Unhandled exception caught in thread {Environment.CurrentManagedThreadId}", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.Debug($"Thread stopped: " + Environment.CurrentManagedThreadId);
|
||||
}
|
||||
})
|
||||
{
|
||||
Name = name
|
||||
};
|
||||
Log.Debug($"Thread created: {name}, id: {created.ManagedThreadId}");
|
||||
_threads.Add(created);
|
||||
if (startNow) created.Start();
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnUnload()
|
||||
{
|
||||
Log.Debug("Stopping background threads");
|
||||
lock (_threads)
|
||||
{
|
||||
foreach (var thread in _threads)
|
||||
{
|
||||
if (thread.IsAlive)
|
||||
{
|
||||
Log.Debug($"Waiting for thread {thread.ManagedThreadId} to stop");
|
||||
// thread.Interrupt(); PlatformNotSupportedException ?
|
||||
thread.Join();
|
||||
}
|
||||
}
|
||||
_threads.Clear();
|
||||
}
|
||||
Log.Debug("Stopping thread watcher");
|
||||
_watcher.Join();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<linker>
|
||||
<assembly fullname="RageCoop.Client" preserve="all" />
|
||||
<assembly fullname="RageCoop.Client.Scripting" preserve="all" />
|
||||
<assembly fullname="RageCoop.Core" preserve="all" />
|
||||
<assembly fullname="System.Collections">
|
||||
<type fullname="System.Collections.Generic.List`1" preserve="all" />
|
||||
<type fullname="System.Collections.Generic.Dictionary`2" preserve="all" />
|
||||
</assembly>
|
||||
</linker>
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using RageCoop.Core;
|
||||
using NativeMemory = SHVDN.NativeMemory;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal unsafe class MemPatch
|
||||
{
|
||||
private readonly IntPtr _address;
|
||||
private readonly byte[] _data;
|
||||
private readonly byte[] _orginal;
|
||||
|
||||
public MemPatch(byte* address, byte[] data)
|
||||
{
|
||||
_data = data;
|
||||
_orginal = new byte[data.Length];
|
||||
_address = (IntPtr)address;
|
||||
Marshal.Copy((IntPtr)address, _orginal, 0, data.Length);
|
||||
}
|
||||
|
||||
public void Install()
|
||||
{
|
||||
Marshal.Copy(_data, 0, _address, _data.Length);
|
||||
}
|
||||
|
||||
public void Uninstall()
|
||||
{
|
||||
Marshal.Copy(_orginal, 0, _address, _orginal.Length);
|
||||
}
|
||||
}
|
||||
|
||||
internal static unsafe class Memory
|
||||
{
|
||||
public static MemPatch VignettingPatch;
|
||||
public static MemPatch VignettingCallPatch;
|
||||
public static MemPatch TimeScalePatch;
|
||||
|
||||
static Memory()
|
||||
{
|
||||
// Weapon/radio wheel slow-mo patch
|
||||
// Thanks to @CamxxCore, https://github.com/CamxxCore/GTAVWeaponWheelMod
|
||||
var result = NativeMemory.FindPattern("\x38\x51\x64\x74\x19", "xxxxx");
|
||||
if (result == null)
|
||||
throw new NotSupportedException("Can't find memory pattern to patch weapon/radio slowdown");
|
||||
var address = result + 26;
|
||||
address = address + *(int*)address + 4u;
|
||||
VignettingPatch = new MemPatch(address, new byte[] { RET, 0x90, 0x90, 0x90, 0x90 });
|
||||
VignettingCallPatch = new MemPatch(result + 8, new byte[] { 0x90, 0x90, 0x90, 0x90, 0x90 });
|
||||
TimeScalePatch = new MemPatch(result + 34, new byte[] { XOR_32_64, 0xD2 });
|
||||
}
|
||||
|
||||
public static void ApplyPatches()
|
||||
{
|
||||
VignettingPatch.Install();
|
||||
VignettingCallPatch.Install();
|
||||
TimeScalePatch.Install();
|
||||
}
|
||||
|
||||
public static void RestorePatches()
|
||||
{
|
||||
VignettingPatch.Uninstall();
|
||||
VignettingCallPatch.Uninstall();
|
||||
TimeScalePatch.Uninstall();
|
||||
}
|
||||
|
||||
public static Vector3 ReadPosition(this Entity e)
|
||||
{
|
||||
return ReadVector3(e.MemoryAddress + PositionOffset);
|
||||
}
|
||||
|
||||
public static Quaternion ReadQuaternion(this Entity e)
|
||||
{
|
||||
return Quaternion.RotationMatrix(e.Matrix);
|
||||
}
|
||||
|
||||
public static Vector3 ReadRotation(this Entity e)
|
||||
{
|
||||
return e.ReadQuaternion().ToEulerDegrees();
|
||||
}
|
||||
|
||||
public static Vector3 ReadVelocity(this Ped e)
|
||||
{
|
||||
return ReadVector3(e.MemoryAddress + VelocityOffset);
|
||||
}
|
||||
|
||||
public static Vector3 ReadVector3(IntPtr address)
|
||||
{
|
||||
var ptr = (float*)address.ToPointer();
|
||||
return new Vector3
|
||||
{
|
||||
X = *ptr,
|
||||
Y = ptr[1],
|
||||
Z = ptr[2]
|
||||
};
|
||||
}
|
||||
|
||||
public static List<int> FindOffset(float toSearch, IntPtr start, int range = 1000, float tolerance = 0.01f)
|
||||
{
|
||||
var foundOffsets = new List<int>(100);
|
||||
for (var i = 0; i <= range; i++)
|
||||
{
|
||||
var val = NativeMemory.ReadFloat(start + i);
|
||||
if (Math.Abs(val - toSearch) < tolerance) foundOffsets.Add(i);
|
||||
}
|
||||
|
||||
return foundOffsets;
|
||||
}
|
||||
|
||||
#region PATCHES
|
||||
|
||||
#endregion
|
||||
|
||||
#region OFFSET-CONST
|
||||
|
||||
public const int PositionOffset = 144;
|
||||
public const int VelocityOffset = 800;
|
||||
public const int MatrixOffset = 96;
|
||||
|
||||
#endregion
|
||||
|
||||
#region OPCODE
|
||||
|
||||
private const byte XOR_32_64 = 0x31;
|
||||
private const byte RET = 0xC3;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 80)]
|
||||
public struct HeadBlendData
|
||||
{
|
||||
[FieldOffset(0)] public int ShapeFirst;
|
||||
|
||||
[FieldOffset(8)] public int ShapeSecond;
|
||||
|
||||
[FieldOffset(16)] public int ShapeThird;
|
||||
|
||||
[FieldOffset(24)] public int SkinFirst;
|
||||
|
||||
[FieldOffset(32)] public int SkinSecond;
|
||||
|
||||
[FieldOffset(40)] public int SkinThird;
|
||||
|
||||
[FieldOffset(48)] public float ShapeMix;
|
||||
|
||||
[FieldOffset(56)] public float SkinMix;
|
||||
|
||||
[FieldOffset(64)] public float ThirdMix;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 24)]
|
||||
public struct NativeVector3
|
||||
{
|
||||
[FieldOffset(0)] public float X;
|
||||
|
||||
[FieldOffset(8)] public float Y;
|
||||
|
||||
[FieldOffset(16)] public float Z;
|
||||
|
||||
public static implicit operator Vector3(NativeVector3 vec)
|
||||
{
|
||||
return new Vector3 { X = vec.X, Y = vec.Y, Z = vec.Z };
|
||||
}
|
||||
|
||||
public static implicit operator NativeVector3(Vector3 vec)
|
||||
{
|
||||
return new NativeVector3 { X = vec.X, Y = vec.Y, Z = vec.Z };
|
||||
}
|
||||
}
|
||||
|
||||
public static class NativeCaller
|
||||
{
|
||||
// These are borrowed from ScriptHookVDotNet's
|
||||
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeInit@@YAX_K@Z")]
|
||||
private static extern void NativeInit(ulong hash);
|
||||
|
||||
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativePush64@@YAX_K@Z")]
|
||||
private static extern void NativePush64(ulong val);
|
||||
|
||||
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeCall@@YAPEA_KXZ")]
|
||||
private static extern unsafe ulong* NativeCall();
|
||||
|
||||
// These are from ScriptHookV's nativeCaller.h
|
||||
private static unsafe void NativePush<T>(T val) where T : unmanaged
|
||||
{
|
||||
ulong val64 = 0;
|
||||
*(T*)(&val64) = val;
|
||||
NativePush64(val64);
|
||||
}
|
||||
|
||||
public static unsafe R Invoke<R>(ulong hash) where R : unmanaged
|
||||
{
|
||||
NativeInit(hash);
|
||||
return *(R*)NativeCall();
|
||||
}
|
||||
|
||||
public static unsafe R Invoke<R>(Hash hash, params object[] args)
|
||||
where R : unmanaged
|
||||
{
|
||||
NativeInit((ulong)hash);
|
||||
var arguments = ConvertPrimitiveArguments(args);
|
||||
foreach (var arg in arguments)
|
||||
NativePush(arg);
|
||||
|
||||
return *(R*)NativeCall();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper function that converts an array of primitive values to a native stack.
|
||||
/// </summary>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
private static unsafe ulong[] ConvertPrimitiveArguments(object[] args)
|
||||
{
|
||||
var result = new ulong[args.Length];
|
||||
for (var i = 0; i < args.Length; ++i)
|
||||
{
|
||||
if (args[i] is bool valueBool)
|
||||
{
|
||||
result[i] = valueBool ? 1ul : 0ul;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is byte valueByte)
|
||||
{
|
||||
result[i] = valueByte;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is int valueInt32)
|
||||
{
|
||||
result[i] = (ulong)valueInt32;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is ulong valueUInt64)
|
||||
{
|
||||
result[i] = valueUInt64;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is float valueFloat)
|
||||
{
|
||||
result[i] = *(ulong*)&valueFloat;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args[i] is IntPtr valueIntPtr)
|
||||
{
|
||||
result[i] = (ulong)valueIntPtr.ToInt64();
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unknown primitive type in native argument list", nameof(args));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
global using static RageCoop.Client.PInvoke;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal partial class PInvoke
|
||||
{
|
||||
public static void ClearLastError()
|
||||
{
|
||||
SetLastErrorEx(0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check and throw if an error occurred during last winapi call in current thread
|
||||
/// </summary>
|
||||
public static void ErrorCheck32()
|
||||
{
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
if (error != 0)
|
||||
{
|
||||
ClearLastError();
|
||||
throw new Win32Exception(error);
|
||||
}
|
||||
}
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
internal static partial void SetLastErrorEx(uint dwErrCode, uint dwType);
|
||||
|
||||
[LibraryImport("Kernel32.dll", SetLastError = true)]
|
||||
public static unsafe partial uint GetFinalPathNameByHandleW(IntPtr hFile, char* lpszFilePath, uint cchFilePath, uint dwFlags);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
// Potential names and hash collisions included as comments
|
||||
internal enum PedConfigFlags
|
||||
{
|
||||
_0x67D1A445 = 0,
|
||||
_0xC63DE95E = 1,
|
||||
CPED_CONFIG_FLAG_NoCriticalHits = 2,
|
||||
CPED_CONFIG_FLAG_DrownsInWater = 3,
|
||||
CPED_CONFIG_FLAG_DisableReticuleFixedLockon = 4,
|
||||
_0x37D196F4 = 5,
|
||||
_0xE2462399 = 6,
|
||||
CPED_CONFIG_FLAG_UpperBodyDamageAnimsOnly = 7,
|
||||
_0xEDDEB838 = 8,
|
||||
_0xB398B6FD = 9,
|
||||
_0xF6664E68 = 10,
|
||||
_0xA05E7CA3 = 11,
|
||||
_0xCE394045 = 12,
|
||||
CPED_CONFIG_FLAG_NeverLeavesGroup = 13,
|
||||
_0xCD8D1411 = 14,
|
||||
_0xB031F1A9 = 15,
|
||||
_0xFE65BEE3 = 16,
|
||||
CPED_CONFIG_FLAG_BlockNonTemporaryEvents = 17,
|
||||
_0x380165BD = 18,
|
||||
_0x07C045C7 = 19,
|
||||
_0x583B5E2D = 20,
|
||||
_0x475EDA58 = 21,
|
||||
_0x8629D05B = 22,
|
||||
_0x1522968B = 23,
|
||||
CPED_CONFIG_FLAG_IgnoreSeenMelee = 24,
|
||||
_0x4CC09C4B = 25,
|
||||
_0x034F3053 = 26,
|
||||
_0xD91BA7CC = 27,
|
||||
_0x5C8DC66E = 28,
|
||||
_0x8902EAA0 = 29,
|
||||
_0x6580B9D2 = 30,
|
||||
_0x0EF7A297 = 31,
|
||||
_0x6BF86E5B = 32,
|
||||
CPED_CONFIG_FLAG_DieWhenRagdoll = 33,
|
||||
CPED_CONFIG_FLAG_HasHelmet = 34,
|
||||
CPED_CONFIG_FLAG_UseHelmet = 35,
|
||||
_0xEEB3D630 = 36,
|
||||
_0xB130D17B = 37,
|
||||
_0x5F071200 = 38,
|
||||
CPED_CONFIG_FLAG_DisableEvasiveDives = 39,
|
||||
_0xC287AAFF = 40,
|
||||
_0x203328CC = 41,
|
||||
CPED_CONFIG_FLAG_DontInfluenceWantedLevel = 42,
|
||||
CPED_CONFIG_FLAG_DisablePlayerLockon = 43,
|
||||
CPED_CONFIG_FLAG_DisableLockonToRandomPeds = 44,
|
||||
_0xEC4A8ACF = 45,
|
||||
_0xDB115BFA = 46,
|
||||
CPED_CONFIG_FLAG_PedBeingDeleted = 47,
|
||||
CPED_CONFIG_FLAG_BlockWeaponSwitching = 48,
|
||||
_0xF8E99565 = 49,
|
||||
_0xDD17FEE6 = 50,
|
||||
_0x7ED9B2C9 = 51,
|
||||
_0x655E8618 = 52,
|
||||
_0x5A6C1F6E = 53,
|
||||
_0xD749FC41 = 54,
|
||||
_0x357F63F3 = 55,
|
||||
_0xC5E60961 = 56,
|
||||
_0x29275C3E = 57,
|
||||
CPED_CONFIG_FLAG_IsFiring = 58,
|
||||
CPED_CONFIG_FLAG_WasFiring = 59,
|
||||
CPED_CONFIG_FLAG_IsStanding = 60,
|
||||
CPED_CONFIG_FLAG_WasStanding = 61,
|
||||
CPED_CONFIG_FLAG_InVehicle = 62,
|
||||
CPED_CONFIG_FLAG_OnMount = 63,
|
||||
CPED_CONFIG_FLAG_AttachedToVehicle = 64,
|
||||
CPED_CONFIG_FLAG_IsSwimming = 65,
|
||||
CPED_CONFIG_FLAG_WasSwimming = 66,
|
||||
CPED_CONFIG_FLAG_IsSkiing = 67,
|
||||
CPED_CONFIG_FLAG_IsSitting = 68,
|
||||
CPED_CONFIG_FLAG_KilledByStealth = 69,
|
||||
CPED_CONFIG_FLAG_KilledByTakedown = 70,
|
||||
CPED_CONFIG_FLAG_Knockedout = 71,
|
||||
_0x3E3C4560 = 72,
|
||||
_0x2994C7B7 = 73,
|
||||
_0x6D59D275 = 74,
|
||||
CPED_CONFIG_FLAG_UsingCoverPoint = 75,
|
||||
CPED_CONFIG_FLAG_IsInTheAir = 76,
|
||||
_0x2D493FB7 = 77,
|
||||
CPED_CONFIG_FLAG_IsAimingGun = 78,
|
||||
_0x14D69875 = 79,
|
||||
_0x40B05311 = 80,
|
||||
_0x8B230BC5 = 81,
|
||||
_0xC74E5842 = 82,
|
||||
_0x9EA86147 = 83,
|
||||
_0x674C746C = 84,
|
||||
_0x3E56A8C2 = 85,
|
||||
_0xC144A1EF = 86,
|
||||
_0x0548512D = 87,
|
||||
_0x31C93909 = 88,
|
||||
_0xA0269315 = 89,
|
||||
_0xD4D59D4D = 90,
|
||||
_0x411D4420 = 91,
|
||||
_0xDF4AEF0D = 92,
|
||||
CPED_CONFIG_FLAG_ForcePedLoadCover = 93,
|
||||
_0x300E4CD3 = 94,
|
||||
_0xF1C5BF04 = 95,
|
||||
_0x89C2EF13 = 96,
|
||||
CPED_CONFIG_FLAG_VaultFromCover = 97,
|
||||
_0x02A852C8 = 98,
|
||||
_0x3D9407F1 = 99,
|
||||
_0x319B4558 = 100,
|
||||
CPED_CONFIG_FLAG_ForcedAim = 101,
|
||||
_0xB942D71A = 102,
|
||||
_0xD26C55A8 = 103,
|
||||
_0xB89E703B = 104,
|
||||
CPED_CONFIG_FLAG_ForceReload = 105,
|
||||
_0xD9E73DA2 = 106,
|
||||
_0xFF71DC2C = 107,
|
||||
_0x1E27E8D8 = 108,
|
||||
_0xF2C53966 = 109,
|
||||
_0xC4DBE247 = 110,
|
||||
_0x83C0A4BF = 111,
|
||||
_0x0E0FAF8C = 112,
|
||||
_0x26616660 = 113,
|
||||
_0x43B80B79 = 114,
|
||||
_0x0D2A9309 = 115,
|
||||
_0x12C1C983 = 116,
|
||||
CPED_CONFIG_FLAG_BumpedByPlayer = 117,
|
||||
_0xE586D504 = 118,
|
||||
_0x52374204 = 119,
|
||||
CPED_CONFIG_FLAG_IsHandCuffed = 120,
|
||||
CPED_CONFIG_FLAG_IsAnkleCuffed = 121,
|
||||
CPED_CONFIG_FLAG_DisableMelee = 122,
|
||||
_0xFE714397 = 123,
|
||||
_0xB3E660BD = 124,
|
||||
_0x5FED6BFD = 125,
|
||||
_0xC9D6F66F = 126,
|
||||
_0x519BC986 = 127,
|
||||
CPED_CONFIG_FLAG_CanBeAgitated = 128,
|
||||
_0x9A4B617C = 129, // CPED_CONFIG_FLAG_FaceDirInsult
|
||||
_0xDAB70E9F = 130,
|
||||
_0xE569438A = 131,
|
||||
_0xBBC77D6D = 132,
|
||||
_0xCB59EF0F = 133,
|
||||
_0x8C5EA971 = 134,
|
||||
CPED_CONFIG_FLAG_IsScuba = 135,
|
||||
CPED_CONFIG_FLAG_WillArrestRatherThanJack = 136,
|
||||
_0xDCE59B58 = 137,
|
||||
CPED_CONFIG_FLAG_RidingTrain = 138,
|
||||
CPED_CONFIG_FLAG_ArrestResult = 139,
|
||||
CPED_CONFIG_FLAG_CanAttackFriendly = 140,
|
||||
_0x98A4BE43 = 141,
|
||||
_0x6901E731 = 142,
|
||||
_0x9EC9BF6C = 143,
|
||||
_0x42841A8F = 144,
|
||||
CPED_CONFIG_FLAG_ShootingAnimFlag = 145,
|
||||
CPED_CONFIG_FLAG_DisableLadderClimbing = 146,
|
||||
CPED_CONFIG_FLAG_StairsDetected = 147,
|
||||
CPED_CONFIG_FLAG_SlopeDetected = 148,
|
||||
_0x1A15670B = 149,
|
||||
_0x61786EE5 = 150,
|
||||
_0xCB9186BD = 151,
|
||||
_0xF0710152 = 152,
|
||||
_0x43DFE310 = 153,
|
||||
_0xC43C624E = 154,
|
||||
CPED_CONFIG_FLAG_CanPerformArrest = 155,
|
||||
CPED_CONFIG_FLAG_CanPerformUncuff = 156,
|
||||
CPED_CONFIG_FLAG_CanBeArrested = 157,
|
||||
_0xF7960FF5 = 158,
|
||||
_0x59564113 = 159,
|
||||
_0x0C6C3099 = 160,
|
||||
_0x645F927A = 161,
|
||||
_0xA86549B9 = 162,
|
||||
_0x8AAF337A = 163,
|
||||
_0x13BAA6E7 = 164,
|
||||
_0x5FB9D1F5 = 165,
|
||||
CPED_CONFIG_FLAG_IsInjured = 166,
|
||||
_0x6398A20B = 167,
|
||||
_0xD8072639 = 168,
|
||||
_0xA05B1845 = 169,
|
||||
_0x83F6D220 = 170,
|
||||
_0xD8430331 = 171,
|
||||
_0x4B547520 = 172,
|
||||
_0xE66E1406 = 173,
|
||||
_0x1C4BFE0C = 174,
|
||||
_0x90008BFA = 175,
|
||||
_0x07C7A910 = 176,
|
||||
_0xF15F8191 = 177,
|
||||
_0xCE4E8BE2 = 178,
|
||||
_0x1D46E4F2 = 179,
|
||||
CPED_CONFIG_FLAG_IsInCustody = 180,
|
||||
_0xE4FD9B3A = 181,
|
||||
_0x67AE0812 = 182,
|
||||
CPED_CONFIG_FLAG_IsAgitated = 183,
|
||||
CPED_CONFIG_FLAG_PreventAutoShuffleToDriversSeat = 184,
|
||||
_0x7B2D325E = 185,
|
||||
CPED_CONFIG_FLAG_EnableWeaponBlocking = 186,
|
||||
CPED_CONFIG_FLAG_HasHurtStarted = 187,
|
||||
CPED_CONFIG_FLAG_DisableHurt = 188,
|
||||
CPED_CONFIG_FLAG_PlayerIsWeird = 189,
|
||||
_0x32FC208B = 190,
|
||||
_0x0C296E5A = 191,
|
||||
_0xE63B73EC = 192,
|
||||
_0x04E9CC80 = 193,
|
||||
CPED_CONFIG_FLAG_UsingScenario = 194,
|
||||
CPED_CONFIG_FLAG_VisibleOnScreen = 195,
|
||||
_0xD88C58A1 = 196,
|
||||
_0x5A3DCF43 = 197, // CPED_CONFIG_FLAG_AvoidUnderSide
|
||||
_0xEA02B420 = 198,
|
||||
_0x3F559CFF = 199,
|
||||
_0x8C55D029 = 200,
|
||||
_0x5E6466F6 = 201,
|
||||
_0xEB5AD706 = 202,
|
||||
_0x0EDDDDE7 = 203,
|
||||
_0xA64F7B1D = 204,
|
||||
_0x48532CBA = 205,
|
||||
_0xAA25A9E7 = 206,
|
||||
_0x415B26B9 = 207,
|
||||
CPED_CONFIG_FLAG_DisableExplosionReactions = 208,
|
||||
CPED_CONFIG_FLAG_DodgedPlayer = 209,
|
||||
_0x67405504 = 210,
|
||||
_0x75DDD68C = 211,
|
||||
_0x2AD879B4 = 212,
|
||||
_0x51486F91 = 213,
|
||||
_0x32F79E21 = 214,
|
||||
_0xBF099213 = 215,
|
||||
_0x054AC8E2 = 216,
|
||||
_0x14E495CC = 217,
|
||||
_0x3C7DF9DF = 218,
|
||||
_0x848FFEF2 = 219,
|
||||
CPED_CONFIG_FLAG_DontEnterLeadersVehicle = 220,
|
||||
_0x2618E1CF = 221,
|
||||
_0x84F722FA = 222,
|
||||
_0xD1B87B1F = 223,
|
||||
_0x728AA918 = 224,
|
||||
CPED_CONFIG_FLAG_DisablePotentialToBeWalkedIntoResponse = 225,
|
||||
CPED_CONFIG_FLAG_DisablePedAvoidance = 226,
|
||||
_0x59E91185 = 227,
|
||||
_0x1EA7225F = 228,
|
||||
CPED_CONFIG_FLAG_DisablePanicInVehicle = 229,
|
||||
_0x6DCA7D88 = 230,
|
||||
_0xFC3E572D = 231,
|
||||
_0x08E9F9CF = 232,
|
||||
_0x2D3BA52D = 233,
|
||||
_0xFD2F53EA = 234,
|
||||
_0x31A1B03B = 235,
|
||||
CPED_CONFIG_FLAG_IsHoldingProp = 236,
|
||||
_0x82ED0A66 = 237, // CPED_CONFIG_FLAG_BlocksPathingWhenDead
|
||||
_0xCE57C9A3 = 238,
|
||||
_0x26149198 = 239,
|
||||
_0x1B33B598 = 240,
|
||||
_0x719B6E87 = 241,
|
||||
_0x13E8E8E8 = 242,
|
||||
_0xF29739AE = 243,
|
||||
_0xABEA8A74 = 244,
|
||||
_0xB60EA2BA = 245,
|
||||
_0x536B0950 = 246,
|
||||
_0x0C754ACA = 247,
|
||||
CPED_CONFIG_FLAG_DisableVehicleSeatRandomAnimations = 248,
|
||||
_0x12659168 = 249,
|
||||
_0x1BDF2F04 = 250,
|
||||
_0x7728FAA3 = 251,
|
||||
_0x6A807ED8 = 252,
|
||||
CPED_CONFIG_FLAG_OnStairs = 253,
|
||||
_0xE1A2F73F = 254,
|
||||
_0x5B3697C8 = 255,
|
||||
_0xF1EB20A9 = 256,
|
||||
_0x8B7DF407 = 257,
|
||||
_0x329DCF1A = 258,
|
||||
_0x8D90DD1B = 259,
|
||||
_0xB8A292B7 = 260,
|
||||
_0x8374B087 = 261,
|
||||
_0x2AF558F0 = 262,
|
||||
_0x82251455 = 263,
|
||||
_0x30CF498B = 264,
|
||||
_0xE1CD50AF = 265,
|
||||
_0x72E4AE48 = 266,
|
||||
_0xC2657EA1 = 267,
|
||||
_0x29FF6030 = 268,
|
||||
_0x8248A5EC = 269,
|
||||
CPED_CONFIG_FLAG_OnStairSlope = 270,
|
||||
_0xA0897933 = 271,
|
||||
CPED_CONFIG_FLAG_DontBlipCop = 272,
|
||||
CPED_CONFIG_FLAG_ClimbedShiftedFence = 273,
|
||||
_0xF7823618 = 274,
|
||||
_0xDC305CCE = 275, // CPED_CONFIG_FLAG_KillWhenTrapped
|
||||
CPED_CONFIG_FLAG_EdgeDetected = 276,
|
||||
_0x92B67896 = 277,
|
||||
_0xCAD677C9 = 278,
|
||||
CPED_CONFIG_FLAG_AvoidTearGas = 279,
|
||||
_0x5276AC7B = 280,
|
||||
_0x1032692A = 281,
|
||||
_0xDA23E7F1 = 282,
|
||||
_0x9139724D = 283,
|
||||
_0xA1457461 = 284,
|
||||
_0x4186E095 = 285,
|
||||
_0xAC68E2EB = 286,
|
||||
CPED_CONFIG_FLAG_RagdollingOnBoat = 287,
|
||||
CPED_CONFIG_FLAG_HasBrandishedWeapon = 288,
|
||||
_0x1B9EE8A1 = 289,
|
||||
_0xF3F5758C = 290,
|
||||
_0x2A9307F1 = 291,
|
||||
_0x7403D216 = 292,
|
||||
_0xA06A3C6C = 293,
|
||||
CPED_CONFIG_FLAG_DisableShockingEvents = 294,
|
||||
_0xF8DA25A5 = 295,
|
||||
_0x7EF55802 = 296,
|
||||
_0xB31F1187 = 297,
|
||||
_0x84315402 = 298,
|
||||
_0x0FD69867 = 299,
|
||||
_0xC7829B67 = 300,
|
||||
CPED_CONFIG_FLAG_DisablePedConstraints = 301,
|
||||
_0x6D23CF25 = 302,
|
||||
_0x2ADA871B = 303,
|
||||
_0x47BC8A58 = 304,
|
||||
_0xEB692FA5 = 305,
|
||||
_0x4A133C50 = 306,
|
||||
_0xC58099C3 = 307,
|
||||
_0xF3D76D41 = 308,
|
||||
_0xB0EEE9F2 = 309,
|
||||
CPED_CONFIG_FLAG_IsInCluster = 310,
|
||||
_0x0FA153EF = 311,
|
||||
_0xD73F5CD3 = 312,
|
||||
_0xD4136C22 = 313,
|
||||
_0xE404CA6B = 314,
|
||||
_0xB9597446 = 315,
|
||||
_0xD5C98277 = 316,
|
||||
_0xD5060A9C = 317,
|
||||
_0x3E5F1CBB = 318,
|
||||
_0xD8BE1D54 = 319,
|
||||
_0x0B1F191F = 320,
|
||||
_0xC995167A = 321,
|
||||
CPED_CONFIG_FLAG_HasHighHeels = 322,
|
||||
_0x86B01E54 = 323,
|
||||
_0x3A56FE15 = 324,
|
||||
_0xC03B736C = 325, // CPED_CONFIG_FLAG_SpawnedAtScenario
|
||||
_0xBBF47729 = 326,
|
||||
_0x22B668A8 = 327,
|
||||
_0x2624D4D4 = 328,
|
||||
CPED_CONFIG_FLAG_DisableTalkTo = 329,
|
||||
CPED_CONFIG_FLAG_DontBlip = 330,
|
||||
CPED_CONFIG_FLAG_IsSwitchingWeapon = 331,
|
||||
_0x630F55F3 = 332,
|
||||
_0x150468FD = 333,
|
||||
_0x914EBD6B = 334,
|
||||
_0x79AF3B6D = 335,
|
||||
_0x75C7A632 = 336,
|
||||
_0x52D530E2 = 337,
|
||||
_0xDB2A90E0 = 338,
|
||||
_0x5922763D = 339,
|
||||
_0x12ADB567 = 340,
|
||||
_0x105C8518 = 341,
|
||||
_0x106F703D = 342,
|
||||
_0xED152C3E = 343,
|
||||
_0xA0EFE6A8 = 344,
|
||||
_0xBF348C82 = 345,
|
||||
_0xCDDFE830 = 346,
|
||||
_0x7B59BD9B = 347,
|
||||
_0x0124C788 = 348,
|
||||
CPED_CONFIG_FLAG_EquipJetpack = 349,
|
||||
_0x08D361A5 = 350,
|
||||
_0xE13D1F7C = 351,
|
||||
_0x40E25FB9 = 352,
|
||||
_0x930629D9 = 353,
|
||||
_0xECCF0C7F = 354,
|
||||
_0xB6E9613B = 355,
|
||||
_0x490C0478 = 356,
|
||||
_0xE8865BEA = 357,
|
||||
_0xF3C34A29 = 358,
|
||||
CPED_CONFIG_FLAG_IsDuckingInVehicle = 359,
|
||||
_0xF660E115 = 360,
|
||||
_0xAB0E6DED = 361,
|
||||
CPED_CONFIG_FLAG_HasReserveParachute = 362,
|
||||
CPED_CONFIG_FLAG_UseReserveParachute = 363,
|
||||
_0x5C5D9CD3 = 364,
|
||||
_0x8F7701F3 = 365,
|
||||
_0xBC4436AD = 366,
|
||||
_0xD7E07D37 = 367,
|
||||
_0x03C4FD24 = 368,
|
||||
_0x7675789A = 369,
|
||||
_0xB7288A88 = 370,
|
||||
_0xC06B6291 = 371,
|
||||
_0x95A4A805 = 372,
|
||||
_0xA8E9A042 = 373,
|
||||
CPED_CONFIG_FLAG_NeverLeaveTrain = 374,
|
||||
_0xBAC674B3 = 375,
|
||||
_0x147F1FFB = 376,
|
||||
_0x4376DD79 = 377,
|
||||
_0xCD3DB518 = 378,
|
||||
_0xFE4BA4B6 = 379,
|
||||
_0x5DF03A55 = 380,
|
||||
_0xBCD816CD = 381,
|
||||
_0xCF02DD69 = 382,
|
||||
_0xF73AFA2E = 383,
|
||||
_0x80B9A9D0 = 384,
|
||||
_0xF601F7EE = 385,
|
||||
_0xA91350FC = 386,
|
||||
_0x3AB23B96 = 387,
|
||||
CPED_CONFIG_FLAG_IsClimbingLadder = 388,
|
||||
CPED_CONFIG_FLAG_HasBareFeet = 389,
|
||||
_0xB4B1CD4C = 390,
|
||||
_0x5459AFB8 = 391,
|
||||
_0x54F27667 = 392,
|
||||
_0xC11D3E8F = 393,
|
||||
_0x5419EB3E = 394,
|
||||
_0x82D8DBB4 = 395,
|
||||
_0x33B02D2F = 396,
|
||||
_0xAE66176D = 397,
|
||||
_0xA2692593 = 398,
|
||||
_0x714C7E31 = 399,
|
||||
_0xEC488AC7 = 400,
|
||||
_0xAE398504 = 401,
|
||||
_0xABC58D72 = 402,
|
||||
_0x5E5B9591 = 403,
|
||||
_0x6BA1091E = 404,
|
||||
_0x77840177 = 405,
|
||||
_0x1C7ACAC4 = 406,
|
||||
_0x124420E9 = 407,
|
||||
_0x75A65587 = 408,
|
||||
_0xDFD2D55B = 409,
|
||||
_0xBDD39919 = 410,
|
||||
_0x43DEC267 = 411,
|
||||
_0xE42B7797 = 412,
|
||||
CPED_CONFIG_FLAG_IsHolsteringWeapon = 413,
|
||||
_0x4F8149F5 = 414,
|
||||
_0xDD9ECA7A = 415,
|
||||
_0x9E7EF9D2 = 416,
|
||||
_0x2C6ED942 = 417,
|
||||
CPED_CONFIG_FLAG_IsSwitchingHelmetVisor = 418,
|
||||
_0xA488727D = 419,
|
||||
_0xCFF5F6DE = 420,
|
||||
_0x6D614599 = 421,
|
||||
CPED_CONFIG_FLAG_DisableVehicleCombat = 422,
|
||||
_0xFE401D26 = 423,
|
||||
CPED_CONFIG_FLAG_FallsLikeAircraft = 424,
|
||||
_0x2B42AE82 = 425,
|
||||
_0x7A95734F = 426,
|
||||
_0xDF4D8617 = 427,
|
||||
_0x578F1F14 = 428,
|
||||
CPED_CONFIG_FLAG_DisableStartEngine = 429,
|
||||
CPED_CONFIG_FLAG_IgnoreBeingOnFire = 430,
|
||||
_0x153C9500 = 431,
|
||||
_0xCB7A632E = 432,
|
||||
_0xDE727981 = 433,
|
||||
CPED_CONFIG_FLAG_DisableHomingMissileLockon = 434,
|
||||
_0x12BBB935 = 435,
|
||||
_0xAD0A1277 = 436,
|
||||
_0xEA6AA46A = 437,
|
||||
CPED_CONFIG_FLAG_DisableHelmetArmor = 438,
|
||||
_0xCB7F3A1E = 439,
|
||||
_0x50178878 = 440,
|
||||
_0x051B4F0D = 441,
|
||||
_0x2FC3DECC = 442,
|
||||
_0xC0030B0B = 443,
|
||||
_0xBBDAF1E9 = 444,
|
||||
_0x944FE59C = 445,
|
||||
_0x506FBA39 = 446,
|
||||
_0xDD45FE84 = 447,
|
||||
_0xE698AE75 = 448,
|
||||
_0x199633F8 = 449,
|
||||
CPED_CONFIG_FLAG_PedIsArresting = 450,
|
||||
CPED_CONFIG_FLAG_IsDecoyPed = 451,
|
||||
_0x3A251D83 = 452,
|
||||
_0xA56F6986 = 453,
|
||||
_0x1D19C622 = 454,
|
||||
_0xB68D3EAB = 455,
|
||||
CPED_CONFIG_FLAG_CanBeIncapacitated = 456,
|
||||
_0x4BD5EBAD = 457
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class PedExtensions
|
||||
{
|
||||
public static bool IsBetween<T>(this T item, T start, T end)
|
||||
{
|
||||
return Comparer<T>.Default.Compare(item, start) >= 0 && Comparer<T>.Default.Compare(item, end) <= 0;
|
||||
}
|
||||
|
||||
public static bool Compare<T, Y>(this Dictionary<T, Y> item, Dictionary<T, Y> item2)
|
||||
{
|
||||
if (item == null || item2 == null || item.Count != item2.Count) return false;
|
||||
|
||||
foreach (var pair in item)
|
||||
{
|
||||
if (item2.TryGetValue(pair.Key, out var value) && Equals(value, pair.Value)) continue;
|
||||
|
||||
// TryGetValue() or Equals failed
|
||||
return false;
|
||||
}
|
||||
|
||||
// No difference between item and item2
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static Vector3 RaycastEverything(Vector2 screenCoord)
|
||||
{
|
||||
var camPos = GameplayCamera.Position;
|
||||
var camRot = GameplayCamera.Rotation;
|
||||
const float raycastToDist = 100.0f;
|
||||
const float raycastFromDist = 1f;
|
||||
|
||||
var target3D = ScreenRelToWorld(camPos, camRot, screenCoord);
|
||||
var source3D = camPos;
|
||||
|
||||
Entity ignoreEntity = Game.Player.Character;
|
||||
if (Game.Player.Character.IsInVehicle()) ignoreEntity = Game.Player.Character.CurrentVehicle;
|
||||
|
||||
var dir = target3D - source3D;
|
||||
dir.Normalize();
|
||||
var raycastResults = World.Raycast(source3D + dir * raycastFromDist,
|
||||
source3D + dir * raycastToDist,
|
||||
IntersectFlags.Everything,
|
||||
ignoreEntity);
|
||||
|
||||
if (raycastResults.DidHit) return raycastResults.HitPosition;
|
||||
|
||||
return camPos + dir * raycastToDist;
|
||||
}
|
||||
|
||||
public static Vector3 ScreenRelToWorld(Vector3 camPos, Vector3 camRot, Vector2 coord)
|
||||
{
|
||||
var camForward = camRot.ToDirection();
|
||||
var rotUp = camRot + new Vector3(10, 0, 0);
|
||||
var rotDown = camRot + new Vector3(-10, 0, 0);
|
||||
var rotLeft = camRot + new Vector3(0, 0, -10);
|
||||
var rotRight = camRot + new Vector3(0, 0, 10);
|
||||
|
||||
var camRight = rotRight.ToDirection() - rotLeft.ToDirection();
|
||||
var camUp = rotUp.ToDirection() - rotDown.ToDirection();
|
||||
|
||||
double rollRad = -camRot.Y.ToRadians();
|
||||
|
||||
var camRightRoll = camRight * (float)Math.Cos(rollRad) - camUp * (float)Math.Sin(rollRad);
|
||||
var camUpRoll = camRight * (float)Math.Sin(rollRad) + camUp * (float)Math.Cos(rollRad);
|
||||
|
||||
var point3D = camPos + camForward * 10.0f + camRightRoll + camUpRoll;
|
||||
if (!WorldToScreenRel(point3D, out var point2D)) return camPos + camForward * 10.0f;
|
||||
|
||||
var point3DZero = camPos + camForward * 10.0f;
|
||||
if (!WorldToScreenRel(point3DZero, out var point2DZero)) return camPos + camForward * 10.0f;
|
||||
|
||||
const double eps = 0.001;
|
||||
if (Math.Abs(point2D.X - point2DZero.X) < eps || Math.Abs(point2D.Y - point2DZero.Y) < eps)
|
||||
return camPos + camForward * 10.0f;
|
||||
|
||||
var scaleX = (coord.X - point2DZero.X) / (point2D.X - point2DZero.X);
|
||||
var scaleY = (coord.Y - point2DZero.Y) / (point2D.Y - point2DZero.Y);
|
||||
|
||||
return camPos + camForward * 10.0f + camRightRoll * scaleX + camUpRoll * scaleY;
|
||||
}
|
||||
|
||||
public static bool WorldToScreenRel(Vector3 worldCoords, out Vector2 screenCoords)
|
||||
{
|
||||
var num1 = new OutputArgument();
|
||||
var num2 = new OutputArgument();
|
||||
|
||||
if (!Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, worldCoords.X, worldCoords.Y,
|
||||
worldCoords.Z, num1, num2))
|
||||
{
|
||||
screenCoords = new Vector2();
|
||||
return false;
|
||||
}
|
||||
|
||||
screenCoords = new Vector2((num1.GetResult<float>() - 0.5f) * 2, (num2.GetResult<float>() - 0.5f) * 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void StayInCover(this Ped p)
|
||||
{
|
||||
Call(TASK_STAY_IN_COVER, p);
|
||||
}
|
||||
|
||||
public static bool IsTurretSeat(this Vehicle veh, int seat)
|
||||
{
|
||||
if (Call<bool>(IS_TURRET_SEAT, veh, seat)) return true;
|
||||
if (!Call<bool>(DOES_VEHICLE_HAVE_WEAPONS, veh.Handle)) return false;
|
||||
|
||||
switch (seat)
|
||||
{
|
||||
case -1:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Rhino
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Khanjari
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.FireTruck
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Riot2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus3;
|
||||
case 0:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Apc
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dune3;
|
||||
case 1:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical3
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.HalfTrack
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
|
||||
case 2:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
|
||||
case 3:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Limo2
|
||||
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dinghy5;
|
||||
case 7:
|
||||
return (VehicleHash)veh.Model.Hash == VehicleHash.Insurgent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsOnTurretSeat(this Ped P)
|
||||
{
|
||||
if (P.CurrentVehicle == null) return false;
|
||||
return IsTurretSeat(P.CurrentVehicle, (int)P.SeatIndex);
|
||||
}
|
||||
|
||||
|
||||
#region PED
|
||||
|
||||
public static byte GetPedSpeed(this Ped ped)
|
||||
{
|
||||
if (ped.IsSittingInVehicle()) return 4;
|
||||
if (ped.IsTaskActive(TaskType.CTaskEnterVehicle)) return 5;
|
||||
if (ped.IsTaskActive(TaskType.CTaskExitVehicle)) return 6;
|
||||
if (ped.IsWalking) return 1;
|
||||
if (ped.IsRunning) return 2;
|
||||
if (ped.IsSprinting) return 3;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Not sure whether component will always be lesser than 255, whatever...
|
||||
public static byte[] GetPedClothes(this Ped ped)
|
||||
{
|
||||
var result = new byte[36];
|
||||
for (byte i = 0; i < 12; i++)
|
||||
{
|
||||
result[i] = (byte)Call<short>(GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
|
||||
result[i + 12] = (byte)Call<short>(GET_PED_TEXTURE_VARIATION, ped.Handle, i);
|
||||
result[i + 24] = (byte)Call<short>(GET_PED_PALETTE_VARIATION, ped.Handle, i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static PedDataFlags GetPedFlags(this Ped ped)
|
||||
{
|
||||
var flags = PedDataFlags.None;
|
||||
|
||||
if (ped.IsAiming || ped.IsOnTurretSeat()) flags |= PedDataFlags.IsAiming;
|
||||
|
||||
|
||||
if (ped.IsReloading) flags |= PedDataFlags.IsReloading;
|
||||
|
||||
if (ped.IsJumping) flags |= PedDataFlags.IsJumping;
|
||||
|
||||
// Fake death
|
||||
if (ped.IsRagdoll || (ped.Health == 1 && ped.IsPlayer)) flags |= PedDataFlags.IsRagdoll;
|
||||
|
||||
if (ped.IsOnFire) flags |= PedDataFlags.IsOnFire;
|
||||
|
||||
if (ped.IsInParachuteFreeFall) flags |= PedDataFlags.IsInParachuteFreeFall;
|
||||
|
||||
if (ped.ParachuteState == ParachuteState.Gliding) flags |= PedDataFlags.IsParachuteOpen;
|
||||
|
||||
var climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder);
|
||||
if (climbingLadder) flags |= PedDataFlags.IsOnLadder;
|
||||
|
||||
if (ped.IsVaulting && !climbingLadder) flags |= PedDataFlags.IsVaulting;
|
||||
|
||||
if (ped.IsInCover || ped.IsGoingIntoCover)
|
||||
{
|
||||
flags |= PedDataFlags.IsInCover;
|
||||
if (ped.IsInCoverFacingLeft) flags |= PedDataFlags.IsInCover;
|
||||
if (!Call<bool>(IS_PED_IN_HIGH_COVER, ped)) flags |= PedDataFlags.IsInLowCover;
|
||||
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire)) flags |= PedDataFlags.IsBlindFiring;
|
||||
}
|
||||
|
||||
if (ped.IsInvincible) flags |= PedDataFlags.IsInvincible;
|
||||
|
||||
if (Call<bool>(GET_PED_STEALTH_MOVEMENT, ped)) flags |= PedDataFlags.IsInStealthMode;
|
||||
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static string[] GetReloadingAnimation(this Ped ped)
|
||||
{
|
||||
switch (ped.Weapons.Current.Hash)
|
||||
{
|
||||
case WeaponHash.Revolver:
|
||||
case WeaponHash.RevolverMk2:
|
||||
case WeaponHash.DoubleActionRevolver:
|
||||
case WeaponHash.NavyRevolver:
|
||||
return new string[2] { "anim@weapons@pistol@revolver_str", "reload_aim" };
|
||||
case WeaponHash.APPistol:
|
||||
return new string[2] { "weapons@pistol@ap_pistol_str", "reload_aim" };
|
||||
case WeaponHash.Pistol50:
|
||||
return new string[2] { "weapons@pistol@pistol_50_str", "reload_aim" };
|
||||
case WeaponHash.Pistol:
|
||||
case WeaponHash.PistolMk2:
|
||||
case WeaponHash.PericoPistol:
|
||||
case WeaponHash.SNSPistol:
|
||||
case WeaponHash.SNSPistolMk2:
|
||||
case WeaponHash.HeavyPistol:
|
||||
case WeaponHash.VintagePistol:
|
||||
case WeaponHash.CeramicPistol:
|
||||
case WeaponHash.MachinePistol:
|
||||
return new string[2] { "weapons@pistol@pistol_str", "reload_aim" };
|
||||
case WeaponHash.AssaultRifle:
|
||||
case WeaponHash.AssaultrifleMk2:
|
||||
return new string[2] { "weapons@rifle@hi@assault_rifle_str", "reload_aim" };
|
||||
case WeaponHash.SniperRifle:
|
||||
return new string[2] { "weapons@rifle@hi@sniper_rifle_str", "reload_aim" };
|
||||
case WeaponHash.HeavySniper:
|
||||
case WeaponHash.HeavySniperMk2:
|
||||
return new string[2] { "weapons@rifle@lo@sniper_heavy_str", "reload_aim" };
|
||||
case WeaponHash.PumpShotgun:
|
||||
case WeaponHash.PumpShotgunMk2:
|
||||
return new string[2] { "weapons@rifle@pump_str", "reload_aim" };
|
||||
case WeaponHash.Railgun:
|
||||
return new string[2] { "weapons@rifle@lo@rail_gun_str", "reload_aim" };
|
||||
case WeaponHash.SawnOffShotgun:
|
||||
return new string[2] { "weapons@rifle@lo@sawnoff_str", "reload_aim" };
|
||||
case WeaponHash.AssaultShotgun:
|
||||
return new string[2] { "weapons@rifle@lo@shotgun_assault_str", "reload_aim" };
|
||||
case WeaponHash.BullpupShotgun:
|
||||
return new string[2] { "weapons@rifle@lo@shotgun_bullpup_str", "reload_aim" };
|
||||
case WeaponHash.AdvancedRifle:
|
||||
return new string[2] { "weapons@submg@advanced_rifle_str", "reload_aim" };
|
||||
case WeaponHash.CarbineRifle:
|
||||
case WeaponHash.CarbineRifleMk2:
|
||||
case WeaponHash.CompactRifle:
|
||||
return new string[2] { "weapons@rifle@lo@carbine_str", "reload_aim" };
|
||||
case WeaponHash.Gusenberg:
|
||||
return new string[2] { "anim@weapons@machinegun@gusenberg_str", "reload_aim" };
|
||||
case WeaponHash.Musket:
|
||||
return new string[2] { "anim@weapons@musket@musket_str", "reload_aim" };
|
||||
case WeaponHash.FlareGun:
|
||||
return new string[2] { "anim@weapons@pistol@flare_str", "reload_aim" };
|
||||
case WeaponHash.SpecialCarbine:
|
||||
case WeaponHash.SpecialCarbineMk2:
|
||||
return new string[2] { "anim@weapons@rifle@lo@spcarbine_str", "reload_aim" };
|
||||
case WeaponHash.CombatPDW:
|
||||
return new string[2] { "anim@weapons@rifle@lo@pdw_str", "reload_aim" };
|
||||
case WeaponHash.BullpupRifle:
|
||||
case WeaponHash.BullpupRifleMk2:
|
||||
return new string[2] { "anim@weapons@submg@bullpup_rifle_str", "reload_aim" };
|
||||
case WeaponHash.AssaultSMG:
|
||||
return new string[2] { "weapons@submg@assault_smg_str", "reload_aim" };
|
||||
case WeaponHash.MicroSMG:
|
||||
case WeaponHash.MiniSMG:
|
||||
return new string[2] { "weapons@submg@lo@micro_smg_str", "reload_aim" };
|
||||
case WeaponHash.SMG:
|
||||
case WeaponHash.SMGMk2:
|
||||
return new string[2] { "weapons@rifle@smg_str", "reload_aim" };
|
||||
case WeaponHash.GrenadeLauncher:
|
||||
case WeaponHash.GrenadeLauncherSmoke:
|
||||
case WeaponHash.CompactGrenadeLauncher:
|
||||
return new string[2] { "weapons@heavy@grenade_launcher_str", "reload_aim" };
|
||||
case WeaponHash.RPG:
|
||||
case WeaponHash.Firework:
|
||||
return new string[2] { "weapons@heavy@rpg_str", "reload_aim" };
|
||||
case WeaponHash.CombatMG:
|
||||
case WeaponHash.CombatMGMk2:
|
||||
return new string[2] { "weapons@machinegun@combat_mg_str", "reload_aim" };
|
||||
case WeaponHash.MG:
|
||||
return new string[2] { "weapons@machinegun@mg_str", "reload_aim" };
|
||||
default:
|
||||
Log.Warning(
|
||||
$"~r~Reloading failed! Weapon ~g~[{ped.Weapons.Current.Hash}]~r~ could not be found!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<string, int> _vehicleDoors=new()
|
||||
{
|
||||
{ "door_dside_f", -1 },
|
||||
{ "door_pside_f", 0 },
|
||||
{ "door_dside_r", 1 },
|
||||
{ "door_pside_r", 2 }
|
||||
};
|
||||
public static VehicleSeat GetNearestSeat(this Ped ped, Vehicle veh, float distanceToignoreDoors = 50f)
|
||||
{
|
||||
var num = 99f;
|
||||
var result = -2;
|
||||
foreach (var text in _vehicleDoors.Keys)
|
||||
{
|
||||
var flag = veh.Bones[text].Position != Vector3.Zero;
|
||||
if (flag)
|
||||
{
|
||||
var num2 = ped.Position.DistanceTo(Call<Vector3>(GET_WORLD_POSITION_OF_ENTITY_BONE, veh, veh.Bones[text].Index));
|
||||
var flag2 = num2 < distanceToignoreDoors && num2 < num &&
|
||||
IsSeatUsableByPed(ped, veh, _vehicleDoors[text]);
|
||||
if (flag2)
|
||||
{
|
||||
num = num2;
|
||||
result = _vehicleDoors[text];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (VehicleSeat)result;
|
||||
}
|
||||
|
||||
public static bool IsSeatUsableByPed(Ped ped, Vehicle veh, int _seat)
|
||||
{
|
||||
var seat = (VehicleSeat)_seat;
|
||||
var result = false;
|
||||
var flag = veh.IsSeatFree(seat);
|
||||
if (flag)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else if (veh.GetPedOnSeat(seat) != null)
|
||||
{
|
||||
var isDead = veh.GetPedOnSeat(seat).IsDead;
|
||||
if (isDead)
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var num = Call<int>(GET_RELATIONSHIP_BETWEEN_PEDS, ped, veh.GetPedOnSeat(seat));
|
||||
var flag2 = num > 2;
|
||||
if (flag2) result = true;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool IsTaskActive(this Ped p, TaskType task)
|
||||
{
|
||||
return Call<bool>(GET_IS_TASK_ACTIVE, p.Handle, task);
|
||||
}
|
||||
|
||||
public static Vector3 GetAimCoord(this Ped p)
|
||||
{
|
||||
Prop weapon;
|
||||
|
||||
EntityBone b;
|
||||
if (p.IsOnTurretSeat())
|
||||
{
|
||||
if ((b = p.CurrentVehicle.GetMuzzleBone(p.VehicleWeapon)) != null)
|
||||
return b.Position + b.ForwardVector * 50;
|
||||
return GetLookingCoord(p);
|
||||
}
|
||||
|
||||
if ((weapon = p.Weapons.CurrentWeaponObject) != null)
|
||||
{
|
||||
// Not very accurate, but doesn't matter
|
||||
var dir = weapon.RightVector;
|
||||
return weapon.Position + dir * 20;
|
||||
}
|
||||
|
||||
return GetLookingCoord(p);
|
||||
}
|
||||
|
||||
public static Vector3 GetLookingCoord(this Ped p)
|
||||
{
|
||||
if (p == P && Call<int>(GET_FOLLOW_PED_CAM_VIEW_MODE) == 4)
|
||||
return RaycastEverything(default);
|
||||
EntityBone b = p.Bones[Bone.FacialForehead];
|
||||
var v = b.UpVector.Normalized;
|
||||
return b.Position + 200 * v;
|
||||
}
|
||||
|
||||
public static VehicleSeat GetSeatTryingToEnter(this Ped p)
|
||||
{
|
||||
return (VehicleSeat)Call<int>(GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
// WARNING: values can change after a game update
|
||||
// if R* adds in the middle!
|
||||
// This is up-to-date for b2372
|
||||
|
||||
internal enum TaskType
|
||||
{
|
||||
CTaskHandsUp = 0,
|
||||
CTaskClimbLadder = 1,
|
||||
CTaskExitVehicle = 2,
|
||||
CTaskCombatRoll = 3,
|
||||
CTaskAimGunOnFoot = 4,
|
||||
CTaskMovePlayer = 5,
|
||||
CTaskPlayerOnFoot = 6,
|
||||
CTaskWeapon = 8,
|
||||
CTaskPlayerWeapon = 9,
|
||||
CTaskPlayerIdles = 10,
|
||||
CTaskAimGun = 12,
|
||||
CTaskComplex = 12,
|
||||
CTaskFSMClone = 12,
|
||||
CTaskMotionBase = 12,
|
||||
CTaskMove = 12,
|
||||
CTaskMoveBase = 12,
|
||||
CTaskNMBehaviour = 12,
|
||||
CTaskNavBase = 12,
|
||||
CTaskScenario = 12,
|
||||
CTaskSearchBase = 12,
|
||||
CTaskSearchInVehicleBase = 12,
|
||||
CTaskShockingEvent = 12,
|
||||
CTaskTrainBase = 12,
|
||||
CTaskVehicleFSM = 12,
|
||||
CTaskVehicleGoTo = 12,
|
||||
CTaskVehicleMissionBase = 12,
|
||||
CTaskVehicleTempAction = 12,
|
||||
CTaskPause = 14,
|
||||
CTaskDoNothing = 15,
|
||||
CTaskGetUp = 16,
|
||||
CTaskGetUpAndStandStill = 17,
|
||||
CTaskFallOver = 18,
|
||||
CTaskFallAndGetUp = 19,
|
||||
CTaskCrawl = 20,
|
||||
CTaskComplexOnFire = 25,
|
||||
CTaskDamageElectric = 26,
|
||||
CTaskTriggerLookAt = 28,
|
||||
CTaskClearLookAt = 29,
|
||||
CTaskSetCharDecisionMaker = 30,
|
||||
CTaskSetPedDefensiveArea = 31,
|
||||
CTaskUseSequence = 32,
|
||||
CTaskMoveStandStill = 34,
|
||||
CTaskComplexControlMovement = 35,
|
||||
CTaskMoveSequence = 36,
|
||||
CTaskAmbientClips = 38,
|
||||
CTaskMoveInAir = 39,
|
||||
CTaskNetworkClone = 40,
|
||||
CTaskUseClimbOnRoute = 41,
|
||||
CTaskUseDropDownOnRoute = 42,
|
||||
CTaskUseLadderOnRoute = 43,
|
||||
CTaskSetBlockingOfNonTemporaryEvents = 44,
|
||||
CTaskForceMotionState = 45,
|
||||
CTaskSlopeScramble = 46,
|
||||
CTaskGoToAndClimbLadder = 47,
|
||||
CTaskClimbLadderFully = 48,
|
||||
CTaskRappel = 49,
|
||||
CTaskVault = 50,
|
||||
CTaskDropDown = 51,
|
||||
CTaskAffectSecondaryBehaviour = 52,
|
||||
CTaskAmbientLookAtEvent = 53,
|
||||
CTaskOpenDoor = 54,
|
||||
CTaskShovePed = 55,
|
||||
CTaskSwapWeapon = 56,
|
||||
CTaskGeneralSweep = 57,
|
||||
CTaskPolice = 58,
|
||||
CTaskPoliceOrderResponse = 59,
|
||||
CTaskPursueCriminal = 60,
|
||||
CTaskArrestPed = 62,
|
||||
CTaskArrestPed2 = 63,
|
||||
CTaskBusted = 64,
|
||||
CTaskFirePatrol = 65,
|
||||
CTaskHeliOrderResponse = 66,
|
||||
CTaskHeliPassengerRappel = 67,
|
||||
CTaskAmbulancePatrol = 68,
|
||||
CTaskPoliceWantedResponse = 69,
|
||||
CTaskSwat = 70,
|
||||
CTaskSwatWantedResponse = 72,
|
||||
CTaskSwatOrderResponse = 73,
|
||||
CTaskSwatGoToStagingArea = 74,
|
||||
CTaskSwatFollowInLine = 75,
|
||||
CTaskWitness = 76,
|
||||
CTaskGangPatrol = 77,
|
||||
CTaskArmy = 78,
|
||||
CTaskShockingEventWatch = 80,
|
||||
CTaskShockingEventGoto = 82,
|
||||
CTaskShockingEventHurryAway = 83,
|
||||
CTaskShockingEventReactToAircraft = 84,
|
||||
CTaskShockingEventReact = 85,
|
||||
CTaskShockingEventBackAway = 86,
|
||||
CTaskShockingPoliceInvestigate = 87,
|
||||
CTaskShockingEventStopAndStare = 88,
|
||||
CTaskShockingNiceCarPicture = 89,
|
||||
CTaskShockingEventThreatResponse = 90,
|
||||
CTaskTakeOffHelmet = 92,
|
||||
CTaskCarReactToVehicleCollision = 93,
|
||||
CTaskCarReactToVehicleCollisionGetOut = 95,
|
||||
CTaskDyingDead = 97,
|
||||
CTaskWanderingScenario = 100,
|
||||
CTaskWanderingInRadiusScenario = 101,
|
||||
CTaskMoveBetweenPointsScenario = 103,
|
||||
CTaskChatScenario = 104,
|
||||
CTaskCowerScenario = 106,
|
||||
CTaskDeadBodyScenario = 107,
|
||||
CTaskSayAudio = 114,
|
||||
CTaskWaitForSteppingOut = 116,
|
||||
CTaskCoupleScenario = 117,
|
||||
CTaskUseScenario = 118,
|
||||
CTaskUseVehicleScenario = 119,
|
||||
CTaskUnalerted = 120,
|
||||
CTaskStealVehicle = 121,
|
||||
CTaskReactToPursuit = 122,
|
||||
CTaskHitWall = 125,
|
||||
CTaskCower = 126,
|
||||
CTaskCrouch = 127,
|
||||
CTaskMelee = 128,
|
||||
CTaskMoveMeleeMovement = 129,
|
||||
CTaskMeleeActionResult = 130,
|
||||
CTaskMeleeUpperbodyAnims = 131,
|
||||
CTaskMoVEScripted = 133,
|
||||
CTaskScriptedAnimation = 134,
|
||||
CTaskSynchronizedScene = 135,
|
||||
CTaskComplexEvasiveStep = 137,
|
||||
CTaskWalkRoundCarWhileWandering = 138,
|
||||
CTaskComplexStuckInAir = 140,
|
||||
CTaskWalkRoundEntity = 141,
|
||||
CTaskMoveWalkRoundVehicle = 142,
|
||||
CTaskReactToGunAimedAt = 144,
|
||||
CTaskDuckAndCover = 146,
|
||||
CTaskAggressiveRubberneck = 147,
|
||||
CTaskInVehicleBasic = 150,
|
||||
CTaskCarDriveWander = 151,
|
||||
CTaskLeaveAnyCar = 152,
|
||||
CTaskComplexGetOffBoat = 153,
|
||||
CTaskCarSetTempAction = 155,
|
||||
CTaskBringVehicleToHalt = 156,
|
||||
CTaskCarDrive = 157,
|
||||
CTaskPlayerDrive = 159,
|
||||
CTaskEnterVehicle = 160,
|
||||
CTaskEnterVehicleAlign = 161,
|
||||
CTaskOpenVehicleDoorFromOutside = 162,
|
||||
CTaskEnterVehicleSeat = 163,
|
||||
CTaskCloseVehicleDoorFromInside = 164,
|
||||
CTaskInVehicleSeatShuffle = 165,
|
||||
CTaskExitVehicleSeat = 167,
|
||||
CTaskCloseVehicleDoorFromOutside = 168,
|
||||
CTaskControlVehicle = 169,
|
||||
CTaskMotionInAutomobile = 170,
|
||||
CTaskMotionOnBicycle = 171,
|
||||
CTaskMotionOnBicycleController = 172,
|
||||
CTaskMotionInVehicle = 173,
|
||||
CTaskMotionInTurret = 174,
|
||||
CTaskReactToBeingJacked = 175,
|
||||
CTaskReactToBeingAskedToLeaveVehicle = 176,
|
||||
CTaskTryToGrabVehicleDoor = 177,
|
||||
CTaskGetOnTrain = 178,
|
||||
CTaskGetOffTrain = 179,
|
||||
CTaskRideTrain = 180,
|
||||
CTaskMountThrowProjectile = 190,
|
||||
CTaskGoToCarDoorAndStandStill = 195,
|
||||
CTaskMoveGoToVehicleDoor = 196,
|
||||
CTaskSetPedInVehicle = 197,
|
||||
CTaskSetPedOutOfVehicle = 198,
|
||||
CTaskVehicleMountedWeapon = 199,
|
||||
CTaskVehicleGun = 200,
|
||||
CTaskVehicleProjectile = 201,
|
||||
CTaskSmashCarWindow = 204,
|
||||
CTaskMoveGoToPoint = 205,
|
||||
CTaskMoveAchieveHeading = 206,
|
||||
CTaskMoveFaceTarget = 207,
|
||||
CTaskComplexGoToPointAndStandStillTimed = 208,
|
||||
CTaskMoveGoToPointAndStandStill = 208,
|
||||
CTaskMoveFollowPointRoute = 209,
|
||||
CTaskMoveSeekEntity_CEntitySeekPosCalculatorStandard = 210,
|
||||
CTaskMoveSeekEntity_CEntitySeekPosCalculatorLastNavMeshIntersection = 211,
|
||||
CTaskMoveSeekEntity_CEntitySeekPosCalculatorLastNavMeshIntersection2 = 212,
|
||||
CTaskMoveSeekEntity_CEntitySeekPosCalculatorXYOffsetFixed = 213,
|
||||
CTaskMoveSeekEntity_CEntitySeekPosCalculatorXYOffsetFixed2 = 214,
|
||||
CTaskExhaustedFlee = 215,
|
||||
CTaskGrowlAndFlee = 216,
|
||||
CTaskScenarioFlee = 217,
|
||||
CTaskSmartFlee = 218,
|
||||
CTaskFlyAway = 219,
|
||||
CTaskWalkAway = 220,
|
||||
CTaskWander = 221,
|
||||
CTaskWanderInArea = 222,
|
||||
CTaskFollowLeaderInFormation = 223,
|
||||
CTaskGoToPointAnyMeans = 224,
|
||||
CTaskTurnToFaceEntityOrCoord = 225,
|
||||
CTaskFollowLeaderAnyMeans = 226,
|
||||
CTaskFlyToPoint = 228,
|
||||
CTaskFlyingWander = 229,
|
||||
CTaskGoToPointAiming = 230,
|
||||
CTaskGoToScenario = 231,
|
||||
CTaskSeekEntityAiming = 233,
|
||||
CTaskSlideToCoord = 234,
|
||||
CTaskSwimmingWander = 235,
|
||||
CTaskMoveTrackingEntity = 237,
|
||||
CTaskMoveFollowNavMesh = 238,
|
||||
CTaskMoveGoToPointOnRoute = 239,
|
||||
CTaskEscapeBlast = 240,
|
||||
CTaskMoveWander = 241,
|
||||
CTaskMoveBeInFormation = 242,
|
||||
CTaskMoveCrowdAroundLocation = 243,
|
||||
CTaskMoveCrossRoadAtTrafficLights = 244,
|
||||
CTaskMoveWaitForTraffic = 245,
|
||||
CTaskMoveGoToPointStandStillAchieveHeading = 246,
|
||||
CTaskMoveGetOntoMainNavMesh = 251,
|
||||
CTaskMoveSlideToCoord = 252,
|
||||
CTaskMoveGoToPointRelativeToEntityAndStandStill = 253,
|
||||
CTaskHelicopterStrafe = 254,
|
||||
CTaskGetOutOfWater = 256,
|
||||
CTaskMoveFollowEntityOffset = 259,
|
||||
CTaskFollowWaypointRecording = 261,
|
||||
CTaskMotionPed = 264,
|
||||
CTaskMotionPedLowLod = 265,
|
||||
CTaskHumanLocomotion = 268,
|
||||
CTaskMotionBasicLocomotionLowLod = 269,
|
||||
CTaskMotionStrafing = 270,
|
||||
CTaskMotionTennis = 271,
|
||||
CTaskMotionAiming = 272,
|
||||
CTaskBirdLocomotion = 273,
|
||||
CTaskFlightlessBirdLocomotion = 274,
|
||||
CTaskFishLocomotion = 278,
|
||||
CTaskQuadLocomotion = 279,
|
||||
CTaskMotionDiving = 280,
|
||||
CTaskMotionSwimming = 281,
|
||||
CTaskMotionParachuting = 282,
|
||||
CTaskMotionDrunk = 283,
|
||||
CTaskRepositionMove = 284,
|
||||
CTaskMotionAimingTransition = 285,
|
||||
CTaskThrowProjectile = 286,
|
||||
CTaskCover = 287,
|
||||
CTaskMotionInCover = 288,
|
||||
CTaskAimAndThrowProjectile = 289,
|
||||
CTaskGun = 290,
|
||||
CTaskAimFromGround = 291,
|
||||
CTaskAimGunVehicleDriveBy = 295,
|
||||
CTaskAimGunScripted = 296,
|
||||
CTaskReloadGun = 298,
|
||||
CTaskWeaponBlocked = 299,
|
||||
CTaskEnterCover = 300,
|
||||
CTaskExitCover = 301,
|
||||
CTaskAimGunFromCoverIntro = 302,
|
||||
CTaskAimGunFromCoverOutro = 303,
|
||||
CTaskAimGunBlindFire = 304,
|
||||
CTaskCombatClosestTargetInArea = 307,
|
||||
CTaskCombatAdditionalTask = 308,
|
||||
CTaskInCover = 309,
|
||||
CTaskAimSweep = 313,
|
||||
CTaskSharkCircle = 319,
|
||||
CTaskSharkAttack = 320,
|
||||
CTaskAgitated = 321,
|
||||
CTaskAgitatedAction = 322,
|
||||
CTaskConfront = 323,
|
||||
CTaskIntimidate = 324,
|
||||
CTaskShove = 325,
|
||||
CTaskShoved = 326,
|
||||
CTaskCrouchToggle = 328,
|
||||
CTaskRevive = 329,
|
||||
CTaskParachute = 335,
|
||||
CTaskParachuteObject = 336,
|
||||
CTaskTakeOffPedVariation = 337,
|
||||
CTaskCombatSeekCover = 340,
|
||||
CTaskCombatFlank = 342,
|
||||
CTaskCombat = 343,
|
||||
CTaskCombatMounted = 344,
|
||||
CTaskMoveCircle = 345,
|
||||
CTaskMoveCombatMounted = 346,
|
||||
CTaskSearch = 347,
|
||||
CTaskSearchOnFoot = 348,
|
||||
CTaskSearchInAutomobile = 349,
|
||||
CTaskSearchInBoat = 350,
|
||||
CTaskSearchInHeli = 351,
|
||||
CTaskThreatResponse = 352,
|
||||
CTaskInvestigate = 353,
|
||||
CTaskStandGuardFSM = 354,
|
||||
CTaskPatrol = 355,
|
||||
CTaskShootAtTarget = 356,
|
||||
CTaskSetAndGuardArea = 357,
|
||||
CTaskStandGuard = 358,
|
||||
CTaskSeparate = 359,
|
||||
CTaskStayInCover = 360,
|
||||
CTaskVehicleCombat = 361,
|
||||
CTaskVehiclePersuit = 362,
|
||||
CTaskVehicleChase = 363,
|
||||
CTaskDraggingToSafety = 364,
|
||||
CTaskDraggedToSafety = 365,
|
||||
CTaskVariedAimPose = 366,
|
||||
CTaskMoveWithinAttackWindow = 367,
|
||||
CTaskMoveWithinDefensiveArea = 368,
|
||||
CTaskShootOutTire = 369,
|
||||
CTaskShellShocked = 370,
|
||||
CTaskBoatChase = 371,
|
||||
CTaskBoatCombat = 372,
|
||||
CTaskBoatStrafe = 373,
|
||||
CTaskHeliChase = 374,
|
||||
CTaskHeliCombat = 375,
|
||||
CTaskSubmarineCombat = 376,
|
||||
CTaskSubmarineChase = 377,
|
||||
CTaskPlaneChase = 378,
|
||||
CTaskTargetUnreachable = 379,
|
||||
CTaskTargetUnreachableInInterior = 380,
|
||||
CTaskTargetUnreachableInExterior = 381,
|
||||
CTaskStealthKill = 382,
|
||||
CTaskWrithe = 383,
|
||||
CTaskAdvance = 384,
|
||||
CTaskCharge = 385,
|
||||
CTaskMoveToTacticalPoint = 386,
|
||||
CTaskToHurtTransit = 387,
|
||||
CTaskAnimatedHitByExplosion = 388,
|
||||
CTaskNMRelax = 389,
|
||||
CTaskNMPose = 391,
|
||||
CTaskNMBrace = 392,
|
||||
CTaskNMBuoyancy = 393,
|
||||
CTaskNMInjuredOnGround = 394,
|
||||
CTaskNMShot = 395,
|
||||
CTaskNMHighFall = 396,
|
||||
CTaskNMBalance = 397,
|
||||
CTaskNMElectrocute = 398,
|
||||
CTaskNMPrototype = 399,
|
||||
CTaskNMExplosion = 400,
|
||||
CTaskNMOnFire = 401,
|
||||
CTaskNMScriptControl = 402,
|
||||
CTaskNMJumpRollFromRoadVehicle = 403,
|
||||
CTaskNMFlinch = 404,
|
||||
CTaskNMSit = 405,
|
||||
CTaskNMFallDown = 406,
|
||||
CTaskBlendFromNM = 407,
|
||||
CTaskNMControl = 408,
|
||||
CTaskNMDangle = 409,
|
||||
CTaskNMGenericAttach = 412,
|
||||
CTaskNMDraggingToSafety = 414,
|
||||
CTaskNMThroughWindscreen = 415,
|
||||
CTaskNMRiverRapids = 416,
|
||||
CTaskNMSimple = 417,
|
||||
CTaskRageRagdoll = 418,
|
||||
CTaskJumpVault = 421,
|
||||
CTaskJump = 422,
|
||||
CTaskFall = 423,
|
||||
CTaskReactAimWeapon = 425,
|
||||
CTaskChat = 426,
|
||||
CTaskMobilePhone = 427,
|
||||
CTaskReactToDeadPed = 428,
|
||||
CTaskSearchForUnknownThreat = 430,
|
||||
CTaskBomb = 432,
|
||||
CTaskDetonator = 433,
|
||||
CTaskAnimatedAttach = 435,
|
||||
CTaskCutScene = 441,
|
||||
CTaskReactToExplosion = 442,
|
||||
CTaskReactToImminentExplosion = 443,
|
||||
CTaskDiveToGround = 444,
|
||||
CTaskReactAndFlee = 445,
|
||||
CTaskSidestep = 446,
|
||||
CTaskCallPolice = 447,
|
||||
CTaskReactInDirection = 448,
|
||||
CTaskReactToBuddyShot = 449,
|
||||
CTaskVehicleGoToAutomobileNew = 454,
|
||||
CTaskVehicleGoToPlane = 455,
|
||||
CTaskVehicleGoToHelicopter = 456,
|
||||
CTaskVehicleGoToSubmarine = 457,
|
||||
CTaskVehicleGoToBoat = 458,
|
||||
CTaskVehicleGoToPointAutomobile = 459,
|
||||
CTaskVehicleGoToPointWithAvoidanceAutomobile = 460,
|
||||
CTaskVehiclePursue = 461,
|
||||
CTaskVehicleRam = 462,
|
||||
CTaskVehicleSpinOut = 463,
|
||||
CTaskVehicleApproach = 464,
|
||||
CTaskVehicleThreePointTurn = 465,
|
||||
CTaskVehicleDeadDriver = 466,
|
||||
CTaskVehicleCruiseNew = 467,
|
||||
CTaskVehicleCruiseBoat = 468,
|
||||
CTaskVehicleStop = 469,
|
||||
CTaskVehiclePullOver = 470,
|
||||
CTaskVehiclePassengerExit = 471,
|
||||
CTaskVehicleFlee = 472,
|
||||
CTaskVehicleFleeAirborne = 473,
|
||||
CTaskVehicleFleeBoat = 474,
|
||||
CTaskVehicleFollowRecording = 475,
|
||||
CTaskVehicleFollow = 476,
|
||||
CTaskVehicleBlock = 477,
|
||||
CTaskVehicleBlockCruiseInFront = 478,
|
||||
CTaskVehicleBlockBrakeInFront = 479,
|
||||
CTaskVehicleBlockBackAndForth = 478,
|
||||
CTaskVehicleCrash = 481,
|
||||
CTaskVehicleLand = 482,
|
||||
CTaskVehicleLandPlane = 483,
|
||||
CTaskVehicleHover = 484,
|
||||
CTaskVehicleAttack = 485,
|
||||
CTaskVehicleAttackTank = 486,
|
||||
CTaskVehicleCircle = 487,
|
||||
CTaskVehiclePoliceBehaviour = 488,
|
||||
CTaskVehiclePoliceBehaviourHelicopter = 489,
|
||||
CTaskVehiclePoliceBehaviourBoat = 490,
|
||||
CTaskVehicleEscort = 491,
|
||||
CTaskVehicleHeliProtect = 492,
|
||||
CTaskVehiclePlayerDriveAutomobile = 494,
|
||||
CTaskVehiclePlayerDriveBike = 495,
|
||||
CTaskVehiclePlayerDriveBoat = 496,
|
||||
CTaskVehiclePlayerDriveSubmarine = 497,
|
||||
CTaskVehiclePlayerDriveSubmarineCar = 498,
|
||||
CTaskVehiclePlayerDriveAmphibiousAutomobile = 499,
|
||||
CTaskVehiclePlayerDrivePlane = 500,
|
||||
CTaskVehiclePlayerDriveHeli = 501,
|
||||
CTaskVehiclePlayerDriveAutogyro = 502,
|
||||
CTaskVehiclePlayerDriveDiggerArm = 503,
|
||||
CTaskVehiclePlayerDriveTrain = 504,
|
||||
CTaskVehiclePlaneChase = 505,
|
||||
CTaskVehicleNoDriver = 506,
|
||||
CTaskVehicleAnimation = 507,
|
||||
CTaskVehicleConvertibleRoof = 508,
|
||||
CTaskVehicleParkNew = 509,
|
||||
CTaskVehicleFollowWaypointRecording = 510,
|
||||
CTaskVehicleGoToNavmesh = 511,
|
||||
CTaskVehicleReactToCopSiren = 512,
|
||||
CTaskVehicleGotoLongRange = 513,
|
||||
CTaskVehicleWait = 514,
|
||||
CTaskVehicleReverse = 515,
|
||||
CTaskVehicleBrake = 516,
|
||||
CTaskVehicleHandBrake = 517,
|
||||
CTaskVehicleTurn = 518,
|
||||
CTaskVehicleGoForward = 519,
|
||||
CTaskVehicleSwerve = 520,
|
||||
CTaskVehicleFlyDirection = 521,
|
||||
CTaskVehicleHeadonCollision = 522,
|
||||
CTaskVehicleBoostUseSteeringAngle = 523,
|
||||
CTaskVehicleShotTire = 524,
|
||||
CTaskVehicleBurnout = 525,
|
||||
CTaskVehicleRevEngine = 526,
|
||||
CTaskVehicleSurfaceInSubmarine = 527,
|
||||
CTaskVehiclePullAlongside = 528,
|
||||
CTaskVehicleTransformToSubmarine = 529,
|
||||
CTaskAnimatedFallback = 530
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using LemonUI.Elements;
|
||||
using RageCoop.Core;
|
||||
using static RageCoop.Client.Shared;
|
||||
using Font = GTA.UI.Font;
|
||||
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Util
|
||||
{
|
||||
/// <summary>
|
||||
/// The location of the cursor on screen between 0 and 1.
|
||||
/// </summary>
|
||||
public static PointF CursorPositionRelative
|
||||
{
|
||||
get
|
||||
{
|
||||
var cursorX = Game.IsControlEnabled(Control.CursorX)
|
||||
? Game.GetControlValueNormalized(Control.CursorX)
|
||||
: Game.GetDisabledControlValueNormalized(Control.CursorX);
|
||||
var cursorY = Game.IsControlEnabled(Control.CursorY)
|
||||
? Game.GetControlValueNormalized(Control.CursorY)
|
||||
: Game.GetDisabledControlValueNormalized(Control.CursorY);
|
||||
return new PointF(cursorX, cursorY);
|
||||
}
|
||||
}
|
||||
|
||||
public static Point CursorPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
var p = CursorPositionRelative;
|
||||
var res = Screen.Resolution;
|
||||
return new Point((int)(p.X * res.Width), (int)(p.Y * res.Height));
|
||||
}
|
||||
}
|
||||
|
||||
public static SizeF ResolutionMaintainRatio
|
||||
{
|
||||
get
|
||||
{
|
||||
// Get the game width and height
|
||||
var screenw = Screen.Resolution.Width;
|
||||
var screenh = Screen.Resolution.Height;
|
||||
// Calculate the ratio
|
||||
var ratio = (float)screenw / screenh;
|
||||
// And the width with that ratio
|
||||
var width = 1080f * ratio;
|
||||
// Finally, return a SizeF
|
||||
return new SizeF(width, 1080f);
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 GetRotation(this EntityBone b)
|
||||
{
|
||||
return b.ForwardVector.ToEulerRotation(b.UpVector);
|
||||
}
|
||||
|
||||
public static void DrawTextFromCoord(Vector3 coord, string text, float scale = 0.5f, Point offset = default)
|
||||
{
|
||||
Point toDraw = default;
|
||||
if (WorldToScreen(coord, ref toDraw))
|
||||
{
|
||||
toDraw.X += offset.X;
|
||||
toDraw.Y += offset.Y;
|
||||
new ScaledText(toDraw, text, scale, Font.ChaletLondon)
|
||||
{
|
||||
Outline = true,
|
||||
Alignment = Alignment.Center,
|
||||
Color = Color.White
|
||||
}.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe bool WorldToScreen(Vector3 pos, ref Point screenPos)
|
||||
{
|
||||
float x, y;
|
||||
var res = ResolutionMaintainRatio;
|
||||
if (Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, pos.X, pos.Y, pos.Z, &x, &y))
|
||||
{
|
||||
screenPos = new Point((int)(res.Width * x), (int)(y * 1080));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ClientSettings ReadSettings(string path = null)
|
||||
{
|
||||
path = path ?? SettingsPath;
|
||||
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
ClientSettings settings;
|
||||
try
|
||||
{
|
||||
settings = JsonDeserialize<ClientSettings>(File.ReadAllText(path));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log?.Error(ex);
|
||||
File.WriteAllText(path, JsonSerialize(settings = new ClientSettings()));
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public static bool SaveSettings(string path = null, ClientSettings settings = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
path = path ?? SettingsPath;
|
||||
settings = settings ?? Settings;
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
|
||||
File.WriteAllText(path, JsonSerialize(settings));
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log?.Error(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Vehicle CreateVehicle(Model model, Vector3 position, float heading = 0f)
|
||||
{
|
||||
if (!model.IsLoaded) return null;
|
||||
return (Vehicle)Entity.FromHandle(Call<int>(CREATE_VEHICLE, model.Hash, position.X,
|
||||
position.Y, position.Z, heading, false, false));
|
||||
}
|
||||
|
||||
public static Ped CreatePed(Model model, Vector3 position, float heading = 0f)
|
||||
{
|
||||
if (!model.IsLoaded) return null;
|
||||
return (Ped)Entity.FromHandle(Call<int>(CREATE_PED, 26, model.Hash, position.X, position.Y,
|
||||
position.Z, heading, false, false));
|
||||
}
|
||||
|
||||
public static void SetOnFire(this Entity e, bool toggle)
|
||||
{
|
||||
if (toggle)
|
||||
Call(START_ENTITY_FIRE, e.Handle);
|
||||
else
|
||||
Call(STOP_ENTITY_FIRE, e.Handle);
|
||||
}
|
||||
|
||||
public static void SetFrozen(this Entity e, bool toggle)
|
||||
{
|
||||
Call(FREEZE_ENTITY_POSITION, e, toggle);
|
||||
}
|
||||
|
||||
public static SyncedPed GetSyncEntity(this Ped p)
|
||||
{
|
||||
if (p == null) return null;
|
||||
var c = EntityPool.GetPedByHandle(p.Handle);
|
||||
if (c == null) EntityPool.Add(c = new SyncedPed(p));
|
||||
return c;
|
||||
}
|
||||
|
||||
public static SyncedVehicle GetSyncEntity(this Vehicle veh)
|
||||
{
|
||||
if (veh == null) return null;
|
||||
var v = EntityPool.GetVehicleByHandle(veh.Handle);
|
||||
if (v == null) EntityPool.Add(v = new SyncedVehicle(veh));
|
||||
return v;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current radio index for player, returns 255 if there's none
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static byte GetPlayerRadioIndex()
|
||||
{
|
||||
return (byte)Call<int>(GET_PLAYER_RADIO_STATION_INDEX);
|
||||
}
|
||||
|
||||
public static void SetPlayerRadioIndex(int playerVeh, int index)
|
||||
{
|
||||
if (playerVeh == 0)
|
||||
playerVeh = Call<int>(GET_VEHICLE_PED_IS_IN, P.Handle, false);
|
||||
|
||||
if (index == byte.MaxValue)
|
||||
Call(SET_VEH_RADIO_STATION, playerVeh, "OFF");
|
||||
else
|
||||
Call(SET_RADIO_TO_STATION_INDEX, index);
|
||||
}
|
||||
|
||||
public static EntityPopulationType GetPopulationType(int handle)
|
||||
=> (EntityPopulationType)Call<int>(GET_ENTITY_POPULATION_TYPE, handle);
|
||||
|
||||
public static unsafe void DeleteEntity(int handle)
|
||||
{
|
||||
Call(SET_ENTITY_AS_MISSION_ENTITY, handle, false, true);
|
||||
Call(DELETE_ENTITY, &handle);
|
||||
}
|
||||
|
||||
[LibraryImport("kernel32.dll")]
|
||||
public static partial ulong GetTickCount64();
|
||||
|
||||
[LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
|
||||
public static partial IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string lpModuleName);
|
||||
|
||||
#region -- POINTER --
|
||||
|
||||
private static int _steeringAngleOffset { get; set; }
|
||||
|
||||
public static unsafe void NativeMemory()
|
||||
{
|
||||
IntPtr address;
|
||||
|
||||
address = Game.FindPattern("\x74\x0A\xF3\x0F\x11\xB3\x1C\x09\x00\x00\xEB\x25", "xxxxxx????xx");
|
||||
if (address != IntPtr.Zero) _steeringAngleOffset = *(int*)(address + 6) + 8;
|
||||
}
|
||||
|
||||
public static unsafe void CustomSteeringAngle(this Vehicle veh, float value)
|
||||
{
|
||||
var address = new IntPtr((long)veh.MemoryAddress);
|
||||
if (address == IntPtr.Zero || _steeringAngleOffset == 0) return;
|
||||
|
||||
*(float*)(address + _steeringAngleOffset).ToPointer() = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MATH
|
||||
|
||||
public static Vector3 LinearVectorLerp(Vector3 start, Vector3 end, ulong currentTime, int duration)
|
||||
{
|
||||
return new Vector3
|
||||
{
|
||||
X = LinearFloatLerp(start.X, end.X, currentTime, duration),
|
||||
Y = LinearFloatLerp(start.Y, end.Y, currentTime, duration),
|
||||
Z = LinearFloatLerp(start.Z, end.Z, currentTime, duration)
|
||||
};
|
||||
}
|
||||
|
||||
public static float LinearFloatLerp(float start, float end, ulong currentTime, int duration)
|
||||
{
|
||||
return (end - start) * currentTime / duration + start;
|
||||
}
|
||||
|
||||
public static float Lerp(float from, float to, float fAlpha)
|
||||
{
|
||||
return from * (1.0f - fAlpha) + to * fAlpha; //from + (to - from) * fAlpha
|
||||
}
|
||||
|
||||
public static Vector3 RotationToDirection(Vector3 rotation)
|
||||
{
|
||||
var z = MathExtensions.DegToRad(rotation.Z);
|
||||
var x = MathExtensions.DegToRad(rotation.X);
|
||||
var num = Math.Abs(Math.Cos(x));
|
||||
|
||||
return new Vector3
|
||||
{
|
||||
X = (float)(-Math.Sin(z) * num),
|
||||
Y = (float)(Math.Cos(z) * num),
|
||||
Z = (float)Math.Sin(x)
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class VehicleExtensions
|
||||
{
|
||||
#region VEHICLE
|
||||
|
||||
public static VehicleDataFlags GetVehicleFlags(this SyncedVehicle v)
|
||||
{
|
||||
var veh = v.MainVehicle;
|
||||
VehicleDataFlags flags = 0;
|
||||
|
||||
if (veh.IsEngineRunning) flags |= VehicleDataFlags.IsEngineRunning;
|
||||
|
||||
if (veh.AreLightsOn) flags |= VehicleDataFlags.AreLightsOn;
|
||||
|
||||
if (veh.BrakePower >= 0.01f) flags |= VehicleDataFlags.AreBrakeLightsOn;
|
||||
|
||||
if (veh.AreHighBeamsOn) flags |= VehicleDataFlags.AreHighBeamsOn;
|
||||
|
||||
if (veh.IsSirenActive) flags |= VehicleDataFlags.IsSirenActive;
|
||||
|
||||
if (veh.IsDead) flags |= VehicleDataFlags.IsDead;
|
||||
|
||||
if (Call<bool>(IS_HORN_ACTIVE, veh.Handle)) flags |= VehicleDataFlags.IsHornActive;
|
||||
|
||||
if (v.IsSubmarineCar && Call<bool>(IS_VEHICLE_IN_SUBMARINE_MODE, veh.Handle))
|
||||
flags |= VehicleDataFlags.IsTransformed;
|
||||
|
||||
if (v.IsAircraft) flags |= VehicleDataFlags.IsAircraft;
|
||||
|
||||
if (v.IsDeluxo && veh.IsDeluxoHovering()) flags |= VehicleDataFlags.IsDeluxoHovering;
|
||||
|
||||
if (v.HasRoof) flags |= VehicleDataFlags.HasRoof;
|
||||
|
||||
if (v.HasRocketBoost && veh.IsRocketBoostActive) flags |= VehicleDataFlags.IsRocketBoostActive;
|
||||
|
||||
if (v.HasParachute && veh.IsParachuteDeployed) flags |= VehicleDataFlags.IsParachuteActive;
|
||||
|
||||
if (veh.IsOnFire) flags |= VehicleDataFlags.IsOnFire;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public static (int, int)[] GetVehicleMods(this SyncedVehicle sv, out byte togglesMask)
|
||||
{
|
||||
var modsArr = sv.MainVehicle.Mods.ToArray();
|
||||
var result = new (int, int)[modsArr.Length];
|
||||
for (int i = 0; i < modsArr.Length; i++)
|
||||
{
|
||||
result[i] = ((int)modsArr[i].Type, modsArr[i].Index);
|
||||
}
|
||||
togglesMask = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (Call<bool>(IS_TOGGLE_MOD_ON, sv.MainVehicle.Handle, i + 17))
|
||||
{
|
||||
togglesMask |= (byte)(1 << i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static ushort GetVehicleExtras(this SyncedVehicle sv)
|
||||
{
|
||||
ushort result = 0;
|
||||
for (int i = 1; i < 15; i++)
|
||||
{
|
||||
var flag = (ushort)(1 << i);
|
||||
var hasExtra = (sv.AvalibleExtras & (ushort)(1 << i)) != 0;
|
||||
if (hasExtra && Call<bool>(IS_VEHICLE_EXTRA_TURNED_ON, sv.MainVehicle.Handle, i))
|
||||
result |= flag;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static VehicleDamageModel GetVehicleDamageModel(this Vehicle veh)
|
||||
{
|
||||
// Broken windows
|
||||
byte brokenWindows = 0;
|
||||
for (var i = 0; i < 8; i++)
|
||||
if (!veh.Windows[(VehicleWindowIndex)i].IsIntact)
|
||||
brokenWindows |= (byte)(1 << i);
|
||||
|
||||
// Broken doors
|
||||
byte brokenDoors = 0;
|
||||
byte openedDoors = 0;
|
||||
foreach (var door in veh.Doors)
|
||||
if (door.IsBroken)
|
||||
brokenDoors |= (byte)(1 << (byte)door.Index);
|
||||
else if (door.IsOpen) openedDoors |= (byte)(1 << (byte)door.Index);
|
||||
|
||||
// Bursted tires
|
||||
short burstedTires = 0;
|
||||
foreach (var wheel in veh.Wheels.GetAllWheels())
|
||||
if (wheel.IsBursted)
|
||||
burstedTires |= (short)(1 << (int)wheel.BoneId);
|
||||
|
||||
return new VehicleDamageModel
|
||||
{
|
||||
BrokenDoors = brokenDoors,
|
||||
OpenedDoors = openedDoors,
|
||||
BrokenWindows = brokenWindows,
|
||||
BurstedTires = burstedTires,
|
||||
LeftHeadLightBroken = (byte)(veh.IsLeftHeadLightBroken ? 1 : 0),
|
||||
RightHeadLightBroken = (byte)(veh.IsRightHeadLightBroken ? 1 : 0)
|
||||
};
|
||||
}
|
||||
|
||||
public static void SetDamageModel(this Vehicle veh, VehicleDamageModel model, bool leavedoors = true)
|
||||
{
|
||||
for (var i = 0; i < 8; i++)
|
||||
{
|
||||
var door = veh.Doors[(VehicleDoorIndex)i];
|
||||
if ((model.BrokenDoors & (byte)(1 << i)) != 0)
|
||||
{
|
||||
if (!door.IsBroken) door.Break(leavedoors);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (door.IsBroken)
|
||||
{
|
||||
// The vehicle can only fix a door if the vehicle was completely fixed
|
||||
veh.Repair();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((model.OpenedDoors & (byte)(1 << i)) != 0)
|
||||
{
|
||||
if (!door.IsOpen && !door.IsBroken) door.Open();
|
||||
}
|
||||
else if (door.IsOpen)
|
||||
{
|
||||
if (!door.IsBroken) door.Close();
|
||||
}
|
||||
|
||||
if ((model.BrokenWindows & (byte)(1 << i)) != 0)
|
||||
veh.Windows[(VehicleWindowIndex)i].Smash();
|
||||
else if (!veh.Windows[(VehicleWindowIndex)i].IsIntact) veh.Windows[(VehicleWindowIndex)i].Repair();
|
||||
}
|
||||
|
||||
foreach (var wheel in veh.Wheels)
|
||||
if ((model.BurstedTires & (short)(1 << (int)wheel.BoneId)) != 0)
|
||||
{
|
||||
if (!wheel.IsBursted)
|
||||
{
|
||||
wheel.Puncture();
|
||||
wheel.Burst();
|
||||
}
|
||||
}
|
||||
else if (wheel.IsBursted)
|
||||
{
|
||||
wheel.Fix();
|
||||
}
|
||||
|
||||
veh.IsLeftHeadLightBroken = model.LeftHeadLightBroken > 0;
|
||||
veh.IsRightHeadLightBroken = model.RightHeadLightBroken > 0;
|
||||
}
|
||||
|
||||
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
|
||||
{
|
||||
Call(SET_SPECIAL_FLIGHT_MODE_TARGET_RATIO, deluxo, hover ? 1f : 0f);
|
||||
}
|
||||
|
||||
public static bool IsDeluxoHovering(this Vehicle deluxo)
|
||||
{
|
||||
return Math.Abs(deluxo.Bones[27].ForwardVector.GetCosTheta(deluxo.ForwardVector) - 1) > 0.05;
|
||||
}
|
||||
|
||||
public static void SetDeluxoWingRatio(this Vehicle v, float ratio)
|
||||
{
|
||||
Call(SET_HOVER_MODE_WING_RATIO, v, ratio);
|
||||
}
|
||||
|
||||
public static float GetDeluxoWingRatio(this Vehicle v)
|
||||
{
|
||||
return v.Bones[99].Position.DistanceTo(v.Bones[92].Position) - 1.43f;
|
||||
}
|
||||
|
||||
public static float GetNozzleAngel(this Vehicle plane)
|
||||
{
|
||||
return Call<float>(GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane);
|
||||
}
|
||||
|
||||
public static bool HasNozzle(this Vehicle v)
|
||||
{
|
||||
switch (v.Model.Hash)
|
||||
{
|
||||
// Hydra
|
||||
case 970385471:
|
||||
return true;
|
||||
|
||||
// Avenger
|
||||
case -2118308144:
|
||||
return true;
|
||||
|
||||
// Tula
|
||||
case 1043222410:
|
||||
return true;
|
||||
|
||||
// Avenger
|
||||
case 408970549:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SetNozzleAngel(this Vehicle plane, float ratio)
|
||||
{
|
||||
Call(SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Client.Scripting;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class WeaponFix
|
||||
{
|
||||
public Dictionary<uint, string> Bullet = new Dictionary<uint, string>();
|
||||
public Dictionary<uint, string> Lazer = new Dictionary<uint, string>();
|
||||
public Dictionary<uint, string> Others = new Dictionary<uint, string>();
|
||||
}
|
||||
|
||||
internal static class WeaponUtil
|
||||
{
|
||||
public static Dictionary<uint, VehicleWeaponInfo> VehicleWeapons = new Dictionary<uint, VehicleWeaponInfo>();
|
||||
public static WeaponFix WeaponFix;
|
||||
public static Dictionary<uint, WeaponInfo> Weapons;
|
||||
|
||||
static WeaponUtil()
|
||||
{
|
||||
// Parse and load to memory
|
||||
foreach (var w in JsonDeserialize<VehicleWeaponInfo[]>(
|
||||
File.ReadAllText(VehicleWeaponDataPath))) VehicleWeapons.Add(w.Hash, w);
|
||||
|
||||
Weapons = JsonDeserialize<Dictionary<uint, WeaponInfo>>(
|
||||
File.ReadAllText(WeaponInfoDataPath));
|
||||
|
||||
if (File.Exists(WeaponFixDataPath))
|
||||
WeaponFix = JsonDeserialize<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
|
||||
else
|
||||
Log.Warning("Weapon fix data not found");
|
||||
}
|
||||
|
||||
public static void DumpWeaponFix(string path)
|
||||
{
|
||||
var P = Game.Player.Character;
|
||||
var pos = P.Position + Vector3.WorldUp * 3;
|
||||
var types = new HashSet<int> { 3 };
|
||||
P.IsInvincible = true;
|
||||
var fix = new WeaponFix();
|
||||
foreach (var w in Weapons)
|
||||
{
|
||||
Console.PrintInfo("Testing " + w.Value.Name);
|
||||
if (w.Value.FireType != "PROJECTILE")
|
||||
{
|
||||
var asset = new WeaponAsset(w.Key);
|
||||
asset.Request(1000);
|
||||
World.ShootBullet(pos, pos + Vector3.WorldUp, P, asset, 0, 1000);
|
||||
if (!Call<bool>(IS_BULLET_IN_AREA, pos.X, pos.Y, pos.Z, 10f, true) &&
|
||||
!Call<bool>(IS_PROJECTILE_IN_AREA, pos.X - 10, pos.Y - 10, pos.Z - 10, pos.X + 10,
|
||||
pos.Y + 10, pos.Z + 10, true))
|
||||
switch (w.Value.DamageType)
|
||||
{
|
||||
case "BULLET":
|
||||
fix.Bullet.Add(w.Key, w.Value.Name);
|
||||
break;
|
||||
case "EXPLOSIVE":
|
||||
fix.Lazer.Add(w.Key, w.Value.Name);
|
||||
break;
|
||||
default:
|
||||
fix.Others.Add(w.Key, w.Value.Name);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var p in World.GetAllProjectiles()) p.Delete();
|
||||
Script.Wait(50);
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllText(path, JsonSerialize(fix));
|
||||
|
||||
P.IsInvincible = false;
|
||||
}
|
||||
|
||||
public static uint GetWeaponFix(uint hash)
|
||||
{
|
||||
if (WeaponFix.Bullet.TryGetValue(hash, out _)) return 0x461DDDB0;
|
||||
if (WeaponFix.Lazer.TryGetValue(hash, out _)) return 0xE2822A29;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
public static Dictionary<uint, bool> GetWeaponComponents(this Weapon weapon)
|
||||
{
|
||||
Dictionary<uint, bool> result = null;
|
||||
|
||||
if (weapon.Components.Count > 0)
|
||||
{
|
||||
result = new Dictionary<uint, bool>();
|
||||
|
||||
foreach (var comp in weapon.Components) result.Add((uint)comp.ComponentHash, comp.Active);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static EntityBone GetMuzzleBone(this Ped p)
|
||||
{
|
||||
return p.Weapons.CurrentWeaponObject?.Bones["gun_muzzle"];
|
||||
}
|
||||
|
||||
public static float GetWeaponDamage(this Ped P, uint hash)
|
||||
{
|
||||
var comp = P.Weapons.Current.Components.GetSuppressorComponent();
|
||||
return Call<float>(GET_WEAPON_DAMAGE, hash,
|
||||
comp.Active ? comp.ComponentHash : WeaponComponentHash.Invalid);
|
||||
}
|
||||
|
||||
public static int GetMuzzleIndex(this Vehicle v, VehicleWeaponHash hash)
|
||||
{
|
||||
if (VehicleWeapons.TryGetValue((uint)v.Model.Hash, out var veh) &&
|
||||
veh.Weapons.TryGetValue((uint)hash, out var wp))
|
||||
return (int)wp.Bones[CoreUtils.RandInt(0, wp.Bones.Length)].BoneIndex;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static EntityBone GetMuzzleBone(this Vehicle v, VehicleWeaponHash hash)
|
||||
{
|
||||
if ((uint)hash == 1422046295) hash = VehicleWeaponHash.WaterCannon;
|
||||
var i = v.GetMuzzleIndex(hash);
|
||||
if (i == -1) return null;
|
||||
return v.Bones[i];
|
||||
}
|
||||
|
||||
public static bool IsUsingProjectileWeapon(this Ped p)
|
||||
{
|
||||
var vp = p.VehicleWeapon;
|
||||
return Weapons.TryGetValue(vp != VehicleWeaponHash.Invalid ? (uint)vp : (uint)p.Weapons.Current.Hash,
|
||||
out var info)
|
||||
&& info.FireType == "PROJECTILE";
|
||||
}
|
||||
|
||||
public static string GetFlashFX(this WeaponHash w, bool veh)
|
||||
{
|
||||
if (veh)
|
||||
switch ((VehicleWeaponHash)w)
|
||||
{
|
||||
case VehicleWeaponHash.Tank:
|
||||
return "muz_tank";
|
||||
default: return "muz_buzzard";
|
||||
}
|
||||
|
||||
switch (w.GetWeaponGroup())
|
||||
{
|
||||
case WeaponGroup.SMG:
|
||||
return "muz_smg";
|
||||
|
||||
case WeaponGroup.Shotgun:
|
||||
return "muz_smg";
|
||||
|
||||
case WeaponGroup.AssaultRifle:
|
||||
return "muz_assault_rifle";
|
||||
|
||||
case WeaponGroup.Pistol:
|
||||
return "muz_pistol";
|
||||
|
||||
case WeaponGroup.Stungun:
|
||||
return "muz_stungun";
|
||||
|
||||
case WeaponGroup.Heavy:
|
||||
switch (w)
|
||||
{
|
||||
case WeaponHash.Minigun:
|
||||
return "muz_minigun";
|
||||
|
||||
case WeaponHash.RPG:
|
||||
return "muz_rpg";
|
||||
|
||||
default:
|
||||
return "muz_minigun";
|
||||
}
|
||||
case WeaponGroup.Sniper:
|
||||
return "muz_alternate_star";
|
||||
|
||||
case WeaponGroup.PetrolCan:
|
||||
return "weap_petrol_can";
|
||||
|
||||
case WeaponGroup.FireExtinguisher:
|
||||
return "weap_extinguisher";
|
||||
default:
|
||||
return "muz_assault_rifle";
|
||||
}
|
||||
}
|
||||
|
||||
public static WeaponGroup GetWeaponGroup(this WeaponHash hash)
|
||||
{
|
||||
return Call<WeaponGroup>(GET_WEAPONTYPE_GROUP, hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
using RageCoop.Client.Menus;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
|
||||
internal class WorldThread : Script
|
||||
{
|
||||
public static Script Instance;
|
||||
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
|
||||
private static bool _trafficEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public WorldThread()
|
||||
{
|
||||
Instance = this;
|
||||
Aborted += (e) => { DoQueuedActions(); ChangeTraffic(true); };
|
||||
}
|
||||
protected override void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
while(Game.IsLoading)
|
||||
Yield();
|
||||
|
||||
Notification.Show(NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!",
|
||||
$"Press ~g~{Settings.MenuKey}~s~ to open the menu.");
|
||||
}
|
||||
protected override void OnTick()
|
||||
{
|
||||
base.OnTick();
|
||||
|
||||
if (Game.IsLoading) return;
|
||||
|
||||
try
|
||||
{
|
||||
CoopMenu.MenuPool.Process();
|
||||
DoQueuedActions();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
|
||||
if (!Networking.IsOnServer) return;
|
||||
|
||||
Game.DisableControlThisFrame(Control.FrontendPause);
|
||||
|
||||
if (Settings.DisableAlternatePause) Game.DisableControlThisFrame(Control.FrontendPauseAlternate);
|
||||
// Sets a value that determines how aggressive the ocean waves will be.
|
||||
// Values of 2.0 or more make for very aggressive waves like you see during a thunderstorm.
|
||||
Call(SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
|
||||
|
||||
if (Settings.ShowEntityOwnerName)
|
||||
unsafe
|
||||
{
|
||||
int handle;
|
||||
if (Call<bool>(GET_ENTITY_PLAYER_IS_FREE_AIMING_AT, 0, &handle))
|
||||
{
|
||||
var entity = Entity.FromHandle(handle);
|
||||
if (entity != null)
|
||||
{
|
||||
var owner = "invalid";
|
||||
if (entity.EntityType == EntityType.Vehicle)
|
||||
owner = (entity as Vehicle).GetSyncEntity()?.Owner?.Username ?? "unknown";
|
||||
if (entity.EntityType == EntityType.Ped)
|
||||
owner = (entity as Ped).GetSyncEntity()?.Owner?.Username ?? "unknown";
|
||||
Screen.ShowHelpTextThisFrame("Entity owner: " + owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_trafficEnabled)
|
||||
{
|
||||
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
|
||||
Call(SET_PED_POPULATION_BUDGET, 0);
|
||||
Call(SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
|
||||
Call(SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
|
||||
Call(SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
|
||||
Call(SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
|
||||
Call(SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Traffic(bool enable)
|
||||
{
|
||||
ChangeTraffic(enable);
|
||||
_trafficEnabled = enable;
|
||||
}
|
||||
|
||||
private static void ChangeTraffic(bool enable)
|
||||
{
|
||||
if (enable)
|
||||
{
|
||||
Call(REMOVE_SCENARIO_BLOCKING_AREAS);
|
||||
Call(SET_CREATE_RANDOM_COPS, true);
|
||||
Call(SET_RANDOM_TRAINS, true);
|
||||
Call(SET_RANDOM_BOATS, true);
|
||||
Call(SET_GARBAGE_TRUCKS, true);
|
||||
Call(SET_PED_POPULATION_BUDGET, 3); // 0 - 3
|
||||
Call(SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3
|
||||
Call(SET_ALL_VEHICLE_GENERATORS_ACTIVE);
|
||||
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
|
||||
Call(SET_NUMBER_OF_PARKED_VEHICLES, -1);
|
||||
Call(SET_DISTANT_CARS_ENABLED, true);
|
||||
Call(DISABLE_VEHICLE_DISTANTLIGHTS, false);
|
||||
}
|
||||
else if (Networking.IsOnServer)
|
||||
{
|
||||
Call(ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f,
|
||||
1000.0f, 0, 1, 1, 1);
|
||||
Call(SET_CREATE_RANDOM_COPS, false);
|
||||
Call(SET_RANDOM_TRAINS, false);
|
||||
Call(SET_RANDOM_BOATS, false);
|
||||
Call(SET_GARBAGE_TRUCKS, false);
|
||||
Call(DELETE_ALL_TRAINS);
|
||||
Call(SET_PED_POPULATION_BUDGET, 0);
|
||||
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
|
||||
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
|
||||
Call(SET_FAR_DRAW_VEHICLES, false);
|
||||
Call(SET_NUMBER_OF_PARKED_VEHICLES, 0);
|
||||
Call(SET_DISTANT_CARS_ENABLED, false);
|
||||
Call(DISABLE_VEHICLE_DISTANTLIGHTS, true);
|
||||
foreach (var ped in World.GetAllPeds())
|
||||
{
|
||||
if (ped == Game.Player.Character) continue;
|
||||
var c = EntityPool.GetPedByHandle(ped.Handle);
|
||||
if (c == null || (c.IsLocal && ped.Handle != Game.Player.Character.Handle &&
|
||||
ped.PopulationType != EntityPopulationType.Mission))
|
||||
{
|
||||
Log.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
|
||||
ped.CurrentVehicle?.Delete();
|
||||
ped.Kill();
|
||||
ped.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var veh in World.GetAllVehicles())
|
||||
{
|
||||
var v = veh.GetSyncEntity();
|
||||
if (v.MainVehicle == Game.Player.LastVehicle ||
|
||||
v.MainVehicle == Game.Player.Character.CurrentVehicle)
|
||||
// Don't delete player's vehicle
|
||||
continue;
|
||||
if (v == null || (v.IsLocal && veh.PopulationType != EntityPopulationType.Mission))
|
||||
// Log.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");
|
||||
|
||||
veh.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Delay(Action a, int time)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
QueueAction(a);
|
||||
});
|
||||
}
|
||||
|
||||
internal static void DoQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
foreach (var action in QueuedActions.ToArray())
|
||||
try
|
||||
{
|
||||
if (action()) QueuedActions.Remove(action);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// </summary>
|
||||
/// <param name="a">
|
||||
/// An action to be executed with a return value indicating whether the action can be removed after
|
||||
/// execution.
|
||||
/// </param>
|
||||
internal static void QueueAction(Func<bool> a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void QueueAction(Action a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(() =>
|
||||
{
|
||||
a();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all queued actions
|
||||
/// </summary>
|
||||
internal static void ClearQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user