Compare commits
407 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e88e903096 | |||
| 99642fd40c | |||
| 2fbf06b504 | |||
| 13b771ec9f | |||
| 3b987f59e0 | |||
| de96f29097 | |||
| 6136cbfc14 | |||
| ed145aedd6 | |||
| 3f3b5fd2d0 | |||
| 9173e9a99e | |||
| 6e64c458df | |||
| 76f959abe9 | |||
| f2e85d66ab | |||
| e30ef1f4bd | |||
| 6e8f6e78f6 | |||
| f28c83ccbd | |||
| a83821b3d2 | |||
| 3b5436064e | |||
| c4b321324e | |||
| ba8d525ddf | |||
| 884e2f39f0 | |||
| 76c529f1d1 | |||
| df0064bd38 | |||
| f1fc96bbd7 | |||
| 23e9326f5f | |||
| 4621fb4987 | |||
| 6c82895fa7 | |||
| 84b040766f | |||
| f44558cd3b | |||
| accdbbcbc6 | |||
| 2d4107f35e | |||
| bac53fd769 | |||
| 8f63fee5b5 | |||
| 8d0ad0b600 | |||
| dc08c0c1f6 | |||
| faaa856aa5 | |||
| eab64f9254 | |||
| 6c936cb8f9 | |||
| 83a37f8556 | |||
| bb4eacce26 | |||
| d5b71db5d4 | |||
| 89ebd0305c | |||
| b209202292 | |||
| f199241ed8 | |||
| 50229116a7 | |||
| f192caeae1 | |||
| 6005b2ba11 | |||
| 6e213611cc | |||
| 347d65af62 | |||
| 636ee3a33f | |||
| 8ff08e0804 | |||
| 6d7fe58719 | |||
| c7e14ef191 | |||
| 39eb5ef80a | |||
| d25c2539e0 | |||
| eb31a33104 | |||
| 8b3f8ffd9b | |||
| cca09c4178 | |||
| 871bb7e64e | |||
| 02da3ce8af | |||
| 711ba62fc2 | |||
| 8f7d3135db | |||
| cb563a1bf8 | |||
| a84ab42a42 | |||
| b3e285f92f | |||
| 01433f0de6 | |||
| c034cd8aa9 | |||
| 363c2ccb00 | |||
| 7380c162be | |||
| f1c7192029 | |||
| 6720636d48 | |||
| 2af9482da9 | |||
| bf22c17bba | |||
| 01492a0c55 | |||
| c17b234057 | |||
| 739577bbf1 | |||
| 2d2da624e4 | |||
| 9e66762061 | |||
| 84d578366a | |||
| 03bc3a7742 | |||
| 5bfc7f836e | |||
| 54977c79f1 | |||
| 0352bfa328 | |||
| 6fbc386b45 | |||
| 203e3b2528 | |||
| c9aa995224 | |||
| c201b94897 | |||
| 87f873d1a5 | |||
| 6c355b109d | |||
| 2ef3012672 | |||
| 7229574ff4 | |||
| eff9fd50b1 | |||
| e2cbf26360 | |||
| b0f448fd5f | |||
| 721c4d3dee | |||
| 87dfc18d91 | |||
| fa4fe2c10f | |||
| 3a088ddbfc | |||
| 52cc9b647c | |||
| 1aa23901d5 | |||
| 52584d5774 | |||
| 62bf15dede | |||
| 722ca16f57 | |||
| 3e61498366 | |||
| 1ff2fa5691 | |||
| 5088973474 | |||
| b63378f4d0 | |||
| f234830c54 | |||
| 5075289657 | |||
| c20118aac1 | |||
| c4312faf4a | |||
| 7566423889 | |||
| be13e0d102 | |||
| 490802862a | |||
| e369a50b89 | |||
| 13a6431248 | |||
| cb73db7146 | |||
| 9dcd6bb363 | |||
| 842e1435f2 | |||
| 4d03f8e371 | |||
| e040d53cc2 | |||
| 22bfa43ec5 | |||
| 7123bea8e5 | |||
| 973f6873e4 | |||
| efb759b9f9 | |||
| ad3ff96f61 | |||
| e156b2195f | |||
| 73a99e033d | |||
| b44d722b5f | |||
| 307b6365f7 | |||
| a49ee19d3d | |||
| 9c5c1da4c9 | |||
| 0348296dc1 | |||
| adb9439226 | |||
| ae10da874b | |||
| cb91486848 | |||
| 3289c03fd2 | |||
| d4ffb0c929 | |||
| 3c089e2c47 | |||
| 7a4e4182b5 | |||
| aa6c45b20d | |||
| 0943a7992f | |||
| d30e1f6bb3 | |||
| 0c71d4dbaa | |||
| c78fbd89bc | |||
| dc83cd5be9 | |||
| b5f6fc578d | |||
| 4acf964f0a | |||
| 560f092304 | |||
| 8305d9997a | |||
| d68941b3ac | |||
| 67c4646198 | |||
| a7dab1e0e1 | |||
| 2b8a3db6bf | |||
| f9609afd8a | |||
| 609fd90561 | |||
| a7d64d241d | |||
| b436abf131 | |||
| c14221f1a3 | |||
| abbc871d1a | |||
| edb8838455 | |||
| 4cca75ee35 | |||
| 669b457275 | |||
| 2ef0060037 | |||
| 55649f26ae | |||
| 6bb7d5d0fd | |||
| 8f5deaa33b | |||
| 91b0afb6ef | |||
| 4dc3cc5c28 | |||
| f9a411833a | |||
| d24d786fd3 | |||
| edae7a075a | |||
| 85b028e3e7 | |||
| 36de46a2ce | |||
| 6e5f72ee70 | |||
| 8d450813e1 | |||
| 495e5dc6b0 | |||
| 0bd1d2c2a7 | |||
| 339f817c7e | |||
| b583618e6c | |||
| dc0fafbe9e | |||
| b9e22a880f | |||
| cb7f0290ec | |||
| d70c739208 | |||
| 29202906e1 | |||
| 1d3e53f734 | |||
| a26a799a9f | |||
| 033d0a3f2e | |||
| 95e302a698 | |||
| ff9e16bd5e | |||
| 01c668b159 | |||
| 74e51c8070 | |||
| 01524b1796 | |||
| 83d79b0a17 | |||
| c3d7c2b335 | |||
| 6b6125eb09 | |||
| 09bb121ffe | |||
| a50be5b869 | |||
| ca5ef8cf4c | |||
| 4e33956acd | |||
| 9aae315e11 | |||
| 742e7ea998 | |||
| 69bd4c837c | |||
| 8c10d24842 | |||
| 6b681afdee | |||
| 6186497999 | |||
| 2571da594e | |||
| 7c52cad569 | |||
| 303bc940f2 | |||
| eda903abde | |||
| 5d5bc668eb | |||
| 6e5653a168 | |||
| 87343b8255 | |||
| cb21291983 | |||
| 8dd2241590 | |||
| 858fa81ea2 | |||
| 8d6c70b75e | |||
| ffd6ce23c3 | |||
| 5a882d0bc8 | |||
| cb1b4f4970 | |||
| 0de040548d | |||
| 40f4cf88c9 | |||
| 275b39fb1e | |||
| fe5da3cf90 | |||
| 4ed4e37604 | |||
| 10552d119f | |||
| d4e0e6f0ab | |||
| e2fce3cf9b | |||
| ad5ffc22ac | |||
| f980790bd7 | |||
| df8519961a | |||
| 171a6be736 | |||
| 91035d9a3f | |||
| 60e7d91409 | |||
| d05d1dac85 | |||
| 8184b4d155 | |||
| a0d5f9d2df | |||
| 49f7f9e3e1 | |||
| 911d8d5d64 | |||
| 3015ffe3ba | |||
| eb74bcf987 | |||
| b63898d8b9 | |||
| 459cfccff5 | |||
| fdb66cba6d | |||
| 93d705e402 | |||
| c8bbdc69d0 | |||
| 9b1587f334 | |||
| 3e67cc519e | |||
| eee6f614f1 | |||
| 62e5bad6ac | |||
| 163f342d7a | |||
| 370247eef2 | |||
| f22ecb03f2 | |||
| 577857ab5a | |||
| 095d16a57e | |||
| 862c2c0a71 | |||
| d7e9ce8ff1 | |||
| e657a02f7a | |||
| 182ff877f0 | |||
| 521358abb5 | |||
| b62aed5c5a | |||
| 5c4e31806d | |||
| 9f8d695d45 | |||
| e0e5f52d36 | |||
| 9fda563438 | |||
| 6d5bb285c9 | |||
| 7ac405b8f9 | |||
| 45d53bd38c | |||
| cc52f66f5d | |||
| e072c2aee8 | |||
| e52135c343 | |||
| cb53e8a664 | |||
| 44a8445b54 | |||
| b0a197cd18 | |||
| 9ba52b332c | |||
| ecfc77e57a | |||
| 2ba3e33d39 | |||
| 71ecdbd5b9 | |||
| bbe0b21aa4 | |||
| 51dd445a31 | |||
| 264158957c | |||
| 9d8963222c | |||
| c1a35dbab3 | |||
| d3931cc57c | |||
| 3795ced530 | |||
| 1372a5c33f | |||
| 9527da9785 | |||
| 3072db0e21 | |||
| 444e6d7ffa | |||
| 809c935d37 | |||
| 10bd54a785 | |||
| ece899f2a1 | |||
| 5d1a182e47 | |||
| 228ab9e727 | |||
| 2605aafc4f | |||
| 582bb2a7d7 | |||
| 0a3dbb1f59 | |||
| 5d1832fcef | |||
| 30ed509c55 | |||
| 2ad91011c3 | |||
| e40d4695ea | |||
| e4c03f8ccc | |||
| e10a0f5415 | |||
| 2a29011015 | |||
| a37e8869ac | |||
| 038860f158 | |||
| 51e678d760 | |||
| 1e2a9992d8 | |||
| 5b7bb917ef | |||
| 72aff78718 | |||
| cdeef7279c | |||
| 9cb5c557c5 | |||
| 1e1259d0a5 | |||
| 20b326d396 | |||
| b7cad3b8f7 | |||
| cf8b54a3b5 | |||
| edca1e2b98 | |||
| 89410b8b46 | |||
| a9397690e2 | |||
| ea678ece94 | |||
| f28f23f560 | |||
| b2911017d0 | |||
| cda4f702a1 | |||
| b6a0ae7f4a | |||
| b6f3508680 | |||
| ad0368b4c0 | |||
| b1d67bd1c4 | |||
| cda3d37f37 | |||
| abed0f146f | |||
| bdeaeee293 | |||
| b37ce4d0c1 | |||
| e3ff62bdc7 | |||
| eab6c79e15 | |||
| 260678c1bc | |||
| b2d300784f | |||
| 8d1d4f5eb7 | |||
| 25a03c7e27 | |||
| 27ab63ae7a | |||
| 9567e97b31 | |||
| b10a8b47d5 | |||
| 1bc274e2d0 | |||
| dca3420071 | |||
| bc3fc70a95 | |||
| eee983dfd2 | |||
| fbec69cd71 | |||
| 615c49e395 | |||
| 462d68df6b | |||
| 0224abc9bd | |||
| 16fe2fe1ab | |||
| 61c4486551 | |||
| 17ef088492 | |||
| 1111879c4d | |||
| e9d0b73296 | |||
| 8eadbf6fee | |||
| 8ae491d0d5 | |||
| 3dd125f861 | |||
| 01b4753dc6 | |||
| 5a5d78ac27 | |||
| a05eedd68e | |||
| 05304f9461 | |||
| fff5ec4534 | |||
| cf7641cee0 | |||
| a77e0c1fc9 | |||
| f374de2064 | |||
| 8e0620bedb | |||
| 8bb39b1bfa | |||
| 2450956fda | |||
| 56cd17401b | |||
| a53b514fec | |||
| de93af73f2 | |||
| 44f106fd0c | |||
| 8ac5cc40c1 | |||
| 8d7eebd390 | |||
| 0a5bb67c54 | |||
| f866151cc5 | |||
| 08e17b1714 | |||
| fa96f4c073 | |||
| dc5cf2b965 | |||
| f47f0570f9 | |||
| e0c96cc200 | |||
| 17e1ae9ea9 | |||
| 8401d616e0 | |||
| d8ac486984 | |||
| 335ea2ca38 | |||
| c73ff96690 | |||
| 15b23e3a4e | |||
| 47f86c098d | |||
| 6c2c7e881f | |||
| 74a2265200 | |||
| 0b2ec1d626 | |||
| 8ee188cf19 | |||
| eb5b23ae17 | |||
| 77999fe8f3 | |||
| 64fda51917 | |||
| 4165b757a5 | |||
| d31799ab7b | |||
| 8a46bd6b68 | |||
| 58362c2613 | |||
| 7d6c5fe3bd | |||
| f9b9d78b79 | |||
| 7670af88a2 | |||
| 0800361b40 | |||
| cdf3f41127 | |||
| 552bf8d61c | |||
| 2f58e48f42 | |||
| 67035f5d2f | |||
| b48206d942 |
@@ -0,0 +1,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,48 @@
|
||||
name: Build test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*' # matches every branch that doesn't contain a '/'
|
||||
- '*/*' # matches every branch containing a single '/'
|
||||
- '**' # 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: ['6.0.x']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ matrix.dotnet-version }}
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Restore nuget packages
|
||||
run: nuget restore
|
||||
- name: Build client and installer
|
||||
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
|
||||
- name: Build server win-x64
|
||||
run: dotnet build RageCoop.Server/RageCoop.Server.csproj -o bin/Release/Server
|
||||
- name: Upload server
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: RageCoop.Server
|
||||
path: bin/Release/Server
|
||||
- name: Upload Client
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: RageCoop.Client
|
||||
path: bin/Release/Client
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -2,9 +2,7 @@ name: Nightly-build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: [ "dev-nightly" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -22,29 +20,79 @@ jobs:
|
||||
dotnet-version: ${{ matrix.dotnet-version }}
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build client
|
||||
run: dotnet build RageCoop.Client/RageCoop.Client.csproj --no-restore --configuration Release -o RageCoop.Client/bin/RageCoop
|
||||
- name: Restore nuget packages
|
||||
run: nuget restore
|
||||
- name: Build client and installer
|
||||
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
|
||||
- name: Build server win-x64
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r win-x64 -o RageCoop.Server/bin/win-x64 -c Release
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o bin/Release/Server/win-x64 -c Release
|
||||
- name: Build server linux-x64
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-x64 -o RageCoop.Server/bin/linux-x64 -c Release
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o bin/Release/Server/linux-x64 -c Release
|
||||
- name: Build server linux-arm
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-arm -o RageCoop.Server/bin/linux-arm -c Release
|
||||
- uses: actions/upload-artifact@v3
|
||||
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o bin/Release/Server/linux-arm -c Release
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Client
|
||||
path: RageCoop.Client/bin
|
||||
- uses: actions/upload-artifact@v3
|
||||
files: bin/Release/Client
|
||||
dest: RageCoop.Client.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Server-win-x64
|
||||
path: RageCoop.Server/bin/win-x64
|
||||
- uses: actions/upload-artifact@v3
|
||||
files: bin/Release/Server/win-x64
|
||||
dest: RageCoop.Server-win-x64.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Server-linux-x64
|
||||
path: RageCoop.Server/bin/linux-x64
|
||||
- uses: actions/upload-artifact@v3
|
||||
files: bin/Release/Server/linux-x64
|
||||
dest: RageCoop.Server-linux-x64.zip
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
name: RageCoop.Server-linux-arm
|
||||
path: RageCoop.Server/bin/linux-arm
|
||||
files: bin/Release/Server/linux-arm
|
||||
dest: RageCoop.Server-linux-arm.zip
|
||||
|
||||
- uses: WebFreak001/deploy-nightly@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
|
||||
release_id: 70603992
|
||||
asset_path: RageCoop.Client.zip
|
||||
asset_name: RageCoop.Client.zip
|
||||
asset_content_type: application/zip
|
||||
max_releases: 7
|
||||
|
||||
- uses: WebFreak001/deploy-nightly@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
|
||||
release_id: 70603992
|
||||
asset_path: RageCoop.Server-win-x64.zip
|
||||
asset_name: RageCoop.Server-win-x64.zip
|
||||
asset_content_type: application/zip
|
||||
max_releases: 7
|
||||
|
||||
- uses: WebFreak001/deploy-nightly@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
|
||||
release_id: 70603992
|
||||
asset_path: RageCoop.Server-linux-x64.zip
|
||||
asset_name: RageCoop.Server-linux-x64.zip
|
||||
asset_content_type: application/zip
|
||||
max_releases: 7
|
||||
|
||||
|
||||
- uses: WebFreak001/deploy-nightly@v1.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
|
||||
release_id: 70603992
|
||||
asset_path: RageCoop.Server-linux-arm.zip
|
||||
asset_name: RageCoop.Server-linux-arm.zip
|
||||
asset_content_type: application/zip
|
||||
max_releases: 7
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
**/bin
|
||||
**/obj
|
||||
**/packages
|
||||
**/.vs
|
||||
**/.vs
|
||||
**/.vscode
|
||||
@@ -1,76 +1,76 @@
|
||||
|
||||
|
||||
# 🌐 RAGECOOP
|
||||
|
||||
[![Downloads][downloads-shield]][downloads-url]
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![Stargazers][stars-shield]][stars-url]
|
||||
[![Issues][issues-shield]][issues-url]
|
||||
|
||||
|
||||
# ⚠ Notice
|
||||
The original author of this project is [EntenKoeniq](https://github.com/EntenKoeniq).
|
||||
The project has been reworked and is currently maintained by [Sardelka9515](https://github.com/Sardelka9515).
|
||||
To download the legacy versions, go to [this repository](https://github.com/RAGECOOP/RAGECOOP-V.OLD)
|
||||
|
||||
# 🧠 That's it
|
||||
RAGECOOP is a multiplayer mod to play story mode or some mods made for RAGECOOP or just drive around with your buddy.
|
||||
|
||||
_Old name: GTACOOP:R_
|
||||
RAGECOOP brings multiplayer experience to the story mode, you can complete missions together with your friends, use mods without any restriction/getting banned, or just mess around with your fella!
|
||||
|
||||
# 📋 Requirements
|
||||
- Visual Studio 2022
|
||||
- .NET 6.0
|
||||
- .NET Framework 4.8
|
||||
# 👁 Requirements
|
||||
- ScriptHookV
|
||||
- ScriptHookVDotNet 3.5.1 or later
|
||||
- .NET Framework 4.8 Runtime or SDK
|
||||
|
||||
# 📚 Libraries
|
||||
- [ScriptHookVDotNet3](https://github.com/crosire/scripthookvdotnet/releases/tag/v3.4.0)
|
||||
- [LemonUI.SHVDN3](https://github.com/justalemon/LemonUI/releases/tag/v1.6)
|
||||
- Lidgren Network Custom (***PRIVATE***)
|
||||
# 📋 Building the project
|
||||
|
||||
You'll need:
|
||||
- .NET 6.0 SDK
|
||||
- .NET Framework 4.8 SDK
|
||||
|
||||
Recommended IDE:
|
||||
- Visual Studio Code
|
||||
- Visul Studio 2022
|
||||
|
||||
Then run `dotnet build` in the solution directory, built binaries are in the `bin` folder
|
||||
|
||||
# 📚 Third-party libraries
|
||||
- [ScriptHookVDotNet3](https://github.com/crosire/scripthookvdotnet)
|
||||
- [LemonUI.SHVDN3](https://github.com/justalemon/LemonUI)
|
||||
- Lidgren Network Custom
|
||||
- - No new features (only improvements)
|
||||
- [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/13.0.1)
|
||||
- [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
|
||||
- [ClearScript](https://github.com/microsoft/ClearScript)
|
||||
- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
|
||||
- [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins)
|
||||
|
||||
# Features
|
||||
# 👋 Features
|
||||
|
||||
1. Synchronized bullets
|
||||
2. Synchronized vehicle/player/NPC
|
||||
3. Synchronized projectiles
|
||||
4. Simple ragdoll sync
|
||||
5. Smoother vehicle/ped movement.
|
||||
6. Ownership based sync logic, carjacking is now working (sort of).
|
||||
7. Introduced SyncEvents.
|
||||
8. Code refactoring and namespace cleanup
|
||||
9. Synchronized vehicle doors, brake and throttle.
|
||||
10. Weaponized vehicle sync(WIP).
|
||||
11. Other improvements
|
||||
5. Decent compatibility with other mods, set up a private modded server to have some fun!
|
||||
6. Weaponized vehicle sync(WIP).
|
||||
7. Optimization for high-Ping condition, play with friends around the world!
|
||||
8. Powerful scripting API and resource system, easily [add multiplayer functionality to your mod](HTTPS://docs.ragecoop.online).
|
||||
|
||||
# Known issues
|
||||
# ⚠ Known issues
|
||||
|
||||
1. Weapon sounds are missing.
|
||||
2. Cover sync is still buggy.
|
||||
3. Framerate drop with high number of synchronized entities.
|
||||
5. Scripting API is screwed.(will be rewritten in the future)
|
||||
See [Bugs](https://github.com/RAGECOOP/RAGECOOP-V/issues/33)
|
||||
|
||||
|
||||
## Installation
|
||||
# 🔫 Installation
|
||||
Refer to the [wiki](https://github.com/RAGECOOP/RAGECOOP-V/wiki)
|
||||
|
||||
# Downloads
|
||||
# 🧨 Downloads
|
||||
|
||||
Download latest release [here](https://github.com/RAGECOOP/RAGECOOP-V/releases/latest)
|
||||
|
||||
You can also download nightly builds [here](https://github.com/RAGECOOP/RAGECOOP-V/actions), which includes latest features and bug-fixes, but has not been thoroughly tested.
|
||||
Select latest workflow run, scroll down to bottom then download the artifacts.
|
||||
You can also download nightly builds [here](https://github.com/RAGECOOP/RAGECOOP-V/releases/nightly), which includes latest features and bug-fixes, but has not been thoroughly tested.
|
||||
|
||||
Please note that this is incompatible with all previous versions of ragecoop, remove old files before installing.
|
||||
|
||||
|
||||
# Support us
|
||||
|
||||
<a href="https://patreon.com/Sardelka"><img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3DSardelka%26type%3Dpatrons&style=for-the-badge" /></a>
|
||||
|
||||
# 🦆 Special thanks to
|
||||
|
||||
- [Makinolo](https://github.com/Makinolo), [oldnapalm](https://github.com/oldnapalm)
|
||||
- - For testing, ideas, contributions and the first modification with the API
|
||||
- [crosire](https://github.com/crosire)
|
||||
@@ -79,8 +79,11 @@ Please note that this is incompatible with all previous versions of ragecoop, re
|
||||
- - For the extensive work in LemonUI
|
||||
|
||||
# 📝 License
|
||||
|
||||
This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOOP-V/blob/main/LICENSE)
|
||||
|
||||
[downloads-shield]: https://img.shields.io/github/downloads/RAGECOOP/RAGECOOP-V/total?style=for-the-badge
|
||||
[downloads-url]: https://github.com/RAGECOOP/RAGECOOP-V/releases
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
|
||||
[contributors-url]: https://github.com/RAGECOOP/RAGECOOP-V/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
|
||||
@@ -90,3 +93,4 @@ This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOO
|
||||
[issues-shield]: https://img.shields.io/github/issues/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
|
||||
[issues-url]: https://github.com/RAGECOOP/RAGECOOP-V/issues
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "RageCoop.C
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "RageCoop.Client\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Installer", "RageCoop.Client.Installer\RageCoop.Client.Installer.csproj", "{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -41,6 +43,14 @@ Global
|
||||
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.Build.0 = Release|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -0,0 +1,9 @@
|
||||
<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"
|
||||
xmlns:local="clr-namespace:RageCoop.Client.Installer"
|
||||
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,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"
|
||||
xmlns:local="clr-namespace:RageCoop.Client.Installer"
|
||||
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,213 @@
|
||||
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.Forms;
|
||||
using System.Windows.Input;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||
using Path = System.IO.Path;
|
||||
using RageCoop.Core;
|
||||
|
||||
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() ?? false == true)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Install(Directory.GetParent(od.FileName).FullName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Installation failed: " + ex.ToString());
|
||||
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, "ScriptHookVDotNet3.dll");
|
||||
var scriptsPath = Path.Combine(root, "Scripts");
|
||||
var lemonPath = Path.Combine(scriptsPath, "LemonUI.SHVDN3.dll");
|
||||
var installPath = Path.Combine(scriptsPath, "RageCoop");
|
||||
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName == 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.");
|
||||
}
|
||||
Directory.CreateDirectory(installPath);
|
||||
if (!File.Exists(shvPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookV first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
if (!File.Exists(shvdnPath))
|
||||
{
|
||||
MessageBox.Show("Please install ScriptHookVDotNet first!");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
var shvdnVer = GetVer(shvdnPath);
|
||||
if (shvdnVer < new Version(3, 6, 0))
|
||||
{
|
||||
MessageBox.Show("Please update ScriptHookVDotNet to latest version!" +
|
||||
$"\nCurrent version is {shvdnVer}, 3.6.0 or higher is required");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
if (File.Exists(lemonPath))
|
||||
{
|
||||
var lemonVer = GetVer(lemonPath);
|
||||
if (lemonVer < new Version(1, 7))
|
||||
{
|
||||
UpdateStatus("Updating LemonUI");
|
||||
File.WriteAllBytes(lemonPath, getLemon());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
UpdateStatus("Removing old versions");
|
||||
|
||||
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (f.EndsWith("RageCoop.Client.Settings.xml")) { continue; }
|
||||
File.Delete(f);
|
||||
}
|
||||
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
File.Delete(f);
|
||||
}
|
||||
|
||||
if (File.Exists("RageCoop.Core.dll") && File.Exists("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 zip from official website");
|
||||
}
|
||||
|
||||
void Finish()
|
||||
{
|
||||
|
||||
checkKeys:
|
||||
UpdateStatus("Checking conflicts");
|
||||
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
|
||||
var settingsPath = Path.Combine(installPath, @"Data\RageCoop.Client.Settings.xml");
|
||||
Settings settings = null;
|
||||
try
|
||||
{
|
||||
settings = Util.ReadSettings(settingsPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
settings = new Settings();
|
||||
}
|
||||
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 = (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 officail website");
|
||||
Process.Start(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatus("Completed!");
|
||||
MessageBox.Show("Installation sucessful!");
|
||||
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);
|
||||
}
|
||||
|
||||
private byte[] getLemon()
|
||||
{
|
||||
return (byte[])Resource.ResourceManager.GetObject("LemonUI_SHVDN3");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<Project 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>
|
||||
<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>
|
||||
|
||||
</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>
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
internal static byte[] LemonUI_SHVDN3 {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("LemonUI_SHVDN3", resourceCulture);
|
||||
return ((byte[])(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?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" />
|
||||
<data name="LemonUI_SHVDN3" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>Resources\LemonUI.SHVDN3.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
@@ -21,7 +18,7 @@ namespace RageCoop.Client
|
||||
CheckProjectiles,
|
||||
GetAllEntities,
|
||||
Receive,
|
||||
|
||||
ProjectilesTotal,
|
||||
}
|
||||
internal static class Debug
|
||||
{
|
||||
@@ -29,7 +26,7 @@ namespace RageCoop.Client
|
||||
private static int _lastNfHandle;
|
||||
static Debug()
|
||||
{
|
||||
foreach(TimeStamp t in Enum.GetValues(typeof(TimeStamp)))
|
||||
foreach (TimeStamp t in Enum.GetValues(typeof(TimeStamp)))
|
||||
{
|
||||
TimeStamps.Add(t, 0);
|
||||
}
|
||||
@@ -37,16 +34,16 @@ namespace RageCoop.Client
|
||||
public static string Dump(this Dictionary<TimeStamp, long> d)
|
||||
{
|
||||
string s = "";
|
||||
foreach(KeyValuePair<TimeStamp, long> kvp in d)
|
||||
foreach (KeyValuePair<TimeStamp, long> kvp in d)
|
||||
{
|
||||
s+=kvp.Key+":"+kvp.Value+"\n";
|
||||
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());
|
||||
_lastNfHandle = GTA.UI.Notification.Show(Debug.TimeStamps.Dump());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal class DevTool:Script
|
||||
internal class DevTool : Script
|
||||
{
|
||||
public static Vehicle ToMark;
|
||||
public static bool UseSecondary=false;
|
||||
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;
|
||||
Tick += OnTick;
|
||||
KeyDown += OnKeyDown;
|
||||
}
|
||||
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (ToMark==null||(!ToMark.Exists())) { return; }
|
||||
if (DevToolMenu.Menu.SelectedItem==DevToolMenu.boneIndexItem) {
|
||||
if (ToMark == null || (!ToMark.Exists())) { return; }
|
||||
if (DevToolMenu.Menu.SelectedItem == DevToolMenu.boneIndexItem)
|
||||
{
|
||||
|
||||
switch (e.KeyCode)
|
||||
{
|
||||
@@ -39,7 +36,7 @@ namespace RageCoop.Client
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (DevToolMenu.Menu.SelectedItem==DevToolMenu.secondaryBoneIndexItem)
|
||||
else if (DevToolMenu.Menu.SelectedItem == DevToolMenu.secondaryBoneIndexItem)
|
||||
{
|
||||
|
||||
switch (e.KeyCode)
|
||||
@@ -57,28 +54,28 @@ namespace RageCoop.Client
|
||||
private static void Update()
|
||||
{
|
||||
|
||||
if (Current>ToMark.Bones.Count-1)
|
||||
if (Current > ToMark.Bones.Count - 1)
|
||||
{
|
||||
Current=0;
|
||||
Current = 0;
|
||||
}
|
||||
else if (Current< 0)
|
||||
else if (Current < 0)
|
||||
{
|
||||
Current=ToMark.Bones.Count-1;
|
||||
Current = ToMark.Bones.Count - 1;
|
||||
}
|
||||
DevToolMenu.boneIndexItem.AltTitle= Current.ToString();
|
||||
if (Secondary>ToMark.Bones.Count-1)
|
||||
DevToolMenu.boneIndexItem.AltTitle = Current.ToString();
|
||||
if (Secondary > ToMark.Bones.Count - 1)
|
||||
{
|
||||
Secondary=0;
|
||||
Secondary = 0;
|
||||
}
|
||||
else if (Secondary< 0)
|
||||
else if (Secondary < 0)
|
||||
{
|
||||
Secondary=ToMark.Bones.Count-1;
|
||||
Secondary = ToMark.Bones.Count - 1;
|
||||
}
|
||||
DevToolMenu.secondaryBoneIndexItem.AltTitle= Secondary.ToString();
|
||||
DevToolMenu.secondaryBoneIndexItem.AltTitle = Secondary.ToString();
|
||||
}
|
||||
private static void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
if(ToMark == null || !ToMark.Exists()){ return;}
|
||||
if (ToMark == null || !ToMark.Exists()) { return; }
|
||||
Update();
|
||||
Draw(Current);
|
||||
if (UseSecondary)
|
||||
@@ -90,95 +87,93 @@ namespace RageCoop.Client
|
||||
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);
|
||||
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;
|
||||
todraw = bone.ForwardVector;
|
||||
break;
|
||||
case 1:
|
||||
todraw=bone.RightVector;
|
||||
todraw = bone.RightVector;
|
||||
break;
|
||||
case 2:
|
||||
todraw=bone.UpVector;
|
||||
todraw = bone.UpVector;
|
||||
break;
|
||||
case 3:
|
||||
todraw=bone.ForwardVector*-1;
|
||||
todraw = bone.ForwardVector * -1;
|
||||
break;
|
||||
case 4:
|
||||
todraw=bone.RightVector*-1;
|
||||
todraw = bone.RightVector * -1;
|
||||
break;
|
||||
case 5:
|
||||
todraw=bone.UpVector*-1;
|
||||
todraw = bone.UpVector * -1;
|
||||
break;
|
||||
}
|
||||
World.DrawLine(bone.Position, bone.Position+10*todraw, Color.Red);
|
||||
World.DrawLine(bone.Position, bone.Position + 10 * todraw, Color.Red);
|
||||
}
|
||||
public static void CopyToClipboard(MuzzleDir dir)
|
||||
{
|
||||
|
||||
if (ToMark!=null)
|
||||
if (ToMark != null)
|
||||
{
|
||||
string s;
|
||||
if (UseSecondary)
|
||||
{
|
||||
if ((byte)dir<3)
|
||||
if ((byte)dir < 3)
|
||||
{
|
||||
s=$@"
|
||||
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);
|
||||
return BulletsShot%2==0 ? {Current} : {Secondary};
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
s=$@"
|
||||
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);
|
||||
return BulletsShot%2==0 ? {Current} : {Secondary};
|
||||
";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((byte)dir<3)
|
||||
if ((byte)dir < 3)
|
||||
{
|
||||
s=$@"
|
||||
s = $@"
|
||||
// {ToMark.DisplayName}
|
||||
case {ToMark.Model.Hash}:
|
||||
return new MuzzleInfo(v.Bones[{Current}].Position, v.Bones[{Current}].{dir}Vector);
|
||||
return {Current};
|
||||
";
|
||||
}
|
||||
else
|
||||
{
|
||||
s=$@"
|
||||
s = $@"
|
||||
// {ToMark.DisplayName}
|
||||
case {ToMark.Model.Hash}:
|
||||
return new MuzzleInfo(v.Bones[{Current}].Position, v.Bones[{Current}].{((MuzzleDir)(dir-3)).ToString()}Vector*-1);
|
||||
return {Current};
|
||||
";
|
||||
}
|
||||
}
|
||||
Thread thread = new Thread(() => Clipboard.SetText(s));
|
||||
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
GTA.UI.Notification.Show("Copied to clipboard, please paste it on the GitHub issue page!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public enum MuzzleDir:byte
|
||||
internal enum MuzzleDir : byte
|
||||
{
|
||||
Forward=0,
|
||||
Forward = 0,
|
||||
Right = 1,
|
||||
Up=2,
|
||||
Backward=3,
|
||||
Up = 2,
|
||||
Backward = 3,
|
||||
Left = 4,
|
||||
Down=5,
|
||||
Down = 5,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura ExcludeAssemblies="RageCoop.Core"/>
|
||||
</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>
|
||||
@@ -1,41 +0,0 @@
|
||||
// NO MERGE
|
||||
|
||||
// Taken from the .NET Runtime Repository
|
||||
// https://github.com/dotnet/runtime
|
||||
// Copyright (c) .NET Foundation and Contributors
|
||||
// Under the MIT License
|
||||
#if FIVEM
|
||||
using System;
|
||||
|
||||
namespace LemonUI // Previously System.ComponentModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the event raised when canceling an event.
|
||||
/// </summary>
|
||||
public delegate void CancelEventHandler(object sender, CancelEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs used to describe a cancel event.
|
||||
/// </summary>
|
||||
public class CancelEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether we should cancel the operation or not
|
||||
/// </summary>
|
||||
public bool Cancel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public CancelEventArgs()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper constructor
|
||||
/// </summary>
|
||||
/// <param name="cancel"></param>
|
||||
public CancelEventArgs(bool cancel) => Cancel = cancel;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,138 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage.Native;
|
||||
using Control = Rage.GameControl;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Tools for dealing with controls.
|
||||
/// </summary>
|
||||
internal static class Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets if the player used a controller for the last input.
|
||||
/// </summary>
|
||||
public static bool IsUsingController
|
||||
{
|
||||
get
|
||||
{
|
||||
#if FIVEM
|
||||
return !API.IsInputDisabled(2);
|
||||
#elif RAGEMP
|
||||
return !Invoker.Invoke<bool>(Natives.IsInputDisabled, 2);
|
||||
#elif RPH
|
||||
return !NativeFunction.CallByHash<bool>(0xA571D46727E2B718, 2);
|
||||
#elif SHVDN3
|
||||
return !Function.Call<bool>(Hash._IS_USING_KEYBOARD, 2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a control was pressed during the last frame.
|
||||
/// </summary>
|
||||
/// <param name="control">The control to check.</param>
|
||||
/// <returns>true if the control was pressed, false otherwise.</returns>
|
||||
public static bool IsJustPressed(Control control)
|
||||
{
|
||||
#if FIVEM
|
||||
return API.IsDisabledControlJustPressed(0, (int)control);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<bool>(Natives.IsDisabledControlJustPressed, 0, (int)control);
|
||||
#elif RPH
|
||||
return NativeFunction.CallByHash<bool>(0x91AEF906BCA88877, 0, (int)control);
|
||||
#elif SHVDN3
|
||||
return Function.Call<bool>(Hash.IS_DISABLED_CONTROL_JUST_PRESSED, 0, (int)control);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a control is currently pressed.
|
||||
/// </summary>
|
||||
/// <param name="control">The control to check.</param>
|
||||
/// <returns>true if the control is pressed, false otherwise.</returns>
|
||||
public static bool IsPressed(Control control)
|
||||
{
|
||||
#if FIVEM
|
||||
return API.IsDisabledControlPressed(0, (int)control);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<bool>(Natives.IsDisabledControlJustPressed, 0, (int)control);
|
||||
#elif RPH
|
||||
return NativeFunction.CallByHash<bool>(0xE2587F8CBBD87B1D, 0, (int)control);
|
||||
#elif SHVDN3
|
||||
return Function.Call<bool>(Hash.IS_DISABLED_CONTROL_PRESSED, 0, (int)control);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables all of the controls during the next frame.
|
||||
/// </summary>
|
||||
public static void DisableAll(int inputGroup = 0)
|
||||
{
|
||||
#if FIVEM
|
||||
API.DisableAllControlActions(inputGroup);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.DisableAllControlActions, inputGroup);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x5F4B6931816E599B, inputGroup);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, inputGroup);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables a control during the next frame.
|
||||
/// </summary>
|
||||
/// <param name="control">The control to enable.</param>
|
||||
public static void EnableThisFrame(Control control)
|
||||
{
|
||||
#if FIVEM
|
||||
API.EnableControlAction(0, (int)control, true);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.EnableControlAction, 0, (int)control, true);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x351220255D64C155, 0, (int)control);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.ENABLE_CONTROL_ACTION, 0, (int)control);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Enables a specific set of controls during the next frame.
|
||||
/// </summary>
|
||||
/// <param name="controls">The controls to enable.</param>
|
||||
public static void EnableThisFrame(IEnumerable<Control> controls)
|
||||
{
|
||||
foreach (Control control in controls)
|
||||
{
|
||||
EnableThisFrame(control);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables a control during the next frame.
|
||||
/// </summary>
|
||||
/// <param name="control">The control to disable.</param>
|
||||
public static void DisableThisFrame(Control control)
|
||||
{
|
||||
#if FIVEM
|
||||
API.DisableControlAction(0, (int)control, true);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.DisableControlAction, 0, (int)control, true);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xFE99B66D079CF6BC, 0, (int)control, true);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.DISABLE_CONTROL_ACTION, 0, (int)control, true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#if RPH
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// The alignment of the element to draw.
|
||||
/// </summary>
|
||||
public enum Alignment
|
||||
{
|
||||
/// <summary>
|
||||
/// Aligns the element to the Center.
|
||||
/// </summary>
|
||||
Center = 0,
|
||||
/// <summary>
|
||||
/// Aligns the element to the Left.
|
||||
/// </summary>
|
||||
Left = 1,
|
||||
/// <summary>
|
||||
/// Aligns the element to the RIght.
|
||||
/// </summary>
|
||||
Right = 2,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,113 +0,0 @@
|
||||
using LemonUI.Extensions;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all of the 2D elements.
|
||||
/// </summary>
|
||||
public abstract class BaseElement : I2Dimensional
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// The 1080 scaled position.
|
||||
/// </summary>
|
||||
protected internal PointF literalPosition = PointF.Empty;
|
||||
/// <summary>
|
||||
/// The relative position between 0 and 1.
|
||||
/// </summary>
|
||||
protected internal PointF relativePosition = PointF.Empty;
|
||||
/// <summary>
|
||||
/// The 1080 scaled size.
|
||||
/// </summary>
|
||||
protected internal SizeF literalSize = SizeF.Empty;
|
||||
/// <summary>
|
||||
/// The relative size between 0 and 1.
|
||||
/// </summary>
|
||||
protected internal SizeF relativeSize = SizeF.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The Position of the drawable.
|
||||
/// </summary>
|
||||
public PointF Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return literalPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
literalPosition = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Size of the drawable.
|
||||
/// </summary>
|
||||
public SizeF Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return literalSize;
|
||||
}
|
||||
set
|
||||
{
|
||||
literalSize = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Color of the drawable.
|
||||
/// </summary>
|
||||
public Color Color { get; set; } = Color.FromArgb(255, 255, 255, 255);
|
||||
/// <summary>
|
||||
/// The rotation of the drawable.
|
||||
/// </summary>
|
||||
public float Heading { get; set; } = 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BaseElement"/> with the specified Position and Size.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the Element.</param>
|
||||
/// <param name="size">The size of the Element.</param>
|
||||
public BaseElement(PointF pos, SizeF size)
|
||||
{
|
||||
literalPosition = pos;
|
||||
literalSize = size;
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the size and position of this item.
|
||||
/// </summary>
|
||||
public virtual void Recalculate()
|
||||
{
|
||||
relativePosition = literalPosition.ToRelative();
|
||||
relativeSize = literalSize.ToRelative();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Draws the item on the screen.
|
||||
/// </summary>
|
||||
public abstract void Draw();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// NO MERGE
|
||||
|
||||
#if RPH
|
||||
namespace LemonUI.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// An enum representing the fonts available in game.
|
||||
/// </summary>
|
||||
public enum Font
|
||||
{
|
||||
/// <summary>
|
||||
/// Chalet London Nineteen Sixty.
|
||||
/// </summary>
|
||||
ChaletLondon = 0,
|
||||
/// <summary>
|
||||
/// SignPainter HouseScript Regular.
|
||||
/// </summary>
|
||||
HouseScript = 1,
|
||||
/// <summary>
|
||||
/// Unknown Monospaced Font.
|
||||
/// </summary>
|
||||
Monospace = 2,
|
||||
/// <summary>
|
||||
/// Chalet Comprime Cologne.
|
||||
/// </summary>
|
||||
ChaletComprimeCologne = 4,
|
||||
/// <summary>
|
||||
/// Pricedown.
|
||||
/// </summary>
|
||||
Pricedown = 7
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// A 2D item that can be drawn on the screen.
|
||||
/// </summary>
|
||||
public interface I2Dimensional : IRecalculable, IDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The Position of the drawable.
|
||||
/// </summary>
|
||||
PointF Position { get; set; }
|
||||
/// <summary>
|
||||
/// The Size of the drawable.
|
||||
/// </summary>
|
||||
SizeF Size { get; set; }
|
||||
/// <summary>
|
||||
/// The Color of the drawable.
|
||||
/// </summary>
|
||||
Color Color { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#if FIVEM
|
||||
using Alignment = CitizenFX.Core.UI.Alignment;
|
||||
using Font = CitizenFX.Core.UI.Font;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif SHVDN3
|
||||
using Alignment = GTA.UI.Alignment;
|
||||
using Font = GTA.UI.Font;
|
||||
#endif
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// A Drawable screen text.
|
||||
/// </summary>
|
||||
public interface IText : IRecalculable, IDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The position of the text.
|
||||
/// </summary>
|
||||
PointF Position { get; set; }
|
||||
/// <summary>
|
||||
/// The text to draw.
|
||||
/// </summary>
|
||||
string Text { get; set; }
|
||||
/// <summary>
|
||||
/// The color of the text.
|
||||
/// </summary>
|
||||
Color Color { get; set; }
|
||||
/// <summary>
|
||||
/// The game font to use.
|
||||
/// </summary>
|
||||
Font Font { get; set; }
|
||||
/// <summary>
|
||||
/// The scale of the text.
|
||||
/// </summary>
|
||||
float Scale { get; set; }
|
||||
/// <summary>
|
||||
/// If the text should have a drop down shadow.
|
||||
/// </summary>
|
||||
bool Shadow { get; set; }
|
||||
/// <summary>
|
||||
/// If the text should have an outline.
|
||||
/// </summary>
|
||||
bool Outline { get; set; }
|
||||
/// <summary>
|
||||
/// The alignment of the text.
|
||||
/// </summary>
|
||||
Alignment Alignment { get; set; }
|
||||
/// <summary>
|
||||
/// The maximum distance from X where the text would wrap into a new line.
|
||||
/// </summary>
|
||||
float WordWrap { get; set; }
|
||||
/// <summary>
|
||||
/// The width that the text takes from the screen.
|
||||
/// </summary>
|
||||
float Width { get; }
|
||||
/// <summary>
|
||||
/// The number of lines used by this text.
|
||||
/// </summary>
|
||||
int LineCount { get; }
|
||||
/// <summary>
|
||||
/// The height of each line of text.
|
||||
/// </summary>
|
||||
float LineHeight { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage.Native;
|
||||
#elif SHVDN3
|
||||
using GTA.Native;
|
||||
#endif
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// A 2D rectangle.
|
||||
/// </summary>
|
||||
public class ScaledRectangle : BaseElement
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScaledRectangle"/> with the specified Position and Size.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the Rectangle.</param>
|
||||
/// <param name="size">The size of the Rectangle.</param>
|
||||
public ScaledRectangle(PointF pos, SizeF size) : base(pos, size)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Draws the rectangle on the screen.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
if (Size == SizeF.Empty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if FIVEM
|
||||
API.DrawRect(relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.DrawRect, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x3A618A217E5154F0, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.DRAW_RECT, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Recalculates the position based on the size.
|
||||
/// </summary>
|
||||
public override void Recalculate()
|
||||
{
|
||||
base.Recalculate();
|
||||
relativePosition.X += relativeSize.Width * 0.5f;
|
||||
relativePosition.Y += relativeSize.Height * 0.5f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,506 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core.Native;
|
||||
using CitizenFX.Core.UI;
|
||||
using Font = CitizenFX.Core.UI.Font;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage.Native;
|
||||
#elif SHVDN3
|
||||
using GTA.UI;
|
||||
using GTA.Native;
|
||||
using Font = GTA.UI.Font;
|
||||
#endif
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Text;
|
||||
using LemonUI.Extensions;
|
||||
|
||||
namespace LemonUI.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// A text string.
|
||||
/// </summary>
|
||||
public class ScaledText : IText
|
||||
{
|
||||
#region Consistent Values
|
||||
|
||||
/// <summary>
|
||||
/// The size of every chunk of text.
|
||||
/// </summary>
|
||||
private const int chunkSize = 90;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// The absolute 1080p based screen position.
|
||||
/// </summary>
|
||||
private PointF absolutePosition = PointF.Empty;
|
||||
/// <summary>
|
||||
/// The relative 0-1 relative position.
|
||||
/// </summary>
|
||||
private PointF relativePosition = PointF.Empty;
|
||||
/// <summary>
|
||||
/// The raw string of text.
|
||||
/// </summary>
|
||||
private string text = string.Empty;
|
||||
/// <summary>
|
||||
/// The raw string split into equally sized strings.
|
||||
/// </summary>
|
||||
private List<string> chunks = new List<string>();
|
||||
/// <summary>
|
||||
/// The alignment of the item.
|
||||
/// </summary>
|
||||
private Alignment alignment = Alignment.Left;
|
||||
/// <summary>
|
||||
/// The word wrap value passed by the user.
|
||||
/// </summary>
|
||||
private float internalWrap = 0f;
|
||||
/// <summary>
|
||||
/// The real word wrap value based on the position of the text.
|
||||
/// </summary>
|
||||
private float realWrap = 0f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The position of the text.
|
||||
/// </summary>
|
||||
public PointF Position
|
||||
{
|
||||
get => absolutePosition;
|
||||
set
|
||||
{
|
||||
absolutePosition = value;
|
||||
relativePosition = value.ToRelative();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The text to draw.
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get => text;
|
||||
set
|
||||
{
|
||||
text = value;
|
||||
Slice();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The color of the text.
|
||||
/// </summary>
|
||||
public Color Color { get; set; } = Color.FromArgb(255, 255, 255, 255);
|
||||
/// <summary>
|
||||
/// The game font to use.
|
||||
/// </summary>
|
||||
public Font Font { get; set; } = Font.ChaletLondon;
|
||||
/// <summary>
|
||||
/// The scale of the text.
|
||||
/// </summary>
|
||||
public float Scale { get; set; } = 1f;
|
||||
/// <summary>
|
||||
/// If the text should have a drop down shadow.
|
||||
/// </summary>
|
||||
public bool Shadow { get; set; } = false;
|
||||
/// <summary>
|
||||
/// If the test should have an outline.
|
||||
/// </summary>
|
||||
public bool Outline { get; set; } = false;
|
||||
/// <summary>
|
||||
/// The alignment of the text.
|
||||
/// </summary>
|
||||
public Alignment Alignment
|
||||
{
|
||||
get => alignment;
|
||||
set
|
||||
{
|
||||
alignment = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The distance from the start position where the text will be wrapped into new lines.
|
||||
/// </summary>
|
||||
public float WordWrap
|
||||
{
|
||||
get
|
||||
{
|
||||
return internalWrap;
|
||||
}
|
||||
set
|
||||
{
|
||||
internalWrap = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The width that the text takes from the screen.
|
||||
/// </summary>
|
||||
public float Width
|
||||
{
|
||||
get
|
||||
{
|
||||
#if FIVEM
|
||||
API.BeginTextCommandWidth("CELL_EMAIL_BCON");
|
||||
Add();
|
||||
return API.EndTextCommandGetWidth(true) * 1f.ToXAbsolute();
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.BeginTextCommandWidth, "CELL_EMAIL_BCON");
|
||||
Add();
|
||||
return Invoker.Invoke<float>(Natives.EndTextCommandGetWidth) * 1f.ToXAbsolute();
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x54CE8AC98E120CAB, "CELL_EMAIL_BCON");
|
||||
Add();
|
||||
return NativeFunction.CallByHash<float>(0x85F061DA64ED2F67, true) * 1f.ToXAbsolute();
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash._BEGIN_TEXT_COMMAND_GET_WIDTH, "CELL_EMAIL_BCON");
|
||||
Add();
|
||||
return Function.Call<float>(Hash._END_TEXT_COMMAND_GET_WIDTH, true) * 1f.ToXAbsolute();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The number of lines used by this text.
|
||||
/// </summary>
|
||||
public int LineCount
|
||||
{
|
||||
get
|
||||
{
|
||||
#if FIVEM
|
||||
API.BeginTextCommandLineCount("CELL_EMAIL_BCON");
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.BeginTextCommandLineCount, "CELL_EMAIL_BCON");
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x521FB041D93DD0E4, "CELL_EMAIL_BCON");
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash._BEGIN_TEXT_COMMAND_LINE_COUNT, "CELL_EMAIL_BCON");
|
||||
#endif
|
||||
Add();
|
||||
#if FIVEM
|
||||
return API.EndTextCommandGetLineCount(relativePosition.X, relativePosition.Y);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<int>(Natives.EndTextCommandGetLineCount, relativePosition.X, relativePosition.Y);
|
||||
#elif RPH
|
||||
return NativeFunction.CallByHash<int>(0x9040DFB09BE75706, relativePosition.X, relativePosition.Y);
|
||||
#elif SHVDN3
|
||||
return Function.Call<int>(Hash._END_TEXT_COMMAND_LINE_COUNT, relativePosition.X, relativePosition.Y);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The relative height of each line in the text.
|
||||
/// </summary>
|
||||
public float LineHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
// Height will always be 1080
|
||||
#if FIVEM
|
||||
return 1080 * API.GetTextScaleHeight(Scale, (int)Font);
|
||||
#elif RAGEMP
|
||||
return 1080 * Invoker.Invoke<float>(Natives.GetTextScaleHeight, Scale, (int)Font);
|
||||
#elif RPH
|
||||
return 1080 * NativeFunction.CallByHash<float>(0xDB88A37483346780, Scale, (int)Font);
|
||||
#elif SHVDN3
|
||||
return 1080 * Function.Call<float>(Hash.GET_RENDERED_CHARACTER_HEIGHT, Scale, (int)Font);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a text with the specified options.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position where the text should be located.</param>
|
||||
/// <param name="text">The text to show.</param>
|
||||
public ScaledText(PointF pos, string text) : this(pos, text, 1f, Font.ChaletLondon)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a text with the specified options.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position where the text should be located.</param>
|
||||
/// <param name="text">The text to show.</param>
|
||||
/// <param name="scale">The scale of the text.</param>
|
||||
public ScaledText(PointF pos, string text, float scale) : this(pos, text, scale, Font.ChaletLondon)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a text with the specified options
|
||||
/// </summary>
|
||||
/// <param name="pos">The position where the text should be located.</param>
|
||||
/// <param name="text">The text to show.</param>
|
||||
/// <param name="scale">The scale of the text.</param>
|
||||
/// <param name="font">The font to use.</param>
|
||||
public ScaledText(PointF pos, string text, float scale, Font font)
|
||||
{
|
||||
Position = pos;
|
||||
Text = text;
|
||||
Scale = scale;
|
||||
Font = font;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tools
|
||||
|
||||
/// <summary>
|
||||
/// Adds the text information for measurement.
|
||||
/// </summary>
|
||||
private void Add()
|
||||
{
|
||||
if (Scale == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if FIVEM
|
||||
foreach (string chunk in chunks)
|
||||
{
|
||||
API.AddTextComponentString(chunk);
|
||||
}
|
||||
API.SetTextFont((int)Font);
|
||||
API.SetTextScale(1f, Scale);
|
||||
API.SetTextColour(Color.R, Color.G, Color.B, Color.A);
|
||||
API.SetTextJustification((int)Alignment);
|
||||
if (Shadow)
|
||||
{
|
||||
API.SetTextDropShadow();
|
||||
}
|
||||
if (Outline)
|
||||
{
|
||||
API.SetTextOutline();
|
||||
}
|
||||
if (WordWrap > 0)
|
||||
{
|
||||
switch (Alignment)
|
||||
{
|
||||
case Alignment.Center:
|
||||
API.SetTextWrap(relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
|
||||
break;
|
||||
case Alignment.Left:
|
||||
API.SetTextWrap(relativePosition.X, relativePosition.X + realWrap);
|
||||
break;
|
||||
case Alignment.Right:
|
||||
API.SetTextWrap(relativePosition.X - realWrap, relativePosition.X);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Alignment == Alignment.Right)
|
||||
{
|
||||
API.SetTextWrap(0f, relativePosition.X);
|
||||
}
|
||||
#elif RAGEMP
|
||||
foreach (string chunk in chunks)
|
||||
{
|
||||
Invoker.Invoke(Natives.AddTextComponentSubstringPlayerName, chunk);
|
||||
}
|
||||
Invoker.Invoke(Natives.SetTextFont, (int)Font);
|
||||
Invoker.Invoke(Natives.SetTextScale, 1f, Scale);
|
||||
Invoker.Invoke(Natives.SetTextColour, Color.R, Color.G, Color.B, Color.A);
|
||||
Invoker.Invoke(Natives.SetTextJustification, (int)Alignment);
|
||||
if (Shadow)
|
||||
{
|
||||
Invoker.Invoke(Natives.SetTextDropShadow);
|
||||
}
|
||||
if (Outline)
|
||||
{
|
||||
Invoker.Invoke(Natives.SetTextOutline);
|
||||
}
|
||||
if (WordWrap > 0)
|
||||
{
|
||||
switch (Alignment)
|
||||
{
|
||||
case Alignment.Center:
|
||||
Invoker.Invoke(Natives.SetTextWrap, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
|
||||
break;
|
||||
case Alignment.Left:
|
||||
Invoker.Invoke(Natives.SetTextWrap, relativePosition.X, relativePosition.X + realWrap);
|
||||
break;
|
||||
case Alignment.Right:
|
||||
Invoker.Invoke(Natives.SetTextWrap, relativePosition.X - realWrap, relativePosition.X);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Alignment == Alignment.Right)
|
||||
{
|
||||
Invoker.Invoke(0x63145D9C883A1A70, 0f, relativePosition.X);
|
||||
}
|
||||
#elif RPH
|
||||
foreach (string chunk in chunks)
|
||||
{
|
||||
NativeFunction.CallByHash<int>(0x6C188BE134E074AA, chunk);
|
||||
}
|
||||
NativeFunction.CallByHash<int>(0x66E0276CC5F6B9DA, (int)Font);
|
||||
NativeFunction.CallByHash<int>(0x07C837F9A01C34C9, 1f, Scale);
|
||||
NativeFunction.CallByHash<int>(0xBE6B23FFA53FB442, Color.R, Color.G, Color.B, Color.A);
|
||||
NativeFunction.CallByHash<int>(0x4E096588B13FFECA, (int)Alignment);
|
||||
if (Shadow)
|
||||
{
|
||||
NativeFunction.CallByHash<int>(0x1CA3E9EAC9D93E5E);
|
||||
}
|
||||
if (Outline)
|
||||
{
|
||||
NativeFunction.CallByHash<int>(0x2513DFB0FB8400FE);
|
||||
}
|
||||
if (WordWrap > 0)
|
||||
{
|
||||
switch (Alignment)
|
||||
{
|
||||
case Alignment.Center:
|
||||
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
|
||||
break;
|
||||
case Alignment.Left:
|
||||
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X, relativePosition.X + realWrap);
|
||||
break;
|
||||
case Alignment.Right:
|
||||
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X - realWrap, relativePosition.X);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Alignment == Alignment.Right)
|
||||
{
|
||||
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, 0f, relativePosition.X);
|
||||
}
|
||||
#elif SHVDN3
|
||||
foreach (string chunk in chunks)
|
||||
{
|
||||
Function.Call((Hash)0x6C188BE134E074AA, chunk); // _ADD_TEXT_COMPONENT_STRING on v2, ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME on v3
|
||||
}
|
||||
Function.Call(Hash.SET_TEXT_FONT, (int)Font);
|
||||
Function.Call(Hash.SET_TEXT_SCALE, 1f, Scale);
|
||||
Function.Call(Hash.SET_TEXT_COLOUR, Color.R, Color.G, Color.B, Color.A);
|
||||
Function.Call(Hash.SET_TEXT_JUSTIFICATION, (int)Alignment);
|
||||
if (Shadow)
|
||||
{
|
||||
Function.Call(Hash.SET_TEXT_DROP_SHADOW);
|
||||
}
|
||||
if (Outline)
|
||||
{
|
||||
Function.Call(Hash.SET_TEXT_OUTLINE);
|
||||
}
|
||||
if (WordWrap > 0)
|
||||
{
|
||||
switch (Alignment)
|
||||
{
|
||||
case Alignment.Center:
|
||||
Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
|
||||
break;
|
||||
case Alignment.Left:
|
||||
Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X, relativePosition.X + realWrap);
|
||||
break;
|
||||
case Alignment.Right:
|
||||
Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X - realWrap, relativePosition.X);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Alignment == Alignment.Right)
|
||||
{
|
||||
Function.Call(Hash.SET_TEXT_WRAP, 0f, relativePosition.X);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Slices the string of text into appropiately saved chunks.
|
||||
/// </summary>
|
||||
private void Slice()
|
||||
{
|
||||
// If the entire text is under 90 bytes, save it as is and return
|
||||
if (Encoding.UTF8.GetByteCount(text) <= chunkSize)
|
||||
{
|
||||
chunks.Clear();
|
||||
chunks.Add(text);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new list of chunks and a temporary string
|
||||
List<string> newChunks = new List<string>();
|
||||
string temp = string.Empty;
|
||||
|
||||
// Iterate over the characters in the string
|
||||
foreach (char character in text)
|
||||
{
|
||||
// Create a temporary string with the character
|
||||
string with = string.Concat(temp, character);
|
||||
// If this string is higher than 90 bytes, add the existing string onto the list
|
||||
if (Encoding.UTF8.GetByteCount(with) > chunkSize)
|
||||
{
|
||||
newChunks.Add(temp);
|
||||
temp = character.ToString();
|
||||
continue;
|
||||
}
|
||||
// And save the new string generated
|
||||
temp = with;
|
||||
}
|
||||
|
||||
// If after finishing we still have a piece, save it
|
||||
if (temp != string.Empty)
|
||||
{
|
||||
newChunks.Add(temp);
|
||||
}
|
||||
|
||||
// Once we have finished, replace the old chunks
|
||||
chunks = newChunks;
|
||||
}
|
||||
/// <summary>
|
||||
/// Recalculates the size, position and word wrap of this item.
|
||||
/// </summary>
|
||||
public void Recalculate()
|
||||
{
|
||||
// Do the normal Size and Position recalculation
|
||||
relativePosition = absolutePosition.ToRelative();
|
||||
// And recalculate the word wrap if necessary
|
||||
if (internalWrap <= 0)
|
||||
{
|
||||
realWrap = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
realWrap = internalWrap.ToXRelative();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Draws the text on the screen.
|
||||
/// </summary>
|
||||
public void Draw()
|
||||
{
|
||||
#if FIVEM
|
||||
API.SetTextEntry("CELL_EMAIL_BCON");
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.BeginTextCommandDisplayText, "CELL_EMAIL_BCON");
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x25FBB336DF1804CB, "CELL_EMAIL_BCON");
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0x25FBB336DF1804CB, "CELL_EMAIL_BCON"); // _SET_TEXT_ENTRY on v2, BEGIN_TEXT_COMMAND_DISPLAY_TEXT on v3
|
||||
#endif
|
||||
|
||||
Add();
|
||||
|
||||
#if FIVEM
|
||||
API.DrawText(relativePosition.X, relativePosition.Y);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.DrawDebugText, relativePosition.X, relativePosition.Y);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y); // _DRAW_TEXT on v2, END_TEXT_COMMAND_DISPLAY_TEXT on v3
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage.Native;
|
||||
#elif SHVDN3
|
||||
using GTA.Native;
|
||||
#endif
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// A 2D game texture.
|
||||
/// </summary>
|
||||
public class ScaledTexture : BaseElement
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The dictionary where the texture is loaded.
|
||||
/// </summary>
|
||||
public string Dictionary { get; set; }
|
||||
/// <summary>
|
||||
/// The texture to draw from the dictionary.
|
||||
/// </summary>
|
||||
public string Texture { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScaledTexture"/> with a Position and Size of Zero.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary where the texture is located.</param>
|
||||
/// <param name="texture">The texture to draw.</param>
|
||||
public ScaledTexture(string dictionary, string texture) : this(PointF.Empty, SizeF.Empty, dictionary, texture)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScaledTexture"/> with a Position and Size of zero.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the Texture.</param>
|
||||
/// <param name="size">The size of the Texture.</param>
|
||||
/// <param name="dictionary">The dictionary where the texture is located.</param>
|
||||
/// <param name="texture">The texture to draw.</param>
|
||||
public ScaledTexture(PointF pos, SizeF size, string dictionary, string texture) : base(pos, size)
|
||||
{
|
||||
Dictionary = dictionary;
|
||||
Texture = texture;
|
||||
Request();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
/// <summary>
|
||||
/// Requests the texture dictionary for this class.
|
||||
/// </summary>
|
||||
private void Request()
|
||||
{
|
||||
#if FIVEM
|
||||
if (!API.HasStreamedTextureDictLoaded(Dictionary))
|
||||
{
|
||||
API.RequestStreamedTextureDict(Dictionary, true);
|
||||
}
|
||||
#elif RAGEMP
|
||||
if (!Invoker.Invoke<bool>(Natives.HasStreamedTextureDictLoaded, Dictionary))
|
||||
{
|
||||
Invoker.Invoke(Natives.RequestStreamedTextureDict, Dictionary, true);
|
||||
}
|
||||
#elif RPH
|
||||
if (!NativeFunction.CallByHash<bool>(0x0145F696AAAAD2E4, Dictionary))
|
||||
{
|
||||
NativeFunction.CallByHash<int>(0xDFA2EF8E04127DD5, Dictionary, true);
|
||||
}
|
||||
#elif SHVDN3
|
||||
if (!Function.Call<bool>(Hash.HAS_STREAMED_TEXTURE_DICT_LOADED, Dictionary))
|
||||
{
|
||||
Function.Call(Hash.REQUEST_STREAMED_TEXTURE_DICT, Dictionary, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Draws the texture on the screen.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
if (Size == SizeF.Empty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Request();
|
||||
#if FIVEM
|
||||
API.DrawSprite(Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.DrawSprite, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xE7FFAE5EBF23D890, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.DRAW_SPRITE, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws a specific part of the texture on the screen.
|
||||
/// </summary>
|
||||
public void DrawSpecific(PointF topLeft, PointF bottomRight)
|
||||
{
|
||||
if (Size == SizeF.Empty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Request();
|
||||
#if FIVEM
|
||||
API.DrawSpriteUv(Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0x95812F9B26074726, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x95812F9B26074726, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0x95812F9B26074726, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Recalculates the position based on the size.
|
||||
/// </summary>
|
||||
public override void Recalculate()
|
||||
{
|
||||
base.Recalculate();
|
||||
relativePosition.X += relativeSize.Width * 0.5f;
|
||||
relativePosition.Y += relativeSize.Height * 0.5f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
namespace LemonUI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the float class.
|
||||
/// </summary>
|
||||
public static class FloatExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an absolute X or Width float to a relative one.
|
||||
/// </summary>
|
||||
/// <param name="fin">The float to convert.</param>
|
||||
/// <returns>A relative float between 0 and 1.</returns>
|
||||
public static float ToXRelative(this float fin)
|
||||
{
|
||||
Screen.ToRelative(fin, 0, out float fout, out _);
|
||||
return fout;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts an absolute Y or Height float to a relative one.
|
||||
/// </summary>
|
||||
/// <param name="fin">The float to convert.</param>
|
||||
/// <returns>A relative float between 0 and 1.</returns>
|
||||
public static float ToYRelative(this float fin)
|
||||
{
|
||||
Screen.ToRelative(0, fin, out _, out float fout);
|
||||
return fout;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts an relative X or Width float to an absolute one.
|
||||
/// </summary>
|
||||
/// <param name="fin">The float to convert.</param>
|
||||
/// <returns>An absolute float.</returns>
|
||||
public static float ToXAbsolute(this float fin)
|
||||
{
|
||||
Screen.ToAbsolute(fin, 0, out float fout, out _);
|
||||
return fout;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts an relative Y or Height float to an absolute one.
|
||||
/// </summary>
|
||||
/// <param name="fin">The float to convert.</param>
|
||||
/// <returns>An absolute float.</returns>
|
||||
public static float ToYAbsolute(this float fin)
|
||||
{
|
||||
Screen.ToAbsolute(0, fin, out _, out float fout);
|
||||
return fout;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the Point and PointF classes.
|
||||
/// </summary>
|
||||
public static class PointExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an absolute 1080-based position into a relative one.
|
||||
/// </summary>
|
||||
/// <param name="point">The absolute PointF.</param>
|
||||
/// <returns>A new PointF with relative values.</returns>
|
||||
public static PointF ToRelative(this PointF point)
|
||||
{
|
||||
Screen.ToRelative(point.X, point.Y, out float x, out float y);
|
||||
return new PointF(x, y);
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts a normalized 0-1 position into an absolute one.
|
||||
/// </summary>
|
||||
/// <param name="point">The relative PointF.</param>
|
||||
/// <returns>A new PointF with absolute values.</returns>
|
||||
public static PointF ToAbsolute(this PointF point)
|
||||
{
|
||||
Screen.ToAbsolute(point.X, point.Y, out float x, out float y);
|
||||
return new PointF(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the Size and SizeF classes.
|
||||
/// </summary>
|
||||
public static class SizeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an absolute 1080-based size into a relative one.
|
||||
/// </summary>
|
||||
/// <param name="size">The absolute SizeF.</param>
|
||||
/// <returns>A new SizeF with relative values.</returns>
|
||||
public static SizeF ToRelative(this SizeF size)
|
||||
{
|
||||
Screen.ToRelative(size.Width, size.Height, out float width, out float height);
|
||||
return new SizeF(width, height);
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts a normalized 0-1 size into an absolute one.
|
||||
/// </summary>
|
||||
/// <param name="size">The relative SizeF.</param>
|
||||
/// <returns>A new SizeF with absolute values.</returns>
|
||||
public static SizeF ToAbsolute(this SizeF size)
|
||||
{
|
||||
Screen.ToAbsolute(size.Width, size.Height, out float width, out float height);
|
||||
return new SizeF(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a container that can hold other UI Elements.
|
||||
/// </summary>
|
||||
public interface IContainer<T> : IRecalculable, IProcessable
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the specified item into the Container.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
void Add(T item);
|
||||
/// <summary>
|
||||
/// Removes the item from the container.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to remove.</param>
|
||||
void Remove(T item);
|
||||
/// <summary>
|
||||
/// Removes all of the items that match the function.
|
||||
/// </summary>
|
||||
/// <param name="func">The function to check items.</param>
|
||||
void Remove(Func<T, bool> func);
|
||||
/// <summary>
|
||||
/// Clears all of the items in the container.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
/// <summary>
|
||||
/// Checks if the item is part of the container.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to check.</param>
|
||||
/// <returns><see langword="true"/> if the item is in this container, <see langword="false"/> otherwise.</returns>
|
||||
bool Contains(T item);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an item that can be drawn.
|
||||
/// </summary>
|
||||
public interface IDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws the item on the screen.
|
||||
/// </summary>
|
||||
void Draw();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for items that can be processed in an Object Pool.
|
||||
/// </summary>
|
||||
public interface IProcessable
|
||||
{
|
||||
/// <summary>
|
||||
/// If this processable item is visible on the screen.
|
||||
/// </summary>
|
||||
bool Visible { get; set; }
|
||||
/// <summary>
|
||||
/// Processes the object.
|
||||
/// </summary>
|
||||
void Process();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for classes that have values that need to be recalculated on resolution changes.
|
||||
/// </summary>
|
||||
public interface IRecalculable
|
||||
{
|
||||
/// <summary>
|
||||
/// Recalculates the values.
|
||||
/// </summary>
|
||||
void Recalculate();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a badge that can be applied to a <see cref="NativeItem"/>.
|
||||
/// </summary>
|
||||
public class BadgeSet
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The texture dictionary where the normal texture is located.
|
||||
/// </summary>
|
||||
public string NormalDictionary { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// The texture to use when the item is not hovered.
|
||||
/// </summary>
|
||||
public string NormalTexture { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// The texture dictionary where the normal texture is located.
|
||||
/// </summary>
|
||||
public string HoveredDictionary { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// The texture to use when the item is hovered.
|
||||
/// </summary>
|
||||
public string HoveredTexture { get; set; } = string.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new empty <see cref="BadgeSet"/>.
|
||||
/// </summary>
|
||||
public BadgeSet()
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BadgeSet"/> where both textures are in the same dictionary.
|
||||
/// </summary>
|
||||
/// <param name="dict">The dictionary where the textures are located.</param>
|
||||
/// <param name="normal">The normal texture name.</param>
|
||||
/// <param name="hovered">The hovered texture name.</param>
|
||||
public BadgeSet(string dict, string normal, string hovered)
|
||||
{
|
||||
NormalDictionary = dict;
|
||||
NormalTexture = normal;
|
||||
HoveredDictionary = dict;
|
||||
HoveredTexture = hovered;
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BadgeSet"/> where both textures are in different dictionaries.
|
||||
/// </summary>
|
||||
/// <param name="normalDict">The dictionary where the normal texture is located.</param>
|
||||
/// <param name="normalTexture">The normal texture name.</param>
|
||||
/// <param name="hoveredDict">The dictionary where the hovered texture is located.</param>
|
||||
/// <param name="hoveredTexture">The hovered texture name.</param>
|
||||
public BadgeSet(string normalDict, string normalTexture, string hoveredDict, string hoveredTexture)
|
||||
{
|
||||
NormalDictionary = normalDict;
|
||||
NormalTexture = normalTexture;
|
||||
HoveredDictionary = hoveredDict;
|
||||
HoveredTexture = hoveredTexture;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores the different colors required to make the colors of a <see cref="NativeItem"/> dynamic.
|
||||
/// </summary>
|
||||
public class ColorSet
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private static readonly Color colorWhite = Color.FromArgb(255, 255, 255, 255);
|
||||
private static readonly Color colorWhiteSmoke = Color.FromArgb(255, 245, 245, 245);
|
||||
private static readonly Color colorBlack = Color.FromArgb(255, 0, 0, 0);
|
||||
private static readonly Color colorDisabled = Color.FromArgb(255, 163, 159, 148);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.Title"/> when the <see cref="NativeItem"/> is not hovered and enabled.
|
||||
/// </summary>
|
||||
public Color TitleNormal { get; set; } = colorWhiteSmoke;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.Title"/> when the <see cref="NativeItem"/> is hovered.
|
||||
/// </summary>
|
||||
public Color TitleHovered { get; set; } = colorBlack;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.Title"/> when the <see cref="NativeItem"/> is disabled.
|
||||
/// </summary>
|
||||
public Color TitleDisabled { get; set; } = colorDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.AltTitle"/> when the <see cref="NativeItem"/> is not hovered and enabled.
|
||||
/// </summary>
|
||||
public Color AltTitleNormal { get; set; } = colorWhiteSmoke;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.AltTitle"/> when the <see cref="NativeItem"/> is hovered.
|
||||
/// </summary>
|
||||
public Color AltTitleHovered { get; set; } = colorBlack;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.AltTitle"/> when the <see cref="NativeItem"/> is disabled.
|
||||
/// </summary>
|
||||
public Color AltTitleDisabled { get; set; } = colorDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeSlidableItem"/> arrows when the item is not hovered and enabled.
|
||||
/// </summary>
|
||||
public Color ArrowsNormal { get; set; } = colorWhiteSmoke;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeSlidableItem"/> arrows when the item is hovered.
|
||||
/// </summary>
|
||||
public Color ArrowsHovered { get; set; } = colorBlack;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeSlidableItem"/> arrows when the item is disabled.
|
||||
/// </summary>
|
||||
public Color ArrowsDisabled { get; set; } = colorDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.LeftBadge"/> when the <see cref="NativeItem"/> is not hovered and enabled.
|
||||
/// </summary>
|
||||
public Color BadgeLeftNormal { get; set; } = colorWhite;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.LeftBadge"/> when the <see cref="NativeItem"/> is hovered.
|
||||
/// </summary>
|
||||
public Color BadgeLeftHovered { get; set; } = colorWhite;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.LeftBadge"/> when the <see cref="NativeItem"/> is disabled.
|
||||
/// </summary>
|
||||
public Color BadgeLeftDisabled { get; set; } = colorWhite;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.RightBadge"/> or <see cref="NativeCheckboxItem"/> checkbox when the <see cref="NativeItem"/> is not hovered and enabled.
|
||||
/// </summary>
|
||||
public Color BadgeRightNormal { get; set; } = colorWhite;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.RightBadge"/> or <see cref="NativeCheckboxItem"/> checkbox when the <see cref="NativeItem"/> is hovered.
|
||||
/// </summary>
|
||||
public Color BadgeRightHovered { get; set; } = colorWhite;
|
||||
/// <summary>
|
||||
/// The color of the <see cref="NativeItem.RightBadge"/> or <see cref="NativeCheckboxItem"/> checkbox when the <see cref="NativeItem"/> is disabled.
|
||||
/// </summary>
|
||||
public Color BadgeRightDisabled { get; set; } = colorWhite;
|
||||
|
||||
/// <summary>
|
||||
/// The normal color of the custom background if <see cref="NativeItem.UseCustomBackground"/> is set to <see langword="true"/>.
|
||||
/// </summary>
|
||||
public Color BackgroundNormal { get; set; } = colorWhite;
|
||||
/// <summary>
|
||||
/// The hovered color of the custom background if <see cref="NativeItem.UseCustomBackground"/> is set to <see langword="true"/>.
|
||||
/// </summary>
|
||||
public Color BackgroundHovered { get; set; } = colorWhite;
|
||||
/// <summary>
|
||||
/// The disabled color of the custom background if <see cref="NativeItem.UseCustomBackground"/> is set to <see langword="true"/>.
|
||||
/// </summary>
|
||||
public Color BackgroundDisabled { get; set; } = colorWhite;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// The Style of title for the Color Panel.
|
||||
/// </summary>
|
||||
public enum ColorTitleStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Does not shows any Title.
|
||||
/// The count will still be shown if <see cref="NativeColorPanel.ShowCount"/> is set to <see langword="true"/>.
|
||||
/// </summary>
|
||||
None = -1,
|
||||
/// <summary>
|
||||
/// Shows a Simple Title for all of the Colors.
|
||||
/// </summary>
|
||||
Simple = 0,
|
||||
/// <summary>
|
||||
/// Shows the Color Name as the Title.
|
||||
/// </summary>
|
||||
ColorName = 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// The visibility setting for the Item Count of the Menu.
|
||||
/// </summary>
|
||||
public enum CountVisibility
|
||||
{
|
||||
/// <summary>
|
||||
/// The Item Count is never shown.
|
||||
/// </summary>
|
||||
Never = -1,
|
||||
/// <summary>
|
||||
/// The Item Count is shown when is not possible to show all of the items in the screen.
|
||||
/// </summary>
|
||||
Auto = 0,
|
||||
/// <summary>
|
||||
/// The Item Count is always shown.
|
||||
/// </summary>
|
||||
Always = 1
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// The movement direction of the item change.
|
||||
/// </summary>
|
||||
public enum Direction
|
||||
{
|
||||
/// <summary>
|
||||
/// The Direction is Unknown.
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
/// <summary>
|
||||
/// The item was moved to the Left.
|
||||
/// </summary>
|
||||
Left = 1,
|
||||
/// <summary>
|
||||
/// The item was moved to the Right.
|
||||
/// </summary>
|
||||
Right = 2,
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// The style of the Grid Panel.
|
||||
/// </summary>
|
||||
public enum GridStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// The full grid with X and Y values.
|
||||
/// </summary>
|
||||
Full = 0,
|
||||
/// <summary>
|
||||
/// A single row on the center with the X value only.
|
||||
/// </summary>
|
||||
Row = 1,
|
||||
/// <summary>
|
||||
/// A single column on the center with the Y value only.
|
||||
/// </summary>
|
||||
Column = 2,
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Previous and Current X and Y values when changing the position on a grid.
|
||||
/// </summary>
|
||||
public class GridValueChangedArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The values present before they were changed.
|
||||
/// </summary>
|
||||
public PointF Before { get; }
|
||||
/// <summary>
|
||||
/// The values present after they were changed.
|
||||
/// </summary>
|
||||
public PointF After { get; }
|
||||
|
||||
internal GridValueChangedArgs(PointF before, PointF after)
|
||||
{
|
||||
Before = before;
|
||||
After = after;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that is called when the value on a grid is changed.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">An <see cref="ItemActivatedArgs"/> with the item information.</param>
|
||||
public delegate void GridValueChangedEventHandler(object sender, GridValueChangedArgs e);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the arguments of an item activation.
|
||||
/// </summary>
|
||||
public class ItemActivatedArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The item that was just activated.
|
||||
/// </summary>
|
||||
public NativeItem Item { get; }
|
||||
|
||||
internal ItemActivatedArgs(NativeItem item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that is called when an item is activated on a menu.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">An <see cref="ItemActivatedArgs"/> with the item information.</param>
|
||||
public delegate void ItemActivatedEventHandler(object sender, ItemActivatedArgs e);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the change of the selection of an item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object that got changed.</typeparam>
|
||||
public class ItemChangedEventArgs<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The new object.
|
||||
/// </summary>
|
||||
public T Object { get; set; }
|
||||
/// <summary>
|
||||
/// The index of the object.
|
||||
/// </summary>
|
||||
public int Index { get; }
|
||||
/// <summary>
|
||||
/// The direction of the Item Changed event.
|
||||
/// </summary>
|
||||
public Direction Direction { get; }
|
||||
|
||||
internal ItemChangedEventArgs(T obj, int index, Direction direction)
|
||||
{
|
||||
Object = obj;
|
||||
Index = index;
|
||||
Direction = direction;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that is called when the selected item is changed on a List Item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item that was changed.</typeparam>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">A <see cref="ItemChangedEventArgs{T}"/> with the information of the selected item.</param>
|
||||
public delegate void ItemChangedEventHandler<T>(object sender, ItemChangedEventArgs<T> e);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// The operation performed when the menu items are modified.
|
||||
/// </summary>
|
||||
public enum ItemOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// The item has been removed.
|
||||
/// </summary>
|
||||
Removed = -1,
|
||||
/// <summary>
|
||||
/// The item has been added.
|
||||
/// </summary>
|
||||
Added = 0,
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the different
|
||||
/// </summary>
|
||||
public class MenuModifiedEventArgs
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The item that was modified.
|
||||
/// </summary>
|
||||
public NativeItem Item { get; }
|
||||
/// <summary>
|
||||
/// The operation that was performed in the item.
|
||||
/// </summary>
|
||||
public ItemOperation Operation { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MenuModifiedEventArgs"/>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item that was modified.</param>
|
||||
/// <param name="operation">The operation that was performed in the item.</param>
|
||||
public MenuModifiedEventArgs(NativeItem item, ItemOperation operation)
|
||||
{
|
||||
Item = item;
|
||||
Operation = operation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that is called when the items on a menu are changed (added or removed).
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">An <see cref="MenuModifiedEventArgs"/> with the menu operation.</param>
|
||||
public delegate void MenuModifiedEventHandler(object sender, MenuModifiedEventArgs e);
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Rockstar-like checkbox item.
|
||||
/// </summary>
|
||||
public class NativeCheckboxItem : NativeItem
|
||||
{
|
||||
#region Fields
|
||||
|
||||
/// <summary>
|
||||
/// The image shown on the checkbox.
|
||||
/// </summary>
|
||||
internal protected ScaledTexture check = new ScaledTexture(PointF.Empty, SizeF.Empty, "commonmenu", string.Empty);
|
||||
/// <summary>
|
||||
/// If this item is checked or not.
|
||||
/// </summary>
|
||||
private bool checked_ = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// If this item is checked or not.
|
||||
/// </summary>
|
||||
public bool Checked
|
||||
{
|
||||
get => checked_;
|
||||
set
|
||||
{
|
||||
if (checked_ == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
checked_ = value;
|
||||
UpdateTexture(lastSelected);
|
||||
CheckboxChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the checkbox changes.
|
||||
/// </summary>
|
||||
public event EventHandler CheckboxChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeCheckboxItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title used for the Item.</param>
|
||||
public NativeCheckboxItem(string title) : this(title, string.Empty, false)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeCheckboxItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title used for the Item.</param>
|
||||
/// <param name="check">If the checkbox should be enabled or not.</param>
|
||||
public NativeCheckboxItem(string title, bool check) : this(title, string.Empty, check)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeCheckboxItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title used for the Item.</param>
|
||||
/// <param name="description">The description of the Item.</param>
|
||||
public NativeCheckboxItem(string title, string description) : this(title, description, false)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeCheckboxItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title used for the Item.</param>
|
||||
/// <param name="description">The description of the Item.</param>
|
||||
/// <param name="check">If the checkbox should be enabled or not.</param>
|
||||
public NativeCheckboxItem(string title, string description, bool check) : base(title, description)
|
||||
{
|
||||
Checked = check;
|
||||
Activated += Toggle;
|
||||
EnabledChanged += NativeCheckboxItem_EnabledChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local Events
|
||||
|
||||
private void NativeCheckboxItem_EnabledChanged(object sender, EventArgs e) => UpdateTexture(lastSelected);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Functions
|
||||
|
||||
/// <summary>
|
||||
/// Inverts the checkbox activation.
|
||||
/// </summary>
|
||||
private void Toggle(object sender, EventArgs e) => Checked = !Checked;
|
||||
/// <summary>
|
||||
/// Updates the texture of the sprite.
|
||||
/// </summary>
|
||||
internal protected void UpdateTexture(bool selected)
|
||||
{
|
||||
// If the item is not selected or is not enabled, use the white pictures
|
||||
if (!selected || !Enabled)
|
||||
{
|
||||
check.Texture = Checked ? "shop_box_tick" : "shop_box_blank";
|
||||
}
|
||||
// Otherwise, use the black ones
|
||||
else
|
||||
{
|
||||
check.Texture = Checked ? "shop_box_tickb" : "shop_box_blankb";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the item positions and sizes with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the item.</param>
|
||||
/// <param name="size">The size of the item.</param>
|
||||
/// <param name="selected">If this item has been selected.</param>
|
||||
public override void Recalculate(PointF pos, SizeF size, bool selected)
|
||||
{
|
||||
base.Recalculate(pos, size, selected);
|
||||
// Set the correct texture
|
||||
UpdateTexture(selected);
|
||||
// And set the checkbox positions
|
||||
check.Position = new PointF(pos.X + size.Width - 50, pos.Y - 6);
|
||||
check.Size = new SizeF(50, 50);
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the Checkbox on the screen.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
title.Draw();
|
||||
badgeLeft?.Draw();
|
||||
check.Draw();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateColors()
|
||||
{
|
||||
base.UpdateColors();
|
||||
|
||||
if (!Enabled)
|
||||
{
|
||||
check.Color = Colors.BadgeRightDisabled;
|
||||
}
|
||||
else if (lastSelected)
|
||||
{
|
||||
check.Color = Colors.BadgeRightHovered;
|
||||
}
|
||||
else
|
||||
{
|
||||
check.Color = Colors.BadgeRightNormal;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.Drawing;
|
||||
using LemonUI.Elements;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Color Information shown on the Panel.
|
||||
/// </summary>
|
||||
public class NativeColorData
|
||||
{
|
||||
#region Internal Fields
|
||||
|
||||
internal readonly ScaledRectangle rectangle = new ScaledRectangle(PointF.Empty, SizeF.Empty);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the color.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
/// <summary>
|
||||
/// The RGBA values of the color.
|
||||
/// </summary>
|
||||
public Color Color
|
||||
{
|
||||
get => rectangle.Color;
|
||||
set => rectangle.Color = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Color Panel information.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the color.</param>
|
||||
/// <param name="color">The RGBA values of the color.</param>
|
||||
public NativeColorData(string name, Color color)
|
||||
{
|
||||
Name = name;
|
||||
rectangle.Color = color;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,678 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.UI;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Control = Rage.GameControl;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
using GTA.UI;
|
||||
#endif
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// A Panel that allows you to select a Color.
|
||||
/// </summary>
|
||||
public class NativeColorPanel : NativePanel
|
||||
{
|
||||
#region Constants
|
||||
|
||||
/// <summary>
|
||||
/// The space difference for the colors and opacity bar on the left.
|
||||
/// </summary>
|
||||
private const float leftDifference = 16;
|
||||
/// <summary>
|
||||
/// The space difference for the colors and opacity bar on the left.
|
||||
/// </summary>
|
||||
private const float rightDifference = 12;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// The position reported after the last Recalculation.
|
||||
/// </summary>
|
||||
private PointF lastPosition = PointF.Empty;
|
||||
/// <summary>
|
||||
/// The Width reported after the last Recalculation.
|
||||
/// </summary>
|
||||
private float lastWidth = 0;
|
||||
/// <summary>
|
||||
/// The title of the Color Panel.
|
||||
/// </summary>
|
||||
private ScaledText title = new ScaledText(PointF.Empty, string.Empty, 0.325f)
|
||||
{
|
||||
Alignment = Alignment.Center
|
||||
};
|
||||
/// <summary>
|
||||
/// The rectangle used for marking the item selection on the screen.
|
||||
/// </summary>
|
||||
private ScaledRectangle selectionRectangle = new ScaledRectangle(PointF.Empty, SizeF.Empty);
|
||||
/// <summary>
|
||||
/// The "Opacity" text when the opacity bar is enabled
|
||||
/// </summary>
|
||||
private ScaledText opacityText = new ScaledText(PointF.Empty, "Opacity", 0.325f)
|
||||
{
|
||||
Alignment = Alignment.Center
|
||||
};
|
||||
/// <summary>
|
||||
/// The zero percent text when the opacity bar is enabled.
|
||||
/// </summary>
|
||||
private ScaledText percentMin = new ScaledText(PointF.Empty, "0%", 0.325f);
|
||||
/// <summary>
|
||||
/// The 100 percent text when the opacity bar is enabled.
|
||||
/// </summary>
|
||||
private ScaledText percentMax = new ScaledText(PointF.Empty, "100%", 0.325f);
|
||||
/// <summary>
|
||||
/// The top section of the opacity bar.
|
||||
/// </summary>
|
||||
private ScaledRectangle opacityForeground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
|
||||
{
|
||||
Color = Color.FromArgb(255, 240, 240, 240)
|
||||
};
|
||||
/// <summary>
|
||||
/// The background of the opacity bar.
|
||||
/// </summary>
|
||||
private ScaledRectangle opacityBackground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
|
||||
{
|
||||
Color = Color.FromArgb(150, 88, 88, 88)
|
||||
};
|
||||
/// <summary>
|
||||
/// If the opacity bar is available to the user.
|
||||
/// </summary>
|
||||
private bool showOpacity = false;
|
||||
/// <summary>
|
||||
/// The current value of the opacity slider.
|
||||
/// </summary>
|
||||
private int opacity = 0;
|
||||
/// <summary>
|
||||
/// The current index of the Colors.
|
||||
/// </summary>
|
||||
private int index = 0;
|
||||
/// <summary>
|
||||
/// The position of the first item.
|
||||
/// </summary>
|
||||
private int firstItem = 0;
|
||||
/// <summary>
|
||||
/// The maximum number of items shown at once.
|
||||
/// </summary>
|
||||
private int maxItems = 9;
|
||||
/// <summary>
|
||||
/// The generic title for this color.
|
||||
/// </summary>
|
||||
private string simpleTitle = "Color";
|
||||
/// <summary>
|
||||
/// The style of the title.
|
||||
/// </summary>
|
||||
private ColorTitleStyle titleStyle = ColorTitleStyle.Simple;
|
||||
/// <summary>
|
||||
/// If the number of colors should be shown.
|
||||
/// </summary>
|
||||
private bool showCount = true;
|
||||
/// <summary>
|
||||
/// The items that are currently visible on the screen.
|
||||
/// </summary>
|
||||
private List<NativeColorData> visibleItems = new List<NativeColorData>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Fields
|
||||
|
||||
/// <summary>
|
||||
/// The default sound used for the Color Navigation.
|
||||
/// </summary>
|
||||
public static readonly Sound DefaultSound = new Sound("HUD_FRONTEND_DEFAULT_SOUNDSET", "NAV_LEFT_RIGHT");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Clickable => true;
|
||||
/// <summary>
|
||||
/// If the Opacity selector should be shown.
|
||||
/// </summary>
|
||||
public bool ShowOpacity
|
||||
{
|
||||
get => showOpacity;
|
||||
set
|
||||
{
|
||||
showOpacity = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The opacity value of the color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value needs to be set between 100 and 0.
|
||||
/// It will return -1 if <see cref="ShowOpacity"/> is set to <see langword="false"/>.
|
||||
/// </remarks>
|
||||
public int Opacity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ShowOpacity)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return opacity;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!ShowOpacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value > 100 || value < 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException("The value needs to be over 0 and under 100.");
|
||||
}
|
||||
opacity = value;
|
||||
UpdateOpacityBar();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The currently selected color.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If <see cref="ShowOpacity"/> is set to <see langword="true"/>.
|
||||
/// </remarks>
|
||||
public Color SelectedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
// If there is no selected color information, return
|
||||
NativeColorData data = SelectedItem;
|
||||
if (data == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
// Otherwise, return the color
|
||||
return Color.FromArgb(ShowOpacity ? (int)(255 * (Opacity * 0.01f)) : 255, data.Color.R, data.Color.G, data.Color.B);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the currently selected <see cref="NativeColorData"/>.
|
||||
/// </summary>
|
||||
public NativeColorData SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Colors.Count == 0 || index >= Colors.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Colors[SelectedIndex];
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The index of the currently selected Color.
|
||||
/// </summary>
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Colors.Count == 0 || index >= Colors.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
set
|
||||
{
|
||||
// If the list of items is empty, don't allow the user to set the index
|
||||
if (Colors == null || Colors.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("There are no items in this menu.");
|
||||
}
|
||||
// If the value is over or equal than the number of items, raise an exception
|
||||
else if (value >= Colors.Count)
|
||||
{
|
||||
throw new InvalidOperationException($"The index is over {Colors.Count - 1}");
|
||||
}
|
||||
|
||||
// Calculate the bounds of the menu
|
||||
int lower = firstItem;
|
||||
int upper = firstItem + maxItems;
|
||||
|
||||
// Time to set the first item based on the total number of items
|
||||
// If the item is between the allowed values, do nothing because we are on the correct first item
|
||||
if (value >= lower && value < upper - 1)
|
||||
{
|
||||
}
|
||||
// If the upper bound + 1 equals the new index, increase it by one
|
||||
else if (upper == value)
|
||||
{
|
||||
firstItem += 1;
|
||||
}
|
||||
// If the first item minus one equals the value, decrease it by one
|
||||
else if (lower - 1 == value)
|
||||
{
|
||||
firstItem -= 1;
|
||||
}
|
||||
// Otherwise, set it somewhere
|
||||
else
|
||||
{
|
||||
// If the value is under the max items, set it to zero
|
||||
if (value < maxItems)
|
||||
{
|
||||
firstItem = 0;
|
||||
}
|
||||
// Otherwise, set it at the bottom
|
||||
else
|
||||
{
|
||||
firstItem = value - maxItems + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the index and update the items
|
||||
index = value;
|
||||
UpdateItems();
|
||||
// Finally, play the switch change sound
|
||||
Sound?.PlayFrontend();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Title used for the Panel when <see cref="TitleStyle"/> is set to <see cref="ColorTitleStyle.Simple"/>.
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => simpleTitle;
|
||||
set
|
||||
{
|
||||
simpleTitle = value;
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The style of the Panel Title.
|
||||
/// </summary>
|
||||
public ColorTitleStyle TitleStyle
|
||||
{
|
||||
get => titleStyle;
|
||||
set
|
||||
{
|
||||
titleStyle = value;
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// If the count of items should be shown as part of the title.
|
||||
/// </summary>
|
||||
public bool ShowCount
|
||||
{
|
||||
get => showCount;
|
||||
set
|
||||
{
|
||||
showCount = value;
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// THe maximum number of items shown on the screen.
|
||||
/// </summary>
|
||||
public int MaxItems
|
||||
{
|
||||
get => maxItems;
|
||||
set
|
||||
{
|
||||
if (value == maxItems)
|
||||
{
|
||||
return;
|
||||
}
|
||||
maxItems = value;
|
||||
UpdateItems();
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The colors shown on this Panel.
|
||||
/// </summary>
|
||||
public List<NativeColorData> Colors { get; } = new List<NativeColorData>();
|
||||
/// <summary>
|
||||
/// The sound played when the item is changed.
|
||||
/// </summary>
|
||||
public Sound Sound { get; set; } = DefaultSound;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a color panel with no Items or Title.
|
||||
/// </summary>
|
||||
public NativeColorPanel() : this(string.Empty)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a Panel with a specific Title and set of Colors.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the panel.</param>
|
||||
/// <param name="colors">The colors of the panel.</param>
|
||||
public NativeColorPanel(string title, params NativeColorData[] colors)
|
||||
{
|
||||
// Set the title of the Panel
|
||||
Title = title;
|
||||
// Add the colors that we got
|
||||
Colors.AddRange(colors);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Text of the Title.
|
||||
/// </summary>
|
||||
private void UpdateTitle()
|
||||
{
|
||||
string newTitle = string.Empty;
|
||||
|
||||
// Add the title based on the correct style
|
||||
switch (titleStyle)
|
||||
{
|
||||
case ColorTitleStyle.Simple:
|
||||
newTitle = Title;
|
||||
break;
|
||||
case ColorTitleStyle.ColorName:
|
||||
newTitle = SelectedItem == null ? string.Empty : SelectedItem.Name;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we need to add the count of colors
|
||||
if (ShowCount)
|
||||
{
|
||||
// Add a space at the end if required
|
||||
if (!newTitle.EndsWith(" "))
|
||||
{
|
||||
newTitle += " ";
|
||||
}
|
||||
|
||||
// And add the item count
|
||||
newTitle += $"({SelectedIndex + 1} of {Colors.Count})";
|
||||
}
|
||||
|
||||
// And finally set the new title
|
||||
title.Text = newTitle;
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the position of the Items.
|
||||
/// </summary>
|
||||
private void UpdateItems()
|
||||
{
|
||||
// See UpdateItemList() on LemonUI.Menus.NativeMenu to understand this section
|
||||
List<NativeColorData> list = new List<NativeColorData>();
|
||||
|
||||
for (int i = 0; i < MaxItems; i++)
|
||||
{
|
||||
int start = firstItem + i;
|
||||
|
||||
if (start >= Colors.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
list.Add(Colors[start]);
|
||||
}
|
||||
|
||||
visibleItems = list;
|
||||
|
||||
// Get the width based on the maximum number of items
|
||||
float width = (lastWidth - leftDifference - rightDifference) / maxItems;
|
||||
// And store the number of items already completed
|
||||
int count = 0;
|
||||
|
||||
// Select the correct extra distance based on the prescence of the Opacity toggle
|
||||
float extra = ShowOpacity ? 78 : 0;
|
||||
|
||||
// Then, start iterating over the colors visible on the screen
|
||||
foreach (NativeColorData color in visibleItems)
|
||||
{
|
||||
// Set the position based on the number of items completed
|
||||
color.rectangle.Position = new PointF(lastPosition.X + leftDifference + (width * count), lastPosition.Y + extra + 54);
|
||||
// And set the size of it based on the number of items
|
||||
color.rectangle.Size = new SizeF(width, 45);
|
||||
// Finally, increase the count by one
|
||||
count++;
|
||||
}
|
||||
|
||||
// If there is a selected color item
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
// Set the position and size of the selection rectangle based on the currently selected color
|
||||
ScaledRectangle colorRect = SelectedItem.rectangle;
|
||||
const float height = 8;
|
||||
selectionRectangle.Position = new PointF(colorRect.Position.X, colorRect.Position.Y - height);
|
||||
selectionRectangle.Size = new SizeF(colorRect.Size.Width, height);
|
||||
}
|
||||
|
||||
// Finally, update the text of the title
|
||||
UpdateTitle();
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the size of the opacity bar.
|
||||
/// </summary>
|
||||
private void UpdateOpacityBar()
|
||||
{
|
||||
// If the opacity bar is disabled, return
|
||||
if (!ShowOpacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, set the size based in the last known position
|
||||
float x = lastPosition.X + 13;
|
||||
float y = lastPosition.Y + 48;
|
||||
float width = lastWidth - leftDifference - rightDifference;
|
||||
const float height = 9;
|
||||
opacityBackground.Position = new PointF(x, y);
|
||||
opacityBackground.Size = new SizeF(width, height);
|
||||
opacityForeground.Position = new PointF(x, y);
|
||||
opacityForeground.Size = new SizeF(width * (Opacity * 0.01f), height);
|
||||
}
|
||||
/// <summary>
|
||||
/// Recalculates the Color panel with the last known Position and Width.
|
||||
/// </summary>
|
||||
private void Recalculate() => Recalculate(lastPosition, lastWidth);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the Previous Color.
|
||||
/// </summary>
|
||||
public void Previous()
|
||||
{
|
||||
// If there are no items, return
|
||||
if (Colors.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are on the first item, go back to the last one
|
||||
if (SelectedIndex == 0)
|
||||
{
|
||||
SelectedIndex = Colors.Count - 1;
|
||||
}
|
||||
// Otherwise, reduce it by one
|
||||
else
|
||||
{
|
||||
SelectedIndex -= 1;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Moves to the Next Color.
|
||||
/// </summary>
|
||||
public void Next()
|
||||
{
|
||||
// If there are no items, return
|
||||
if (Colors.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are on the last item, go back to the first one
|
||||
if (Colors.Count - 1 == SelectedIndex)
|
||||
{
|
||||
SelectedIndex = 0;
|
||||
}
|
||||
// Otherwise, increase it by one
|
||||
else
|
||||
{
|
||||
SelectedIndex += 1;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a color to the Panel.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to add.</param>
|
||||
public void Add(NativeColorData color)
|
||||
{
|
||||
if (Colors.Contains(color))
|
||||
{
|
||||
throw new ArgumentException("Color is already part of the Panel.", nameof(color));
|
||||
}
|
||||
Colors.Add(color);
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a color from the panel.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to remove.</param>
|
||||
public void Remove(NativeColorData color)
|
||||
{
|
||||
// Remove it if there
|
||||
// If not, ignore it
|
||||
Colors.Remove(color);
|
||||
// If the index is higher or equal than the max number of items
|
||||
// Set the max - 1
|
||||
if (SelectedIndex >= Colors.Count)
|
||||
{
|
||||
SelectedIndex = Colors.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateItems();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the
|
||||
/// </summary>
|
||||
/// <param name="func"></param>
|
||||
public void Remove(Func<NativeColorData, bool> func)
|
||||
{
|
||||
foreach (NativeColorData color in new List<NativeColorData>(Colors))
|
||||
{
|
||||
if (func(color))
|
||||
{
|
||||
Colors.Remove(color);
|
||||
}
|
||||
}
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the colors from the Panel.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Colors.Clear();
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks if the Color Data is present on this Panel.
|
||||
/// </summary>
|
||||
/// <param name="color">The Color Data to check.</param>
|
||||
public void Contains(NativeColorData color) => Colors.Contains(color);
|
||||
/// <summary>
|
||||
/// Recalculates the position of the Color Panel.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the panel.</param>
|
||||
/// <param name="width">The width of the menu.</param>
|
||||
public override void Recalculate(PointF position, float width)
|
||||
{
|
||||
// Save the last position and width
|
||||
lastPosition = position;
|
||||
lastWidth = width;
|
||||
|
||||
// Select the correct extra distance based on the prescence of the Opacity toggle
|
||||
float extra = ShowOpacity ? 78 : 0;
|
||||
|
||||
// Set the position and size of the Background
|
||||
Background.Position = position;
|
||||
Background.Size = new SizeF(width, ShowOpacity ? 188 : 111);
|
||||
// And then set the position of the text
|
||||
title.Position = new PointF(position.X + (width * 0.5f), position.Y + extra + 10f);
|
||||
// Then, set the position of the opacity bar and texts
|
||||
UpdateOpacityBar();
|
||||
opacityText.Position = new PointF(position.X + (width * 0.5f), position.Y + 10f);
|
||||
percentMin.Position = new PointF(position.X + 9, position.Y + 11);
|
||||
percentMax.Position = new PointF(position.X + width - 60, position.Y + 11);
|
||||
|
||||
// Finally, update the list of items
|
||||
UpdateItems();
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the Color Panel.
|
||||
/// </summary>
|
||||
public override void Process()
|
||||
{
|
||||
// If the user pressed one of the keys, move to the left or right
|
||||
if (Controls.IsJustPressed(Control.FrontendLt))
|
||||
{
|
||||
Previous();
|
||||
}
|
||||
else if (Controls.IsJustPressed(Control.FrontendRt))
|
||||
{
|
||||
Next();
|
||||
}
|
||||
// If the user pressed one of the bumpers with the Opacity bar enabled, increase or decrease it
|
||||
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendLb))
|
||||
{
|
||||
if (Opacity > 0)
|
||||
{
|
||||
Opacity--;
|
||||
Sound?.PlayFrontend();
|
||||
}
|
||||
}
|
||||
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendRb))
|
||||
{
|
||||
if (Opacity < 100)
|
||||
{
|
||||
Opacity++;
|
||||
Sound?.PlayFrontend();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the items
|
||||
base.Process();
|
||||
title.Draw();
|
||||
foreach (NativeColorData color in visibleItems)
|
||||
{
|
||||
color.rectangle.Draw();
|
||||
}
|
||||
if (Colors.Count != 0)
|
||||
{
|
||||
selectionRectangle.Draw();
|
||||
}
|
||||
if (ShowOpacity)
|
||||
{
|
||||
opacityText.Draw();
|
||||
percentMin.Draw();
|
||||
percentMax.Draw();
|
||||
opacityBackground.Draw();
|
||||
opacityForeground.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Dynamic Items allow you to dynamically change the item shown to the user.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of item.</typeparam>
|
||||
public class NativeDynamicItem<T> : NativeSlidableItem
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly ScaledText text = new ScaledText(PointF.Empty, string.Empty, 0.35f);
|
||||
private T item = default;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The currently selected item.
|
||||
/// </summary>
|
||||
public T SelectedItem
|
||||
{
|
||||
get => item;
|
||||
set
|
||||
{
|
||||
item = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the user has changed the item.
|
||||
/// </summary>
|
||||
public event ItemChangedEventHandler<T> ItemChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Dynamic List Item.
|
||||
/// </summary>
|
||||
/// <param name="title">The Title of the item.</param>
|
||||
public NativeDynamicItem(string title) : this(title, string.Empty, default)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new Dynamic List Item.
|
||||
/// </summary>
|
||||
/// <param name="title">The Title of the item.</param>
|
||||
/// <param name="item">The Item to set.</param>
|
||||
public NativeDynamicItem(string title, T item) : this(title, string.Empty, item)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new Dynamic List Item.
|
||||
/// </summary>
|
||||
/// <param name="title">The Title of the item.</param>
|
||||
/// <param name="description">The Description of the item.</param>
|
||||
public NativeDynamicItem(string title, string description) : this(title, description, default)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new Dynamic List Item.
|
||||
/// </summary>
|
||||
/// <param name="title">The Title of the item.</param>
|
||||
/// <param name="description">The Description of the item.</param>
|
||||
/// <param name="item">The Item to set.</param>
|
||||
public NativeDynamicItem(string title, string description, T item) : base(title, description)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <summary>
|
||||
/// Updates the currently selected item based on the index.
|
||||
/// </summary>
|
||||
private void UpdateItemName()
|
||||
{
|
||||
// This is the SAME as the normal NativeListItem
|
||||
|
||||
text.Text = !SelectedItem.Equals(default) ? SelectedItem.ToString() : string.Empty;
|
||||
|
||||
text.Position = new PointF(RightArrow.Position.X - text.Width + 3, text.Position.Y);
|
||||
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, LeftArrow.Position.Y);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the previous item.
|
||||
/// </summary>
|
||||
public override void GoLeft()
|
||||
{
|
||||
ItemChangedEventArgs<T> arguments = new ItemChangedEventArgs<T>(item, -1, Direction.Left);
|
||||
ItemChanged?.Invoke(this, arguments);
|
||||
SelectedItem = arguments.Object;
|
||||
UpdateItemName();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the next item.
|
||||
/// </summary>
|
||||
public override void GoRight()
|
||||
{
|
||||
ItemChangedEventArgs<T> arguments = new ItemChangedEventArgs<T>(item, -1, Direction.Right);
|
||||
ItemChanged?.Invoke(this, arguments);
|
||||
SelectedItem = arguments.Object;
|
||||
UpdateItemName();
|
||||
}
|
||||
/// <summary>
|
||||
/// Recalculates the position of the current List Item.
|
||||
/// </summary>
|
||||
/// <param name="pos">The new position of the item.</param>
|
||||
/// <param name="size">The Size of the item.</param>
|
||||
/// <param name="selected">If the item is selected or not.</param>
|
||||
public override void Recalculate(PointF pos, SizeF size, bool selected)
|
||||
{
|
||||
// This is the SAME as the normal NativeListItem
|
||||
|
||||
base.Recalculate(pos, size, selected);
|
||||
|
||||
float textWidth = RightArrow.Size.Width;
|
||||
text.Position = new PointF(pos.X + size.Width - textWidth - 1 - text.Width, pos.Y + 3);
|
||||
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, pos.Y + 4);
|
||||
|
||||
UpdateItemName();
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the List on the screen.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw(); // Arrows, Title and Left Badge
|
||||
text.Draw();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateColors()
|
||||
{
|
||||
base.UpdateColors();
|
||||
|
||||
if (!Enabled)
|
||||
{
|
||||
text.Color = Colors.TitleDisabled;
|
||||
}
|
||||
else if (lastSelected)
|
||||
{
|
||||
text.Color = Colors.TitleHovered;
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Color = Colors.TitleNormal;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,371 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.Native;
|
||||
using CitizenFX.Core.UI;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage;
|
||||
using Rage.Native;
|
||||
using Control = Rage.GameControl;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
#endif
|
||||
using LemonUI.Elements;
|
||||
using LemonUI.Extensions;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a grid where you can select X and Y values.
|
||||
/// </summary>
|
||||
public class NativeGridPanel : NativePanel
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private PointF position = PointF.Empty;
|
||||
private float width = 0;
|
||||
|
||||
private readonly ScaledText labelTop = new ScaledText(PointF.Empty, "Y+", 0.33f)
|
||||
{
|
||||
Alignment = Alignment.Center
|
||||
};
|
||||
private readonly ScaledText labelBottom = new ScaledText(PointF.Empty, "Y-", 0.33f)
|
||||
{
|
||||
Alignment = Alignment.Center
|
||||
};
|
||||
private readonly ScaledText labelLeft = new ScaledText(PointF.Empty, "X-", 0.33f)
|
||||
{
|
||||
Alignment = Alignment.Right
|
||||
};
|
||||
private readonly ScaledText labelRight = new ScaledText(PointF.Empty, "X+", 0.33f);
|
||||
private readonly ScaledTexture grid = new ScaledTexture("pause_menu_pages_char_mom_dad", "nose_grid")
|
||||
{
|
||||
Color = Color.FromArgb(205, 105, 105, 102)
|
||||
};
|
||||
private readonly ScaledTexture dot = new ScaledTexture("commonmenu", "common_medal")
|
||||
{
|
||||
Color = Color.FromArgb(255, 255, 255, 255)
|
||||
};
|
||||
private PointF innerPosition = PointF.Empty;
|
||||
private SizeF innerSize = SizeF.Empty;
|
||||
private GridStyle style = GridStyle.Full;
|
||||
private float x;
|
||||
private float y;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Clickable => true;
|
||||
/// <summary>
|
||||
/// The X value between 0 and 1.
|
||||
/// </summary>
|
||||
public float X
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case GridStyle.Full:
|
||||
case GridStyle.Row:
|
||||
return x;
|
||||
default:
|
||||
return 0.5f;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 1 || value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
if (style == GridStyle.Column)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PointF before = new PointF(X, Y);
|
||||
x = value;
|
||||
UpdateDot(before);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The X value between 0 and 1.
|
||||
/// </summary>
|
||||
public float Y
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (style)
|
||||
{
|
||||
case GridStyle.Full:
|
||||
case GridStyle.Column:
|
||||
return y;
|
||||
default:
|
||||
return 0.5f;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 1 || value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
if (style == GridStyle.Row)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PointF before = new PointF(X, Y);
|
||||
y = value;
|
||||
UpdateDot(before);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The text label shown on the top.
|
||||
/// </summary>
|
||||
public string LabelTop
|
||||
{
|
||||
get => labelTop.Text;
|
||||
set => labelTop.Text = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The text label shown on the bottom.
|
||||
/// </summary>
|
||||
public string LabelBottom
|
||||
{
|
||||
get => labelBottom.Text;
|
||||
set => labelBottom.Text = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The text label shown on the left.
|
||||
/// </summary>
|
||||
public string LabelLeft
|
||||
{
|
||||
get => labelLeft.Text;
|
||||
set => labelLeft.Text = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The text label shown on the right.
|
||||
/// </summary>
|
||||
public string LabelRight
|
||||
{
|
||||
get => labelRight.Text;
|
||||
set => labelRight.Text = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The style of this grid.
|
||||
/// </summary>
|
||||
public GridStyle Style
|
||||
{
|
||||
get => style;
|
||||
set
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(GridStyle), value))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The Grid style is not valid! Expected Full, Row or Column.");
|
||||
}
|
||||
|
||||
style = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when X and/or Y values are changed.
|
||||
/// </summary>
|
||||
public event GridValueChangedEventHandler ValuesChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeGridPanel"/>.
|
||||
/// </summary>
|
||||
public NativeGridPanel() : base()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
private void Recalculate() => Recalculate(position, width);
|
||||
private void UpdateDot(PointF before, bool trigger = true)
|
||||
{
|
||||
float posX = innerSize.Width * (style == GridStyle.Full || style == GridStyle.Row ? x : 0.5f);
|
||||
float posY = innerSize.Height * (style == GridStyle.Full || style == GridStyle.Column ? y : 0.5f);
|
||||
|
||||
dot.Size = new SizeF(45, 45);
|
||||
dot.Position = new PointF(innerPosition.X + posX - (dot.Size.Width * 0.5f),
|
||||
innerPosition.Y + posY - (dot.Size.Height * 0.5f));
|
||||
|
||||
if (trigger)
|
||||
{
|
||||
ValuesChanged?.Invoke(this, new GridValueChangedArgs(before, new PointF(X, Y)));
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void Recalculate(PointF position, float width)
|
||||
{
|
||||
this.position = position;
|
||||
this.width = width;
|
||||
|
||||
const float height = 270;
|
||||
const int offsetX = 20;
|
||||
const int offsetY = 20;
|
||||
|
||||
base.Recalculate(position, width);
|
||||
Background.Size = new SizeF(width, height);
|
||||
|
||||
switch (style)
|
||||
{
|
||||
case GridStyle.Full:
|
||||
grid.Position = new PointF(position.X + (width * 0.5f) - 95, position.Y + (height * 0.5f) - 94);
|
||||
grid.Size = new SizeF(192, 192);
|
||||
break;
|
||||
case GridStyle.Row:
|
||||
grid.Position = new PointF(position.X + (width * 0.5f) - 95, position.Y + (height * 0.5f) - 15);
|
||||
grid.Size = new SizeF(192, 36);
|
||||
break;
|
||||
case GridStyle.Column:
|
||||
grid.Position = new PointF(position.X + (width * 0.5f) - 17, position.Y + (height * 0.5f) - 94);
|
||||
grid.Size = new SizeF(36, 192);
|
||||
break;
|
||||
}
|
||||
|
||||
labelTop.Position = new PointF(position.X + (width * 0.5f), position.Y + 10);
|
||||
labelBottom.Position = new PointF(position.X + (width * 0.5f), position.Y + height - 34);
|
||||
labelLeft.Position = new PointF(position.X + (width * 0.5f) - 102, position.Y + (height * 0.5f) - (labelLeft.LineHeight * 0.5f));
|
||||
labelRight.Position = new PointF(position.X + (width * 0.5f) + 102, position.Y + (height * 0.5f) - (labelLeft.LineHeight * 0.5f));
|
||||
|
||||
innerPosition = new PointF(grid.Position.X + offsetX, grid.Position.Y + offsetY);
|
||||
innerSize = new SizeF(grid.Size.Width - (offsetX * 2), grid.Size.Height - (offsetY * 2));
|
||||
|
||||
UpdateDot(PointF.Empty, false);
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void Process()
|
||||
{
|
||||
float previousX = X;
|
||||
float previousY = Y;
|
||||
|
||||
Background.Draw();
|
||||
switch (style)
|
||||
{
|
||||
case GridStyle.Full:
|
||||
labelTop.Draw();
|
||||
labelBottom.Draw();
|
||||
labelLeft.Draw();
|
||||
labelRight.Draw();
|
||||
grid.Draw();
|
||||
break;
|
||||
case GridStyle.Row:
|
||||
labelLeft.Draw();
|
||||
labelRight.Draw();
|
||||
grid.DrawSpecific(new PointF(0, 0.4f), new PointF(1, 0.6f));
|
||||
break;
|
||||
case GridStyle.Column:
|
||||
labelTop.Draw();
|
||||
labelBottom.Draw();
|
||||
grid.DrawSpecific(new PointF(0.4f, 0), new PointF(0.6f, 1));
|
||||
break;
|
||||
}
|
||||
dot.Draw();
|
||||
|
||||
#if FIVEM
|
||||
bool usingKeyboard = API.IsInputDisabled(2);
|
||||
#elif RAGEMP
|
||||
bool usingKeyboard = Invoker.Invoke<bool>(0xA571D46727E2B718, 2);
|
||||
#elif RPH
|
||||
bool usingKeyboard = NativeFunction.CallByHash<bool>(0xA571D46727E2B718, 2);
|
||||
#elif SHVDN3
|
||||
bool usingKeyboard = Function.Call<bool>(Hash._IS_USING_KEYBOARD, 2);
|
||||
#endif
|
||||
if (usingKeyboard)
|
||||
{
|
||||
if (Screen.IsCursorInArea(grid.Position, grid.Size) && Controls.IsPressed(Control.CursorAccept))
|
||||
{
|
||||
PointF cursor = Screen.CursorPositionRelative;
|
||||
PointF pos = innerPosition.ToRelative();
|
||||
|
||||
PointF start = new PointF(cursor.X - pos.X, cursor.Y - pos.Y);
|
||||
SizeF size = innerSize.ToRelative();
|
||||
|
||||
x = start.X / size.Width;
|
||||
y = start.Y / size.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Controls.DisableThisFrame(Control.LookUpDown);
|
||||
Controls.DisableThisFrame(Control.LookLeftRight);
|
||||
Controls.EnableThisFrame(Control.ScriptRightAxisX);
|
||||
Controls.EnableThisFrame(Control.ScriptRightAxisY);
|
||||
|
||||
#if FIVEM
|
||||
float rX = Game.GetControlNormal(0, Control.ScriptRightAxisX);
|
||||
float rY = Game.GetControlNormal(0, Control.ScriptRightAxisY);
|
||||
float frameTime = Game.LastFrameTime;
|
||||
#elif RAGEMP
|
||||
float rX = Invoker.Invoke<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisX);
|
||||
float rY = Invoker.Invoke<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisY);
|
||||
float frameTime = Invoker.Invoke<float>(Natives.GetFrameTime);
|
||||
#elif RPH
|
||||
float rX = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisX);
|
||||
float rY = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisY);
|
||||
float frameTime = Game.FrameTime;
|
||||
#elif SHVDN3
|
||||
float rX = Game.GetControlValueNormalized(Control.ScriptRightAxisX);
|
||||
float rY = Game.GetControlValueNormalized(Control.ScriptRightAxisY);
|
||||
float frameTime = Game.LastFrameTime;
|
||||
#endif
|
||||
|
||||
x += rX * frameTime;
|
||||
y += rY * frameTime;
|
||||
}
|
||||
|
||||
// Make sure that the values are not under zero or over one
|
||||
if (x < 0)
|
||||
{
|
||||
x = 0;
|
||||
}
|
||||
else if (x > 1)
|
||||
{
|
||||
x = 1;
|
||||
}
|
||||
if (y < 0)
|
||||
{
|
||||
y = 0;
|
||||
}
|
||||
else if (y > 1)
|
||||
{
|
||||
y = 1;
|
||||
}
|
||||
|
||||
if (previousX != x || previousY != y)
|
||||
{
|
||||
UpdateDot(new PointF(previousX, previousX));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
#if FIVEM
|
||||
using Font = CitizenFX.Core.UI.Font;
|
||||
#elif RAGEMP
|
||||
using Font = RAGE.Game.Font;
|
||||
#elif RPH
|
||||
using Font = LemonUI.Elements.Font;
|
||||
#elif SHVDN3
|
||||
using Font = GTA.UI.Font;
|
||||
#endif
|
||||
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic Rockstar-like item.
|
||||
/// </summary>
|
||||
public class NativeItem : IDrawable
|
||||
{
|
||||
#region Protected Internal Fields
|
||||
|
||||
/// <summary>
|
||||
/// The title of the object.
|
||||
/// </summary>
|
||||
protected internal ScaledText title = null;
|
||||
/// <summary>
|
||||
/// The last known Item Position.
|
||||
/// </summary>
|
||||
protected internal PointF lastPosition = PointF.Empty;
|
||||
/// <summary>
|
||||
/// The last known Item Size.
|
||||
/// </summary>
|
||||
protected internal SizeF lastSize = SizeF.Empty;
|
||||
/// <summary>
|
||||
/// The last known Item Selection.
|
||||
/// </summary>
|
||||
protected internal bool lastSelected = false;
|
||||
/// <summary>
|
||||
/// The left badge of the Item.
|
||||
/// </summary>
|
||||
protected internal I2Dimensional badgeLeft = null;
|
||||
/// <summary>
|
||||
/// The left badge of the Item.
|
||||
/// </summary>
|
||||
protected internal I2Dimensional badgeRight = null;
|
||||
/// <summary>
|
||||
/// The alternate title of the menu.
|
||||
/// </summary>
|
||||
protected internal ScaledText altTitle = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private bool enabled = true;
|
||||
private BadgeSet badgeSetLeft = null;
|
||||
private BadgeSet badgeSetRight = null;
|
||||
private ColorSet colors = new ColorSet();
|
||||
private ScaledRectangle background = new ScaledRectangle(PointF.Empty, SizeF.Empty);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// If this item can be used or not.
|
||||
/// </summary>
|
||||
public bool Enabled
|
||||
{
|
||||
get => enabled;
|
||||
set
|
||||
{
|
||||
if (enabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
enabled = value;
|
||||
EnabledChanged?.Invoke(this, EventArgs.Empty);
|
||||
UpdateColors();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Object that contains data about this Item.
|
||||
/// </summary>
|
||||
public virtual object Tag { get; set; }
|
||||
/// <summary>
|
||||
/// The title of the item.
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => title.Text;
|
||||
set => title.Text = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The alternative title of the item shown on the right.
|
||||
/// </summary>
|
||||
public string AltTitle
|
||||
{
|
||||
get => altTitle.Text;
|
||||
set
|
||||
{
|
||||
altTitle.Text = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The font of title item.
|
||||
/// </summary>
|
||||
public Font TitleFont
|
||||
{
|
||||
get => title.Font;
|
||||
set => title.Font = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The font of alternative title item shown on the right.
|
||||
/// </summary>
|
||||
public Font AltTitleFont
|
||||
{
|
||||
get => altTitle.Font;
|
||||
set => altTitle.Font = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The description of the item.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// The Left badge of the Item.
|
||||
/// </summary>
|
||||
public I2Dimensional LeftBadge
|
||||
{
|
||||
get => badgeLeft;
|
||||
set
|
||||
{
|
||||
badgeLeft = value;
|
||||
Recalculate();
|
||||
UpdateColors();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Left badge set of the Item.
|
||||
/// </summary>
|
||||
public BadgeSet LeftBadgeSet
|
||||
{
|
||||
get => badgeSetLeft;
|
||||
set
|
||||
{
|
||||
badgeSetLeft = value;
|
||||
Recalculate();
|
||||
UpdateColors();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Right badge of the Item.
|
||||
/// </summary>
|
||||
public I2Dimensional RightBadge
|
||||
{
|
||||
get => badgeRight;
|
||||
set
|
||||
{
|
||||
badgeRight = value;
|
||||
Recalculate();
|
||||
UpdateColors();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Right badge set of the Item.
|
||||
/// </summary>
|
||||
public BadgeSet RightBadgeSet
|
||||
{
|
||||
get => badgeSetRight;
|
||||
set
|
||||
{
|
||||
badgeSetRight = value;
|
||||
Recalculate();
|
||||
UpdateColors();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The different colors that change dynamically when the item is used.
|
||||
/// </summary>
|
||||
public ColorSet Colors
|
||||
{
|
||||
get => colors;
|
||||
set
|
||||
{
|
||||
colors = value;
|
||||
UpdateColors();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Panel asociated to this <see cref="NativeItem"/>.
|
||||
/// </summary>
|
||||
public NativePanel Panel { get; set; } = null;
|
||||
/// <summary>
|
||||
/// If a custom colored background should be used.
|
||||
/// </summary>
|
||||
public bool UseCustomBackground { get; set; }
|
||||
/// <summary>
|
||||
/// If this item is being hovered.
|
||||
/// </summary>
|
||||
public bool IsHovered => Screen.IsCursorInArea(background.Position, background.Size);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the item is selected.
|
||||
/// </summary>
|
||||
public event SelectedEventHandler Selected;
|
||||
/// <summary>
|
||||
/// Event triggered when the item is activated.
|
||||
/// </summary>
|
||||
public event EventHandler Activated;
|
||||
/// <summary>
|
||||
/// Event triggered when the <see cref="Enabled"/> property is changed.
|
||||
/// </summary>
|
||||
public event EventHandler EnabledChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the item.</param>
|
||||
public NativeItem(string title) : this(title, string.Empty, string.Empty)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the item.</param>
|
||||
/// <param name="description">The description of the item.</param>
|
||||
public NativeItem(string title, string description) : this(title, description, string.Empty)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the item.</param>
|
||||
/// <param name="description">The description of the item.</param>
|
||||
/// <param name="altTitle">The alternative title of the item, shown on the right.</param>
|
||||
public NativeItem(string title, string description, string altTitle)
|
||||
{
|
||||
this.title = new ScaledText(PointF.Empty, title, 0.345f);
|
||||
Description = description;
|
||||
this.altTitle = new ScaledText(PointF.Empty, altTitle, 0.345f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Triggers
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the Selected event.
|
||||
/// </summary>
|
||||
protected internal void OnSelected(object sender, SelectedEventArgs e) => Selected?.Invoke(sender, e);
|
||||
/// <summary>
|
||||
/// Triggers the Activated event.
|
||||
/// </summary>
|
||||
protected internal void OnActivated(object sender) => Activated?.Invoke(sender, EventArgs.Empty);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the item with the last known values.
|
||||
/// </summary>
|
||||
protected void Recalculate() => Recalculate(lastPosition, lastSize, lastSelected);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the item positions and sizes with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the item.</param>
|
||||
/// <param name="size">The size of the item.</param>
|
||||
/// <param name="selected">If this item has been selected.</param>
|
||||
public virtual void Recalculate(PointF pos, SizeF size, bool selected)
|
||||
{
|
||||
lastPosition = pos;
|
||||
lastSize = size;
|
||||
lastSelected = selected;
|
||||
|
||||
background.Position = pos;
|
||||
background.Size = size;
|
||||
|
||||
if (badgeSetLeft != null)
|
||||
{
|
||||
if (!(badgeLeft is ScaledTexture))
|
||||
{
|
||||
badgeLeft = new ScaledTexture(string.Empty, string.Empty);
|
||||
}
|
||||
ScaledTexture left = (ScaledTexture)badgeLeft;
|
||||
left.Dictionary = selected ? badgeSetLeft.HoveredDictionary : badgeSetLeft.NormalDictionary;
|
||||
left.Texture = selected ? badgeSetLeft.HoveredTexture : badgeSetLeft.NormalTexture;
|
||||
}
|
||||
if (badgeSetRight != null)
|
||||
{
|
||||
if (!(badgeRight is ScaledTexture))
|
||||
{
|
||||
badgeRight = new ScaledTexture(string.Empty, string.Empty);
|
||||
}
|
||||
ScaledTexture right = (ScaledTexture)badgeRight;
|
||||
right.Dictionary = selected ? badgeSetRight.HoveredDictionary : badgeSetRight.NormalDictionary;
|
||||
right.Texture = selected ? badgeSetRight.HoveredTexture : badgeSetRight.NormalTexture;
|
||||
}
|
||||
|
||||
if (badgeLeft != null)
|
||||
{
|
||||
badgeLeft.Position = new PointF(pos.X + 2, pos.Y - 3);
|
||||
badgeLeft.Size = new SizeF(45, 45);
|
||||
}
|
||||
if (badgeRight != null)
|
||||
{
|
||||
badgeRight.Position = new PointF(pos.X + size.Width - 47, pos.Y - 3);
|
||||
badgeRight.Size = new SizeF(45, 45);
|
||||
}
|
||||
|
||||
title.Position = new PointF(pos.X + (badgeLeft == null ? 0 : 34) + 6, pos.Y + 3);
|
||||
altTitle.Position = new PointF(pos.X + size.Width - (badgeRight == null ? 0 : 34) - altTitle.Width - 6, pos.Y + 3);
|
||||
|
||||
UpdateColors();
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the item.
|
||||
/// </summary>
|
||||
public virtual void Draw()
|
||||
{
|
||||
if (UseCustomBackground)
|
||||
{
|
||||
background.Draw();
|
||||
}
|
||||
|
||||
title.Draw();
|
||||
altTitle.Draw();
|
||||
badgeLeft?.Draw();
|
||||
badgeRight?.Draw();
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the colors of the <see cref="Elements"/> from the <see cref="Colors"/> <see cref="ColorSet"/>.
|
||||
/// </summary>
|
||||
public virtual void UpdateColors()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
background.Color = Colors.BackgroundDisabled;
|
||||
title.Color = Colors.TitleDisabled;
|
||||
altTitle.Color = Colors.AltTitleDisabled;
|
||||
if (badgeLeft != null)
|
||||
{
|
||||
badgeLeft.Color = Colors.BadgeLeftDisabled;
|
||||
}
|
||||
if (badgeRight != null)
|
||||
{
|
||||
badgeRight.Color = Colors.BadgeRightDisabled;
|
||||
}
|
||||
}
|
||||
else if (lastSelected)
|
||||
{
|
||||
background.Color = Colors.BackgroundHovered;
|
||||
title.Color = Colors.TitleHovered;
|
||||
altTitle.Color = Colors.AltTitleHovered;
|
||||
if (badgeLeft != null)
|
||||
{
|
||||
badgeLeft.Color = Colors.BadgeLeftHovered;
|
||||
}
|
||||
if (badgeRight != null)
|
||||
{
|
||||
badgeRight.Color = Colors.BadgeRightHovered;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
background.Color = Colors.BackgroundNormal;
|
||||
title.Color = Colors.TitleNormal;
|
||||
altTitle.Color = Colors.AltTitleNormal;
|
||||
if (badgeLeft != null)
|
||||
{
|
||||
badgeLeft.Color = Colors.BadgeLeftNormal;
|
||||
}
|
||||
if (badgeRight != null)
|
||||
{
|
||||
badgeRight.Color = Colors.BadgeRightNormal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for list items.
|
||||
/// </summary>
|
||||
public abstract class NativeListItem : NativeSlidableItem
|
||||
{
|
||||
/// <summary>
|
||||
/// The text of the current item.
|
||||
/// </summary>
|
||||
internal protected ScaledText text = null;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new list item with a title and subtitle.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
/// <param name="subtitle">The subtitle of the Item.</param>
|
||||
public NativeListItem(string title, string subtitle) : base(title, subtitle)
|
||||
{
|
||||
text = new ScaledText(PointF.Empty, string.Empty, 0.35f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An item that allows you to scroll between a set of objects.
|
||||
/// </summary>
|
||||
public class NativeListItem<T> : NativeListItem
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private int index = 0;
|
||||
private List<T> items = new List<T>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The index of the currently selected index.
|
||||
/// </summary>
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Items.Count == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Items.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("There are no available items.");
|
||||
}
|
||||
if (value < 0)
|
||||
{
|
||||
throw new InvalidOperationException("The index is under zero.");
|
||||
}
|
||||
if (value >= Items.Count)
|
||||
{
|
||||
throw new InvalidOperationException($"The index is over the limit of {Items.Count - 1}");
|
||||
}
|
||||
if (index == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
index = value;
|
||||
TriggerEvent(value, Direction.Unknown);
|
||||
UpdateIndex();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The currently selected item.
|
||||
/// </summary>
|
||||
public T SelectedItem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Items.Count == 0)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return Items[SelectedIndex];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (Items.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("There are no available items.");
|
||||
}
|
||||
|
||||
int newIndex = Items.IndexOf(value);
|
||||
|
||||
if (newIndex == -1)
|
||||
{
|
||||
throw new InvalidOperationException("The object is not the list of Items.");
|
||||
}
|
||||
|
||||
SelectedIndex = newIndex;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The objects used by this item.
|
||||
/// </summary>
|
||||
public List<T> Items
|
||||
{
|
||||
get => items;
|
||||
set
|
||||
{
|
||||
items = value;
|
||||
UpdateIndex();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the selected item is changed.
|
||||
/// </summary>
|
||||
public event ItemChangedEventHandler<T> ItemChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeListItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
/// <param name="objs">The objects that are available on the Item.</param>
|
||||
public NativeListItem(string title, params T[] objs) : this(title, string.Empty, objs)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NativeListItem"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
/// <param name="subtitle">The subtitle of the Item.</param>
|
||||
/// <param name="objs">The objects that are available on the Item.</param>
|
||||
public NativeListItem(string title, string subtitle, params T[] objs) : base(title, subtitle)
|
||||
{
|
||||
items = new List<T>();
|
||||
items.AddRange(objs);
|
||||
UpdateIndex();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tools
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the <seealso cref="ItemChangedEventHandler{T}"/> event.
|
||||
/// </summary>
|
||||
private void TriggerEvent(int index, Direction direction)
|
||||
{
|
||||
ItemChanged?.Invoke(this, new ItemChangedEventArgs<T>(items[index], index, direction));
|
||||
}
|
||||
private void FixIndexIfRequired()
|
||||
{
|
||||
if (index >= items.Count)
|
||||
{
|
||||
index = items.Count - 1;
|
||||
UpdateIndex();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the currently selected item based on the index.
|
||||
/// </summary>
|
||||
private void UpdateIndex()
|
||||
{
|
||||
text.Text = SelectedIndex != -1 ? SelectedItem.ToString() : string.Empty;
|
||||
|
||||
text.Position = new PointF(RightArrow.Position.X - text.Width + 3, text.Position.Y);
|
||||
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, LeftArrow.Position.Y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <typeparamref name="T" /> into this item.
|
||||
/// </summary>
|
||||
/// <param name="item">The <typeparamref name="T" /> to add.</param>
|
||||
public void Add(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
if (items.Contains(item))
|
||||
{
|
||||
throw new InvalidOperationException("Item is already part of this NativeListItem.");
|
||||
}
|
||||
|
||||
items.Add(item);
|
||||
|
||||
if (items.Count == 1)
|
||||
{
|
||||
UpdateIndex();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a <typeparamref name="T" /> in a specific location.
|
||||
/// </summary>
|
||||
/// <param name="position">The position where the item should be added.</param>
|
||||
/// <param name="item">The <typeparamref name="T" /> to add.</param>
|
||||
public void Add(int position, T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
if (position < 0 || position > Items.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(position), "The position is out of the range of items.");
|
||||
}
|
||||
|
||||
Items.Insert(position, item);
|
||||
|
||||
FixIndexIfRequired();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a specific <typeparamref name="T" />.
|
||||
/// </summary>
|
||||
/// <param name="item">The <typeparamref name="T" /> to remove.</param>
|
||||
public void Remove(T item)
|
||||
{
|
||||
if (items.Remove(item))
|
||||
{
|
||||
FixIndexIfRequired();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a <typeparamref name="T" /> at a specific location.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the <typeparamref name="T" />.</param>
|
||||
public void RemoveAt(int position)
|
||||
{
|
||||
if (position >= items.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
items.RemoveAt(position);
|
||||
FixIndexIfRequired();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the items that match the <paramref name="pred"/>.
|
||||
/// </summary>
|
||||
/// <param name="pred">The function to use as a check.</param>
|
||||
public void Remove(Func<T, bool> pred)
|
||||
{
|
||||
if (items.RemoveAll(pred.Invoke) > 0)
|
||||
{
|
||||
FixIndexIfRequired();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the <typeparamref name="T" /> from this item.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
items.Clear();
|
||||
|
||||
UpdateIndex();
|
||||
}
|
||||
/// <summary>
|
||||
/// Recalculates the item positions and sizes with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the item.</param>
|
||||
/// <param name="size">The size of the item.</param>
|
||||
/// <param name="selected">If this item has been selected.</param>
|
||||
public override void Recalculate(PointF pos, SizeF size, bool selected)
|
||||
{
|
||||
base.Recalculate(pos, size, selected);
|
||||
|
||||
text.Position = new PointF(pos.X + size.Width - RightArrow.Size.Width - 1 - text.Width, pos.Y + 3);
|
||||
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, pos.Y + 4);
|
||||
}
|
||||
/// <summary>
|
||||
/// Moves to the previous item.
|
||||
/// </summary>
|
||||
public override void GoLeft()
|
||||
{
|
||||
if (Items.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
index = Items.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
index--;
|
||||
}
|
||||
|
||||
TriggerEvent(index, Direction.Left);
|
||||
UpdateIndex();
|
||||
}
|
||||
/// <summary>
|
||||
/// Moves to the next item.
|
||||
/// </summary>
|
||||
public override void GoRight()
|
||||
{
|
||||
if (Items.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == Items.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
TriggerEvent(index, Direction.Right);
|
||||
UpdateIndex();
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the List on the screen.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw(); // Arrows, Title and Left Badge
|
||||
text.Draw();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateColors()
|
||||
{
|
||||
base.UpdateColors();
|
||||
|
||||
if (!Enabled)
|
||||
{
|
||||
text.Color = Colors.TitleDisabled;
|
||||
}
|
||||
else if (lastSelected)
|
||||
{
|
||||
text.Color = Colors.TitleHovered;
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Color = Colors.TitleNormal;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a panel shown under the description of the item description.
|
||||
/// </summary>
|
||||
public abstract class NativePanel
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// If this panel is visible to the user.
|
||||
/// </summary>
|
||||
public virtual bool Visible { get; set; } = true;
|
||||
/// <summary>
|
||||
/// If the item has controls that can be clicked.
|
||||
/// </summary>
|
||||
public virtual bool Clickable { get; } = false;
|
||||
/// <summary>
|
||||
/// The Background of the panel itself.
|
||||
/// </summary>
|
||||
public ScaledTexture Background { get; } = new ScaledTexture("commonmenu", "gradient_bgd");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the menu contents.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the panel.</param>
|
||||
/// <param name="width">The width of the menu.</param>
|
||||
public virtual void Recalculate(PointF position, float width)
|
||||
{
|
||||
Background.Position = position;
|
||||
}
|
||||
/// <summary>
|
||||
/// Processes and Draws the panel.
|
||||
/// </summary>
|
||||
public virtual void Process()
|
||||
{
|
||||
Background.Draw();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// A Blank Separator Item for creating empty spaces between menu items.
|
||||
/// </summary>
|
||||
public class NativeSeparatorItem : NativeItem
|
||||
{
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Menu Separator.
|
||||
/// </summary>
|
||||
public NativeSeparatorItem() : base(string.Empty, string.Empty, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Draws nothing.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Basic elements for a slidable item.
|
||||
/// </summary>
|
||||
public abstract class NativeSlidableItem : NativeItem
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private bool alwaysVisible = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Fields
|
||||
|
||||
/// <summary>
|
||||
/// The arrow pointing to the Left.
|
||||
/// </summary>
|
||||
[Obsolete("arrowLeft is Obsolete, use LeftArrow instead.")]
|
||||
internal protected ScaledTexture arrowLeft = null;
|
||||
/// <summary>
|
||||
/// The arrow pointing to the Right.
|
||||
/// </summary>
|
||||
[Obsolete("arrowRight is Obsolete, use RightArrow instead.")]
|
||||
internal protected ScaledTexture arrowRight = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The arrow pointing to the Left.
|
||||
/// </summary>
|
||||
public ScaledTexture LeftArrow { get; }
|
||||
/// <summary>
|
||||
/// The arrow pointing to the Right.
|
||||
/// </summary>
|
||||
public ScaledTexture RightArrow { get; }
|
||||
/// <summary>
|
||||
/// Whether the arrows should always be shown regardless of the visibility of the Item.
|
||||
/// </summary>
|
||||
public bool ArrowsAlwaysVisible
|
||||
{
|
||||
get => alwaysVisible;
|
||||
set
|
||||
{
|
||||
alwaysVisible = value;
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new item that can be sliden.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
/// <param name="description">The description of the Item.</param>
|
||||
public NativeSlidableItem(string title, string description) : base(title, description)
|
||||
{
|
||||
LeftArrow = new ScaledTexture(PointF.Empty, SizeF.Empty, "commonmenu", "arrowleft");
|
||||
RightArrow = new ScaledTexture(PointF.Empty, SizeF.Empty, "commonmenu", "arrowright");
|
||||
#pragma warning disable CS0618
|
||||
arrowLeft = LeftArrow;
|
||||
arrowRight = RightArrow;
|
||||
#pragma warning restore CS0618
|
||||
EnabledChanged += NativeSlidableItem_EnabledChanged;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local Events
|
||||
|
||||
private void NativeSlidableItem_EnabledChanged(object sender, EventArgs e) => Recalculate();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the item positions and sizes with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the item.</param>
|
||||
/// <param name="size">The size of the item.</param>
|
||||
/// <param name="selected">If this item has been selected.</param>
|
||||
public override void Recalculate(PointF pos, SizeF size, bool selected)
|
||||
{
|
||||
base.Recalculate(pos, size, selected);
|
||||
|
||||
LeftArrow.Size = (selected && Enabled) || ArrowsAlwaysVisible ? new SizeF(30, 30) : SizeF.Empty;
|
||||
RightArrow.Size = (selected && Enabled) || ArrowsAlwaysVisible ? new SizeF(30, 30) : SizeF.Empty;
|
||||
|
||||
RightArrow.Position = new PointF(pos.X + size.Width - RightArrow.Size.Width - 5, pos.Y + 4);
|
||||
}
|
||||
/// <summary>
|
||||
/// Moves to the previous item.
|
||||
/// </summary>
|
||||
public abstract void GoLeft();
|
||||
/// <summary>
|
||||
/// Moves to the next item.
|
||||
/// </summary>
|
||||
public abstract void GoRight();
|
||||
/// <summary>
|
||||
/// Draws the left and right arrow.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
title.Draw();
|
||||
badgeLeft?.Draw();
|
||||
LeftArrow.Draw();
|
||||
RightArrow.Draw();
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void UpdateColors()
|
||||
{
|
||||
base.UpdateColors();
|
||||
|
||||
if (!Enabled)
|
||||
{
|
||||
LeftArrow.Color = Colors.ArrowsDisabled;
|
||||
RightArrow.Color = Colors.ArrowsDisabled;
|
||||
}
|
||||
else if (lastSelected)
|
||||
{
|
||||
LeftArrow.Color = Colors.ArrowsHovered;
|
||||
RightArrow.Color = Colors.ArrowsHovered;
|
||||
}
|
||||
else
|
||||
{
|
||||
LeftArrow.Color = Colors.ArrowsNormal;
|
||||
RightArrow.Color = Colors.ArrowsNormal;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// A slider item for changing integer values.
|
||||
/// </summary>
|
||||
public class NativeSliderItem : NativeSlidableItem
|
||||
{
|
||||
#region Internal Fields
|
||||
|
||||
/// <summary>
|
||||
/// The background of the slider.
|
||||
/// </summary>
|
||||
internal protected ScaledRectangle background = new ScaledRectangle(PointF.Empty, SizeF.Empty)
|
||||
{
|
||||
Color = Color.FromArgb(255, 4, 32, 57)
|
||||
};
|
||||
/// <summary>
|
||||
/// THe front of the slider.
|
||||
/// </summary>
|
||||
internal protected ScaledRectangle slider = new ScaledRectangle(PointF.Empty, SizeF.Empty)
|
||||
{
|
||||
Color = Color.FromArgb(255, 57, 116, 200)
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// The maximum value of the slider.
|
||||
/// </summary>
|
||||
private int maximum = 0;
|
||||
/// <summary>
|
||||
/// The current value of the slider.
|
||||
/// </summary>
|
||||
private int _value = 100;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The color of the Slider.
|
||||
/// </summary>
|
||||
public Color SliderColor
|
||||
{
|
||||
get => slider.Color;
|
||||
set => slider.Color = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The maximum value of the slider.
|
||||
/// </summary>
|
||||
public int Maximum
|
||||
{
|
||||
get => maximum;
|
||||
set
|
||||
{
|
||||
// If the value was not changed, return
|
||||
if (maximum == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Otherwise, save the new value
|
||||
maximum = value;
|
||||
// If the current value is higher than the max, set the max
|
||||
if (_value > maximum)
|
||||
{
|
||||
_value = maximum;
|
||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
// Finally, update the location of the slider
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The current value of the slider.
|
||||
/// </summary>
|
||||
public int Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
// If the value is over the limit, raise an exception
|
||||
if (value > maximum)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), $"The value is over the maximum of {maximum - 1}");
|
||||
}
|
||||
// Otherwise, save it
|
||||
_value = value;
|
||||
// Trigger the respective event
|
||||
ValueChanged?.Invoke(this, EventArgs.Empty);
|
||||
// And update the location of the slider
|
||||
UpdatePosition();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The multiplier for increasing and decreasing the value.
|
||||
/// </summary>
|
||||
public int Multiplier { get; set; } = 1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the value of the menu changes.
|
||||
/// </summary>
|
||||
public event EventHandler ValueChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="NativeSliderItem"/> with a maximum of 100.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
public NativeSliderItem(string title) : this(title, string.Empty, 100, 0)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a <see cref="NativeSliderItem"/> with a maximum of 100.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
/// <param name="description">The description of the Item.</param>
|
||||
public NativeSliderItem(string title, string description) : this(title, description, 100, 0)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a <see cref="NativeSliderItem"/> with a specific current and maximum value.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
/// <param name="max">The maximum value of the Slider.</param>
|
||||
/// <param name="value">The current value of the Slider.</param>
|
||||
public NativeSliderItem(string title, int max, int value) : this(title, string.Empty, max, value)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a <see cref="NativeSliderItem"/> with a specific maximum.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the Item.</param>
|
||||
/// <param name="description">The description of the Item.</param>
|
||||
/// <param name="max">The maximum value of the Slider.</param>
|
||||
/// <param name="value">The current value of the Slider.</param>
|
||||
public NativeSliderItem(string title, string description, int max, int value) : base(title, description)
|
||||
{
|
||||
maximum = max;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Functions
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position of the bar based on the value.
|
||||
/// </summary>
|
||||
internal protected void UpdatePosition()
|
||||
{
|
||||
// Calculate the increment, and then the value of X
|
||||
float increment = _value / (float)maximum;
|
||||
float x = (background.Size.Width - slider.Size.Width) * increment;
|
||||
// Then, add the X to the slider position
|
||||
slider.Position = new PointF(background.Position.X + x, background.Position.Y);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the item positions and sizes with the specified values.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position of the item.</param>
|
||||
/// <param name="size">The size of the item.</param>
|
||||
/// <param name="selected">If this item has been selected.</param>
|
||||
public override void Recalculate(PointF pos, SizeF size, bool selected)
|
||||
{
|
||||
base.Recalculate(pos, size, selected);
|
||||
// Set the position and size of the background
|
||||
background.Size = new SizeF(150, 9);
|
||||
background.Position = new PointF(pos.X + size.Width - background.Size.Width - 7 - LeftArrow.Size.Width, pos.Y + 14);
|
||||
// And do the same for the left arrow
|
||||
LeftArrow.Position = new PointF(background.Position.X - LeftArrow.Size.Width, pos.Y + 4);
|
||||
// Finally, set the correct position of the slider
|
||||
slider.Size = new SizeF(75, 9);
|
||||
UpdatePosition();
|
||||
}
|
||||
/// <summary>
|
||||
/// Reduces the value of the slider.
|
||||
/// </summary>
|
||||
public override void GoLeft()
|
||||
{
|
||||
// Calculate the new value
|
||||
int newValue = _value - Multiplier;
|
||||
// If is under zero, set it to zero
|
||||
if (newValue < 0)
|
||||
{
|
||||
Value = 0;
|
||||
}
|
||||
// Otherwise, set it to the new value
|
||||
else
|
||||
{
|
||||
Value = newValue;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Increases the value of the slider.
|
||||
/// </summary>
|
||||
public override void GoRight()
|
||||
{
|
||||
// Calculate the new value
|
||||
int newValue = _value + Multiplier;
|
||||
// If the value is over the maximum, set the max
|
||||
if (newValue > maximum)
|
||||
{
|
||||
Value = maximum;
|
||||
}
|
||||
// Otherwise, set the calculated value
|
||||
else
|
||||
{
|
||||
Value = newValue;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the slider.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
base.Draw(); // Arrows, Title and Left Badge
|
||||
background.Draw();
|
||||
slider.Draw();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using LemonUI.Elements;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Information of a specific field in a <see cref="NativeStatsPanel"/>.
|
||||
/// </summary>
|
||||
public class NativeStatsInfo
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly ScaledText text = new ScaledText(PointF.Empty, string.Empty, 0.35f);
|
||||
private float value = 100;
|
||||
private readonly List<ScaledRectangle> backgrounds = new List<ScaledRectangle>();
|
||||
private readonly List<ScaledRectangle> foregrounds = new List<ScaledRectangle>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The name of the Stats Field.
|
||||
/// </summary>
|
||||
public string Name
|
||||
{
|
||||
get => text.Text;
|
||||
set => text.Text = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The value of the Stats bar.
|
||||
/// </summary>
|
||||
public float Value
|
||||
{
|
||||
get => value;
|
||||
set
|
||||
{
|
||||
if (value > 100 || value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The Value of the Stat can't be over 100 or under 0.");
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
UpdateBars();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Stat Info with the specified name and value set to zero.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the Stat.</param>
|
||||
public NativeStatsInfo(string name) : this(name, 0)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new Stat Info with the specified name and value.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the Stat.</param>
|
||||
/// <param name="value"></param>
|
||||
public NativeStatsInfo(string name, int value)
|
||||
{
|
||||
Name = name;
|
||||
this.value = value;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
backgrounds.Add(new ScaledRectangle(PointF.Empty, SizeF.Empty));
|
||||
foregrounds.Add(new ScaledRectangle(PointF.Empty, SizeF.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
internal void SetColor(Color background, Color foreground)
|
||||
{
|
||||
foreach (ScaledRectangle rectangle in backgrounds)
|
||||
{
|
||||
rectangle.Color = background;
|
||||
}
|
||||
foreach (ScaledRectangle rectangle in foregrounds)
|
||||
{
|
||||
rectangle.Color = foreground;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the values of the bars.
|
||||
/// </summary>
|
||||
private void UpdateBars()
|
||||
{
|
||||
SizeF @default = new SizeF(35, 9);
|
||||
|
||||
// FIRST BAR
|
||||
if (value > 0 && value < 20)
|
||||
{
|
||||
foregrounds[0].Size = new SizeF(@default.Width * (value / 20), @default.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
foregrounds[0].Size = value > 20 ? @default : SizeF.Empty;
|
||||
}
|
||||
|
||||
// SECOND BAR
|
||||
if (value > 20 && value < 40)
|
||||
{
|
||||
foregrounds[1].Size = new SizeF(@default.Width * ((value - 20) / 20), @default.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
foregrounds[1].Size = value > 40 ? @default : SizeF.Empty;
|
||||
}
|
||||
|
||||
// THIRD BAR
|
||||
if (value > 40 && value < 60)
|
||||
{
|
||||
foregrounds[2].Size = new SizeF(@default.Width * ((value - 40) / 20), @default.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
foregrounds[2].Size = value > 60 ? @default : SizeF.Empty;
|
||||
}
|
||||
|
||||
// FOURTH BAR
|
||||
if (value > 60 && value < 80)
|
||||
{
|
||||
foregrounds[3].Size = new SizeF(@default.Width * ((value - 60) / 20), @default.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
foregrounds[3].Size = value > 80 ? @default : SizeF.Empty;
|
||||
}
|
||||
|
||||
// FIFTH BAR
|
||||
if (value > 80 && value < 100)
|
||||
{
|
||||
foregrounds[4].Size = new SizeF(@default.Width * ((value - 80) / 20), @default.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
foregrounds[4].Size = value == 100 ? @default : SizeF.Empty;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Recalculates the position of the stat Text and Bar.
|
||||
/// </summary>
|
||||
/// <param name="position">The new position fot the Stat.</param>
|
||||
/// <param name="width">The Width of the parent Stats Panel.</param>
|
||||
public void Recalculate(PointF position, float width)
|
||||
{
|
||||
text.Position = new PointF(position.X, position.Y);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
PointF pos = new PointF(position.X + width - 234 + ((35 + 3) * i), position.Y + 10);
|
||||
backgrounds[i].Position = pos;
|
||||
backgrounds[i].Size = new SizeF(35, 9);
|
||||
foregrounds[i].Position = pos;
|
||||
}
|
||||
|
||||
UpdateBars();
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the stat information.
|
||||
/// </summary>
|
||||
public void Draw()
|
||||
{
|
||||
foreach (ScaledRectangle background in backgrounds)
|
||||
{
|
||||
background.Draw();
|
||||
}
|
||||
foreach (ScaledRectangle foreground in foregrounds)
|
||||
{
|
||||
foreground.Draw();
|
||||
}
|
||||
text.Draw();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Statistics panel.
|
||||
/// </summary>
|
||||
public class NativeStatsPanel : NativePanel, IContainer<NativeStatsInfo>
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly List<NativeStatsInfo> fields = new List<NativeStatsInfo>();
|
||||
private PointF lastPosition = PointF.Empty;
|
||||
private float lastWidth = 0;
|
||||
private Color backgroundColor = Color.FromArgb(255, 169, 169, 169);
|
||||
private Color foregroundColor = Color.FromArgb(255, 255, 255, 255);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The color of the background of the bars.
|
||||
/// </summary>
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get => backgroundColor;
|
||||
set
|
||||
{
|
||||
backgroundColor = value;
|
||||
|
||||
foreach (NativeStatsInfo field in fields)
|
||||
{
|
||||
field.SetColor(value, foregroundColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The color of the foreground of the bars.
|
||||
/// </summary>
|
||||
public Color ForegroundColor
|
||||
{
|
||||
get => foregroundColor;
|
||||
set
|
||||
{
|
||||
foregroundColor = value;
|
||||
|
||||
foreach (NativeStatsInfo field in fields)
|
||||
{
|
||||
field.SetColor(backgroundColor, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Stats Panel.
|
||||
/// </summary>
|
||||
/// <param name="stats">The Statistics to add.</param>
|
||||
public NativeStatsPanel(params NativeStatsInfo[] stats)
|
||||
{
|
||||
foreach (NativeStatsInfo field in stats)
|
||||
{
|
||||
Add(field);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <summary>
|
||||
/// Adds a stat to the player field.
|
||||
/// </summary>
|
||||
/// <param name="field">The Field to add.</param>
|
||||
public void Add(NativeStatsInfo field)
|
||||
{
|
||||
if (fields.Contains(field))
|
||||
{
|
||||
throw new InvalidOperationException("Stat is already part of the panel.");
|
||||
}
|
||||
fields.Add(field);
|
||||
field.SetColor(backgroundColor, foregroundColor);
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a field from the panel.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to remove.</param>
|
||||
public void Remove(NativeStatsInfo field)
|
||||
{
|
||||
if (!fields.Contains(field))
|
||||
{
|
||||
return;
|
||||
}
|
||||
fields.Remove(field);
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes the items that match the function.
|
||||
/// </summary>
|
||||
/// <param name="func">The function used to match items.</param>
|
||||
public void Remove(Func<NativeStatsInfo, bool> func)
|
||||
{
|
||||
foreach (NativeStatsInfo item in new List<NativeStatsInfo>(fields))
|
||||
{
|
||||
if (func(item))
|
||||
{
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the Stats fields.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
fields.Clear();
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks if the field is part of the Stats Panel.
|
||||
/// </summary>
|
||||
/// <param name="field">The field to check.</param>
|
||||
/// <returns><see langword="true"/> if the item is part of the Panel, <see langword="false"/> otherwise.</returns>
|
||||
public bool Contains(NativeStatsInfo field) => fields.Contains(field);
|
||||
/// <summary>
|
||||
/// Recalculates the Stats panel with the last known Position and Width.
|
||||
/// </summary>
|
||||
public void Recalculate() => Recalculate(lastPosition, lastWidth);
|
||||
/// <summary>
|
||||
/// Recalculates the position of the Stats panel.
|
||||
/// </summary>
|
||||
/// <param name="position">The new position of the Stats Panel.</param>
|
||||
/// <param name="width">The width of the menu.</param>
|
||||
public override void Recalculate(PointF position, float width)
|
||||
{
|
||||
base.Recalculate(position, width);
|
||||
|
||||
Background.Size = new SizeF(width, (fields.Count * 38) + 9);
|
||||
|
||||
for (int i = 0; i < fields.Count; i++)
|
||||
{
|
||||
fields[i].Recalculate(new PointF(position.X + 9, position.Y + 9 + (38 * i)), width);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Processes the Stats Panel.
|
||||
/// </summary>
|
||||
public override void Process()
|
||||
{
|
||||
base.Process();
|
||||
|
||||
foreach (NativeStatsInfo info in fields)
|
||||
{
|
||||
info.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Item used for opening submenus.
|
||||
/// </summary>
|
||||
public class NativeSubmenuItem : NativeItem
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The menu opened by this item.
|
||||
/// </summary>
|
||||
public NativeMenu Menu { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Item that opens a Submenu.
|
||||
/// </summary>
|
||||
/// <param name="menu">The menu that this item will open.</param>
|
||||
/// <param name="parent">The parent menu where this item will be located.</param>
|
||||
public NativeSubmenuItem(NativeMenu menu, NativeMenu parent) : this(menu, parent, ">>>")
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new Item that opens a Submenu.
|
||||
/// </summary>
|
||||
/// <param name="menu">The menu that this item will open.</param>
|
||||
/// <param name="parent">The parent menu where this item will be located.</param>
|
||||
/// <param name="endlabel">The alternative title of the item, shown on the right.</param>
|
||||
public NativeSubmenuItem(NativeMenu menu, NativeMenu parent, string endlabel) : base(menu.Subtitle, menu.Description, endlabel)
|
||||
{
|
||||
Menu = menu ?? throw new ArgumentNullException(nameof(menu));
|
||||
Menu.Parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
|
||||
Activated += NativeSubmenuItem_Activated;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Draw()
|
||||
{
|
||||
// There is no Process(), so let's use draw to update the description
|
||||
if (Description != Menu.Description)
|
||||
{
|
||||
Description = Menu.Description;
|
||||
}
|
||||
|
||||
base.Draw();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local Events
|
||||
|
||||
private void NativeSubmenuItem_Activated(object sender, EventArgs e)
|
||||
{
|
||||
Menu.Parent.Visible = false;
|
||||
|
||||
if (!Menu.Parent.Visible)
|
||||
{
|
||||
Menu.Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the selection of an item in the screen.
|
||||
/// </summary>
|
||||
public class SelectedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The index of the item in the full list of items.
|
||||
/// </summary>
|
||||
public int Index { get; }
|
||||
/// <summary>
|
||||
/// The index of the item in the screen.
|
||||
/// </summary>
|
||||
public int OnScreen { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="SelectedEventArgs"/>.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item in the menu.</param>
|
||||
/// <param name="screen">The index of the item based on the number of items shown on screen,</param>
|
||||
public SelectedEventArgs(int index, int screen)
|
||||
{
|
||||
Index = index;
|
||||
OnScreen = screen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that is called when a new item is selected in the Menu.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">A <see cref="SelectedEventArgs"/> with the index information.</param>
|
||||
public delegate void SelectedEventHandler(object sender, SelectedEventArgs e);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace LemonUI.Menus
|
||||
{
|
||||
/// <summary>
|
||||
/// The behavior of the <see cref="NativeMenu"/>'s subtitle.
|
||||
/// </summary>
|
||||
public enum SubtitleBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// The subtitle will always be shown.
|
||||
/// </summary>
|
||||
AlwaysShow = 0,
|
||||
/// <summary>
|
||||
/// The subtitle will always be shown, except when is empty.
|
||||
/// </summary>
|
||||
ShowIfRequired = 1,
|
||||
/// <summary>
|
||||
/// The subtitle will never be shown.
|
||||
/// </summary>
|
||||
AlwaysHide = 2
|
||||
}
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.UI;
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
using RAGE.NUI;
|
||||
#elif RPH
|
||||
using Rage;
|
||||
using Rage.Native;
|
||||
#elif SHVDN3
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
#endif
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the method that reports a Resolution change in the Game Settings.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">A <see cref="ResolutionChangedEventArgs"/> containing the Previous and Current resolution.</param>
|
||||
public delegate void ResolutionChangedEventHandler(object sender, ResolutionChangedEventArgs e);
|
||||
/// <summary>
|
||||
/// Represents the method that reports a Safe Zone change in the Game Settings.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event event.</param>
|
||||
/// <param name="e">A <see cref="ResolutionChangedEventArgs"/> containing the Previous and Current Safe Zone.</param>
|
||||
public delegate void SafeZoneChangedEventHandler(object sender, SafeZoneChangedEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Represents the information after a Resolution Change in the game.
|
||||
/// </summary>
|
||||
public class ResolutionChangedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The Game Resolution before it was changed.
|
||||
/// </summary>
|
||||
public SizeF Before { get; }
|
||||
/// <summary>
|
||||
/// The Game Resolution after it was changed.
|
||||
/// </summary>
|
||||
public SizeF After { get; }
|
||||
|
||||
internal ResolutionChangedEventArgs(SizeF before, SizeF after)
|
||||
{
|
||||
Before = before;
|
||||
After = after;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents the information after a Safe Zone Change in the game.
|
||||
/// </summary>
|
||||
public class SafeZoneChangedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The raw Safezone size before the change.
|
||||
/// </summary>
|
||||
public float Before { get; }
|
||||
/// <summary>
|
||||
/// The Safezone size after the change.
|
||||
/// </summary>
|
||||
public float After { get; }
|
||||
|
||||
internal SafeZoneChangedEventArgs(float before, float after)
|
||||
{
|
||||
Before = before;
|
||||
After = after;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manager for Menus and Items.
|
||||
/// </summary>
|
||||
public class ObjectPool
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
/// <summary>
|
||||
/// The last known resolution by the object pool.
|
||||
/// </summary>
|
||||
#if FIVEM
|
||||
private SizeF lastKnownResolution = CitizenFX.Core.UI.Screen.Resolution;
|
||||
#elif RAGEMP
|
||||
private SizeF lastKnownResolution = new SizeF(Game.ScreenResolution.Width, Game.ScreenResolution.Height);
|
||||
#elif RPH
|
||||
private SizeF lastKnownResolution = Game.Resolution;
|
||||
#elif SHVDN3
|
||||
private SizeF lastKnownResolution = GTA.UI.Screen.Resolution;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The last know Safezone size.
|
||||
/// </summary>
|
||||
#if FIVEM
|
||||
private float lastKnownSafezone = API.GetSafeZoneSize();
|
||||
#elif RAGEMP
|
||||
private float lastKnownSafezone = Invoker.Invoke<float>(Natives.GetSafeZoneSize);
|
||||
#elif RPH
|
||||
private float lastKnownSafezone = NativeFunction.CallByHash<float>(0xBAF107B6BB2C97F0);
|
||||
#elif SHVDN3
|
||||
private float lastKnownSafezone = Function.Call<float>(Hash.GET_SAFE_ZONE_SIZE);
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The list of processable objects.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<int, IProcessable> objects = new ConcurrentDictionary<int, IProcessable>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there are objects visible on the screen.
|
||||
/// </summary>
|
||||
public bool AreAnyVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
// Iterate over the objects
|
||||
foreach (IProcessable obj in objects.Values)
|
||||
{
|
||||
// If is visible return true
|
||||
if (obj.Visible)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If none were visible return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the game resolution is changed.
|
||||
/// </summary>
|
||||
public event ResolutionChangedEventHandler ResolutionChanged;
|
||||
/// <summary>
|
||||
/// Event triggered when the Safezone size option in the Display settings is changed.
|
||||
/// </summary>
|
||||
public event SafeZoneChangedEventHandler SafezoneChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tools
|
||||
|
||||
/// <summary>
|
||||
/// Detects resolution changes by comparing the last known resolution and the current one.
|
||||
/// </summary>
|
||||
private void DetectResolutionChanges()
|
||||
{
|
||||
// Get the current resolution
|
||||
#if FIVEM
|
||||
SizeF resolution = CitizenFX.Core.UI.Screen.Resolution;
|
||||
#elif RAGEMP
|
||||
ScreenResolutionType raw = Game.ScreenResolution;
|
||||
SizeF resolution = new SizeF(raw.Width, raw.Height);
|
||||
#elif RPH
|
||||
SizeF resolution = Game.Resolution;
|
||||
#elif SHVDN3
|
||||
SizeF resolution = GTA.UI.Screen.Resolution;
|
||||
#endif
|
||||
// If the old res does not matches the current one
|
||||
if (lastKnownResolution != resolution)
|
||||
{
|
||||
// Trigger the event
|
||||
ResolutionChanged?.Invoke(this, new ResolutionChangedEventArgs(lastKnownResolution, resolution));
|
||||
// Refresh everything
|
||||
RefreshAll();
|
||||
// And save the new resolution
|
||||
lastKnownResolution = resolution;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Detects Safezone changes by comparing the last known value to the current one.
|
||||
/// </summary>
|
||||
private void DetectSafezoneChanges()
|
||||
{
|
||||
// Get the current Safezone size
|
||||
#if FIVEM
|
||||
float safezone = API.GetSafeZoneSize();
|
||||
#elif RAGEMP
|
||||
float safezone = Invoker.Invoke<float>(Natives.GetSafeZoneSize);
|
||||
#elif RPH
|
||||
float safezone = NativeFunction.CallByHash<float>(0xBAF107B6BB2C97F0);
|
||||
#elif SHVDN3
|
||||
float safezone = Function.Call<float>(Hash.GET_SAFE_ZONE_SIZE);
|
||||
#endif
|
||||
|
||||
// If is not the same as the last one
|
||||
if (lastKnownSafezone != safezone)
|
||||
{
|
||||
// Trigger the event
|
||||
SafezoneChanged?.Invoke(this, new SafeZoneChangedEventArgs(lastKnownSafezone, safezone));
|
||||
// Refresh everything
|
||||
RefreshAll();
|
||||
// And save the new safezone
|
||||
lastKnownSafezone = safezone;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Function
|
||||
|
||||
/// <summary>
|
||||
/// Adds the object into the pool.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to add.</param>
|
||||
public void Add(IProcessable obj)
|
||||
{
|
||||
// Make sure that the object is not null
|
||||
if (obj == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
}
|
||||
|
||||
int key = obj.GetHashCode();
|
||||
// Otherwise, add it to the general pool
|
||||
if (objects.ContainsKey(key))
|
||||
{
|
||||
throw new InvalidOperationException("The object is already part of this pool.");
|
||||
}
|
||||
objects.TryAdd(key, obj);
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes the object from the pool.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to remove.</param>
|
||||
public void Remove(IProcessable obj)
|
||||
{
|
||||
objects.TryRemove(obj.GetHashCode(), out _);
|
||||
}
|
||||
/// <summary>
|
||||
/// Performs the specified action on each element that matches T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to match.</typeparam>
|
||||
/// <param name="action">The action delegate to perform on each T.</param>
|
||||
public void ForEach<T>(Action<T> action)
|
||||
{
|
||||
foreach (IProcessable obj in objects.Values)
|
||||
{
|
||||
if (obj is T conv)
|
||||
{
|
||||
action(conv);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Refreshes all of the items.
|
||||
/// </summary>
|
||||
public void RefreshAll()
|
||||
{
|
||||
// Iterate over the objects and recalculate those possible
|
||||
foreach (IProcessable obj in objects.Values)
|
||||
{
|
||||
if (obj is IRecalculable recal)
|
||||
{
|
||||
recal.Recalculate();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Hides all of the objects.
|
||||
/// </summary>
|
||||
public void HideAll()
|
||||
{
|
||||
foreach (IProcessable obj in objects.Values)
|
||||
{
|
||||
obj.Visible = false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Processes the objects and features in this pool.
|
||||
/// This needs to be called every tick.
|
||||
/// </summary>
|
||||
public void Process()
|
||||
{
|
||||
// See if there are resolution or safezone changes
|
||||
DetectResolutionChanges();
|
||||
DetectSafezoneChanges();
|
||||
// And process the objects in the pool
|
||||
foreach (IProcessable obj in objects.Values)
|
||||
{
|
||||
obj.Process();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage.Native;
|
||||
#elif SHVDN3
|
||||
using GTA.Native;
|
||||
#endif
|
||||
using System;
|
||||
|
||||
namespace LemonUI.Scaleform
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a generic Scaleform object.
|
||||
/// </summary>
|
||||
public abstract class BaseScaleform : IScaleform
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
#if FIVEM || SHVDN3
|
||||
/// <summary>
|
||||
/// The ID of the scaleform.
|
||||
/// </summary>
|
||||
[Obsolete("Please use the Handle or Name properties and call the methods manually.", true)]
|
||||
#endif
|
||||
#if FIVEM
|
||||
protected CitizenFX.Core.Scaleform scaleform = new CitizenFX.Core.Scaleform(string.Empty);
|
||||
#elif SHVDN3
|
||||
protected GTA.Scaleform scaleform = new GTA.Scaleform(string.Empty);
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The ID or Handle of the Scaleform.
|
||||
/// </summary>
|
||||
public int Handle { get; private set; }
|
||||
/// <summary>
|
||||
/// The Name of the Scaleform.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
/// <summary>
|
||||
/// If the Scaleform should be visible or not.
|
||||
/// </summary>
|
||||
public bool Visible { get; set; }
|
||||
/// <summary>
|
||||
/// If the Scaleform is loaded or not.
|
||||
/// </summary>
|
||||
public bool IsLoaded
|
||||
{
|
||||
get
|
||||
{
|
||||
#if FIVEM
|
||||
return API.HasScaleformMovieLoaded(Handle);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<bool>(Natives.HasScaleformMovieLoaded, Handle);
|
||||
#elif RPH
|
||||
return NativeFunction.CallByHash<bool>(0x85F01B8D5B90570E, Handle);
|
||||
#elif SHVDN3
|
||||
return Function.Call<bool>(Hash.HAS_SCALEFORM_MOVIE_LOADED, Handle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Scaleform class with the specified Scaleform object name.
|
||||
/// </summary>
|
||||
/// <param name="sc">The Scalform object.</param>
|
||||
public BaseScaleform(string sc)
|
||||
{
|
||||
Name = sc;
|
||||
|
||||
#if FIVEM
|
||||
Handle = API.RequestScaleformMovie(Name);
|
||||
#elif RAGEMP
|
||||
Handle = Invoker.Invoke<int>(Natives.RequestScaleformMovie, Name);
|
||||
#elif RPH
|
||||
Handle = NativeFunction.CallByHash<int>(0x11FE353CF9733E6F, Name);
|
||||
#elif SHVDN3
|
||||
Handle = Function.Call<int>(Hash.REQUEST_SCALEFORM_MOVIE, Name);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Functions
|
||||
|
||||
private void CallFunctionBase(string function, params object[] parameters)
|
||||
{
|
||||
#if FIVEM
|
||||
API.BeginScaleformMovieMethod(Handle, function);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xF6E48914C7A8694E, Handle, function);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xF6E48914C7A8694E, Handle, function);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0xF6E48914C7A8694E, Handle, function);
|
||||
#endif
|
||||
|
||||
foreach (object obj in parameters)
|
||||
{
|
||||
if (obj is int objInt)
|
||||
{
|
||||
#if FIVEM
|
||||
API.ScaleformMovieMethodAddParamInt(objInt);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xC3D0841A0CC546A6, objInt);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xC3D0841A0CC546A6, objInt);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0xC3D0841A0CC546A6, objInt);
|
||||
#endif
|
||||
}
|
||||
else if (obj is string objString)
|
||||
{
|
||||
#if FIVEM
|
||||
API.BeginTextCommandScaleformString("STRING");
|
||||
API.AddTextComponentSubstringPlayerName(objString);
|
||||
API.EndTextCommandScaleformString();
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.BeginTextCommandScaleformString, "STRING");
|
||||
Invoker.Invoke(Natives.AddTextComponentSubstringPlayerName, objString);
|
||||
Invoker.Invoke(Natives.EndTextCommandScaleformString);
|
||||
#elif RPH
|
||||
|
||||
NativeFunction.CallByHash<int>(0x80338406F3475E55, "STRING");
|
||||
NativeFunction.CallByHash<int>(0x6C188BE134E074AA, objString);
|
||||
NativeFunction.CallByHash<int>(0x362E2D3FE93A9959);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0x80338406F3475E55, "STRING");
|
||||
Function.Call((Hash)0x6C188BE134E074AA, objString);
|
||||
Function.Call((Hash)0x362E2D3FE93A9959);
|
||||
#endif
|
||||
}
|
||||
else if (obj is float objFloat)
|
||||
{
|
||||
#if FIVEM
|
||||
API.ScaleformMovieMethodAddParamFloat(objFloat);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xD69736AAE04DB51A, objFloat);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xD69736AAE04DB51A, objFloat);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0xD69736AAE04DB51A, objFloat);
|
||||
#endif
|
||||
}
|
||||
else if (obj is double objDouble)
|
||||
{
|
||||
#if FIVEM
|
||||
API.ScaleformMovieMethodAddParamFloat((float)objDouble);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xD69736AAE04DB51A, (float)objDouble);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xD69736AAE04DB51A, (float)objDouble);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0xD69736AAE04DB51A, (float)objDouble);
|
||||
#endif
|
||||
}
|
||||
else if (obj is bool objBool)
|
||||
{
|
||||
#if FIVEM
|
||||
API.ScaleformMovieMethodAddParamBool(objBool);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xC58424BA936EB458, objBool);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xC58424BA936EB458, objBool);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0xC58424BA936EB458, objBool);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Unexpected argument type {obj.GetType().Name}.", nameof(parameters));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specified Scaleform Return Value is ready to be fetched.
|
||||
/// </summary>
|
||||
/// <param name="id">The Identifier of the Value.</param>
|
||||
/// <returns><see langword="true"/> if the value is ready, <see langword="false"/> otherwise.</returns>
|
||||
public bool IsValueReady(int id)
|
||||
{
|
||||
#if FIVEM
|
||||
return API.IsScaleformMovieMethodReturnValueReady(id);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<bool>(Natives._0x768FF8961BA904D6, id);
|
||||
#elif RPH
|
||||
return NativeFunction.CallByHash<bool>(0x768FF8961BA904D6, id);
|
||||
#elif SHVDN3
|
||||
return Function.Call<bool>(Hash.IS_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_READY, id);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a specific value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value.</typeparam>
|
||||
/// <param name="id">The Identifier of the value.</param>
|
||||
/// <returns>The value returned by the native.</returns>
|
||||
public T GetValue<T>(int id)
|
||||
{
|
||||
if (typeof(T) == typeof(string))
|
||||
{
|
||||
#if FIVEM
|
||||
return (T)(object)API.GetScaleformMovieMethodReturnValueString(id);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<T>(0xE1E258829A885245, id);
|
||||
#elif RPH
|
||||
return (T)NativeFunction.CallByHash(0xE1E258829A885245, typeof(string), id);
|
||||
#elif SHVDN3
|
||||
return (T)(object)Function.Call<string>(Hash.GET_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_STRING, id);
|
||||
#endif
|
||||
}
|
||||
else if (typeof(T) == typeof(int))
|
||||
{
|
||||
#if FIVEM
|
||||
return (T)(object)API.GetScaleformMovieMethodReturnValueInt(id);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<T>(0x2DE7EFA66B906036, id);
|
||||
#elif RPH
|
||||
return (T)(object)NativeFunction.CallByHash<int>(0x2DE7EFA66B906036, id);
|
||||
#elif SHVDN3
|
||||
return (T)(object)Function.Call<int>(Hash.GET_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_INT, id);
|
||||
#endif
|
||||
}
|
||||
else if (typeof(T) == typeof(bool))
|
||||
{
|
||||
#if FIVEM
|
||||
return (T)(object)API.GetScaleformMovieMethodReturnValueBool(id);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<T>(0xD80A80346A45D761, id);
|
||||
#elif RPH
|
||||
return (T)(object)NativeFunction.CallByHash<bool>(0xD80A80346A45D761, id);
|
||||
#elif SHVDN3
|
||||
return (T)(object)Function.Call<bool>(Hash._GET_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_BOOL, id);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Expected string, int or bool, got {typeof(T).Name}.");
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Calls a Scaleform function.
|
||||
/// </summary>
|
||||
/// <param name="function">The name of the function to call.</param>
|
||||
/// <param name="parameters">The parameters to pass.</param>
|
||||
public void CallFunction(string function, params object[] parameters)
|
||||
{
|
||||
CallFunctionBase(function, parameters);
|
||||
#if FIVEM
|
||||
API.EndScaleformMovieMethod();
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xC6796A8FFA375E53);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xC6796A8FFA375E53);
|
||||
#elif SHVDN3
|
||||
Function.Call((Hash)0xC6796A8FFA375E53);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Calls a Scaleform function with a return value.
|
||||
/// </summary>
|
||||
/// <param name="function">The name of the function to call.</param>
|
||||
/// <param name="parameters">The parameters to pass.</param>
|
||||
public int CallFunctionReturn(string function, params object[] parameters)
|
||||
{
|
||||
CallFunctionBase(function, parameters);
|
||||
#if FIVEM
|
||||
return API.EndScaleformMovieMethodReturnValue();
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<int>(0xC50AA39A577AF886);
|
||||
#elif RPH
|
||||
return NativeFunction.CallByHash<int>(0xC50AA39A577AF886);
|
||||
#elif SHVDN3
|
||||
return Function.Call<int>((Hash)0xC50AA39A577AF886);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the parameters of the Scaleform.
|
||||
/// </summary>
|
||||
public abstract void Update();
|
||||
/// <summary>
|
||||
/// Draws the scaleform full screen.
|
||||
/// </summary>
|
||||
public virtual void DrawFullScreen()
|
||||
{
|
||||
if (!Visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Update();
|
||||
#if FIVEM
|
||||
API.DrawScaleformMovieFullscreen(Handle, 255, 255, 255, 255, 0);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.DrawScaleformMovieFullscreen, 255, 255, 255, 255, 0);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x0DF606929C105BE1, Handle, 255, 255, 255, 255, 0);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.DRAW_SCALEFORM_MOVIE_FULLSCREEN, Handle, 255, 255, 255, 255, 0);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the scaleform full screen.
|
||||
/// </summary>
|
||||
public virtual void Draw() => DrawFullScreen();
|
||||
/// <summary>
|
||||
/// Draws the scaleform full screen.
|
||||
/// </summary>
|
||||
public virtual void Process() => DrawFullScreen();
|
||||
/// <summary>
|
||||
/// Marks the scaleform as no longer needed.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
int id = Handle;
|
||||
#if FIVEM
|
||||
API.SetScaleformMovieAsNoLongerNeeded(ref id);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.SetScaleformMovieAsNoLongerNeeded, id);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x1D132D614DD86811, new NativeArgument(id));
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.SET_SCALEFORM_MOVIE_AS_NO_LONGER_NEEDED, new OutputArgument(id));
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
#endif
|
||||
using System;
|
||||
|
||||
namespace LemonUI.Scaleform
|
||||
{
|
||||
/// <summary>
|
||||
/// The type for a big message.
|
||||
/// </summary>
|
||||
public enum MessageType
|
||||
{
|
||||
/// <summary>
|
||||
/// A centered message with customizable text an d background colors.
|
||||
/// Internally called SHOW_SHARD_CENTERED_MP_MESSAGE.
|
||||
/// </summary>
|
||||
Customizable = 0,
|
||||
/// <summary>
|
||||
/// Used when you rank up on GTA Online.
|
||||
/// Internally called SHOW_SHARD_CREW_RANKUP_MP_MESSAGE.
|
||||
/// </summary>
|
||||
RankUp = 1,
|
||||
/// <summary>
|
||||
/// The Mission Passed screen on PS3 and Xbox 360.
|
||||
/// Internally called SHOW_MISSION_PASSED_MESSAGE.
|
||||
/// </summary>
|
||||
MissionPassedOldGen = 2,
|
||||
/// <summary>
|
||||
/// The Message Type shown on the Wasted screen.
|
||||
/// Internally called SHOW_SHARD_WASTED_MP_MESSAGE.
|
||||
/// </summary>
|
||||
Wasted = 3,
|
||||
/// <summary>
|
||||
/// Used on the GTA Online Freemode event announcements.
|
||||
/// Internally called SHOW_PLANE_MESSAGE.
|
||||
/// </summary>
|
||||
Plane = 4,
|
||||
/// <summary>
|
||||
/// Development leftover from when GTA Online was Cops and Crooks.
|
||||
/// Internally called SHOW_BIG_MP_MESSAGE.
|
||||
/// </summary>
|
||||
CopsAndCrooks = 5,
|
||||
/// <summary>
|
||||
/// Message shown when the player purchases a weapon.
|
||||
/// Internally called SHOW_WEAPON_PURCHASED.
|
||||
/// </summary>
|
||||
Weapon = 6,
|
||||
/// <summary>
|
||||
/// Unknown where this one is used.
|
||||
/// Internally called SHOW_CENTERED_MP_MESSAGE_LARGE.
|
||||
/// </summary>
|
||||
CenteredLarge = 7,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A customizable big message.
|
||||
/// </summary>
|
||||
public class BigMessage : BaseScaleform
|
||||
{
|
||||
#region Constant Fields
|
||||
|
||||
private const uint unarmed = 0xA2719263;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private MessageType type;
|
||||
private uint weaponHash;
|
||||
private uint hideAfter;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The title of the message.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// The subtitle or description of the message.
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
/// <summary>
|
||||
/// The color of the text.
|
||||
/// Only used on the Customizable message type.
|
||||
/// </summary>
|
||||
public int TextColor { get; set; }
|
||||
/// <summary>
|
||||
/// The color of the background in the Customizable message type.
|
||||
/// </summary>
|
||||
public int BackgroundColor { get; set; }
|
||||
/// <summary>
|
||||
/// The Rank when the mode is set to Cops and Crooks.
|
||||
/// </summary>
|
||||
public string Rank { get; set; }
|
||||
#if !RAGEMP
|
||||
/// <summary>
|
||||
/// The hash of the Weapon as an enum.
|
||||
/// </summary>
|
||||
public WeaponHash Weapon
|
||||
{
|
||||
get => (WeaponHash)weaponHash;
|
||||
set => weaponHash = (uint)value;
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// The hash of the Weapon as it's native value.
|
||||
/// </summary>
|
||||
public uint WeaponHash
|
||||
{
|
||||
get => weaponHash;
|
||||
set => weaponHash = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The type of message to show.
|
||||
/// </summary>
|
||||
public MessageType Type
|
||||
{
|
||||
get => type;
|
||||
set
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(MessageType), value))
|
||||
{
|
||||
throw new InvalidOperationException($"{value} is not a valid message type.");
|
||||
}
|
||||
type = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a standard customizable message with just a title.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
public BigMessage(string title) : this(title, string.Empty, string.Empty, unarmed, 0, 0, MessageType.Customizable)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a custom message with the specified title.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="type">The type of message.</param>
|
||||
public BigMessage(string title, MessageType type) : this(title, string.Empty, string.Empty, unarmed, 0, 0, type)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a standard customizable message with a title and message.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="message">The message to show.</param>
|
||||
public BigMessage(string title, string message) : this(title, message, string.Empty, unarmed, 0, 0, MessageType.Customizable)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a Cops and Crooks message type.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="message">The message to show.</param>
|
||||
/// <param name="rank">Text to show in the Rank space.</param>
|
||||
public BigMessage(string title, string message, string rank) : this(title, message, rank, unarmed, 0, 0, MessageType.CopsAndCrooks)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a message with the specified type, title and message.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="message">The message to show.</param>
|
||||
/// <param name="type">The type of message.</param>
|
||||
public BigMessage(string title, string message, MessageType type) : this(title, message, string.Empty, unarmed, 0, 0, type)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a standard customizable message with a title and a custom text color.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="colorText">The color of the text.</param>
|
||||
public BigMessage(string title, int colorText) : this(title, string.Empty, string.Empty, unarmed, colorText, 0, MessageType.Customizable)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a standard customizable message with a specific title and custom colors.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="colorText">The color of the text.</param>
|
||||
/// <param name="colorBackground">The color of the background.</param>
|
||||
public BigMessage(string title, int colorText, int colorBackground) : this(title, string.Empty, string.Empty, unarmed, colorText, colorBackground, MessageType.Customizable)
|
||||
{
|
||||
}
|
||||
#if !RAGEMP
|
||||
/// <summary>
|
||||
/// Creates a Weapon Purchase message with a custom text and weapons.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="weapon">The name of the Weapon.</param>
|
||||
/// <param name="hash">The hash of the Weapon image.</param>
|
||||
public BigMessage(string title, string weapon, WeaponHash hash) : this(title, string.Empty, weapon, hash, 0, 0, MessageType.Weapon)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a Weapon Purchase message with a custom text and weapons.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="message">The message to show.</param>
|
||||
/// <param name="weapon">The name of the Weapon.</param>
|
||||
/// <param name="hash">The hash of the Weapon image.</param>
|
||||
public BigMessage(string title, string message, string weapon, WeaponHash hash) : this(title, message, weapon, hash, 0, 0, MessageType.Weapon)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a message with all of the selected information.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="message">The message to show.</param>
|
||||
/// <param name="rank">The Rank on Cops and Crooks.</param>
|
||||
/// <param name="weapon">The hash of the Weapon image.</param>
|
||||
/// <param name="colorText">The color of the text.</param>
|
||||
/// <param name="colorBackground">The color of the background.</param>
|
||||
/// <param name="type">The type of message.</param>
|
||||
public BigMessage(string title, string message, string rank, WeaponHash weapon, int colorText, int colorBackground, MessageType type) : this(title, message, rank, (uint)weapon, colorText, colorBackground, type)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Creates a message with all of the selected information.
|
||||
/// </summary>
|
||||
/// <param name="title">The title to use.</param>
|
||||
/// <param name="message">The message to show.</param>
|
||||
/// <param name="rank">The Rank on Cops and Crooks.</param>
|
||||
/// <param name="weapon">The hash of the Weapon image.</param>
|
||||
/// <param name="colorText">The color of the text.</param>
|
||||
/// <param name="colorBackground">The color of the background.</param>
|
||||
/// <param name="type">The type of message.</param>
|
||||
public BigMessage(string title, string message, string rank, uint weapon, int colorText, int colorBackground, MessageType type) : base("MP_BIG_MESSAGE_FREEMODE")
|
||||
{
|
||||
Title = title;
|
||||
Message = message;
|
||||
Rank = rank;
|
||||
WeaponHash = weapon;
|
||||
TextColor = colorText;
|
||||
BackgroundColor = colorBackground;
|
||||
Type = type;
|
||||
Update();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Message information in the Scaleform.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
// Select the correct function to call
|
||||
string function;
|
||||
switch (type)
|
||||
{
|
||||
case MessageType.Customizable:
|
||||
function = "SHOW_SHARD_CENTERED_MP_MESSAGE";
|
||||
break;
|
||||
case MessageType.RankUp:
|
||||
function = "SHOW_SHARD_CREW_RANKUP_MP_MESSAGE";
|
||||
break;
|
||||
case MessageType.MissionPassedOldGen:
|
||||
function = "SHOW_MISSION_PASSED_MESSAGE";
|
||||
break;
|
||||
case MessageType.Wasted:
|
||||
function = "SHOW_SHARD_WASTED_MP_MESSAGE";
|
||||
break;
|
||||
case MessageType.Plane:
|
||||
function = "SHOW_PLANE_MESSAGE";
|
||||
break;
|
||||
case MessageType.CopsAndCrooks:
|
||||
function = "SHOW_BIG_MP_MESSAGE";
|
||||
break;
|
||||
case MessageType.Weapon:
|
||||
function = "SHOW_WEAPON_PURCHASED";
|
||||
break;
|
||||
case MessageType.CenteredLarge:
|
||||
function = "SHOW_CENTERED_MP_MESSAGE_LARGE";
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"{type} is not a valid message type.");
|
||||
}
|
||||
|
||||
// And add the parameters
|
||||
switch (type)
|
||||
{
|
||||
case MessageType.Customizable:
|
||||
CallFunction(function, Title, Message, TextColor, BackgroundColor);
|
||||
break;
|
||||
case MessageType.CopsAndCrooks:
|
||||
CallFunction(function, Title, Message, Rank);
|
||||
break;
|
||||
case MessageType.Weapon:
|
||||
CallFunction(function, Title, Message, (int)weaponHash);
|
||||
break;
|
||||
default:
|
||||
CallFunction(function, Title, Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Fades the big message out.
|
||||
/// </summary>
|
||||
/// <param name="time">The time it will take to do the fade.</param>
|
||||
public void FadeOut(int time)
|
||||
{
|
||||
if (time < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(time), "Time can't be under zero.");
|
||||
}
|
||||
|
||||
CallFunction("SHARD_ANIM_OUT", 0, time);
|
||||
|
||||
#if RAGEMP
|
||||
uint currentTime = (uint)Misc.GetGameTimer();
|
||||
#elif RPH
|
||||
uint currentTime = Game.GameTime;
|
||||
#else
|
||||
uint currentTime = (uint)Game.GameTime;
|
||||
#endif
|
||||
hideAfter = currentTime + (uint)time;
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void DrawFullScreen()
|
||||
{
|
||||
#if RAGEMP
|
||||
uint time = (uint)Misc.GetGameTimer();
|
||||
#elif RPH
|
||||
uint time = Game.GameTime;
|
||||
#else
|
||||
uint time = (uint)Game.GameTime;
|
||||
#endif
|
||||
|
||||
if (hideAfter > 0 && time > hideAfter)
|
||||
{
|
||||
Visible = false;
|
||||
hideAfter = 0;
|
||||
}
|
||||
|
||||
base.DrawFullScreen();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,425 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage;
|
||||
using Rage.Native;
|
||||
using Control = Rage.GameControl;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
#endif
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LemonUI.Scaleform
|
||||
{
|
||||
/// <summary>
|
||||
/// The Background of the BruteForce Hack Minigame.
|
||||
/// </summary>
|
||||
public enum BruteForceBackground
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple Black background.
|
||||
/// </summary>
|
||||
Black = 0,
|
||||
/// <summary>
|
||||
/// A simple Purple background.
|
||||
/// </summary>
|
||||
Purple = 1,
|
||||
/// <summary>
|
||||
/// A simple Gray background.
|
||||
/// </summary>
|
||||
Gray = 2,
|
||||
/// <summary>
|
||||
/// A simple Light Blue background.
|
||||
/// </summary>
|
||||
LightBlue = 3,
|
||||
/// <summary>
|
||||
/// A Light Blue Wallpaper.
|
||||
/// </summary>
|
||||
Wallpaper1 = 4,
|
||||
/// <summary>
|
||||
/// A Fade from Gray in the center to Black in the corners.
|
||||
/// </summary>
|
||||
DarkFade = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The status of the BruteForce Hack after finishing.
|
||||
/// </summary>
|
||||
public enum BruteForceStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The user completed the hack successfully.
|
||||
/// </summary>
|
||||
Completed = 0,
|
||||
/// <summary>
|
||||
/// The user ran out of time.
|
||||
/// </summary>
|
||||
OutOfTime = 1,
|
||||
/// <summary>
|
||||
/// The player ran out of lives.
|
||||
/// </summary>
|
||||
OutOfLives = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event information after an the BruteForce hack has finished.
|
||||
/// </summary>
|
||||
public class BruteForceFinishedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The final status of the Hack.
|
||||
/// </summary>
|
||||
public BruteForceStatus Status { get; }
|
||||
|
||||
internal BruteForceFinishedEventArgs(BruteForceStatus status)
|
||||
{
|
||||
Status = status;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that is called when the end user finishes the BruteForce hack.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">An <see cref="BruteForceFinishedEventArgs"/> with the hack status.</param>
|
||||
public delegate void BruteForceFinishedEventHandler(object sender, BruteForceFinishedEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// The BruteForce Hacking Minigame shown in multiple missions.
|
||||
/// </summary>
|
||||
public class BruteForce : BaseScaleform
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private static readonly Random random = new Random();
|
||||
private static readonly Sound soundRowSwitch = new Sound(string.Empty, "HACKING_MOVE_CURSOR");
|
||||
private static readonly Sound soundRowCompleted = new Sound(string.Empty, "HACKING_CLICK");
|
||||
private static readonly Sound soundRowFailed = new Sound(string.Empty, "HACKING_CLICK_BAD");
|
||||
private static readonly Sound soundSuccess = new Sound(string.Empty, "HACKING_SUCCESS");
|
||||
|
||||
private int hideTime = -1;
|
||||
private int output = 0;
|
||||
private bool firstRun = true;
|
||||
private bool inProgress = false;
|
||||
|
||||
private BruteForceBackground background = BruteForceBackground.Black;
|
||||
private string word = "LEMONADE";
|
||||
private int livesTotal = 5;
|
||||
private int livesCurrent = 5;
|
||||
private int closeAfter = -1;
|
||||
private TimeSpan end = TimeSpan.Zero;
|
||||
private TimeSpan countdown = TimeSpan.Zero;
|
||||
private bool showLives = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The Word shown to select in the menu.
|
||||
/// </summary>
|
||||
public string Word
|
||||
{
|
||||
get => word;
|
||||
set
|
||||
{
|
||||
if (value.Length != 8)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("The word needs to be exactly 8 characters long.", nameof(value));
|
||||
}
|
||||
word = value;
|
||||
CallFunction("SET_ROULETTE_WORD", value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The background of the Hacking minigame.
|
||||
/// </summary>
|
||||
public BruteForceBackground Background
|
||||
{
|
||||
get => background;
|
||||
set
|
||||
{
|
||||
background = value;
|
||||
CallFunction("SET_BACKGROUND", (int)value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The number of Lives of the minigame.
|
||||
/// </summary>
|
||||
public int TotalLives
|
||||
{
|
||||
get => livesTotal;
|
||||
set
|
||||
{
|
||||
livesTotal = value;
|
||||
if (livesCurrent > value)
|
||||
{
|
||||
livesCurrent = value;
|
||||
}
|
||||
CallFunction("SET_LIVES", livesCurrent, value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The current number of lives that the player has.
|
||||
/// </summary>
|
||||
public int CurrentLives => livesCurrent;
|
||||
/// <summary>
|
||||
/// The messages that might appear on success.
|
||||
/// </summary>
|
||||
public List<string> SuccessMessages { get; } = new List<string>();
|
||||
/// <summary>
|
||||
/// The messages that will appear when the player fails.
|
||||
/// </summary>
|
||||
public List<string> FailMessages { get; } = new List<string>();
|
||||
/// <summary>
|
||||
/// The time in milliseconds to wait before closing the Hack window automatically.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be set to -1 to keep the Hack window open.
|
||||
/// </remarks>
|
||||
public int CloseAfter
|
||||
{
|
||||
get => closeAfter;
|
||||
set
|
||||
{
|
||||
if (value < -1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("The Closure time can't be under -1.", nameof(value));
|
||||
}
|
||||
closeAfter = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// If the player can retry the hack after failing.
|
||||
/// </summary>
|
||||
public bool CanRetry { get; set; } = false;
|
||||
/// <summary>
|
||||
/// The countdown of the Hack minigame.
|
||||
/// </summary>
|
||||
public TimeSpan Countdown
|
||||
{
|
||||
get => countdown;
|
||||
set => countdown = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// If the lives of the player should be shown on the top right.
|
||||
/// </summary>
|
||||
public bool ShowLives
|
||||
{
|
||||
get => showLives;
|
||||
set
|
||||
{
|
||||
showLives = value;
|
||||
CallFunction("SHOW_LIVES", value);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// If all of the rows should be restarted after the player fails one.
|
||||
/// </summary>
|
||||
public bool ResetOnRowFail { get; set; } = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the player finishes a hack.
|
||||
/// </summary>
|
||||
public event BruteForceFinishedEventHandler HackFinished;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Hacking Scaleform.
|
||||
/// </summary>
|
||||
public BruteForce() : base("HACKING_PC")
|
||||
{
|
||||
Visible = false;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
SetColumnSpeed(i, 100);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <summary>
|
||||
/// Resets the entire Hacking minigame.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
inProgress = true;
|
||||
|
||||
Background = background;
|
||||
RunProgram(4);
|
||||
RunProgram(83);
|
||||
TotalLives = livesTotal;
|
||||
Word = word;
|
||||
ShowLives = showLives;
|
||||
|
||||
#if RAGEMP
|
||||
int time = Misc.GetGameTimer();
|
||||
#elif RPH
|
||||
uint time = Game.GameTime;
|
||||
#else
|
||||
int time = Game.GameTime;
|
||||
#endif
|
||||
|
||||
end = TimeSpan.FromMilliseconds(time) + countdown;
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the speed of one of the 8 columns.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the column.</param>
|
||||
/// <param name="speed">The speed of the column.</param>
|
||||
public void SetColumnSpeed(int index, float speed)
|
||||
{
|
||||
if (index >= 8 || index < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("The index needs to be between 0 and 7.", nameof(index));
|
||||
}
|
||||
CallFunction("SET_COLUMN_SPEED", index, speed);
|
||||
}
|
||||
/// <summary>
|
||||
/// Runs the specified Hacking program.
|
||||
/// </summary>
|
||||
/// <param name="program">The program to open.</param>
|
||||
public void RunProgram(int program)
|
||||
{
|
||||
CallFunction("RUN_PROGRAM", program);
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the information of the Hacking window.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
#if RAGEMP
|
||||
int time = Misc.GetGameTimer();
|
||||
#elif RPH
|
||||
uint time = Game.GameTime;
|
||||
#else
|
||||
int time = Game.GameTime;
|
||||
#endif
|
||||
|
||||
// If there is a time set to hide the Hack window
|
||||
if (hideTime != -1)
|
||||
{
|
||||
// If that time has already passed, go ahead and hide the window
|
||||
if (hideTime <= time)
|
||||
{
|
||||
Visible = false;
|
||||
hideTime = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the first run and is not in progress, reset it
|
||||
if (firstRun && !inProgress)
|
||||
{
|
||||
firstRun = false;
|
||||
Reset();
|
||||
}
|
||||
|
||||
// If the hack minigame is not in progress but the player can retry and he pressed enter, reset it
|
||||
if (!inProgress && CanRetry && Controls.IsJustPressed(Control.FrontendAccept))
|
||||
{
|
||||
Reset();
|
||||
hideTime = -1;
|
||||
}
|
||||
|
||||
// If the Hack minigame is in progress
|
||||
if (inProgress)
|
||||
{
|
||||
// If there is a countdown set
|
||||
if (countdown > TimeSpan.Zero)
|
||||
{
|
||||
// Calculate the time left
|
||||
TimeSpan span = countdown - (TimeSpan.FromMilliseconds(time) - end);
|
||||
|
||||
// If is lower or equal than zero, the player failed
|
||||
if (span <= TimeSpan.Zero)
|
||||
{
|
||||
CallFunction("SET_COUNTDOWN", 0, 0, 0);
|
||||
string err = FailMessages.Count == 0 ? string.Empty : FailMessages[random.Next(FailMessages.Count)];
|
||||
CallFunction("SET_ROULETTE_OUTCOME", false, err);
|
||||
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
|
||||
inProgress = false;
|
||||
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.OutOfTime));
|
||||
return;
|
||||
}
|
||||
// Otherwise, update the visible time
|
||||
else
|
||||
{
|
||||
CallFunction("SET_COUNTDOWN", span.Minutes, span.Seconds, span.Milliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
// If the user pressed left, go to the left
|
||||
if (Controls.IsJustPressed(Control.MoveLeftOnly) || Controls.IsJustPressed(Control.FrontendLeft))
|
||||
{
|
||||
soundRowSwitch.PlayFrontend();
|
||||
CallFunction("SET_INPUT_EVENT", 10);
|
||||
}
|
||||
// If the user pressed right, go to the right
|
||||
else if (Controls.IsJustPressed(Control.MoveRightOnly) || Controls.IsJustPressed(Control.FrontendRight))
|
||||
{
|
||||
soundRowSwitch.PlayFrontend();
|
||||
CallFunction("SET_INPUT_EVENT", 11);
|
||||
}
|
||||
// If the user pressed accept, send the selection event
|
||||
else if (Controls.IsJustPressed(Control.FrontendAccept))
|
||||
{
|
||||
output = CallFunctionReturn("SET_INPUT_EVENT_SELECT");
|
||||
}
|
||||
|
||||
// If there is some output to receive
|
||||
if (output != 0)
|
||||
{
|
||||
// If the value is ready, go ahead and check it
|
||||
if (IsValueReady(output))
|
||||
{
|
||||
switch (GetValue<int>(output))
|
||||
{
|
||||
case 86: // Hack Completed
|
||||
string ok = SuccessMessages.Count == 0 ? string.Empty : SuccessMessages[random.Next(SuccessMessages.Count)];
|
||||
CallFunction("SET_ROULETTE_OUTCOME", true, ok);
|
||||
soundSuccess.PlayFrontend();
|
||||
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.Completed));
|
||||
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
|
||||
inProgress = false;
|
||||
break;
|
||||
case 87: // Row Failed (or lives failed)
|
||||
livesCurrent--;
|
||||
CallFunction("SET_LIVES", livesCurrent, livesTotal);
|
||||
soundRowFailed.PlayFrontend();
|
||||
if (livesCurrent <= 0)
|
||||
{
|
||||
string err = FailMessages.Count == 0 ? string.Empty : FailMessages[random.Next(FailMessages.Count)];
|
||||
CallFunction("SET_ROULETTE_OUTCOME", false, err);
|
||||
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
|
||||
inProgress = false;
|
||||
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.OutOfLives));
|
||||
}
|
||||
break;
|
||||
case 92: // Row Completed
|
||||
soundRowCompleted.PlayFrontend();
|
||||
break;
|
||||
}
|
||||
output = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace LemonUI.Scaleform
|
||||
{
|
||||
/// <summary>
|
||||
/// Scaleforms are 2D Adobe Flash-like objects.
|
||||
/// </summary>
|
||||
public interface IScaleform : IDrawable, IProcessable, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws the Scaleform in full screen.
|
||||
/// </summary>
|
||||
void DrawFullScreen();
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage.Native;
|
||||
using Control = Rage.GameControl;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
#endif
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LemonUI.Scaleform
|
||||
{
|
||||
/// <summary>
|
||||
/// An individual instructional button.
|
||||
/// </summary>
|
||||
public struct InstructionalButton
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private Control control;
|
||||
private string raw;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The description of this button.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// The Control used by this button.
|
||||
/// </summary>
|
||||
public Control Control
|
||||
{
|
||||
get => control;
|
||||
set
|
||||
{
|
||||
control = value;
|
||||
#if FIVEM
|
||||
raw = API.GetControlInstructionalButton(2, (int)value, 1);
|
||||
#elif RAGEMP
|
||||
raw = Invoker.Invoke<string>(Natives.GetControlInstructionalButton, 2, (int)value, 1);
|
||||
#elif RPH
|
||||
raw = (string)NativeFunction.CallByHash(0x0499D7B09FC9B407, typeof(string), 2, (int)control, 1);
|
||||
#elif SHVDN3
|
||||
raw = Function.Call<string>(Hash.GET_CONTROL_INSTRUCTIONAL_BUTTON, 2, (int)value, 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Raw Control sent to the Scaleform.
|
||||
/// </summary>
|
||||
public string Raw
|
||||
{
|
||||
get => raw;
|
||||
set
|
||||
{
|
||||
raw = value;
|
||||
control = (Control)(-1);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instructional button for a Control.
|
||||
/// </summary>
|
||||
/// <param name="description">The text for the description.</param>
|
||||
/// <param name="control">The control to use.</param>
|
||||
public InstructionalButton(string description, Control control)
|
||||
{
|
||||
Description = description;
|
||||
this.control = control;
|
||||
#if FIVEM
|
||||
raw = API.GetControlInstructionalButton(2, (int)control, 1);
|
||||
#elif RAGEMP
|
||||
raw = Invoker.Invoke<string>(Natives.GetControlInstructionalButton, 2, (int)control, 1);
|
||||
#elif RPH
|
||||
raw = (string)NativeFunction.CallByHash(0x0499D7B09FC9B407, typeof(string), 2, (int)control, 1);
|
||||
#elif SHVDN3
|
||||
raw = Function.Call<string>(Hash.GET_CONTROL_INSTRUCTIONAL_BUTTON, 2, (int)control, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instructional button for a raw control.
|
||||
/// </summary>
|
||||
/// <param name="description">The text for the description.</param>
|
||||
/// <param name="raw">The raw value of the control.</param>
|
||||
public InstructionalButton(string description, string raw)
|
||||
{
|
||||
Description = description;
|
||||
control = (Control)(-1);
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buttons shown on the bottom right of the screen.
|
||||
/// </summary>
|
||||
public class InstructionalButtons : BaseScaleform
|
||||
{
|
||||
#region Public Fields
|
||||
|
||||
/// <summary>
|
||||
/// The buttons used in this Scaleform.
|
||||
/// </summary>
|
||||
private readonly List<InstructionalButton> buttons = new List<InstructionalButton>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new set of Instructional Buttons.
|
||||
/// </summary>
|
||||
/// <param name="buttons">The buttons to add into this menu.</param>
|
||||
public InstructionalButtons(params InstructionalButton[] buttons) : base("INSTRUCTIONAL_BUTTONS")
|
||||
{
|
||||
this.buttons.AddRange(buttons);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Adds an Instructional Button.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to add.</param>
|
||||
public void Add(InstructionalButton button)
|
||||
{
|
||||
// If the item is already in the list, raise an exception
|
||||
if (buttons.Contains(button))
|
||||
{
|
||||
throw new InvalidOperationException("The button is already in the Scaleform.");
|
||||
}
|
||||
|
||||
// Otherwise, add it to the list of items
|
||||
buttons.Add(button);
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes an Instructional Button.
|
||||
/// </summary>
|
||||
/// <param name="button">The button to remove.</param>
|
||||
public void Remove(InstructionalButton button)
|
||||
{
|
||||
// If the button is not in the list, return
|
||||
if (!buttons.Contains(button))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, remove it
|
||||
buttons.Remove(button);
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the instructional buttons.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
buttons.Clear();
|
||||
}
|
||||
/// <summary>
|
||||
/// Refreshes the items shown in the Instructional buttons.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
// Clear all of the existing items
|
||||
CallFunction("CLEAR_ALL");
|
||||
CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
|
||||
CallFunction("CREATE_CONTAINER");
|
||||
|
||||
// And add them again
|
||||
for (int i = 0; i < buttons.Count; i++)
|
||||
{
|
||||
InstructionalButton button = buttons[i];
|
||||
CallFunction("SET_DATA_SLOT", i, button.Raw, button.Description);
|
||||
}
|
||||
|
||||
CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
namespace LemonUI.Scaleform
|
||||
{
|
||||
/// <summary>
|
||||
/// Loading screen like the transition between story mode and online.
|
||||
/// </summary>
|
||||
public class LoadingScreen : BaseScaleform
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The title of the loading screen.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// The subtitle of the loading screen.
|
||||
/// </summary>
|
||||
public string Subtitle { get; set; }
|
||||
/// <summary>
|
||||
/// The description of the loading screen.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
/// <summary>
|
||||
/// The Texture Dictionary (TXD) where the texture is loaded.
|
||||
/// </summary>
|
||||
public string Dictionary { get; private set; }
|
||||
/// <summary>
|
||||
/// The texture in the dictionary.
|
||||
/// </summary>
|
||||
public string Texture { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GTA Online like loading screen with no image.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the screen.</param>
|
||||
/// <param name="subtitle">The subtitle of the screen.</param>
|
||||
/// <param name="description">The description of the screen.</param>
|
||||
public LoadingScreen(string title, string subtitle, string description) : this(title, subtitle, description, string.Empty, string.Empty)
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a new GTA Online like loading screen with a custom texture.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the screen.</param>
|
||||
/// <param name="subtitle">The subtitle of the screen.</param>
|
||||
/// <param name="description">The description of the screen.</param>
|
||||
/// <param name="dictionary">The dictionary where the texture is located.</param>
|
||||
/// <param name="texture">The texture to use on the right.</param>
|
||||
public LoadingScreen(string title, string subtitle, string description, string dictionary, string texture) : base("GTAV_ONLINE")
|
||||
{
|
||||
// Tell the Scaleform to use the online loading screen
|
||||
CallFunction("HIDE_ONLINE_LOGO");
|
||||
CallFunction("SETUP_BIGFEED", false);
|
||||
// Save the values
|
||||
Title = title;
|
||||
Subtitle = subtitle;
|
||||
Description = description;
|
||||
Dictionary = dictionary;
|
||||
Texture = texture;
|
||||
// And send them back to the scaleform
|
||||
Update();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Changes the texture shown on the loading screen.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The Texture Dictionary or TXD.</param>
|
||||
/// <param name="texture">The Texture name.</param>
|
||||
public void ChangeTexture(string dictionary, string texture)
|
||||
{
|
||||
Dictionary = dictionary;
|
||||
Texture = texture;
|
||||
Update();
|
||||
}
|
||||
/// <summary>
|
||||
/// Updates the Title, Description and Image of the loading screen.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
CallFunction("SET_BIGFEED_INFO", "footerStr", Description, 0, Dictionary, Texture, Subtitle, "urlDeprecated", Title);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
namespace LemonUI.Scaleform
|
||||
{
|
||||
/// <summary>
|
||||
/// A warning pop-up.
|
||||
/// </summary>
|
||||
public class PopUp : BaseScaleform
|
||||
{
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The title of the Pop-up.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// The subtitle of the Pop-up.
|
||||
/// </summary>
|
||||
public string Subtitle { get; set; }
|
||||
/// <summary>
|
||||
/// The prompt of the Pop-up.
|
||||
/// </summary>
|
||||
public string Prompt { get; set; }
|
||||
/// <summary>
|
||||
/// If the black background should be shown.
|
||||
/// </summary>
|
||||
public bool ShowBackground { get; set; } = true;
|
||||
/// <summary>
|
||||
/// The error message to show.
|
||||
/// </summary>
|
||||
public string Error { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Pop-up instance.
|
||||
/// </summary>
|
||||
public PopUp() : base("POPUP_WARNING")
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <summary>
|
||||
/// Updates the texts of the Pop-up.
|
||||
/// </summary>
|
||||
public override void Update()
|
||||
{
|
||||
// first parameter "msecs" is unused
|
||||
CallFunction("SHOW_POPUP_WARNING", 0, Title, Subtitle, Prompt, ShowBackground, 0, Error);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.Native;
|
||||
using CitizenFX.Core.UI;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage;
|
||||
using Rage.Native;
|
||||
using Control = Rage.GameControl;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.UI;
|
||||
#endif
|
||||
using LemonUI.Extensions;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the internal alignment of screen elements.
|
||||
/// </summary>
|
||||
public enum GFXAlignment
|
||||
{
|
||||
/// <summary>
|
||||
/// Vertical Alignment to the Bottom.
|
||||
/// </summary>
|
||||
Bottom = 66,
|
||||
/// <summary>
|
||||
/// Vertical Alignment to the Top.
|
||||
/// </summary>
|
||||
Top = 84,
|
||||
/// <summary>
|
||||
/// Centered Vertically or Horizontally.
|
||||
/// </summary>
|
||||
Center = 67,
|
||||
/// <summary>
|
||||
/// Horizontal Alignment to the Left.
|
||||
/// </summary>
|
||||
Left = 76,
|
||||
/// <summary>
|
||||
/// Horizontal Alignment to the Right.
|
||||
/// </summary>
|
||||
Right = 82,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains a set of tools to work with the screen information.
|
||||
/// </summary>
|
||||
public static class Screen
|
||||
{
|
||||
/// <summary>
|
||||
/// The Aspect Ratio of the screen resolution.
|
||||
/// </summary>
|
||||
public static float AspectRatio
|
||||
{
|
||||
get
|
||||
{
|
||||
#if FIVEM
|
||||
return API.GetAspectRatio(false);
|
||||
#elif RAGEMP
|
||||
return Invoker.Invoke<float>(Natives.GetAspectRatio);
|
||||
#elif RPH
|
||||
return NativeFunction.CallByHash<float>(0xF1307EF624A80D87, false);
|
||||
#elif SHVDN3
|
||||
return Function.Call<float>(Hash._GET_ASPECT_RATIO, false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The location of the cursor on screen between 0 and 1.
|
||||
/// </summary>
|
||||
public static PointF CursorPositionRelative
|
||||
{
|
||||
get
|
||||
{
|
||||
#if FIVEM
|
||||
float cursorX = API.GetControlNormal(0, (int)Control.CursorX);
|
||||
float cursorY = API.GetControlNormal(0, (int)Control.CursorY);
|
||||
#elif RAGEMP
|
||||
float cursorX = Invoker.Invoke<float>(Natives.GetControlNormal, 0, (int)Control.CursorX);
|
||||
float cursorY = Invoker.Invoke<float>(Natives.GetControlNormal, 0, (int)Control.CursorY);
|
||||
#elif RPH
|
||||
float cursorX = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.CursorX);
|
||||
float cursorY = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.CursorY);
|
||||
#elif SHVDN3
|
||||
float cursorX = Function.Call<float>(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorX);
|
||||
float cursorY = Function.Call<float>(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorY);
|
||||
#endif
|
||||
return new PointF(cursorX, cursorY);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative resolution into one scaled to 1080p.
|
||||
/// </summary>
|
||||
/// <param name="relativeX">The relative value of X.</param>
|
||||
/// <param name="relativeY">The relative value of Y.</param>
|
||||
/// <param name="absoluteX">The value of X scaled to 1080p.</param>
|
||||
/// <param name="absoluteY">The value of Y scaled to 1080p.</param>
|
||||
public static void ToAbsolute(float relativeX, float relativeY, out float absoluteX, out float absoluteY)
|
||||
{
|
||||
// Get the real width based on the aspect ratio
|
||||
float width = 1080f * AspectRatio;
|
||||
// And save the correct values
|
||||
absoluteX = width * relativeX;
|
||||
absoluteY = 1080f * relativeY;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts a 1080p-based resolution into relative values.
|
||||
/// </summary>
|
||||
/// <param name="absoluteX">The 1080p based X coord.</param>
|
||||
/// <param name="absoluteY">The 1080p based Y coord.</param>
|
||||
/// <param name="relativeX">The value of X converted to relative.</param>
|
||||
/// <param name="relativeY">The value of Y converted to relative.</param>
|
||||
public static void ToRelative(float absoluteX, float absoluteY, out float relativeX, out float relativeY)
|
||||
{
|
||||
// Get the real width based on the aspect ratio
|
||||
float width = 1080f * AspectRatio;
|
||||
// And save the correct values
|
||||
relativeX = absoluteX / width;
|
||||
relativeY = absoluteY / 1080f;
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks if the cursor is inside of the specified area.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function takes values scaled to 1080p and is aware of the alignment set via SET_SCRIPT_GFX_ALIGN.
|
||||
/// </remarks>
|
||||
/// <param name="pos">The start of the area.</param>
|
||||
/// <param name="size">The size of the area to check.</param>
|
||||
/// <returns><see langword="true"/> if the cursor is in the specified bounds, <see langword="false"/> otherwise.</returns>
|
||||
public static bool IsCursorInArea(PointF pos, SizeF size) => IsCursorInArea(pos.X, pos.Y, size.Width, size.Height);
|
||||
/// <summary>
|
||||
/// Checks if the cursor is inside of the specified area.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This function takes values scaled to 1080p and is aware of the alignment set via SET_SCRIPT_GFX_ALIGN.
|
||||
/// </remarks>
|
||||
/// <param name="x">The start X position.</param>
|
||||
/// <param name="y">The start Y position.</param>
|
||||
/// <param name="width">The height of the search area from X.</param>
|
||||
/// <param name="height">The height of the search area from Y.</param>
|
||||
/// <returns><see langword="true"/> if the cursor is in the specified bounds, <see langword="false"/> otherwise.</returns>
|
||||
public static bool IsCursorInArea(float x, float y, float width, float height)
|
||||
{
|
||||
PointF cursor = CursorPositionRelative;
|
||||
|
||||
ToRelative(width, height, out float realWidth, out float realHeight);
|
||||
|
||||
PointF realPos = GetRealPosition(x, y).ToRelative();
|
||||
|
||||
bool isX = cursor.X >= realPos.X && cursor.X <= realPos.X + realWidth;
|
||||
bool isY = cursor.Y > realPos.Y && cursor.Y < realPos.Y + realHeight;
|
||||
|
||||
return isX && isY;
|
||||
}
|
||||
/// <summary>
|
||||
/// Converts the specified position into one that is aware of <see cref="SetElementAlignment(GFXAlignment, GFXAlignment)"/>.
|
||||
/// </summary>
|
||||
/// <param name="og">The original 1080p based position.</param>
|
||||
/// <returns>A new 1080p based position that is aware of the the Alignment.</returns>
|
||||
public static PointF GetRealPosition(PointF og) => GetRealPosition(og.X, og.Y);
|
||||
/// <summary>
|
||||
/// Converts the specified position into one that is aware of <see cref="SetElementAlignment(GFXAlignment, GFXAlignment)"/>.
|
||||
/// </summary>
|
||||
/// <param name="x">The 1080p based X position.</param>
|
||||
/// <param name="y">The 1080p based Y position.</param>
|
||||
/// <returns>A new 1080p based position that is aware of the the Alignment.</returns>
|
||||
public static PointF GetRealPosition(float x, float y)
|
||||
{
|
||||
// Convert the resolution to relative
|
||||
ToRelative(x, y, out float relativeX, out float relativeY);
|
||||
// Request the real location of the position
|
||||
float realX = 0, realY = 0;
|
||||
#if FIVEM
|
||||
API.GetScriptGfxPosition(relativeX, relativeY, ref realX, ref realY);
|
||||
#elif RAGEMP
|
||||
FloatReference argX = new FloatReference();
|
||||
FloatReference argY = new FloatReference();
|
||||
Invoker.Invoke<int>(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY);
|
||||
realX = argX.Value;
|
||||
realY = argY.Value;
|
||||
#elif RPH
|
||||
using (NativePointer argX = new NativePointer())
|
||||
using (NativePointer argY = new NativePointer())
|
||||
{
|
||||
NativeFunction.CallByHash<int>(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY);
|
||||
realX = argX.GetValue<float>();
|
||||
realY = argY.GetValue<float>();
|
||||
}
|
||||
#elif SHVDN3
|
||||
OutputArgument argX = new OutputArgument();
|
||||
OutputArgument argY = new OutputArgument();
|
||||
Function.Call((Hash)0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); // _GET_SCRIPT_GFX_POSITION
|
||||
realX = argX.GetResult<float>();
|
||||
realY = argY.GetResult<float>();
|
||||
#endif
|
||||
// And return it converted to absolute
|
||||
ToAbsolute(realX, realY, out float absoluteX, out float absoluteY);
|
||||
return new PointF(absoluteX, absoluteY);
|
||||
}
|
||||
/// <summary>
|
||||
/// Shows the cursor during the current game frame.
|
||||
/// </summary>
|
||||
public static void ShowCursorThisFrame()
|
||||
{
|
||||
#if FIVEM
|
||||
API.SetMouseCursorActiveThisFrame();
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xAAE7CE1D63167423);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xAAE7CE1D63167423);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash._SET_MOUSE_CURSOR_ACTIVE_THIS_FRAME);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the alignment of game elements like <see cref="Elements.ScaledRectangle"/>, <see cref="Elements.ScaledText"/> and <see cref="Elements.ScaledTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="horizontal">The Horizontal alignment of the items.</param>
|
||||
/// <param name="vertical">The vertical alignment of the items.</param>
|
||||
public static void SetElementAlignment(Alignment horizontal, GFXAlignment vertical)
|
||||
{
|
||||
// If the enum value is not correct, raise an exception
|
||||
if (!Enum.IsDefined(typeof(Alignment), horizontal))
|
||||
{
|
||||
throw new ArgumentException("Alignment is not one of the allowed values (Left, Right, Center).", nameof(horizontal));
|
||||
}
|
||||
|
||||
// Otherwise, just call the correct function
|
||||
switch (horizontal)
|
||||
{
|
||||
case Alignment.Left:
|
||||
SetElementAlignment(GFXAlignment.Left, vertical);
|
||||
break;
|
||||
case Alignment.Right:
|
||||
SetElementAlignment(GFXAlignment.Right, vertical);
|
||||
break;
|
||||
case Alignment.Center:
|
||||
SetElementAlignment(GFXAlignment.Right, vertical);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets the alignment of game elements like <see cref="Elements.ScaledRectangle"/>, <see cref="Elements.ScaledText"/> and <see cref="Elements.ScaledTexture"/>.
|
||||
/// </summary>
|
||||
/// <param name="horizontal">The Horizontal alignment of the items.</param>
|
||||
/// <param name="vertical">The vertical alignment of the items.</param>
|
||||
public static void SetElementAlignment(GFXAlignment horizontal, GFXAlignment vertical)
|
||||
{
|
||||
#if FIVEM
|
||||
API.SetScriptGfxAlign((int)horizontal, (int)vertical);
|
||||
API.SetScriptGfxAlignParams(0, 0, 0, 0);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xB8A850F20A067EB6, (int)horizontal, (int)vertical);
|
||||
Invoker.Invoke(0xF5A2C681787E579D, 0, 0, 0, 0);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xB8A850F20A067EB6, (int)horizontal, (int)vertical);
|
||||
NativeFunction.CallByHash<int>(0xF5A2C681787E579D, 0, 0, 0, 0);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.SET_SCRIPT_GFX_ALIGN, (int)horizontal, (int)vertical);
|
||||
Function.Call(Hash.SET_SCRIPT_GFX_ALIGN_PARAMS, 0, 0, 0, 0);
|
||||
#endif
|
||||
}
|
||||
/// <summary>
|
||||
/// Resets the alignment of the game elements.
|
||||
/// </summary>
|
||||
public static void ResetElementAlignment()
|
||||
{
|
||||
#if FIVEM
|
||||
API.ResetScriptGfxAlign();
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(0xE3A3DB414A373DAB);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0xE3A3DB414A373DAB);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.RESET_SCRIPT_GFX_ALIGN);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core;
|
||||
using CitizenFX.Core.Native;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage;
|
||||
using Rage.Native;
|
||||
#elif SHVDN3
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
#endif
|
||||
|
||||
namespace LemonUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information for a Game Sound that is played at specific times.
|
||||
/// </summary>
|
||||
public class Sound
|
||||
{
|
||||
/// <summary>
|
||||
/// The Set where the sound is located.
|
||||
/// </summary>
|
||||
public string Set { get; set; }
|
||||
/// <summary>
|
||||
/// The name of the sound file.
|
||||
/// </summary>
|
||||
public string File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Sound"/> class with the specified Sound Set and File.
|
||||
/// </summary>
|
||||
/// <param name="set">The Set where the sound is located.</param>
|
||||
/// <param name="file">The name of the sound file.</param>
|
||||
public Sound(string set, string file)
|
||||
{
|
||||
Set = set;
|
||||
File = file;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays the sound for the local <see cref="Player"/>.
|
||||
/// </summary>
|
||||
public void PlayFrontend()
|
||||
{
|
||||
#if FIVEM
|
||||
API.PlaySoundFrontend(-1, File, Set, false);
|
||||
int id = API.GetSoundId();
|
||||
API.ReleaseSoundId(id);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.PlaySoundFrontend, -1, File, Set, false);
|
||||
int id = Invoker.Invoke<int>(Natives.GetSoundId);
|
||||
Invoker.Invoke(Natives.ReleaseSoundId, id);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x67C540AA08E4A6F5, -1, File, Set, false);
|
||||
int id = NativeFunction.CallByHash<int>(0x430386FE9BF80B45);
|
||||
NativeFunction.CallByHash<int>(0x353FC880830B88FA, id);
|
||||
#elif SHVDN3
|
||||
Function.Call(Hash.PLAY_SOUND_FRONTEND, -1, File, Set, false);
|
||||
int id = Function.Call<int>(Hash.GET_SOUND_ID);
|
||||
Function.Call(Hash.RELEASE_SOUND_ID, id);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
namespace LemonUI.TimerBars
|
||||
{
|
||||
/// <summary>
|
||||
/// The spacing of the objectives in the timer bar.
|
||||
/// </summary>
|
||||
public enum ObjectiveSpacing
|
||||
{
|
||||
/// <summary>
|
||||
/// The objectives will be equally spaced.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you have way too many objectives and not enough width, you might end up with objectives overlapping each other.
|
||||
/// </remarks>
|
||||
Equal = 0,
|
||||
/// <summary>
|
||||
/// The items will all have the same spacing.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you have way too many objectives and not enough width, the objectives might end up outside of the timer bar.
|
||||
/// </remarks>
|
||||
Fixed = 1
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core.UI;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif SHVDN3
|
||||
using GTA.UI;
|
||||
#endif
|
||||
using LemonUI.Elements;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.TimerBars
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Bar with text information shown in the bottom right.
|
||||
/// </summary>
|
||||
public class TimerBar : IDrawable
|
||||
{
|
||||
#region Constant Fields
|
||||
|
||||
/// <summary>
|
||||
/// The separation between the different timer bars.
|
||||
/// </summary>
|
||||
internal const float separation = 6.25f;
|
||||
/// <summary>
|
||||
/// The width of the background.
|
||||
/// </summary>
|
||||
internal const float backgroundWidth = 220;
|
||||
/// <summary>
|
||||
/// The height of the background.
|
||||
/// </summary>
|
||||
internal const float backgroundHeight = 37;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private string rawTitle = string.Empty;
|
||||
private string rawInfo = string.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Fields
|
||||
|
||||
/// <summary>
|
||||
/// The background of the timer bar.
|
||||
/// </summary>
|
||||
internal protected readonly ScaledTexture background = new ScaledTexture("timerbars", "all_black_bg")
|
||||
{
|
||||
Color = Color.FromArgb(160, 255, 255, 255)
|
||||
};
|
||||
/// <summary>
|
||||
/// The title of the timer bar.
|
||||
/// </summary>
|
||||
internal protected readonly ScaledText title = new ScaledText(PointF.Empty, string.Empty, 0.29f)
|
||||
{
|
||||
Alignment = Alignment.Right,
|
||||
WordWrap = 1000
|
||||
};
|
||||
/// <summary>
|
||||
/// The information of the Timer Bar.
|
||||
/// </summary>
|
||||
internal protected readonly ScaledText info = new ScaledText(PointF.Empty, string.Empty, 0.5f)
|
||||
{
|
||||
Alignment = Alignment.Right,
|
||||
WordWrap = 1000
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The title of the bar, shown on the left.
|
||||
/// </summary>
|
||||
public string Title
|
||||
{
|
||||
get => rawTitle;
|
||||
set
|
||||
{
|
||||
rawTitle = value;
|
||||
title.Text = value.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The information shown on the right.
|
||||
/// </summary>
|
||||
public string Info
|
||||
{
|
||||
get => rawInfo;
|
||||
set
|
||||
{
|
||||
rawInfo = value;
|
||||
info.Text = value.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Width of the information text.
|
||||
/// </summary>
|
||||
public float InfoWidth => info.Width;
|
||||
/// <summary>
|
||||
/// The color of the information text.
|
||||
/// </summary>
|
||||
public Color Color
|
||||
{
|
||||
get => info.Color;
|
||||
set => info.Color = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="TimerBar"/> with the specified Title and Value.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the bar.</param>
|
||||
/// <param name="info">The information shown on the bar.</param>
|
||||
public TimerBar(string title, string info)
|
||||
{
|
||||
Title = title;
|
||||
Info = info;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the position of the timer bar elements based on the location of it on the screen.
|
||||
/// </summary>
|
||||
/// <param name="pos">The Top Left position of the Timer Bar.</param>
|
||||
public virtual void Recalculate(PointF pos)
|
||||
{
|
||||
background.Position = pos;
|
||||
background.Size = new SizeF(backgroundWidth, backgroundHeight);
|
||||
title.Position = new PointF(pos.X + 91, pos.Y + 8);
|
||||
info.Position = new PointF(pos.X + 218, pos.Y - 3);
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the timer bar information.
|
||||
/// </summary>
|
||||
public virtual void Draw()
|
||||
{
|
||||
background.Draw();
|
||||
title.Draw();
|
||||
info.Draw();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
#if FIVEM
|
||||
using CitizenFX.Core.UI;
|
||||
#elif RAGEMP
|
||||
using RAGE.Game;
|
||||
#elif RPH
|
||||
using Rage;
|
||||
using Rage.Native;
|
||||
#elif SHVDN3
|
||||
using GTA.UI;
|
||||
#endif
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.TimerBars
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection or Set of <see cref="TimerBar"/>.
|
||||
/// </summary>
|
||||
public class TimerBarCollection : IContainer<TimerBar>
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// If this collection of Timer Bars is visible to the user.
|
||||
/// </summary>
|
||||
public bool Visible { get; set; } = true;
|
||||
/// <summary>
|
||||
/// The <see cref="TimerBar"/>s that are part of this collection.
|
||||
/// </summary>
|
||||
public List<TimerBar> TimerBars { get; } = new List<TimerBar>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new collection of Timer Bars.
|
||||
/// </summary>
|
||||
/// <param name="bars"></param>
|
||||
public TimerBarCollection(params TimerBar[] bars)
|
||||
{
|
||||
TimerBars.AddRange(bars);
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="TimerBar"/> onto this collection.
|
||||
/// </summary>
|
||||
/// <param name="bar">The <see cref="TimerBar"/> to add.</param>
|
||||
public void Add(TimerBar bar)
|
||||
{
|
||||
// If the item is already on the list, raise an exception
|
||||
if (TimerBars.Contains(bar))
|
||||
{
|
||||
throw new InvalidOperationException("The item is already part of the menu.");
|
||||
}
|
||||
// Also raise an exception if is null
|
||||
if (bar == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bar));
|
||||
}
|
||||
// If we got here, add it
|
||||
TimerBars.Add(bar);
|
||||
// And recalculate the positions of the existing items
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes a <see cref="TimerBar"/> from the Collection.
|
||||
/// </summary>
|
||||
/// <param name="bar">The <see cref="TimerBar"/> to remove.</param>
|
||||
public void Remove(TimerBar bar)
|
||||
{
|
||||
// If the bar is not present, return
|
||||
if (!TimerBars.Contains(bar))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, remove it
|
||||
TimerBars.Remove(bar);
|
||||
// And recalculate the positions
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the <see cref="TimerBar"/> that match the function.
|
||||
/// </summary>
|
||||
/// <param name="func">The function to check the <see cref="TimerBar"/>.</param>
|
||||
public void Remove(Func<TimerBar, bool> func)
|
||||
{
|
||||
// Iterate over the timer bars
|
||||
foreach (TimerBar bar in new List<TimerBar>(TimerBars))
|
||||
{
|
||||
// If it matches the function, remove it
|
||||
if (func(bar))
|
||||
{
|
||||
TimerBars.Remove(bar);
|
||||
}
|
||||
}
|
||||
// Finally, recalculate the positions
|
||||
Recalculate();
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes all of the <see cref="TimerBar"/> in this collection.
|
||||
/// </summary>
|
||||
public void Clear() => TimerBars.Clear();
|
||||
/// <summary>
|
||||
/// Checks if the <see cref="TimerBar"/> is part of this collection.
|
||||
/// </summary>
|
||||
/// <param name="bar">The <see cref="TimerBar"/> to check.</param>
|
||||
public bool Contains(TimerBar bar) => TimerBars.Contains(bar);
|
||||
/// <summary>
|
||||
/// Recalculates the positions and sizes of the <see cref="TimerBar"/>.
|
||||
/// </summary>
|
||||
public void Recalculate()
|
||||
{
|
||||
// Get the position of 0,0 while staying safe zone aware
|
||||
Screen.SetElementAlignment(GFXAlignment.Right, GFXAlignment.Bottom);
|
||||
PointF pos = Screen.GetRealPosition(PointF.Empty);
|
||||
Screen.ResetElementAlignment();
|
||||
|
||||
// Iterate over the existing timer bars and save the count
|
||||
int count = 0;
|
||||
foreach (TimerBar timerBar in TimerBars)
|
||||
{
|
||||
// And send them to the timer bar
|
||||
timerBar.Recalculate(new PointF(pos.X - TimerBar.backgroundWidth, pos.Y - (TimerBar.backgroundHeight * (TimerBars.Count - count)) - (TimerBar.separation * (TimerBars.Count - count - 1))));
|
||||
// Finish by increasing the total count of items
|
||||
count++;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the known timer bars.
|
||||
/// </summary>
|
||||
public void Process()
|
||||
{
|
||||
// If there are no timer bars or the collection is disabled, return
|
||||
if (TimerBars.Count == 0 || !Visible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the texts in the bottom right corner of the screen
|
||||
#if FIVEM
|
||||
CitizenFX.Core.UI.Screen.Hud.HideComponentThisFrame(HudComponent.AreaName);
|
||||
CitizenFX.Core.UI.Screen.Hud.HideComponentThisFrame(HudComponent.StreetName);
|
||||
CitizenFX.Core.UI.Screen.Hud.HideComponentThisFrame(HudComponent.VehicleName);
|
||||
#elif RAGEMP
|
||||
Invoker.Invoke(Natives.HideHudComponentThisFrame, HudComponent.AreaName);
|
||||
Invoker.Invoke(Natives.HideHudComponentThisFrame, HudComponent.StreetName);
|
||||
Invoker.Invoke(Natives.HideHudComponentThisFrame, HudComponent.VehicleName);
|
||||
#elif RPH
|
||||
NativeFunction.CallByHash<int>(0x6806C51AD12B83B8, 7);
|
||||
NativeFunction.CallByHash<int>(0x6806C51AD12B83B8, 9);
|
||||
NativeFunction.CallByHash<int>(0x6806C51AD12B83B8, 6);
|
||||
#elif SHVDN3
|
||||
Hud.HideComponentThisFrame(HudComponent.AreaName);
|
||||
Hud.HideComponentThisFrame(HudComponent.StreetName);
|
||||
Hud.HideComponentThisFrame(HudComponent.VehicleName);
|
||||
#endif
|
||||
// Draw the existing timer bars
|
||||
foreach (TimerBar timerBar in TimerBars)
|
||||
{
|
||||
timerBar.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using LemonUI.Elements;
|
||||
|
||||
namespace LemonUI.TimerBars
|
||||
{
|
||||
/// <summary>
|
||||
/// A timer bar for a specific amount of objectives.
|
||||
/// </summary>
|
||||
public class TimerBarObjective : TimerBar
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private const float width = 20;
|
||||
private const float height = 20;
|
||||
|
||||
private static readonly Color colorWhite = Color.FromArgb(255, 255, 255);
|
||||
private static readonly Color colorCompleted = Color.FromArgb(101, 180, 212);
|
||||
|
||||
private readonly List<ScaledTexture> objectives = new List<ScaledTexture>();
|
||||
|
||||
private PointF lastPosition = default;
|
||||
|
||||
private int count = 1;
|
||||
private int completed = 0;
|
||||
private Color colorSet = colorCompleted;
|
||||
private ObjectiveSpacing objectiveSpacing = ObjectiveSpacing.Equal;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// The number of objectives shown in the timer bar.
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get => count;
|
||||
set
|
||||
{
|
||||
if (count == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The number of objectives can't be under or equal to zero.");
|
||||
}
|
||||
|
||||
count = value;
|
||||
UpdateObjectiveCount();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The number of completed objectives.
|
||||
/// </summary>
|
||||
public int Completed
|
||||
{
|
||||
get => completed;
|
||||
set
|
||||
{
|
||||
if (completed == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (value <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The number of completed objectives can't be under zero.");
|
||||
}
|
||||
if (value > Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "The number of completed objectives can't be over the total number of objectives.");
|
||||
}
|
||||
|
||||
completed = value;
|
||||
|
||||
UpdateObjectiveColors();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The color used for completed objectives.
|
||||
/// </summary>
|
||||
public Color CompletedColor
|
||||
{
|
||||
get => colorSet;
|
||||
set
|
||||
{
|
||||
if (colorSet == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
colorSet = value;
|
||||
UpdateObjectiveColors();
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectiveSpacing Spacing
|
||||
{
|
||||
get => objectiveSpacing;
|
||||
set
|
||||
{
|
||||
if (objectiveSpacing == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
objectiveSpacing = value;
|
||||
Recalculate(lastPosition);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new timer bar used to show objectives.
|
||||
/// </summary>
|
||||
public TimerBarObjective(string title) : base(title, string.Empty)
|
||||
{
|
||||
UpdateObjectiveCount();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tools
|
||||
|
||||
private void UpdateObjectiveCount()
|
||||
{
|
||||
// just to make sure
|
||||
if (completed > count)
|
||||
{
|
||||
completed = count;
|
||||
}
|
||||
|
||||
objectives.Clear();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
objectives.Add(new ScaledTexture("timerbars", "circle_checkpoints"));
|
||||
}
|
||||
|
||||
UpdateObjectiveColors();
|
||||
}
|
||||
private void UpdateObjectiveColors()
|
||||
{
|
||||
for (int i = 0; i < objectives.Count; i++)
|
||||
{
|
||||
ScaledTexture texture = objectives[i];
|
||||
texture.Color = i < completed ? colorSet : colorWhite;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Functions
|
||||
|
||||
/// <summary>
|
||||
/// Draws the objective timer bar.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
background.Draw();
|
||||
title.Draw();
|
||||
|
||||
foreach (ScaledTexture texture in objectives)
|
||||
{
|
||||
texture.Draw();
|
||||
}
|
||||
}
|
||||
/// <inheritdoc/>
|
||||
public override void Recalculate(PointF pos)
|
||||
{
|
||||
lastPosition = pos;
|
||||
|
||||
base.Recalculate(pos);
|
||||
|
||||
const float safe = width + 5;
|
||||
float startY = pos.Y + (backgroundHeight * 0.5f) - (height * 0.5f);
|
||||
|
||||
switch (objectiveSpacing)
|
||||
{
|
||||
case ObjectiveSpacing.Equal:
|
||||
{
|
||||
const float half = backgroundWidth * 0.5f;
|
||||
float startX = pos.X + half;
|
||||
float spacingWidth = (half - safe) / (objectives.Count - 1);
|
||||
|
||||
for (int i = 0; i < objectives.Count; i++)
|
||||
{
|
||||
ScaledTexture texture = objectives[i];
|
||||
texture.Size = new SizeF(width, height);
|
||||
texture.Position = new PointF(startX + (spacingWidth * i), startY);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectiveSpacing.Fixed:
|
||||
{
|
||||
float startX = pos.X + backgroundWidth - safe - (width * (count - 1));
|
||||
|
||||
for (int i = 0; i < objectives.Count; i++)
|
||||
{
|
||||
ScaledTexture texture = objectives[i];
|
||||
texture.Size = new SizeF(width, height);
|
||||
texture.Position = new PointF(startX + (i * width), startY);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
using LemonUI.Elements;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace LemonUI.TimerBars
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Timer Bar that shows the progress of something.
|
||||
/// </summary>
|
||||
public class TimerBarProgress : TimerBar
|
||||
{
|
||||
#region Constant Fields
|
||||
|
||||
private const float barWidth = 108;
|
||||
private const float barHeight = 15;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private float progress = 100;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Fields
|
||||
|
||||
/// <summary>
|
||||
/// The background of the Progress Bar.
|
||||
/// </summary>
|
||||
internal protected readonly ScaledRectangle barBackground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
|
||||
{
|
||||
Color = Color.FromArgb(255, 139, 0, 0)
|
||||
};
|
||||
/// <summary>
|
||||
/// The foreground of the Progress Bar.
|
||||
/// </summary>
|
||||
internal protected readonly ScaledRectangle barForeground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
|
||||
{
|
||||
Color = Color.FromArgb(255, 255, 0, 0)
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// The progress of the bar.
|
||||
/// </summary>
|
||||
public float Progress
|
||||
{
|
||||
get => progress;
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > 100)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
progress = value;
|
||||
barForeground.Size = new SizeF(barWidth * (value * 0.01f), barHeight);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The Foreground color of the Progress bar.
|
||||
/// </summary>
|
||||
public Color ForegroundColor
|
||||
{
|
||||
get => barForeground.Color;
|
||||
set => barForeground.Color = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// The Background color of the Progress bar.
|
||||
/// </summary>
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get => barBackground.Color;
|
||||
set => barBackground.Color = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="TimerBarProgress"/> with the specified title.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the bar.</param>
|
||||
public TimerBarProgress(string title) : base(title, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the position of the timer bar elements based on the location of it on the screen.
|
||||
/// </summary>
|
||||
/// <param name="pos">The Top Left position of the Timer Bar.</param>
|
||||
public override void Recalculate(PointF pos)
|
||||
{
|
||||
// Recalculate the base elements
|
||||
base.Recalculate(pos);
|
||||
// And set the size and position of the progress bar
|
||||
PointF barPos = new PointF(pos.X + 103, pos.Y + 12);
|
||||
barBackground.Position = barPos;
|
||||
barBackground.Size = new SizeF(barWidth, barHeight);
|
||||
barForeground.Position = barPos;
|
||||
barForeground.Size = new SizeF(barWidth * (progress * 0.01f), barHeight);
|
||||
}
|
||||
/// <summary>
|
||||
/// Draws the TimerBar.
|
||||
/// </summary>
|
||||
public override void Draw()
|
||||
{
|
||||
background.Draw();
|
||||
title.Draw();
|
||||
barBackground.Draw();
|
||||
barForeground.Draw();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+164
-158
@@ -1,16 +1,16 @@
|
||||
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 GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
@@ -19,36 +19,49 @@ namespace RageCoop.Client
|
||||
/// </summary>
|
||||
internal class Main : Script
|
||||
{
|
||||
|
||||
private bool _gameLoaded = false;
|
||||
internal static readonly string CurrentVersion = "V0_5_0";
|
||||
internal static Version Version = typeof(Main).Assembly.GetName().Version;
|
||||
|
||||
internal static int LocalPlayerID=0;
|
||||
internal static int LocalPlayerID = 0;
|
||||
|
||||
internal static RelationshipGroup SyncedPedsGroup;
|
||||
|
||||
internal static new Settings Settings = null;
|
||||
internal static Scripting.BaseScript BaseScript=new Scripting.BaseScript();
|
||||
internal static Scripting.BaseScript BaseScript = new Scripting.BaseScript();
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
#endif
|
||||
internal static Chat MainChat = null;
|
||||
internal static Stopwatch Counter = new Stopwatch();
|
||||
internal static Logger Logger = null;
|
||||
|
||||
|
||||
internal static ulong Ticked = 0;
|
||||
internal static Scripting.Resources Resources=null;
|
||||
private static List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
internal static Vector3 PlayerPosition;
|
||||
internal static Scripting.Resources Resources = null;
|
||||
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
|
||||
public static Worker Worker;
|
||||
|
||||
/// <summary>
|
||||
/// Don't use it!
|
||||
/// </summary>
|
||||
public Main()
|
||||
{
|
||||
Settings = Util.ReadSettings();
|
||||
Logger=new Logger()
|
||||
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
|
||||
try
|
||||
{
|
||||
LogPath=$"RageCoop\\RageCoop.Client.log",
|
||||
UseConsole=false,
|
||||
Settings = Util.ReadSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
|
||||
Settings = new Settings();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
Directory.CreateDirectory(Settings.DataDirectory);
|
||||
Logger = new Logger()
|
||||
{
|
||||
LogPath = $"{Settings.DataDirectory}\\RageCoop.Client.log",
|
||||
UseConsole = false,
|
||||
#if DEBUG
|
||||
LogLevel = 0,
|
||||
#else
|
||||
@@ -64,7 +77,6 @@ namespace RageCoop.Client
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_gameLoaded)
|
||||
{
|
||||
GTA.UI.Notification.Show("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
|
||||
@@ -74,27 +86,30 @@ namespace RageCoop.Client
|
||||
return;
|
||||
}
|
||||
BaseScript.OnStart();
|
||||
SyncedPedsGroup=World.AddRelationshipGroup("SYNCPED");
|
||||
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
|
||||
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral, true);
|
||||
#if !NON_INTERACTIVE
|
||||
#endif
|
||||
MainChat = new Chat();
|
||||
Tick += OnTick;
|
||||
Tick += (s,e) => { Scripting.API.Events.InvokeTick(); };
|
||||
Tick += (s, e) => { Scripting.API.Events.InvokeTick(); };
|
||||
KeyDown += OnKeyDown;
|
||||
Aborted += (object sender, EventArgs e) => CleanUp();
|
||||
|
||||
KeyDown += (s, e) => { Scripting.API.Events.InvokeKeyDown(s, e); };
|
||||
KeyUp += (s, e) => { Scripting.API.Events.InvokeKeyUp(s, e); };
|
||||
Aborted += (object sender, EventArgs e) => Disconnected("Abort");
|
||||
|
||||
Util.NativeMemory();
|
||||
Counter.Restart();
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private ulong _lastDebugData;
|
||||
private int _debugBytesSend;
|
||||
private int _debugBytesReceived;
|
||||
#endif
|
||||
|
||||
public static Ped P;
|
||||
public static float FPS;
|
||||
private bool _lastDead;
|
||||
private void OnTick(object sender, EventArgs e)
|
||||
{
|
||||
P = Game.Player.Character;
|
||||
PlayerPosition = P.ReadPosition();
|
||||
FPS = Game.FPS;
|
||||
if (Game.IsLoading)
|
||||
{
|
||||
return;
|
||||
@@ -102,22 +117,23 @@ namespace RageCoop.Client
|
||||
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.");
|
||||
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)
|
||||
if (Game.TimeScale != 1)
|
||||
{
|
||||
Game.TimeScale=1;
|
||||
Game.TimeScale = 1;
|
||||
}
|
||||
try
|
||||
{
|
||||
@@ -125,32 +141,17 @@ namespace RageCoop.Client
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
}
|
||||
|
||||
|
||||
MapLoader.LoadAll();
|
||||
|
||||
#if DEBUG
|
||||
Main.Logger.Error(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (Networking.ShowNetworkInfo)
|
||||
{
|
||||
ulong time = Util.GetTickCount64();
|
||||
if (time - _lastDebugData > 1000)
|
||||
{
|
||||
_lastDebugData = time;
|
||||
|
||||
_debugBytesReceived = Networking.Client.Statistics.ReceivedBytes;
|
||||
_debugBytesSend = Networking.Client.Statistics.SentBytes;
|
||||
Networking.Client.Statistics.Reset();
|
||||
}
|
||||
|
||||
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();
|
||||
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {Lidgren.Network.NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/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(Statistics.BytesUpPerSecond)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
MainChat.Tick();
|
||||
PlayerList.Tick();
|
||||
@@ -158,33 +159,34 @@ namespace RageCoop.Client
|
||||
{
|
||||
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
|
||||
Function.Call(Hash.IGNORE_NEXT_RESTART, true);
|
||||
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
|
||||
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
|
||||
Function.Call(Hash.TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
|
||||
|
||||
var P = Game.Player.Character;
|
||||
if (P.IsDead)
|
||||
{
|
||||
Function.Call(Hash.SET_FADE_OUT_AFTER_DEATH, false);
|
||||
|
||||
if (P.Health!=1)
|
||||
|
||||
if (P.Health != 1)
|
||||
{
|
||||
P.Health=1;
|
||||
Game.Player.WantedLevel=0;
|
||||
P.Health = 1;
|
||||
Game.Player.WantedLevel = 0;
|
||||
Main.Logger.Debug("Player died.");
|
||||
Scripting.API.Events.InvokePlayerDied();
|
||||
}
|
||||
GTA.UI.Screen.StopEffects();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Function.Call(Hash.DISPLAY_HUD, true);
|
||||
}
|
||||
|
||||
}
|
||||
else if (P.IsDead && !_lastDead)
|
||||
{
|
||||
Scripting.API.Events.InvokePlayerDied();
|
||||
}
|
||||
|
||||
_lastDead = P.IsDead;
|
||||
Ticked++;
|
||||
}
|
||||
|
||||
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (MainChat.Focused)
|
||||
@@ -194,12 +196,26 @@ namespace RageCoop.Client
|
||||
}
|
||||
if (Networking.IsOnServer)
|
||||
{
|
||||
if (Voice.WasInitialized())
|
||||
{
|
||||
if (Game.IsControlPressed(GTA.Control.PushToTalk))
|
||||
{
|
||||
Voice.StartRecording();
|
||||
return;
|
||||
}
|
||||
else if (Voice.IsRecording())
|
||||
{
|
||||
Voice.StopRecording();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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;
|
||||
@@ -213,8 +229,8 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (x.Visible)
|
||||
{
|
||||
CoopMenu.LastMenu=x;
|
||||
x.Visible=false;
|
||||
CoopMenu.LastMenu = x;
|
||||
x.Visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -223,14 +239,6 @@ namespace RageCoop.Client
|
||||
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)
|
||||
@@ -238,10 +246,19 @@ namespace RageCoop.Client
|
||||
MainChat.Focused = true;
|
||||
}
|
||||
}
|
||||
else if (e.KeyCode==Settings.PassengerKey)
|
||||
else if (MainChat.Focused) { return; }
|
||||
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 (e.KeyCode == Settings.PassengerKey)
|
||||
{
|
||||
var P = Game.Player.Character;
|
||||
|
||||
|
||||
if (!P.IsInVehicle())
|
||||
{
|
||||
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
|
||||
@@ -250,94 +267,73 @@ namespace RageCoop.Client
|
||||
}
|
||||
else
|
||||
{
|
||||
var V = World.GetClosestVehicle(P.Position, 50);
|
||||
var V = World.GetClosestVehicle(P.ReadPosition(), 50);
|
||||
|
||||
if (V!=null)
|
||||
if (V != null)
|
||||
{
|
||||
var seat = P.GetNearestSeat(V);
|
||||
P.Task.EnterVehicle(V, seat);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void CleanUp()
|
||||
internal static void Connected()
|
||||
{
|
||||
MainChat.Clear();
|
||||
EntityPool.Cleanup();
|
||||
PlayerList.Cleanup();
|
||||
Main.LocalPlayerID=default;
|
||||
|
||||
}
|
||||
|
||||
internal 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
|
||||
};
|
||||
internal static Dictionary<int, byte> ServerItems = new Dictionary<int, byte>();
|
||||
internal static void CleanUpWorld()
|
||||
{
|
||||
if (ServerItems.Count == 0)
|
||||
Memory.ApplyPatches();
|
||||
if (Settings.Voice && !Voice.WasInitialized())
|
||||
{
|
||||
return;
|
||||
Voice.Init();
|
||||
}
|
||||
|
||||
lock (ServerItems)
|
||||
QueueAction(() =>
|
||||
{
|
||||
foreach (KeyValuePair<int, byte> item in ServerItems)
|
||||
WorldThread.Traffic(!Settings.DisableTraffic);
|
||||
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
|
||||
CoopMenu.ConnectedMenuSetting();
|
||||
MainChat.Init();
|
||||
GTA.UI.Notification.Show("~g~Connected!");
|
||||
});
|
||||
|
||||
Logger.Info(">> Connected <<");
|
||||
}
|
||||
public static void Disconnected(string reason)
|
||||
{
|
||||
|
||||
|
||||
Logger.Info($">> Disconnected << reason: {reason}");
|
||||
QueueAction(() =>
|
||||
{
|
||||
if (MainChat.Focused)
|
||||
{
|
||||
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!");
|
||||
}
|
||||
MainChat.Focused = false;
|
||||
}
|
||||
ServerItems.Clear();
|
||||
}
|
||||
PlayerList.Cleanup();
|
||||
MainChat.Clear();
|
||||
EntityPool.Cleanup();
|
||||
WorldThread.Traffic(true);
|
||||
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
|
||||
CoopMenu.DisconnectedMenuSetting();
|
||||
GTA.UI.Notification.Show("~r~Disconnected: " + reason);
|
||||
LocalPlayerID = default;
|
||||
});
|
||||
Memory.RestorePatches();
|
||||
DownloadManager.Cleanup();
|
||||
Voice.ClearAll();
|
||||
Resources.Unload();
|
||||
}
|
||||
|
||||
private static void DoQueuedActions()
|
||||
{
|
||||
lock (QueuedActions)
|
||||
@@ -351,9 +347,11 @@ namespace RageCoop.Client
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Logger.Error(ex);
|
||||
#endif
|
||||
QueuedActions.Remove(action);
|
||||
}
|
||||
}
|
||||
@@ -361,9 +359,9 @@ namespace RageCoop.Client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
|
||||
/// 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>
|
||||
/// <param name="a"> An action to be executed with a return value indicating whether the action can be removed after execution.</param>
|
||||
internal static void QueueAction(Func<bool> a)
|
||||
{
|
||||
lock (QueuedActions)
|
||||
@@ -375,7 +373,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
lock (QueuedActions)
|
||||
{
|
||||
QueuedActions.Add(() => { a(); return true; }) ;
|
||||
QueuedActions.Add(() => { a(); return true; });
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
@@ -386,6 +384,14 @@ namespace RageCoop.Client
|
||||
lock (QueuedActions) { QueuedActions.Clear(); }
|
||||
}
|
||||
|
||||
public static void Delay(Action a, int time)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
QueueAction(a);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using GTA;
|
||||
|
||||
using System.Drawing;
|
||||
|
||||
using GTA.Native;
|
||||
using LemonUI;
|
||||
using LemonUI.Menus;
|
||||
using LemonUI.Scaleform;
|
||||
using System.Drawing;
|
||||
|
||||
namespace RageCoop.Client.Menus
|
||||
{
|
||||
@@ -19,27 +18,28 @@ namespace RageCoop.Client.Menus
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
public static PopUp PopUp=new PopUp()
|
||||
public static PopUp PopUp = new PopUp()
|
||||
{
|
||||
Title="",
|
||||
Prompt="",
|
||||
Title = "",
|
||||
Prompt = "",
|
||||
Subtitle = "",
|
||||
Error="",
|
||||
Error = "",
|
||||
ShowBackground = true,
|
||||
Visible=false,
|
||||
Visible = false,
|
||||
};
|
||||
public static NativeMenu LastMenu { get; set; } = Menu;
|
||||
#region ITEMS
|
||||
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
|
||||
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*',Main.Settings.Password.Length) };
|
||||
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*', Main.Settings.Password.Length) };
|
||||
|
||||
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
|
||||
private static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
|
||||
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.CurrentVersion.Replace("_", ".")) { LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
|
||||
|
||||
Main.Version)
|
||||
{ LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace RageCoop.Client.Menus
|
||||
Menu.Title.Color = Color.FromArgb(255, 165, 0);
|
||||
|
||||
_usernameItem.Activated += UsernameActivated;
|
||||
_passwordItem.Activated+=_passwordActivated;
|
||||
_passwordItem.Activated += _passwordActivated;
|
||||
ServerIpItem.Activated += ServerIpActivated;
|
||||
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
|
||||
|
||||
@@ -67,14 +67,17 @@ namespace RageCoop.Client.Menus
|
||||
|
||||
Menu.AddSubMenu(SettingsMenu.Menu);
|
||||
Menu.AddSubMenu(DevToolMenu.Menu);
|
||||
#if DEBUG
|
||||
Menu.AddSubMenu(DebugMenu.Menu);
|
||||
|
||||
#endif
|
||||
|
||||
MenuPool.Add(Menu);
|
||||
MenuPool.Add(SettingsMenu.Menu);
|
||||
MenuPool.Add(DevToolMenu.Menu);
|
||||
#if DEBUG
|
||||
MenuPool.Add(DebugMenu.Menu);
|
||||
MenuPool.Add(DebugMenu.DiagnosticMenu);
|
||||
#endif
|
||||
MenuPool.Add(ServersMenu.Menu);
|
||||
MenuPool.Add(PopUp);
|
||||
|
||||
@@ -82,21 +85,32 @@ namespace RageCoop.Client.Menus
|
||||
}
|
||||
|
||||
|
||||
public static bool ShowPopUp(string prompt, string title,string subtitle,string error,bool showbackground)
|
||||
public static bool ShowPopUp(string prompt, string title, string subtitle, string error, bool showbackground)
|
||||
{
|
||||
PopUp.Prompt=prompt;
|
||||
PopUp.Title=title;
|
||||
PopUp.Subtitle=subtitle;
|
||||
PopUp.Error=error;
|
||||
PopUp.ShowBackground=showbackground;
|
||||
PopUp.Visible=true;
|
||||
PopUp.Prompt = prompt;
|
||||
PopUp.Title = title;
|
||||
PopUp.Subtitle = subtitle;
|
||||
PopUp.Error = error;
|
||||
PopUp.ShowBackground = showbackground;
|
||||
PopUp.Visible = true;
|
||||
Script.Yield();
|
||||
while (true)
|
||||
{
|
||||
Game.DisableAllControlsThisFrame();
|
||||
MenuPool.Process();
|
||||
|
||||
var scaleform = new Scaleform("instructional_buttons");
|
||||
scaleform.CallFunction("CLEAR_ALL");
|
||||
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
|
||||
scaleform.CallFunction("CREATE_CONTAINER");
|
||||
|
||||
scaleform.CallFunction("SET_DATA_SLOT", 0, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
|
||||
scaleform.CallFunction("SET_DATA_SLOT", 1, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
|
||||
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
|
||||
scaleform.Render2D();
|
||||
if (Game.IsControlJustPressed(Control.FrontendAccept))
|
||||
{
|
||||
PopUp.Visible=false;
|
||||
PopUp.Visible = false;
|
||||
return true;
|
||||
}
|
||||
else if (Game.IsControlJustPressed(Control.FrontendCancel))
|
||||
@@ -105,6 +119,8 @@ namespace RageCoop.Client.Menus
|
||||
return false;
|
||||
}
|
||||
Script.Yield();
|
||||
Game.DisableAllControlsThisFrame();
|
||||
|
||||
}
|
||||
}
|
||||
public static void UsernameActivated(object a, System.EventArgs b)
|
||||
@@ -122,12 +138,9 @@ namespace RageCoop.Client.Menus
|
||||
private static void _passwordActivated(object sender, System.EventArgs e)
|
||||
{
|
||||
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
|
||||
if (!string.IsNullOrWhiteSpace(newPass))
|
||||
{
|
||||
Main.Settings.Password = newPass;
|
||||
Util.SaveSettings();
|
||||
_passwordItem.AltTitle = new string('*', newPass.Length);
|
||||
}
|
||||
Main.Settings.Password = newPass;
|
||||
Util.SaveSettings();
|
||||
_passwordItem.AltTitle = new string('*', newPass.Length);
|
||||
}
|
||||
public static void ServerIpActivated(object a, System.EventArgs b)
|
||||
{
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LemonUI;
|
||||
using LemonUI.Menus;
|
||||
#if DEBUG
|
||||
using GTA;
|
||||
using LemonUI.Menus;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DebugMenu
|
||||
{
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings") {
|
||||
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings")
|
||||
{
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
@@ -21,24 +18,17 @@ namespace RageCoop.Client
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
private static NativeItem d1=new NativeItem("PositionPrediction");
|
||||
public static NativeItem SimulatedLatencyItem = new NativeItem("Simulated network latency", "Simulated network latency in ms (one way)", "0");
|
||||
public static NativeCheckboxItem ShowOwnerItem = new NativeCheckboxItem("Show entity owner", "Show the owner name of the entity you're aiming at", false);
|
||||
private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
|
||||
|
||||
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.Opening += (sender, e) =>
|
||||
{
|
||||
DiagnosticMenu.Clear();
|
||||
DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug()));
|
||||
@@ -47,15 +37,24 @@ namespace RageCoop.Client
|
||||
DiagnosticMenu.Add(new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
|
||||
}
|
||||
};
|
||||
SimulatedLatencyItem.Activated += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SimulatedLatencyItem.AltTitle = ((Networking.SimulatedLatency = int.Parse(Game.GetUserInput(SimulatedLatencyItem.AltTitle)) * 0.002f) * 500).ToString();
|
||||
}
|
||||
catch (Exception ex) { Main.Logger.Error(ex); }
|
||||
};
|
||||
ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; };
|
||||
ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); };
|
||||
Menu.Add(SimulatedLatencyItem);
|
||||
Menu.Add(ShowNetworkInfoItem);
|
||||
Menu.Add(ShowOwnerItem);
|
||||
Menu.AddSubMenu(DiagnosticMenu);
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
d1.AltTitle = SyncParameters.PositioinPredictionDefault.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using GTA;
|
||||
using LemonUI.Menus;
|
||||
using GTA;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
namespace RageCoop.Client
|
||||
@@ -16,9 +12,9 @@ namespace RageCoop.Client
|
||||
UseMouse = false,
|
||||
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
|
||||
};
|
||||
private static NativeCheckboxItem enableItem = new NativeCheckboxItem("Enable");
|
||||
private static readonly NativeCheckboxItem enableItem = new NativeCheckboxItem("Enable");
|
||||
|
||||
private static NativeCheckboxItem enableSecondaryItem = new NativeCheckboxItem("Secondary","Enable if this vehicle have two muzzles");
|
||||
private static readonly 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");
|
||||
@@ -28,18 +24,18 @@ namespace RageCoop.Client
|
||||
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;
|
||||
enableItem.Activated += enableItem_Activated;
|
||||
enableItem.Checked = false;
|
||||
enableSecondaryItem.CheckboxChanged += EnableSecondaryItem_Changed;
|
||||
|
||||
secondaryBoneIndexItem.Enabled=false;
|
||||
clipboardItem.Activated+=ClipboardItem_Activated;
|
||||
dirItem.ItemChanged+=DirItem_ItemChanged;
|
||||
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;
|
||||
dirItem.SelectedIndex = 0;
|
||||
|
||||
Menu.Add(enableItem);
|
||||
Menu.Add(boneIndexItem);
|
||||
@@ -53,19 +49,19 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (enableSecondaryItem.Checked)
|
||||
{
|
||||
DevTool.UseSecondary=true;
|
||||
secondaryBoneIndexItem.Enabled=true;
|
||||
DevTool.UseSecondary = true;
|
||||
secondaryBoneIndexItem.Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevTool.UseSecondary=false;
|
||||
secondaryBoneIndexItem.Enabled=false;
|
||||
DevTool.UseSecondary = false;
|
||||
secondaryBoneIndexItem.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DirItem_ItemChanged(object sender, ItemChangedEventArgs<MuzzleDir> e)
|
||||
{
|
||||
DevTool.Direction=dirItem.SelectedItem;
|
||||
DevTool.Direction = dirItem.SelectedItem;
|
||||
}
|
||||
|
||||
private static void ClipboardItem_Activated(object sender, EventArgs e)
|
||||
@@ -77,11 +73,11 @@ namespace RageCoop.Client
|
||||
{
|
||||
if (enableItem.Checked)
|
||||
{
|
||||
DevTool.ToMark=Game.Player.Character.CurrentVehicle;
|
||||
DevTool.ToMark = Game.Player.Character.CurrentVehicle;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevTool.ToMark=null;
|
||||
DevTool.ToMark = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,15 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Drawing;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using GTA.UI;
|
||||
using LemonUI.Menus;
|
||||
using Newtonsoft.Json;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
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!
|
||||
@@ -59,7 +38,7 @@ namespace RageCoop.Client.Menus
|
||||
Menu.Add(ResultItem = new NativeItem("Loading..."));
|
||||
|
||||
// Prevent freezing
|
||||
GetServersThread=new Thread(()=> GetAllServers());
|
||||
GetServersThread = new Thread(() => GetAllServers());
|
||||
GetServersThread.Start();
|
||||
};
|
||||
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
|
||||
@@ -76,10 +55,10 @@ 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 = Main.Settings.MasterServer;
|
||||
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
|
||||
|
||||
// Need to be processed in main thread
|
||||
Main.QueueAction(() =>
|
||||
{
|
||||
@@ -94,17 +73,25 @@ namespace RageCoop.Client.Menus
|
||||
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;
|
||||
|
||||
Networking.ToggleConnection(address);
|
||||
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, null, null, PublicKey.FromServerInfo(server));
|
||||
#if !NON_INTERACTIVE
|
||||
CoopMenu.ServerIpItem.AltTitle = address;
|
||||
|
||||
@@ -115,7 +102,11 @@ namespace RageCoop.Client.Menus
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using System.Drawing;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using GTA;
|
||||
using GTA;
|
||||
using LemonUI.Menus;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
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")
|
||||
@@ -19,54 +16,66 @@ namespace RageCoop.Client.Menus
|
||||
|
||||
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 _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableAlternatePause);
|
||||
private static readonly NativeCheckboxItem _disableVoice = new NativeCheckboxItem("Enable voice", "Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends", Main.Settings.Voice);
|
||||
|
||||
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());
|
||||
private static readonly NativeItem _menuKey = new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString());
|
||||
private static readonly NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
|
||||
private static readonly 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;
|
||||
_disablePauseAlt.CheckboxChanged += DisablePauseAltCheckboxChanged;
|
||||
_disableVoice.CheckboxChanged += DisableVoiceCheckboxChanged;
|
||||
_flipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged;
|
||||
_showNetworkInfoItem.CheckboxChanged += ShowNetworkInfoCheckboxChanged;
|
||||
_menuKey.Activated+=ChaneMenuKey;
|
||||
_passengerKey.Activated+=ChangePassengerKey;
|
||||
_vehicleSoftLimit.Activated+=vehicleSoftLimit_Activated;
|
||||
_menuKey.Activated += ChaneMenuKey;
|
||||
_passengerKey.Activated += ChangePassengerKey;
|
||||
_vehicleSoftLimit.Activated += VehicleSoftLimitActivated;
|
||||
|
||||
Menu.Add(_disableTrafficItem);
|
||||
Menu.Add(_disablePauseAlt);
|
||||
Menu.Add(_flipMenuItem);
|
||||
Menu.Add(_showNetworkInfoItem);
|
||||
Menu.Add(_disableVoice);
|
||||
Menu.Add(_menuKey);
|
||||
Menu.Add(_passengerKey);
|
||||
Menu.Add(_vehicleSoftLimit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void _disablePauseAlt_CheckboxChanged(object sender, EventArgs e)
|
||||
private static void DisableVoiceCheckboxChanged(object sender, EventArgs e)
|
||||
{
|
||||
Main.Settings.DisableAlternatePause=_disablePauseAlt.Checked;
|
||||
if (_disableVoice.Checked)
|
||||
{
|
||||
if (Networking.IsOnServer && !Voice.WasInitialized())
|
||||
{
|
||||
Voice.Init();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Voice.ClearAll();
|
||||
}
|
||||
|
||||
Main.Settings.Voice = _disableVoice.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
private static void vehicleSoftLimit_Activated(object sender, EventArgs e)
|
||||
|
||||
private static void DisablePauseAltCheckboxChanged(object sender, EventArgs e)
|
||||
{
|
||||
Main.Settings.DisableAlternatePause = _disablePauseAlt.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
private static void VehicleSoftLimitActivated(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.Settings.WorldVehicleSoftLimit =int.Parse(
|
||||
Main.Settings.WorldVehicleSoftLimit = int.Parse(
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Main.Settings.WorldVehicleSoftLimit.ToString(), 20));
|
||||
_menuKey.AltTitle=Main.Settings.WorldVehicleSoftLimit.ToString();
|
||||
_menuKey.AltTitle = Main.Settings.WorldVehicleSoftLimit.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch { }
|
||||
@@ -75,11 +84,11 @@ namespace RageCoop.Client.Menus
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.Settings.MenuKey =(Keys)Enum.Parse(
|
||||
Main.Settings.MenuKey = (Keys)Enum.Parse(
|
||||
typeof(Keys),
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Main.Settings.MenuKey.ToString(), 20));
|
||||
_menuKey.AltTitle=Main.Settings.MenuKey.ToString();
|
||||
_menuKey.AltTitle = Main.Settings.MenuKey.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch { }
|
||||
@@ -89,11 +98,11 @@ namespace RageCoop.Client.Menus
|
||||
{
|
||||
try
|
||||
{
|
||||
Main.Settings.PassengerKey =(Keys)Enum.Parse(
|
||||
Main.Settings.PassengerKey = (Keys)Enum.Parse(
|
||||
typeof(Keys),
|
||||
Game.GetUserInput(WindowTitle.EnterMessage20,
|
||||
Main.Settings.PassengerKey.ToString(), 20));
|
||||
_passengerKey.AltTitle=Main.Settings.PassengerKey.ToString();
|
||||
_passengerKey.AltTitle = Main.Settings.PassengerKey.ToString();
|
||||
Util.SaveSettings();
|
||||
}
|
||||
catch { }
|
||||
@@ -101,8 +110,9 @@ namespace RageCoop.Client.Menus
|
||||
|
||||
public static void DisableTrafficCheckboxChanged(object a, System.EventArgs b)
|
||||
{
|
||||
WorldThread.Traffic(!_disableTrafficItem.Checked);
|
||||
Main.Settings.DisableTraffic = _disableTrafficItem.Checked;
|
||||
Util.SaveSettings() ;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
|
||||
public static void FlipMenuCheckboxChanged(object a, System.EventArgs b)
|
||||
@@ -113,11 +123,5 @@ namespace RageCoop.Client.Menus
|
||||
Main.Settings.FlipMenu = _flipMenuItem.Checked;
|
||||
Util.SaveSettings();
|
||||
}
|
||||
|
||||
public static void ShowNetworkInfoCheckboxChanged(object a, System.EventArgs b)
|
||||
{
|
||||
Networking.ShowNetworkInfo = _showNetworkInfoItem.Checked;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
@@ -17,7 +16,7 @@ namespace RageCoop.Client
|
||||
private bool CurrentFocused { get; set; }
|
||||
public bool Focused
|
||||
{
|
||||
get { return CurrentFocused; }
|
||||
get => CurrentFocused;
|
||||
set
|
||||
{
|
||||
if (value && Hidden)
|
||||
@@ -36,7 +35,7 @@ namespace RageCoop.Client
|
||||
private bool CurrentHidden { get; set; }
|
||||
private bool Hidden
|
||||
{
|
||||
get { return CurrentHidden; }
|
||||
get => CurrentHidden;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
@@ -106,7 +105,7 @@ namespace RageCoop.Client
|
||||
CurrentInput = "";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (key == Keys.PageUp)
|
||||
{
|
||||
MainScaleForm.CallFunction("PAGE_UP");
|
||||
|
||||
@@ -1,42 +1,85 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class DownloadManager
|
||||
{
|
||||
static string downloadFolder = $"RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}";
|
||||
|
||||
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
|
||||
public static void AddFile(int id, string name, long length)
|
||||
public static event EventHandler<string> DownloadCompleted;
|
||||
static DownloadManager()
|
||||
{
|
||||
Main.Logger.Debug($"Downloading file to {downloadFolder}\\{name} , id:{id}");
|
||||
if (!Directory.Exists(downloadFolder))
|
||||
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, (data) =>
|
||||
{
|
||||
Directory.CreateDirectory(downloadFolder);
|
||||
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) =>
|
||||
{
|
||||
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
|
||||
packet.Deserialize(data);
|
||||
|
||||
Main.Logger.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
|
||||
{
|
||||
Main.Resources.Load(ResourceFolder, _resources.ToArray());
|
||||
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.Loaded };
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error("Error occurred when loading server resource:");
|
||||
Main.Logger.Error(ex);
|
||||
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed };
|
||||
}
|
||||
});
|
||||
}
|
||||
public static string ResourceFolder => Path.GetFullPath(Path.Combine(Main.Settings.DataDirectory, "Resources", Main.Settings.LastServerAddress.Replace(":", ".")));
|
||||
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
|
||||
private static readonly HashSet<string> _resources = new HashSet<string>();
|
||||
public static bool AddFile(int id, string name, long length)
|
||||
{
|
||||
var path = $"{ResourceFolder}\\{name}";
|
||||
Main.Logger.Debug($"Downloading file to {path} , id:{id}");
|
||||
if (!Directory.Exists(Directory.GetParent(path).FullName))
|
||||
{
|
||||
Directory.CreateDirectory(Directory.GetParent(path).FullName);
|
||||
}
|
||||
|
||||
if (FileAlreadyExists(downloadFolder, name, length))
|
||||
if (FileAlreadyExists(ResourceFolder, name, length))
|
||||
{
|
||||
Main.Logger.Debug($"File already exists! canceling download:{name}");
|
||||
Cancel(id);
|
||||
if (name=="Resources.zip")
|
||||
{
|
||||
Main.Logger.Debug("Loading resources...");
|
||||
Main.Resources.Load(Path.Combine(downloadFolder));
|
||||
}
|
||||
return;
|
||||
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
if (!name.EndsWith(".zip"))
|
||||
{
|
||||
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;
|
||||
Main.Logger.Error($"File download blocked! [{name}]");
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
InProgressDownloads.Add(id, new DownloadFile()
|
||||
@@ -44,9 +87,10 @@ namespace RageCoop.Client
|
||||
FileID = id,
|
||||
FileName = name,
|
||||
FileLength = length,
|
||||
Stream = new FileStream($"{downloadFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
|
||||
Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,8 +122,7 @@ namespace RageCoop.Client
|
||||
{
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
DownloadFile file;
|
||||
if (InProgressDownloads.TryGetValue(id, out file))
|
||||
if (InProgressDownloads.TryGetValue(id, out DownloadFile file))
|
||||
{
|
||||
|
||||
file.Stream.Write(chunk, 0, chunk.Length);
|
||||
@@ -87,47 +130,19 @@ namespace RageCoop.Client
|
||||
else
|
||||
{
|
||||
Main.Logger.Trace($"Received unhandled file chunk:{id}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void Cancel(int id)
|
||||
{
|
||||
Main.Logger.Debug($"Canceling download:{id}");
|
||||
|
||||
// Tell the server to stop sending chunks
|
||||
Networking.SendDownloadFinish(id);
|
||||
|
||||
DownloadFile file;
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
if (InProgressDownloads.TryGetValue(id, out file))
|
||||
{
|
||||
InProgressDownloads.Remove(id);
|
||||
file.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Complete(int id)
|
||||
{
|
||||
DownloadFile f;
|
||||
|
||||
if (InProgressDownloads.TryGetValue(id, out f))
|
||||
if (InProgressDownloads.TryGetValue(id, out DownloadFile f))
|
||||
{
|
||||
lock (InProgressDownloads)
|
||||
{
|
||||
InProgressDownloads.Remove(id);
|
||||
f.Dispose();
|
||||
Main.Logger.Info($"Download finished:{f.FileName}");
|
||||
if (f.FileName=="Resources.zip")
|
||||
{
|
||||
Main.Logger.Debug("Loading resources...");
|
||||
Main.Resources.Load(Path.Combine(downloadFolder));
|
||||
}
|
||||
Networking.SendDownloadFinish(id);
|
||||
}
|
||||
InProgressDownloads.Remove(id);
|
||||
f.Dispose();
|
||||
Main.Logger.Info($"Download finished:{f.FileName}");
|
||||
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -145,11 +160,12 @@ namespace RageCoop.Client
|
||||
}
|
||||
InProgressDownloads.Clear();
|
||||
}
|
||||
_resources.Clear();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class DownloadFile: System.IDisposable
|
||||
internal class DownloadFile : IDisposable
|
||||
{
|
||||
public int FileID { get; set; } = 0;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
@@ -158,7 +174,7 @@ namespace RageCoop.Client
|
||||
public FileStream Stream { get; set; }
|
||||
public void Dispose()
|
||||
{
|
||||
if(Stream!= null)
|
||||
if (Stream != null)
|
||||
{
|
||||
Stream.Flush();
|
||||
Stream.Close();
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Timers;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial 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))
|
||||
{
|
||||
Main.Logger.Trace($"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.ID}");
|
||||
var msg = Networking.Peer.CreateMessage();
|
||||
new Packets.HolePunch
|
||||
{
|
||||
Puncher = Main.LocalPlayerID,
|
||||
Status = p.HolePunchStatus
|
||||
}.Pack(msg);
|
||||
Networking.Peer.SendUnconnectedMessage(msg, new List<IPEndPoint> { p.InternalEndPoint, p.ExternalEndPoint });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.Logger.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(Packets.HolePunchInit p)
|
||||
{
|
||||
if (PlayerList.Players.TryGetValue(p.TargetID, out var player))
|
||||
{
|
||||
Main.Logger.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
|
||||
{
|
||||
Main.Logger.Warning("No player with specified TargetID found for hole punching:" + p.TargetID);
|
||||
}
|
||||
}
|
||||
public static void Punched(Packets.HolePunch p, IPEndPoint from)
|
||||
{
|
||||
Main.Logger.Debug($"HolePunch message received from:{from}, status:{p.Status}");
|
||||
if (PlayerList.Players.TryGetValue(p.Puncher, out var puncher))
|
||||
{
|
||||
Main.Logger.Debug("Puncher identified as: " + puncher.Username);
|
||||
puncher.HolePunchStatus = (byte)(p.Status + 1);
|
||||
if (p.Status >= 3)
|
||||
{
|
||||
Main.Logger.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
|
||||
if (puncher.ConnectWhenPunched && (puncher.Connection == null || puncher.Connection.Status == NetConnectionStatus.Disconnected))
|
||||
{
|
||||
Main.Logger.Debug("Connecting to peer: " + from);
|
||||
var msg = Networking.Peer.CreateMessage();
|
||||
new Packets.P2PConnect { ID = Main.LocalPlayerID }.Pack(msg);
|
||||
puncher.Connection = Networking.Peer.Connect(from, msg);
|
||||
Networking.Peer.FlushSendQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = $"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,63 +1,68 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using GTA.UI;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using System.Security.Cryptography;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
public static NetClient Client;
|
||||
public static float Latency = 0;
|
||||
public static CoopPeer Peer;
|
||||
public static float Latency => ServerConnection.AverageRoundtripTime / 2;
|
||||
public static bool ShowNetworkInfo = false;
|
||||
public static Security Security;
|
||||
public static NetConnection ServerConnection;
|
||||
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new Dictionary<int, Action<PacketType, NetIncomingMessage>>();
|
||||
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new Dictionary<PacketType, Func<NetIncomingMessage, Packet>>();
|
||||
internal static float SimulatedLatency = 0;
|
||||
public static bool IsConnecting { get; private set; }
|
||||
public static IPEndPoint _targetServerEP;
|
||||
static Networking()
|
||||
{
|
||||
Security=new Security(Main.Logger);
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (Client!=null)
|
||||
{
|
||||
ProcessMessage(Client.WaitMessage(200));
|
||||
Client.FlushSendQueue();
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(20);
|
||||
}
|
||||
}
|
||||
});
|
||||
Security = new Security(Main.Logger);
|
||||
}
|
||||
|
||||
public static void ToggleConnection(string address,string username=null,string password=null)
|
||||
public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null)
|
||||
{
|
||||
Menus.CoopMenu.Menu.Visible = false;
|
||||
Peer?.Shutdown("Bye");
|
||||
if (IsOnServer)
|
||||
{
|
||||
Client.Disconnect("Bye!");
|
||||
// ?
|
||||
}
|
||||
else if (IsConnecting)
|
||||
{
|
||||
_publicKeyReceived.Set();
|
||||
IsConnecting = false;
|
||||
Notification.Show("Connection has been canceled");
|
||||
}
|
||||
else
|
||||
{
|
||||
Peer?.Dispose();
|
||||
|
||||
IsConnecting = true;
|
||||
password = password ?? Main.Settings.Password;
|
||||
username=username ?? Main.Settings.Username;
|
||||
username = username ?? Main.Settings.Username;
|
||||
|
||||
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
|
||||
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
|
||||
{
|
||||
AutoFlushSendQueue = true
|
||||
AutoFlushSendQueue = false,
|
||||
AcceptIncomingConnections = true,
|
||||
MaximumConnections = 32,
|
||||
PingInterval = 5
|
||||
};
|
||||
|
||||
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
|
||||
#if DEBUG
|
||||
config.SimulatedMinimumLatency = SimulatedLatency;
|
||||
config.SimulatedRandomLatency = 0;
|
||||
#endif
|
||||
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
|
||||
|
||||
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
|
||||
|
||||
string[] ip = new string[2];
|
||||
|
||||
@@ -73,73 +78,144 @@ namespace RageCoop.Client
|
||||
throw new Exception("Malformed URL");
|
||||
}
|
||||
|
||||
PlayerList.Cleanup();
|
||||
EntityPool.AddPlayer();
|
||||
if (publicKey == null && !string.IsNullOrEmpty(password) && !Menus.CoopMenu.ShowPopUp("", "WARNING", "Server's IP can be spoofed when using direct connection, do you wish to continue?", "", true))
|
||||
{
|
||||
IsConnecting = false;
|
||||
return;
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
Client = new NetClient(config);
|
||||
Client.Start();
|
||||
|
||||
Security.Regen();
|
||||
GetServerPublicKey(address);
|
||||
|
||||
// Send HandshakePacket
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
var handshake=new Packets.Handshake()
|
||||
try
|
||||
{
|
||||
PedID = Main.LocalPlayerID,
|
||||
Username =username,
|
||||
ModVersion = Main.CurrentVersion,
|
||||
PassHashEncrypted=Security.Encrypt(password.GetHash())
|
||||
};
|
||||
Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted,out handshake.AesIVCrypted);
|
||||
handshake.Pack(outgoingMessage);
|
||||
Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
|
||||
Main.QueueAction(() => { GTA.UI.Notification.Show($"~y~Trying to connect..."); });
|
||||
_targetServerEP = CoreUtils.StringToEndPoint(address);
|
||||
|
||||
// Ensure static constructor invocation
|
||||
DownloadManager.Cleanup();
|
||||
Peer = new CoopPeer(config);
|
||||
Peer.OnMessageReceived += (s, m) =>
|
||||
{
|
||||
try { ProcessMessage(m); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
#if DEBUG
|
||||
Main.Logger.Error(ex);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
Main.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); });
|
||||
Menus.CoopMenu._serverConnectItem.Enabled = false;
|
||||
Security.Regen();
|
||||
if (publicKey == null)
|
||||
{
|
||||
if (!GetServerPublicKey(ip[0], int.Parse(ip[1])))
|
||||
{
|
||||
Menus.CoopMenu._serverConnectItem.Enabled = true;
|
||||
throw new TimeoutException("Failed to retrive server's public key");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Security.SetServerPublicKey(publicKey.Modulus, publicKey.Exponent);
|
||||
}
|
||||
|
||||
// Send handshake packet
|
||||
NetOutgoingMessage outgoingMessage = Peer.CreateMessage();
|
||||
var handshake = new Packets.Handshake()
|
||||
{
|
||||
PedID = Main.LocalPlayerID,
|
||||
Username = username,
|
||||
ModVersion = Main.Version.ToString(),
|
||||
PasswordEncrypted = Security.Encrypt(password.GetBytes()),
|
||||
InternalEndPoint = new System.Net.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)
|
||||
{
|
||||
Main.Logger.Error("Cannot connect to server: ", ex);
|
||||
Main.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message));
|
||||
}
|
||||
IsConnecting = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
public static bool IsOnServer
|
||||
{
|
||||
get { return Client?.ConnectionStatus == NetConnectionStatus.Connected; }
|
||||
}
|
||||
|
||||
#region -- GET --
|
||||
public static bool IsOnServer { get => ServerConnection?.Status == NetConnectionStatus.Connected; }
|
||||
|
||||
#region -- PLAYER --
|
||||
private static void PlayerConnect(Packets.PlayerConnect packet)
|
||||
{
|
||||
var p = new PlayerData
|
||||
var p = new Player
|
||||
{
|
||||
PedID = packet.PedID,
|
||||
Username= packet.Username,
|
||||
ID = 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.QueueAction(() =>
|
||||
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected."));
|
||||
}
|
||||
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
|
||||
{
|
||||
var name=PlayerList.GetPlayer(packet.PedID).Username;
|
||||
GTA.UI.Notification.Show($"{name} left.");
|
||||
var player = PlayerList.GetPlayer(packet.PedID);
|
||||
if (player == null) { return; }
|
||||
PlayerList.RemovePlayer(packet.PedID);
|
||||
EntityPool.RemoveAllFromPlayer(packet.PedID);
|
||||
|
||||
|
||||
Main.QueueAction(() =>
|
||||
{
|
||||
EntityPool.RemoveAllFromPlayer(packet.PedID);
|
||||
GTA.UI.Notification.Show($"~h~{player.Username}~h~ left.");
|
||||
});
|
||||
}
|
||||
|
||||
#endregion // -- PLAYER --
|
||||
|
||||
private static void GetServerPublicKey(string address,int timeout=10000)
|
||||
#region -- GET --
|
||||
private static bool GetServerPublicKey(string host, int port, int timeout = 10000)
|
||||
{
|
||||
var msg=Client.CreateMessage();
|
||||
new Packets.PublicKeyRequest().Pack(msg);
|
||||
var adds =address.Split(':');
|
||||
Client.SendUnconnectedMessage(msg,adds[0],int.Parse(adds[1]));
|
||||
PublicKeyReceived.WaitOne(timeout);
|
||||
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
|
||||
private static int NewRequestID()
|
||||
{
|
||||
int ID = 0;
|
||||
while ((ID == 0) || PendingResponses.ContainsKey(ID))
|
||||
{
|
||||
byte[] rngBytes = new byte[4];
|
||||
|
||||
RandomNumberGenerator.Create().GetBytes(rngBytes);
|
||||
|
||||
// Convert the bytes into an integer
|
||||
ID = BitConverter.ToInt32(rngBytes, 0);
|
||||
}
|
||||
return ID;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,382 +1,409 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using GTA;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using RageCoop.Client.Menus;
|
||||
using RageCoop.Core;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using System.Net;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
private static AutoResetEvent PublicKeyReceived=new AutoResetEvent(false);
|
||||
|
||||
/// <summary>
|
||||
/// Reduce GC pressure by reusing frequently used packets
|
||||
/// </summary>
|
||||
private static class ReceivedPackets
|
||||
{
|
||||
public static Packets.PedSync PedPacket = new Packets.PedSync();
|
||||
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
|
||||
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to reslove entity handle in a <see cref="Packets.CustomEvent"/>
|
||||
/// </summary>
|
||||
private static readonly Func<byte, NetIncomingMessage, object> _resolveHandle = (t, reader) =>
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case 50:
|
||||
return EntityPool.ServerProps[reader.ReadInt32()].MainProp?.Handle;
|
||||
case 51:
|
||||
return EntityPool.GetPedByID(reader.ReadInt32())?.MainPed?.Handle;
|
||||
case 52:
|
||||
return EntityPool.GetVehicleByID(reader.ReadInt32())?.MainVehicle?.Handle;
|
||||
case 60:
|
||||
return EntityPool.ServerBlips[reader.ReadInt32()].Handle;
|
||||
default:
|
||||
throw new ArgumentException("Cannot resolve server side argument: " + t);
|
||||
}
|
||||
};
|
||||
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
|
||||
private static bool _recycle;
|
||||
public static void ProcessMessage(NetIncomingMessage message)
|
||||
{
|
||||
if(message == null) { return; }
|
||||
|
||||
if (message == null) { return; }
|
||||
_recycle = true;
|
||||
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
|
||||
if (message.SenderConnection == ServerConnection)
|
||||
{
|
||||
CoopMenu.InitiateConnectionMenuSetting();
|
||||
}
|
||||
break;
|
||||
case NetConnectionStatus.Connected:
|
||||
Main.QueueAction(() => {
|
||||
CoopMenu.ConnectedMenuSetting();
|
||||
Main.MainChat.Init();
|
||||
PlayerList.Cleanup();
|
||||
GTA.UI.Notification.Show("~g~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);
|
||||
}
|
||||
Main.Connected();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Self-initiated connection
|
||||
if (message.SenderConnection.RemoteHailMessage == null) { return; }
|
||||
|
||||
Main.Logger.Info(">> Connected <<");
|
||||
var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>();
|
||||
if (PlayerList.Players.TryGetValue(p.ID, out var player))
|
||||
{
|
||||
player.Connection = message.SenderConnection;
|
||||
Main.Logger.Debug($"Direct connection to {player.Username} established");
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Logger.Info($"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
|
||||
message.SenderConnection.Disconnect("eat poop");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetConnectionStatus.Disconnected:
|
||||
DownloadManager.Cleanup();
|
||||
|
||||
// Reset all values
|
||||
Latency = 0;
|
||||
|
||||
Main.QueueAction(() => Main.CleanUpWorld());
|
||||
|
||||
if (Main.MainChat.Focused)
|
||||
if (message.SenderConnection == ServerConnection)
|
||||
{
|
||||
Main.MainChat.Focused = false;
|
||||
Main.Disconnected(reason);
|
||||
}
|
||||
|
||||
Main.QueueAction(() => Main.CleanUp());
|
||||
|
||||
#if !NON_INTERACTIVE
|
||||
CoopMenu.DisconnectedMenuSetting();
|
||||
#endif
|
||||
|
||||
Main.QueueAction(() =>
|
||||
GTA.UI.Notification.Show("~r~Disconnected: " + reason));
|
||||
|
||||
MapLoader.DeleteAll();
|
||||
Main.Resources.Unload();
|
||||
|
||||
Main.Logger.Info($">> Disconnected << reason: {reason}");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.Data:
|
||||
{
|
||||
|
||||
if (message.LengthBytes==0) { break; }
|
||||
var packetType = PacketTypes.Unknown;
|
||||
if (message.LengthBytes == 0) { break; }
|
||||
var packetType = PacketType.Unknown;
|
||||
try
|
||||
{
|
||||
packetType = (PacketTypes)message.ReadByte();
|
||||
int len = message.ReadInt32();
|
||||
byte[] data = message.ReadBytes(len);
|
||||
// Get packet type
|
||||
packetType = (PacketType)message.ReadByte();
|
||||
switch (packetType)
|
||||
{
|
||||
case PacketTypes.PlayerConnect:
|
||||
case PacketType.Response:
|
||||
{
|
||||
|
||||
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.UpdatePlayer(packet);
|
||||
int id = message.ReadInt32();
|
||||
if (PendingResponses.TryGetValue(id, out var callback))
|
||||
{
|
||||
callback((PacketType)message.ReadByte(), message);
|
||||
PendingResponses.Remove(id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#region ENTITY SYNC
|
||||
case PacketTypes.VehicleSync:
|
||||
case PacketType.Request:
|
||||
{
|
||||
|
||||
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);
|
||||
int 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
|
||||
{
|
||||
Main.Logger.Debug("Did not find a request handler of type: " + realType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endregion
|
||||
case PacketTypes.ChatMessage:
|
||||
{
|
||||
|
||||
Packets.ChatMessage packet = new Packets.ChatMessage();
|
||||
packet.Unpack(data);
|
||||
|
||||
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
|
||||
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.CustomEvent:
|
||||
{
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent();
|
||||
packet.Unpack(data);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
}
|
||||
break;
|
||||
case PacketTypes.FileTransferChunk:
|
||||
{
|
||||
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
|
||||
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.Name, packet.FileLength);
|
||||
|
||||
}
|
||||
break;
|
||||
case PacketTypes.FileTransferComplete:
|
||||
{
|
||||
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
|
||||
packet.Unpack(data);
|
||||
|
||||
Main.Logger.Debug($"Finalizing download:{packet.ID}");
|
||||
DownloadManager.Complete(packet.ID);
|
||||
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (packetType.IsSyncEvent())
|
||||
{
|
||||
// Dispatch to main thread
|
||||
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
|
||||
|
||||
HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Main.QueueAction(() => {
|
||||
GTA.UI.Notification.Show("~r~~h~Packet Error");
|
||||
#if DEBUG
|
||||
Main.QueueAction(() =>
|
||||
{
|
||||
GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}");
|
||||
return true;
|
||||
});
|
||||
Main.Logger.Error($"[{packetType}] {ex.Message}");
|
||||
Main.Logger.Error(ex);
|
||||
Client.Disconnect($"Packet Error [{packetType}]");
|
||||
Peer.Shutdown($"Packet Error [{packetType}]");
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NetIncomingMessageType.ConnectionLatencyUpdated:
|
||||
Latency = message.ReadFloat();
|
||||
break;
|
||||
case NetIncomingMessageType.UnconnectedData:
|
||||
{
|
||||
var packetType = (PacketTypes)message.ReadByte();
|
||||
int len = message.ReadInt32();
|
||||
byte[] data = message.ReadBytes(len);
|
||||
if (packetType==PacketTypes.PublicKeyResponse)
|
||||
var packetType = (PacketType)message.ReadByte();
|
||||
switch (packetType)
|
||||
{
|
||||
var packet=new Packets.PublicKeyResponse();
|
||||
packet.Unpack(data);
|
||||
Security.SetServerPublicKey(packet.Modulus,packet.Exponent);
|
||||
PublicKeyReceived.Set();
|
||||
|
||||
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:
|
||||
Main.Logger.Trace(message.ReadString());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Client.Recycle(message);
|
||||
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:
|
||||
{
|
||||
|
||||
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
|
||||
packet.Deserialize(msg);
|
||||
|
||||
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.Voice:
|
||||
{
|
||||
if (Main.Settings.Voice)
|
||||
{
|
||||
Packets.Voice packet = new Packets.Voice();
|
||||
packet.Deserialize(msg);
|
||||
|
||||
|
||||
SyncedPed player = EntityPool.GetPedByID(packet.ID);
|
||||
player.IsSpeaking = true;
|
||||
player.LastSpeakingTime = Main.Ticked;
|
||||
|
||||
Voice.AddVoiceData(packet.Buffer, packet.Recorded);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.CustomEvent:
|
||||
{
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
|
||||
packet.Deserialize(msg);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.CustomEventQueued:
|
||||
{
|
||||
recycle = false;
|
||||
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
|
||||
Main.QueueAction(() =>
|
||||
{
|
||||
packet.Deserialize(msg);
|
||||
Scripting.API.Events.InvokeCustomEventReceived(packet);
|
||||
Peer.Recycle(msg);
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case PacketType.FileTransferChunk:
|
||||
{
|
||||
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
|
||||
packet.Deserialize(msg);
|
||||
DownloadManager.Write(packet.ID, packet.FileChunk);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (packetType.IsSyncEvent())
|
||||
{
|
||||
recycle = false;
|
||||
// Dispatch to script thread
|
||||
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); Peer.Recycle(msg); return true; });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PedSync(Packets.PedSync packet)
|
||||
{
|
||||
SyncedPed c = EntityPool.GetPedByID(packet.ID);
|
||||
if (c==null)
|
||||
if (c == null)
|
||||
{
|
||||
Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
|
||||
EntityPool.ThreadSafe.Add(c=new SyncedPed(packet.ID));
|
||||
// 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;
|
||||
PedDataFlags flags = packet.Flags;
|
||||
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.HasPedFlag(PedDataFlags.IsAiming);
|
||||
c.IsReloading = flags.HasPedFlag(PedDataFlags.IsReloading);
|
||||
c.IsJumping = flags.HasPedFlag(PedDataFlags.IsJumping);
|
||||
c.IsRagdoll = flags.HasPedFlag(PedDataFlags.IsRagdoll);
|
||||
c.IsOnFire = flags.HasPedFlag(PedDataFlags.IsOnFire);
|
||||
c.IsInParachuteFreeFall = flags.HasPedFlag(PedDataFlags.IsInParachuteFreeFall);
|
||||
c.IsParachuteOpen = flags.HasPedFlag(PedDataFlags.IsParachuteOpen);
|
||||
c.IsOnLadder = flags.HasPedFlag(PedDataFlags.IsOnLadder);
|
||||
c.IsVaulting = flags.HasPedFlag(PedDataFlags.IsVaulting);
|
||||
c.IsInCover = flags.HasPedFlag(PedDataFlags.IsInCover);
|
||||
c.IsInStealthMode = flags.HasPedFlag(PedDataFlags.IsInStealthMode);
|
||||
c.Heading=packet.Heading;
|
||||
c.LastSynced = Main.Ticked;
|
||||
c.Flags = packet.Flags;
|
||||
c.Heading = packet.Heading;
|
||||
c.Position = packet.Position;
|
||||
c.LastSyncedStopWatch.Restart();
|
||||
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;
|
||||
}
|
||||
c.LastSynced = Main.Ticked;
|
||||
if (c.IsAiming)
|
||||
{
|
||||
c.AimCoords = packet.AimCoords;
|
||||
}
|
||||
if (c.IsRagdoll)
|
||||
if (packet.Flags.HasPedFlag(PedDataFlags.IsFullSync))
|
||||
{
|
||||
c.RotationVelocity=packet.RotationVelocity;
|
||||
c.CurrentWeaponHash = packet.CurrentWeaponHash;
|
||||
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.LastFullSynced = Main.Ticked;
|
||||
}
|
||||
}
|
||||
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.WeaponTint=packet.WeaponTint;
|
||||
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.RoofState=(VehicleRoofState)packet.RoofState;
|
||||
v.EngineRunning = packet.Flag.HasVehFlag(VehicleDataFlags.IsEngineRunning);
|
||||
v.LightsOn = packet.Flag.HasVehFlag(VehicleDataFlags.AreLightsOn);
|
||||
v.BrakeLightsOn = packet.Flag.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
|
||||
v.HighBeamsOn = packet.Flag.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
|
||||
v.SireneActive = packet.Flag.HasVehFlag(VehicleDataFlags.IsSirenActive);
|
||||
v.IsDead = packet.Flag.HasVehFlag(VehicleDataFlags.IsDead);
|
||||
v.HornActive = packet.Flag.HasVehFlag(VehicleDataFlags.IsHornActive);
|
||||
v.Transformed = packet.Flag.HasVehFlag(VehicleDataFlags.IsTransformed);
|
||||
v.Passengers=new Dictionary<VehicleSeat, SyncedPed>();
|
||||
v.LockStatus=packet.LockStatus;
|
||||
v.RadioStation=packet.RadioStation;
|
||||
v.LicensePlate=packet.LicensePlate;
|
||||
v.Flags=packet.Flag;
|
||||
foreach (KeyValuePair<int, int> pair in packet.Passengers)
|
||||
if (v == null)
|
||||
{
|
||||
if (EntityPool.PedExists(pair.Value))
|
||||
{
|
||||
v.Passengers.Add((VehicleSeat)pair.Key, EntityPool.GetPedByID(pair.Value));
|
||||
}
|
||||
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;
|
||||
v.LastSynced = Main.Ticked;
|
||||
v.LastSyncedStopWatch.Restart();
|
||||
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
|
||||
{
|
||||
v.DamageModel = packet.DamageModel;
|
||||
v.EngineHealth = packet.EngineHealth;
|
||||
v.Mods = packet.Mods;
|
||||
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.LastFullSynced = Main.Ticked;
|
||||
}
|
||||
v.LastStateSynced= Main.Ticked;
|
||||
|
||||
}
|
||||
private static void ProjectileSync(Packets.ProjectileSync packet)
|
||||
{
|
||||
|
||||
var p = EntityPool.GetProjectileByID(packet.ID);
|
||||
if (p==null)
|
||||
if (p == null)
|
||||
{
|
||||
if (packet.Exploded) { return; }
|
||||
Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
|
||||
EntityPool.ThreadSafe.Add(p=new SyncedProjectile(packet.ID));
|
||||
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.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;
|
||||
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.LastSynced = Main.Ticked;
|
||||
p.LastSyncedStopWatch.Restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+161
-132
@@ -1,178 +1,207 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using GTA;
|
||||
using GTA.Math;
|
||||
using GTA.Native;
|
||||
using Lidgren.Network;
|
||||
using RageCoop.Core;
|
||||
using GTA;
|
||||
using GTA.Native;
|
||||
using GTA.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static partial class Networking
|
||||
{
|
||||
|
||||
|
||||
#region -- SEND --
|
||||
/// <summary>
|
||||
/// Pack the packet then send to server.
|
||||
/// Reduce GC pressure by reusing frequently used packets
|
||||
/// </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)
|
||||
private static class SendPackets
|
||||
{
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
p.Pack(outgoingMessage);
|
||||
Client.SendMessage(outgoingMessage, method, (int)channel);
|
||||
public static Packets.PedSync PedPacket = new Packets.PedSync();
|
||||
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
|
||||
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
|
||||
}
|
||||
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 c)
|
||||
public static void SendPed(SyncedPed sp, bool full)
|
||||
{
|
||||
Ped p = c.MainPed;
|
||||
var packet=new Packets.PedSync()
|
||||
if (sp.LastSentStopWatch.ElapsedMilliseconds < SyncInterval)
|
||||
{
|
||||
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.HasPedFlag(PedDataFlags.IsAiming))
|
||||
{
|
||||
packet.AimCoords = p.GetAimCoord();
|
||||
return;
|
||||
}
|
||||
if (packet.Flag.HasPedFlag(PedDataFlags.IsRagdoll))
|
||||
Ped 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))
|
||||
{
|
||||
packet.RotationVelocity=p.RotationVelocity;
|
||||
p.AimCoords = ped.GetAimCoord();
|
||||
}
|
||||
Send(packet, ConnectionChannel.PedSync);
|
||||
}
|
||||
public static void SendPedState(SyncedPed c)
|
||||
{
|
||||
Ped p = c.MainPed;
|
||||
|
||||
var packet=new Packets.PedStateSync()
|
||||
if (p.Flags.HasPedFlag(PedDataFlags.IsRagdoll))
|
||||
{
|
||||
ID = c.ID,
|
||||
OwnerID=c.OwnerID,
|
||||
Clothes=p.GetPedClothes(),
|
||||
ModelHash=p.Model.Hash,
|
||||
WeaponComponents=p.Weapons.Current.GetWeaponComponents(),
|
||||
WeaponTint=(byte)Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, p, p.Weapons.Current.Hash)
|
||||
};
|
||||
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) { Main.Logger.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 = Main.LocalPlayerID;
|
||||
SyncEvents.TriggerChangeOwner(veh.ID, Main.LocalPlayerID);
|
||||
}
|
||||
}
|
||||
p.Position = ped.ReadPosition();
|
||||
}
|
||||
sp.LastSentStopWatch.Restart();
|
||||
if (full)
|
||||
{
|
||||
var w = ped.VehicleWeapon;
|
||||
p.CurrentWeaponHash = (w != VehicleWeaponHash.Invalid) ? (uint)w : (uint)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)Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
|
||||
|
||||
Send(packet, ConnectionChannel.PedSync);
|
||||
Blip b;
|
||||
if (sp.IsPlayer)
|
||||
{
|
||||
p.BlipColor = Scripting.API.Config.BlipColor;
|
||||
p.BlipSprite = Scripting.API.Config.BlipSprite;
|
||||
p.BlipScale = Scripting.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)
|
||||
public static void SendVehicle(SyncedVehicle v, bool full)
|
||||
{
|
||||
if (v.LastSentStopWatch.ElapsedMilliseconds < SyncInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Vehicle veh = v.MainVehicle;
|
||||
var packet = new Packets.VehicleSync()
|
||||
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.RotationVelocity;
|
||||
packet.ThrottlePower = veh.ThrottlePower;
|
||||
packet.BrakePower = veh.BrakePower;
|
||||
v.LastSentStopWatch.Restart();
|
||||
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering)) { packet.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio(); }
|
||||
if (full)
|
||||
{
|
||||
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);
|
||||
byte primaryColor = 0;
|
||||
byte secondaryColor = 0;
|
||||
unsafe
|
||||
{
|
||||
Function.Call<byte>(Hash.GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
|
||||
}
|
||||
packet.Flags |= VehicleDataFlags.IsFullSync;
|
||||
packet.Colors = new byte[] { primaryColor, secondaryColor };
|
||||
packet.DamageModel = veh.GetVehicleDamageModel();
|
||||
packet.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
|
||||
packet.RoofState = (byte)veh.RoofState;
|
||||
packet.Mods = veh.Mods.GetVehicleMods();
|
||||
packet.ModelHash = veh.Model.Hash;
|
||||
packet.EngineHealth = veh.EngineHealth;
|
||||
packet.LockStatus = veh.LockStatus;
|
||||
packet.LicensePlate = Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
|
||||
packet.Livery = Function.Call<int>(Hash.GET_VEHICLE_LIVERY, veh);
|
||||
if (v.MainVehicle == Game.Player.LastVehicle)
|
||||
{
|
||||
packet.RadioStation = Util.GetPlayerRadioIndex();
|
||||
}
|
||||
if (packet.EngineHealth > v.LastEngineHealth)
|
||||
{
|
||||
packet.Flags |= VehicleDataFlags.Repaired;
|
||||
}
|
||||
v.LastEngineHealth = packet.EngineHealth;
|
||||
}
|
||||
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,
|
||||
RoofState=(byte)veh.RoofState,
|
||||
Mods = veh.Mods.GetVehicleMods(),
|
||||
ModelHash=veh.Model.Hash,
|
||||
EngineHealth=veh.EngineHealth,
|
||||
Passengers=veh.GetPassengers(),
|
||||
LockStatus=veh.LockStatus,
|
||||
LicensePlate=Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, veh)
|
||||
};
|
||||
if (v.MainVehicle==Game.Player.LastVehicle)
|
||||
{
|
||||
packet.RadioStation=Util.GetPlayerRadioIndex();
|
||||
}
|
||||
Send(packet, ConnectionChannel.VehicleSync);
|
||||
SendSync(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);
|
||||
sp.ExtractData(ref SendPackets.ProjectilePacket);
|
||||
if (sp.MainProjectile.IsDead) { EntityPool.RemoveProjectile(sp.ID, "Dead"); }
|
||||
SendSync(SendPackets.ProjectilePacket, ConnectionChannel.ProjectileSync);
|
||||
}
|
||||
|
||||
|
||||
#region SYNC EVENTS
|
||||
public static void SendBulletShot(Vector3 start,Vector3 end,uint weapon,int ownerID)
|
||||
public static void SendBullet(Vector3 start, Vector3 end, uint weapon, int ownerID)
|
||||
{
|
||||
Send(new Packets.BulletShot()
|
||||
SendSync(new Packets.BulletShot()
|
||||
{
|
||||
StartPosition = start,
|
||||
EndPosition = end,
|
||||
OwnerID = ownerID,
|
||||
WeaponHash=weapon,
|
||||
WeaponHash = weapon,
|
||||
}, ConnectionChannel.SyncEvents);
|
||||
}
|
||||
public static void SendVehicleBullet(uint hash, SyncedPed owner, EntityBone b)
|
||||
{
|
||||
SendSync(new Packets.VehicleBulletShot
|
||||
{
|
||||
StartPosition = b.Position,
|
||||
EndPosition = b.Position + b.ForwardVector,
|
||||
OwnerID = owner.ID,
|
||||
Bone = (ushort)b.Index,
|
||||
WeaponHash = hash
|
||||
});
|
||||
}
|
||||
#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
|
||||
#endif
|
||||
Peer.SendTo(new Packets.ChatMessage(new Func<string, byte[]>((s) => Security.Encrypt(s.GetBytes())))
|
||||
{ Username = Main.Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat, NetDeliveryMethod.ReliableOrdered);
|
||||
Peer.FlushSendQueue();
|
||||
}
|
||||
public static void SendDownloadFinish(int id)
|
||||
public static void SendVoiceMessage(byte[] buffer, int recorded)
|
||||
{
|
||||
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
|
||||
|
||||
new Packets.FileTransferComplete() { ID = id }.Pack(outgoingMessage);
|
||||
|
||||
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.File);
|
||||
Client.FlushSendQueue();
|
||||
|
||||
#if DEBUG
|
||||
#endif
|
||||
SendSync(new Packets.Voice() { ID = Main.LocalPlayerID, Buffer = buffer, Recorded = recorded }, ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RageCoop.Client
|
||||
{
|
||||
internal static class Statistics
|
||||
{
|
||||
public static int BytesDownPerSecond { get; private set; }
|
||||
public static int BytesUpPerSecond { get; private set; }
|
||||
static Statistics()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user