Compare commits
563 Commits
0.4.1a
...
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 | |||
| 87d8102a1a | |||
| bd1f0e1713 | |||
| 04c9081851 | |||
| 9cf23cac7f | |||
| 46f75c5a21 | |||
| 6167417bf8 | |||
| 82ab9237f5 | |||
| 82e7cdd785 | |||
| 41385abc7d | |||
| 84171e2949 | |||
| 42c038d8ab | |||
| fe6b42e509 | |||
| 8bc5c11030 | |||
| 76f7157749 | |||
| 52a242c72a | |||
| 8a42f7c626 | |||
| 6e2c0a3037 | |||
| 1b77234cfe | |||
| a15785a02f | |||
| 1a5f594b36 | |||
| 68b6295126 | |||
| 17d6d86194 | |||
| f82134fb12 | |||
| 37edef425c | |||
| 30f7a281b0 | |||
| e1606889f7 | |||
| 33244b930f | |||
| 212c004d31 | |||
| 190f23240a | |||
| 376830d6dc | |||
| 9a9fb2870c | |||
| 2a093d1da0 | |||
| 1a66f14bae | |||
| 887e7f428d | |||
| cc4f5f46ce | |||
| 13883242e4 | |||
| 09a97c43fd | |||
| 671b4f9f6f | |||
| 1ae913f0d2 | |||
| 45f487c9f4 | |||
| 7f1891236d | |||
| 1c981c331c | |||
| 0c9213936d | |||
| 188556991f | |||
| b6450027b8 | |||
| 82cffea6ed | |||
| 734788f310 | |||
| 45c3ff83cf | |||
| c7655573aa | |||
| 88a4f046d4 | |||
| 7ccccabe1d | |||
| 3d6dbd5f2a | |||
| 9407d80858 | |||
| 51eaf4f56d | |||
| 4b5ec37987 | |||
| 3f246a8f7d | |||
| 7f4833be81 | |||
| 755615a887 | |||
| ef32603801 | |||
| fa80b87f7a | |||
| f8b7062c61 | |||
| 95c4c42ff0 | |||
| 13fa206594 | |||
| a1dda2ffa4 | |||
| 0914afc4ed | |||
| 9f2cf2eb03 | |||
| 1607b7025c |
@@ -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
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
name: Nightly-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "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 artifacts
|
||||
run: .\build.cmd
|
||||
|
||||
- name: Build server for different platforms
|
||||
run: .\publish-server.cmd
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Client
|
||||
dest: RageCoop.Client.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/API
|
||||
dest: bin/Artifacts/SDK.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: bin/Release/Server/win-x64
|
||||
dest: bin/Artifacts/RageCoop.Server-win-x64.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
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
|
||||
@@ -1,192 +0,0 @@
|
||||
#undef DEBUG
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
public static class COOPAPI
|
||||
{
|
||||
|
||||
#region DELEGATES
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
/// <param name="connected"></param>
|
||||
/// <param name="from">The player's id</param>
|
||||
/// <param name="reason"></param>
|
||||
public delegate void ConnectEvent(bool connected, int from, string reason = null);
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="message">The Lidgren-Network net handle</param>
|
||||
/// <param name="args"></param>
|
||||
public delegate void ChatMessage(string from, string message, CancelEventArgs args);
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
/// <param name="from">The Lidgren-Network net handle</param>
|
||||
/// <param name="mod"></param>
|
||||
/// <param name="customID"></param>
|
||||
/// <param name="bytes"></param>
|
||||
public delegate void ModEvent(long from, string mod, byte customID, byte[] bytes);
|
||||
#endregion
|
||||
|
||||
#region EVENTS
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
public static event ConnectEvent OnConnection;
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
public static event ChatMessage OnChatMessage;
|
||||
|
||||
public static void Connected()
|
||||
{
|
||||
OnConnection?.Invoke(true, GetPlayerID());
|
||||
}
|
||||
|
||||
public static void Disconnected(string reason)
|
||||
{
|
||||
OnConnection?.Invoke(false, GetPlayerID(), reason);
|
||||
}
|
||||
|
||||
public static void Connected(int playerID)
|
||||
{
|
||||
OnConnection?.Invoke(true, playerID);
|
||||
}
|
||||
|
||||
public static void Disconnected(int playerID)
|
||||
{
|
||||
OnConnection?.Invoke(false, playerID);
|
||||
}
|
||||
|
||||
public static bool ChatMessageReceived(string from, string message)
|
||||
{
|
||||
CancelEventArgs args = new CancelEventArgs(false);
|
||||
OnChatMessage?.Invoke(from, message, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Send a local chat message to this player
|
||||
/// </summary>
|
||||
/// <param name="from">Username of the player who sent this message</param>
|
||||
/// <param name="message">The player's message</param>
|
||||
public static void LocalChatMessage(string from, string message)
|
||||
{
|
||||
Main.MainChat.AddMessage(from, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to any server
|
||||
/// </summary>
|
||||
/// <param name="serverAddress">The server address to connect. Example: 127.0.0.1:4499</param>
|
||||
public static void Connect(string serverAddress)
|
||||
{
|
||||
Networking.ToggleConnection(serverAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ?
|
||||
/// </summary>
|
||||
public static void Disconnect()
|
||||
{
|
||||
Networking.ToggleConnection(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the player is already on a server
|
||||
/// </summary>
|
||||
public static bool IsOnServer()
|
||||
{
|
||||
return Networking.IsOnServer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the local player's ID
|
||||
/// </summary>
|
||||
/// <returns>PlayerID</returns>
|
||||
public static int GetPlayerID()
|
||||
{
|
||||
return Main.LocalPlayerID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a RAGECOOP menu is visible
|
||||
/// </summary>
|
||||
public static bool IsMenuVisible()
|
||||
{
|
||||
#if NON_INTERACTIVE
|
||||
return false;
|
||||
#else
|
||||
return Menus.CoopMenu.MenuPool.AreAnyVisible;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP chat is visible
|
||||
/// </summary>
|
||||
public static bool IsChatFocused()
|
||||
{
|
||||
return Main.MainChat.Focused;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the RAGECOOP list of players is visible
|
||||
/// </summary>
|
||||
public static bool IsPlayerListVisible()
|
||||
{
|
||||
return Util.GetTickCount64() - PlayerList.Pressed < 5000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of RAGECOOP
|
||||
/// </summary>
|
||||
public static string GetCurrentVersion()
|
||||
{
|
||||
return Main.CurrentVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get that player's local username
|
||||
/// </summary>
|
||||
public static string GetUsername()
|
||||
{
|
||||
return Main.Settings.Username;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new username for this player
|
||||
/// </summary>
|
||||
/// <param name="username">The new username</param>
|
||||
/// <returns>false if the player already joined a server or the username is null or empty otherwise true</returns>
|
||||
public static bool SetUsername(string username)
|
||||
{
|
||||
if (IsOnServer() || string.IsNullOrEmpty(username))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Main.Settings.Username = username;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or set the client's settings.
|
||||
/// </summary>
|
||||
/// <returns>The client's settings, you should NEVER change settings without notifying the player.</returns>
|
||||
public static Settings Settings()
|
||||
{
|
||||
return Main.Settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
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")]
|
||||
[assembly: AssemblyTitle("RageCoop.Client.CefHost")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("RageCoop.Client")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2021")]
|
||||
[assembly: AssemblyProduct("RageCoop.Client.CefHost")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -20,7 +19,7 @@ using System.Runtime.InteropServices;
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ef56d109-1f22-43e0-9dff-cfcfb94e0681")]
|
||||
[assembly: Guid("ba750e08-5e41-4b56-8ad5-875716d2ccea")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
@@ -32,5 +31,5 @@ using System.Runtime.InteropServices;
|
||||
// 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("0.4.1.0")]
|
||||
[assembly: AssemblyFileVersion("0.4.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
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal enum TimeStamp
|
||||
{
|
||||
AddPeds,
|
||||
PedTotal,
|
||||
AddVehicles,
|
||||
VehicleTotal,
|
||||
SendPed,
|
||||
SendPedState,
|
||||
SendVehicle,
|
||||
SendVehicleState,
|
||||
UpdatePed,
|
||||
UpdateVehicle,
|
||||
CheckProjectiles,
|
||||
GetAllEntities,
|
||||
Receive,
|
||||
|
||||
}
|
||||
internal static class Debug
|
||||
{
|
||||
public static Dictionary<TimeStamp, long> TimeStamps = new Dictionary<TimeStamp, long>();
|
||||
private static int _lastNfHandle;
|
||||
static Debug()
|
||||
{
|
||||
foreach(TimeStamp t in Enum.GetValues(typeof(TimeStamp)))
|
||||
{
|
||||
TimeStamps.Add(t, 0);
|
||||
}
|
||||
}
|
||||
public static string Dump(this Dictionary<TimeStamp, long> d)
|
||||
{
|
||||
string s = "";
|
||||
foreach(KeyValuePair<TimeStamp, long> kvp in d)
|
||||
{
|
||||
s+=kvp.Key+":"+kvp.Value+"\n";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
public static void ShowTimeStamps()
|
||||
{
|
||||
GTA.UI.Notification.Hide(_lastNfHandle);
|
||||
_lastNfHandle=GTA.UI.Notification.Show(Debug.TimeStamps.Dump());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using System.Threading;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class DevTool:Script
|
||||
{
|
||||
public static Vehicle ToMark;
|
||||
public static bool UseSecondary=false;
|
||||
public static int Current = 0;
|
||||
public static int Secondary = 0;
|
||||
public static MuzzleDir Direction = MuzzleDir.Forward;
|
||||
public DevTool()
|
||||
{
|
||||
Tick+=OnTick;
|
||||
KeyDown+=OnKeyDown;
|
||||
}
|
||||
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (ToMark==null||(!ToMark.Exists())) { return; }
|
||||
if (DevToolMenu.Menu.SelectedItem==DevToolMenu.boneIndexItem) {
|
||||
|
||||
switch (e.KeyCode)
|
||||
{
|
||||
case Keys.Right:
|
||||
Current++;
|
||||
break;
|
||||
case Keys.Left:
|
||||
Current--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (DevToolMenu.Menu.SelectedItem==DevToolMenu.secondaryBoneIndexItem)
|
||||
{
|
||||
|
||||
switch (e.KeyCode)
|
||||
{
|
||||
case Keys.Right:
|
||||
Secondary++;
|
||||
break;
|
||||
case Keys.Left:
|
||||
Secondary--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Update();
|
||||
}
|
||||
private static void Update()
|
||||
{
|
||||
|
||||
if (Current>ToMark.Bones.Count-1)
|
||||
{
|
||||
Current=0;
|
||||
}
|
||||
else if (Current< 0)
|
||||
{
|
||||
Current=ToMark.Bones.Count-1;
|
||||
}
|
||||
DevToolMenu.boneIndexItem.AltTitle= Current.ToString();
|
||||
if (Secondary>ToMark.Bones.Count-1)
|
||||
{
|
||||
Secondary=0;
|
||||
}
|
||||
else if (Secondary< 0)
|
||||
{
|
||||
Secondary=ToMark.Bones.Count-1;
|
||||
}
|
||||
DevToolMenu.secondaryBoneIndexItem.AltTitle= Secondary.ToString();
|
||||
}
|
||||
private static void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
if(ToMark == null || !ToMark.Exists()){ return;}
|
||||
Update();
|
||||
Draw(Current);
|
||||
if (UseSecondary)
|
||||
{
|
||||
Draw(Secondary);
|
||||
}
|
||||
|
||||
}
|
||||
private static void Draw(int boneindex)
|
||||
{
|
||||
var bone = ToMark.Bones[boneindex];
|
||||
World.DrawLine(bone.Position, bone.Position+2*bone.ForwardVector, Color.Blue);
|
||||
World.DrawLine(bone.Position, bone.Position+2*bone.UpVector, Color.Green);
|
||||
World.DrawLine(bone.Position, bone.Position+2*bone.RightVector, Color.Yellow);
|
||||
Vector3 todraw = bone.ForwardVector;
|
||||
switch ((byte)Direction)
|
||||
{
|
||||
case 0:
|
||||
todraw=bone.ForwardVector;
|
||||
break;
|
||||
case 1:
|
||||
todraw=bone.RightVector;
|
||||
break;
|
||||
case 2:
|
||||
todraw=bone.UpVector;
|
||||
break;
|
||||
case 3:
|
||||
todraw=bone.ForwardVector*-1;
|
||||
break;
|
||||
case 4:
|
||||
todraw=bone.RightVector*-1;
|
||||
break;
|
||||
case 5:
|
||||
todraw=bone.UpVector*-1;
|
||||
break;
|
||||
}
|
||||
World.DrawLine(bone.Position, bone.Position+10*todraw, Color.Red);
|
||||
}
|
||||
public static void CopyToClipboard(MuzzleDir dir)
|
||||
{
|
||||
|
||||
if (ToMark!=null)
|
||||
{
|
||||
string s;
|
||||
if (UseSecondary)
|
||||
{
|
||||
if ((byte)dir<3)
|
||||
{
|
||||
s=$@"
|
||||
// {ToMark.DisplayName}
|
||||
case {ToMark.Model.Hash}:
|
||||
i=BulletsShot%2==0 ? {Current} : {Secondary};
|
||||
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].{dir}Vector);
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
s=$@"
|
||||
// {ToMark.DisplayName}
|
||||
case {ToMark.Model.Hash}:
|
||||
i=BulletsShot%2==0 ? {Current} : {Secondary};
|
||||
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].{((MuzzleDir)(dir-3)).ToString()}Vector*-1);
|
||||
";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((byte)dir<3)
|
||||
{
|
||||
s=$@"
|
||||
// {ToMark.DisplayName}
|
||||
case {ToMark.Model.Hash}:
|
||||
return new MuzzleInfo(v.Bones[{Current}].Position, v.Bones[{Current}].{dir}Vector);
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
s=$@"
|
||||
// {ToMark.DisplayName}
|
||||
case {ToMark.Model.Hash}:
|
||||
return new MuzzleInfo(v.Bones[{Current}].Position, v.Bones[{Current}].{((MuzzleDir)(dir-3)).ToString()}Vector*-1);
|
||||
";
|
||||
}
|
||||
}
|
||||
Thread thread = new Thread(() => Clipboard.SetText(s));
|
||||
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
GTA.UI.Notification.Show("Copied to clipboard, please paste it on the GitHub issue page!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum MuzzleDir:byte
|
||||
{
|
||||
Forward=0,
|
||||
Right = 1,
|
||||
Up=2,
|
||||
Backward=3,
|
||||
Left = 4,
|
||||
Down=5,
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
-422
@@ -1,422 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.Math;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
internal class Main : Script
|
||||
{
|
||||
|
||||
private bool _gameLoaded = false;
|
||||
private bool _lastDead = false;
|
||||
public static readonly string CurrentVersion = "V0_4_1";
|
||||
|
||||
public static int LocalPlayerID=0;
|
||||
|
||||
internal static RelationshipGroup SyncedPedsGroup;
|
||||
|
||||
public static new Settings Settings = null;
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
#endif
|
||||
public static Chat MainChat = null;
|
||||
public static Stopwatch Counter = new Stopwatch();
|
||||
public static Core.Logging.Logger Logger = null;
|
||||
|
||||
public static ulong Ticked = 0;
|
||||
private static List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public Main()
|
||||
{
|
||||
Settings = Util.ReadSettings();
|
||||
Logger=new Core.Logging.Logger()
|
||||
{
|
||||
LogPath="Scripts\\RageCoop\\RageCoop.Client.log",
|
||||
UseConsole=false,
|
||||
#if DEBUG
|
||||
LogLevel = 0,
|
||||
#else
|
||||
LogLevel=Settings.LogLevel,
|
||||
#endif
|
||||
};
|
||||
// Required for some synchronization!
|
||||
/*if (Game.Version < GameVersion.v1_0_1290_1_Steam)
|
||||
{
|
||||
Tick += (object sender, EventArgs e) =>
|
||||
{
|
||||
if (Game.IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_gameLoaded)
|
||||
{
|
||||
GTA.UI.Notification.Show("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
|
||||
_gameLoaded = true;
|
||||
}
|
||||
};
|
||||
return;
|
||||
}*/
|
||||
SyncedPedsGroup=World.AddRelationshipGroup("SYNCPED");
|
||||
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral, true);
|
||||
Networking.Start();
|
||||
#if !NON_INTERACTIVE
|
||||
#endif
|
||||
MainChat = new Chat();
|
||||
|
||||
Tick += OnTick;
|
||||
KeyDown += OnKeyDown;
|
||||
Aborted += (object sender, EventArgs e) => CleanUp();
|
||||
|
||||
Util.NativeMemory();
|
||||
Counter.Restart();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private ulong _lastDebugData;
|
||||
private int _debugBytesSend;
|
||||
private int _debugBytesReceived;
|
||||
#endif
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
if (Game.IsLoading)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (!_gameLoaded && (_gameLoaded = true))
|
||||
{
|
||||
#if !NON_INTERACTIVE
|
||||
GTA.UI.Notification.Show(GTA.UI.NotificationIcon.AllPlayersConf, "RAGECOOP","Welcome!", $"Press ~g~{Main.Settings.MenuKey}~s~ to open the menu.");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
CoopMenu.MenuPool.Process();
|
||||
#endif
|
||||
|
||||
DoQueuedActions();
|
||||
if (!Networking.IsOnServer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Game.TimeScale!=1)
|
||||
{
|
||||
Game.TimeScale=1;
|
||||
}
|
||||
try
|
||||
{
|
||||
Networking.Tick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
}
|
||||
|
||||
if (!DownloadManager.DownloadComplete)
|
||||
{
|
||||
DownloadManager.RenderProgress();
|
||||
}
|
||||
|
||||
MapLoader.LoadAll();
|
||||
|
||||
#if DEBUG
|
||||
if (Networking.ShowNetworkInfo)
|
||||
{
|
||||
ulong time = Util.GetTickCount64();
|
||||
if (time - _lastDebugData > 1000)
|
||||
{
|
||||
_lastDebugData = time;
|
||||
|
||||
_debugBytesReceived = Networking.BytesReceived;
|
||||
Networking.BytesReceived = 0;
|
||||
_debugBytesSend = Networking.BytesSend;
|
||||
Networking.BytesSend = 0;
|
||||
}
|
||||
|
||||
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 0), $"L: {Networking.Latency * 1000:N0}ms", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
|
||||
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {Lidgren.Network.NetUtility.ToHumanReadable(_debugBytesReceived)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
|
||||
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60), $"S: {Lidgren.Network.NetUtility.ToHumanReadable(_debugBytesSend)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
MainChat.Tick();
|
||||
PlayerList.Tick();
|
||||
if (Settings.DisableAutoRespawn)
|
||||
{
|
||||
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
|
||||
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
|
||||
var P = Game.Player.Character;
|
||||
if (P.IsDead)
|
||||
{
|
||||
Function.Call(Hash.TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
|
||||
Function.Call(Hash.SET_FADE_OUT_AFTER_DEATH, false);
|
||||
|
||||
if (P.Health!=1)
|
||||
{
|
||||
P.Health=1;
|
||||
Game.Player.WantedLevel=0;
|
||||
Main.Logger.Debug("Player died.");
|
||||
}
|
||||
GTA.UI.Screen.StopEffects();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Function.Call(Hash.DISPLAY_HUD, true);
|
||||
}
|
||||
_lastDead=P.IsDead;
|
||||
|
||||
}
|
||||
|
||||
Ticked++;
|
||||
}
|
||||
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (MainChat.Focused)
|
||||
{
|
||||
MainChat.OnKeyDown(e.KeyCode);
|
||||
return;
|
||||
}
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
if (Game.IsControlPressed(GTA.Control.FrontendPause))
|
||||
{
|
||||
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
|
||||
return;
|
||||
}
|
||||
if (Game.IsControlPressed(GTA.Control.FrontendPauseAlternate)&&Settings.DisableAlternatePause)
|
||||
{
|
||||
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (e.KeyCode == Settings.MenuKey)
|
||||
{
|
||||
if (CoopMenu.MenuPool.AreAnyVisible)
|
||||
{
|
||||
CoopMenu.MenuPool.ForEach<LemonUI.Menus.NativeMenu>(x =>
|
||||
{
|
||||
if (x.Visible)
|
||||
{
|
||||
CoopMenu.LastMenu=x;
|
||||
x.Visible=false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
CoopMenu.LastMenu.Visible = true;
|
||||
}
|
||||
}
|
||||
else if (Game.IsControlJustPressed(GTA.Control.MultiplayerInfo))
|
||||
{
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
ulong currentTimestamp = Util.GetTickCount64();
|
||||
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000 ? (currentTimestamp - 6000) : currentTimestamp;
|
||||
}
|
||||
}
|
||||
else if (Game.IsControlJustPressed(GTA.Control.MpTextChatAll))
|
||||
{
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
MainChat.Focused = true;
|
||||
}
|
||||
}
|
||||
else if (e.KeyCode==Settings.PassengerKey)
|
||||
{
|
||||
var P = Game.Player.Character;
|
||||
|
||||
if (!P.IsInVehicle())
|
||||
{
|
||||
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
|
||||
{
|
||||
P.Task.ClearAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
var V = World.GetClosestVehicle(P.Position, 50);
|
||||
|
||||
if (V!=null)
|
||||
{
|
||||
var seat = P.GetNearestSeat(V);
|
||||
P.Task.EnterVehicle(V, seat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void CleanUp()
|
||||
{
|
||||
MainChat.Clear();
|
||||
EntityPool.Cleanup();
|
||||
PlayerList.Cleanup();
|
||||
Main.LocalPlayerID=default;
|
||||
|
||||
}
|
||||
|
||||
public static readonly Dictionary<ulong, byte> CheckNativeHash = new Dictionary<ulong, byte>()
|
||||
{
|
||||
{ 0xD49F9B0955C367DE, 1 }, // Entities
|
||||
{ 0xEF29A16337FACADB, 1 }, //
|
||||
{ 0xB4AC7D0CF06BFE8F, 1 }, //
|
||||
{ 0x9B62392B474F44A0, 1 }, //
|
||||
{ 0x7DD959874C1FD534, 1 }, //
|
||||
{ 0xAF35D0D2583051B0, 2 }, // Vehicles
|
||||
{ 0x63C6CCA8E68AE8C8, 2 }, //
|
||||
{ 0x509D5878EB39E842, 3 }, // Props
|
||||
{ 0x9A294B2138ABB884, 3 }, //
|
||||
{ 0x46818D79B1F7499A, 4 }, // Blips
|
||||
{ 0x5CDE92C702A8FCE7, 4 }, //
|
||||
{ 0xBE339365C863BD36, 4 }, //
|
||||
{ 0x5A039BB0BCA604B6, 4 }, //
|
||||
{ 0x0134F0835AB6BFCB, 5 } // Checkpoints
|
||||
};
|
||||
public static Dictionary<int, byte> ServerItems = new Dictionary<int, byte>();
|
||||
public static void CleanUpWorld()
|
||||
{
|
||||
if (ServerItems.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (ServerItems)
|
||||
{
|
||||
foreach (KeyValuePair<int, byte> item in ServerItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (item.Value)
|
||||
{
|
||||
case 1:
|
||||
World.GetAllEntities().FirstOrDefault(x => x.Handle == item.Key)?.Delete();
|
||||
break;
|
||||
case 2:
|
||||
World.GetAllVehicles().FirstOrDefault(x => x.Handle == item.Key)?.Delete();
|
||||
break;
|
||||
case 3:
|
||||
World.GetAllProps().FirstOrDefault(x => x.Handle == item.Key)?.Delete();
|
||||
break;
|
||||
case 4:
|
||||
Blip blip = new Blip(item.Key);
|
||||
if (blip.Exists())
|
||||
{
|
||||
blip.Delete();
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
Checkpoint checkpoint = new Checkpoint(item.Key);
|
||||
if (checkpoint.Exists())
|
||||
{
|
||||
checkpoint.Delete();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
GTA.UI.Notification.Show("~r~~h~CleanUpWorld() Error");
|
||||
Main.Logger.Error($"CleanUpWorld(): ~r~Item {item.Value} cannot be deleted!");
|
||||
}
|
||||
}
|
||||
|
||||
ServerItems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
foreach (var action in QueuedActions.ToArray())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (action())
|
||||
{
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
GTA.UI.Screen.ShowSubtitle(ex.ToString());
|
||||
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"> The action to be executed, must return a bool indicating whether the action cane be removed after execution.</param>
|
||||
public static void QueueAction(Func<bool> a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(a);
|
||||
}
|
||||
}
|
||||
public static void QueueAction(Action a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(() => { a(); return true; }) ;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears all queued actions
|
||||
/// </summary>
|
||||
public static void ClearQueuedActions()
|
||||
{
|
||||
lock (QueuedActions) { QueuedActions.Clear(); }
|
||||
}
|
||||
|
||||
public static string DumpCharacters()
|
||||
{
|
||||
string s = "Characters:";
|
||||
lock (EntityPool.PedsLock)
|
||||
{
|
||||
foreach (int id in EntityPool.GetPedIDs())
|
||||
{
|
||||
var c = EntityPool.GetPedByID(id);
|
||||
s+=$"\r\nID:{c.ID} Owner:{c.OwnerID} LastUpdated:{c.LastUpdated} LastSynced:{c.LastSynced} LastStateSynced:{c.LastStateSynced}";
|
||||
// s+=$"\r\n{c.IsAiming} {c.IsJumping} {c.IsOnFire} {c.IsOnLadder} {c.IsRagdoll} {c.IsReloading} {c.IsShooting} {c.Speed}";
|
||||
}
|
||||
}
|
||||
Main.Logger.Trace(s);
|
||||
return s;
|
||||
}
|
||||
public static string DumpPlayers()
|
||||
{
|
||||
string s = "Players:";
|
||||
foreach (PlayerData p in PlayerList.Players)
|
||||
{
|
||||
|
||||
s+=$"\r\nID:{p.PedID} Username:{p.Username}";
|
||||
}
|
||||
Main.Logger.Trace(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using GTA;
|
||||
|
||||
using System.Drawing;
|
||||
|
||||
using LemonUI;
|
||||
using LemonUI.Menus;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
};
|
||||
public static NativeMenu LastMenu { get; set; } = Menu;
|
||||
#region ITEMS
|
||||
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
|
||||
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!
|
||||
/// </summary>
|
||||
static CoopMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
_usernameItem.Activated += UsernameActivated;
|
||||
ServerIpItem.Activated += ServerIpActivated;
|
||||
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
|
||||
|
||||
|
||||
Menu.AddSubMenu(ServersMenu.Menu);
|
||||
|
||||
Menu.Add(_usernameItem);
|
||||
Menu.Add(ServerIpItem);
|
||||
Menu.Add(_serverConnectItem);
|
||||
|
||||
Menu.AddSubMenu(SettingsMenu.Menu);
|
||||
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(ServersMenu.Menu);
|
||||
|
||||
Menu.Add(_aboutItem);
|
||||
}
|
||||
|
||||
public static void UsernameActivated(object a, System.EventArgs b)
|
||||
{
|
||||
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
|
||||
if (!string.IsNullOrWhiteSpace(newUsername))
|
||||
{
|
||||
Main.Settings.Username = newUsername;
|
||||
Util.SaveSettings();
|
||||
|
||||
_usernameItem.AltTitle = newUsername;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ServerIpActivated(object a, System.EventArgs b)
|
||||
{
|
||||
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
|
||||
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
|
||||
{
|
||||
Main.Settings.LastServerAddress = newServerIp;
|
||||
Util.SaveSettings();
|
||||
|
||||
ServerIpItem.AltTitle = newServerIp;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitiateConnectionMenuSetting()
|
||||
{
|
||||
_usernameItem.Enabled = false;
|
||||
ServerIpItem.Enabled = false;
|
||||
_serverConnectItem.Enabled = false;
|
||||
}
|
||||
|
||||
public static void ConnectedMenuSetting()
|
||||
{
|
||||
_serverConnectItem.Enabled = true;
|
||||
_serverConnectItem.Title = "Disconnect";
|
||||
Menu.Visible = false;
|
||||
}
|
||||
|
||||
public static void DisconnectedMenuSetting()
|
||||
{
|
||||
_usernameItem.Enabled = true;
|
||||
ServerIpItem.Enabled = true;
|
||||
_serverConnectItem.Enabled = true;
|
||||
_serverConnectItem.Title = "Connect";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LemonUI;
|
||||
using LemonUI.Menus;
|
||||
using GTA;
|
||||
using System.Drawing;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DebugMenu
|
||||
{
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings") {
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
public static NativeMenu DiagnosticMenu = new NativeMenu("RAGECOOP", "Diagnostic", "Performence and Diagnostic")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
private static NativeItem d1=new NativeItem("PositionPrediction");
|
||||
static DebugMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
d1.Activated+=(sender,e) =>
|
||||
{
|
||||
try{ SyncParameters.PositioinPredictionDefault =float.Parse(Game.GetUserInput(WindowTitle.EnterMessage20, SyncParameters.PositioinPredictionDefault.ToString(), 20));}
|
||||
catch { }
|
||||
Update();
|
||||
};
|
||||
|
||||
|
||||
Menu.Add(d1);
|
||||
Menu.AddSubMenu(DiagnosticMenu);
|
||||
Menu.Opening+=(sender, e) =>Update();
|
||||
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()));
|
||||
}
|
||||
};
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
d1.AltTitle = SyncParameters.PositioinPredictionDefault.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LemonUI.Menus;
|
||||
using GTA;
|
||||
using System.Drawing;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DevToolMenu
|
||||
{
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "DevTool", "Help with the development")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
private static NativeCheckboxItem enableItem = new NativeCheckboxItem("Enable");
|
||||
|
||||
private static NativeCheckboxItem enableSecondaryItem = new NativeCheckboxItem("Secondary","Enable if this vehicle have two muzzles");
|
||||
public static NativeItem boneIndexItem = new NativeItem("Current bone index");
|
||||
public static NativeItem secondaryBoneIndexItem = new NativeItem("Secondary bone index");
|
||||
public static NativeItem clipboardItem = new NativeItem("Copy to clipboard");
|
||||
public static NativeListItem<MuzzleDir> dirItem = new NativeListItem<MuzzleDir>("Direction");
|
||||
static DevToolMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
enableItem.Activated+=enableItem_Activated;
|
||||
enableItem.Checked=false;
|
||||
enableSecondaryItem.CheckboxChanged+=EnableSecondaryItem_Changed;
|
||||
|
||||
secondaryBoneIndexItem.Enabled=false;
|
||||
clipboardItem.Activated+=ClipboardItem_Activated;
|
||||
dirItem.ItemChanged+=DirItem_ItemChanged;
|
||||
foreach (var d in Enum.GetValues(typeof(MuzzleDir)))
|
||||
{
|
||||
dirItem.Items.Add((MuzzleDir)d);
|
||||
}
|
||||
dirItem.SelectedIndex=0;
|
||||
|
||||
Menu.Add(enableItem);
|
||||
Menu.Add(boneIndexItem);
|
||||
Menu.Add(enableSecondaryItem);
|
||||
Menu.Add(secondaryBoneIndexItem);
|
||||
Menu.Add(dirItem);
|
||||
Menu.Add(clipboardItem);
|
||||
}
|
||||
|
||||
private static void EnableSecondaryItem_Changed(object sender, EventArgs e)
|
||||
{
|
||||
if (enableSecondaryItem.Checked)
|
||||
{
|
||||
DevTool.UseSecondary=true;
|
||||
secondaryBoneIndexItem.Enabled=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevTool.UseSecondary=false;
|
||||
secondaryBoneIndexItem.Enabled=false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DirItem_ItemChanged(object sender, ItemChangedEventArgs<MuzzleDir> e)
|
||||
{
|
||||
DevTool.Direction=dirItem.SelectedItem;
|
||||
}
|
||||
|
||||
private static void ClipboardItem_Activated(object sender, EventArgs e)
|
||||
{
|
||||
DevTool.CopyToClipboard(dirItem.SelectedItem);
|
||||
}
|
||||
|
||||
private static void enableItem_Activated(object sender, EventArgs e)
|
||||
{
|
||||
if (enableItem.Checked)
|
||||
{
|
||||
DevTool.ToMark=Game.Player.Character.CurrentVehicle;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevTool.ToMark=null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
using System.Drawing;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using GTA;
|
||||
using LemonUI.Menus;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
internal static class SettingsMenu
|
||||
{
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
|
||||
private static readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic);
|
||||
private static readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu);
|
||||
private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableTraffic);
|
||||
|
||||
private static readonly NativeCheckboxItem _showNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
|
||||
|
||||
private static NativeItem _menuKey = new NativeItem("Menu Key","The key to open menu", Main.Settings.MenuKey.ToString());
|
||||
private static NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
|
||||
private static NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).",Main.Settings.WorldVehicleSoftLimit.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
static SettingsMenu()
|
||||
{
|
||||
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
_disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged;
|
||||
_disablePauseAlt.CheckboxChanged+=_disablePauseAlt_CheckboxChanged;
|
||||
_flipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged;
|
||||
_showNetworkInfoItem.CheckboxChanged += ShowNetworkInfoCheckboxChanged;
|
||||
_menuKey.Activated+=ChaneMenuKey;
|
||||
_passengerKey.Activated+=ChangePassengerKey;
|
||||
_vehicleSoftLimit.Activated+=vehicleSoftLimit_Activated;
|
||||
|
||||
Menu.Add(_disableTrafficItem);
|
||||
Menu.Add(_disablePauseAlt);
|
||||
Menu.Add(_flipMenuItem);
|
||||
Menu.Add(_showNetworkInfoItem);
|
||||
Menu.Add(_menuKey);
|
||||
Menu.Add(_passengerKey);
|
||||
Menu.Add(_vehicleSoftLimit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void _disablePauseAlt_CheckboxChanged(object sender, EventArgs e)
|
||||
{
|
||||
Main.Settings.DisableAlternatePause=_disablePauseAlt.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
private static void vehicleSoftLimit_Activated(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.Settings.WorldVehicleSoftLimit =int.Parse(
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Main.Settings.WorldVehicleSoftLimit.ToString(), 20));
|
||||
_menuKey.AltTitle=Main.Settings.WorldVehicleSoftLimit.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
private static void ChaneMenuKey(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.Settings.MenuKey =(Keys)Enum.Parse(
|
||||
typeof(Keys),
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Main.Settings.MenuKey.ToString(), 20));
|
||||
_menuKey.AltTitle=Main.Settings.MenuKey.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static void ChangePassengerKey(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.Settings.PassengerKey =(Keys)Enum.Parse(
|
||||
typeof(Keys),
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Main.Settings.PassengerKey.ToString(), 20));
|
||||
_passengerKey.AltTitle=Main.Settings.PassengerKey.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static void DisableTrafficCheckboxChanged(object a, System.EventArgs b)
|
||||
{
|
||||
Main.Settings.DisableTraffic = _disableTrafficItem.Checked;
|
||||
Util.SaveSettings() ;
|
||||
}
|
||||
|
||||
public static void FlipMenuCheckboxChanged(object a, System.EventArgs b)
|
||||
{
|
||||
CoopMenu.Menu.Alignment = _flipMenuItem.Checked ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left;
|
||||
|
||||
Menu.Alignment = _flipMenuItem.Checked ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left;
|
||||
Main.Settings.FlipMenu = _flipMenuItem.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
|
||||
public static void ShowNetworkInfoCheckboxChanged(object a, System.EventArgs b)
|
||||
{
|
||||
Networking.ShowNetworkInfo = _showNetworkInfoItem.Checked;
|
||||
|
||||
if (!Networking.ShowNetworkInfo)
|
||||
{
|
||||
Networking.BytesReceived = 0;
|
||||
Networking.BytesSend = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DownloadManager
|
||||
{
|
||||
private static readonly List<DownloadFile> _downloadFiles = new List<DownloadFile>();
|
||||
private static readonly Dictionary<byte, FileStream> _streams = new Dictionary<byte, FileStream>();
|
||||
private static readonly List<byte> _filesFinished = new List<byte>();
|
||||
public static bool DownloadComplete = false;
|
||||
|
||||
public static void AddFile(byte id, string name, long length)
|
||||
{
|
||||
string downloadFolder = $"Scripts\\RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}";
|
||||
|
||||
if (!Directory.Exists(downloadFolder))
|
||||
{
|
||||
Directory.CreateDirectory(downloadFolder);
|
||||
}
|
||||
|
||||
if (FileAlreadyExists(downloadFolder, name, length))
|
||||
{
|
||||
// Send the server we are already done
|
||||
Networking.SendDownloadFinish(id);
|
||||
|
||||
Cancel(id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!new string[] { ".js", ".xml" }.Any(x => x == Path.GetExtension(name)))
|
||||
{
|
||||
Cancel(id);
|
||||
|
||||
GTA.UI.Notification.Show($"The download of a file from the server was blocked! [{name}]", true);
|
||||
Main.Logger.Error($"The download of a file from the server was blocked! [{name}]");
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_downloadFiles)
|
||||
{
|
||||
_downloadFiles.Add(new DownloadFile()
|
||||
{
|
||||
FileID = id,
|
||||
FileName = name,
|
||||
FileLength = length
|
||||
});
|
||||
}
|
||||
|
||||
lock (_streams)
|
||||
{
|
||||
_streams.Add(id, new FileStream($"{downloadFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
string 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 RenderProgress()
|
||||
{
|
||||
if (_downloadFiles.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_downloadFiles) lock (_filesFinished)
|
||||
{
|
||||
new LemonUI.Elements.ScaledText(new System.Drawing.PointF(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width / 2, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height - 60), $"Downloading files {_filesFinished.Count()} / {_downloadFiles.Count() + _filesFinished.Count()}", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(byte id, byte[] chunk)
|
||||
{
|
||||
lock (_filesFinished)
|
||||
{
|
||||
if (_filesFinished.Contains(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lock (_streams)
|
||||
{
|
||||
FileStream fs = _streams.FirstOrDefault(x => x.Key == id).Value;
|
||||
if (fs == null)
|
||||
{
|
||||
Main.Logger.Error($"Stream for file {id} not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
fs.Write(chunk, 0, chunk.Length);
|
||||
|
||||
lock (_downloadFiles)
|
||||
{
|
||||
DownloadFile file = _downloadFiles.FirstOrDefault(x => x.FileID == id);
|
||||
if (file == null)
|
||||
{
|
||||
Main.Logger.Error($"File {id} couldn't be found in the list!");
|
||||
return;
|
||||
}
|
||||
|
||||
file.FileWritten += chunk.Length;
|
||||
|
||||
if (file.FileWritten >= file.FileLength)
|
||||
{
|
||||
Cancel(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cancel(byte id)
|
||||
{
|
||||
lock (_streams) lock (_downloadFiles) lock (_filesFinished)
|
||||
{
|
||||
FileStream fs = _streams.ContainsKey(id) ? _streams[id] : null;
|
||||
if (fs != null)
|
||||
{
|
||||
fs.Close();
|
||||
fs.Dispose();
|
||||
|
||||
_streams.Remove(id);
|
||||
}
|
||||
|
||||
if (_downloadFiles.Any(x => x.FileID == id))
|
||||
{
|
||||
_downloadFiles.Remove(_downloadFiles.First(x => x.FileID == id));
|
||||
}
|
||||
|
||||
_filesFinished.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Cleanup(bool everything)
|
||||
{
|
||||
lock (_streams) lock (_downloadFiles) lock (_filesFinished)
|
||||
{
|
||||
foreach (KeyValuePair<byte, FileStream> stream in _streams)
|
||||
{
|
||||
stream.Value.Close();
|
||||
stream.Value.Dispose();
|
||||
}
|
||||
_streams.Clear();
|
||||
_downloadFiles.Clear();
|
||||
_filesFinished.Clear();
|
||||
}
|
||||
|
||||
if (everything)
|
||||
{
|
||||
DownloadComplete = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DownloadFile
|
||||
{
|
||||
public byte FileID { get; set; } = 0;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public long FileLength { get; set; } = 0;
|
||||
public long FileWritten { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
using System.Collections.Generic;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[XmlRoot(ElementName = "Map")]
|
||||
public class CoopMap
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[XmlArray("Props")]
|
||||
[XmlArrayItem("Prop")]
|
||||
public List<CoopProp> Props { get; set; } = new List<CoopProp>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public struct CoopProp
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Vector3 Position { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public Vector3 Rotation { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Hash { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool Dynamic { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int Texture { get; set; }
|
||||
}
|
||||
|
||||
public static class MapLoader
|
||||
{
|
||||
// string = file name
|
||||
private static readonly Dictionary<string, CoopMap> _maps = new Dictionary<string, CoopMap>();
|
||||
private static readonly List<int> _createdObjects = new List<int>();
|
||||
|
||||
public static void LoadAll()
|
||||
{
|
||||
string downloadFolder = $"Scripts\\RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}";
|
||||
|
||||
if (!Directory.Exists(downloadFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(downloadFolder);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex.Message);
|
||||
|
||||
// Without the directory we can't do the other stuff
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string[] files = Directory.GetFiles(downloadFolder, "*.xml");
|
||||
lock (_maps)
|
||||
{
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
string filePath = files[i];
|
||||
string fileName = Path.GetFileName(filePath);
|
||||
|
||||
XmlSerializer serializer = new XmlSerializer(typeof(CoopMap));
|
||||
CoopMap map;
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Open))
|
||||
{
|
||||
try
|
||||
{
|
||||
map = (CoopMap)serializer.Deserialize(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error($"The map with the name \"{fileName}\" couldn't be added!");
|
||||
Main.Logger.Error($"{ex.Message}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_maps.Add(fileName, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadMap(string name)
|
||||
{
|
||||
lock (_maps) lock (_createdObjects)
|
||||
{
|
||||
if (!_maps.ContainsKey(name) || _createdObjects.Count != 0)
|
||||
{
|
||||
GTA.UI.Notification.Show($"The map with the name \"{name}\" couldn't be loaded!");
|
||||
Main.Logger.Error($"The map with the name \"{name}\" couldn't be loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
CoopMap map = _maps[name];
|
||||
|
||||
foreach (CoopProp prop in map.Props)
|
||||
{
|
||||
Model model = prop.Hash.ModelRequest();
|
||||
if (model == null)
|
||||
{
|
||||
Main.Logger.Error($"Model for object \"{model.Hash}\" couldn't be loaded!");
|
||||
continue;
|
||||
}
|
||||
|
||||
int handle = Function.Call<int>(Hash.CREATE_OBJECT, model.Hash, prop.Position.X, prop.Position.Y, prop.Position.Z, 1, 1, prop.Dynamic);
|
||||
model.MarkAsNoLongerNeeded();
|
||||
if (handle == 0)
|
||||
{
|
||||
Main.Logger.Error($"Object \"{prop.Hash}\" couldn't be created!");
|
||||
continue;
|
||||
}
|
||||
|
||||
_createdObjects.Add(handle);
|
||||
|
||||
if (prop.Texture > 0 && prop.Texture < 16)
|
||||
{
|
||||
Function.Call(Hash._SET_OBJECT_TEXTURE_VARIATION, handle, prop.Texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AnyMapLoaded()
|
||||
{
|
||||
lock (_createdObjects) return _createdObjects.Any();
|
||||
}
|
||||
|
||||
public static void UnloadMap()
|
||||
{
|
||||
lock (_createdObjects)
|
||||
{
|
||||
foreach (int handle in _createdObjects)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
int tmpHandle = handle;
|
||||
Function.Call(Hash.DELETE_OBJECT, &tmpHandle);
|
||||
}
|
||||
}
|
||||
|
||||
_createdObjects.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteAll()
|
||||
{
|
||||
UnloadMap();
|
||||
lock (_maps)
|
||||
{
|
||||
_maps.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
public static NetClient Client;
|
||||
public static float Latency = 0;
|
||||
public static bool ShowNetworkInfo = false;
|
||||
public static int BytesReceived = 0;
|
||||
public static int BytesSend = 0;
|
||||
private static Thread ReceiveThread;
|
||||
|
||||
public static void ToggleConnection(string address)
|
||||
{
|
||||
if (IsOnServer)
|
||||
{
|
||||
Client.Disconnect("Bye!");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
|
||||
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
|
||||
{
|
||||
AutoFlushSendQueue = false
|
||||
};
|
||||
|
||||
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
|
||||
|
||||
Client = new NetClient(config);
|
||||
|
||||
Client.Start();
|
||||
|
||||
string[] ip = new string[2];
|
||||
|
||||
int 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");
|
||||
}
|
||||
|
||||
// Send HandshakePacket
|
||||
EntityPool.AddPlayer();
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
new Packets.Handshake()
|
||||
{
|
||||
PedID = Main.LocalPlayerID,
|
||||
Username = Main.Settings.Username,
|
||||
ModVersion = Main.CurrentVersion,
|
||||
}.Pack(outgoingMessage);
|
||||
|
||||
Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
|
||||
}
|
||||
}
|
||||
public static bool IsOnServer
|
||||
{
|
||||
get { return Client?.ConnectionStatus == NetConnectionStatus.Connected; }
|
||||
}
|
||||
public static void Start()
|
||||
{
|
||||
ReceiveThread=new Thread(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Client!=null)
|
||||
{
|
||||
Client.FlushSendQueue();
|
||||
ReceiveMessages();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
}
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
});
|
||||
ReceiveThread.Start();
|
||||
}
|
||||
|
||||
#region -- GET --
|
||||
#region -- PLAYER --
|
||||
private static void PlayerConnect(Packets.PlayerConnect packet)
|
||||
{
|
||||
var p = new PlayerData
|
||||
{
|
||||
PedID = packet.PedID,
|
||||
Username= packet.Username,
|
||||
};
|
||||
GTA.UI.Notification.Show($"{p.Username} connected.");
|
||||
PlayerList.SetPlayer(packet.PedID, packet.Username);
|
||||
|
||||
Main.Logger.Debug($"player connected:{p.Username}");
|
||||
Main.DumpCharacters();
|
||||
COOPAPI.Connected(packet.PedID);
|
||||
}
|
||||
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
|
||||
{
|
||||
var name=PlayerList.GetPlayer(packet.PedID).Username;
|
||||
GTA.UI.Notification.Show($"{name} left.");
|
||||
COOPAPI.Disconnected(packet.PedID);
|
||||
PlayerList.RemovePlayer(packet.PedID);
|
||||
EntityPool.RemoveAllFromPlayer(packet.PedID);
|
||||
|
||||
|
||||
}
|
||||
private static object DecodeNativeCall(ulong hash, List<object> args, bool returnValue, byte? returnType = null)
|
||||
{
|
||||
List<InputArgument> arguments = new List<InputArgument>();
|
||||
|
||||
if (args == null || args.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (ushort i = 0; i < args.Count; i++)
|
||||
{
|
||||
object x = args.ElementAt(i);
|
||||
switch (x)
|
||||
{
|
||||
case int _:
|
||||
arguments.Add((int)x);
|
||||
break;
|
||||
case bool _:
|
||||
arguments.Add((bool)x);
|
||||
break;
|
||||
case float _:
|
||||
arguments.Add((float)x);
|
||||
break;
|
||||
case string _:
|
||||
arguments.Add((string)x);
|
||||
break;
|
||||
case Vector3 _:
|
||||
Vector3 vector = (Vector3)x;
|
||||
arguments.Add((float)vector.X);
|
||||
arguments.Add((float)vector.Y);
|
||||
arguments.Add((float)vector.Z);
|
||||
break;
|
||||
default:
|
||||
GTA.UI.Notification.Show("[DecodeNativeCall][" + hash + "]: Type of argument not found!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!returnValue)
|
||||
{
|
||||
Function.Call((Hash)hash, arguments.ToArray());
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (returnType.Value)
|
||||
{
|
||||
case 0x00: // int
|
||||
return Function.Call<int>((Hash)hash, arguments.ToArray());
|
||||
case 0x01: // bool
|
||||
return Function.Call<bool>((Hash)hash, arguments.ToArray());
|
||||
case 0x02: // float
|
||||
return Function.Call<float>((Hash)hash, arguments.ToArray());
|
||||
case 0x03: // string
|
||||
return Function.Call<string>((Hash)hash, arguments.ToArray());
|
||||
case 0x04: // vector3
|
||||
return Function.Call<Vector3>((Hash)hash, arguments.ToArray());
|
||||
default:
|
||||
GTA.UI.Notification.Show("[DecodeNativeCall][" + hash + "]: Type of return not found!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DecodeNativeResponse(Packets.NativeResponse packet)
|
||||
{
|
||||
object result = DecodeNativeCall(packet.Hash, packet.Args, true, packet.ResultType);
|
||||
|
||||
if (Main.CheckNativeHash.ContainsKey(packet.Hash))
|
||||
{
|
||||
foreach (KeyValuePair<ulong, byte> hash in Main.CheckNativeHash)
|
||||
{
|
||||
if (hash.Key == packet.Hash)
|
||||
{
|
||||
lock (Main.ServerItems)
|
||||
{
|
||||
Main.ServerItems.Add((int)result, hash.Value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
new Packets.NativeResponse()
|
||||
{
|
||||
Hash = 0,
|
||||
Args = new List<object>() { result },
|
||||
ID = packet.ID
|
||||
}.Pack(outgoingMessage);
|
||||
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
|
||||
Client.FlushSendQueue();
|
||||
}
|
||||
#endregion // -- PLAYER --
|
||||
|
||||
#endregion
|
||||
public static void Tick()
|
||||
{
|
||||
|
||||
|
||||
|
||||
// Sync
|
||||
EntityPool.DoSync();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,423 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using RageCoop.Client.Menus;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
public static void ReceiveMessages()
|
||||
{
|
||||
if (Client == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NetIncomingMessage message;
|
||||
|
||||
while ((message = Client.ReadMessage()) != null)
|
||||
{
|
||||
BytesReceived += message.LengthBytes;
|
||||
|
||||
switch (message.MessageType)
|
||||
{
|
||||
case NetIncomingMessageType.StatusChanged:
|
||||
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
|
||||
|
||||
string reason = message.ReadString();
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
#if !NON_INTERACTIVE
|
||||
CoopMenu.InitiateConnectionMenuSetting();
|
||||
#endif
|
||||
Main.QueueAction(() => { GTA.UI.Notification.Show("~y~Trying to connect..."); return true; });
|
||||
break;
|
||||
case NetConnectionStatus.Connected:
|
||||
if (message.SenderConnection.RemoteHailMessage.ReadByte() != (byte)PacketTypes.Handshake)
|
||||
{
|
||||
Client.Disconnect("Wrong packet!");
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = message.SenderConnection.RemoteHailMessage.ReadInt32();
|
||||
byte[] data = message.SenderConnection.RemoteHailMessage.ReadBytes(len);
|
||||
|
||||
Packets.Handshake handshakePacket = new Packets.Handshake();
|
||||
handshakePacket.Unpack(data);
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
|
||||
#endif
|
||||
|
||||
COOPAPI.Connected();
|
||||
Main.QueueAction(() => {
|
||||
CoopMenu.ConnectedMenuSetting();
|
||||
Main.MainChat.Init();
|
||||
PlayerList.Cleanup();
|
||||
GTA.UI.Notification.Show("~g~Connected!");
|
||||
});
|
||||
|
||||
Main.Logger.Info(">> Connected <<");
|
||||
}
|
||||
break;
|
||||
case NetConnectionStatus.Disconnected:
|
||||
DownloadManager.Cleanup(true);
|
||||
|
||||
// Reset all values
|
||||
Latency = 0;
|
||||
|
||||
Main.QueueAction(() => Main.CleanUpWorld());
|
||||
|
||||
if (Main.MainChat.Focused)
|
||||
{
|
||||
Main.MainChat.Focused = false;
|
||||
}
|
||||
|
||||
Main.QueueAction(() => Main.CleanUp());
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
CoopMenu.DisconnectedMenuSetting();
|
||||
#endif
|
||||
|
||||
COOPAPI.Disconnected(reason);
|
||||
Main.QueueAction(() =>
|
||||
GTA.UI.Notification.Show("~r~Disconnected: " + reason));
|
||||
|
||||
MapLoader.DeleteAll();
|
||||
|
||||
Main.Logger.Info($">> Disconnected << reason: {reason}");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.Data:
|
||||
if (message.LengthBytes==0) { continue; }
|
||||
|
||||
var packetType = (PacketTypes)message.ReadByte();
|
||||
try
|
||||
{
|
||||
|
||||
int len = message.ReadInt32();
|
||||
byte[] data = message.ReadBytes(len);
|
||||
switch (packetType)
|
||||
{
|
||||
case PacketTypes.CleanUpWorld:
|
||||
{
|
||||
Main.QueueAction(() => { Main.CleanUpWorld(); return true; });
|
||||
}
|
||||
break;
|
||||
case PacketTypes.PlayerConnect:
|
||||
{
|
||||
|
||||
Packets.PlayerConnect packet = new Packets.PlayerConnect();
|
||||
packet.Unpack(data);
|
||||
|
||||
Main.QueueAction(() =>PlayerConnect(packet));
|
||||
}
|
||||
break;
|
||||
case PacketTypes.PlayerDisconnect:
|
||||
{
|
||||
|
||||
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect();
|
||||
packet.Unpack(data);
|
||||
Main.QueueAction(() =>PlayerDisconnect(packet));
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.PlayerInfoUpdate:
|
||||
{
|
||||
var packet = new Packets.PlayerInfoUpdate();
|
||||
packet.Unpack(data);
|
||||
PlayerList.SetPlayer(packet.PedID,packet.Username,packet.Latency);
|
||||
break;
|
||||
}
|
||||
#region ENTITY SYNC
|
||||
case PacketTypes.VehicleSync:
|
||||
{
|
||||
|
||||
Packets.VehicleSync packet = new Packets.VehicleSync();
|
||||
packet.Unpack(data);
|
||||
VehicleSync(packet);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.PedSync:
|
||||
{
|
||||
|
||||
Packets.PedSync packet = new Packets.PedSync();
|
||||
packet.Unpack(data);
|
||||
PedSync(packet);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.VehicleStateSync:
|
||||
{
|
||||
|
||||
Packets.VehicleStateSync packet = new Packets.VehicleStateSync();
|
||||
packet.Unpack(data);
|
||||
VehicleStateSync(packet);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.PedStateSync:
|
||||
{
|
||||
|
||||
|
||||
Packets.PedStateSync packet = new Packets.PedStateSync();
|
||||
packet.Unpack(data);
|
||||
PedStateSync(packet);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.ProjectileSync:
|
||||
{
|
||||
Packets.ProjectileSync packet = new Packets.ProjectileSync();
|
||||
packet.Unpack(data);
|
||||
ProjectileSync(packet);
|
||||
break;
|
||||
}
|
||||
#endregion
|
||||
case PacketTypes.ChatMessage:
|
||||
{
|
||||
|
||||
Packets.ChatMessage packet = new Packets.ChatMessage();
|
||||
packet.Unpack(data);
|
||||
|
||||
if (!COOPAPI.ChatMessageReceived(packet.Username, packet.Message))
|
||||
{
|
||||
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message);return true; });
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.NativeCall:
|
||||
{
|
||||
|
||||
Packets.NativeCall packet = new Packets.NativeCall();
|
||||
packet.Unpack(data);
|
||||
|
||||
DecodeNativeCall(packet.Hash, packet.Args, false);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.NativeResponse:
|
||||
{
|
||||
|
||||
Packets.NativeResponse packet = new Packets.NativeResponse();
|
||||
packet.Unpack(data);
|
||||
|
||||
DecodeNativeResponse(packet);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.Mod:
|
||||
{
|
||||
Packets.Mod packet = new Packets.Mod();
|
||||
packet.Unpack(data);
|
||||
// Need to do some stuff here
|
||||
}
|
||||
break;
|
||||
case PacketTypes.FileTransferTick:
|
||||
{
|
||||
Packets.FileTransferTick packet = new Packets.FileTransferTick();
|
||||
packet.Unpack(data);
|
||||
|
||||
DownloadManager.Write(packet.ID, packet.FileChunk);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.FileTransferRequest:
|
||||
{
|
||||
Packets.FileTransferRequest packet = new Packets.FileTransferRequest();
|
||||
packet.Unpack(data);
|
||||
|
||||
DownloadManager.AddFile(packet.ID, packet.FileName, packet.FileLength);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.FileTransferComplete:
|
||||
{
|
||||
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
|
||||
packet.Unpack(data);
|
||||
|
||||
DownloadManager.Cleanup(false);
|
||||
DownloadManager.DownloadComplete = true;
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.ServerClientEvent:
|
||||
{
|
||||
Packets.ServerClientEvent packet = new Packets.ServerClientEvent();
|
||||
packet.Unpack(data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (packetType.IsSyncEvent())
|
||||
{
|
||||
// Dispatch to main thread
|
||||
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.QueueAction(() => {
|
||||
GTA.UI.Notification.Show("~r~~h~Packet Error");
|
||||
return true;
|
||||
});
|
||||
Main.Logger.Error($"[{packetType}] {ex.Message}");
|
||||
Main.Logger.Error(ex);
|
||||
Client.Disconnect($"Packet Error [{packetType}]");
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.ConnectionLatencyUpdated:
|
||||
Latency = message.ReadFloat();
|
||||
break;
|
||||
case NetIncomingMessageType.DebugMessage:
|
||||
case NetIncomingMessageType.ErrorMessage:
|
||||
case NetIncomingMessageType.WarningMessage:
|
||||
case NetIncomingMessageType.VerboseDebugMessage:
|
||||
#if DEBUG
|
||||
// TODO?
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Client.Recycle(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void PedSync(Packets.PedSync packet)
|
||||
{
|
||||
SyncedPed c = EntityPool.GetPedByID(packet.ID);
|
||||
if (c==null)
|
||||
{
|
||||
Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
|
||||
EntityPool.ThreadSafe.Add(c=new SyncedPed(packet.ID));
|
||||
}
|
||||
PedDataFlags flags = packet.Flag;
|
||||
c.ID=packet.ID;
|
||||
//c.OwnerID=packet.OwnerID;
|
||||
c.Health = packet.Health;
|
||||
c.Position = packet.Position;
|
||||
c.Rotation = packet.Rotation;
|
||||
c.Velocity = packet.Velocity;
|
||||
c.Speed = packet.Speed;
|
||||
c.CurrentWeaponHash = packet.CurrentWeaponHash;
|
||||
c.IsAiming = flags.HasFlag(PedDataFlags.IsAiming);
|
||||
c.IsReloading = flags.HasFlag(PedDataFlags.IsReloading);
|
||||
c.IsJumping = flags.HasFlag(PedDataFlags.IsJumping);
|
||||
c.IsRagdoll = flags.HasFlag(PedDataFlags.IsRagdoll);
|
||||
c.IsOnFire = flags.HasFlag(PedDataFlags.IsOnFire);
|
||||
c.IsInParachuteFreeFall = flags.HasFlag(PedDataFlags.IsInParachuteFreeFall);
|
||||
c.IsParachuteOpen = flags.HasFlag(PedDataFlags.IsParachuteOpen);
|
||||
c.IsOnLadder = flags.HasFlag(PedDataFlags.IsOnLadder);
|
||||
c.IsVaulting = flags.HasFlag(PedDataFlags.IsVaulting);
|
||||
c.IsInCover = flags.HasFlag(PedDataFlags.IsInCover);
|
||||
c.Heading=packet.Heading;
|
||||
c.LastSynced = Main.Ticked;
|
||||
if (c.IsAiming)
|
||||
{
|
||||
c.AimCoords = packet.AimCoords;
|
||||
}
|
||||
if (c.IsRagdoll)
|
||||
{
|
||||
c.RotationVelocity=packet.RotationVelocity;
|
||||
}
|
||||
}
|
||||
private static void PedStateSync(Packets.PedStateSync packet)
|
||||
{
|
||||
SyncedPed c = EntityPool.GetPedByID(packet.ID);
|
||||
if (c==null) { return; }
|
||||
c.ID=packet.ID;
|
||||
c.OwnerID=packet.OwnerID;
|
||||
c.Clothes=packet.Clothes;
|
||||
c.WeaponComponents=packet.WeaponComponents;
|
||||
c.ModelHash=packet.ModelHash;
|
||||
c.LastStateSynced = Main.Ticked;
|
||||
}
|
||||
private static void VehicleSync(Packets.VehicleSync packet)
|
||||
{
|
||||
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
|
||||
if (v==null)
|
||||
{
|
||||
EntityPool.ThreadSafe.Add(v=new SyncedVehicle(packet.ID));
|
||||
}
|
||||
if (v.IsMine) { return; }
|
||||
v.ID= packet.ID;
|
||||
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;
|
||||
v.LastSynced=Main.Ticked;
|
||||
}
|
||||
private static void VehicleStateSync(Packets.VehicleStateSync packet)
|
||||
{
|
||||
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
|
||||
if (v==null||v.IsMine) { return; }
|
||||
v.ID= packet.ID;
|
||||
v.OwnerID= packet.OwnerID;
|
||||
v.DamageModel=packet.DamageModel;
|
||||
v.EngineHealth=packet.EngineHealth;
|
||||
v.OwnerID=packet.OwnerID;
|
||||
v.Mods=packet.Mods;
|
||||
v.ModelHash=packet.ModelHash;
|
||||
v.Colors=packet.Colors;
|
||||
v.LandingGear=packet.LandingGear;
|
||||
v.EngineRunning = packet.Flag.HasFlag(VehicleDataFlags.IsEngineRunning);
|
||||
v.LightsOn = packet.Flag.HasFlag(VehicleDataFlags.AreLightsOn);
|
||||
v.BrakeLightsOn = packet.Flag.HasFlag(VehicleDataFlags.AreBrakeLightsOn);
|
||||
v.HighBeamsOn = packet.Flag.HasFlag(VehicleDataFlags.AreHighBeamsOn);
|
||||
v.SireneActive = packet.Flag.HasFlag(VehicleDataFlags.IsSirenActive);
|
||||
v.IsDead = packet.Flag.HasFlag(VehicleDataFlags.IsDead);
|
||||
v.HornActive = packet.Flag.HasFlag(VehicleDataFlags.IsHornActive);
|
||||
v.Transformed = packet.Flag.HasFlag(VehicleDataFlags.IsTransformed);
|
||||
v.Passengers=new Dictionary<VehicleSeat, SyncedPed>();
|
||||
v.LockStatus=packet.LockStatus;
|
||||
v.RadioStation=packet.RadioStation;
|
||||
v.Flags=packet.Flag;
|
||||
foreach (KeyValuePair<int, int> pair in packet.Passengers)
|
||||
{
|
||||
if (EntityPool.PedExists(pair.Value))
|
||||
{
|
||||
v.Passengers.Add((VehicleSeat)pair.Key, EntityPool.GetPedByID(pair.Value));
|
||||
}
|
||||
}
|
||||
v.LastStateSynced= Main.Ticked;
|
||||
|
||||
}
|
||||
private static void ProjectileSync(Packets.ProjectileSync packet)
|
||||
{
|
||||
var p = EntityPool.GetProjectileByID(packet.ID);
|
||||
if (p==null)
|
||||
{
|
||||
if (packet.Exploded) { return; }
|
||||
Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
|
||||
EntityPool.ThreadSafe.Add(p=new SyncedProjectile(packet.ID));
|
||||
}
|
||||
p.Position=packet.Position;
|
||||
p.Rotation=packet.Rotation;
|
||||
p.Velocity=packet.Velocity;
|
||||
p.Hash=(WeaponHash)packet.WeaponHash;
|
||||
p.ShooterID=packet.ShooterID;
|
||||
p.Exploded=packet.Exploded;
|
||||
p.LastSynced=Main.Ticked;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.Math;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
|
||||
|
||||
#region -- SEND --
|
||||
/// <summary>
|
||||
/// Pack the packet then send to server.
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
/// <param name="channel"></param>
|
||||
/// <param name="method"></param>
|
||||
public static void Send(Packet p, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
|
||||
{
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
p.Pack(outgoingMessage);
|
||||
Client.SendMessage(outgoingMessage, method, (int)channel);
|
||||
}
|
||||
|
||||
public static void SendPed(SyncedPed c)
|
||||
{
|
||||
Ped p = c.MainPed;
|
||||
var packet=new Packets.PedSync()
|
||||
{
|
||||
ID =c.ID,
|
||||
Health = p.Health,
|
||||
Position = p.Position,
|
||||
Rotation = p.Rotation,
|
||||
Velocity = p.Velocity,
|
||||
Speed = p.GetPedSpeed(),
|
||||
CurrentWeaponHash = (uint)p.Weapons.Current.Hash,
|
||||
Flag = p.GetPedFlags(),
|
||||
Heading=p.Heading,
|
||||
};
|
||||
if (packet.Flag.HasFlag(PedDataFlags.IsAiming))
|
||||
{
|
||||
packet.AimCoords = p.GetAimCoord();
|
||||
}
|
||||
if (packet.Flag.HasFlag(PedDataFlags.IsRagdoll))
|
||||
{
|
||||
packet.RotationVelocity=p.RotationVelocity;
|
||||
}
|
||||
Send(packet, ConnectionChannel.PedSync);
|
||||
}
|
||||
public static void SendPedState(SyncedPed c)
|
||||
{
|
||||
Ped p = c.MainPed;
|
||||
|
||||
var packet=new Packets.PedStateSync()
|
||||
{
|
||||
ID = c.ID,
|
||||
OwnerID=c.OwnerID,
|
||||
Clothes=p.GetPedClothes(),
|
||||
ModelHash=p.Model.Hash,
|
||||
WeaponComponents=p.Weapons.Current.GetWeaponComponents(),
|
||||
};
|
||||
|
||||
Send(packet, ConnectionChannel.PedSync);
|
||||
}
|
||||
public static void SendVehicle(SyncedVehicle v)
|
||||
{
|
||||
Vehicle veh = v.MainVehicle;
|
||||
var packet = new Packets.VehicleSync()
|
||||
{
|
||||
ID =v.ID,
|
||||
SteeringAngle = veh.SteeringAngle,
|
||||
Position = veh.PredictPosition(),
|
||||
Quaternion=veh.Quaternion,
|
||||
// Rotation = veh.Rotation,
|
||||
Velocity = veh.Velocity,
|
||||
RotationVelocity=veh.RotationVelocity,
|
||||
ThrottlePower = veh.ThrottlePower,
|
||||
BrakePower = veh.BrakePower,
|
||||
};
|
||||
if (v.MainVehicle.Model.Hash==1483171323) { packet.DeluxoWingRatio=v.MainVehicle.GetDeluxoWingRatio(); }
|
||||
Send(packet,ConnectionChannel.VehicleSync);
|
||||
}
|
||||
public static void SendVehicleState(SyncedVehicle v)
|
||||
{
|
||||
Vehicle veh = v.MainVehicle;
|
||||
byte primaryColor = 0;
|
||||
byte secondaryColor = 0;
|
||||
unsafe
|
||||
{
|
||||
Function.Call<byte>(Hash.GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
|
||||
}
|
||||
var packet=new Packets.VehicleStateSync()
|
||||
{
|
||||
ID =v.ID,
|
||||
OwnerID = v.OwnerID,
|
||||
Flag = veh.GetVehicleFlags(),
|
||||
Colors=new byte[] { primaryColor, secondaryColor },
|
||||
DamageModel=veh.GetVehicleDamageModel(),
|
||||
LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0,
|
||||
Mods = veh.Mods.GetVehicleMods(),
|
||||
ModelHash=veh.Model.Hash,
|
||||
EngineHealth=veh.EngineHealth,
|
||||
Passengers=veh.GetPassengers(),
|
||||
LockStatus=veh.LockStatus,
|
||||
};
|
||||
if (v.MainVehicle==Game.Player.LastVehicle)
|
||||
{
|
||||
packet.RadioStation=Util.GetPlayerRadioIndex();
|
||||
}
|
||||
Send(packet, ConnectionChannel.VehicleSync);
|
||||
}
|
||||
public static void SendProjectile(SyncedProjectile sp)
|
||||
{
|
||||
var p = sp.MainProjectile;
|
||||
var packet = new Packets.ProjectileSync()
|
||||
{
|
||||
ID =sp.ID,
|
||||
ShooterID=sp.ShooterID,
|
||||
Position=p.Position,
|
||||
Rotation=p.Rotation,
|
||||
Velocity=p.Velocity,
|
||||
WeaponHash=(uint)p.WeaponHash,
|
||||
Exploded=p.IsDead
|
||||
};
|
||||
if (p.IsDead) { EntityPool.RemoveProjectile(sp.ID,"Dead"); }
|
||||
Send(packet, ConnectionChannel.ProjectileSync);
|
||||
}
|
||||
|
||||
|
||||
#region SYNC EVENTS
|
||||
public static void SendBulletShot(Vector3 start,Vector3 end,uint weapon,int ownerID)
|
||||
{
|
||||
Send(new Packets.BulletShot()
|
||||
{
|
||||
StartPosition = start,
|
||||
EndPosition = end,
|
||||
OwnerID = ownerID,
|
||||
WeaponHash=weapon,
|
||||
}, ConnectionChannel.SyncEvents);
|
||||
}
|
||||
#endregion
|
||||
public static void SendChatMessage(string message)
|
||||
{
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
|
||||
new Packets.ChatMessage() { Username = Main.Settings.Username, Message = message }.Pack(outgoingMessage);
|
||||
|
||||
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat);
|
||||
Client.FlushSendQueue();
|
||||
|
||||
#if DEBUG
|
||||
if (ShowNetworkInfo)
|
||||
{
|
||||
BytesSend += outgoingMessage.LengthBytes;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
public static void SendDownloadFinish(byte id)
|
||||
{
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
|
||||
new Packets.FileTransferComplete() { ID = id }.Pack(outgoingMessage);
|
||||
|
||||
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableUnordered, (byte)ConnectionChannel.File);
|
||||
Client.FlushSendQueue();
|
||||
|
||||
#if DEBUG
|
||||
if (ShowNetworkInfo)
|
||||
{
|
||||
BytesSend += outgoingMessage.LengthBytes;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
public 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 ulong Pressed { get; set; }
|
||||
|
||||
public static bool LeftAlign = true;
|
||||
public static List<PlayerData> Players=new List<PlayerData> { };
|
||||
public static void Tick()
|
||||
{
|
||||
if (!Networking.IsOnServer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((Util.GetTickCount64() - _lastUpdate) >= 1000)
|
||||
{
|
||||
Update( Main.Settings.Username);
|
||||
}
|
||||
|
||||
if ((Util.GetTickCount64() - Pressed) < 5000 && !Main.MainChat.Focused
|
||||
#if !NON_INTERACTIVE
|
||||
&& !Menus.CoopMenu.MenuPool.AreAnyVisible
|
||||
#endif
|
||||
)
|
||||
{
|
||||
Function.Call(Hash.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( string localUsername)
|
||||
{
|
||||
_lastUpdate = Util.GetTickCount64();
|
||||
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT", 0, $"{Networking.Latency * 1000:N0}ms", localUsername, 116, 0, 0, "", "", 2, "", "", ' ');
|
||||
|
||||
int i = 1;
|
||||
|
||||
foreach (var player in Players)
|
||||
{
|
||||
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Latency * 1000:N0}ms", player.Username, 116, 0, i - 1, "", "", 2, "", "", ' ');
|
||||
}
|
||||
|
||||
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count+1} players");
|
||||
_mainScaleform.CallFunction("DISPLAY_VIEW");
|
||||
}
|
||||
public static void SetPlayer(int id, string username,float latency=0)
|
||||
{
|
||||
|
||||
var toset = Players.Where(x => x.PedID==id);
|
||||
if (toset.Any())
|
||||
{
|
||||
var p=toset.First();
|
||||
p.Username=username;
|
||||
p.PedID=id;
|
||||
p.Latency=latency;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerData p = new PlayerData { PedID=id, Username=username,Latency=latency };
|
||||
Players.Add(p);
|
||||
}
|
||||
}
|
||||
public static PlayerData GetPlayer(int id)
|
||||
{
|
||||
return Players.Find(x => x.PedID==id);
|
||||
}
|
||||
public static void RemovePlayer(int id)
|
||||
{
|
||||
var p = Players.Where(x => x.PedID==id);
|
||||
if (p.Any())
|
||||
{
|
||||
Players.Remove(p.First());
|
||||
}
|
||||
}
|
||||
public static void Cleanup()
|
||||
{
|
||||
Players=new List<PlayerData> { };
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
<?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>{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>RageCoop.Client</RootNamespace>
|
||||
<AssemblyName>RageCoop.Client</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>D:\Games\Grand Theft Auto V\Scripts\RageCoop\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
|
||||
<DocumentationFile>
|
||||
</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="LemonUI.SHVDN3, Version=1.5.1.0, Culture=neutral, processorArchitecture=AMD64">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Libs\Release\scripts\LemonUI.SHVDN3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lidgren.Network, Version=2012.1.7.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Libs\Release\scripts\Lidgren.Network.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Libs\Release\scripts\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ScriptHookVDotNet3, Version=3.3.2.0, Culture=neutral, processorArchitecture=AMD64">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Libs\Release\ScriptHookVDotNet3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<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="DevTools\DevTool.cs" />
|
||||
<Compile Include="Menus\Sub\DevToolMenu.cs" />
|
||||
<Compile Include="Menus\Sub\ServersMenu.cs" />
|
||||
<Compile Include="Scripting\ClientScript.cs" />
|
||||
<Compile Include="Scripting\Engine.cs" />
|
||||
<Compile Include="Util\MathExtensions.cs" />
|
||||
<Compile Include="Util\PedConfigFlags.cs" />
|
||||
<Compile Include="Util\Util.cs" />
|
||||
<Compile Include="Util\VehicleExtensions.cs" />
|
||||
<Compile Include="Util\WeaponUtil.cs" />
|
||||
<Compile Include="Networking\Chat.cs" />
|
||||
<Compile Include="COOPAPI.cs" />
|
||||
<Compile Include="Debug.cs" />
|
||||
<Compile Include="Networking\DownloadManager.cs" />
|
||||
<Compile Include="Util\TaskType.cs" />
|
||||
<Compile Include="Menus\Sub\DebugMenu.cs" />
|
||||
<Compile Include="Networking\Receive.cs" />
|
||||
<Compile Include="Networking\Send.cs" />
|
||||
<Compile Include="Sync\Entities\SyncedPed.cs" />
|
||||
<None Include="app.config" />
|
||||
<Compile Include="Sync\Entities\SyncedProjectile.cs" />
|
||||
<Compile Include="Sync\EntityPool.cs" />
|
||||
<Compile Include="Sync\SyncEvents.cs" />
|
||||
<Compile Include="Sync\Entities\SyncedEntity.cs" />
|
||||
<Compile Include="Sync\Entities\SyncedVehicle.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="Networking\MapLoader.cs" />
|
||||
<Compile Include="Menus\CoopMenu.cs" />
|
||||
<Compile Include="Menus\Sub\SettingsMenu.cs" />
|
||||
<Compile Include="Networking\Networking.cs" />
|
||||
<Compile Include="PlayerList.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Sync\SyncParameters.cs" />
|
||||
<Compile Include="Util\PedExtensions.cs" />
|
||||
<Compile Include="WorldThread.cs" />
|
||||
<Compile Include="Settings.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Core\RageCoop.Core.csproj">
|
||||
<Project>{cc2e8102-e568-4524-aa1f-f8e0f1cfe58a}</Project>
|
||||
<Name>RageCoop.Core</Name>
|
||||
</ProjectReference>
|
||||
</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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using RageCoop.Core;
|
||||
using RageCoop.Core.Scripting;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Inherit from this class, constructor will be called when the script is loaded.
|
||||
/// </summary>
|
||||
public abstract class ClientScript
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.CodeDom.Compiler;
|
||||
|
||||
namespace RageCoop.Client.Scripting
|
||||
{
|
||||
internal class Engine : Core.Scripting.ScriptingEngine
|
||||
{
|
||||
public Engine() : base(typeof(ClientScript), Main.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
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!
|
||||
/// </summary>
|
||||
internal static class CoopMenu
|
||||
{
|
||||
public static ObjectPool MenuPool = new ObjectPool();
|
||||
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "MAIN")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
|
||||
};
|
||||
|
||||
public static PopUp PopUp = new PopUp
|
||||
{
|
||||
Title = "",
|
||||
Prompt = "",
|
||||
Subtitle = "",
|
||||
Error = "",
|
||||
ShowBackground = true,
|
||||
Visible = false
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
ServerIpItem.Activated += ServerIpActivated;
|
||||
_serverConnectItem.Activated += (sender, item) =>
|
||||
{
|
||||
Networking.ToggleConnection(Settings.LastServerAddress);
|
||||
};
|
||||
|
||||
|
||||
Menu.AddSubMenu(ServersMenu.Menu);
|
||||
|
||||
Menu.Add(_usernameItem);
|
||||
Menu.Add(_passwordItem);
|
||||
Menu.Add(ServerIpItem);
|
||||
Menu.Add(_serverConnectItem);
|
||||
|
||||
Menu.AddSubMenu(SettingsMenu.Menu);
|
||||
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)
|
||||
{
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Game.IsControlJustPressed(Control.FrontendCancel))
|
||||
{
|
||||
PopUp.Visible = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
Script.Yield();
|
||||
Game.DisableAllControlsThisFrame();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UsernameActivated(object a, EventArgs b)
|
||||
{
|
||||
var newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
|
||||
if (!string.IsNullOrWhiteSpace(newUsername))
|
||||
{
|
||||
Settings.Username = newUsername;
|
||||
Util.SaveSettings();
|
||||
|
||||
_usernameItem.AltTitle = newUsername;
|
||||
}
|
||||
}
|
||||
|
||||
private static void _passwordActivated(object sender, EventArgs e)
|
||||
{
|
||||
var newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
|
||||
Settings.Password = newPass;
|
||||
Util.SaveSettings();
|
||||
_passwordItem.AltTitle = new string('*', newPass.Length);
|
||||
}
|
||||
|
||||
public static void ServerIpActivated(object a, EventArgs b)
|
||||
{
|
||||
var newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
|
||||
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
|
||||
{
|
||||
Settings.LastServerAddress = newServerIp;
|
||||
Util.SaveSettings();
|
||||
|
||||
ServerIpItem.AltTitle = newServerIp;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitiateConnectionMenuSetting()
|
||||
{
|
||||
_usernameItem.Enabled = false;
|
||||
ServerIpItem.Enabled = false;
|
||||
_serverConnectItem.Enabled = false;
|
||||
}
|
||||
|
||||
public static void ConnectedMenuSetting()
|
||||
{
|
||||
_serverConnectItem.Enabled = true;
|
||||
_serverConnectItem.Title = "Disconnect";
|
||||
Menu.Visible = false;
|
||||
}
|
||||
|
||||
public static void DisconnectedMenuSetting()
|
||||
{
|
||||
_usernameItem.Enabled = true;
|
||||
ServerIpItem.Enabled = true;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using RageCoop.Core;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class Security
|
||||
{
|
||||
|
||||
public Security()
|
||||
{
|
||||
ClientAes.GenerateKey();
|
||||
ClientAes.GenerateIV();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public byte[] Encrypt(byte[] data)
|
||||
{
|
||||
return new CryptoStream(new MemoryStream(data), ClientAes.CreateEncryptor(), CryptoStreamMode.Read)
|
||||
.ReadToEnd();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user