211 Commits
0.3a ... 0.5a

Author SHA1 Message Date
1e1259d0a5 Fix 2022-07-12 17:16:28 +08:00
20b326d396 Check before breaking a door 2022-07-12 17:15:54 +08:00
b7cad3b8f7 Move GetWeaponComponents to WeaponUtil 2022-07-12 17:14:06 +08:00
cf8b54a3b5 Fix WeatherTimeSync option 2022-07-12 17:10:16 +08:00
edca1e2b98 Don't sync invisible vehicles 2022-07-11 14:53:22 +08:00
89410b8b46 Fix master server 2022-07-11 13:26:28 +08:00
a9397690e2 Fix 2022-07-11 12:02:46 +08:00
ea678ece94 Update master server 2022-07-11 12:01:12 +08:00
f28f23f560 Change master server 2022-07-11 12:00:25 +08:00
b2911017d0 Cleanup 2022-07-11 11:59:32 +08:00
cda4f702a1 Small API cleanup 2022-07-11 11:30:00 +08:00
b6a0ae7f4a Remove MapLoader 2022-07-11 11:24:17 +08:00
b6f3508680 Fix 2022-07-11 11:11:48 +08:00
ad0368b4c0 Merge resource loading 2022-07-11 11:10:43 +08:00
b1d67bd1c4 Add OnKeyDown and OnKeyUp event for API 2022-07-11 11:06:57 +08:00
cda3d37f37 Connect fix 2022-07-11 11:06:31 +08:00
abed0f146f Don't load resources previously downloaded 2022-07-11 10:08:21 +08:00
bdeaeee293 Update Resources.cs 2022-07-10 23:30:29 +08:00
b37ce4d0c1 Update 2022-07-10 18:02:53 +08:00
e3ff62bdc7 Remove docs 2022-07-10 16:49:41 +08:00
eab6c79e15 Update docs 2022-07-10 16:39:28 +08:00
260678c1bc Update index.md 2022-07-10 16:34:42 +08:00
b2d300784f Update docs 2022-07-10 16:33:59 +08:00
8d1d4f5eb7 Update nightly-build.yaml 2022-07-10 16:25:44 +08:00
25a03c7e27 Update nightly-build.yaml 2022-07-10 16:22:24 +08:00
27ab63ae7a Add weather and time sync 2022-07-10 16:13:08 +08:00
9567e97b31 Add vehicle livery sync 2022-07-09 22:18:00 +08:00
b10a8b47d5 Small vehicle exit fix 2022-07-09 19:59:17 +08:00
1bc274e2d0 Some tweaks 2022-07-09 19:32:11 +08:00
dca3420071 Use Array.Copy() instead of Linq 2022-07-09 14:04:39 +08:00
bc3fc70a95 Some tweaks 2022-07-09 13:55:06 +08:00
eee983dfd2 Change output path 2022-07-09 13:27:07 +08:00
fbec69cd71 Reload SHVDN on disconnect if there're client resources 2022-07-09 13:16:41 +08:00
615c49e395 Add country 2022-07-09 11:39:37 +08:00
462d68df6b Add addtional server info 2022-07-09 11:36:04 +08:00
0224abc9bd Request model before creating vehicle 2022-07-09 11:21:42 +08:00
16fe2fe1ab Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-09 10:51:26 +08:00
61c4486551 Disable code trimming 2022-07-09 10:44:54 +08:00
17ef088492 Don't show info if no script loaded 2022-07-09 09:59:17 +08:00
1111879c4d Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-09 09:55:22 +08:00
e9d0b73296 Add managed and loaded assembly check 2022-07-09 09:55:12 +08:00
8eadbf6fee Update README.md 2022-07-08 19:51:21 +08:00
8ae491d0d5 Move ServerPbject and ServerEntities to RageCoop.Server.Scripting 2022-07-07 16:57:43 +08:00
3dd125f861 Reference custom SHVDN build 2022-07-07 16:24:03 +08:00
01b4753dc6 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-06 01:03:35 +08:00
5a5d78ac27 Networking statistics, disable resource unload 2022-07-06 01:03:18 +08:00
a05eedd68e Fix NativeResponse 2022-07-05 21:58:37 +08:00
05304f9461 Add SendCustomEventQueued() 2022-07-05 11:18:26 +08:00
fff5ec4534 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-05 10:52:33 +08:00
cf7641cee0 Change object list to array for CustomEvent 2022-07-05 10:52:22 +08:00
a77e0c1fc9 Fix ServerBlip 2022-07-05 00:18:12 +08:00
f374de2064 Update BaseScript.cs 2022-07-05 00:16:15 +08:00
8e0620bedb Fix CreateVehicle() not setting owner properly 2022-07-05 00:02:22 +08:00
8bb39b1bfa Add PedBlip 2022-07-04 23:59:51 +08:00
2450956fda Add ped blip sync 2022-07-04 22:50:47 +08:00
56cd17401b Change parameter order for consistency 2022-07-04 21:35:01 +08:00
a53b514fec Add ServerEntities.CreateVehicle() 2022-07-04 21:29:13 +08:00
de93af73f2 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-04 09:57:13 +08:00
44f106fd0c Add ServerBlip.Handle handle 2022-07-04 09:54:43 +08:00
8ac5cc40c1 Change parameter order for SendCustomEvent and SendNativecall
Use NativeCall to update entity
2022-07-04 09:41:00 +08:00
8d7eebd390 Fix DeleteEntity not being dispatched to main thread 2022-07-03 23:42:00 +08:00
0a5bb67c54 Add Name property for blip, fix blip scale 2022-07-03 21:00:05 +08:00
f866151cc5 Update docs 2022-07-03 15:37:51 +08:00
08e17b1714 Server side blip and Vector2 argument 2022-07-03 15:28:28 +08:00
fa96f4c073 Add Freeze() method for ServerObject 2022-07-03 10:55:49 +08:00
dc5cf2b965 ServerObject Inheritance, getter and setter for Position and Roatation, Fix connect error. 2022-07-03 10:46:24 +08:00
f47f0570f9 Update docs 2022-07-02 18:32:49 +08:00
e0c96cc200 Minor documentation fix 2022-07-02 18:30:16 +08:00
17e1ae9ea9 Update documentation 2022-07-02 18:04:07 +08:00
8401d616e0 Convert object to handle at client side 2022-07-02 17:53:35 +08:00
d8ac486984 Server side prop control 2022-07-02 17:14:56 +08:00
335ea2ca38 Fix data folder 2022-07-02 13:53:14 +08:00
c73ff96690 Add JB7002 weapon (not tested), closes #15 2022-07-02 13:37:44 +08:00
15b23e3a4e Add DOMINATOR5, IMPALER3, IMPERATOR2, SLAMVAN5 (not tested), closes #16 2022-07-02 13:32:26 +08:00
47f86c098d Add Runier2 weapon, closes #18 2022-07-02 13:21:55 +08:00
6c2c7e881f Add TAMPA3 weapon, closes #19 2022-07-02 13:12:59 +08:00
74a2265200 Add DUNE3 weapon sync 2022-07-02 12:53:18 +08:00
0b2ec1d626 Fix player position not correctly set 2022-07-02 12:39:50 +08:00
8ee188cf19 update docs 2022-07-02 11:23:12 +08:00
eb5b23ae17 Server side entities and custom resource location 2022-07-01 19:02:38 +08:00
77999fe8f3 Add docfx documentation 2022-07-01 17:00:42 +08:00
64fda51917 Restore 2022-07-01 14:39:43 +08:00
4165b757a5 XML comments 2022-07-01 13:54:18 +08:00
d31799ab7b Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-01 12:25:37 +08:00
8a46bd6b68 Resource loading 2022-07-01 12:22:31 +08:00
58362c2613 Preparing for PluginLoader 2022-06-30 09:28:13 +08:00
7d6c5fe3bd Remove "nightly" suffix 2022-06-27 16:00:19 +08:00
f9b9d78b79 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-06-27 15:35:32 +08:00
7670af88a2 Thread termination fix 2022-06-27 15:35:23 +08:00
0800361b40 Minor fixes 2022-06-27 14:42:57 +08:00
cdf3f41127 Update README.md 2022-06-27 14:13:55 +08:00
552bf8d61c Update README.md 2022-06-27 14:12:53 +08:00
2f58e48f42 Nightly 2022-06-27 14:07:13 +08:00
67035f5d2f test 2022-06-27 13:56:42 +08:00
b48206d942 nightly test 2022-06-27 13:49:16 +08:00
87d8102a1a Release profile 2022-06-27 13:32:06 +08:00
bd1f0e1713 Worker 2022-06-27 13:30:35 +08:00
04c9081851 Security fix and merge LemonUI 2022-06-27 13:02:31 +08:00
9cf23cac7f never gonna give~ 2022-06-24 18:25:24 +08:00
46f75c5a21 Update README.md 2022-06-24 16:59:41 +08:00
6167417bf8 Reworked on resource loading, Added RegisterCommands overload to register commands from an instance. 2022-06-24 16:49:59 +08:00
82ab9237f5 Security implementation 2022-06-24 10:33:36 +08:00
82e7cdd785 Multiple server instances in one process is now possible 2022-06-23 14:10:16 +08:00
41385abc7d Rework on SendNativeCall 2022-06-23 09:46:38 +08:00
84171e2949 We're almost here... 2022-06-22 14:18:20 +08:00
42c038d8ab An attempt to fix #12, along with some cleanup and refactor 2022-06-22 10:29:41 +08:00
fe6b42e509 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-06-22 10:05:11 +08:00
8bc5c11030 Force terminate. 2022-06-22 10:03:45 +08:00
76f7157749 Update README.md 2022-06-22 09:58:18 +08:00
52a242c72a Delete ignored assembly 2022-06-22 09:46:36 +08:00
8a42f7c626 Add ServerScript/ClientScript.CurrentResource 2022-06-22 09:45:17 +08:00
6e2c0a3037 Flush before disposal 2022-06-22 09:03:58 +08:00
1b77234cfe Add flushImmediately to logger 2022-06-22 09:00:35 +08:00
a15785a02f Move PlayerData to RageCoop.Client, remove RageCoop.Core.Logging 2022-06-22 08:58:36 +08:00
1a5f594b36 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-06-22 08:28:11 +08:00
68b6295126 fix 2022-06-22 08:28:08 +08:00
17d6d86194 Oops 2022-06-21 23:52:30 +08:00
f82134fb12 Fix Client ID not set 2022-06-21 23:43:12 +08:00
37edef425c Add OnPlayerUpdate 2022-06-21 19:06:50 +08:00
30f7a281b0 Resource system 2022-06-21 18:13:30 +08:00
e1606889f7 Add stealth mode sync 2022-06-20 11:08:46 +08:00
33244b930f Remove CLSCompliant attribute 2022-06-20 10:55:12 +08:00
212c004d31 Add license plate sync 2022-06-20 10:27:45 +08:00
190f23240a PopUp 2022-06-19 15:04:38 +08:00
376830d6dc Add RoofState sync. 2022-06-19 13:36:23 +08:00
9a9fb2870c Add weapon tint sync 2022-06-19 11:47:39 +08:00
2a093d1da0 Fix radio not changed when entering vehicle 2022-06-19 11:22:45 +08:00
1a66f14bae Fix component for mp models 2022-06-19 11:12:20 +08:00
887e7f428d shhh~ 2022-06-18 12:06:22 +08:00
cc4f5f46ce Urgh... 2022-06-18 12:04:31 +08:00
13883242e4 Add ped cloth texture and palette sync, closes #11 2022-06-17 19:14:56 +08:00
09a97c43fd Spread out state, networking statistics fix 2022-06-17 18:11:39 +08:00
671b4f9f6f Mixed rotation calculation 2022-06-17 15:26:27 +08:00
1ae913f0d2 Fix vehicle projectile 2022-06-17 11:33:53 +08:00
45f487c9f4 Better weapon damage reproduction 2022-06-17 11:08:49 +08:00
7f1891236d Fix projectile issue. 2022-06-16 11:57:48 +08:00
1c981c331c Blah 2022-06-14 09:46:47 +08:00
0c9213936d Back to old projectile sync 2022-06-14 09:40:55 +08:00
188556991f Don't flush 2022-06-12 18:02:01 +08:00
b6450027b8 Don't flush 2022-06-12 18:00:42 +08:00
82cffea6ed Some fixes 2022-06-12 17:11:14 +08:00
734788f310 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-06-12 15:39:42 +08:00
45c3ff83cf API cleanup, prepare for CustomEvent, fix server not shutting down 2022-06-12 15:39:32 +08:00
c7655573aa Add linux binary for nightly build 2022-06-12 15:07:10 +08:00
88a4f046d4 Many poops.
Basically working resource system
Improved projectile movement
Better receive messages
Include Lidgren.Network in core
2022-06-11 18:41:10 +08:00
7ccccabe1d Update README.md 2022-06-07 08:38:47 +08:00
3d6dbd5f2a Update nightly-build.yaml 2022-06-07 08:31:25 +08:00
9407d80858 Update nightly-build.yaml 2022-06-07 08:23:16 +08:00
51eaf4f56d Update nightly-build.yaml 2022-06-07 08:11:58 +08:00
4b5ec37987 Update nightly-build.yaml 2022-06-06 20:34:05 +08:00
3f246a8f7d Update nightly-build.yaml 2022-06-06 20:07:57 +08:00
7f4833be81 Update nightly-build.yaml 2022-06-06 18:32:02 +08:00
755615a887 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-06-06 18:29:58 +08:00
ef32603801 Update nightly-build.yaml 2022-06-06 18:29:50 +08:00
fa80b87f7a Change folder name 2022-06-06 18:27:19 +08:00
f8b7062c61 Update nightly-build.yaml 2022-06-06 17:53:03 +08:00
95c4c42ff0 Rename nightly-build to nightly-build.yaml 2022-06-06 17:50:20 +08:00
13fa206594 Create nightly-build 2022-06-06 17:49:54 +08:00
a1dda2ffa4 Fixed three players issue, some work on API and resources management. 2022-06-06 17:37:11 +08:00
0914afc4ed OK that's enough for today... 2022-06-04 18:16:32 +08:00
9f2cf2eb03 Scripting prototype? 2022-06-04 18:09:42 +08:00
1607b7025c Yeah I figured it out... 2022-06-04 14:13:08 +08:00
2c0b7d4d64 Update version 2022-06-04 12:31:45 +08:00
62dc98f4a7 Remove unnecessary packages 2022-06-04 12:25:01 +08:00
d061b5ecbe Fix NullReferenceException on start. 2022-06-04 12:04:02 +08:00
546f4b16f4 No, 0.5 is better. 2022-06-03 17:56:36 +08:00
5a455d0487 Auto fetch master server address 2022-06-03 16:28:02 +08:00
11d178498f Back to old rotation calculation. 2022-06-03 14:40:41 +08:00
88a51cc154 Add radio station sync. 2022-06-03 13:11:17 +08:00
291ba5691b Request WeaponAsset before swapping weapon. 2022-06-02 16:40:38 +08:00
de63cd1271 Add Tank muzzle flash 2022-06-02 15:51:58 +08:00
8479502d75 Fix scramjet 2022-06-02 14:10:29 +08:00
051b496021 Two muzzles fix 2022-06-02 13:52:18 +08:00
2c6f0dbe3d Don't delete player's vehicle 2022-06-02 13:11:01 +08:00
ffb73c1ce4 Fix savage 2022-06-02 13:09:13 +08:00
c941ca96fc Projectile and stromberg fix. 2022-06-02 12:44:05 +08:00
e2d4ce2159 Weapon fix 2022-06-02 10:34:33 +08:00
a500a4eeed closes #4, add scramjet. 2022-06-02 09:27:39 +08:00
603e8af746 closes #5, add Vigilante. 2022-06-02 09:26:53 +08:00
0ae4e1c122 closes #6, add Apocalypse 380Z 2022-06-02 09:25:23 +08:00
83f260048d closes #7, add Future Shock 380Z. 2022-06-02 09:24:37 +08:00
9b79956c71 closes #8, add Nightmare 380Z 2022-06-02 09:23:44 +08:00
82c6dab4c4 add stromberg 2022-06-02 09:21:46 +08:00
6611ecac18 closes #10, add apocalypse 2022-06-02 09:17:19 +08:00
3fa62f1a69 closes #3, add savage 2022-06-02 09:15:18 +08:00
ad3031ef59 Logger fix 2022-06-02 08:53:01 +08:00
42d057e2c5 Latency update and threading fixes. 2022-06-01 19:05:45 +08:00
87b9a67d0b Update assemby version 2022-06-01 18:01:38 +08:00
4fca28f0e0 Ah~ 2022-06-01 17:57:54 +08:00
8ebf03ca99 Hmm... 2022-06-01 17:57:27 +08:00
f2760b1533 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-06-01 17:56:02 +08:00
7eec12486f country road, take me home... 2022-06-01 17:55:38 +08:00
d789e4cb0b Update README.md 2022-06-01 16:27:50 +08:00
485497286f Update README.md
some shit
2022-06-01 16:27:10 +08:00
a305fb349f Update README.md 2022-06-01 16:06:02 +08:00
7aa41a6e0c Master server is back! 2022-05-31 23:12:32 -08:00
9d9d421671 Small fix 2022-05-31 19:46:40 -08:00
a8a16fac61 Scripting? 2022-05-31 19:35:01 -08:00
b863b7037a Added ability to disable automatic respawn
Prepare for client side scripting.
2022-05-31 02:16:12 -08:00
a33f839004 opt 2022-05-31 10:11:06 +08:00
a48170fec8 EnterVehicle fix, reduce NPC 2022-05-31 09:55:54 +08:00
2181a52d6c Add Tula 2022-05-31 09:24:28 +08:00
b96d349e4a Lots of sh*t
API cleanup
Complete deluxo transformation sync
blahblahblah
2022-05-31 09:14:30 +08:00
ad698a656d Removed LVector, LQuaternion.
Server side optimization.
Nework optimization (streaming distance).
2022-05-30 14:32:38 +08:00
aefea337f1 Some optimization. 2022-05-30 11:11:40 +08:00
811e7df14e fix for changing owner. 2022-05-29 18:27:07 +08:00
5cc63e688f Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-05-29 18:16:44 +08:00
08aa1370de Add nozzle and deluxo transformation sync. 2022-05-29 18:16:32 +08:00
5d6b99442b Update README.md 2022-05-27 17:08:40 +08:00
65b695cb78 Update README.md 2022-05-27 17:06:41 +08:00
ce94a9c7af Update README.md 2022-05-27 17:01:31 +08:00
137 changed files with 9843 additions and 57517 deletions

99
.github/workflows/nightly-build.yaml vendored Normal file
View File

@ -0,0 +1,99 @@
name: Nightly-build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
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: Build client
run: dotnet publish RageCoop.Client/RageCoop.Client.csproj --no-restore --configuration Release -o RageCoop.Client/bin/RageCoop -f net48
- name: Build server win-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o RageCoop.Server/bin/win-x64 -c Release
- name: Build server linux-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o RageCoop.Server/bin/linux-x64 -c Release
- name: Build server linux-arm
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o RageCoop.Server/bin/linux-arm -c Release
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Client/bin
dest: RageCoop.Client.zip
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Server/bin/win-x64
dest: RageCoop.Server-win-x64.zip
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Server/bin/linux-x64
dest: RageCoop.Server-linux-x64.zip
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Server/bin/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

View File

@ -1,270 +0,0 @@
#undef DEBUG
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using RageCoop.Core;
namespace RageCoop.Client
{
/// <summary>
/// ?
/// </summary>
public static class COOPAPI
{
#region DELEGATES
/// <summary>
/// ?
/// </summary>
/// <param name="connected"></param>
/// <param name="from">The Lidgren-Network net handle</param>
/// <param name="reason"></param>
public delegate void ConnectEvent(bool connected, long from, string reason = null);
/// <summary>
/// ?
/// </summary>
/// <param name="from"></param>
/// <param name="message">The Lidgren-Network net handle</param>
/// <param name="args"></param>
public delegate void ChatMessage(string from, string message, CancelEventArgs args);
/// <summary>
/// ?
/// </summary>
/// <param name="from">The Lidgren-Network net handle</param>
/// <param name="mod"></param>
/// <param name="customID"></param>
/// <param name="bytes"></param>
public delegate void ModEvent(long from, string mod, byte customID, byte[] bytes);
#endregion
#region EVENTS
/// <summary>
/// ?
/// </summary>
public static event ConnectEvent OnConnection;
/// <summary>
/// ?
/// </summary>
public static event ChatMessage OnChatMessage;
/// <summary>
/// ?
/// </summary>
public static event ModEvent OnModPacketReceived;
public static void Connected()
{
OnConnection?.Invoke(true, GetPlayerID());
}
public static void Disconnected(string reason)
{
OnConnection?.Invoke(false, GetPlayerID(), reason);
}
public static void Connected(long netHandle)
{
OnConnection?.Invoke(true, netHandle);
}
public static void Disconnected(long netHandle)
{
OnConnection?.Invoke(false, netHandle);
}
public static void ModPacketReceived(long from, string mod, byte customID, byte[] bytes)
{
OnModPacketReceived?.Invoke(from, mod, customID, bytes);
}
public static bool ChatMessageReceived(string from, string message)
{
CancelEventArgs args = new CancelEventArgs(false);
OnChatMessage?.Invoke(from, message, args);
return args.Cancel;
}
#endregion
/// <summary>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Username of the player who sent this message</param>
/// <param name="message">The player's message</param>
public static void LocalChatMessage(string from, string message)
{
Main.MainChat.AddMessage(from, message);
}
/// <summary>
/// Connect to any server
/// </summary>
/// <param name="serverAddress">The server address to connect. Example: 127.0.0.1:4499</param>
public static void Connect(string serverAddress)
{
Networking.DisConnectFromServer(serverAddress);
}
/// <summary>
/// ?
/// </summary>
public static void Disconnect()
{
Networking.DisConnectFromServer(null);
}
/// <summary>
/// Check if the player is already on a server
/// </summary>
public static bool IsOnServer()
{
return Networking.IsOnServer;
}
/// <summary>
/// Get the local player's ID
/// </summary>
/// <returns>PlayerID</returns>
public static long GetPlayerID()
{
return Main.LocalPlayerID;
}
/*
/// <summary>
/// Get a player using their Lidgren Network net handle
/// </summary>
/// <param name="handle">Lidgren-Network net handle</param>
public static CharacterEntity GetPed(int ID)
{
lock (Main.Characters)
{
return Main.Characters.ContainsKey(ID) ? Main.Characters[ID] : null;
}
}
*/
/// <summary>
/// Check if a RAGECOOP menu is visible
/// </summary>
public static bool IsMenuVisible()
{
#if NON_INTERACTIVE
return false;
#else
return Main.MainMenu.MenuPool.AreAnyVisible;
#endif
}
/// <summary>
/// Check if the RAGECOOP chat is visible
/// </summary>
public static bool IsChatFocused()
{
return Main.MainChat.Focused;
}
/// <summary>
/// Check if the RAGECOOP list of players is visible
/// </summary>
public static bool IsPlayerListVisible()
{
return Util.GetTickCount64() - PlayerList.Pressed < 5000;
}
/// <summary>
/// Get the version of RAGECOOP
/// </summary>
public static string GetCurrentVersion()
{
return Main.CurrentVersion;
}
/// <summary>
/// Send any data (bytes) to the server
/// </summary>
/// <param name="modName">The name of this modification (script)</param>
/// <param name="customID">The ID to know what the data is</param>
/// <param name="bytes">Your class, structure or whatever in bytes</param>
public static void SendDataToServer(string modName, byte customID, byte[] bytes)
{
Networking.SendModData(-1, modName, customID, bytes);
}
/// <summary>
/// Send any data (bytes) to the all player
/// </summary>
/// <param name="modName">The name of this modification (script)</param>
/// <param name="customID">The ID to know what the data is</param>
/// <param name="bytes">Your class, structure or whatever in bytes</param>
public static void SendDataToAll(string modName, byte customID, byte[] bytes)
{
Networking.SendModData(0, modName, customID, bytes);
}
/// <summary>
/// Send any data (bytes) to a player
/// </summary>
/// <param name="netHandle">The Lidgren Network net handle that receives the data</param>
/// <param name="modName">The name of this modification (script)</param>
/// <param name="customID">The ID to know what the data is</param>
/// <param name="bytes">Your class, structure or whatever in bytes</param>
public static void SendDataToPlayer(long netHandle, string modName, byte customID, byte[] bytes)
{
Networking.SendModData(netHandle, modName, customID, bytes);
}
/// <summary>
/// Get that player's local username
/// </summary>
public static string GetUsername()
{
return Main.Settings.Username;
}
/// <summary>
/// Set a new username for this player
/// </summary>
/// <param name="username">The new username</param>
/// <returns>false if the player already joined a server or the username is null or empty otherwise true</returns>
public static bool SetUsername(string username)
{
if (IsOnServer() || string.IsNullOrEmpty(username))
{
return false;
}
Main.Settings.Username = username;
return true;
}
/// <summary>
/// Enable or disable the local traffic for this player
/// </summary>
/// <param name="enable">true to disable traffic</param>
public static void SetLocalTraffic(bool enable)
{
Main.Settings.DisableTraffic = !enable;
}
/// <summary>
/// Sets the alignment for the player list, if set to true it will align left,
/// otherwise it will align right
/// </summary>
/// <param name="leftAlign">true to move the player list to the left</param>
public static void SetPlayerListLeftAlign(bool leftAlign)
{
PlayerList.LeftAlign = leftAlign;
}
#if DEBUG
/// <summary>
/// ?
/// </summary>
/// <param name="value"></param>
public static void SetDebug(bool value)
{
Main.UseDebug = value;
}
#endif
}
}

View File

@ -1,115 +0,0 @@
using GTA;
using System.Drawing;
using LemonUI;
using LemonUI.Menus;
namespace RageCoop.Client.Menus
{
/// <summary>
/// Don't use it!
/// </summary>
public class RageCoopMenu
{
public ObjectPool MenuPool = new ObjectPool();
public NativeMenu MainMenu = new NativeMenu("RAGECOOP", "MAIN")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
#region SUB
public Sub.SettingsMenu SubSettings = new Sub.SettingsMenu();
#endregion
#region ITEMS
private readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
public readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
private readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.CurrentVersion.Replace("_", ".")) { LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
#endregion
/// <summary>
/// Don't use it!
/// </summary>
public RageCoopMenu()
{
MainMenu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
MainMenu.Title.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated;
ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) => { Networking.DisConnectFromServer(Main.Settings.LastServerAddress); };
MainMenu.Add(_usernameItem);
MainMenu.Add(ServerIpItem);
MainMenu.Add(_serverConnectItem);
MainMenu.Add(_aboutItem);
MainMenu.AddSubMenu(SubSettings.Menu);
MainMenu.AddSubMenu(DevToolMenu.Menu);
MainMenu.AddSubMenu(DebugMenu.Menu);
MenuPool.Add(MainMenu);
MenuPool.Add(SubSettings.Menu);
MenuPool.Add(DevToolMenu.Menu);
MenuPool.Add(DebugMenu.Menu);
MenuPool.Add(DebugMenu.DiagnosticMenu);
}
public void UsernameActivated(object a, System.EventArgs b)
{
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
if (!string.IsNullOrWhiteSpace(newUsername))
{
Main.Settings.Username = newUsername;
Util.SaveSettings();
_usernameItem.AltTitle = newUsername;
}
}
public void ServerIpActivated(object a, System.EventArgs b)
{
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
{
Main.Settings.LastServerAddress = newServerIp;
Util.SaveSettings();
ServerIpItem.AltTitle = newServerIp;
}
}
public void InitiateConnectionMenuSetting()
{
MainMenu.Items[0].Enabled = false;
MainMenu.Items[1].Enabled = false;
MainMenu.Items[2].Enabled = false;
}
public void ConnectedMenuSetting()
{
MainMenu.Items[2].Enabled = true;
MainMenu.Items[2].Title = "Disconnect";
MainMenu.Visible = false;
}
public void DisconnectedMenuSetting()
{
MainMenu.Items[0].Enabled = true;
MainMenu.Items[1].Enabled = true;
MainMenu.Items[2].Enabled = true;
MainMenu.Items[2].Title = "Connect";
SubSettings.Menu.Items[1].Enabled = false;
}
}
}

View File

@ -1,903 +0,0 @@
using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using RageCoop.Core;
using GTA;
using GTA.Native;
using GTA.Math;
using System.Linq;
using System.Diagnostics;
namespace RageCoop.Client
{
/// <summary>
///
/// </summary>
internal static partial class Util
{
#region -- POINTER --
private static int _steeringAngleOffset { get; set; }
public static unsafe void NativeMemory()
{
IntPtr address;
address = Game.FindPattern("\x74\x0A\xF3\x0F\x11\xB3\x1C\x09\x00\x00\xEB\x25", "xxxxxx????xx");
if (address != IntPtr.Zero)
{
_steeringAngleOffset = *(int*)(address + 6) + 8;
}
// breaks some stuff.
/*
address = Game.FindPattern("\x32\xc0\xf3\x0f\x11\x09", "xxxxxx"); // Weapon / Radio slowdown
if (address != IntPtr.Zero)
{
for (int i = 0; i < 6; i++)
{
*(byte*)(address + i).ToPointer() = 0x90;
}
}
*/
}
public static unsafe void CustomSteeringAngle(this Vehicle veh, float value)
{
IntPtr address = new IntPtr((long)veh.MemoryAddress);
if (address == IntPtr.Zero || _steeringAngleOffset == 0)
{
return;
}
*(float*)(address + _steeringAngleOffset).ToPointer() = value;
}
#endregion
public static Settings ReadSettings()
{
XmlSerializer ser = new XmlSerializer(typeof(Settings));
string path = Directory.GetCurrentDirectory() + "\\Scripts\\RageCoop\\RageCoop.Client.Settings.xml";
Settings settings = null;
if (File.Exists(path))
{
using (FileStream stream = File.OpenRead(path))
{
settings = (RageCoop.Client.Settings)ser.Deserialize(stream);
}
using (FileStream stream = new FileStream(path, FileMode.Truncate, FileAccess.ReadWrite))
{
ser.Serialize(stream, settings);
}
}
else
{
using (FileStream stream = File.OpenWrite(path))
{
ser.Serialize(stream, settings = new Settings());
}
}
return settings;
}
public static void SaveSettings()
{
try
{
string path = Directory.GetCurrentDirectory() + "\\Scripts\\RageCoop\\RageCoop.Client.Settings.xml";
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite))
{
XmlSerializer ser = new XmlSerializer(typeof(Settings));
ser.Serialize(stream, Main.Settings);
}
}
catch (Exception ex)
{
GTA.UI.Notification.Show("Error saving player settings: " + ex.Message);
}
}
public static bool IsBetween<T>(this T item, T start, T end)
{
return Comparer<T>.Default.Compare(item, start) >= 0 && Comparer<T>.Default.Compare(item, end) <= 0;
}
public static bool Compare<T, Y>(this Dictionary<T, Y> item, Dictionary<T, Y> item2)
{
if (item == null || item2 == null || item.Count != item2.Count)
{
return false;
}
foreach (KeyValuePair<T, Y> pair in item)
{
if (item2.TryGetValue(pair.Key, out Y value) && Equals(value, pair.Value))
{
continue;
}
// TryGetValue() or Equals failed
return false;
}
// No difference between item and item2
return true;
}
#region MATH
public static Vector3 LinearVectorLerp(Vector3 start, Vector3 end, ulong currentTime, int duration)
{
return new Vector3()
{
X = LinearFloatLerp(start.X, end.X, currentTime, duration),
Y = LinearFloatLerp(start.Y, end.Y, currentTime, duration),
Z = LinearFloatLerp(start.Z, end.Z, currentTime, duration),
};
}
public static float LinearFloatLerp(float start, float end, ulong currentTime, int duration)
{
return (end - start) * currentTime / duration + start;
}
public static float Lerp(float from, float to, float fAlpha)
{
return (from * (1.0f - fAlpha)) + (to * fAlpha); //from + (to - from) * fAlpha
}
public static Vector3 RotationToDirection(Vector3 rotation)
{
double z = DegToRad(rotation.Z);
double x = DegToRad(rotation.X);
double num = Math.Abs(Math.Cos(x));
return new Vector3
{
X = (float)(-Math.Sin(z) * num),
Y = (float)(Math.Cos(z) * num),
Z = (float)Math.Sin(x)
};
}
#endregion
public static Model ModelRequest(this int hash)
{
Model model = new Model(hash);
if (!model.IsValid)
{
//GTA.UI.Notification.Show("~y~Not valid!");
return null;
}
if (!model.IsLoaded)
{
return model.Request(1000) ? model : null;
}
return model;
}
#region PED
public static byte GetPedSpeed(this Ped ped)
{
if (ped.IsSprinting)
{
return 3;
}
if (ped.IsRunning)
{
return 2;
}
if (ped.IsWalking)
{
return 1;
}
return 0;
}
public static Dictionary<byte, short> GetPedClothes(this Ped ped)
{
Dictionary<byte, short> result = new Dictionary<byte, short>();
for (byte i = 0; i < 11; i++)
{
short mod = Function.Call<short>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result.Add(i, mod);
}
return result;
}
public static PedDataFlags GetPedFlags(this Ped ped)
{
PedDataFlags flags = PedDataFlags.None;
if (ped.IsAiming || ped.IsOnTurretSeat())
{
flags |= PedDataFlags.IsAiming;
}
if (ped.IsReloading)
{
flags |= PedDataFlags.IsReloading;
}
if (ped.IsJumping)
{
flags |= PedDataFlags.IsJumping;
}
if (ped.IsRagdoll)
{
flags |= PedDataFlags.IsRagdoll;
}
if (ped.IsOnFire)
{
flags |= PedDataFlags.IsOnFire;
}
if (ped.IsInParachuteFreeFall)
{
flags |= PedDataFlags.IsInParachuteFreeFall;
}
if (ped.ParachuteState == ParachuteState.Gliding)
{
flags |= PedDataFlags.IsParachuteOpen;
}
bool climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder);
if (climbingLadder)
{
flags |= PedDataFlags.IsOnLadder;
}
if (ped.IsVaulting && !climbingLadder)
{
flags |= PedDataFlags.IsVaulting;
}
if (ped.IsInCover || ped.IsGoingIntoCover)
{
flags |=PedDataFlags.IsInCover;
}
return flags;
}
public static string[] GetReloadingAnimation(this Ped ped)
{
switch (ped.Weapons.Current.Hash)
{
case WeaponHash.Revolver:
case WeaponHash.RevolverMk2:
case WeaponHash.DoubleActionRevolver:
case WeaponHash.NavyRevolver:
return new string[2] { "anim@weapons@pistol@revolver_str", "reload_aim" };
case WeaponHash.APPistol:
return new string[2] { "weapons@pistol@ap_pistol_str", "reload_aim" };
case WeaponHash.Pistol50:
return new string[2] { "weapons@pistol@pistol_50_str", "reload_aim" };
case WeaponHash.Pistol:
case WeaponHash.PistolMk2:
case WeaponHash.PericoPistol:
case WeaponHash.SNSPistol:
case WeaponHash.SNSPistolMk2:
case WeaponHash.HeavyPistol:
case WeaponHash.VintagePistol:
case WeaponHash.CeramicPistol:
case WeaponHash.MachinePistol:
return new string[2] { "weapons@pistol@pistol_str", "reload_aim" };
case WeaponHash.AssaultRifle:
case WeaponHash.AssaultrifleMk2:
return new string[2] { "weapons@rifle@hi@assault_rifle_str", "reload_aim" };
case WeaponHash.SniperRifle:
return new string[2] { "weapons@rifle@hi@sniper_rifle_str", "reload_aim" };
case WeaponHash.HeavySniper:
case WeaponHash.HeavySniperMk2:
return new string[2] { "weapons@rifle@lo@sniper_heavy_str", "reload_aim" };
case WeaponHash.PumpShotgun:
case WeaponHash.PumpShotgunMk2:
return new string[2] { "weapons@rifle@pump_str", "reload_aim" };
case WeaponHash.Railgun:
return new string[2] { "weapons@rifle@lo@rail_gun_str", "reload_aim" };
case WeaponHash.SawnOffShotgun:
return new string[2] { "weapons@rifle@lo@sawnoff_str", "reload_aim" };
case WeaponHash.AssaultShotgun:
return new string[2] { "weapons@rifle@lo@shotgun_assault_str", "reload_aim" };
case WeaponHash.BullpupShotgun:
return new string[2] { "weapons@rifle@lo@shotgun_bullpup_str", "reload_aim" };
case WeaponHash.AdvancedRifle:
return new string[2] { "weapons@submg@advanced_rifle_str", "reload_aim" };
case WeaponHash.CarbineRifle:
case WeaponHash.CarbineRifleMk2:
case WeaponHash.CompactRifle:
return new string[2] { "weapons@rifle@lo@carbine_str", "reload_aim" };
case WeaponHash.Gusenberg:
return new string[2] { "anim@weapons@machinegun@gusenberg_str", "reload_aim" };
case WeaponHash.Musket:
return new string[2] { "anim@weapons@musket@musket_str", "reload_aim" };
case WeaponHash.FlareGun:
return new string[2] { "anim@weapons@pistol@flare_str", "reload_aim" };
case WeaponHash.SpecialCarbine:
case WeaponHash.SpecialCarbineMk2:
return new string[2] { "anim@weapons@rifle@lo@spcarbine_str", "reload_aim" };
case WeaponHash.CombatPDW:
return new string[2] { "anim@weapons@rifle@lo@pdw_str", "reload_aim" };
case WeaponHash.BullpupRifle:
case WeaponHash.BullpupRifleMk2:
return new string[2] { "anim@weapons@submg@bullpup_rifle_str", "reload_aim" };
case WeaponHash.AssaultSMG:
return new string[2] { "weapons@submg@assault_smg_str", "reload_aim" };
case WeaponHash.MicroSMG:
case WeaponHash.MiniSMG:
return new string[2] { "weapons@submg@lo@micro_smg_str", "reload_aim" };
case WeaponHash.SMG:
case WeaponHash.SMGMk2:
return new string[2] { "weapons@rifle@smg_str", "reload_aim" };
case WeaponHash.GrenadeLauncher:
case WeaponHash.GrenadeLauncherSmoke:
case WeaponHash.CompactGrenadeLauncher:
return new string[2] { "weapons@heavy@grenade_launcher_str", "reload_aim" };
case WeaponHash.RPG:
case WeaponHash.Firework:
return new string[2] { "weapons@heavy@rpg_str", "reload_aim" };
case WeaponHash.CombatMG:
case WeaponHash.CombatMGMk2:
return new string[2] { "weapons@machinegun@combat_mg_str", "reload_aim" };
case WeaponHash.MG:
return new string[2] { "weapons@machinegun@mg_str", "reload_aim" };
default:
Main.Logger.Warning($"~r~Reloading failed! Weapon ~g~[{ped.Weapons.Current.Hash}]~r~ could not be found!");
return null;
}
}
public static VehicleSeat GetNearestSeat(Ped ped, Vehicle veh, float distanceToignoreDoors = 50f)
{
float num = 99f;
int result = -2;
Dictionary<string, int> dictionary = new Dictionary<string, int>();
dictionary.Add("door_dside_f", -1);
dictionary.Add("door_pside_f", 0);
dictionary.Add("door_dside_r", 1);
dictionary.Add("door_pside_r", 2);
foreach (string text in dictionary.Keys)
{
bool flag = veh.Bones[text].Position != Vector3.Zero;
if (flag)
{
float num2 = ped.Position.DistanceTo(Function.Call<Vector3>(Hash.GET_WORLD_POSITION_OF_ENTITY_BONE, new InputArgument[]
{
veh,
veh.Bones[text].Index
}));
bool flag2 = (num2 < distanceToignoreDoors) && (num2 < num)&& IsSeatUsableByPed(ped, veh, dictionary[text]);
if (flag2)
{
num = num2;
result = dictionary[text];
}
}
}
return (VehicleSeat)result;
}
public static bool IsSeatUsableByPed(Ped ped, Vehicle veh, int _seat)
{
VehicleSeat seat = (VehicleSeat)_seat;
bool result = false;
bool flag = veh.IsSeatFree(seat);
if (flag)
{
result = true;
}
else
{
bool isDead = veh.GetPedOnSeat(seat).IsDead;
if (isDead)
{
result = true;
}
else
{
int num = Function.Call<int>(Hash.GET_RELATIONSHIP_BETWEEN_PEDS, new InputArgument[]
{
ped,
veh.GetPedOnSeat(seat)
});
bool flag2 = num > 2;
if (flag2)
{
result = true;
}
}
}
return result;
}
public static bool IsTaskActive(this Ped p,TaskType task)
{
return Function.Call<bool>(Hash.GET_IS_TASK_ACTIVE, p.Handle, task);
}
public static Vector3 GetAimCoord(this Ped p)
{
var weapon = p.Weapons.CurrentWeaponObject;
var v = p.CurrentVehicle;
// Rhino
if (v!=null && v.Model.Hash==782665360)
{
return v.Bones[35].Position+v.Bones[35].ForwardVector*100;
}
if (p.IsOnTurretSeat()) { return p.GetLookingCoord(); }
if (weapon!=null)
{
// Not very accurate, but doesn't matter
Vector3 dir = weapon.RightVector;
return weapon.Position+dir*20;
}
return GetLookingCoord(p);
}
public static Vector3 GetLookingCoord(this Ped p)
{
EntityBone b = p.Bones[Bone.FacialForehead];
Vector3 v = b.UpVector.Normalized;
return b.Position+200*v;
}
public static void StayInCover(this Ped p)
{
Function.Call(Hash.TASK_STAY_IN_COVER, p);
}
public static VehicleSeat GetSeatTryingToEnter(this Ped p)
{
return (VehicleSeat)Function.Call<int>(Hash.GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
}
#endregion
#region VEHICLE
public static VehicleDataFlags GetVehicleFlags(this Vehicle veh)
{
VehicleDataFlags flags = 0;
if (veh.IsEngineRunning)
{
flags |= VehicleDataFlags.IsEngineRunning;
}
if (veh.AreLightsOn)
{
flags |= VehicleDataFlags.AreLightsOn;
}
if (veh.BrakePower >= 0.01f)
{
flags |= VehicleDataFlags.AreBrakeLightsOn;
}
if (veh.AreHighBeamsOn)
{
flags |= VehicleDataFlags.AreHighBeamsOn;
}
if (veh.IsSirenActive)
{
flags |= VehicleDataFlags.IsSirenActive;
}
if (veh.IsDead)
{
flags |= VehicleDataFlags.IsDead;
}
if (Function.Call<bool>(Hash.IS_HORN_ACTIVE, veh.Handle))
{
flags |= VehicleDataFlags.IsHornActive;
}
if (veh.IsSubmarineCar && Function.Call<bool>(Hash._GET_IS_SUBMARINE_VEHICLE_TRANSFORMED, veh.Handle))
{
flags |= VehicleDataFlags.IsTransformed;
}
if (veh.HasRoof && (veh.RoofState == VehicleRoofState.Opened || veh.RoofState == VehicleRoofState.Opening))
{
flags |= VehicleDataFlags.RoofOpened;
}
if (veh.IsAircraft)
{
flags |= VehicleDataFlags.IsAircraft;
}
return flags;
}
public static bool HasFlag(this PedDataFlags flagToCheck,PedDataFlags flag)
{
return (flagToCheck & flag)!=0;
}
public static bool HasFlag(this VehicleDataFlags flagToCheck, VehicleDataFlags flag)
{
return (flagToCheck & flag)!=0;
}
public static Dictionary<uint, bool> GetWeaponComponents(this Weapon weapon)
{
Dictionary<uint, bool> result = null;
if (weapon.Components.Count > 0)
{
result = new Dictionary<uint, bool>();
foreach (var comp in weapon.Components)
{
result.Add((uint)comp.ComponentHash, comp.Active);
}
}
return result;
}
public static Dictionary<int, int> GetVehicleMods(this VehicleModCollection mods)
{
Dictionary<int, int> result = new Dictionary<int, int>();
foreach (VehicleMod mod in mods.ToArray())
{
result.Add((int)mod.Type, mod.Index);
}
return result;
}
public static VehicleDamageModel GetVehicleDamageModel(this Vehicle veh)
{
// Broken windows
byte brokenWindows = 0;
for (int i = 0; i < 8; i++)
{
if (!veh.Windows[(VehicleWindowIndex)i].IsIntact)
{
brokenWindows |= (byte)(1 << i);
}
}
// Broken doors
byte brokenDoors = 0;
byte openedDoors = 0;
foreach (VehicleDoor door in veh.Doors)
{
if (door.IsBroken)
{
brokenDoors |= (byte)(1 << (byte)door.Index);
}
else if (door.IsOpen)
{
openedDoors |= (byte)(1 << (byte)door.Index);
}
}
// Bursted tires
short burstedTires = 0;
foreach (VehicleWheel wheel in veh.Wheels.GetAllWheels())
{
if (wheel.IsBursted)
{
burstedTires |= (short)(1 << (int)wheel.BoneId);
}
}
return new VehicleDamageModel()
{
BrokenDoors = brokenDoors,
OpenedDoors = openedDoors,
BrokenWindows = brokenWindows,
BurstedTires = burstedTires,
LeftHeadLightBroken = (byte)(veh.IsLeftHeadLightBroken ? 1 : 0),
RightHeadLightBroken = (byte)(veh.IsRightHeadLightBroken ? 1 : 0)
};
}
public static Dictionary<int,int> GetPassengers(this Vehicle veh)
{
Dictionary<int,int> ps=new Dictionary<int, int>();
var d = veh.Driver;
if (d!=null&&d.IsSittingInVehicle())
{
ps.Add(-1, d.GetSyncEntity().ID);
}
foreach(Ped p in veh.Passengers)
{
if (p.IsSittingInVehicle())
{
ps.Add((int)p.SeatIndex, (int)p.GetSyncEntity().ID);
}
}
return ps;
}
public static void SetDamageModel(this Vehicle veh, VehicleDamageModel model, bool leavedoors = true)
{
for (int i = 0; i < 8; i++)
{
var door = veh.Doors[(VehicleDoorIndex)i];
if ((model.BrokenDoors & (byte)(1 << i)) != 0)
{
door.Break(leavedoors);
}
else if (door.IsBroken)
{
// The vehicle can only fix a door if the vehicle was completely fixed
veh.Repair();
return;
}
if ((model.OpenedDoors & (byte)(1 << i)) != 0)
{
if ((!door.IsOpen)&&(!door.IsBroken))
{
door.Open();
}
}
else if (door.IsOpen)
{
if (!door.IsBroken) { door.Close(); }
}
if ((model.BrokenWindows & (byte)(1 << i)) != 0)
{
veh.Windows[(VehicleWindowIndex)i].Smash();
}
else if (!veh.Windows[(VehicleWindowIndex)i].IsIntact)
{
veh.Windows[(VehicleWindowIndex)i].Repair();
}
}
foreach (VehicleWheel wheel in veh.Wheels)
{
if ((model.BurstedTires & (short)(1 << (int)wheel.BoneId)) != 0)
{
if (!wheel.IsBursted)
{
wheel.Puncture();
wheel.Burst();
}
}
else if (wheel.IsBursted)
{
wheel.Fix();
}
}
veh.IsLeftHeadLightBroken = model.LeftHeadLightBroken > 0;
veh.IsRightHeadLightBroken = model.RightHeadLightBroken > 0;
}
#endregion
public static void SetOnFire(this Entity e,bool toggle)
{
if (toggle)
{
Function.Call(Hash.START_ENTITY_FIRE, e.Handle);
}
else
{
Function.Call(Hash.STOP_ENTITY_FIRE, e.Handle);
}
}
public static SyncedPed GetSyncEntity(this Ped p)
{
if(p == null) { return null; }
var c = EntityPool.GetPedByHandle(p.Handle);
if(c==null) { EntityPool.Add(c=new SyncedPed(p)); }
return c;
}
public static SyncedVehicle GetSyncEntity(this Vehicle veh)
{
if(veh == null) { return null; }
var v=EntityPool.GetVehicleByHandle(veh.Handle);
if (v==null) { EntityPool.Add(v=new SyncedVehicle(veh)); }
return v;
}
public static double DegToRad(double deg)
{
return deg * Math.PI / 180.0;
}
public static bool IsTurretSeat(this Vehicle veh, int seat)
{
if (!Function.Call<bool>(Hash.DOES_VEHICLE_HAVE_WEAPONS, veh.Handle))
{
return false;
}
switch (seat)
{
case -1:
return (VehicleHash)veh.Model.Hash == VehicleHash.Rhino
|| (VehicleHash)veh.Model.Hash == VehicleHash.Khanjari
|| (VehicleHash)veh.Model.Hash == VehicleHash.FireTruck
|| (VehicleHash)veh.Model.Hash == VehicleHash.Riot2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus3;
case 0:
return (VehicleHash)veh.Model.Hash == VehicleHash.Apc;
case 1:
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical3
|| (VehicleHash)veh.Model.Hash == VehicleHash.HalfTrack
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
case 2:
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
case 3:
return (VehicleHash)veh.Model.Hash == VehicleHash.Limo2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dinghy5;
case 7:
return (VehicleHash)veh.Model.Hash == VehicleHash.Insurgent;
}
return false;
}
public static bool IsOnTurretSeat(this Ped P)
{
if (P.CurrentVehicle == null) { return false; }
return IsTurretSeat(P.CurrentVehicle, (int)P.SeatIndex);
}
[DllImport("kernel32.dll")]
public static extern ulong GetTickCount64();
}
/// <summary>
///
/// </summary>
public static class VectorExtensions
{
/// <summary>
///
/// </summary>
public static Vector3 ToVector(this Quaternion vec)
{
return new Vector3()
{
X = vec.X,
Y = vec.Y,
Z = vec.Z
};
}
/// <summary>
///
/// </summary>
public static Quaternion ToQuaternion(this Vector3 vec, float vW = 0.0f)
{
return new Quaternion()
{
X = vec.X,
Y = vec.Y,
Z = vec.Z,
W = vW
};
}
public static float Denormalize(this float h)
{
return h < 0f ? h + 360f : h;
}
public static float ToRadians(this float val)
{
return (float)(Math.PI / 180) * val;
}
public static Vector3 ToRadians(this Vector3 i)
{
return new Vector3()
{
X = ToRadians(i.X),
Y = ToRadians(i.Y),
Z = ToRadians(i.Z),
};
}
public static Quaternion ToQuaternion(this Vector3 vect)
{
vect = new Vector3()
{
X = vect.X.Denormalize() * -1,
Y = vect.Y.Denormalize() - 180f,
Z = vect.Z.Denormalize() - 180f,
};
vect = vect.ToRadians();
float rollOver2 = vect.Z * 0.5f;
float sinRollOver2 = (float)Math.Sin((double)rollOver2);
float cosRollOver2 = (float)Math.Cos((double)rollOver2);
float pitchOver2 = vect.Y * 0.5f;
float sinPitchOver2 = (float)Math.Sin((double)pitchOver2);
float cosPitchOver2 = (float)Math.Cos((double)pitchOver2);
float yawOver2 = vect.X * 0.5f; // pitch
float sinYawOver2 = (float)Math.Sin((double)yawOver2);
float cosYawOver2 = (float)Math.Cos((double)yawOver2);
Quaternion result = new Quaternion()
{
X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2,
Y = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2,
Z = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2,
W = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2
};
return result;
}
/// <summary>
///
/// </summary>
public static LVector3 ToLVector(this Vector3 vec)
{
return new LVector3()
{
X = vec.X,
Y = vec.Y,
Z = vec.Z
};
}
/// <summary>
///
/// </summary>
public static LQuaternion ToLQuaternion(this Quaternion vec)
{
return new LQuaternion()
{
X = vec.X,
Y = vec.Y,
Z = vec.Z,
W = vec.W
};
}
}
}

View File

@ -1,185 +0,0 @@
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace RageCoop.Client
{
public static class DownloadManager
{
private static readonly List<DownloadFile> _downloadFiles = new List<DownloadFile>();
private static readonly Dictionary<byte, FileStream> _streams = new Dictionary<byte, FileStream>();
private static readonly List<byte> _filesFinished = new List<byte>();
public static bool DownloadComplete = false;
public static void AddFile(byte id, string name, long length)
{
string downloadFolder = $"Scripts\\RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}";
if (!Directory.Exists(downloadFolder))
{
Directory.CreateDirectory(downloadFolder);
}
if (FileAlreadyExists(downloadFolder, name, length))
{
// Send the server we are already done
Networking.SendDownloadFinish(id);
Cancel(id);
return;
}
if (!new string[] { ".js", ".xml" }.Any(x => x == Path.GetExtension(name)))
{
Cancel(id);
GTA.UI.Notification.Show($"The download of a file from the server was blocked! [{name}]", true);
Main.Logger.Error($"The download of a file from the server was blocked! [{name}]");
return;
}
lock (_downloadFiles)
{
_downloadFiles.Add(new DownloadFile()
{
FileID = id,
FileName = name,
FileLength = length
});
}
lock (_streams)
{
_streams.Add(id, new FileStream($"{downloadFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite));
}
}
/// <summary>
/// Check if the file already exists and if the size correct otherwise delete this file
/// </summary>
/// <param name="folder"></param>
/// <param name="name"></param>
/// <param name="length"></param>
/// <returns></returns>
private static bool FileAlreadyExists(string folder, string name, long length)
{
string filePath = $"{folder}\\{name}";
if (File.Exists(filePath))
{
if (new FileInfo(filePath).Length == length)
{
return true;
}
// Delete the file because the length is wrong (maybe the file was updated)
File.Delete(filePath);
}
return false;
}
public static void RenderProgress()
{
if (_downloadFiles.Count == 0)
{
return;
}
lock (_downloadFiles) lock (_filesFinished)
{
new LemonUI.Elements.ScaledText(new System.Drawing.PointF(System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width / 2, System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height - 60), $"Downloading files {_filesFinished.Count()} / {_downloadFiles.Count() + _filesFinished.Count()}", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
}
}
public static void Write(byte id, byte[] chunk)
{
lock (_filesFinished)
{
if (_filesFinished.Contains(id))
{
return;
}
}
lock (_streams)
{
FileStream fs = _streams.FirstOrDefault(x => x.Key == id).Value;
if (fs == null)
{
Main.Logger.Error($"Stream for file {id} not found!");
return;
}
fs.Write(chunk, 0, chunk.Length);
lock (_downloadFiles)
{
DownloadFile file = _downloadFiles.FirstOrDefault(x => x.FileID == id);
if (file == null)
{
Main.Logger.Error($"File {id} couldn't be found in the list!");
return;
}
file.FileWritten += chunk.Length;
if (file.FileWritten >= file.FileLength)
{
Cancel(id);
}
}
}
}
public static void Cancel(byte id)
{
lock (_streams) lock (_downloadFiles) lock (_filesFinished)
{
FileStream fs = _streams.ContainsKey(id) ? _streams[id] : null;
if (fs != null)
{
fs.Close();
fs.Dispose();
_streams.Remove(id);
}
if (_downloadFiles.Any(x => x.FileID == id))
{
_downloadFiles.Remove(_downloadFiles.First(x => x.FileID == id));
}
_filesFinished.Add(id);
}
}
public static void Cleanup(bool everything)
{
lock (_streams) lock (_downloadFiles) lock (_filesFinished)
{
foreach (KeyValuePair<byte, FileStream> stream in _streams)
{
stream.Value.Close();
stream.Value.Dispose();
}
_streams.Clear();
_downloadFiles.Clear();
_filesFinished.Clear();
}
if (everything)
{
DownloadComplete = false;
}
}
}
public class DownloadFile
{
public byte FileID { get; set; } = 0;
public string FileName { get; set; } = string.Empty;
public long FileLength { get; set; } = 0;
public long FileWritten { get; set; } = 0;
}
}

View File

@ -1,180 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using System.Collections.Generic;
using RageCoop.Core;
using GTA;
using GTA.Math;
using GTA.Native;
namespace RageCoop.Client
{
/// <summary>
///
/// </summary>
[XmlRoot(ElementName = "Map")]
public class CoopMap
{
/// <summary>
///
/// </summary>
[XmlArray("Props")]
[XmlArrayItem("Prop")]
public List<CoopProp> Props { get; set; } = new List<CoopProp>();
}
/// <summary>
///
/// </summary>
public struct CoopProp
{
/// <summary>
///
/// </summary>
public Vector3 Position { get; set; }
/// <summary>
///
/// </summary>
public Vector3 Rotation { get; set; }
/// <summary>
///
/// </summary>
public int Hash { get; set; }
/// <summary>
///
/// </summary>
public bool Dynamic { get; set; }
/// <summary>
///
/// </summary>
public int Texture { get; set; }
}
public static class MapLoader
{
// string = file name
private static readonly Dictionary<string, CoopMap> _maps = new Dictionary<string, CoopMap>();
private static readonly List<int> _createdObjects = new List<int>();
public static void LoadAll()
{
string downloadFolder = $"Scripts\\RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}";
if (!Directory.Exists(downloadFolder))
{
try
{
Directory.CreateDirectory(downloadFolder);
}
catch (Exception ex)
{
Main.Logger.Error(ex.Message);
// Without the directory we can't do the other stuff
return;
}
}
string[] files = Directory.GetFiles(downloadFolder, "*.xml");
lock (_maps)
{
for (int i = 0; i < files.Length; i++)
{
string filePath = files[i];
string fileName = Path.GetFileName(filePath);
XmlSerializer serializer = new XmlSerializer(typeof(CoopMap));
CoopMap map;
using (var stream = new FileStream(filePath, FileMode.Open))
{
try
{
map = (CoopMap)serializer.Deserialize(stream);
}
catch (Exception ex)
{
Main.Logger.Error($"The map with the name \"{fileName}\" couldn't be added!");
Main.Logger.Error($"{ex.Message}");
continue;
}
}
_maps.Add(fileName, map);
}
}
}
public static void LoadMap(string name)
{
lock (_maps) lock (_createdObjects)
{
if (!_maps.ContainsKey(name) || _createdObjects.Count != 0)
{
GTA.UI.Notification.Show($"The map with the name \"{name}\" couldn't be loaded!");
Main.Logger.Error($"The map with the name \"{name}\" couldn't be loaded!");
return;
}
CoopMap map = _maps[name];
foreach (CoopProp prop in map.Props)
{
Model model = prop.Hash.ModelRequest();
if (model == null)
{
Main.Logger.Error($"Model for object \"{model.Hash}\" couldn't be loaded!");
continue;
}
int handle = Function.Call<int>(Hash.CREATE_OBJECT, model.Hash, prop.Position.X, prop.Position.Y, prop.Position.Z, 1, 1, prop.Dynamic);
model.MarkAsNoLongerNeeded();
if (handle == 0)
{
Main.Logger.Error($"Object \"{prop.Hash}\" couldn't be created!");
continue;
}
_createdObjects.Add(handle);
if (prop.Texture > 0 && prop.Texture < 16)
{
Function.Call(Hash._SET_OBJECT_TEXTURE_VARIATION, handle, prop.Texture);
}
}
}
}
public static bool AnyMapLoaded()
{
lock (_createdObjects) return _createdObjects.Any();
}
public static void UnloadMap()
{
lock (_createdObjects)
{
foreach (int handle in _createdObjects)
{
unsafe
{
int tmpHandle = handle;
Function.Call(Hash.DELETE_OBJECT, &tmpHandle);
}
}
_createdObjects.Clear();
}
}
public static void DeleteAll()
{
UnloadMap();
lock (_maps)
{
_maps.Clear();
}
}
}
}

View File

@ -1,225 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using Lidgren.Network;
using RageCoop.Core;
using System.Threading.Tasks;
using System.Threading;
using GTA;
using GTA.Native;
namespace RageCoop.Client
{
internal static partial class Networking
{
public static NetClient Client;
public static float Latency = 0;
public static bool ShowNetworkInfo = false;
public static int BytesReceived = 0;
public static int BytesSend = 0;
private static Thread ReceiveThread;
public static void DisConnectFromServer(string address)
{
if (IsOnServer)
{
Client.Disconnect("Bye!");
}
else
{
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{
AutoFlushSendQueue = true
};
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
Client = new NetClient(config);
Client.Start();
string[] ip = new string[2];
int idx = address.LastIndexOf(':');
if (idx != -1)
{
ip[0] = address.Substring(0, idx);
ip[1] = address.Substring(idx + 1);
}
if (ip.Length != 2)
{
throw new Exception("Malformed URL");
}
// Send HandshakePacket
EntityPool.AddPlayer();
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.Handshake()
{
PedID = Main.LocalPlayerID,
Username = Main.Settings.Username,
ModVersion = Main.CurrentVersion,
NPCsAllowed = false
}.Pack(outgoingMessage);
Client.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
}
}
public static bool IsOnServer
{
get { return Client?.ConnectionStatus == NetConnectionStatus.Connected; }
}
public static void Start()
{
ReceiveThread=new Thread(() =>
{
while (true)
{
try
{
ReceiveMessages();
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
Thread.Sleep(5);
}
});
ReceiveThread.Start();
}
#region -- GET --
#region -- PLAYER --
private static void PlayerConnect(Packets.PlayerConnect packet)
{
var p = new PlayerData
{
PedID = packet.PedID,
Username= packet.Username,
};
GTA.UI.Notification.Show($"{p.Username} connected.");
PlayerList.SetPlayer(packet.PedID, packet.Username);
Main.Logger.Debug($"player connected:{p.Username}");
Main.DumpCharacters();
COOPAPI.Connected(packet.PedID);
}
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
{
var name=PlayerList.GetPlayer(packet.PedID).Username;
GTA.UI.Notification.Show($"{name} left.");
COOPAPI.Disconnected(packet.PedID);
PlayerList.RemovePlayer(packet.PedID);
EntityPool.RemoveAllFromPlayer(packet.PedID);
}
private static object DecodeNativeCall(ulong hash, List<object> args, bool returnValue, byte? returnType = null)
{
List<InputArgument> arguments = new List<InputArgument>();
if (args == null || args.Count == 0)
{
return null;
}
for (ushort i = 0; i < args.Count; i++)
{
object x = args.ElementAt(i);
switch (x)
{
case int _:
arguments.Add((int)x);
break;
case bool _:
arguments.Add((bool)x);
break;
case float _:
arguments.Add((float)x);
break;
case string _:
arguments.Add((string)x);
break;
case LVector3 _:
LVector3 vector = (LVector3)x;
arguments.Add((float)vector.X);
arguments.Add((float)vector.Y);
arguments.Add((float)vector.Z);
break;
default:
GTA.UI.Notification.Show("[DecodeNativeCall][" + hash + "]: Type of argument not found!");
return null;
}
}
if (!returnValue)
{
Function.Call((Hash)hash, arguments.ToArray());
return null;
}
switch (returnType.Value)
{
case 0x00: // int
return Function.Call<int>((Hash)hash, arguments.ToArray());
case 0x01: // bool
return Function.Call<bool>((Hash)hash, arguments.ToArray());
case 0x02: // float
return Function.Call<float>((Hash)hash, arguments.ToArray());
case 0x03: // string
return Function.Call<string>((Hash)hash, arguments.ToArray());
case 0x04: // vector3
return Function.Call<GTA.Math.Vector3>((Hash)hash, arguments.ToArray()).ToLVector();
default:
GTA.UI.Notification.Show("[DecodeNativeCall][" + hash + "]: Type of return not found!");
return null;
}
}
private static void DecodeNativeResponse(Packets.NativeResponse packet)
{
object result = DecodeNativeCall(packet.Hash, packet.Args, true, packet.ResultType);
if (Main.CheckNativeHash.ContainsKey(packet.Hash))
{
foreach (KeyValuePair<ulong, byte> hash in Main.CheckNativeHash)
{
if (hash.Key == packet.Hash)
{
lock (Main.ServerItems)
{
Main.ServerItems.Add((int)result, hash.Value);
}
break;
}
}
}
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.NativeResponse()
{
Hash = 0,
Args = new List<object>() { result },
ID = packet.ID
}.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Native);
Client.FlushSendQueue();
}
#endregion // -- PLAYER --
#endregion
public static void Tick()
{
// Sync
EntityPool.DoSync();
}
}
}

View File

@ -1,434 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using Lidgren.Network;
using RageCoop.Core;
using GTA;
using GTA.Math;
using GTA.Native;
namespace RageCoop.Client
{
internal static partial class Networking
{
public static void ReceiveMessages()
{
if (Client == null)
{
return;
}
NetIncomingMessage message;
while ((message = Client.ReadMessage()) != null)
{
BytesReceived += message.LengthBytes;
switch (message.MessageType)
{
case NetIncomingMessageType.StatusChanged:
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
string reason = message.ReadString();
switch (status)
{
case NetConnectionStatus.InitiatedConnect:
#if !NON_INTERACTIVE
Main.MainMenu.InitiateConnectionMenuSetting();
#endif
Main.QueueAction(() => { GTA.UI.Notification.Show("~y~Trying to connect..."); return true; });
break;
case NetConnectionStatus.Connected:
if (message.SenderConnection.RemoteHailMessage.ReadByte() != (byte)PacketTypes.Handshake)
{
Client.Disconnect("Wrong packet!");
}
else
{
int len = message.SenderConnection.RemoteHailMessage.ReadInt32();
byte[] data = message.SenderConnection.RemoteHailMessage.ReadBytes(len);
Packets.Handshake handshakePacket = new Packets.Handshake();
handshakePacket.Unpack(data);
// Main.LocalNetHandle = handshakePacket.NetHandle;
Main.NPCsAllowed = handshakePacket.NPCsAllowed;
#if !NON_INTERACTIVE
#endif
COOPAPI.Connected();
Main.QueueAction(() => {
Main.MainMenu.ConnectedMenuSetting();
Main.MainChat.Init();
PlayerList.Cleanup();
GTA.UI.Notification.Show("~g~Connected!");
});
Main.Logger.Info(">> Connected <<");
}
break;
case NetConnectionStatus.Disconnected:
DownloadManager.Cleanup(true);
// Reset all values
Latency = 0;
Main.QueueAction(() => { Main.CleanUpWorld();});
Main.NPCsAllowed = false;
if (Main.MainChat.Focused)
{
Main.MainChat.Focused = false;
}
Main.QueueAction(() => Main.CleanUp());
#if !NON_INTERACTIVE
Main.MainMenu.DisconnectedMenuSetting();
#endif
COOPAPI.Disconnected(reason);
Main.QueueAction(() =>
GTA.UI.Notification.Show("~r~Disconnected: " + reason));
MapLoader.DeleteAll();
Main.Logger.Info($">> Disconnected << reason: {reason}");
break;
}
break;
case NetIncomingMessageType.Data:
if (message.LengthBytes==0) { continue; }
var packetType = (PacketTypes)message.ReadByte();
try
{
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
switch (packetType)
{
case PacketTypes.CleanUpWorld:
{
Main.QueueAction(() => { Main.CleanUpWorld(); return true; });
}
break;
case PacketTypes.PlayerConnect:
{
Packets.PlayerConnect packet = new Packets.PlayerConnect();
packet.Unpack(data);
Main.QueueAction(() =>PlayerConnect(packet));
}
break;
case PacketTypes.PlayerDisconnect:
{
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect();
packet.Unpack(data);
Main.QueueAction(() =>PlayerDisconnect(packet));
}
break;
case PacketTypes.PlayerInfoUpdate:
{
var packet = new Packets.PlayerInfoUpdate();
packet.Unpack(data);
PlayerList.SetPlayer(packet.PedID,packet.Username,packet.Latency);
break;
}
#region ENTITY SYNC
case PacketTypes.VehicleSync:
{
Packets.VehicleSync packet = new Packets.VehicleSync();
packet.Unpack(data);
VehicleSync(packet);
}
break;
case PacketTypes.PedSync:
{
Packets.PedSync packet = new Packets.PedSync();
packet.Unpack(data);
PedSync(packet);
}
break;
case PacketTypes.VehicleStateSync:
{
Packets.VehicleStateSync packet = new Packets.VehicleStateSync();
packet.Unpack(data);
VehicleStateSync(packet);
}
break;
case PacketTypes.PedStateSync:
{
Packets.PedStateSync packet = new Packets.PedStateSync();
packet.Unpack(data);
PedStateSync(packet);
}
break;
case PacketTypes.ProjectileSync:
{
Packets.ProjectileSync packet = new Packets.ProjectileSync();
packet.Unpack(data);
ProjectileSync(packet);
break;
}
#endregion
case PacketTypes.ChatMessage:
{
Packets.ChatMessage packet = new Packets.ChatMessage();
packet.Unpack(data);
if (!COOPAPI.ChatMessageReceived(packet.Username, packet.Message))
{
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message);return true; });
}
}
break;
case PacketTypes.NativeCall:
{
Packets.NativeCall packet = new Packets.NativeCall();
packet.Unpack(data);
DecodeNativeCall(packet.Hash, packet.Args, false);
}
break;
case PacketTypes.NativeResponse:
{
Packets.NativeResponse packet = new Packets.NativeResponse();
packet.Unpack(data);
DecodeNativeResponse(packet);
}
break;
case PacketTypes.Mod:
{
Packets.Mod packet = new Packets.Mod();
packet.Unpack(data);
COOPAPI.ModPacketReceived(packet.NetHandle, packet.Name, packet.CustomPacketID, packet.Bytes);
}
break;
case PacketTypes.FileTransferTick:
{
Packets.FileTransferTick packet = new Packets.FileTransferTick();
packet.Unpack(data);
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
case PacketTypes.FileTransferRequest:
{
Packets.FileTransferRequest packet = new Packets.FileTransferRequest();
packet.Unpack(data);
DownloadManager.AddFile(packet.ID, packet.FileName, packet.FileLength);
}
break;
case PacketTypes.FileTransferComplete:
{
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
packet.Unpack(data);
DownloadManager.Cleanup(false);
DownloadManager.DownloadComplete = true;
}
break;
case PacketTypes.ServerClientEvent:
{
Packets.ServerClientEvent packet = new Packets.ServerClientEvent();
packet.Unpack(data);
}
break;
default:
if (packetType.IsSyncEvent())
{
// Dispatch to main thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
}
break;
}
}
catch (Exception ex)
{
Main.QueueAction(() => {
GTA.UI.Notification.Show("~r~~h~Packet Error");
return true;
});
Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex);
Client.Disconnect($"Packet Error [{packetType}]");
}
break;
case NetIncomingMessageType.ConnectionLatencyUpdated:
Latency = message.ReadFloat();
break;
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.ErrorMessage:
case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.VerboseDebugMessage:
#if DEBUG
// TODO?
#endif
break;
default:
break;
}
Client.Recycle(message);
}
}
private static void PedSync(Packets.PedSync packet)
{
if (!EntityPool.PedExists(packet.ID))
{
Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(new SyncedPed(packet.ID));
}
PedDataFlags flags = packet.Flag;
SyncedPed c = EntityPool.GetPedByID(packet.ID);
c.ID=packet.ID;
//c.OwnerID=packet.OwnerID;
c.Health = packet.Health;
c.Position = packet.Position.ToVector();
c.Rotation = packet.Rotation.ToVector();
c.Velocity = packet.Velocity.ToVector();
c.Speed = packet.Speed;
c.CurrentWeaponHash = packet.CurrentWeaponHash;
c.IsAiming = flags.HasFlag(PedDataFlags.IsAiming);
c.IsReloading = flags.HasFlag(PedDataFlags.IsReloading);
c.IsJumping = flags.HasFlag(PedDataFlags.IsJumping);
c.IsRagdoll = flags.HasFlag(PedDataFlags.IsRagdoll);
c.IsOnFire = flags.HasFlag(PedDataFlags.IsOnFire);
c.IsInParachuteFreeFall = flags.HasFlag(PedDataFlags.IsInParachuteFreeFall);
c.IsParachuteOpen = flags.HasFlag(PedDataFlags.IsParachuteOpen);
c.IsOnLadder = flags.HasFlag(PedDataFlags.IsOnLadder);
c.IsVaulting = flags.HasFlag(PedDataFlags.IsVaulting);
c.IsInCover = flags.HasFlag(PedDataFlags.IsInCover);
c.Heading=packet.Heading;
c.LastSynced = Main.Ticked;
if (c.IsAiming)
{
c.AimCoords = packet.AimCoords.ToVector();
}
if (c.IsRagdoll)
{
c.RotationVelocity=packet.RotationVelocity.ToVector();
}
}
private static void PedStateSync(Packets.PedStateSync packet)
{
if (!EntityPool.PedExists(packet.ID))
{
Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(new SyncedPed(packet.ID));
}
SyncedPed c = EntityPool.GetPedByID(packet.ID);
c.ID=packet.ID;
c.OwnerID=packet.OwnerID;
c.Clothes=packet.Clothes;
c.WeaponComponents=packet.WeaponComponents;
c.ModelHash=packet.ModelHash;
c.LastSynced=c.LastStateSynced = Main.Ticked;
}
private static void VehicleSync(Packets.VehicleSync packet)
{
if (!EntityPool.VehicleExists(packet.ID))
{
EntityPool.ThreadSafe.Add(new SyncedVehicle(packet.ID));
}
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
v.ID= packet.ID;
v.Position=packet.Position.ToVector();
v.Rotation=packet.Rotation.ToVector();
v.SteeringAngle=packet.SteeringAngle;
v.ThrottlePower=packet.ThrottlePower;
v.BrakePower=packet.BrakePower;
v.Velocity=packet.Velocity.ToVector();
v.RotationVelocity=packet.RotationVelocity.ToVector();
v.LastSynced=Main.Ticked;
}
private static void VehicleStateSync(Packets.VehicleStateSync packet)
{
if (!EntityPool.VehicleExists(packet.ID))
{
EntityPool.ThreadSafe.Add(new SyncedVehicle(packet.ID));
}
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
v.ID= packet.ID;
v.OwnerID= packet.OwnerID;
v.DamageModel=packet.DamageModel;
v.EngineHealth=packet.EngineHealth;
v.OwnerID=packet.OwnerID;
v.Mods=packet.Mods;
v.ModelHash=packet.ModelHash;
v.Colors=packet.Colors;
v.LandingGear=packet.LandingGear;
v.EngineRunning = packet.Flag.HasFlag(VehicleDataFlags.IsEngineRunning);
v.LightsOn = packet.Flag.HasFlag(VehicleDataFlags.AreLightsOn);
v.BrakeLightsOn = packet.Flag.HasFlag(VehicleDataFlags.AreBrakeLightsOn);
v.HighBeamsOn = packet.Flag.HasFlag(VehicleDataFlags.AreHighBeamsOn);
v.SireneActive = packet.Flag.HasFlag(VehicleDataFlags.IsSirenActive);
v.IsDead = packet.Flag.HasFlag(VehicleDataFlags.IsDead);
v.HornActive = packet.Flag.HasFlag(VehicleDataFlags.IsHornActive);
v.Transformed = packet.Flag.HasFlag(VehicleDataFlags.IsTransformed);
v.Passengers=new Dictionary<VehicleSeat, SyncedPed>();
v.LockStatus=packet.LockStatus;
foreach (KeyValuePair<int, int> pair in packet.Passengers)
{
if (EntityPool.PedExists(pair.Value))
{
v.Passengers.Add((VehicleSeat)pair.Key, EntityPool.GetPedByID(pair.Value));
}
}
v.LastStateSynced=v.LastSynced= Main.Ticked;
}
private static void ProjectileSync(Packets.ProjectileSync packet)
{
var p = EntityPool.GetProjectileByID(packet.ID);
if (p==null)
{
if (packet.Exploded) { return; }
Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p=new SyncedProjectile(packet.ID));
}
p.Position=packet.Position.ToVector();
p.Rotation=packet.Rotation.ToVector();
p.Velocity=packet.Velocity.ToVector();
p.Hash=(WeaponHash)packet.WeaponHash;
p.ShooterID=packet.ShooterID;
p.Exploded=packet.Exploded;
p.LastSynced=Main.Ticked;
}
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RageCoop.Client")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RageCoop.Client")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ef56d109-1f22-43e0-9dff-cfcfb94e0681")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.2.0.0")]
[assembly: AssemblyFileVersion("0.2.0.0")]

View File

@ -1,162 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RageCoop.Client</RootNamespace>
<AssemblyName>RageCoop.Client</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>D:\Games\Grand Theft Auto V\Scripts\RageCoop\</OutputPath>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<DocumentationFile>
</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClearScript.Core, Version=7.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libs\Release\scripts\ClearScript.Core.dll</HintPath>
</Reference>
<Reference Include="ClearScript.V8, Version=7.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libs\Release\scripts\ClearScript.V8.dll</HintPath>
</Reference>
<Reference Include="LemonUI.SHVDN3, Version=1.5.1.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libs\Release\scripts\LemonUI.SHVDN3.dll</HintPath>
</Reference>
<Reference Include="Lidgren.Network, Version=2012.1.7.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libs\Release\scripts\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.6.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.6.0.0\lib\net461\Microsoft.Extensions.DependencyInjection.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.6.0.0\lib\net461\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Options, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Options.6.0.0\lib\net461\Microsoft.Extensions.Options.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.Primitives, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.Primitives.6.0.0\lib\net461\Microsoft.Extensions.Primitives.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libs\Release\scripts\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3, Version=3.3.2.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libs\Release\ScriptHookVDotNet3.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.6.0.0\lib\net461\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DevTools\DevTool.cs" />
<Compile Include="Menus\Sub\DevToolMenu.cs" />
<Compile Include="Misc\WeaponUtil.cs" />
<Compile Include="Networking\Chat.cs" />
<Compile Include="COOPAPI.cs" />
<Compile Include="Debug.cs" />
<Compile Include="Networking\DownloadManager.cs" />
<Compile Include="Misc\TaskType.cs" />
<Compile Include="Menus\Sub\DebugMenu.cs" />
<Compile Include="Networking\Receive.cs" />
<Compile Include="Networking\Send.cs" />
<Compile Include="Sync\Entities\SyncedPed.cs" />
<None Include="app.config" />
<None Include="packages.config" />
<Compile Include="Sync\Entities\SyncedProjectile.cs" />
<Compile Include="Sync\EntityPool.cs" />
<Compile Include="Sync\SyncEvents.cs" />
<Compile Include="Sync\Entities\SyncedEntity.cs" />
<Compile Include="Sync\Entities\SyncedVehicle.cs" />
<Compile Include="Main.cs" />
<Compile Include="Networking\MapLoader.cs" />
<Compile Include="Menus\RageCoopMenu.cs" />
<Compile Include="Menus\Sub\SettingsMenu.cs" />
<Compile Include="Networking\Networking.cs" />
<Compile Include="PlayerList.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Sync\SyncParameters.cs" />
<Compile Include="Misc\Util.cs" />
<Compile Include="WorldThread.cs" />
<Compile Include="Settings.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Core\RageCoop.Core.csproj">
<Project>{cc2e8102-e568-4524-aa1f-f8e0f1cfe58a}</Project>
<Name>RageCoop.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="6.0.0" targetFramework="net48" />
<package id="Microsoft.Extensions.DependencyInjection" version="6.0.0" targetFramework="net48" />
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="6.0.0" targetFramework="net48" />
<package id="Microsoft.Extensions.Options" version="6.0.0" targetFramework="net48" />
<package id="Microsoft.Extensions.Primitives" version="6.0.0" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="6.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.4" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net48" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net48" />
</packages>

View File

@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Core
{
public class CoreUtils
{
public static (byte, byte[]) GetBytesFromObject(object obj)
{
switch (obj)
{
case byte _:
return (0x01, BitConverter.GetBytes((byte)obj));
case short _:
return (0x02, BitConverter.GetBytes((short)obj));
case ushort _:
return (0x03, BitConverter.GetBytes((ushort)obj));
case int _:
return (0x04, BitConverter.GetBytes((int)obj));
case uint _:
return (0x05, BitConverter.GetBytes((uint)obj));
case long _:
return (0x06, BitConverter.GetBytes((long)obj));
case ulong _:
return (0x07, BitConverter.GetBytes((ulong)obj));
case float _:
return (0x08, BitConverter.GetBytes((float)obj));
case bool _:
return (0x09, BitConverter.GetBytes((bool)obj));
default:
return (0x0, null);
}
}
}
public static class Extensions
{
public static void AddLVector3(this List<byte> bytes, LVector3 vec3)
{
bytes.AddRange(BitConverter.GetBytes(vec3.X));
bytes.AddRange(BitConverter.GetBytes(vec3.Y));
bytes.AddRange(BitConverter.GetBytes(vec3.Z));
}
public static void AddLQuaternion(this List<byte> bytes, LQuaternion quat)
{
bytes.AddRange(BitConverter.GetBytes(quat.X));
bytes.AddRange(BitConverter.GetBytes(quat.Y));
bytes.AddRange(BitConverter.GetBytes(quat.Z));
bytes.AddRange(BitConverter.GetBytes(quat.W));
}
public static void AddInt(this List<byte> bytes,int i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUint(this List<byte> bytes, uint i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddLong(this List<byte> bytes, long i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUlong(this List<byte> bytes, ulong i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddFloat(this List<byte> bytes, float i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static byte ToByte(this bool[] source)
{
byte result = 0;
// This assumes the array never contains more than 8 elements!
int index = 8 - source.Length;
// Loop through the array
foreach (bool b in source)
{
// if the element is 'true' set the bit at that position
if (b)
result |= (byte)(1 << (7 - index));
index++;
}
return result;
}
public static bool[] ToBoolArray(this byte b)
{
bool[] result = new bool[8];
// check each bit in the byte. if 1 set to true, if 0 set to false
for (int i = 0; i < 8; i++)
result[i] = (b & (1 << i)) != 0;
// reverse the array
Array.Reverse(result);
return result;
}
}
}

View File

@ -1,150 +0,0 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
namespace RageCoop.Core
{
public class Loggger
{
public string LogPath;
private StreamWriter logWriter;
private bool UseConsole=false;
/// <summary>
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 0;
private string Buffer="";
public Loggger(string path,bool overwrite=true)
{
LogPath=path;
if (File.Exists(path)&&overwrite) { File.Delete(path); }
Task.Run(() =>
{
while (true)
{
Flush();
Thread.Sleep(1000);
}
});
}
public Loggger()
{
UseConsole=true;
Task.Run(() =>
{
while (true)
{
Flush();
Thread.Sleep(1000);
}
});
}
public void Info(string message)
{
if (LogLevel>2) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [INF] {1}", Date(), message, Process.GetCurrentProcess().Id);
Buffer+=msg+"\r\n";
}
}
public void Warning(string message)
{
if (LogLevel>3) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [WRN] {1}", Date(), message, Process.GetCurrentProcess().Id);
Buffer+=msg+"\r\n";
}
}
public void Error(string message)
{
if (LogLevel>4) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [ERR] {1}", Date(), message, Process.GetCurrentProcess().Id);
Buffer+=msg+"\r\n";
}
}
public void Error(Exception ex)
{
if (LogLevel>4) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [ERR] {1}", Date(),string.Join("\r\n",ex.Message,ex.StackTrace,ex.ToString()), Process.GetCurrentProcess().Id);
Buffer+=msg+"\r\n";
}
}
public void Debug(string message)
{
if (LogLevel>1) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [DBG] {1}", Date(), message,Process.GetCurrentProcess().Id);
Buffer+=msg+"\r\n";
}
}
public void Trace(string message)
{
if (LogLevel>0) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [TRC] {1}", Date(), message, Process.GetCurrentProcess().Id);
Buffer+=msg+"\r\n";
}
}
private string Date()
{
return DateTime.Now.ToString();
}
public void Flush()
{
lock (Buffer)
{
if (Buffer!="")
{
if (UseConsole)
{
Console.Write(Buffer);
Buffer="";
}
else
{
try
{
logWriter=new StreamWriter(LogPath, true, Encoding.UTF8);
logWriter.Write(Buffer);
logWriter.Close();
Buffer="";
}
catch { }
}
}
}
}
}
}

View File

@ -1,716 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
using Newtonsoft.Json;
using GTA.Math;
namespace RageCoop.Core
{
/// <summary>
///
/// </summary>
public struct LVector3
{
#region CLIENT-ONLY
/// <summary>
///
/// </summary>
public Vector3 ToVector()
{
return new Vector3(X, Y, Z);
}
#endregion
#region SERVER-ONLY
public float Length() => (float)Math.Sqrt((X * X) + (Y * Y) + (Z * Z));
public static LVector3 Subtract(LVector3 pos1, LVector3 pos2) { return new LVector3(pos1.X - pos2.X, pos1.Y - pos2.Y, pos1.Z - pos2.Z); }
public static bool Equals(LVector3 value1, LVector3 value2) => value1.X == value2.X && value1.Y == value2.Y && value1.Z == value2.Z;
public static LVector3 operator /(LVector3 value1, float value2)
{
float num = 1f / value2;
return new LVector3(value1.X * num, value1.Y * num, value1.Z * num);
}
public static LVector3 operator -(LVector3 left, LVector3 right)
{
return new LVector3(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
}
public static LVector3 operator -(LVector3 value)
{
return default(LVector3) - value;
}
#endregion
/// <summary>
///
/// </summary>
public LVector3(float X, float Y, float Z)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
/// <summary>
///
/// </summary>
public float X { get; set; }
/// <summary>
///
/// </summary>
public float Y { get; set; }
/// <summary>
///
/// </summary>
public float Z { get; set; }
}
/// <summary>
///
/// </summary>
public struct LQuaternion
{
#region CLIENT-ONLY
/// <summary>
///
/// </summary>
public Quaternion ToQuaternion()
{
return new Quaternion(X, Y, Z, W);
}
#endregion
/// <summary>
///
/// </summary>
public LQuaternion(float X, float Y, float Z, float W)
{
this.X = X;
this.Y = Y;
this.Z = Z;
this.W = W;
}
/// <summary>
///
/// </summary>
public float X { get; set; }
/// <summary>
///
/// </summary>
public float Y { get; set; }
/// <summary>
///
/// </summary>
public float Z { get; set; }
/// <summary>
///
/// </summary>
public float W { get; set; }
}
public enum PacketTypes:byte
{
Handshake=0,
PlayerConnect=1,
PlayerDisconnect=2,
PlayerInfoUpdate=3,
ChatMessage=10,
NativeCall=11,
NativeResponse=12,
Mod=13,
CleanUpWorld=14,
FileTransferTick=15,
FileTransferRequest=16,
FileTransferComplete=17,
ServerClientEvent=18,
#region Sync
#region INTERVAL
VehicleSync = 20,
VehicleStateSync = 21,
PedSync = 22,
PedStateSync = 23,
ProjectileSync=24,
#endregion
#region EVENT
PedKilled=30,
BulletShot=31,
EnteringVehicle=32,
LeaveVehicle = 33,
EnteredVehicle=34,
OwnerChanged=35,
VehicleBulletShot = 36,
#endregion
#endregion
}
public static class PacketExtensions
{
public static bool IsSyncEvent(this PacketTypes p)
{
return (30<=(byte)p)&&((byte)p<=40);
}
}
public enum ConnectionChannel
{
Default = 0,
Chat = 5,
Native = 6,
Mod = 7,
File = 8,
Event = 9,
VehicleSync=20,
PedSync=21,
ProjectileSync = 22,
SyncEvents =30,
}
[Flags]
public enum PedDataFlags:ushort
{
None=0,
IsAiming = 1 << 0,
IsReloading = 1 << 2,
IsJumping = 1 << 3,
IsRagdoll = 1 << 4,
IsOnFire = 1 << 5,
IsInParachuteFreeFall = 1 << 6,
IsParachuteOpen = 1 << 7,
IsOnLadder = 1 << 8,
IsVaulting = 1 << 9,
IsInCover=1<< 10,
}
#region ===== VEHICLE DATA =====
public enum VehicleDataFlags:ushort
{
IsEngineRunning = 1 << 0,
AreLightsOn = 1 << 1,
AreBrakeLightsOn = 1 << 2,
AreHighBeamsOn = 1 << 3,
IsSirenActive = 1 << 4,
IsDead = 1 << 5,
IsHornActive = 1 << 6,
IsTransformed = 1 << 7,
RoofOpened = 1 << 8,
OnTurretSeat = 1 << 9,
IsAircraft = 1 << 10,
IsHandBrakeOn=1<<11,
}
public struct VehicleDamageModel
{
public byte BrokenDoors { get; set; }
public byte OpenedDoors { get; set; }
public byte BrokenWindows { get; set; }
public short BurstedTires { get; set; }
public byte LeftHeadLightBroken { get; set; }
public byte RightHeadLightBroken { get; set; }
}
#endregion
interface IPacket
{
void Pack(NetOutgoingMessage message);
void Unpack(byte[] array);
}
public abstract class Packet : IPacket
{
public abstract void Pack(NetOutgoingMessage message);
public abstract void Unpack(byte[] array);
}
public partial class Packets
{
public class Mod : Packet
{
public long NetHandle { get; set; }
public long Target { get; set; }
public string Name { get; set; }
public byte CustomPacketID { get; set; }
public byte[] Bytes { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.Mod);
List<byte> byteArray = new List<byte>();
// Write NetHandle
byteArray.AddRange(BitConverter.GetBytes(NetHandle));
// Write Target
byteArray.AddRange(BitConverter.GetBytes(Target));
// Write Name
byte[] nameBytes = Encoding.UTF8.GetBytes(Name);
byteArray.AddRange(BitConverter.GetBytes(nameBytes.Length));
byteArray.AddRange(nameBytes);
// Write CustomPacketID
byteArray.Add(CustomPacketID);
// Write Bytes
byteArray.AddRange(BitConverter.GetBytes(Bytes.Length));
byteArray.AddRange(Bytes);
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read NetHandle
NetHandle = reader.ReadLong();
// Read Target
Target = reader.ReadLong();
// Read Name
int nameLength = reader.ReadInt();
Name = reader.ReadString(nameLength);
// Read CustomPacketID
CustomPacketID = reader.ReadByte();
// Read Bytes
int bytesLength = reader.ReadInt();
Bytes = reader.ReadByteArray(bytesLength);
#endregion
}
}
public class ChatMessage : Packet
{
public string Username { get; set; }
public string Message { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.ChatMessage);
List<byte> byteArray = new List<byte>();
byte[] usernameBytes = Encoding.UTF8.GetBytes(Username);
byte[] messageBytes = Encoding.UTF8.GetBytes(Message);
// Write UsernameLength
byteArray.AddRange(BitConverter.GetBytes(usernameBytes.Length));
// Write Username
byteArray.AddRange(usernameBytes);
// Write MessageLength
byteArray.AddRange(BitConverter.GetBytes(messageBytes.Length));
// Write Message
byteArray.AddRange(messageBytes);
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read username
int usernameLength = reader.ReadInt();
Username = reader.ReadString(usernameLength);
// Read message
int messageLength = reader.ReadInt();
Message = reader.ReadString(messageLength);
#endregion
}
}
public class ServerClientEvent : Packet
{
public string EventName { get; set; }
public List<object> Args { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.ServerClientEvent);
List<byte> byteArray = new List<byte>();
byte[] eventNameBytes = Encoding.UTF8.GetBytes(EventName);
// Write event name
byteArray.AddRange(BitConverter.GetBytes(eventNameBytes.Length));
byteArray.AddRange(eventNameBytes);
// Write args
byteArray.AddRange(BitConverter.GetBytes(Args.Count));
foreach (object arg in Args)
{
var test = CoreUtils.GetBytesFromObject(arg);
if (test.Item1 == 0x0)
{
// Logger.Write("Can't get type of an object!", Logger.LogLevel.Server);
byteArray.Add(0x0);
}
else
{
byteArray.Add(test.Item1);
byteArray.AddRange(test.Item2);
}
}
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read event name
int eventNameBytes = reader.ReadInt();
EventName = reader.ReadString(eventNameBytes);
// Read args
Args = new List<object>();
int argsCount = reader.ReadInt();
for (int i = 0; i < argsCount; i++)
{
byte argType = reader.ReadByte();
switch (argType)
{
case 0x01:
Args.Add(reader.ReadByte());
break;
case 0x02:
Args.Add(reader.ReadShort());
break;
case 0x03:
Args.Add(reader.ReadUShort());
break;
case 0x04:
Args.Add(reader.ReadInt());
break;
case 0x05:
Args.Add(reader.ReadUInt());
break;
case 0x06:
Args.Add(reader.ReadLong());
break;
case 0x07:
Args.Add(reader.ReadULong());
break;
case 0x08:
Args.Add(reader.ReadFloat());
break;
case 0x09:
Args.Add(reader.ReadBool());
break;
}
}
#endregion
}
}
#region ===== NATIVECALL =====
public class NativeCall : Packet
{
public ulong Hash { get; set; }
public List<object> Args { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.NativeCall);
List<byte> byteArray = new List<byte>();
// Write Hash
byteArray.AddRange(BitConverter.GetBytes(Hash));
// Write Args
byteArray.AddRange(BitConverter.GetBytes(Args.Count));
Args.ForEach(x =>
{
Type type = x.GetType();
if (type == typeof(int))
{
byteArray.Add(0x00);
byteArray.AddRange(BitConverter.GetBytes((int)x));
}
else if (type == typeof(bool))
{
byteArray.Add(0x01);
byteArray.AddRange(BitConverter.GetBytes((bool)x));
}
else if (type == typeof(float))
{
byteArray.Add(0x02);
byteArray.AddRange(BitConverter.GetBytes((float)x));
}
else if (type == typeof(string))
{
byteArray.Add(0x03);
byte[] stringBytes = Encoding.UTF8.GetBytes((string)x);
byteArray.AddRange(BitConverter.GetBytes(stringBytes.Length));
byteArray.AddRange(stringBytes);
}
else if (type == typeof(LVector3))
{
byteArray.Add(0x04);
LVector3 vector = (LVector3)x;
byteArray.AddRange(BitConverter.GetBytes(vector.X));
byteArray.AddRange(BitConverter.GetBytes(vector.Y));
byteArray.AddRange(BitConverter.GetBytes(vector.Z));
}
});
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read Hash
Hash = reader.ReadULong();
// Read Args
Args = new List<object>();
int argsLength = reader.ReadInt();
for (int i = 0; i < argsLength; i++)
{
byte argType = reader.ReadByte();
switch (argType)
{
case 0x00:
Args.Add(reader.ReadInt());
break;
case 0x01:
Args.Add(reader.ReadBool());
break;
case 0x02:
Args.Add(reader.ReadFloat());
break;
case 0x03:
int stringLength = reader.ReadInt();
Args.Add(reader.ReadString(stringLength));
break;
case 0x04:
Args.Add(new LVector3()
{
X = reader.ReadFloat(),
Y = reader.ReadFloat(),
Z = reader.ReadFloat()
});
break;
}
}
#endregion
}
}
public class NativeResponse : Packet
{
public ulong Hash { get; set; }
public List<object> Args { get; set; }
public byte? ResultType { get; set; }
public long ID { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.NativeResponse);
List<byte> byteArray = new List<byte>();
// Write Hash
byteArray.AddRange(BitConverter.GetBytes(Hash));
Type type;
// Write Args
byteArray.AddRange(BitConverter.GetBytes(Args.Count));
Args.ForEach(x =>
{
type = x.GetType();
if (type == typeof(int))
{
byteArray.Add(0x00);
byteArray.AddRange(BitConverter.GetBytes((int)x));
}
else if (type == typeof(bool))
{
byteArray.Add(0x01);
byteArray.AddRange(BitConverter.GetBytes((bool)x));
}
else if (type == typeof(float))
{
byteArray.Add(0x02);
byteArray.AddRange(BitConverter.GetBytes((float)x));
}
else if (type == typeof(string))
{
byteArray.Add(0x03);
byte[] stringBytes = Encoding.UTF8.GetBytes((string)x);
byteArray.AddRange(BitConverter.GetBytes(stringBytes.Length));
byteArray.AddRange(stringBytes);
}
else if (type == typeof(LVector3))
{
byteArray.Add(0x04);
LVector3 vector = (LVector3)x;
byteArray.AddRange(BitConverter.GetBytes(vector.X));
byteArray.AddRange(BitConverter.GetBytes(vector.Y));
byteArray.AddRange(BitConverter.GetBytes(vector.Z));
}
});
byteArray.AddRange(BitConverter.GetBytes(ID));
// Write type of result
if (ResultType.HasValue)
{
byteArray.Add(ResultType.Value);
}
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read Hash
Hash = reader.ReadULong();
// Read Args
Args = new List<object>();
int argsLength = reader.ReadInt();
for (int i = 0; i < argsLength; i++)
{
byte argType = reader.ReadByte();
switch (argType)
{
case 0x00:
Args.Add(reader.ReadInt());
break;
case 0x01:
Args.Add(reader.ReadBool());
break;
case 0x02:
Args.Add(reader.ReadFloat());
break;
case 0x03:
int stringLength = reader.ReadInt();
Args.Add(reader.ReadString(stringLength));
break;
case 0x04:
Args.Add(new LVector3()
{
X = reader.ReadFloat(),
Y = reader.ReadFloat(),
Z = reader.ReadFloat()
});
break;
}
}
ID = reader.ReadLong();
// Read type of result
if (reader.CanRead(1))
{
ResultType = reader.ReadByte();
}
#endregion
}
}
#endregion // ===== NATIVECALL =====
}
/// <summary>
/// ?
/// </summary>
public static class CoopSerializer
{
/// <summary>
/// ?
/// </summary>
public static byte[] Serialize(this object obj)
{
if (obj == null)
{
return null;
}
string jsonString = JsonConvert.SerializeObject(obj);
return System.Text.Encoding.UTF8.GetBytes(jsonString);
}
/// <summary>
/// ?
/// </summary>
public static T Deserialize<T>(this byte[] bytes) where T : class
{
if (bytes == null)
{
return null;
}
var jsonString = System.Text.Encoding.UTF8.GetString(bytes);
return JsonConvert.DeserializeObject<T>(jsonString);
}
}
}

View File

@ -1,34 +0,0 @@
using System.Linq;
namespace RageCoop.Core
{
public class PlayerData
{
public string Username { get; set; }
/// <summary>
/// Universal character ID.
/// </summary>
public int PedID
{
get; set;
}
/// <summary>
/// Universal vehicle ID.
/// </summary>
public int VehicleID { get; set; }
public bool IsInVehicle { get; internal set; }
public LVector3 Position { get; set; }
/// <summary>
/// Player Latency in second.
/// </summary>
public float Latency { get; set; }
public int Health { get; set; }
public bool IsInRangeOf(LVector3 position, float distance)
{
return LVector3.Subtract(Position, position).Length() < distance;
}
}
}

View File

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.1</AssemblyVersion>
<FileVersion>0.1</FileVersion>
<Version>0.1</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Lidgren.Network">
<HintPath>..\Libs\Release\scripts\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\Libs\Release\scripts\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\Libs\Release\ScriptHookVDotNet3.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

View File

@ -1,2 +0,0 @@
ReloadKey=None
ConsoleKey=F4

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +1,87 @@
# 🌐 RAGECOOP
The original author of this project is @EntenKoeniq.
The project has been reworked and is currently maintained by @Sardelka9515.
# **Help to survive the project**
#### The project is in active development as for now but may get discontinued if user's support is not present!
## if you are a developer...
Please **CONTRIBUTE** to the project and make RAGECOOP better together!
## otherwise...
**[Become a patreon](https://www.patreon.com/Sardelka)** to help keep the project alive and get exclusive support to individual issues.
<br><br><br>
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
# 🧠 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_
# 📋 Requirements
- Visual Studio 2022
- .NET 6.0
- .NET Framework 4.8
# 📚 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***)
- - No new features (only improvements)
- [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/13.0.1)
- [ClearScript](https://github.com/microsoft/ClearScript)
- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
- [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins)
# Features
1. Synchronized bullets
2. Synchronized vehicle/player/NPC
3. Synchronized Projectile
4. Basic ragdoll sync
3. Synchronized projectiles
4. Simple ragdoll sync
5. Smoother vehicle/ped movement.
6. Ownership based sync logic, carjacking is now working properly.
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. Other improvements
10. Weaponized vehicle sync(WIP).
11. Other improvements
# Known issues
1. Weapon sounds are missing.
2. Cover sync is still buggy.
3. Weaponized vehicle doen't work currently
4. Framerate drop with high number of synchronized entities.
3. Framerate drop with high number of synchronized entities.
5. Scripting API is screwed.(will be rewritten in the future)
# Installation
## Client
Download latest release, remove old version of the mod. Extract the zip and put `RageCoop` in `Grand Theft Auto V/Scripts`.
## Server
Download latest release for your OS, then extract and run.
## Installation
Refer to the [wiki](https://github.com/RAGECOOP/RAGECOOP-V/wiki)
# Downloads
Download latest release [here](https://github.com/RAGECOOP/RAGECOOP-V/releases/latest)
Old release can be downloaded [here](https://gitlab.com/justasausage/RageCOOP-V/-/tree/main/Release)
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)
- - For the extensive work in ScriptHookVDotNet
- [justalemon](https://github.com/justalemon)
- - For the extensive work in LemonUI
# 📝 License
This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOOP-V/blob/main/LICENSE)
[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
[forks-url]: https://github.com/RAGECOOP/RAGECOOP-V/network/members
[stars-shield]: https://img.shields.io/github/stars/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
[stars-url]: https://github.com/RAGECOOP/RAGECOOP-V/stargazers
[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

View File

@ -3,11 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31919.166
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Server", "RageCoop.Server\RageCoop.Server.csproj", "{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "Client\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "RageCoop.Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "Core\RageCoop.Core.csproj", "{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "RageCoop.Client\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -25,14 +25,6 @@ Global
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|Any CPU.Build.0 = Release|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|x64.ActiveCfg = Release|Any CPU
{84AB99D9-5E00-4CA2-B1DD-56B8419AAD24}.Release|x64.Build.0 = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|x64.ActiveCfg = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|x64.Build.0 = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|Any CPU.ActiveCfg = Release|Any CPU
{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
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -41,6 +33,14 @@ Global
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|Any CPU.Build.0 = Release|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.ActiveCfg = Release|Any CPU
{CC2E8102-E568-4524-AA1F-F8E0F1CFE58A}.Release|x64.Build.0 = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|x64.ActiveCfg = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Debug|x64.Build.0 = Debug|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|Any CPU.ActiveCfg = Release|Any CPU
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -130,7 +130,7 @@ namespace RageCoop.Client
s=$@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
i=Main.Ticked%2==0 ? {Current} : {Secondary};
i=BulletsShot%2==0 ? {Current} : {Secondary};
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].{dir}Vector);
";
}
@ -139,7 +139,7 @@ namespace RageCoop.Client
s=$@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
i=Main.Ticked%2==0 ? {Current} : {Secondary};
i=BulletsShot%2==0 ? {Current} : {Secondary};
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].{((MuzzleDir)(dir-3)).ToString()}Vector*-1);
";
}
@ -172,7 +172,7 @@ namespace RageCoop.Client
}
}
public enum MuzzleDir:byte
internal enum MuzzleDir:byte
{
Forward=0,
Right = 1,

View File

@ -19,37 +19,45 @@ namespace RageCoop.Client
/// </summary>
internal class Main : Script
{
private bool _gameLoaded = false;
private static bool _isGoingToCar = false;
internal static readonly string CurrentVersion = "V0_5_0";
public static readonly string CurrentVersion = "V0_2";
internal static int LocalPlayerID=0;
public static int LocalPlayerID=0;
public static bool NPCsAllowed = false;
internal static RelationshipGroup SyncedPedsGroup;
public static new Settings Settings = null;
internal static new Settings Settings = null;
internal static Scripting.BaseScript BaseScript=new Scripting.BaseScript();
#if !NON_INTERACTIVE
public static RageCoopMenu MainMenu = null;
#endif
public static Chat MainChat = null;
public static Stopwatch Counter = new Stopwatch();
public static ulong Ticked = 0;
public static Loggger Logger=new Loggger("Scripts\\RageCoop\\RageCoop.Client.log");
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>>();
/// <summary>
/// Don't use it!
/// </summary>
public Main()
{
// Required for some synchronization!
/*if (Game.Version < GameVersion.v1_0_1290_1_Steam)
Settings = Util.ReadSettings();
Directory.CreateDirectory(Settings.DataDirectory);
Logger=new Logger()
{
LogPath=$"{Settings.DataDirectory}\\RageCoop.Client.log",
UseConsole=false,
#if DEBUG
LogLevel = 0,
#else
LogLevel=Settings.LogLevel,
#endif
};
Resources = new Scripting.Resources();
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{
Tick += (object sender, EventArgs e) =>
{
@ -57,7 +65,6 @@ namespace RageCoop.Client
{
return;
}
if (!_gameLoaded)
{
GTA.UI.Notification.Show("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
@ -65,23 +72,18 @@ namespace RageCoop.Client
}
};
return;
}*/
}
BaseScript.OnStart();
SyncedPedsGroup=World.AddRelationshipGroup("SYNCPED");
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral, true);
Settings = Util.ReadSettings();
Networking.Start();
#if !NON_INTERACTIVE
MainMenu = new RageCoopMenu();
#endif
MainChat = new Chat();
#if DEBUG
Logger.LogLevel = 0;
#else
Logger.LogLevel=Settings.LogLevel;
#endif
Tick += OnTick;
Tick += (s,e) => { Scripting.API.Events.InvokeTick(); };
KeyDown += OnKeyDown;
KeyDown+=(s, e) => { Scripting.API.Events.InvokeKeyDown(s, e); };
KeyUp+=(s, e) => { Scripting.API.Events.InvokeKeyUp(s, e); };
Aborted += (object sender, EventArgs e) => CleanUp();
Util.NativeMemory();
@ -89,9 +91,6 @@ namespace RageCoop.Client
}
#if DEBUG
private ulong _lastDebugData;
private int _debugBytesSend;
private int _debugBytesReceived;
#endif
private void OnTick(object sender, EventArgs e)
{
@ -107,14 +106,9 @@ namespace RageCoop.Client
}
#if !NON_INTERACTIVE
MainMenu.MenuPool.Process();
CoopMenu.MenuPool.Process();
#endif
if (_isGoingToCar && Game.Player.Character.IsInVehicle())
{
_isGoingToCar = false;
}
DoQueuedActions();
if (!Networking.IsOnServer)
{
@ -126,37 +120,22 @@ namespace RageCoop.Client
}
try
{
Networking.Tick();
EntityPool.DoSync();
}
catch (Exception ex)
{
Logger.Error(ex);
Main.Logger.Error(ex);
}
if (!DownloadManager.DownloadComplete)
{
DownloadManager.RenderProgress();
}
MapLoader.LoadAll();
#if DEBUG
if (Networking.ShowNetworkInfo)
{
ulong time = Util.GetTickCount64();
if (time - _lastDebugData > 1000)
{
_lastDebugData = time;
_debugBytesReceived = Networking.BytesReceived;
Networking.BytesReceived = 0;
_debugBytesSend = Networking.BytesSend;
Networking.BytesSend = 0;
}
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 0), $"L: {Networking.Latency * 1000:N0}ms", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {Lidgren.Network.NetUtility.ToHumanReadable(_debugBytesReceived)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60), $"S: {Lidgren.Network.NetUtility.ToHumanReadable(_debugBytesSend)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
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
@ -164,13 +143,37 @@ namespace RageCoop.Client
MainChat.Tick();
PlayerList.Tick();
if (!Scripting.API.Config.EnableAutoRespawn)
{
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.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)
{
P.Health=1;
Game.Player.WantedLevel=0;
Main.Logger.Debug("Player died.");
}
GTA.UI.Screen.StopEffects();
}
else
{
Function.Call(Hash.DISPLAY_HUD, true);
}
}
Ticked++;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (MainChat.Focused)
@ -193,14 +196,20 @@ namespace RageCoop.Client
}
if (e.KeyCode == Settings.MenuKey)
{
if (MainMenu.MenuPool.AreAnyVisible)
if (CoopMenu.MenuPool.AreAnyVisible)
{
MainMenu.MainMenu.Visible = false;
MainMenu.SubSettings.Menu.Visible = false;
CoopMenu.MenuPool.ForEach<LemonUI.Menus.NativeMenu>(x =>
{
if (x.Visible)
{
CoopMenu.LastMenu=x;
x.Visible=false;
}
});
}
else
{
MainMenu.MainMenu.Visible = true;
CoopMenu.LastMenu.Visible = true;
}
}
else if (Game.IsControlJustPressed(GTA.Control.MultiplayerInfo))
@ -234,7 +243,7 @@ namespace RageCoop.Client
if (V!=null)
{
var seat = Util.GetNearestSeat(P, V);
var seat = P.GetNearestSeat(V);
P.Task.EnterVehicle(V, seat);
}
}
@ -250,7 +259,7 @@ namespace RageCoop.Client
}
public static readonly Dictionary<ulong, byte> CheckNativeHash = new Dictionary<ulong, byte>()
internal static readonly Dictionary<ulong, byte> CheckNativeHash = new Dictionary<ulong, byte>()
{
{ 0xD49F9B0955C367DE, 1 }, // Entities
{ 0xEF29A16337FACADB, 1 }, //
@ -267,8 +276,8 @@ namespace RageCoop.Client
{ 0x5A039BB0BCA604B6, 4 }, //
{ 0x0134F0835AB6BFCB, 5 } // Checkpoints
};
public static Dictionary<int, byte> ServerItems = new Dictionary<int, byte>();
public static void CleanUpWorld()
internal static Dictionary<int, byte> ServerItems = new Dictionary<int, byte>();
internal static void CleanUpWorld()
{
if (ServerItems.Count == 0)
{
@ -311,10 +320,9 @@ namespace RageCoop.Client
catch
{
GTA.UI.Notification.Show("~r~~h~CleanUpWorld() Error");
Logger.Error($"CleanUpWorld(): ~r~Item {item.Value} cannot be deleted!");
Main.Logger.Error($"CleanUpWorld(): ~r~Item {item.Value} cannot be deleted!");
}
}
ServerItems.Clear();
}
}
@ -334,7 +342,7 @@ namespace RageCoop.Client
}
catch(Exception ex)
{
GTA.UI.Screen.ShowSubtitle(ex.ToString());
Logger.Error(ex);
QueuedActions.Remove(action);
}
}
@ -345,14 +353,14 @@ namespace RageCoop.Client
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> The action to be executed, must return a bool indicating whether the action cane be removed after execution.</param>
public static void QueueAction(Func<bool> a)
internal static void QueueAction(Func<bool> a)
{
lock (QueuedActions)
{
QueuedActions.Add(a);
}
}
public static void QueueAction(Action a)
internal static void QueueAction(Action a)
{
lock (QueuedActions)
{
@ -362,37 +370,11 @@ namespace RageCoop.Client
/// <summary>
/// Clears all queued actions
/// </summary>
public static void ClearQueuedActions()
internal static void ClearQueuedActions()
{
lock (QueuedActions) { QueuedActions.Clear(); }
}
public static string DumpCharacters()
{
string s = "Characters:";
lock (EntityPool.PedsLock)
{
foreach (int id in EntityPool.GetPedIDs())
{
var c = EntityPool.GetPedByID(id);
s+=$"\r\nID:{c.ID} Owner:{c.OwnerID} LastUpdated:{c.LastUpdated} LastSynced:{c.LastSynced} LastStateSynced:{c.LastStateSynced}";
// s+=$"\r\n{c.IsAiming} {c.IsJumping} {c.IsOnFire} {c.IsOnLadder} {c.IsRagdoll} {c.IsReloading} {c.IsShooting} {c.Speed}";
}
}
Logger.Trace(s);
return s;
}
public static string DumpPlayers()
{
string s = "Players:";
foreach (PlayerData p in PlayerList.Players)
{
s+=$"\r\nID:{p.PedID} Username:{p.Username}";
}
Logger.Trace(s);
return s;
}
}
}

View File

@ -0,0 +1,166 @@
using GTA;
using System.Drawing;
using LemonUI;
using LemonUI.Menus;
using LemonUI.Scaleform;
namespace RageCoop.Client.Menus
{
/// <summary>
/// Don't use it!
/// </summary>
internal static class CoopMenu
{
public static ObjectPool MenuPool = new ObjectPool();
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "MAIN")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
public static PopUp PopUp=new PopUp()
{
Title="",
Prompt="",
Subtitle = "",
Error="",
ShowBackground = true,
Visible=false,
};
public static NativeMenu LastMenu { get; set; } = Menu;
#region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*',Main.Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
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") };
#endregion
/// <summary>
/// Don't use it!
/// </summary>
static CoopMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated;
_passwordItem.Activated+=_passwordActivated;
ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
Menu.AddSubMenu(ServersMenu.Menu);
Menu.Add(_usernameItem);
Menu.Add(_passwordItem);
Menu.Add(ServerIpItem);
Menu.Add(_serverConnectItem);
Menu.AddSubMenu(SettingsMenu.Menu);
Menu.AddSubMenu(DevToolMenu.Menu);
Menu.AddSubMenu(DebugMenu.Menu);
MenuPool.Add(Menu);
MenuPool.Add(SettingsMenu.Menu);
MenuPool.Add(DevToolMenu.Menu);
MenuPool.Add(DebugMenu.Menu);
MenuPool.Add(DebugMenu.DiagnosticMenu);
MenuPool.Add(ServersMenu.Menu);
MenuPool.Add(PopUp);
Menu.Add(_aboutItem);
}
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;
while (true)
{
Game.DisableAllControlsThisFrame();
MenuPool.Process();
if (Game.IsControlJustPressed(Control.FrontendAccept))
{
PopUp.Visible=false;
return true;
}
else if (Game.IsControlJustPressed(Control.FrontendCancel))
{
PopUp.Visible = false;
return false;
}
Script.Yield();
}
}
public static void UsernameActivated(object a, System.EventArgs b)
{
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
if (!string.IsNullOrWhiteSpace(newUsername))
{
Main.Settings.Username = newUsername;
Util.SaveSettings();
_usernameItem.AltTitle = newUsername;
}
}
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);
}
}
public static void ServerIpActivated(object a, System.EventArgs b)
{
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
{
Main.Settings.LastServerAddress = newServerIp;
Util.SaveSettings();
ServerIpItem.AltTitle = newServerIp;
}
}
public static void InitiateConnectionMenuSetting()
{
_usernameItem.Enabled = false;
ServerIpItem.Enabled = false;
_serverConnectItem.Enabled = false;
}
public static void ConnectedMenuSetting()
{
_serverConnectItem.Enabled = true;
_serverConnectItem.Title = "Disconnect";
Menu.Visible = false;
}
public static void DisconnectedMenuSetting()
{
_usernameItem.Enabled = true;
ServerIpItem.Enabled = true;
_serverConnectItem.Enabled = true;
_serverConnectItem.Title = "Connect";
}
}
}

View File

@ -29,7 +29,7 @@ namespace RageCoop.Client
d1.Activated+=(sender,e) =>
{
try{ SyncParameters.PositioinPrediction =float.Parse(Game.GetUserInput(WindowTitle.EnterMessage20, SyncParameters.PositioinPrediction.ToString(), 20));}
try{ SyncParameters.PositioinPredictionDefault =float.Parse(Game.GetUserInput(WindowTitle.EnterMessage20, SyncParameters.PositioinPredictionDefault.ToString(), 20));}
catch { }
Update();
};
@ -55,7 +55,7 @@ namespace RageCoop.Client
private static void Update()
{
d1.AltTitle = SyncParameters.PositioinPrediction.ToString();
d1.AltTitle = SyncParameters.PositioinPredictionDefault.ToString();
}
}
}

View File

@ -9,7 +9,7 @@ using System.Drawing;
namespace RageCoop.Client
{
internal class DevToolMenu
internal static class DevToolMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "DevTool", "Help with the development")
{

View File

@ -0,0 +1,148 @@
using System;
using System.Net;
using System.Drawing;
using System.Collections.Generic;
using Newtonsoft.Json;
using LemonUI.Menus;
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!
/// </summary>
internal static class ServersMenu
{
private static Thread GetServersThread;
internal static NativeMenu Menu = new NativeMenu("RAGECOOP", "Servers", "Go to the server list")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
internal static NativeItem ResultItem = null;
/// <summary>
/// Don't use it!
/// </summary>
static ServersMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) =>
{
CleanUpList();
Menu.Add(ResultItem = new NativeItem("Loading..."));
// Prevent freezing
GetServersThread=new Thread(()=> GetAllServers());
GetServersThread.Start();
};
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
{
CleanUpList();
};
}
private static void CleanUpList()
{
Menu.Clear();
ResultItem = null;
}
private static void GetAllServers()
{
List<ServerListClass> serverList = null;
var realUrl = Main.Settings.MasterServer;
serverList = JsonConvert.DeserializeObject<List<ServerListClass>>(DownloadString(realUrl));
// Need to be processed in main thread
Main.QueueAction(() =>
{
if (serverList == null)
{
ResultItem.Title = "Something went wrong!";
return;
}
if (serverList.Count == 0)
{
ResultItem.Title = "No server was found!";
return;
}
CleanUpList();
foreach (ServerListClass server in serverList)
{
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 !NON_INTERACTIVE
CoopMenu.ServerIpItem.AltTitle = address;
CoopMenu.Menu.Visible = true;
#endif
Main.Settings.LastServerAddress = address;
Util.SaveSettings();
}
catch (Exception ex)
{
GTA.UI.Notification.Show($"~r~{ex.Message}");
}
};
Menu.Add(tmpItem);
}
});
}
private static string DownloadString(string url)
{
try
{
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
WebClient client = new WebClient();
return client.DownloadString(url);
}
catch (Exception ex)
{
Main.QueueAction(() =>
{
ResultItem.Title = "Download failed!";
ResultItem.Description = ex.Message;
});
return "";
}
}
}
}

View File

@ -4,24 +4,24 @@ using System.Windows.Forms;
using GTA;
using LemonUI.Menus;
namespace RageCoop.Client.Menus.Sub
namespace RageCoop.Client.Menus
{
/// <summary>
/// Don't use it!
/// </summary>
public class SettingsMenu
internal static class SettingsMenu
{
public NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
private readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic);
private readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu);
private readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableTraffic);
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 readonly NativeCheckboxItem _showNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
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());
@ -30,7 +30,7 @@ namespace RageCoop.Client.Menus.Sub
/// <summary>
/// Don't use it!
/// </summary>
public SettingsMenu()
static SettingsMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
@ -52,14 +52,14 @@ namespace RageCoop.Client.Menus.Sub
Menu.Add(_vehicleSoftLimit);
}
private void _disablePauseAlt_CheckboxChanged(object sender, EventArgs e)
private static void _disablePauseAlt_CheckboxChanged(object sender, EventArgs e)
{
Main.Settings.DisableAlternatePause=_disablePauseAlt.Checked;
Util.SaveSettings();
}
private void vehicleSoftLimit_Activated(object sender, EventArgs e)
private static void vehicleSoftLimit_Activated(object sender, EventArgs e)
{
try
{
@ -71,7 +71,7 @@ namespace RageCoop.Client.Menus.Sub
}
catch { }
}
private void ChaneMenuKey(object sender, EventArgs e)
private static void ChaneMenuKey(object sender, EventArgs e)
{
try
{
@ -85,7 +85,7 @@ namespace RageCoop.Client.Menus.Sub
catch { }
}
private void ChangePassengerKey(object sender, EventArgs e)
private static void ChangePassengerKey(object sender, EventArgs e)
{
try
{
@ -99,30 +99,25 @@ namespace RageCoop.Client.Menus.Sub
catch { }
}
public void DisableTrafficCheckboxChanged(object a, System.EventArgs b)
public static void DisableTrafficCheckboxChanged(object a, System.EventArgs b)
{
Main.Settings.DisableTraffic = _disableTrafficItem.Checked;
Util.SaveSettings() ;
}
public void FlipMenuCheckboxChanged(object a, System.EventArgs b)
public static void FlipMenuCheckboxChanged(object a, System.EventArgs b)
{
Main.MainMenu.MainMenu.Alignment = _flipMenuItem.Checked ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left;
CoopMenu.Menu.Alignment = _flipMenuItem.Checked ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left;
Menu.Alignment = _flipMenuItem.Checked ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left;
Main.Settings.FlipMenu = _flipMenuItem.Checked;
Util.SaveSettings();
}
public void ShowNetworkInfoCheckboxChanged(object a, System.EventArgs b)
public static void ShowNetworkInfoCheckboxChanged(object a, System.EventArgs b)
{
Networking.ShowNetworkInfo = _showNetworkInfoItem.Checked;
if (!Networking.ShowNetworkInfo)
{
Networking.BytesReceived = 0;
Networking.BytesSend = 0;
}
}
}
}

View File

@ -8,7 +8,7 @@ using GTA.Native;
namespace RageCoop.Client
{
public class Chat
internal class Chat
{
private readonly Scaleform MainScaleForm;

View File

@ -0,0 +1,200 @@
using System.IO;
using System.Linq;
using System.Collections.Generic;
using RageCoop.Core;
using System;
namespace RageCoop.Client
{
internal static class DownloadManager
{
static DownloadManager()
{
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, (data) =>
{
var fr = new Packets.FileTransferRequest();
fr.Unpack(data);
if (fr.Name.EndsWith(".zip"))
{
_zips.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.Unpack(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,_zips.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 {
get {
return Path.Combine(Main.Settings.DataDirectory,"Resources", Main.Settings.LastServerAddress.Replace(":", "."));
}
}
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
private static readonly List<string> _zips = new List<string>();
public static bool AddFile(int id, string name, long length)
{
Main.Logger.Debug($"Downloading file to {ResourceFolder}\\{name} , id:{id}");
if (!Directory.Exists(ResourceFolder))
{
Directory.CreateDirectory(ResourceFolder);
}
if (FileAlreadyExists(ResourceFolder, name, length))
{
Main.Logger.Debug($"File already exists! canceling download:{name}");
return false;
}
if (!name.EndsWith(".zip"))
{
Main.Logger.Error($"File download blocked! [{name}]");
return false;
}
lock (InProgressDownloads)
{
InProgressDownloads.Add(id, new DownloadFile()
{
FileID = id,
FileName = name,
FileLength = length,
Stream = new FileStream($"{ResourceFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
});
}
return true;
}
/// <summary>
/// Check if the file already exists and if the size correct otherwise delete this file
/// </summary>
/// <param name="folder"></param>
/// <param name="name"></param>
/// <param name="length"></param>
/// <returns></returns>
private static bool FileAlreadyExists(string folder, string name, long length)
{
string filePath = $"{folder}\\{name}";
if (File.Exists(filePath))
{
if (new FileInfo(filePath).Length == length)
{
return true;
}
// Delete the file because the length is wrong (maybe the file was updated)
File.Delete(filePath);
}
return false;
}
public static void Write(int id, byte[] chunk)
{
lock (InProgressDownloads)
{
DownloadFile file;
if (InProgressDownloads.TryGetValue(id, out file))
{
file.Stream.Write(chunk, 0, chunk.Length);
}
else
{
Main.Logger.Trace($"Received unhandled file chunk:{id}");
}
}
}
public static void Complete(int id)
{
DownloadFile f;
if (InProgressDownloads.TryGetValue(id, out f))
{
InProgressDownloads.Remove(id);
f.Dispose();
if (f.FileName.EndsWith(".zip"))
{
_zips.Add(f.FileName);
}
Main.Logger.Info($"Download finished:{f.FileName}");
}
else
{
Main.Logger.Error($"Download not found! {id}");
}
}
public static void Cleanup()
{
lock (InProgressDownloads)
{
foreach (var file in InProgressDownloads.Values)
{
file.Dispose();
}
InProgressDownloads.Clear();
}
if (Directory.Exists(ResourceFolder))
{
foreach (var zip in Directory.GetDirectories(ResourceFolder, "*.zip"))
{
File.Delete(zip);
}
}
_zips.Clear();
}
}
internal class DownloadFile: System.IDisposable
{
public int FileID { get; set; } = 0;
public string FileName { get; set; } = string.Empty;
public long FileLength { get; set; } = 0;
public long FileWritten { get; set; } = 0;
public FileStream Stream { get; set; }
public void Dispose()
{
if(Stream!= null)
{
Stream.Flush();
Stream.Close();
Stream.Dispose();
}
}
}
}

View File

@ -0,0 +1,153 @@
using System;
using Lidgren.Network;
using RageCoop.Core;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
namespace RageCoop.Client
{
internal static partial class Networking
{
public static NetClient Client;
public static float Latency = 0;
public static bool ShowNetworkInfo = false;
public static Security Security;
static Networking()
{
Security=new Security(Main.Logger);
Task.Run(() =>
{
while (true)
{
if (Client!=null)
{
ProcessMessage(Client.WaitMessage(200));
Client.FlushSendQueue();
}
else
{
Thread.Sleep(20);
}
}
});
}
public static void ToggleConnection(string address,string username=null,string password=null)
{
if (IsOnServer)
{
Client.Disconnect("Bye!");
}
else
{
password = password ?? Main.Settings.Password;
username=username ?? Main.Settings.Username;
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{
AutoFlushSendQueue = true
};
config.EnableMessageType(NetIncomingMessageType.ConnectionLatencyUpdated);
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
string[] ip = new string[2];
int idx = address.LastIndexOf(':');
if (idx != -1)
{
ip[0] = address.Substring(0, idx);
ip[1] = address.Substring(idx + 1);
}
if (ip.Length != 2)
{
throw new Exception("Malformed URL");
}
EntityPool.AddPlayer();
Task.Run(() =>
{
try
{
DownloadManager.Cleanup();
Client = new NetClient(config);
Client.Start();
Main.QueueAction(() => { GTA.UI.Notification.Show($"~y~Trying to connect..."); });
Menus.CoopMenu._serverConnectItem.Enabled=false;
Security.Regen();
if(!GetServerPublicKey(address))
{
Menus.CoopMenu._serverConnectItem.Enabled=true;
throw new TimeoutException("Failed to retrive server's public key");
}
// Send HandshakePacket
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
var handshake = new Packets.Handshake()
{
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);
}
catch(Exception ex)
{
Main.Logger.Error("Cannot connect to server: ", ex);
Main.QueueAction(() => GTA.UI.Notification.Show("Cannot connect to server: "+ex.Message));
}
});
}
}
public static bool IsOnServer
{
get { return Client?.ConnectionStatus == NetConnectionStatus.Connected; }
}
#region -- GET --
#region -- PLAYER --
private static void PlayerConnect(Packets.PlayerConnect packet)
{
var p = new PlayerData
{
PedID = packet.PedID,
Username= packet.Username,
};
GTA.UI.Notification.Show($"{p.Username} connected.");
PlayerList.SetPlayer(packet.PedID, packet.Username);
Main.Logger.Debug($"player connected:{p.Username}");
}
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
{
var name=PlayerList.GetPlayer(packet.PedID).Username;
GTA.UI.Notification.Show($"{name} left.");
PlayerList.RemovePlayer(packet.PedID);
EntityPool.RemoveAllFromPlayer(packet.PedID);
}
#endregion // -- PLAYER --
private static bool GetServerPublicKey(string address,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]));
return PublicKeyReceived.WaitOne(timeout);
}
#endregion
}
}

View File

@ -0,0 +1,429 @@
using System;
using System.Collections.Generic;
using Lidgren.Network;
using RageCoop.Core;
using GTA;
using RageCoop.Client.Menus;
using System.Threading;
namespace RageCoop.Client
{
internal static partial class Networking
{
private static Func<byte, BitReader, object> _resolveHandle = (t, reader) =>
{
switch (t)
{
case 50:
return EntityPool.ServerProps[reader.ReadInt()].MainProp?.Handle;
case 51:
return EntityPool.GetPedByID(reader.ReadInt())?.MainPed?.Handle;
case 52:
return EntityPool.GetVehicleByID(reader.ReadInt())?.MainVehicle?.Handle;
case 60:
return EntityPool.ServerBlips[reader.ReadInt()].Handle;
default:
throw new ArgumentException("Cannot resolve server side argument: "+t);
}
};
private static AutoResetEvent PublicKeyReceived=new AutoResetEvent(false);
private static Dictionary<int, Action<PacketType, byte[]>> PendingResponses = new Dictionary<int, Action<PacketType, byte[]>>();
internal static Dictionary<PacketType, Func< byte[], Packet>> RequestHandlers = new Dictionary<PacketType, Func< byte[], Packet>>();
public static void ProcessMessage(NetIncomingMessage message)
{
if(message == null) { return; }
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
break;
case NetConnectionStatus.Connected:
Main.QueueAction(() => {
CoopMenu.ConnectedMenuSetting();
Main.MainChat.Init();
PlayerList.Cleanup();
GTA.UI.Notification.Show("~g~Connected!");
});
Main.Logger.Info(">> Connected <<");
break;
case NetConnectionStatus.Disconnected:
DownloadManager.Cleanup();
// Reset all values
Latency = 0;
Main.QueueAction(() => Main.CleanUpWorld());
if (Main.MainChat.Focused)
{
Main.MainChat.Focused = false;
}
Main.QueueAction(() => Main.CleanUp());
#if !NON_INTERACTIVE
CoopMenu.DisconnectedMenuSetting();
#endif
Main.QueueAction(() =>
GTA.UI.Notification.Show("~r~Disconnected: " + reason));
Main.Resources.Unload();
Main.Logger.Info($">> Disconnected << reason: {reason}");
break;
}
break;
case NetIncomingMessageType.Data:
{
if (message.LengthBytes==0) { break; }
var packetType = PacketType.Unknown;
try
{
// Get packet type
packetType = (PacketType)message.ReadByte();
switch (packetType)
{
case PacketType.Response:
{
int id = message.ReadInt32();
if (PendingResponses.TryGetValue(id, out var callback))
{
callback((PacketType)message.ReadByte(), message.ReadBytes(message.ReadInt32()));
PendingResponses.Remove(id);
}
break;
}
case PacketType.Request:
{
int id = message.ReadInt32();
var realType = (PacketType)message.ReadByte();
int len = message.ReadInt32();
Main.Logger.Debug($"{id},{realType},{len}");
if (RequestHandlers.TryGetValue(realType, out var handler))
{
var response = Client.CreateMessage();
response.Write((byte)PacketType.Response);
response.Write(id);
handler(message.ReadBytes(len)).Pack(response);
Client.SendMessage(response, NetDeliveryMethod.ReliableOrdered);
}
break;
}
default:
{
byte[] data = message.ReadBytes(message.ReadInt32());
HandlePacket(packetType, data);
break;
}
}
}
catch (Exception ex)
{
Main.QueueAction(() => {
GTA.UI.Notification.Show("~r~~h~Packet Error");
return true;
});
Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex);
Client.Disconnect($"Packet Error [{packetType}]");
}
break;
}
case NetIncomingMessageType.ConnectionLatencyUpdated:
Latency = message.ReadFloat();
break;
case NetIncomingMessageType.UnconnectedData:
{
var packetType = (PacketType)message.ReadByte();
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
if (packetType==PacketType.PublicKeyResponse)
{
var packet=new Packets.PublicKeyResponse();
packet.Unpack(data);
Security.SetServerPublicKey(packet.Modulus,packet.Exponent);
PublicKeyReceived.Set();
}
break;
}
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.ErrorMessage:
case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.VerboseDebugMessage:
break;
default:
break;
}
Client.Recycle(message);
}
private static void HandlePacket(PacketType packetType, byte[] data)
{
switch (packetType)
{
case PacketType.PlayerConnect:
{
Packets.PlayerConnect packet = new Packets.PlayerConnect();
packet.Unpack(data);
Main.QueueAction(() => PlayerConnect(packet));
}
break;
case PacketType.PlayerDisconnect:
{
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect();
packet.Unpack(data);
Main.QueueAction(() => PlayerDisconnect(packet));
}
break;
case PacketType.PlayerInfoUpdate:
{
var packet = new Packets.PlayerInfoUpdate();
packet.Unpack(data);
PlayerList.UpdatePlayer(packet);
break;
}
#region ENTITY SYNC
case PacketType.VehicleSync:
{
Packets.VehicleSync packet = new Packets.VehicleSync();
packet.Unpack(data);
VehicleSync(packet);
}
break;
case PacketType.PedSync:
{
Packets.PedSync packet = new Packets.PedSync();
packet.Unpack(data);
PedSync(packet);
}
break;
case PacketType.VehicleStateSync:
{
Packets.VehicleStateSync packet = new Packets.VehicleStateSync();
packet.Unpack(data);
VehicleStateSync(packet);
}
break;
case PacketType.PedStateSync:
{
Packets.PedStateSync packet = new Packets.PedStateSync();
packet.Unpack(data);
PedStateSync(packet);
}
break;
case PacketType.ProjectileSync:
{
Packets.ProjectileSync packet = new Packets.ProjectileSync();
packet.Unpack(data);
ProjectileSync(packet);
break;
}
#endregion
case PacketType.ChatMessage:
{
Packets.ChatMessage packet = new Packets.ChatMessage();
packet.Unpack(data);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
packet.Unpack(data);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
break;
case PacketType.CustomEventQueued:
{
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
Main.QueueAction(() =>
{
packet.Unpack(data);
Scripting.API.Events.InvokeCustomEventReceived(packet);
});
}
break;
case PacketType.FileTransferChunk:
{
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
packet.Unpack(data);
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
default:
if (packetType.IsSyncEvent())
{
// Dispatch to main thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
}
break;
}
}
private static void PedSync(Packets.PedSync packet)
{
SyncedPed c = EntityPool.GetPedByID(packet.ID);
if (c==null)
{
// Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c=new SyncedPed(packet.ID));
}
PedDataFlags flags = packet.Flag;
c.ID=packet.ID;
//c.OwnerID=packet.OwnerID;
c.Health = packet.Health;
c.Position = packet.Position;
c.Rotation = packet.Rotation;
c.Velocity = packet.Velocity;
c.Speed = packet.Speed;
c.CurrentWeaponHash = packet.CurrentWeaponHash;
c.IsAiming = flags.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;
if (c.IsAiming)
{
c.AimCoords = packet.AimCoords;
}
if (c.IsRagdoll)
{
c.RotationVelocity=packet.RotationVelocity;
}
}
private static void PedStateSync(Packets.PedStateSync packet)
{
SyncedPed c = EntityPool.GetPedByID(packet.ID);
if (c==null) { return; }
c.ID=packet.ID;
c.OwnerID=packet.OwnerID;
c.Clothes=packet.Clothes;
c.WeaponComponents=packet.WeaponComponents;
c.WeaponTint=packet.WeaponTint;
c.ModelHash=packet.ModelHash;
c.LastStateSynced = Main.Ticked;
c.BlipColor=packet.BlipColor;
c.BlipSprite=packet.BlipSprite;
c.BlipScale=packet.BlipScale;
}
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.IsLocal) { 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.IsLocal) { 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.Livery=packet.Livery;
v.Flags=packet.Flag;
foreach (KeyValuePair<int, int> pair in packet.Passengers)
{
if (EntityPool.PedExists(pair.Value))
{
v.Passengers.Add((VehicleSeat)pair.Key, EntityPool.GetPedByID(pair.Value));
}
}
v.LastStateSynced= Main.Ticked;
}
private static void ProjectileSync(Packets.ProjectileSync packet)
{
var p = EntityPool.GetProjectileByID(packet.ID);
if (p==null)
{
if (packet.Exploded) { return; }
// Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p=new SyncedProjectile(packet.ID));
}
p.Position=packet.Position;
p.Rotation=packet.Rotation;
p.Velocity=packet.Velocity;
p.Hash=(WeaponHash)packet.WeaponHash;
p.ShooterID=packet.ShooterID;
p.Exploded=packet.Exploded;
p.LastSynced=Main.Ticked;
}
}
}

View File

@ -1,9 +1,4 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Lidgren.Network;
using Lidgren.Network;
using RageCoop.Core;
using GTA;
using GTA.Native;
@ -36,21 +31,21 @@ namespace RageCoop.Client
{
ID =c.ID,
Health = p.Health,
Position = p.Position.ToLVector(),
Rotation = p.Rotation.ToLVector(),
Velocity = p.Velocity.ToLVector(),
Position = p.Position,
Rotation = p.Rotation,
Velocity = p.Velocity,
Speed = p.GetPedSpeed(),
CurrentWeaponHash = (uint)p.Weapons.Current.Hash,
Flag = p.GetPedFlags(),
Heading=p.Heading,
};
if (packet.Flag.HasFlag(PedDataFlags.IsAiming))
if (packet.Flag.HasPedFlag(PedDataFlags.IsAiming))
{
packet.AimCoords = p.GetAimCoord().ToLVector();
packet.AimCoords = p.GetAimCoord();
}
if (packet.Flag.HasFlag(PedDataFlags.IsRagdoll))
if (packet.Flag.HasPedFlag(PedDataFlags.IsRagdoll))
{
packet.RotationVelocity=p.RotationVelocity.ToLVector();
packet.RotationVelocity=p.RotationVelocity;
}
Send(packet, ConnectionChannel.PedSync);
}
@ -58,15 +53,32 @@ namespace RageCoop.Client
{
Ped p = c.MainPed;
var packet=new Packets.PedStateSync()
var packet = new Packets.PedStateSync()
{
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),
};
Blip b;
if (c.IsPlayer)
{
packet.BlipColor=Scripting.API.Config.BlipColor;
packet.BlipSprite=Scripting.API.Config.BlipSprite;
packet.BlipScale=Scripting.API.Config.BlipScale;
}
else if ((b = p.AttachedBlip) !=null)
{
packet.BlipColor=b.Color;
packet.BlipSprite=b.Sprite;
if (packet.BlipSprite==BlipSprite.PoliceOfficer || packet.BlipSprite==BlipSprite.PoliceOfficer2)
{
packet.BlipScale=0.5f;
}
}
Send(packet, ConnectionChannel.PedSync);
}
public static void SendVehicle(SyncedVehicle v)
@ -76,13 +88,15 @@ namespace RageCoop.Client
{
ID =v.ID,
SteeringAngle = veh.SteeringAngle,
Position = veh.Position.ToLVector(),
Rotation = veh.Rotation.ToLVector(),
Velocity = veh.Velocity.ToLVector(),
RotationVelocity=veh.RotationVelocity.ToLVector(),
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)
@ -94,7 +108,7 @@ namespace RageCoop.Client
{
Function.Call<byte>(Hash.GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
}
var packet=new Packets.VehicleStateSync()
var packet = new Packets.VehicleStateSync()
{
ID =v.ID,
OwnerID = v.OwnerID,
@ -102,12 +116,19 @@ namespace RageCoop.Client
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),
Livery=Function.Call<int>(Hash.GET_VEHICLE_LIVERY, veh)
};
if (v.MainVehicle==Game.Player.LastVehicle)
{
packet.RadioStation=Util.GetPlayerRadioIndex();
}
Send(packet, ConnectionChannel.VehicleSync);
}
public static void SendProjectile(SyncedProjectile sp)
@ -117,9 +138,9 @@ namespace RageCoop.Client
{
ID =sp.ID,
ShooterID=sp.ShooterID,
Position=p.Position.ToLVector(),
Rotation=p.Rotation.ToLVector(),
Velocity=p.Velocity.ToLVector(),
Position=p.Position,
Rotation=p.Rotation,
Velocity=p.Velocity,
WeaponHash=(uint)p.WeaponHash,
Exploded=p.IsDead
};
@ -133,8 +154,8 @@ namespace RageCoop.Client
{
Send(new Packets.BulletShot()
{
StartPosition = start.ToLVector(),
EndPosition = end.ToLVector(),
StartPosition = start,
EndPosition = end,
OwnerID = ownerID,
WeaponHash=weapon,
}, ConnectionChannel.SyncEvents);
@ -150,62 +171,8 @@ namespace RageCoop.Client
Client.FlushSendQueue();
#if DEBUG
if (ShowNetworkInfo)
{
BytesSend += outgoingMessage.LengthBytes;
}
#endif
}
public static void SendModData(long target, string modName, byte customID, byte[] bytes)
{
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.Mod()
{
// NetHandle = Main.LocalNetHandle,
Target = target,
Name = modName,
CustomPacketID = customID,
Bytes = bytes
}.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Mod);
Client.FlushSendQueue();
#if DEBUG
if (ShowNetworkInfo)
{
BytesSend += outgoingMessage.LengthBytes;
}
#endif
}
public static void SendDownloadFinish(byte id)
{
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.FileTransferComplete() { ID = id }.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableUnordered, (byte)ConnectionChannel.File);
Client.FlushSendQueue();
#if DEBUG
if (ShowNetworkInfo)
{
BytesSend += outgoingMessage.LengthBytes;
}
#endif
}
public static void SendTriggerEvent(string eventName, params object[] args)
{
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.ServerClientEvent()
{
EventName = eventName,
Args = new List<object>(args)
}.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableUnordered, (byte)ConnectionChannel.Event);
Client.FlushSendQueue();
}
#endregion
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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.Client.Statistics.SentBytes;
var bd = Networking.Client.Statistics.ReceivedBytes;
Thread.Sleep(1000);
BytesUpPerSecond=Networking.Client.Statistics.SentBytes-bu;
BytesDownPerSecond=Networking.Client.Statistics.ReceivedBytes-bd;
}
});
}
}
}

View File

@ -1,12 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using RageCoop.Core;
using GTA;
using GTA.Math;
using RageCoop.Core;
using GTA.Native;
namespace RageCoop.Client
{
public static class PlayerList
internal static class PlayerList
{
private const float LEFT_POSITION = 0.122f;
private const float RIGHT_POSITION = 0.9f;
@ -15,7 +16,7 @@ namespace RageCoop.Client
public static ulong Pressed { get; set; }
public static bool LeftAlign = true;
public static List<PlayerData> Players=new List<PlayerData> { };
public static Dictionary<int,PlayerData> Players=new Dictionary<int, PlayerData> { };
public static void Tick()
{
if (!Networking.IsOnServer)
@ -30,7 +31,7 @@ namespace RageCoop.Client
if ((Util.GetTickCount64() - Pressed) < 5000 && !Main.MainChat.Focused
#if !NON_INTERACTIVE
&& !Main.MainMenu.MenuPool.AreAnyVisible
&& !Menus.CoopMenu.MenuPool.AreAnyVisible
#endif
)
{
@ -52,45 +53,82 @@ namespace RageCoop.Client
foreach (var player in Players)
{
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Latency * 1000:N0}ms", player.Username, 116, 0, i - 1, "", "", 2, "", "", ' ');
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Value.Latency * 1000:N0}ms", player.Value.Username, 116, 0, i - 1, "", "", 2, "", "", ' ');
}
_mainScaleform.CallFunction("SET_TITLE", "Player list", (Players.Count) + " players");
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count+1} players");
_mainScaleform.CallFunction("DISPLAY_VIEW");
}
public static void SetPlayer(int id, string username,float latency=0)
{
var toset = Players.Where(x => x.PedID==id);
if (toset.Any())
PlayerData p;
if (Players.TryGetValue(id,out p))
{
var p=toset.First();
p.Username=username;
p.PedID=id;
p.Latency=latency;
}
else
{
PlayerData p = new PlayerData { PedID=id, Username=username,Latency=latency };
Players.Add(p);
p = new PlayerData { PedID=id, Username=username,Latency=latency };
Players.Add(id,p);
}
}
public static void UpdatePlayer(Packets.PlayerInfoUpdate packet)
{
var p = GetPlayer(packet.PedID);
if(p?.Character != null)
{
p.Latency= packet.Latency;
}
}
public static PlayerData GetPlayer(int id)
{
return Players.Find(x => x.PedID==id);
PlayerData p;
Players.TryGetValue(id, out p);
return p;
}
public static PlayerData GetPlayer(SyncedPed p)
{
var player = GetPlayer(p.ID);
if (player!=null)
{
player.Character=p;
}
return player;
}
public static void RemovePlayer(int id)
{
var p = Players.Where(x => x.PedID==id);
if (p.Any())
if (Players.ContainsKey(id))
{
Players.Remove(p.First());
Players.Remove(id);
}
}
public static void Cleanup()
{
Players=new List<PlayerData> { };
Players=new Dictionary<int, PlayerData>{ };
}
}
internal class PlayerData
{
public string Username { get; internal set; }
/// <summary>
/// Universal character ID.
/// </summary>
public int PedID
{
get; internal set;
}
public SyncedPed Character { get; set; }
/// <summary>
/// Player Latency in second.
/// </summary>
public float Latency { get; set; }
public bool DisplayNameTag { get; set; } = true;
}
}

View File

@ -0,0 +1,63 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<UseWindowsForms>True</UseWindowsForms>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile></DocumentationFile>
<DebugType>portable</DebugType>
<AssemblyVersion>0.5.0</AssemblyVersion>
<FileVersion>0.5.0</FileVersion>
<Version>0.5.0</Version>
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
<Authors>RAGECOOP</Authors>
<Description>An API reference for developing client-side resource for RAGECOOP</Description>
<PackageProjectUrl>https://ragecoop.online/</PackageProjectUrl>
<RepositoryUrl>https://github.com/RAGECOOP/RAGECOOP-V</RepositoryUrl>
<ApplicationIcon>icon.ico</ApplicationIcon>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<DefineConstants>SHVDN3</DefineConstants>
<TargetFrameworks>net48</TargetFrameworks>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutDir>..\bin\Debug\Client</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutDir>..\bin\Release\Client</OutDir>
</PropertyGroup>
<ItemGroup>
<Content Include="icon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="LemonUI.SHVDN3">
<HintPath>..\libs\LemonUI.SHVDN3.dll</HintPath>
</Reference>
<Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\libs\ScriptHookVDotNet.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>M:\SandBox-Shared\repo\RageCoop\RageCoop-V\RageCoop.Client\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,275 @@
#undef DEBUG
using System.Collections.Generic;
using System;
using System.Linq;
using RageCoop.Core;
using System.Windows.Forms;
using GTA;
namespace RageCoop.Client.Scripting
{
/// <summary>
///
/// </summary>
public class CustomEventReceivedArgs : EventArgs
{
/// <summary>
/// The event hash
/// </summary>
public int Hash { get; set; }
/// <summary>
/// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion
/// </summary>
public object[] Args { get; set; }
}
/// <summary>
/// Provides vital functionality to interact with RAGECOOP
/// </summary>
public static class API
{
#region INTERNAL
internal static Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new Dictionary<int, List<Action<CustomEventReceivedArgs>>>();
#endregion
/// <summary>
/// Client configuration, this will conflict with server-side config.
/// </summary>
public static class Config
{
/// <summary>
/// Get or set local player's username, set won't be effective if already connected to a server.
/// </summary>
public static string Username
{
get { return Main.Settings.Username; }
set
{
if (Networking.IsOnServer || string.IsNullOrEmpty(value))
{
return;
}
Main.Settings.Username = value;
}
}
/// <summary>
/// Enable automatic respawn for this player.
/// </summary>
public static bool EnableAutoRespawn { get; set; } = true;
/// <summary>
/// Get or set player's blip color
/// </summary>
public static BlipColor BlipColor { get; set; } = BlipColor.White;
/// <summary>
/// Get or set player's blip sprite
/// </summary>
public static BlipSprite BlipSprite { get; set; } = BlipSprite.Standard;
/// <summary>
/// Get or set scale of player's blip
/// </summary>
public static float BlipScale { get; set; } = 1;
}
/// <summary>
/// Base events for RageCoop
/// </summary>
public static class Events
{
#region DELEGATES
/// <summary>
///
/// </summary>
public delegate void EmptyEvent();
/// <summary>
///
/// </summary>
/// <param name="hash"></param>
/// <param name="args"></param>
public delegate void CustomEvent(int hash, List<object> args);
#endregion
/// <summary>
/// The local player is dead
/// </summary>
public static event EmptyEvent OnPlayerDied;
/// <summary>
/// A local vehicle is spawned
/// </summary>
public static event EventHandler<SyncedVehicle> OnVehicleSpawned;
/// <summary>
/// A local vehicle is deleted
/// </summary>
public static event EventHandler<SyncedVehicle> OnVehicleDeleted;
/// <summary>
/// A local ped is spawned
/// </summary>
public static event EventHandler<SyncedPed> OnPedSpawned;
/// <summary>
/// A local ped is deleted
/// </summary>
public static event EventHandler<SyncedPed> OnPedDeleted;
/// <summary>
/// This is equivalent of <see cref="GTA.Script.Tick"/>.
/// </summary>
public static event EmptyEvent OnTick;
/// <summary>
/// This is equivalent of <see cref="Script.KeyDown"/>
/// </summary>
public static KeyEventHandler OnKeyDown;
/// <summary>
/// This is equivalent of <see cref="Script.KeyUp"/>
/// </summary>
public static KeyEventHandler OnKeyUp;
#region INVOKE
internal static void InvokeVehicleSpawned(SyncedVehicle v) { OnVehicleSpawned?.Invoke(null, v); }
internal static void InvokeVehicleDeleted(SyncedVehicle v) { OnVehicleDeleted?.Invoke(null, v); }
internal static void InvokePedSpawned(SyncedPed p) { OnPedSpawned?.Invoke(null, p); }
internal static void InvokePedDeleted(SyncedPed p) { OnPedDeleted?.Invoke(null, p); }
internal static void InvokePlayerDied() { OnPlayerDied?.Invoke(); }
internal static void InvokeTick() { OnTick?.Invoke(); }
internal static void InvokeKeyDown(object s,KeyEventArgs e) { OnKeyDown?.Invoke(s,e); }
internal static void InvokeKeyUp(object s, KeyEventArgs e) { OnKeyUp?.Invoke(s, e); }
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
{
var args = new CustomEventReceivedArgs() { Hash=p.Hash, Args=p.Args};
// Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
List<Action<CustomEventReceivedArgs>> handlers;
if (CustomEventHandlers.TryGetValue(p.Hash, out handlers))
{
handlers.ForEach((x) => { x.Invoke(args); });
}
}
#endregion
}
#region PROPERTIES
/// <summary>
/// Get the local player's ID
/// </summary>
/// <returns>PlayerID</returns>
public static int LocalPlayerID
{
get { return Main.LocalPlayerID; }
}
/// <summary>
/// Check if a RAGECOOP menu is visible
/// </summary>
public static bool IsMenuVisible
{
get { return Menus.CoopMenu.MenuPool.AreAnyVisible; }
}
/// <summary>
/// Check if the RAGECOOP chat is visible
/// </summary>
public static bool IsChatFocused
{
get { return Main.MainChat.Focused; }
}
/// <summary>
/// Check if the RAGECOOP list of players is visible
/// </summary>
public static bool IsPlayerListVisible
{
get { return Util.GetTickCount64() - PlayerList.Pressed < 5000; }
}
/// <summary>
/// Get the version of RAGECOOP
/// </summary>
public static string CurrentVersion
{
get { return Main.CurrentVersion; }
}
/// <summary>
/// Get a <see cref="Core.Logger"/> that RAGECOOP is currently using.
/// </summary>
/// <returns></returns>
public static Logger Logger
{
get
{
return Main.Logger;
}
}
#endregion
#region FUNCTIONS
/// <summary>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Name of the sender</param>
/// <param name="message">The player's message</param>
public static void LocalChatMessage(string from, string message)
{
Main.MainChat.AddMessage(from, message);
}
/// <summary>
/// Queue an action to be executed on next tick.
/// </summary>
/// <param name="a"></param>
public static void QueueAction(Action a)
{
Main.QueueAction(a);
}
/// <summary>
/// Disconnect from the server
/// </summary>
public static void Disconnect()
{
Networking.ToggleConnection(null);
}
/// <summary>
/// Send an event and data to the server.
/// </summary>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
public static void SendCustomEvent(int eventHash, params object[] args)
{
var p = new Packets.CustomEvent()
{
Args=args,
Hash=eventHash
};
Networking.Send(p, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from backgound thread, use <see cref="QueueAction(Action)"/> in the handler to dispatch code to script thread.
/// </summary>
/// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public static void RegisterCustomEventHandler(int hash, Action<CustomEventReceivedArgs> handler)
{
lock (CustomEventHandlers)
{
if (!CustomEventHandlers.TryGetValue(hash, out List<Action<CustomEventReceivedArgs>> handlers))
{
CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
}
handlers.Add(handler);
}
}
#endregion
}
}

View File

@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using GTA.Native;
using GTA.Math;
using GTA;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
namespace RageCoop.Client.Scripting
{
internal class BaseScript : ClientScript
{
private bool _isHost=false;
public override void OnStart()
{
API.Events.OnPedDeleted+=(s, p) => { API.SendCustomEvent(CustomEvents.OnPedDeleted, p.ID); };
API.Events.OnVehicleDeleted+=(s, p) => { API.SendCustomEvent(CustomEvents.OnVehicleDeleted, p.ID); };
API.RegisterCustomEventHandler(CustomEvents.SetAutoRespawn,SetAutoRespawn);
API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag,SetDisplayNameTag);
API.RegisterCustomEventHandler(CustomEvents.NativeCall,NativeCall);
API.RegisterCustomEventHandler(CustomEvents.ServerPropSync, ServerObjectSync);
API.RegisterCustomEventHandler(CustomEvents.DeleteServerProp, DeleteServerProp);
API.RegisterCustomEventHandler(CustomEvents.DeleteEntity, DeleteEntity);
API.RegisterCustomEventHandler(CustomEvents.SetDisplayNameTag, SetNameTag);
API.RegisterCustomEventHandler(CustomEvents.ServerBlipSync, ServerBlipSync);
API.RegisterCustomEventHandler(CustomEvents.DeleteServerBlip, DeleteServerBlip);
API.RegisterCustomEventHandler(CustomEvents.CreateVehicle, CreateVehicle);
API.RegisterCustomEventHandler(CustomEvents.UpdatePedBlip, UpdatePedBlip);
API.RegisterCustomEventHandler(CustomEvents.IsHost, (e) => { _isHost=(bool)e.Args[0]; });
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
Task.Run(() =>
{
while (true)
{
if (_isHost)
{
API.QueueAction(() =>
{
unsafe
{
var time = World.CurrentTimeOfDay;
int weather1 = default(int);
int weather2 = default(int);
float percent2 = default(float);
Function.Call(Hash._GET_WEATHER_TYPE_TRANSITION, &weather1, &weather2, &percent2);
API.SendCustomEvent(CustomEvents.WeatherTimeSync, time.Hours, time.Minutes, time.Seconds, weather1, weather2, percent2);
}
});
}
Thread.Sleep(1000);
}
});
}
private void WeatherTimeSync(CustomEventReceivedArgs e)
{
World.CurrentTimeOfDay=new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
Function.Call(Hash._SET_WEATHER_TYPE_TRANSITION, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
}
private void SetDisplayNameTag(CustomEventReceivedArgs e)
{
var p = PlayerList.GetPlayer((int)e.Args[0]);
if(p != null) { p.DisplayNameTag=(bool)e.Args[1]; }
}
private void UpdatePedBlip(CustomEventReceivedArgs e)
{
var p = Entity.FromHandle((int)e.Args[0]);
if (p == null) { return; }
if (p.Handle==Game.Player.Character.Handle)
{
API.Config.BlipColor=(BlipColor)(byte)e.Args[1];
API.Config.BlipSprite=(BlipSprite)(ushort)e.Args[2];
API.Config.BlipScale=(float)e.Args[3];
}
else
{
var b = p.AttachedBlip;
if (b == null) { b=p.AddBlip(); }
b.Color=(BlipColor)(byte)e.Args[1];
b.Sprite=(BlipSprite)(ushort)e.Args[2];
b.Scale=(float)e.Args[3];
}
}
private void CreateVehicle(CustomEventReceivedArgs e)
{
var vehicleModel = (Model)e.Args[1];
vehicleModel.Request(1000);
Vehicle veh= World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
while (veh==null)
{
veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
Thread.Sleep(10);
}
veh.CanPretendOccupants=false;
var v = new SyncedVehicle()
{
ID=(int)e.Args[0],
MainVehicle=veh,
OwnerID=Main.LocalPlayerID,
};
EntityPool.Add(v);
}
private void DeleteServerBlip(CustomEventReceivedArgs e)
{
if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip))
{
EntityPool.ServerBlips.Remove((int)e.Args[0]);
blip?.Delete();
}
}
private void ServerBlipSync(CustomEventReceivedArgs obj)
{
int id= (int)obj.Args[0];
var sprite=(BlipSprite)(ushort)obj.Args[1];
var color = (BlipColor)(byte)obj.Args[2];
var scale=(float)obj.Args[3];
var pos=(Vector3)obj.Args[4];
int rot= (int)obj.Args[5];
var name=(string)obj.Args[6];
Blip blip;
if (!EntityPool.ServerBlips.TryGetValue(id, out blip))
{
EntityPool.ServerBlips.Add(id, blip=World.CreateBlip(pos));
}
blip.Sprite = sprite;
blip.Color = color;
blip.Scale = scale;
blip.Position = pos;
blip.Rotation = rot;
blip.Name = name;
}
private void DeleteEntity(CustomEventReceivedArgs e)
{
Entity.FromHandle((int)e.Args[0])?.Delete();
}
public override void OnStop()
{
}
private void SetNameTag(CustomEventReceivedArgs e)
{
var p =PlayerList.GetPlayer((int)e.Args[0]);
if(p!= null)
{
p.DisplayNameTag=(bool)e.Args[1];
}
}
private void SetAutoRespawn(CustomEventReceivedArgs args)
{
API.Config.EnableAutoRespawn=(bool)args.Args[0];
}
private void DeleteServerProp(CustomEventReceivedArgs e)
{
var id = (int)e.Args[0];
if (EntityPool.ServerProps.TryGetValue(id, out var prop))
{
EntityPool.ServerProps.Remove(id);
prop?.MainProp?.Delete();
}
}
private void ServerObjectSync(CustomEventReceivedArgs e)
{
SyncedProp prop;
var id = (int)e.Args[0];
lock (EntityPool.PropsLock)
{
if (!EntityPool.ServerProps.TryGetValue(id, out prop))
{
EntityPool.ServerProps.Add(id, prop=new SyncedProp(id));
}
}
prop.LastSynced=Main.Ticked+1;
prop.ModelHash= (Model)e.Args[1];
prop.Position=(Vector3)e.Args[2];
prop.Rotation=(Vector3)e.Args[3];
prop.Update();
}
private void NativeCall(CustomEventReceivedArgs e)
{
List<InputArgument> arguments = new List<InputArgument>();
int i;
var ty = (byte)e.Args[0];
TypeCode returnType=(TypeCode)ty;
i = returnType==TypeCode.Empty ? 1 : 2;
var hash = (Hash)e.Args[i++];
for(; i<e.Args.Length;i++)
{
arguments.Add(GetInputArgument(e.Args[i]));
}
if (returnType==TypeCode.Empty)
{
Function.Call(hash, arguments.ToArray());
return;
}
var t = returnType;
int id = (int)e.Args[1];
switch (returnType)
{
case TypeCode.Boolean:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<bool>(hash, arguments.ToArray()) });
break;
case TypeCode.Byte:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<byte>(hash, arguments.ToArray()) });
break;
case TypeCode.Char:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<char>(hash, arguments.ToArray()) });
break;
case TypeCode.Single:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<float>(hash, arguments.ToArray()) });
break;
case TypeCode.Double:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<double>(hash, arguments.ToArray()) });
break;
case TypeCode.Int16:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<short>(hash, arguments.ToArray()) });
break;
case TypeCode.Int32:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<int>(hash, arguments.ToArray()) });
break;
case TypeCode.Int64:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<long>(hash, arguments.ToArray()) });
break;
case TypeCode.String:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<string>(hash, arguments.ToArray()) });
break;
case TypeCode.UInt16:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<ushort>(hash, arguments.ToArray()) });
break;
case TypeCode.UInt32:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<uint>(hash, arguments.ToArray()) });
break;
case TypeCode.UInt64:
API.SendCustomEvent(CustomEvents.NativeResponse,
new object[] { id, Function.Call<ulong>(hash, arguments.ToArray()) });
break;
}
}
private InputArgument GetInputArgument(object obj)
{
// Implicit conversion
switch (obj)
{
case byte _:
return (byte)obj;
case short _:
return (short)obj;
case ushort _:
return (ushort)obj;
case int _:
return (int)obj;
case uint _:
return (uint)obj;
case long _:
return (long)obj;
case ulong _:
return (ulong)obj;
case float _:
return (float)obj;
case bool _:
return (bool)obj;
case string _:
return (obj as string);
default:
return null;
}
}
}
}

View File

@ -0,0 +1,31 @@
using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting
{
/// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary>
public abstract class ClientScript
{
/// <summary>
/// This method would be called from background thread, call <see cref="API.QueueAction(System.Action)"/> to dispatch it to main thread.
/// </summary>
public abstract void OnStart();
/// <summary>
/// This method would be called from background thread when the client disconnected from the server, you MUST terminate all background jobs/threads in this method.
/// </summary>
public abstract void OnStop();
/// <summary>
/// Get the <see cref="ResourceFile"/> instance where this script is loaded from.
/// </summary>
public ResourceFile CurrentFile { get; internal set; }
/// <summary>
/// Get the <see cref="ClientResource"/> that this script belongs to.
/// </summary>
public ClientResource CurrentResource { get; internal set; }
}
}

View File

@ -0,0 +1,286 @@
using System.IO;
using RageCoop.Core.Scripting;
using RageCoop.Core;
using ICSharpCode.SharpZipLib.Zip;
using System;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
namespace RageCoop.Client.Scripting
{
/// <summary>
///
/// </summary>
public class ClientResource
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get; internal set; }
/// <summary>
/// Get all <see cref="ClientScript"/> instance in this resource.
/// </summary>
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
/// <summary>
/// Get the <see cref="ResourceFile"/> where this script is loaded from.
/// </summary>
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
}
internal class Resources
{
public Resources(){
BaseScriptType = "RageCoop.Client.Scripting.ClientScript";
Logger = Main.Logger;
}
private void StartAll()
{
lock (LoadedResources)
{
foreach (var d in LoadedResources)
{
foreach (var s in d.Scripts)
{
try
{
s.OnStart();
}
catch(Exception ex)
{
Logger.Error("Error occurred when starting script:"+s.GetType().FullName);
Logger?.Error(ex);
}
}
}
}
}
private void StopAll()
{
lock (LoadedResources)
{
foreach (var d in LoadedResources)
{
foreach (var s in d.Scripts)
{
try
{
s.OnStop();
}
catch (Exception ex)
{
Logger.Error("Error occurred when stopping script:"+s.GetType().FullName);
Logger?.Error(ex);
}
}
}
}
}
public void Load(string path,string[] zips)
{
foreach (var zip in zips)
{
var zipPath=Path.Combine(path, zip);
Logger?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
LoadResource(new ZipFile(zipPath),Path.Combine(path,"data"));
}
StartAll();
}
public void Unload()
{
StopAll();
if (LoadedResources.Count > 0)
{
API.QueueAction(()=>Util.Reload());
}
LoadedResources.Clear();
}
private List<string> ToIgnore = new List<string>
{
"RageCoop.Client.dll",
"RageCoop.Core.dll",
"RageCoop.Server.dll",
"ScriptHookVDotNet3.dll"
};
private List<ClientResource> LoadedResources = new List<ClientResource>();
private string BaseScriptType;
public Logger Logger { get; set; }
private void LoadResource(ZipFile file, string dataFolderRoot)
{
var r = new ClientResource()
{
Scripts = new List<ClientScript>(),
Name=Path.GetFileNameWithoutExtension(file.Name),
DataFolder=Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(file.Name))
};
Directory.CreateDirectory(r.DataFolder);
foreach (ZipEntry entry in file)
{
ResourceFile rFile;
r.Files.Add(entry.Name, rFile=new ResourceFile()
{
Name=entry.Name,
IsDirectory=entry.IsDirectory,
});
if (!entry.IsDirectory)
{
rFile.GetStream=() => { return file.GetInputStream(entry); };
if (entry.Name.EndsWith(".dll"))
{
var tmp = Path.GetTempFileName();
var f = File.OpenWrite(tmp);
rFile.GetStream().CopyTo(f);
f.Close();
LoadScriptsFromAssembly(rFile, tmp, r, false);
}
}
}
LoadedResources.Add(r);
file.Close();
}
private bool LoadScriptsFromAssembly(ResourceFile file, string path, ClientResource resource, bool shadowCopy = true)
{
lock (LoadedResources)
{
if (!IsManagedAssembly(path)) { return false; }
if (ToIgnore.Contains(file.Name)) { try { File.Delete(path); } catch { }; return false; }
Logger?.Debug($"Loading assembly {file.Name} ...");
Assembly assembly;
try
{
if (shadowCopy)
{
var temp = Path.GetTempFileName();
File.Copy(path, temp, true);
assembly = Assembly.LoadFrom(temp);
}
else
{
assembly = Assembly.LoadFrom(path);
}
}
catch (Exception ex)
{
Logger?.Error("Unable to load "+file.Name);
Logger?.Error(ex);
return false;
}
return LoadScriptsFromAssembly(file, assembly, path, resource);
}
}
private bool LoadScriptsFromAssembly(ResourceFile rfile, Assembly assembly, string filename, ClientResource toload)
{
int count = 0;
try
{
// Find all script types in the assembly
foreach (var type in assembly.GetTypes().Where(x => IsSubclassOf(x, BaseScriptType)))
{
ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
if (constructor != null && constructor.IsPublic)
{
try
{
// Invoke script constructor
var script = constructor.Invoke(null) as ClientScript;
// script.CurrentResource = toload;
script.CurrentFile=rfile;
script.CurrentResource=toload;
toload.Scripts.Add(script);
count++;
}
catch (Exception ex)
{
Logger?.Error($"Error occurred when loading script: {type.FullName}.");
Logger?.Error(ex);
}
}
else
{
Logger?.Error($"Script {type.FullName} has an invalid contructor.");
}
}
}
catch (ReflectionTypeLoadException ex)
{
Logger?.Error($"Failed to load assembly {rfile.Name}: ");
Logger?.Error(ex);
foreach (var e in ex.LoaderExceptions)
{
Logger?.Error(e);
}
return false;
}
Logger?.Info($"Loaded {count} script(s) in {rfile.Name}");
return count != 0;
}
private bool IsManagedAssembly(string filename)
{
try
{
using (Stream file = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
if (file.Length < 64)
return false;
using (BinaryReader bin = new BinaryReader(file))
{
// PE header starts at offset 0x3C (60). Its a 4 byte header.
file.Position = 0x3C;
uint offset = bin.ReadUInt32();
if (offset == 0)
offset = 0x80;
// Ensure there is at least enough room for the following structures:
// 24 byte PE Signature & Header
// 28 byte Standard Fields (24 bytes for PE32+)
// 68 byte NT Fields (88 bytes for PE32+)
// >= 128 byte Data Dictionary Table
if (offset > file.Length - 256)
return false;
// Check the PE signature. Should equal 'PE\0\0'.
file.Position = offset;
if (bin.ReadUInt32() != 0x00004550)
return false;
// Read PE magic number from Standard Fields to determine format.
file.Position += 20;
var peFormat = bin.ReadUInt16();
if (peFormat != 0x10b /* PE32 */ && peFormat != 0x20b /* PE32Plus */)
return false;
// Read the 15th Data Dictionary RVA field which contains the CLI header RVA.
// When this is non-zero then the file contains CLI data otherwise not.
file.Position = offset + (peFormat == 0x10b ? 232 : 248);
return bin.ReadUInt32() != 0;
}
}
}
catch
{
// This is likely not a valid assembly if any IO exceptions occur during reading
return false;
}
}
private bool IsSubclassOf(Type type, string baseTypeName)
{
for (Type t = type.BaseType; t != null; t = t.BaseType)
if (t.FullName == baseTypeName)
return true;
return false;
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using RageCoop.Core;
namespace RageCoop.Client
{
internal class Security
{
public RSA ServerRSA { get; set; }
public Aes ClientAes { get; set; }=Aes.Create();
private Logger Logger;
public Security(Logger logger)
{
Logger = logger;
ClientAes.GenerateKey();
ClientAes.GenerateIV();
}
public void GetSymmetricKeysCrypted(out byte[] cryptedKey,out byte[] cryptedIV)
{
// Logger?.Debug($"Aes.Key:{ClientAes.Key.Dump()}, Aes.IV:{ClientAes.IV.Dump()}");
cryptedKey =ServerRSA.Encrypt(ClientAes.Key, RSAEncryptionPadding.Pkcs1);
cryptedIV =ServerRSA.Encrypt(ClientAes.IV,RSAEncryptionPadding.Pkcs1);
}
public byte[] Encrypt(byte[] data)
{
return new CryptoStream(new MemoryStream(data), ClientAes.CreateEncryptor(), CryptoStreamMode.Read).ReadToEnd();
}
public void SetServerPublicKey(byte[] modulus,byte[] exponent)
{
var para = new RSAParameters();
para.Modulus = modulus;
para.Exponent = exponent;
ServerRSA=RSA.Create(para);
}
public void Regen()
{
ClientAes=Aes.Create();
ClientAes.GenerateKey();
ClientAes.GenerateIV();
}
}
}

View File

@ -12,13 +12,17 @@ namespace RageCoop.Client
/// </summary>
public string Username { get; set; } = "Player";
/// <summary>
/// The password used to authenticate when connecting to a server.
/// </summary>
public string Password { get; set; } = "";
/// <summary>
/// Don't use it!
/// </summary>
public string LastServerAddress { get; set; } = "127.0.0.1:4499";
/// <summary>
/// Don't use it!
/// </summary>
public string MasterServer { get; set; } = "https://ragecoop.online/gtav/servers";
public string MasterServer { get; set; } = "https://masterserver.ragecoop.online/";
/// <summary>
/// Don't use it!
/// </summary>
@ -55,6 +59,13 @@ namespace RageCoop.Client
/// </summary>
public int WorldVehicleSoftLimit { get; set; } = 35;
/// <summary>
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary>
public int WorldPedSoftLimit { get; set; } = 50;
/// <summary>
/// The directory where log and resources downloaded from server will be placed.
/// </summary>
public string DataDirectory { get; set; } = "Scripts\\RageCoop\\Data";
}
}

View File

@ -8,13 +8,16 @@ using GTA.Math;
namespace RageCoop.Client
{
/// <summary>
///
/// </summary>
public abstract class SyncedEntity
{
/// <summary>
/// Indicates whether the current player is responsible for syncing this entity.
/// </summary>
public bool IsMine
public bool IsLocal
{
get
{
@ -22,20 +25,30 @@ namespace RageCoop.Client
}
}
public int ID { get; set; }
public int OwnerID { get;set; }
/// <summary>
/// Network ID for this entity
/// </summary>
public int ID { get;internal set; }
/// <summary>
///
/// </summary>
public int OwnerID { get; internal set; }
/// <summary>
///
/// </summary>
public bool IsOutOfSync
{
get
{
return Main.Ticked-LastSynced>200;
return Main.Ticked-LastSynced>200 && ID!=0;
}
}
public bool IsReady
internal bool IsReady
{
get {return !(LastSynced==0||LastStateSynced==0);}
get {return (LastSynced>0||LastStateSynced==0);}
}
public bool NeedUpdate
internal bool IsInvincible { get; set; } = false;
internal bool NeedUpdate
{
get { return LastSynced>LastUpdated; }
}
@ -54,10 +67,20 @@ namespace RageCoop.Client
public ulong LastUpdated { get; set; } = 0;
#endregion
public Vector3 Position { get; set; }
public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public abstract void Update();
/// <summary>
///
/// </summary>
internal protected bool _lastFrozen=false;
internal int ModelHash { get; set; }
internal Vector3 Position { get; set; }
internal Vector3 Rotation { get; set; }
internal Quaternion Quaternion { get; set; }
internal Vector3 Velocity { get; set; }
internal abstract void Update();
internal void PauseUpdate(ulong frames)
{
LastUpdated=Main.Ticked+frames;
}
}
}

View File

@ -14,7 +14,7 @@ namespace RageCoop.Client
/// <summary>
/// ?
/// </summary>
public partial class SyncedPed:SyncedEntity
public class SyncedPed:SyncedEntity
{
#region CONSTRUCTORS
@ -22,7 +22,7 @@ namespace RageCoop.Client
/// Create a local entity (outgoing sync)
/// </summary>
/// <param name="p"></param>
public SyncedPed(Ped p)
internal SyncedPed(Ped p)
{
ID=EntityPool.RequestNewID();
p.CanWrithe=false;
@ -30,20 +30,27 @@ namespace RageCoop.Client
MainPed=p;
OwnerID=Main.LocalPlayerID;
Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
// MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableMelee, true);
}
/// <summary>
/// Create an empty character with ID
/// </summary>
public SyncedPed(int id)
internal SyncedPed(int id)
{
ID=id;
LastSynced=Main.Ticked;
}
#endregion
#region PLAYER -- ONLY
public string Username = "N/A";
public Blip PedBlip = null;
internal Blip PedBlip = null;
internal BlipColor BlipColor = (BlipColor)255;
internal BlipSprite BlipSprite = (BlipSprite)0;
internal float BlipScale=1;
internal PlayerData Player;
#endregion
/// <summary>
@ -54,45 +61,32 @@ namespace RageCoop.Client
/// real entity
/// </summary>
public Ped MainPed { get; internal set; }
/// <summary>
/// The latest character health (may not have been applied yet)
/// </summary>
public int Health { get; internal set; }
public bool _lastEnteringVehicle=false;
public bool _lastSittingInVehicle=false;
internal int Health { get; set; }
internal bool IsInStealthMode { get; set; }
internal byte WeaponTint { get; set; }
internal bool _lastEnteringVehicle=false;
internal bool _lastSittingInVehicle=false;
private bool _lastRagdoll=false;
private ulong _lastRagdollTime=0;
private bool _lastInCover = false;
/// <summary>
/// The latest character model hash (may not have been applied yet)
/// </summary>
public int ModelHash
private byte[] _lastClothes = null;
internal byte[] Clothes { get; set; }
internal float Heading { get; set; }
internal Vector3 RotationVelocity { get; set; }
internal Vector3 AimCoords { get; set; }
private WeaponAsset WeaponAsset { get; set; }
internal override void Update()
{
get;set;
}
private Dictionary<byte, short> _lastClothes = null;
public Dictionary<byte, short> Clothes { get; set; }
public float Heading { get; set; }
public Vector3 RotationVelocity { get; set; }
public Vector3 AimCoords { get; set; }
public override void Update()
{
if (IsPlayer && PedBlip!=null)
if (IsPlayer)
{
if (Username=="N/A")
if (Player==null)
{
var p = PlayerList.GetPlayer(ID);
if (p!=null)
{
Username=p.Username;
PedBlip.Name=Username;
}
Player = PlayerList.GetPlayer(this);
return;
}
RenderNameTag();
}
@ -107,10 +101,6 @@ namespace RageCoop.Client
}
bool characterExist = (MainPed != null) && MainPed.Exists();
if (!characterExist)
@ -118,7 +108,27 @@ namespace RageCoop.Client
CreateCharacter();
return;
}
if (((byte)BlipColor==255) && (PedBlip!=null))
{
PedBlip.Delete();
PedBlip=null;
}
else if (((byte)BlipColor != 255) && PedBlip==null)
{
PedBlip=MainPed.AddBlip();
if (IsPlayer)
{
Main.Logger.Debug("blip:"+Player.Username);
PedBlip.Name=Player.Username;
}
PedBlip.Color=BlipColor;
PedBlip.Sprite=BlipSprite;
PedBlip.Scale=BlipScale;
}
// Need to update state
@ -131,10 +141,23 @@ namespace RageCoop.Client
return;
}
if (!Clothes.Compare(_lastClothes))
if (!Clothes.SequenceEqual(_lastClothes))
{
SetClothes();
}
var b = MainPed.AttachedBlip;
if (b==null || b.Color!=BlipColor || b.Sprite!=BlipSprite)
{
PedBlip?.Delete();
PedBlip=MainPed.AddBlip();
PedBlip.Color=BlipColor;
PedBlip.Sprite =BlipSprite;
if (IsPlayer)
{
Main.Logger.Debug("blip:"+Player.Username);
b.Name=Player.Username;
}
}
CheckCurrentWeapon();
}
@ -162,7 +185,6 @@ namespace RageCoop.Client
if (Health <= 0 && !MainPed.IsDead)
{
MainPed.IsInvincible = false;
Main.Logger.Debug($"Killing ped {ID}. Reason:PedDied");
MainPed.Kill();
return;
}
@ -183,12 +205,12 @@ namespace RageCoop.Client
private void RenderNameTag()
{
if (!IsPlayer || !MainPed.IsVisible || !MainPed.IsInRange(Game.Player.Character.Position, 20f))
if (!Player.DisplayNameTag || (MainPed==null) || !MainPed.IsVisible || !MainPed.IsInRange(Game.Player.Character.Position, 20f))
{
return;
}
string renderText = IsOutOfSync ? "~r~AFK" : Username;
string renderText = IsOutOfSync ? "~r~AFK" : Player.Username;
Vector3 targetPos = MainPed.Bones[Bone.IKHead].Position + new Vector3(0, 0, 0.35f);
Function.Call(Hash.SET_DRAW_ORIGIN, targetPos.X, targetPos.Y, targetPos.Z, 0);
@ -240,33 +262,38 @@ namespace RageCoop.Client
}
Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
Function.Call(Hash.SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
Function.Call(Hash.SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
MainPed.BlockPermanentEvents = true;
MainPed.CanWrithe=false;
MainPed.CanBeDraggedOutOfVehicle = true;
MainPed.IsOnlyDamagedByPlayer = false;
MainPed.RelationshipGroup=Main.SyncedPedsGroup;
if (IsPlayer)
{
MainPed.IsInvincible=true;
}
MainPed.IsFireProof=false;
MainPed.IsExplosionProof=false;
Function.Call(Hash.SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
Function.Call(Hash.SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater,false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableExplosionReactions, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_AvoidTearGas, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableShockingEvents, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
SetClothes();
if (IsPlayer)
{
// Add a new blip for the ped
PedBlip=MainPed.AddBlip();
MainPed.AttachedBlip.Color = BlipColor.White;
MainPed.AttachedBlip.Scale = 0.8f;
MainPed.AttachedBlip.Name =Username;
MainPed.IsInvincible=true;
}
if (IsInvincible) { MainPed.IsInvincible=true; }
// Add to EntityPool so this Character can be accessed by handle.
EntityPool.Add(this);
@ -274,37 +301,36 @@ namespace RageCoop.Client
private void SetClothes()
{
foreach (KeyValuePair<byte, short> cloth in Clothes)
for (byte i = 0; i < 12; i++)
{
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, MainPed.Handle, cloth.Key, cloth.Value, 0, 0);
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i], (int)Clothes[i+12], (int)Clothes[i+24]);
}
_lastClothes = Clothes;
}
#region Onfoot
#region ONFOOT
#region -- VARIABLES --
/// <summary>
/// The latest character rotation (may not have been applied yet)
/// </summary>
public byte Speed { get; set; }
private bool _lastIsJumping = false;
public bool IsJumping { get; set; }
public bool IsOnLadder { get; set; }
public bool IsVaulting { get; set; }
public bool IsInParachuteFreeFall { get; set; }
public bool IsParachuteOpen { get; set; }
public Prop ParachuteProp { get; set; } = null;
public bool IsRagdoll { get; set; }
public bool IsOnFire { get; set; }
public bool IsAiming { get; set; }
public bool IsReloading { get; set; }
public bool IsInCover { get; set; }
public uint CurrentWeaponHash { get; set; }
internal bool IsJumping { get; set; }
internal bool IsOnLadder { get; set; }
internal bool IsVaulting { get; set; }
internal bool IsInParachuteFreeFall { get; set; }
internal bool IsParachuteOpen { get; set; }
internal Prop ParachuteProp { get; set; } = null;
internal bool IsRagdoll { get; set; }
internal bool IsOnFire { get; set; }
internal bool IsAiming { get; set; }
internal bool IsReloading { get; set; }
internal bool IsInCover { get; set; }
internal uint CurrentWeaponHash { get; set; }
private Dictionary<uint, bool> _lastWeaponComponents = null;
public Dictionary<uint, bool> WeaponComponents { get; set; } = null;
internal Dictionary<uint, bool> WeaponComponents { get; set; } = null;
private int _lastWeaponObj = 0;
#endregion
private bool _isPlayingAnimation = false;
private string[] _currentAnimation = new string[2] { "", "" };
private void DisplayOnFoot()
@ -514,12 +540,6 @@ namespace RageCoop.Client
*/
SmoothTransition();
}
else if (_currentAnimation[1] == "reload_aim")
{
MainPed.Task.ClearAnimation(_currentAnimation[0], _currentAnimation[1]);
_isPlayingAnimation = false;
_currentAnimation = new string[2] { "", "" };
}
else if (IsInCover)
{
@ -559,21 +579,15 @@ namespace RageCoop.Client
WalkTo();
}
}
private void DisplayInVehicle()
{
if (MainPed.IsOnTurretSeat())
{
Function.Call(Hash.SET_VEHICLE_TURRET_SPEED_THIS_FRAME, MainPed.CurrentVehicle, 100);
Function.Call(Hash.TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y, AimCoords.Z);
}
}
#region WEAPON
WeaponHash appliedWeaponhash = WeaponHash.Unarmed;
private void CheckCurrentWeapon()
{
if (MainPed.Weapons.Current.Hash != (WeaponHash)CurrentWeaponHash || !WeaponComponents.Compare(_lastWeaponComponents))
{
if (WeaponAsset!=null) { WeaponAsset.MarkAsNoLongerNeeded(); }
WeaponAsset=new WeaponAsset(CurrentWeaponHash);
if (!WeaponAsset.IsLoaded) { WeaponAsset.Request(); }
MainPed.Weapons.RemoveAll();
_lastWeaponObj = Function.Call<int>(Hash.CREATE_WEAPON_OBJECT, CurrentWeaponHash, -1, Position.X, Position.Y, Position.Z, true, 0, 0);
@ -595,9 +609,12 @@ namespace RageCoop.Client
Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, _lastWeaponObj, MainPed.Handle);
}
_lastWeaponComponents = WeaponComponents;
}
if (Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX,MainPed,CurrentWeaponHash)!=WeaponTint)
{
Function.Call<int>(Hash.SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeaponHash, WeaponTint);
}
}
private void DisplayAiming()
@ -676,7 +693,7 @@ namespace RageCoop.Client
MainPed.PositionNoOffset=Position;
return;
}
var f = dist*(Position+SyncParameters.PositioinPrediction*Velocity-MainPed.Position)+(Velocity-MainPed.Velocity)*0.2f;
var f = dist*(Position+SyncParameters.PositioinPredictionDefault*Velocity-MainPed.Position)+(Velocity-MainPed.Velocity)*0.2f;
if (!localRagdoll) { f*=5; }
if (!(localRagdoll|| MainPed.IsDead))
{
@ -706,5 +723,19 @@ namespace RageCoop.Client
return anim;
}
#endregion
private void DisplayInVehicle()
{
if (MainPed.IsOnTurretSeat())
{
Function.Call(Hash.SET_VEHICLE_TURRET_SPEED_THIS_FRAME, MainPed.CurrentVehicle, 100);
Function.Call(Hash.TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y, AimCoords.Z);
}
/*
Function.Call(Hash.TASK_SWEEP_AIM_ENTITY,P, "random@paparazzi@pap_anims", "sweep_low", "sweep_med", "sweep_high", -1,V, 1.57f, 0.25f);
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, P,true, 0);
return Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, P);
*/
}
}
}

View File

@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks;
using GTA;
using GTA.Math;
using RageCoop.Core;
namespace RageCoop.Client
{
@ -13,39 +14,45 @@ namespace RageCoop.Client
public SyncedProjectile(Projectile p)
{
ID=EntityPool.RequestNewID();
IsMine=true;
MainProjectile = p;
Origin=p.Position;
var shooter = EntityPool.GetPedByHandle(p.Owner.Handle);
if(shooter != null)
{
ShooterID=shooter.ID;
}
else
var shooter = EntityPool.GetPedByHandle((p.Owner?.Handle).GetValueOrDefault());
if (shooter==null)
{
// Owner will be the vehicle if projectile is shot with a vehicle
var shooterVeh = EntityPool.GetVehicleByHandle(p.Owner.Handle);
var shooterVeh = EntityPool.GetVehicleByHandle((p.Owner?.Handle).GetValueOrDefault());
if (shooterVeh!=null && shooterVeh.MainVehicle.Driver!=null)
{
ShooterID=shooterVeh.MainVehicle.Driver.GetSyncEntity().ID;
shooter=shooterVeh.MainVehicle.Driver?.GetSyncEntity();
}
else
{
Main.Logger.Warning($"Could not find owner for projectile, owner handle:{p.Owner.Handle}");
Main.Logger.Warning($"Could not find owner for projectile:{Hash}");
}
}
if(shooter != null)
{
if (shooter.MainPed!=null && (p.AttachedEntity==shooter.MainPed.Weapons.CurrentWeaponObject || p.AttachedEntity== shooter.MainPed))
{
// Reloading
IsValid=false;
}
ShooterID=shooter.ID;
IsLocal=shooter.IsLocal;
}
}
public SyncedProjectile(int id)
{
ID= id;
IsMine=false;
IsLocal=false;
}
public new bool IsMine { get; private set; }
public bool IsValid { get; private set; } = true;
public new bool IsLocal { get; private set; } = false;
public bool Exploded { get; set; } = false;
public Projectile MainProjectile { get; set; }
public int ShooterID { get; set; }
private SyncedPed Shooter { get;set; }
public Vector3 Origin { get; set; }
/// <summary>
@ -54,7 +61,7 @@ namespace RageCoop.Client
private new int OwnerID{ set { } }
public WeaponHash Hash { get; set; }
private WeaponAsset Asset { get; set; }
public override void Update()
internal override void Update()
{
// Skip update if no new sync message has arrived.
@ -65,8 +72,7 @@ namespace RageCoop.Client
CreateProjectile();
return;
}
MainProjectile.PositionNoOffset=Position+Velocity*Networking.Latency;
MainProjectile.Velocity=Velocity;
MainProjectile.Velocity=Velocity+(Position+Networking.Latency*Velocity-MainProjectile.Position);
MainProjectile.Rotation=Rotation;
LastUpdated=Main.Ticked;
}
@ -75,9 +81,17 @@ namespace RageCoop.Client
{
Asset=new WeaponAsset(Hash);
if (!Asset.IsLoaded) { Asset.Request(); }
World.ShootBullet(Position,Position+Velocity,EntityPool.GetPedByID(ShooterID)?.MainPed,Asset,0);
World.ShootBullet(Position,Position+Velocity,(Shooter=EntityPool.GetPedByID(ShooterID))?.MainPed,Asset,0);
var ps = World.GetAllProjectiles();
MainProjectile=ps[ps.Length-1];
if (Hash==(WeaponHash)VehicleWeaponHash.Tank)
{
var v = Shooter?.MainPed?.CurrentVehicle;
if (v!=null)
{
World.CreateParticleEffectNonLooped(SyncEvents.CorePFXAsset, "muz_tank", v.GetMuzzleInfo().Position, v.Bones[35].ForwardVector.ToEulerRotation(v.Bones[35].UpVector), 1);
}
}
EntityPool.Add(this);
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA.Native;
using GTA;
namespace RageCoop.Client
{
/// <summary>
/// Synchronized prop, mostly owned by server
/// </summary>
public class SyncedProp : SyncedEntity
{
internal SyncedProp(int id)
{
ID= id;
}
/// <summary>
/// The real entity
/// </summary>
public Prop MainProp { get; set; }
internal new int OwnerID { get
{
// alwayse owned by server
return 0;
} }
internal override void Update()
{
if (!NeedUpdate) { return; }
if (MainProp== null || !MainProp.Exists())
{
MainProp=World.CreateProp(ModelHash,Position,Rotation,false,false);
MainProp.IsInvincible=true;
}
MainProp.Position=Position;
MainProp.Rotation=Rotation;
MainProp.SetFrozen(true);
LastUpdated=Main.Ticked;
}
}
}

View File

@ -11,6 +11,9 @@ using RageCoop.Core;
namespace RageCoop.Client
{
/// <summary>
/// A synchronized vehicle instance
/// </summary>
public class SyncedVehicle : SyncedEntity
{
@ -19,8 +22,8 @@ namespace RageCoop.Client
/// <summary>
/// Create a local entity (outgoing sync)
/// </summary>
/// <param name="p"></param>
public SyncedVehicle(Vehicle v)
/// <param name="v"></param>
internal SyncedVehicle(Vehicle v)
{
ID=EntityPool.RequestNewID();
@ -33,11 +36,11 @@ namespace RageCoop.Client
/// <summary>
/// Create an empty VehicleEntity
/// </summary>
public SyncedVehicle()
internal SyncedVehicle()
{
}
public SyncedVehicle(int id)
internal SyncedVehicle(int id)
{
ID=id;
LastSynced=Main.Ticked;
@ -46,63 +49,71 @@ namespace RageCoop.Client
/// <summary>
/// VehicleSeat,ID
/// </summary>
public Vehicle MainVehicle { get; set; }
public Vehicle MainVehicle { get;internal set; }
#region LAST STATE STORE
#region LAST STATE
private ulong _vehicleStopTime { get; set; }
private byte[] _lastVehicleColors = new byte[] { 0, 0 };
private Dictionary<int, int> _lastVehicleMods = new Dictionary<int, int>();
#endregion
#region -- CRITICAL STUFF --
public Vector3 RotationVelocity { get; set; }
public float SteeringAngle { get; set; }
public float ThrottlePower { get; set; }
public float BrakePower { get; set; }
internal Vector3 RotationVelocity { get; set; }
internal float SteeringAngle { get; set; }
internal float ThrottlePower { get; set; }
internal float BrakePower { get; set; }
internal float DeluxoWingRatio { get; set; } = -1;
internal bool IsFlipped
{
get
{
return (Quaternion*Vector3.RelativeTop).Z <(Quaternion*Vector3.RelativeBottom).Z;
}
}
#endregion
#region -- VEHICLE STATE --
public bool EngineRunning { get; set; }
internal VehicleDataFlags Flags { get; set; }
internal bool EngineRunning { get; set; }
private bool _lastTransformed = false;
public bool Transformed { get; set; }
internal bool Transformed { get; set; }
private bool _lastHornActive = false;
public bool HornActive { get; set; }
public bool LightsOn { get; set; }
public bool BrakeLightsOn { get; set; } = false;
public bool HighBeamsOn { get; set; }
public byte LandingGear { get; set; }
public bool RoofOpened { get; set; }
public bool SireneActive { get; set; }
public VehicleDamageModel DamageModel { get; set; }
public int ModelHash { get; set; }
public byte[] Colors { get; set; }
public Dictionary<int, int> Mods { get; set; }
public bool IsDead { get; set; }
public float EngineHealth { get; set; }
public VehicleLockStatus LockStatus{get;set;}
internal bool HornActive { get; set; }
internal bool LightsOn { get; set; }
internal bool BrakeLightsOn { get; set; } = false;
internal bool HighBeamsOn { get; set; }
internal byte LandingGear { get; set; }
internal VehicleRoofState RoofState { get; set; }
internal bool SireneActive { get; set; }
internal VehicleDamageModel DamageModel { get; set; }
internal byte[] Colors { get; set; }
internal Dictionary<int, int> Mods { get; set; }
internal bool IsDead { get; set; }
internal float EngineHealth { get; set; }
internal VehicleLockStatus LockStatus{get;set;}
/// <summary>
/// VehicleSeat,PedID
/// </summary>
public Dictionary<VehicleSeat, SyncedPed> Passengers { get; set; }
private long _lastPositionCalibrated { get; set; }
internal Dictionary<VehicleSeat, SyncedPed> Passengers { get; set; }
internal byte RadioStation = 255;
internal string LicensePlate { get; set; }
internal int _lastLivery = -1;
internal int Livery { get; set; } = -1;
internal bool _checkSeat { get; set; } = true;
#endregion
public override void Update()
internal override void Update()
{
#region -- INITIAL CHECK --
// Check if all data avalible
if(!IsReady) { return; }
// Skip update if no new sync message has arrived.
if (!NeedUpdate) { return; }
#endregion
#region -- CHECK EXISTENCE --
if ((MainVehicle == null) || (!MainVehicle.Exists()) || (MainVehicle.Model.Hash != ModelHash))
@ -111,77 +122,80 @@ namespace RageCoop.Client
return;
}
#endregion
#region -- SYNC CRITICAL --
if (SteeringAngle != MainVehicle.SteeringAngle)
{
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * SteeringAngle);
}
if (MainVehicle.ThrottlePower!=ThrottlePower)
{
MainVehicle.ThrottlePower=ThrottlePower;
}
if (MainVehicle.BrakePower!=BrakePower)
{
MainVehicle.BrakePower=BrakePower;
}
MainVehicle.ThrottlePower=ThrottlePower;
MainVehicle.BrakePower=BrakePower;
if (MainVehicle.Position.DistanceTo(Position)<5)
{
MainVehicle.Velocity = Velocity+5*(Position+Velocity*SyncParameters.PositioinPrediction - MainVehicle.Position);
_lastPositionCalibrated=Main.Counter.ElapsedMilliseconds;
MainVehicle.Velocity = Velocity+5*(Position+Velocity*SyncParameters.PositioinPredictionDefault - MainVehicle.Position);
if (IsFlipped)
{
MainVehicle.Quaternion=Quaternion.Slerp(MainVehicle.Quaternion, Quaternion, 0.5f);
MainVehicle.RotationVelocity=RotationVelocity;
}
else
{
Vector3 cali = GetCalibrationRotation();
if (cali.Length()<50)
{
MainVehicle.RotationVelocity = RotationVelocity+cali*0.2f;
}
else
{
MainVehicle.Quaternion=Quaternion;
MainVehicle.RotationVelocity=RotationVelocity;
}
}
}
else
{
MainVehicle.Position=Position;
MainVehicle.Velocity=Velocity;
MainVehicle.Quaternion=Quaternion;
}
Vector3 r = GetCalibrationRotation();
if (r.Length() < 20f)
if (DeluxoWingRatio!=-1)
{
MainVehicle.RotationVelocity = r * 0.15f + RotationVelocity;
MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
}
else
{
MainVehicle.Rotation = Rotation;
MainVehicle.RotationVelocity = RotationVelocity;
}
_vehicleStopTime = Util.GetTickCount64();
#endregion
if (LastStateSynced>LastUpdated)
{
#region -- SYNC STATE --
#region -- PASSENGER SYNC --
// check passengers (and driver).
var currentPassengers = MainVehicle.GetPassengers();
lock (Passengers)
if (_checkSeat)
{
for (int i = -1; i<MainVehicle.PassengerCapacity; i++)
{
VehicleSeat seat = (VehicleSeat)i;
if (Passengers.ContainsKey(seat))
{
SyncedPed c = Passengers[seat];
if ((c!=null)&&c.MainPed!=null&&(!currentPassengers.ContainsKey(i))&&(!c.MainPed.IsBeingJacked)) {
Passengers[seat].MainPed.SetIntoVehicle(MainVehicle, seat);
}
}
else if (!MainVehicle.IsSeatFree(seat))
var currentPassengers = MainVehicle.GetPassengers();
lock (Passengers)
{
for (int i = -1; i<MainVehicle.PassengerCapacity; i++)
{
if (seat==VehicleSeat.Driver &&MainVehicle.Driver.IsSittingInVehicle())
VehicleSeat seat = (VehicleSeat)i;
if (Passengers.ContainsKey(seat))
{
MainVehicle.Driver.Task.WarpOutOfVehicle(MainVehicle);
SyncedPed c = Passengers[seat];
if (c?.ID==Main.LocalPlayerID && (RadioStation!=Function.Call<int>(Hash.GET_PLAYER_RADIO_STATION_INDEX)))
{
Util.SetPlayerRadioIndex(RadioStation);
}
if (c?.MainPed!=null&&(!currentPassengers.ContainsKey(i))&&(!c.MainPed.IsBeingJacked)&&(!c.MainPed.IsTaskActive(TaskType.CTaskExitVehicleSeat)))
{
Passengers[seat].MainPed.SetIntoVehicle(MainVehicle, seat);
}
}
else
else if (!MainVehicle.IsSeatFree(seat))
{
var p = MainVehicle.Passengers.Where(x => x.SeatIndex==seat).FirstOrDefault();
if ((p!=null)&&p.IsSittingInVehicle())
var p = MainVehicle.Occupants.Where(x => x.SeatIndex==seat).FirstOrDefault();
if ((p!=null)&& !p.IsTaskActive(TaskType.CTaskLeaveAnyCar))
{
p.Task.WarpOutOfVehicle(MainVehicle);
}
@ -289,13 +303,9 @@ namespace RageCoop.Client
MainVehicle.SoundHorn(1);
}
if (MainVehicle.HasRoof)
if (MainVehicle.HasRoof && MainVehicle.RoofState!=RoofState)
{
bool roofOpened = MainVehicle.RoofState == VehicleRoofState.Opened || MainVehicle.RoofState == VehicleRoofState.Opening;
if (roofOpened != RoofOpened)
{
MainVehicle.RoofState = RoofOpened ? VehicleRoofState.Opening : VehicleRoofState.Closing;
}
MainVehicle.RoofState=RoofState;
}
Function.Call(Hash.SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
@ -303,15 +313,41 @@ namespace RageCoop.Client
}
MainVehicle.LockStatus=LockStatus;
if (Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
{
if (!MainVehicle.IsDeluxoHovering())
{
MainVehicle.SetDeluxoHoverState(true);
}
}
else if(ModelHash==1483171323)
{
if (MainVehicle.IsDeluxoHovering())
{
MainVehicle.SetDeluxoHoverState(false);
}
}
if (Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle)!=LicensePlate)
{
Function.Call(Hash.SET_VEHICLE_NUMBER_PLATE_TEXT,MainVehicle,LicensePlate);
}
if (_lastLivery!=Livery)
{
Function.Call(Hash.SET_VEHICLE_LIVERY, MainVehicle, Livery);
_lastLivery=Livery;
}
#endregion
}
LastUpdated=Main.Ticked;
}
private Vector3 GetCalibrationRotation()
{
var r = Rotation-MainVehicle.Rotation;
var rot=Quaternion.LookRotation(Quaternion*Vector3.RelativeFront, Quaternion*Vector3.RelativeTop).ToEulerAngles();
var curRot=Quaternion.LookRotation(MainVehicle.Quaternion*Vector3.RelativeFront, MainVehicle.Quaternion*Vector3.RelativeTop).ToEulerAngles();
var r = (rot-curRot).ToDegree();
if (r.X>180) { r.X=r.X-360; }
else if(r.X<-180) { r.X=360+r.X; }
@ -321,6 +357,7 @@ namespace RageCoop.Client
if (r.Z>180) { r.Z=r.Z-360; }
else if (r.Z<-180) { r.Z=360+r.Z; }
return r;
}
private void CreateVehicle()
{
@ -336,16 +373,12 @@ namespace RageCoop.Client
{
EntityPool.Add( this);
}
MainVehicle.Rotation = Rotation;
MainVehicle.Quaternion = Quaternion;
if (MainVehicle.HasRoof)
{
bool roofOpened = MainVehicle.RoofState == VehicleRoofState.Opened || MainVehicle.RoofState == VehicleRoofState.Opening;
if (roofOpened != RoofOpened)
{
MainVehicle.RoofState = RoofOpened ? VehicleRoofState.Opened : VehicleRoofState.Closed;
}
MainVehicle.RoofState=RoofState;
}
if (IsInvincible) { MainVehicle.IsInvincible=true; }
vehicleModel.MarkAsNoLongerNeeded();
}
#region -- PEDALING --
@ -385,7 +418,9 @@ namespace RageCoop.Client
}
#endregion
#region OUTGOING
internal float LastNozzleAngle { get; set; }
#endregion
}
}

View File

@ -5,6 +5,7 @@ using GTA.Native;
using RageCoop.Core;
using System.Collections.Generic;
using System.Linq;
using RageCoop.Client.Scripting;
using System.Text;
using System.Diagnostics;
using System.Threading.Tasks;
@ -14,6 +15,7 @@ namespace RageCoop.Client
{
internal class EntityPool
{
private static bool _trafficSpawning=true;
public static object PedsLock = new object();
private static Dictionary<int, SyncedPed> ID_Peds = new Dictionary<int, SyncedPed>();
public static int CharactersCount { get { return ID_Peds.Count; } }
@ -35,6 +37,12 @@ namespace RageCoop.Client
private static Dictionary<int, SyncedProjectile> ID_Projectiles = new Dictionary<int, SyncedProjectile>();
private static Dictionary<int, SyncedProjectile> Handle_Projectiles = new Dictionary<int, SyncedProjectile>();
public static object PropsLock=new object();
public static Dictionary<int,SyncedProp> ServerProps=new Dictionary<int,SyncedProp>();
public static object BlipsLock = new object();
public static Dictionary<int, Blip> ServerBlips = new Dictionary<int, Blip>();
public static void Cleanup(bool keepPlayer=true,bool keepMine=true)
{
@ -64,6 +72,21 @@ namespace RageCoop.Client
}
ID_Projectiles.Clear();
Handle_Projectiles.Clear();
foreach(var p in ServerProps.Values)
{
p?.MainProp?.Delete();
}
ServerProps.Clear();
foreach(var b in ServerBlips.Values)
{
if (b.Exists())
{
b.Delete();
}
}
ServerBlips.Clear();
}
#region PEDS
@ -112,7 +135,7 @@ namespace RageCoop.Client
SyncedPed c = new SyncedPed(p);
Main.LocalPlayerID=c.OwnerID=c.ID;
Add(c);
Main.Logger.Debug($"My player ID is:{c.ID}");
Main.Logger.Debug($"Local player ID is:{c.ID}");
PlayerList.SetPlayer(c.ID, Main.Settings.Username );
return true;
}
@ -137,6 +160,10 @@ namespace RageCoop.Client
{
Handle_Peds.Add(c.MainPed.Handle, c);
}
if (c.IsLocal)
{
API.Events.InvokePedSpawned(c);
}
}
public static void RemovePed(int id,string reason="Cleanup")
{
@ -150,7 +177,7 @@ namespace RageCoop.Client
{
Handle_Peds.Remove(p.Handle);
}
Main.Logger.Debug($"Removing ped {c.ID}. Reason:{reason}");
// Main.Logger.Debug($"Removing ped {c.ID}. Reason:{reason}");
p.AttachedBlip?.Delete();
p.Kill();
p.MarkAsNoLongerNeeded();
@ -159,6 +186,10 @@ namespace RageCoop.Client
c.PedBlip?.Delete();
c.ParachuteProp?.Delete();
ID_Peds.Remove(id);
if (c.IsLocal)
{
API.Events.InvokePedDeleted(c);
}
}
}
#endregion
@ -195,6 +226,10 @@ namespace RageCoop.Client
{
Handle_Vehicles.Add(v.MainVehicle.Handle, v);
}
if (v.IsLocal)
{
API.Events.InvokeVehicleSpawned(v);
}
}
public static void RemoveVehicle(int id,string reason = "Cleanup")
{
@ -208,12 +243,13 @@ namespace RageCoop.Client
{
Handle_Vehicles.Remove(veh.Handle);
}
Main.Logger.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
// Main.Logger.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
veh.AttachedBlip?.Delete();
veh.MarkAsNoLongerNeeded();
veh.Delete();
}
ID_Vehicles.Remove(id);
if (v.IsLocal) { API.Events.InvokeVehicleDeleted(v); }
}
}
@ -226,6 +262,7 @@ namespace RageCoop.Client
}
public static void Add(SyncedProjectile p)
{
if (!p.IsValid) { return; }
if (ID_Projectiles.ContainsKey(p.ID))
{
ID_Projectiles[p.ID]=p;
@ -276,6 +313,14 @@ namespace RageCoop.Client
return ID_Projectiles.ContainsKey(id);
}
#endregion
static int vehStateIndex;
static int pedStateIndex;
static int vehStatesPerFrame;
static int pedStatesPerFrame;
static int i;
public static Ped[] allPeds=new Ped[0];
public static Vehicle[] allVehicles=new Vehicle[0];
public static Projectile[] allProjectiles=new Projectile[0];
public static void DoSync()
{
@ -283,19 +328,27 @@ namespace RageCoop.Client
PerfCounter.Restart();
Debug.TimeStamps[TimeStamp.CheckProjectiles]=PerfCounter.ElapsedTicks;
#endif
var allPeds = World.GetAllPeds();
var allVehicles=World.GetAllVehicles();
var allProjectiles=World.GetAllProjectiles();
if (Main.Settings.WorldVehicleSoftLimit>-1)
allPeds = World.GetAllPeds();
allVehicles=World.GetAllVehicles();
allProjectiles=World.GetAllProjectiles();
vehStatesPerFrame=allVehicles.Length*2/(int)Game.FPS+1;
pedStatesPerFrame=allPeds.Length*2/(int)Game.FPS+1;
if (Main.Ticked%50==0)
{
if (Main.Ticked%100==0) { if (allVehicles.Length>Main.Settings.WorldVehicleSoftLimit) { SetBudget(0); } else { SetBudget(1); } }
bool flag1 = allVehicles.Length>Main.Settings.WorldVehicleSoftLimit && Main.Settings.WorldVehicleSoftLimit>-1;
bool flag2 = allPeds.Length>Main.Settings.WorldPedSoftLimit && Main.Settings.WorldPedSoftLimit>-1;
if ((flag1||flag2) && _trafficSpawning)
{ SetBudget(0); _trafficSpawning=false; }
else if(!_trafficSpawning)
{ SetBudget(1); _trafficSpawning=true; }
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.GetAllEntities]=PerfCounter.ElapsedTicks;
#endif
#endif
lock (ProjectilesLock)
{
@ -312,12 +365,12 @@ namespace RageCoop.Client
{
// Outgoing sync
if (p.IsMine)
if (p.IsLocal)
{
if (p.MainProjectile.AttachedEntity==null)
{
/// Prevent projectiles from exploding next to vehicle
// Prevent projectiles from exploding next to vehicle
if (WeaponUtil.VehicleProjectileWeapons.Contains((VehicleWeaponHash)p.MainProjectile.WeaponHash))
{
if (p.MainProjectile.WeaponHash!=(WeaponHash)VehicleWeaponHash.Tank && p.Origin.DistanceTo(p.MainProjectile.Position)<2)
@ -346,6 +399,7 @@ namespace RageCoop.Client
}
i=-1;
lock (PedsLock)
{
@ -356,7 +410,7 @@ namespace RageCoop.Client
SyncedPed c = EntityPool.GetPedByHandle(p.Handle);
if (c==null && (p!=Game.Player.Character))
{
Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
// Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
c=new SyncedPed(p);
EntityPool.Add(c);
@ -368,9 +422,15 @@ namespace RageCoop.Client
Debug.TimeStamps[TimeStamp.AddPeds]=PerfCounter.ElapsedTicks;
#endif
foreach (SyncedPed c in ID_Peds.Values.ToArray())
var ps = ID_Peds.Values.ToArray();
pedStateIndex+=pedStatesPerFrame;
if (pedStateIndex>=ps.Length)
{
pedStateIndex=0;
}
foreach (SyncedPed c in ps)
{
i++;
if ((c.MainPed!=null)&&(!c.MainPed.Exists()))
{
EntityPool.RemovePed(c.ID, "non-existent");
@ -378,7 +438,7 @@ namespace RageCoop.Client
}
// Outgoing sync
if (c.IsMine)
if (c.IsLocal)
{
#if BENCHMARK
var start = PerfCounter2.ElapsedTicks;
@ -386,14 +446,13 @@ namespace RageCoop.Client
// event check
SyncEvents.Check(c);
if (Main.Ticked%20==0)
Networking.SendPed(c);
// Send state
if ((i-pedStateIndex)<pedStatesPerFrame)
{
Networking.SendPedState(c);
}
else
{
Networking.SendPed(c);
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.SendPed]=PerfCounter2.ElapsedTicks-start;
@ -420,6 +479,9 @@ namespace RageCoop.Client
Debug.TimeStamps[TimeStamp.PedTotal]=PerfCounter.ElapsedTicks;
#endif
}
i=-1;
lock (VehiclesLock)
{
@ -427,7 +489,7 @@ namespace RageCoop.Client
{
if (!Handle_Vehicles.ContainsKey(veh.Handle))
{
Main.Logger.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
// Main.Logger.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
EntityPool.Add(new SyncedVehicle(veh));
@ -438,8 +500,15 @@ namespace RageCoop.Client
Debug.TimeStamps[TimeStamp.AddVehicles]=PerfCounter.ElapsedTicks;
#endif
foreach (SyncedVehicle v in ID_Vehicles.Values.ToArray())
var vs = ID_Vehicles.Values.ToArray();
vehStateIndex+=vehStatesPerFrame;
if (vehStateIndex>=vs.Length)
{
vehStateIndex=0;
}
foreach (SyncedVehicle v in vs)
{
i++;
if ((v.MainVehicle!=null)&&(!v.MainVehicle.Exists()))
{
EntityPool.RemoveVehicle(v.ID,"non-existent");
@ -447,16 +516,17 @@ namespace RageCoop.Client
}
// Outgoing sync
if (v.IsMine)
if (v.IsLocal)
{
if (Main.Ticked%20==0)
if (!v.MainVehicle.IsVisible) { continue; }
SyncEvents.Check(v);
Networking.SendVehicle(v);
// Send state
if ((i-vehStateIndex)<vehStatesPerFrame)
{
Networking.SendVehicleState(v);
}
else
{
Networking.SendVehicle(v);
}
}
@ -473,6 +543,7 @@ namespace RageCoop.Client
}
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.VehicleTotal]=PerfCounter.ElapsedTicks;
#endif
@ -529,6 +600,10 @@ namespace RageCoop.Client
s+="\nHandle_Peds: "+Handle_Peds.Count;
s+="\nID_Vehicles: "+ID_Vehicles.Count;
s+="\nHandle_Vehicles: "+Handle_Vehicles.Count;
s+="\nID_Projectiles: "+ID_Projectiles.Count;
s+="\nHandle_Projectiles: "+Handle_Projectiles.Count;
s+="\npedStatesPerFrame:"+pedStatesPerFrame;
s+="\nvehStatesPerFrame:"+vehStatesPerFrame;
return s;
}
public static class ThreadSafe

View File

@ -12,7 +12,6 @@ using System.Threading;
namespace RageCoop.Client {
internal static class SyncEvents
{
#region TRIGGER
public static void TriggerPedKilled(SyncedPed victim)
{
@ -34,9 +33,9 @@ namespace RageCoop.Client {
{
if (seat==VehicleSeat.Driver)
{
TriggerChangeOwner(veh, c.ID);
veh.OwnerID=Main.LocalPlayerID;
veh.LastSynced=Main.Ticked;
TriggerChangeOwner(veh, c.ID);
}
Networking.Send(new Packets.EnteredVehicle()
{
@ -59,15 +58,8 @@ namespace RageCoop.Client {
public static void TriggerBulletShot(uint hash,SyncedPed owner,Vector3 impactPosition)
{
Main.Logger.Trace($"bullet shot:{(WeaponHash)hash}");
// Main.Logger.Trace($"bullet shot:{(WeaponHash)hash}");
// Minigun, not working for some reason
if (hash==(uint)WeaponHash.Minigun)
{
hash=(uint)WeaponHash.HeavyRifle;
}
// Valkyire, not working for some reason
if (hash==2756787765) { hash=(uint)WeaponHash.HeavyRifle; }
var start = owner.MainPed.GetMuzzlePosition();
if (owner.MainPed.IsOnTurretSeat()) { start=owner.MainPed.Bones[Bone.SkelHead].Position; }
@ -89,10 +81,6 @@ namespace RageCoop.Client {
public static void TriggerVehBulletShot(uint hash, Vehicle veh, SyncedPed owner)
{
if (hash==(uint)VehicleWeaponHash.PlayerBuzzard)
{
hash=(uint)WeaponHash.HeavyRifle;
}
// ANNIHL
if (veh.Model.Hash==837858166)
{
@ -107,19 +95,36 @@ namespace RageCoop.Client {
if (info==null) { Main.Logger.Warning($"Failed to get muzzle info for vehicle:{veh.DisplayName}");return; }
Networking.SendBulletShot(info.Position,info.Position+info.ForawardVector,hash,owner.ID);
}
public static void TriggerNozzleTransform(int vehID,bool hover)
{
Networking.Send(new Packets.NozzleTransform() { VehicleID=vehID, Hover=hover }, ConnectionChannel.SyncEvents);
}
#endregion
#region HANDLE
public static ParticleEffectAsset CorePFXAsset = new ParticleEffectAsset("core");
static WeaponAsset _weaponAsset = default;
static uint _lastWeaponHash;
private static void HandleLeaveVehicle(Packets.LeaveVehicle p)
{
var ped = EntityPool.GetPedByID(p.ID)?.MainPed;
var ped = EntityPool.GetPedByID(p.ID);
var veh = ped.MainPed.CurrentVehicle.GetSyncEntity();
veh._checkSeat=false;
var flag = LeaveVehicleFlags.None;
if (ped.MainPed?.CurrentVehicle==null) { return; }
// Bail out
if (ped?.CurrentVehicle==null) { return; }
if (ped.CurrentVehicle.Speed>5) { flag|=LeaveVehicleFlags.BailOut;}
ped.Task.LeaveVehicle(flag) ;
if (ped.MainPed.CurrentVehicle.Speed>5) { flag|=LeaveVehicleFlags.BailOut;}
// ped.PauseUpdate((ulong)Game.FPS*2);
ped.MainPed.Task.LeaveVehicle(flag) ;
Task.Run(() =>
{
Thread.Sleep(1000);
veh._checkSeat=true;
});
}
private static void HandlePedKilled(Packets.PedKilled p)
{
@ -150,30 +155,60 @@ namespace RageCoop.Client {
v.OwnerID=p.NewOwnerID;
v.ModelHash=v.MainVehicle.Model;
v.LastSynced=Main.Ticked;
// So this vehicle doesn's get re-spawned
}
private static ParticleEffectAsset CorePFXAsset = default;
static WeaponAsset _weaponAsset = default;
static uint _lastWeaponHash;
private static void HandleNozzleTransform(Packets.NozzleTransform p)
{
EntityPool.GetVehicleByID(p.VehicleID)?.MainVehicle?.SetNozzleAngel(p.Hover ? 1 : 0);
}
private static void HandleBulletShot(Vector3 start, Vector3 end, uint weaponHash, int ownerID)
{
if (CorePFXAsset==default) {
CorePFXAsset= new ParticleEffectAsset("core");
switch (weaponHash)
{
// Minigun, not working for some reason
case (uint)WeaponHash.Minigun:
weaponHash=1176362416;
break;
// Valkyire, not working for some reason
case 2756787765:
weaponHash=1176362416;
break;
// Tampa3, not working for some reason
case 3670375085:
weaponHash=1176362416;
break;
// Ruiner2, not working for some reason
case 50118905:
weaponHash=1176362416;
break;
// SAVAGE
case 1638077257:
weaponHash=(uint)VehicleWeaponHash.PlayerLazer;
break;
case (uint)VehicleWeaponHash.PlayerBuzzard:
weaponHash=1176362416;
break ;
}
if (!CorePFXAsset.IsLoaded) { CorePFXAsset.Request(); }
var p = EntityPool.GetPedByID(ownerID)?.MainPed;
if (p == null) { p=Game.Player.Character; Main.Logger.Warning("Failed to find owner for bullet"); }
if (!CorePFXAsset.IsLoaded) { CorePFXAsset.Request(); }
if (_lastWeaponHash!=weaponHash)
{
_weaponAsset.MarkAsNoLongerNeeded();
_weaponAsset=new WeaponAsset(weaponHash);
_lastWeaponHash=weaponHash;
}
if (!_weaponAsset.IsLoaded) { _weaponAsset.Request(); }
World.ShootBullet(start, end, p, _weaponAsset, p.GetWeaponDamage());
var w = p.Weapons.CurrentWeaponObject;
if(w != null)
World.ShootBullet(start, end, p, _weaponAsset, (int)p.GetWeaponDamage(weaponHash));
Prop w;
if(((w = p.Weapons.CurrentWeaponObject) != null)&&(p.VehicleWeapon==VehicleWeaponHash.Invalid))
{
if (p.Weapons.Current.Components.GetSuppressorComponent().Active)
{
@ -185,19 +220,26 @@ namespace RageCoop.Client {
}
}
else if (p.VehicleWeapon!=VehicleWeaponHash.Invalid)
{
if (p.VehicleWeapon==VehicleWeaponHash.Tank)
{
World.CreateParticleEffectNonLooped(CorePFXAsset, "muz_tank", p.CurrentVehicle.GetMuzzleInfo().Position, p.CurrentVehicle.Bones[35].ForwardVector.ToEulerRotation(p.CurrentVehicle.Bones[35].UpVector), 1);
}
}
}
public static void HandleEvent(PacketTypes type,byte[] data)
public static void HandleEvent(PacketType type,byte[] data)
{
switch (type)
{
case PacketTypes.BulletShot:
case PacketType.BulletShot:
{
Packets.BulletShot p = new Packets.BulletShot();
p.Unpack(data);
HandleBulletShot(p.StartPosition.ToVector(), p.EndPosition.ToVector(), p.WeaponHash, p.OwnerID);
HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID);
break;
}
case PacketTypes.EnteringVehicle:
case PacketType.EnteringVehicle:
{
Packets.EnteringVehicle p = new Packets.EnteringVehicle();
p.Unpack(data);
@ -206,55 +248,43 @@ namespace RageCoop.Client {
}
break;
case PacketTypes.LeaveVehicle:
case PacketType.LeaveVehicle:
{
Packets.LeaveVehicle packet = new Packets.LeaveVehicle();
packet.Unpack(data);
HandleLeaveVehicle(packet);
}
break;
case PacketTypes.OwnerChanged:
case PacketType.OwnerChanged:
{
Packets.OwnerChanged packet = new Packets.OwnerChanged();
packet.Unpack(data);
HandleOwnerChanged(packet);
}
break;
case PacketTypes.PedKilled:
case PacketType.PedKilled:
{
var packet = new Packets.PedKilled();
packet.Unpack(data);
HandlePedKilled(packet);
}
break;
case PacketTypes.EnteredVehicle:
case PacketType.EnteredVehicle:
{
var packet = new Packets.EnteredVehicle();
packet.Unpack(data);
HandleEnteredVehicle(packet.PedID,packet.VehicleID,(VehicleSeat)packet.VehicleSeat);
break;
}
case PacketType.NozzleTransform:
{
var packet = new Packets.NozzleTransform();
packet.Unpack(data);
HandleNozzleTransform(packet);
break;
}
}
}
public static int GetWeaponDamage(this Ped p)
{
if (p.VehicleWeapon!=VehicleWeaponHash.Invalid)
{
return 30;
}
switch (p.Weapons.Current.Group)
{
case WeaponGroup.Pistol: return 30;
case WeaponGroup.AssaultRifle: return 30;
case WeaponGroup.SMG: return 20;
case WeaponGroup.MG: return 40;
case WeaponGroup.Shotgun: return 30;
case WeaponGroup.Sniper: return 200;
case WeaponGroup.Heavy: return 30;
}
return 0;
}
#endregion
@ -264,6 +294,8 @@ namespace RageCoop.Client {
public static void Check(SyncedPed c)
{
Ped subject = c.MainPed;
// Check bullets
if (subject.IsShooting)
{
if (!subject.IsUsingProjectileWeapon())
@ -360,7 +392,21 @@ namespace RageCoop.Client {
c._lastEnteringVehicle=g;
}
public static void Check(SyncedVehicle v)
{
if (v.MainVehicle!=null&&v.MainVehicle.HasNozzle())
{
if((v.LastNozzleAngle==1) && (v.MainVehicle.GetNozzleAngel()!=1))
{
TriggerNozzleTransform(v.ID,false);
}
else if((v.LastNozzleAngle==0) && (v.MainVehicle.GetNozzleAngel()!=0))
{
TriggerNozzleTransform(v.ID,true);
}
v.LastNozzleAngle=v.MainVehicle.GetNozzleAngel();
}
}
#endregion
}
}

View File

@ -8,6 +8,6 @@ namespace RageCoop.Client
{
internal class SyncParameters
{
public static float PositioinPrediction = 0.03f;
public static float PositioinPredictionDefault = 0.01f;
}
}

View File

@ -0,0 +1,471 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client
{
// Potential names and hash collisions included as comments
internal enum PedConfigFlags
{
_0x67D1A445 = 0,
_0xC63DE95E = 1,
CPED_CONFIG_FLAG_NoCriticalHits = 2,
CPED_CONFIG_FLAG_DrownsInWater = 3,
CPED_CONFIG_FLAG_DisableReticuleFixedLockon = 4,
_0x37D196F4 = 5,
_0xE2462399 = 6,
CPED_CONFIG_FLAG_UpperBodyDamageAnimsOnly = 7,
_0xEDDEB838 = 8,
_0xB398B6FD = 9,
_0xF6664E68 = 10,
_0xA05E7CA3 = 11,
_0xCE394045 = 12,
CPED_CONFIG_FLAG_NeverLeavesGroup = 13,
_0xCD8D1411 = 14,
_0xB031F1A9 = 15,
_0xFE65BEE3 = 16,
CPED_CONFIG_FLAG_BlockNonTemporaryEvents = 17,
_0x380165BD = 18,
_0x07C045C7 = 19,
_0x583B5E2D = 20,
_0x475EDA58 = 21,
_0x8629D05B = 22,
_0x1522968B = 23,
CPED_CONFIG_FLAG_IgnoreSeenMelee = 24,
_0x4CC09C4B = 25,
_0x034F3053 = 26,
_0xD91BA7CC = 27,
_0x5C8DC66E = 28,
_0x8902EAA0 = 29,
_0x6580B9D2 = 30,
_0x0EF7A297 = 31,
_0x6BF86E5B = 32,
CPED_CONFIG_FLAG_DieWhenRagdoll = 33,
CPED_CONFIG_FLAG_HasHelmet = 34,
CPED_CONFIG_FLAG_UseHelmet = 35,
_0xEEB3D630 = 36,
_0xB130D17B = 37,
_0x5F071200 = 38,
CPED_CONFIG_FLAG_DisableEvasiveDives = 39,
_0xC287AAFF = 40,
_0x203328CC = 41,
CPED_CONFIG_FLAG_DontInfluenceWantedLevel = 42,
CPED_CONFIG_FLAG_DisablePlayerLockon = 43,
CPED_CONFIG_FLAG_DisableLockonToRandomPeds = 44,
_0xEC4A8ACF = 45,
_0xDB115BFA = 46,
CPED_CONFIG_FLAG_PedBeingDeleted = 47,
CPED_CONFIG_FLAG_BlockWeaponSwitching = 48,
_0xF8E99565 = 49,
_0xDD17FEE6 = 50,
_0x7ED9B2C9 = 51,
_0x655E8618 = 52,
_0x5A6C1F6E = 53,
_0xD749FC41 = 54,
_0x357F63F3 = 55,
_0xC5E60961 = 56,
_0x29275C3E = 57,
CPED_CONFIG_FLAG_IsFiring = 58,
CPED_CONFIG_FLAG_WasFiring = 59,
CPED_CONFIG_FLAG_IsStanding = 60,
CPED_CONFIG_FLAG_WasStanding = 61,
CPED_CONFIG_FLAG_InVehicle = 62,
CPED_CONFIG_FLAG_OnMount = 63,
CPED_CONFIG_FLAG_AttachedToVehicle = 64,
CPED_CONFIG_FLAG_IsSwimming = 65,
CPED_CONFIG_FLAG_WasSwimming = 66,
CPED_CONFIG_FLAG_IsSkiing = 67,
CPED_CONFIG_FLAG_IsSitting = 68,
CPED_CONFIG_FLAG_KilledByStealth = 69,
CPED_CONFIG_FLAG_KilledByTakedown = 70,
CPED_CONFIG_FLAG_Knockedout = 71,
_0x3E3C4560 = 72,
_0x2994C7B7 = 73,
_0x6D59D275 = 74,
CPED_CONFIG_FLAG_UsingCoverPoint = 75,
CPED_CONFIG_FLAG_IsInTheAir = 76,
_0x2D493FB7 = 77,
CPED_CONFIG_FLAG_IsAimingGun = 78,
_0x14D69875 = 79,
_0x40B05311 = 80,
_0x8B230BC5 = 81,
_0xC74E5842 = 82,
_0x9EA86147 = 83,
_0x674C746C = 84,
_0x3E56A8C2 = 85,
_0xC144A1EF = 86,
_0x0548512D = 87,
_0x31C93909 = 88,
_0xA0269315 = 89,
_0xD4D59D4D = 90,
_0x411D4420 = 91,
_0xDF4AEF0D = 92,
CPED_CONFIG_FLAG_ForcePedLoadCover = 93,
_0x300E4CD3 = 94,
_0xF1C5BF04 = 95,
_0x89C2EF13 = 96,
CPED_CONFIG_FLAG_VaultFromCover = 97,
_0x02A852C8 = 98,
_0x3D9407F1 = 99,
_0x319B4558 = 100,
CPED_CONFIG_FLAG_ForcedAim = 101,
_0xB942D71A = 102,
_0xD26C55A8 = 103,
_0xB89E703B = 104,
CPED_CONFIG_FLAG_ForceReload = 105,
_0xD9E73DA2 = 106,
_0xFF71DC2C = 107,
_0x1E27E8D8 = 108,
_0xF2C53966 = 109,
_0xC4DBE247 = 110,
_0x83C0A4BF = 111,
_0x0E0FAF8C = 112,
_0x26616660 = 113,
_0x43B80B79 = 114,
_0x0D2A9309 = 115,
_0x12C1C983 = 116,
CPED_CONFIG_FLAG_BumpedByPlayer = 117,
_0xE586D504 = 118,
_0x52374204 = 119,
CPED_CONFIG_FLAG_IsHandCuffed = 120,
CPED_CONFIG_FLAG_IsAnkleCuffed = 121,
CPED_CONFIG_FLAG_DisableMelee = 122,
_0xFE714397 = 123,
_0xB3E660BD = 124,
_0x5FED6BFD = 125,
_0xC9D6F66F = 126,
_0x519BC986 = 127,
CPED_CONFIG_FLAG_CanBeAgitated = 128,
_0x9A4B617C = 129, // CPED_CONFIG_FLAG_FaceDirInsult
_0xDAB70E9F = 130,
_0xE569438A = 131,
_0xBBC77D6D = 132,
_0xCB59EF0F = 133,
_0x8C5EA971 = 134,
CPED_CONFIG_FLAG_IsScuba = 135,
CPED_CONFIG_FLAG_WillArrestRatherThanJack = 136,
_0xDCE59B58 = 137,
CPED_CONFIG_FLAG_RidingTrain = 138,
CPED_CONFIG_FLAG_ArrestResult = 139,
CPED_CONFIG_FLAG_CanAttackFriendly = 140,
_0x98A4BE43 = 141,
_0x6901E731 = 142,
_0x9EC9BF6C = 143,
_0x42841A8F = 144,
CPED_CONFIG_FLAG_ShootingAnimFlag = 145,
CPED_CONFIG_FLAG_DisableLadderClimbing = 146,
CPED_CONFIG_FLAG_StairsDetected = 147,
CPED_CONFIG_FLAG_SlopeDetected = 148,
_0x1A15670B = 149,
_0x61786EE5 = 150,
_0xCB9186BD = 151,
_0xF0710152 = 152,
_0x43DFE310 = 153,
_0xC43C624E = 154,
CPED_CONFIG_FLAG_CanPerformArrest = 155,
CPED_CONFIG_FLAG_CanPerformUncuff = 156,
CPED_CONFIG_FLAG_CanBeArrested = 157,
_0xF7960FF5 = 158,
_0x59564113 = 159,
_0x0C6C3099 = 160,
_0x645F927A = 161,
_0xA86549B9 = 162,
_0x8AAF337A = 163,
_0x13BAA6E7 = 164,
_0x5FB9D1F5 = 165,
CPED_CONFIG_FLAG_IsInjured = 166,
_0x6398A20B = 167,
_0xD8072639 = 168,
_0xA05B1845 = 169,
_0x83F6D220 = 170,
_0xD8430331 = 171,
_0x4B547520 = 172,
_0xE66E1406 = 173,
_0x1C4BFE0C = 174,
_0x90008BFA = 175,
_0x07C7A910 = 176,
_0xF15F8191 = 177,
_0xCE4E8BE2 = 178,
_0x1D46E4F2 = 179,
CPED_CONFIG_FLAG_IsInCustody = 180,
_0xE4FD9B3A = 181,
_0x67AE0812 = 182,
CPED_CONFIG_FLAG_IsAgitated = 183,
CPED_CONFIG_FLAG_PreventAutoShuffleToDriversSeat = 184,
_0x7B2D325E = 185,
CPED_CONFIG_FLAG_EnableWeaponBlocking = 186,
CPED_CONFIG_FLAG_HasHurtStarted = 187,
CPED_CONFIG_FLAG_DisableHurt = 188,
CPED_CONFIG_FLAG_PlayerIsWeird = 189,
_0x32FC208B = 190,
_0x0C296E5A = 191,
_0xE63B73EC = 192,
_0x04E9CC80 = 193,
CPED_CONFIG_FLAG_UsingScenario = 194,
CPED_CONFIG_FLAG_VisibleOnScreen = 195,
_0xD88C58A1 = 196,
_0x5A3DCF43 = 197, // CPED_CONFIG_FLAG_AvoidUnderSide
_0xEA02B420 = 198,
_0x3F559CFF = 199,
_0x8C55D029 = 200,
_0x5E6466F6 = 201,
_0xEB5AD706 = 202,
_0x0EDDDDE7 = 203,
_0xA64F7B1D = 204,
_0x48532CBA = 205,
_0xAA25A9E7 = 206,
_0x415B26B9 = 207,
CPED_CONFIG_FLAG_DisableExplosionReactions = 208,
CPED_CONFIG_FLAG_DodgedPlayer = 209,
_0x67405504 = 210,
_0x75DDD68C = 211,
_0x2AD879B4 = 212,
_0x51486F91 = 213,
_0x32F79E21 = 214,
_0xBF099213 = 215,
_0x054AC8E2 = 216,
_0x14E495CC = 217,
_0x3C7DF9DF = 218,
_0x848FFEF2 = 219,
CPED_CONFIG_FLAG_DontEnterLeadersVehicle = 220,
_0x2618E1CF = 221,
_0x84F722FA = 222,
_0xD1B87B1F = 223,
_0x728AA918 = 224,
CPED_CONFIG_FLAG_DisablePotentialToBeWalkedIntoResponse = 225,
CPED_CONFIG_FLAG_DisablePedAvoidance = 226,
_0x59E91185 = 227,
_0x1EA7225F = 228,
CPED_CONFIG_FLAG_DisablePanicInVehicle = 229,
_0x6DCA7D88 = 230,
_0xFC3E572D = 231,
_0x08E9F9CF = 232,
_0x2D3BA52D = 233,
_0xFD2F53EA = 234,
_0x31A1B03B = 235,
CPED_CONFIG_FLAG_IsHoldingProp = 236,
_0x82ED0A66 = 237, // CPED_CONFIG_FLAG_BlocksPathingWhenDead
_0xCE57C9A3 = 238,
_0x26149198 = 239,
_0x1B33B598 = 240,
_0x719B6E87 = 241,
_0x13E8E8E8 = 242,
_0xF29739AE = 243,
_0xABEA8A74 = 244,
_0xB60EA2BA = 245,
_0x536B0950 = 246,
_0x0C754ACA = 247,
CPED_CONFIG_FLAG_DisableVehicleSeatRandomAnimations = 248,
_0x12659168 = 249,
_0x1BDF2F04 = 250,
_0x7728FAA3 = 251,
_0x6A807ED8 = 252,
CPED_CONFIG_FLAG_OnStairs = 253,
_0xE1A2F73F = 254,
_0x5B3697C8 = 255,
_0xF1EB20A9 = 256,
_0x8B7DF407 = 257,
_0x329DCF1A = 258,
_0x8D90DD1B = 259,
_0xB8A292B7 = 260,
_0x8374B087 = 261,
_0x2AF558F0 = 262,
_0x82251455 = 263,
_0x30CF498B = 264,
_0xE1CD50AF = 265,
_0x72E4AE48 = 266,
_0xC2657EA1 = 267,
_0x29FF6030 = 268,
_0x8248A5EC = 269,
CPED_CONFIG_FLAG_OnStairSlope = 270,
_0xA0897933 = 271,
CPED_CONFIG_FLAG_DontBlipCop = 272,
CPED_CONFIG_FLAG_ClimbedShiftedFence = 273,
_0xF7823618 = 274,
_0xDC305CCE = 275, // CPED_CONFIG_FLAG_KillWhenTrapped
CPED_CONFIG_FLAG_EdgeDetected = 276,
_0x92B67896 = 277,
_0xCAD677C9 = 278,
CPED_CONFIG_FLAG_AvoidTearGas = 279,
_0x5276AC7B = 280,
_0x1032692A = 281,
_0xDA23E7F1 = 282,
_0x9139724D = 283,
_0xA1457461 = 284,
_0x4186E095 = 285,
_0xAC68E2EB = 286,
CPED_CONFIG_FLAG_RagdollingOnBoat = 287,
CPED_CONFIG_FLAG_HasBrandishedWeapon = 288,
_0x1B9EE8A1 = 289,
_0xF3F5758C = 290,
_0x2A9307F1 = 291,
_0x7403D216 = 292,
_0xA06A3C6C = 293,
CPED_CONFIG_FLAG_DisableShockingEvents = 294,
_0xF8DA25A5 = 295,
_0x7EF55802 = 296,
_0xB31F1187 = 297,
_0x84315402 = 298,
_0x0FD69867 = 299,
_0xC7829B67 = 300,
CPED_CONFIG_FLAG_DisablePedConstraints = 301,
_0x6D23CF25 = 302,
_0x2ADA871B = 303,
_0x47BC8A58 = 304,
_0xEB692FA5 = 305,
_0x4A133C50 = 306,
_0xC58099C3 = 307,
_0xF3D76D41 = 308,
_0xB0EEE9F2 = 309,
CPED_CONFIG_FLAG_IsInCluster = 310,
_0x0FA153EF = 311,
_0xD73F5CD3 = 312,
_0xD4136C22 = 313,
_0xE404CA6B = 314,
_0xB9597446 = 315,
_0xD5C98277 = 316,
_0xD5060A9C = 317,
_0x3E5F1CBB = 318,
_0xD8BE1D54 = 319,
_0x0B1F191F = 320,
_0xC995167A = 321,
CPED_CONFIG_FLAG_HasHighHeels = 322,
_0x86B01E54 = 323,
_0x3A56FE15 = 324,
_0xC03B736C = 325, // CPED_CONFIG_FLAG_SpawnedAtScenario
_0xBBF47729 = 326,
_0x22B668A8 = 327,
_0x2624D4D4 = 328,
CPED_CONFIG_FLAG_DisableTalkTo = 329,
CPED_CONFIG_FLAG_DontBlip = 330,
CPED_CONFIG_FLAG_IsSwitchingWeapon = 331,
_0x630F55F3 = 332,
_0x150468FD = 333,
_0x914EBD6B = 334,
_0x79AF3B6D = 335,
_0x75C7A632 = 336,
_0x52D530E2 = 337,
_0xDB2A90E0 = 338,
_0x5922763D = 339,
_0x12ADB567 = 340,
_0x105C8518 = 341,
_0x106F703D = 342,
_0xED152C3E = 343,
_0xA0EFE6A8 = 344,
_0xBF348C82 = 345,
_0xCDDFE830 = 346,
_0x7B59BD9B = 347,
_0x0124C788 = 348,
CPED_CONFIG_FLAG_EquipJetpack = 349,
_0x08D361A5 = 350,
_0xE13D1F7C = 351,
_0x40E25FB9 = 352,
_0x930629D9 = 353,
_0xECCF0C7F = 354,
_0xB6E9613B = 355,
_0x490C0478 = 356,
_0xE8865BEA = 357,
_0xF3C34A29 = 358,
CPED_CONFIG_FLAG_IsDuckingInVehicle = 359,
_0xF660E115 = 360,
_0xAB0E6DED = 361,
CPED_CONFIG_FLAG_HasReserveParachute = 362,
CPED_CONFIG_FLAG_UseReserveParachute = 363,
_0x5C5D9CD3 = 364,
_0x8F7701F3 = 365,
_0xBC4436AD = 366,
_0xD7E07D37 = 367,
_0x03C4FD24 = 368,
_0x7675789A = 369,
_0xB7288A88 = 370,
_0xC06B6291 = 371,
_0x95A4A805 = 372,
_0xA8E9A042 = 373,
CPED_CONFIG_FLAG_NeverLeaveTrain = 374,
_0xBAC674B3 = 375,
_0x147F1FFB = 376,
_0x4376DD79 = 377,
_0xCD3DB518 = 378,
_0xFE4BA4B6 = 379,
_0x5DF03A55 = 380,
_0xBCD816CD = 381,
_0xCF02DD69 = 382,
_0xF73AFA2E = 383,
_0x80B9A9D0 = 384,
_0xF601F7EE = 385,
_0xA91350FC = 386,
_0x3AB23B96 = 387,
CPED_CONFIG_FLAG_IsClimbingLadder = 388,
CPED_CONFIG_FLAG_HasBareFeet = 389,
_0xB4B1CD4C = 390,
_0x5459AFB8 = 391,
_0x54F27667 = 392,
_0xC11D3E8F = 393,
_0x5419EB3E = 394,
_0x82D8DBB4 = 395,
_0x33B02D2F = 396,
_0xAE66176D = 397,
_0xA2692593 = 398,
_0x714C7E31 = 399,
_0xEC488AC7 = 400,
_0xAE398504 = 401,
_0xABC58D72 = 402,
_0x5E5B9591 = 403,
_0x6BA1091E = 404,
_0x77840177 = 405,
_0x1C7ACAC4 = 406,
_0x124420E9 = 407,
_0x75A65587 = 408,
_0xDFD2D55B = 409,
_0xBDD39919 = 410,
_0x43DEC267 = 411,
_0xE42B7797 = 412,
CPED_CONFIG_FLAG_IsHolsteringWeapon = 413,
_0x4F8149F5 = 414,
_0xDD9ECA7A = 415,
_0x9E7EF9D2 = 416,
_0x2C6ED942 = 417,
CPED_CONFIG_FLAG_IsSwitchingHelmetVisor = 418,
_0xA488727D = 419,
_0xCFF5F6DE = 420,
_0x6D614599 = 421,
CPED_CONFIG_FLAG_DisableVehicleCombat = 422,
_0xFE401D26 = 423,
CPED_CONFIG_FLAG_FallsLikeAircraft = 424,
_0x2B42AE82 = 425,
_0x7A95734F = 426,
_0xDF4D8617 = 427,
_0x578F1F14 = 428,
CPED_CONFIG_FLAG_DisableStartEngine = 429,
CPED_CONFIG_FLAG_IgnoreBeingOnFire = 430,
_0x153C9500 = 431,
_0xCB7A632E = 432,
_0xDE727981 = 433,
CPED_CONFIG_FLAG_DisableHomingMissileLockon = 434,
_0x12BBB935 = 435,
_0xAD0A1277 = 436,
_0xEA6AA46A = 437,
CPED_CONFIG_FLAG_DisableHelmetArmor = 438,
_0xCB7F3A1E = 439,
_0x50178878 = 440,
_0x051B4F0D = 441,
_0x2FC3DECC = 442,
_0xC0030B0B = 443,
_0xBBDAF1E9 = 444,
_0x944FE59C = 445,
_0x506FBA39 = 446,
_0xDD45FE84 = 447,
_0xE698AE75 = 448,
_0x199633F8 = 449,
CPED_CONFIG_FLAG_PedIsArresting = 450,
CPED_CONFIG_FLAG_IsDecoyPed = 451,
_0x3A251D83 = 452,
_0xA56F6986 = 453,
_0x1D19C622 = 454,
_0xB68D3EAB = 455,
CPED_CONFIG_FLAG_CanBeIncapacitated = 456,
_0x4BD5EBAD = 457,
}
}

View File

@ -0,0 +1,399 @@
using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using RageCoop.Core;
using GTA;
using GTA.Native;
using GTA.Math;
using System.Linq;
using System.Diagnostics;
namespace RageCoop.Client
{
internal static partial class PedExtensions
{
public static bool IsBetween<T>(this T item, T start, T end)
{
return Comparer<T>.Default.Compare(item, start) >= 0 && Comparer<T>.Default.Compare(item, end) <= 0;
}
public static bool Compare<T, Y>(this Dictionary<T, Y> item, Dictionary<T, Y> item2)
{
if (item == null || item2 == null || item.Count != item2.Count)
{
return false;
}
foreach (KeyValuePair<T, Y> pair in item)
{
if (item2.TryGetValue(pair.Key, out Y value) && Equals(value, pair.Value))
{
continue;
}
// TryGetValue() or Equals failed
return false;
}
// No difference between item and item2
return true;
}
#region PED
public static byte GetPedSpeed(this Ped ped)
{
if (ped.IsSprinting)
{
return 3;
}
if (ped.IsRunning)
{
return 2;
}
if (ped.IsWalking)
{
return 1;
}
return 0;
}
// Not sure whether component will always be lesser than 255, whatever...
public static byte[] GetPedClothes(this Ped ped)
{
var result = new byte[36];
for (byte i = 0; i < 12; i++)
{
result[i]=(byte)Function.Call<short>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result[i+12]=(byte)Function.Call<short>(Hash.GET_PED_TEXTURE_VARIATION, ped.Handle, i);
result[i+24]=(byte)Function.Call<short>(Hash.GET_PED_PALETTE_VARIATION, ped.Handle, i);
}
return result;
}
public static PedDataFlags GetPedFlags(this Ped ped)
{
PedDataFlags flags = PedDataFlags.None;
if (ped.IsAiming || ped.IsOnTurretSeat())
{
flags |= PedDataFlags.IsAiming;
}
if (ped.IsReloading)
{
flags |= PedDataFlags.IsReloading;
}
if (ped.IsJumping)
{
flags |= PedDataFlags.IsJumping;
}
// Fake death
if (ped.IsRagdoll || (ped.Health==1 && ped.IsPlayer))
{
flags |= PedDataFlags.IsRagdoll;
}
if (ped.IsOnFire)
{
flags |= PedDataFlags.IsOnFire;
}
if (ped.IsInParachuteFreeFall)
{
flags |= PedDataFlags.IsInParachuteFreeFall;
}
if (ped.ParachuteState == ParachuteState.Gliding)
{
flags |= PedDataFlags.IsParachuteOpen;
}
bool climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder);
if (climbingLadder)
{
flags |= PedDataFlags.IsOnLadder;
}
if (ped.IsVaulting && !climbingLadder)
{
flags |= PedDataFlags.IsVaulting;
}
if (ped.IsInCover || ped.IsGoingIntoCover)
{
flags |=PedDataFlags.IsInCover;
}
if(Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped))
{
flags |= PedDataFlags.IsInStealthMode;
}
return flags;
}
public static string[] GetReloadingAnimation(this Ped ped)
{
switch (ped.Weapons.Current.Hash)
{
case WeaponHash.Revolver:
case WeaponHash.RevolverMk2:
case WeaponHash.DoubleActionRevolver:
case WeaponHash.NavyRevolver:
return new string[2] { "anim@weapons@pistol@revolver_str", "reload_aim" };
case WeaponHash.APPistol:
return new string[2] { "weapons@pistol@ap_pistol_str", "reload_aim" };
case WeaponHash.Pistol50:
return new string[2] { "weapons@pistol@pistol_50_str", "reload_aim" };
case WeaponHash.Pistol:
case WeaponHash.PistolMk2:
case WeaponHash.PericoPistol:
case WeaponHash.SNSPistol:
case WeaponHash.SNSPistolMk2:
case WeaponHash.HeavyPistol:
case WeaponHash.VintagePistol:
case WeaponHash.CeramicPistol:
case WeaponHash.MachinePistol:
return new string[2] { "weapons@pistol@pistol_str", "reload_aim" };
case WeaponHash.AssaultRifle:
case WeaponHash.AssaultrifleMk2:
return new string[2] { "weapons@rifle@hi@assault_rifle_str", "reload_aim" };
case WeaponHash.SniperRifle:
return new string[2] { "weapons@rifle@hi@sniper_rifle_str", "reload_aim" };
case WeaponHash.HeavySniper:
case WeaponHash.HeavySniperMk2:
return new string[2] { "weapons@rifle@lo@sniper_heavy_str", "reload_aim" };
case WeaponHash.PumpShotgun:
case WeaponHash.PumpShotgunMk2:
return new string[2] { "weapons@rifle@pump_str", "reload_aim" };
case WeaponHash.Railgun:
return new string[2] { "weapons@rifle@lo@rail_gun_str", "reload_aim" };
case WeaponHash.SawnOffShotgun:
return new string[2] { "weapons@rifle@lo@sawnoff_str", "reload_aim" };
case WeaponHash.AssaultShotgun:
return new string[2] { "weapons@rifle@lo@shotgun_assault_str", "reload_aim" };
case WeaponHash.BullpupShotgun:
return new string[2] { "weapons@rifle@lo@shotgun_bullpup_str", "reload_aim" };
case WeaponHash.AdvancedRifle:
return new string[2] { "weapons@submg@advanced_rifle_str", "reload_aim" };
case WeaponHash.CarbineRifle:
case WeaponHash.CarbineRifleMk2:
case WeaponHash.CompactRifle:
return new string[2] { "weapons@rifle@lo@carbine_str", "reload_aim" };
case WeaponHash.Gusenberg:
return new string[2] { "anim@weapons@machinegun@gusenberg_str", "reload_aim" };
case WeaponHash.Musket:
return new string[2] { "anim@weapons@musket@musket_str", "reload_aim" };
case WeaponHash.FlareGun:
return new string[2] { "anim@weapons@pistol@flare_str", "reload_aim" };
case WeaponHash.SpecialCarbine:
case WeaponHash.SpecialCarbineMk2:
return new string[2] { "anim@weapons@rifle@lo@spcarbine_str", "reload_aim" };
case WeaponHash.CombatPDW:
return new string[2] { "anim@weapons@rifle@lo@pdw_str", "reload_aim" };
case WeaponHash.BullpupRifle:
case WeaponHash.BullpupRifleMk2:
return new string[2] { "anim@weapons@submg@bullpup_rifle_str", "reload_aim" };
case WeaponHash.AssaultSMG:
return new string[2] { "weapons@submg@assault_smg_str", "reload_aim" };
case WeaponHash.MicroSMG:
case WeaponHash.MiniSMG:
return new string[2] { "weapons@submg@lo@micro_smg_str", "reload_aim" };
case WeaponHash.SMG:
case WeaponHash.SMGMk2:
return new string[2] { "weapons@rifle@smg_str", "reload_aim" };
case WeaponHash.GrenadeLauncher:
case WeaponHash.GrenadeLauncherSmoke:
case WeaponHash.CompactGrenadeLauncher:
return new string[2] { "weapons@heavy@grenade_launcher_str", "reload_aim" };
case WeaponHash.RPG:
case WeaponHash.Firework:
return new string[2] { "weapons@heavy@rpg_str", "reload_aim" };
case WeaponHash.CombatMG:
case WeaponHash.CombatMGMk2:
return new string[2] { "weapons@machinegun@combat_mg_str", "reload_aim" };
case WeaponHash.MG:
return new string[2] { "weapons@machinegun@mg_str", "reload_aim" };
default:
Main.Logger.Warning($"~r~Reloading failed! Weapon ~g~[{ped.Weapons.Current.Hash}]~r~ could not be found!");
return null;
}
}
public static VehicleSeat GetNearestSeat(this Ped ped, Vehicle veh, float distanceToignoreDoors = 50f)
{
float num = 99f;
int result = -2;
Dictionary<string, int> dictionary = new Dictionary<string, int>();
dictionary.Add("door_dside_f", -1);
dictionary.Add("door_pside_f", 0);
dictionary.Add("door_dside_r", 1);
dictionary.Add("door_pside_r", 2);
foreach (string text in dictionary.Keys)
{
bool flag = veh.Bones[text].Position != Vector3.Zero;
if (flag)
{
float num2 = ped.Position.DistanceTo(Function.Call<Vector3>(Hash.GET_WORLD_POSITION_OF_ENTITY_BONE, new InputArgument[]
{
veh,
veh.Bones[text].Index
}));
bool flag2 = (num2 < distanceToignoreDoors) && (num2 < num)&& IsSeatUsableByPed(ped, veh, dictionary[text]);
if (flag2)
{
num = num2;
result = dictionary[text];
}
}
}
return (VehicleSeat)result;
}
public static bool IsSeatUsableByPed(Ped ped, Vehicle veh, int _seat)
{
VehicleSeat seat = (VehicleSeat)_seat;
bool result = false;
bool flag = veh.IsSeatFree(seat);
if (flag)
{
result = true;
}
else
{
bool isDead = veh.GetPedOnSeat(seat).IsDead;
if (isDead)
{
result = true;
}
else
{
int num = Function.Call<int>(Hash.GET_RELATIONSHIP_BETWEEN_PEDS, new InputArgument[]
{
ped,
veh.GetPedOnSeat(seat)
});
bool flag2 = num > 2;
if (flag2)
{
result = true;
}
}
}
return result;
}
public static bool IsTaskActive(this Ped p,TaskType task)
{
return Function.Call<bool>(Hash.GET_IS_TASK_ACTIVE, p.Handle, task);
}
public static Vector3 GetAimCoord(this Ped p)
{
var weapon = p.Weapons.CurrentWeaponObject;
var v = p.CurrentVehicle;
// Rhino
if (v!=null && v.Model.Hash==782665360)
{
return v.Bones[35].Position+v.Bones[35].ForwardVector*100;
}
if (p.IsOnTurretSeat()) { return p.GetLookingCoord(); }
if (weapon!=null)
{
// Not very accurate, but doesn't matter
Vector3 dir = weapon.RightVector;
return weapon.Position+dir*20;
}
return GetLookingCoord(p);
}
public static Vector3 GetLookingCoord(this Ped p)
{
EntityBone b = p.Bones[Bone.FacialForehead];
Vector3 v = b.UpVector.Normalized;
return b.Position+200*v;
}
public static void StayInCover(this Ped p)
{
Function.Call(Hash.TASK_STAY_IN_COVER, p);
}
public static VehicleSeat GetSeatTryingToEnter(this Ped p)
{
return (VehicleSeat)Function.Call<int>(Hash.GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
}
#endregion
public static bool IsTurretSeat(this Vehicle veh, int seat)
{
if (!Function.Call<bool>(Hash.DOES_VEHICLE_HAVE_WEAPONS, veh.Handle))
{
return false;
}
switch (seat)
{
case -1:
return (VehicleHash)veh.Model.Hash == VehicleHash.Rhino
|| (VehicleHash)veh.Model.Hash == VehicleHash.Khanjari
|| (VehicleHash)veh.Model.Hash == VehicleHash.FireTruck
|| (VehicleHash)veh.Model.Hash == VehicleHash.Riot2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus3;
case 0:
return (VehicleHash)veh.Model.Hash == VehicleHash.Apc
|| (VehicleHash)veh.Model.Hash==VehicleHash.Dune3;
case 1:
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical3
|| (VehicleHash)veh.Model.Hash == VehicleHash.HalfTrack
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
case 2:
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
case 3:
return (VehicleHash)veh.Model.Hash == VehicleHash.Limo2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dinghy5;
case 7:
return (VehicleHash)veh.Model.Hash == VehicleHash.Insurgent;
}
return false;
}
public static bool IsOnTurretSeat(this Ped P)
{
if (P.CurrentVehicle == null) { return false; }
return IsTurretSeat(P.CurrentVehicle, (int)P.SeatIndex);
}
}
}

View File

@ -1,7 +1,7 @@
// WARNING: values can change after a game update
// if R* adds in the middle!
// This is up-to-date for b2372
public enum TaskType
internal enum TaskType
{
CTaskHandsUp = 0,
CTaskClimbLadder = 1,

View File

@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using GTA.Math;
using GTA;
using RageCoop.Core;
using GTA.Native;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace RageCoop.Client
{
internal static class Util
{
#region -- POINTER --
private static int _steeringAngleOffset { get; set; }
public static unsafe void NativeMemory()
{
IntPtr address;
address = Game.FindPattern("\x74\x0A\xF3\x0F\x11\xB3\x1C\x09\x00\x00\xEB\x25", "xxxxxx????xx");
if (address != IntPtr.Zero)
{
_steeringAngleOffset = *(int*)(address + 6) + 8;
}
// breaks some stuff.
/*
address = Game.FindPattern("\x32\xc0\xf3\x0f\x11\x09", "xxxxxx"); // Weapon / Radio slowdown
if (address != IntPtr.Zero)
{
for (int i = 0; i < 6; i++)
{
*(byte*)(address + i).ToPointer() = 0x90;
}
}
*/
}
public static unsafe void CustomSteeringAngle(this Vehicle veh, float value)
{
IntPtr address = new IntPtr((long)veh.MemoryAddress);
if (address == IntPtr.Zero || _steeringAngleOffset == 0)
{
return;
}
*(float*)(address + _steeringAngleOffset).ToPointer() = value;
}
#endregion
#region MATH
public static Vector3 LinearVectorLerp(Vector3 start, Vector3 end, ulong currentTime, int duration)
{
return new Vector3()
{
X = LinearFloatLerp(start.X, end.X, currentTime, duration),
Y = LinearFloatLerp(start.Y, end.Y, currentTime, duration),
Z = LinearFloatLerp(start.Z, end.Z, currentTime, duration),
};
}
public static float LinearFloatLerp(float start, float end, ulong currentTime, int duration)
{
return (end - start) * currentTime / duration + start;
}
public static float Lerp(float from, float to, float fAlpha)
{
return (from * (1.0f - fAlpha)) + (to * fAlpha); //from + (to - from) * fAlpha
}
public static Vector3 RotationToDirection(Vector3 rotation)
{
double z = MathExtensions.DegToRad(rotation.Z);
double x = MathExtensions.DegToRad(rotation.X);
double num = Math.Abs(Math.Cos(x));
return new Vector3
{
X = (float)(-Math.Sin(z) * num),
Y = (float)(Math.Cos(z) * num),
Z = (float)Math.Sin(x)
};
}
#endregion
public static string SettingsPath= "Scripts\\RageCoop\\Data\\RageCoop.Client.Settings.xml";
public static Settings ReadSettings()
{
XmlSerializer ser = new XmlSerializer(typeof(Settings));
string path = SettingsPath;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
Settings settings = null;
if (File.Exists(path))
{
using (FileStream stream = File.OpenRead(path))
{
settings = (RageCoop.Client.Settings)ser.Deserialize(stream);
}
using (FileStream stream = new FileStream(path, FileMode.Truncate, FileAccess.ReadWrite))
{
ser.Serialize(stream, settings);
}
}
else
{
using (FileStream stream = File.OpenWrite(path))
{
ser.Serialize(stream, settings = new Settings());
}
}
return settings;
}
public static void SaveSettings()
{
try
{
string path = SettingsPath ;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite))
{
XmlSerializer ser = new XmlSerializer(typeof(Settings));
ser.Serialize(stream, Main.Settings);
}
}
catch (Exception ex)
{
GTA.UI.Notification.Show("Error saving player settings: " + ex.Message);
}
}
public static Vector3 PredictPosition(this Entity e, bool applyDefault = true)
{
return e.Position+e.Velocity*((applyDefault ? SyncParameters.PositioinPredictionDefault : 0)+Networking.Latency);
}
public static Model ModelRequest(this int hash)
{
Model model = new Model(hash);
if (!model.IsValid)
{
//GTA.UI.Notification.Show("~y~Not valid!");
return null;
}
if (!model.IsLoaded)
{
return model.Request(1000) ? model : null;
}
return model;
}
public static void SetOnFire(this Entity e, bool toggle)
{
if (toggle)
{
Function.Call(Hash.START_ENTITY_FIRE, e.Handle);
}
else
{
Function.Call(Hash.STOP_ENTITY_FIRE, e.Handle);
}
}
public static void SetFrozen(this Entity e,bool toggle)
{
Function.Call(Hash.FREEZE_ENTITY_POSITION, e, toggle);
}
public static SyncedPed GetSyncEntity(this Ped p)
{
if (p == null) { return null; }
var c = EntityPool.GetPedByHandle(p.Handle);
if (c==null) { EntityPool.Add(c=new SyncedPed(p)); }
return c;
}
public static SyncedVehicle GetSyncEntity(this Vehicle veh)
{
if (veh == null) { return null; }
var v = EntityPool.GetVehicleByHandle(veh.Handle);
if (v==null) { EntityPool.Add(v=new SyncedVehicle(veh)); }
return v;
}
public static byte GetPlayerRadioIndex()
{
return (byte)Function.Call<int>(Hash.GET_PLAYER_RADIO_STATION_INDEX);
}
public static void SetPlayerRadioIndex(int index)
{
Function.Call(Hash.SET_RADIO_TO_STATION_INDEX, index);
}
public static byte[] GetHash(this string inputString)
{
using (HashAlgorithm algorithm = SHA256.Create())
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
}
const UInt32 WM_KEYDOWN = 0x0100;
public static void Reload()
{
string reloadKey="None";
var lines = File.ReadAllLines("ScriptHookVDotNet.ini");
foreach (var l in lines)
{
var ss = l.Split('=');
if(ss.Length > 0 && ss[0]=="ReloadKey")
{
reloadKey = ss[1];
}
}
var lineList = lines.ToList();
if (reloadKey=="None")
{
foreach (var l in lines)
{
var ss = l.Split('=');
if (ss.Length > 0 && ss[0]=="ReloadKey")
{
reloadKey = ss[1];
lineList.Remove(l);
}
}
lineList.Add("ReloadKey=Insert");
File.WriteAllLines("ScriptHookVDotNet.ini",lineList.ToArray());
}
Keys key = (Keys)Enum.Parse(typeof(Keys), reloadKey, true);
PostMessage(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle, WM_KEYDOWN, (int)key, 0);
}
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
[DllImport("kernel32.dll")]
public static extern ulong GetTickCount64();
}
}

View File

@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA;
using GTA.Native;
using RageCoop.Core;
using GTA.Math;
namespace RageCoop.Client
{
internal static class VehicleExtensions
{
#region VEHICLE
public static VehicleDataFlags GetVehicleFlags(this Vehicle veh)
{
VehicleDataFlags flags = 0;
if (veh.IsEngineRunning)
{
flags |= VehicleDataFlags.IsEngineRunning;
}
if (veh.AreLightsOn)
{
flags |= VehicleDataFlags.AreLightsOn;
}
if (veh.BrakePower >= 0.01f)
{
flags |= VehicleDataFlags.AreBrakeLightsOn;
}
if (veh.AreHighBeamsOn)
{
flags |= VehicleDataFlags.AreHighBeamsOn;
}
if (veh.IsSirenActive)
{
flags |= VehicleDataFlags.IsSirenActive;
}
if (veh.IsDead)
{
flags |= VehicleDataFlags.IsDead;
}
if (Function.Call<bool>(Hash.IS_HORN_ACTIVE, veh.Handle))
{
flags |= VehicleDataFlags.IsHornActive;
}
if (veh.IsSubmarineCar && Function.Call<bool>(Hash._GET_IS_SUBMARINE_VEHICLE_TRANSFORMED, veh.Handle))
{
flags |= VehicleDataFlags.IsTransformed;
}
if (veh.HasRoof && (veh.RoofState == VehicleRoofState.Opened || veh.RoofState == VehicleRoofState.Opening))
{
flags |= VehicleDataFlags.RoofOpened;
}
if (veh.IsAircraft)
{
flags |= VehicleDataFlags.IsAircraft;
}
if (veh.Model.Hash==1483171323 && veh.IsDeluxoHovering())
{
flags|= VehicleDataFlags.IsDeluxoHovering;
}
if (veh.HasRoof)
{
flags|=VehicleDataFlags.HasRoof;
}
return flags;
}
public static Dictionary<int, int> GetVehicleMods(this VehicleModCollection mods)
{
Dictionary<int, int> result = new Dictionary<int, int>();
foreach (VehicleMod mod in mods.ToArray())
{
result.Add((int)mod.Type, mod.Index);
}
return result;
}
public static VehicleDamageModel GetVehicleDamageModel(this Vehicle veh)
{
// Broken windows
byte brokenWindows = 0;
for (int i = 0; i < 8; i++)
{
if (!veh.Windows[(VehicleWindowIndex)i].IsIntact)
{
brokenWindows |= (byte)(1 << i);
}
}
// Broken doors
byte brokenDoors = 0;
byte openedDoors = 0;
foreach (VehicleDoor door in veh.Doors)
{
if (door.IsBroken)
{
brokenDoors |= (byte)(1 << (byte)door.Index);
}
else if (door.IsOpen)
{
openedDoors |= (byte)(1 << (byte)door.Index);
}
}
// Bursted tires
short burstedTires = 0;
foreach (VehicleWheel wheel in veh.Wheels.GetAllWheels())
{
if (wheel.IsBursted)
{
burstedTires |= (short)(1 << (int)wheel.BoneId);
}
}
return new VehicleDamageModel()
{
BrokenDoors = brokenDoors,
OpenedDoors = openedDoors,
BrokenWindows = brokenWindows,
BurstedTires = burstedTires,
LeftHeadLightBroken = (byte)(veh.IsLeftHeadLightBroken ? 1 : 0),
RightHeadLightBroken = (byte)(veh.IsRightHeadLightBroken ? 1 : 0)
};
}
public static void SetDamageModel(this Vehicle veh, VehicleDamageModel model, bool leavedoors = true)
{
for (int i = 0; i < 8; i++)
{
var door = veh.Doors[(VehicleDoorIndex)i];
if ((model.BrokenDoors & (byte)(1 << i)) != 0)
{
if (!door.IsBroken)
{
door.Break(leavedoors);
}
}
else if (door.IsBroken)
{
// The vehicle can only fix a door if the vehicle was completely fixed
veh.Repair();
return;
}
if ((model.OpenedDoors & (byte)(1 << i)) != 0)
{
if ((!door.IsOpen)&&(!door.IsBroken))
{
door.Open();
}
}
else if (door.IsOpen)
{
if (!door.IsBroken) { door.Close(); }
}
if ((model.BrokenWindows & (byte)(1 << i)) != 0)
{
veh.Windows[(VehicleWindowIndex)i].Smash();
}
else if (!veh.Windows[(VehicleWindowIndex)i].IsIntact)
{
veh.Windows[(VehicleWindowIndex)i].Repair();
}
}
foreach (VehicleWheel wheel in veh.Wheels)
{
if ((model.BurstedTires & (short)(1 << (int)wheel.BoneId)) != 0)
{
if (!wheel.IsBursted)
{
wheel.Puncture();
wheel.Burst();
}
}
else if (wheel.IsBursted)
{
wheel.Fix();
}
}
veh.IsLeftHeadLightBroken = model.LeftHeadLightBroken > 0;
veh.IsRightHeadLightBroken = model.RightHeadLightBroken > 0;
}
public static Dictionary<int, int> GetPassengers(this Vehicle veh)
{
Dictionary<int, int> ps = new Dictionary<int, int>();
var d = veh.Driver;
if (d!=null&&d.IsSittingInVehicle())
{
ps.Add(-1, d.GetSyncEntity().ID);
}
foreach (Ped p in veh.Passengers)
{
if (p.IsSittingInVehicle())
{
ps.Add((int)p.SeatIndex, (int)p.GetSyncEntity().ID);
}
}
return ps;
}
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
{
Function.Call(Hash._SET_VEHICLE_HOVER_TRANSFORM_PERCENTAGE, deluxo, hover ? 1f : 0f);
}
public static bool IsDeluxoHovering(this Vehicle deluxo)
{
return Math.Abs(deluxo.Bones[27].ForwardVector.GetCosTheta(deluxo.ForwardVector)-1)>0.05;
}
public static void SetDeluxoWingRatio(this Vehicle v, float ratio)
{
Function.Call(Hash._SET_SPECIALFLIGHT_WING_RATIO, v, ratio);
}
public static float GetDeluxoWingRatio(this Vehicle v)
{
return v.Bones[99].Position.DistanceTo(v.Bones[92].Position)-1.43f;
}
public static float GetNozzleAngel(this Vehicle plane)
{
return Function.Call<float>(Hash._GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane);
}
public static bool HasNozzle(this Vehicle v)
{
switch (v.Model.Hash)
{
// Hydra
case 970385471:
return true;
// Avenger
case -2118308144:
return true;
// Tula
case 1043222410:
return true;
// Avenger
case 408970549:
return true;
}
return false;
}
public static void SetNozzleAngel(this Vehicle plane, float ratio)
{
Function.Call(Hash.SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
}
#endregion
}
}

View File

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA;
using GTA.Native;
using GTA.Math;
namespace RageCoop.Client
@ -20,6 +21,23 @@ namespace RageCoop.Client
}
internal static class WeaponUtil
{
public static Dictionary<uint, bool> GetWeaponComponents(this Weapon weapon)
{
Dictionary<uint, bool> result = null;
if (weapon.Components.Count > 0)
{
result = new Dictionary<uint, bool>();
foreach (var comp in weapon.Components)
{
result.Add((uint)comp.ComponentHash, comp.Active);
}
}
return result;
}
public static Vector3 GetMuzzlePosition(this Ped p)
{
var w = p.Weapons.CurrentWeaponObject;
@ -31,29 +49,152 @@ namespace RageCoop.Client
}
return p.Bones[Bone.SkelRightHand].Position;
}
static long BulletsShot=0;
public static float GetWeaponDamage(this Ped P, uint hash)
{
var comp = P.Weapons.Current.Components.GetSuppressorComponent();
return Function.Call<float>(Hash.GET_WEAPON_DAMAGE, hash, comp.Active ? comp.ComponentHash : WeaponComponentHash.Invalid);
/*
if (P.IsInVehicle() && (hash!=(uint)P.Weapons.Current.Hash))
{
// This is a vehicle weapon
P.VehicleWeapon=(VehicleWeaponHash)hash;
return 100;
}
switch (P.Weapons.Current.Group)
{
case WeaponGroup.Pistol: return 30;
case WeaponGroup.AssaultRifle: return 30;
case WeaponGroup.SMG: return 20;
case WeaponGroup.MG: return 40;
case WeaponGroup.Shotgun: return 30;
case WeaponGroup.Sniper: return 200;
case WeaponGroup.Heavy: return 30;
}
return 0;
*/
}
public static MuzzleInfo GetMuzzleInfo(this Vehicle v)
{
BulletsShot++;
int i;
switch (v.Model.Hash)
{
// JB7002
case 394110044:
i=BulletsShot%2==0 ? 54 : 53;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// DOMINATOR5
case -1375060657:
i=BulletsShot%2==0 ? 35 : 36;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// IMPALER3
case -1924800695:
i=BulletsShot%2==0 ? 75 : 76;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// IMPERATOR2
case 1637620610:
i=BulletsShot%2==0 ? 97 : 99;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// SLAMVAN5
case 373261600:
i=BulletsShot%2==0 ? 51 : 53;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// RUINER2
case 941494461:
i=BulletsShot%2==0 ? 65 : 66;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// TAMPA3
case -1210451983:
return new MuzzleInfo(v.Bones[87].Position, v.Bones[87].ForwardVector);
// SCRAMJET
case -638562243:
i=BulletsShot%2==0 ? 44 : 45;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// VIGILANTE
case -1242608589:
i=BulletsShot%2==0 ? 42 : 43;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// ZR380
case 540101442:
i=BulletsShot%2==0 ? 57 : 63;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// ZR3802
case -1106120762:
i=BulletsShot%2==0 ? 57 : 63;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// ZR3803
case -1478704292:
i=BulletsShot%2==0 ? 53 : 59;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// STROMBERG
case 886810209:
i=BulletsShot%2==0 ? 85 : 84;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// SLAMVAN4
case -2061049099:
i=BulletsShot%2==0 ? 76 : 78;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// IMPERATOR
case 444994115:
i=BulletsShot%2==0 ? 88 : 86;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// IMPALER2
case 1009171724:
i=BulletsShot%2==0 ? 63 : 64;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// DOMINATOR4
case -688189648:
i=BulletsShot%2==0 ? 59 : 60;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// SAVAGE
case -82626025:
return new MuzzleInfo(v.Bones[30].Position, v.Bones[30].ForwardVector);
// BUZZARD
case 788747387:
i=Main.Ticked%2==0 ? 28 : 23;
i=BulletsShot%2==0 ? 28 : 23;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// ANNIHL
case 837858166:
i=(int)Main.Ticked%4+35;
i=(int)BulletsShot%4+35;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// HYDRA
case 970385471:
i=Main.Ticked%2==0 ? 29 : 28;
i=BulletsShot%2==0 ? 29 : 28;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// STARLING
case -1700874274:
i=Main.Ticked%2==0 ? 24 : 12;
i=BulletsShot%2==0 ? 24 : 12;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// RHINO
@ -78,25 +219,7 @@ namespace RageCoop.Client
return w.Group==WeaponGroup.Thrown || ProjectileWeapons.Contains(w.Hash);
}
}
public static int GetDamage(this Weapon w)
{
int damage = 0;
switch (w.Group)
{
case WeaponGroup.AssaultRifle: damage=30; break;
case WeaponGroup.Heavy: damage=30; break;
case WeaponGroup.MG: damage=30; break;
case WeaponGroup.PetrolCan: damage=0; break;
case WeaponGroup.Pistol: damage=30; break;
case WeaponGroup.Shotgun: damage=30; break;
case WeaponGroup.SMG: damage=20; break;
case WeaponGroup.Sniper: damage=100; break;
case WeaponGroup.Thrown: damage=0; break;
case WeaponGroup.Unarmed: damage=0; break;
}
return damage;
}
public static readonly Dictionary<WeaponHash, int> MuzzleBoneIndexes = new Dictionary<WeaponHash, int>
{
{WeaponHash.HeavySniper,6},
@ -181,6 +304,8 @@ namespace RageCoop.Client
VehicleWeaponHash.PlaneRocket,
VehicleWeaponHash.SpaceRocket,
VehicleWeaponHash.Tank,
(VehicleWeaponHash)3565779982, // STROMBERG missiles
(VehicleWeaponHash)3169388763, // SCRAMJET missiles
};
}
}

View File

@ -49,7 +49,7 @@ namespace RageCoop.Client
// Values of 2.0 or more make for very aggressive waves like you see during a thunderstorm.
Function.Call(Hash.SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, Game.Player.Character.Handle, true, false);
// Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, Game.Player.Character.Handle, true, false);
if (Main.Settings==null) { return; }
if (Main.Settings.DisableTraffic)
{
@ -99,8 +99,8 @@ namespace RageCoop.Client
Function.Call(Hash.SET_RANDOM_BOATS, false);
Function.Call(Hash.SET_GARBAGE_TRUCKS, false);
Function.Call(Hash.DELETE_ALL_TRAINS);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 3);
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 3);
Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
Function.Call(Hash.SET_FAR_DRAW_VEHICLES, false);
Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, 0);
@ -111,7 +111,7 @@ namespace RageCoop.Client
foreach (Ped ped in World.GetAllPeds())
{
SyncedPed c = EntityPool.GetPedByHandle(ped.Handle);
if ((c==null) || (c.IsMine && (ped.Handle!=Game.Player.Character.Handle)&&ped.PopulationType!=EntityPopulationType.Mission))
if ((c==null) || (c.IsLocal && (ped.Handle!=Game.Player.Character.Handle)&&ped.PopulationType!=EntityPopulationType.Mission))
{
if (ped.Handle==Game.Player.Character.Handle) { continue; }
@ -126,7 +126,12 @@ namespace RageCoop.Client
foreach (Vehicle veh in World.GetAllVehicles())
{
SyncedVehicle v = veh.GetSyncEntity();
if((v== null) || (v.IsMine&&veh.PopulationType!=EntityPopulationType.Mission))
if (v.MainVehicle==Game.Player.LastVehicle)
{
// Don't delete player's vehicle
continue;
}
if((v== null) || (v.IsLocal&&veh.PopulationType!=EntityPopulationType.Mission))
{
Main.Logger.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");

BIN
RageCoop.Client/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,10 +1,11 @@
using System;
using System.Text;
using System.Linq;
using GTA.Math;
namespace RageCoop.Core
{
public class BitReader
internal class BitReader
{
public int CurrentIndex { get; set; }
@ -49,11 +50,15 @@ namespace RageCoop.Core
public byte[] ReadByteArray(int length)
{
byte[] value = ResultArray.Skip(CurrentIndex).Take(length).ToArray();
byte[] value = new byte[length];
Array.Copy(ResultArray, CurrentIndex,value,0,length);
CurrentIndex += length;
return value;
}
public byte[] ReadByteArray()
{
return ReadByteArray(ReadInt());
}
public short ReadShort()
{
short value = BitConverter.ToInt16(ResultArray, CurrentIndex);
@ -102,19 +107,34 @@ namespace RageCoop.Core
CurrentIndex += index;
return value;
}
public LVector3 ReadLVector3()
public string ReadString()
{
return new LVector3()
var len = ReadInt();
string value = Encoding.UTF8.GetString(ResultArray.Skip(CurrentIndex).Take(len).ToArray());
CurrentIndex += len;
return value;
}
public Vector3 ReadVector3()
{
return new Vector3()
{
X = ReadFloat(),
Y = ReadFloat(),
Z = ReadFloat()
};
}
public LQuaternion ReadLQuaternion()
public Vector2 ReadVector2()
{
return new LQuaternion()
return new Vector2()
{
X = ReadFloat(),
Y = ReadFloat()
};
}
public Quaternion ReadQuaternion()
{
return new Quaternion()
{
X = ReadFloat(),
Y = ReadFloat(),

287
RageCoop.Core/CoreUtils.cs Normal file
View File

@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA.Math;
using System.Security.Cryptography;
using System.Net;
using System.IO;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")]
namespace RageCoop.Core
{
internal class CoreUtils
{
public static (byte, byte[]) GetBytesFromObject(object obj)
{
switch (obj)
{
case byte _:
return (0x01, new byte[] { (byte)obj });
case short _:
return (0x02, BitConverter.GetBytes((short)obj));
case ushort _:
return (0x03, BitConverter.GetBytes((ushort)obj));
case int _:
return (0x04, BitConverter.GetBytes((int)obj));
case uint _:
return (0x05, BitConverter.GetBytes((uint)obj));
case long _:
return (0x06, BitConverter.GetBytes((long)obj));
case ulong _:
return (0x07, BitConverter.GetBytes((ulong)obj));
case float _:
return (0x08, BitConverter.GetBytes((float)obj));
case bool _:
return (0x09, BitConverter.GetBytes((bool)obj));
case string _:
return (0x10, ((string)obj).GetBytesWithLength());
case Vector3 _:
return (0x11,((Vector3)obj).GetBytes());
case Quaternion _:
return (0x12, ((Quaternion)obj).GetBytes());
case GTA.Model _:
return (0x13, BitConverter.GetBytes((GTA.Model)obj));
case Vector2 _:
return (0x14, ((Vector2)obj).GetBytes());
case Tuple<byte, byte[]> _:
var tup = (Tuple<byte, byte[]>)obj;
return (tup.Item1, tup.Item2);
default:
return (0x0, null);
}
}
}
internal static class Extensions
{
public static void AddVector3(this List<byte> bytes, Vector3 vec3)
{
bytes.AddRange(BitConverter.GetBytes(vec3.X));
bytes.AddRange(BitConverter.GetBytes(vec3.Y));
bytes.AddRange(BitConverter.GetBytes(vec3.Z));
}
public static void AddQuaternion(this List<byte> bytes, Quaternion quat)
{
bytes.AddRange(BitConverter.GetBytes(quat.X));
bytes.AddRange(BitConverter.GetBytes(quat.Y));
bytes.AddRange(BitConverter.GetBytes(quat.Z));
bytes.AddRange(BitConverter.GetBytes(quat.W));
}
public static void AddInt(this List<byte> bytes,int i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUint(this List<byte> bytes, uint i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddShort(this List<byte> bytes, short i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUshort(this List<byte> bytes, ushort i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddLong(this List<byte> bytes, long i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddUlong(this List<byte> bytes, ulong i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddFloat(this List<byte> bytes, float i)
{
bytes.AddRange(BitConverter.GetBytes(i));
}
public static void AddString(this List<byte> bytes, string s)
{
var sb = Encoding.UTF8.GetBytes(s);
bytes.AddInt(sb.Length);
bytes.AddRange(sb);
}
public static void AddArray(this List<byte> bytes, byte[] toadd)
{
bytes.AddInt(toadd.Length);
bytes.AddRange(toadd);
}
public static int GetHash(string s)
{
MD5 md5Hasher = MD5.Create();
var hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(s));
return BitConverter.ToInt32(hashed, 0);
}
public static byte[] GetBytes(this string s)
{
return Encoding.UTF8.GetBytes(s);
}
public static byte[] GetBytesWithLength(this string s)
{
var data = new List<byte>(100);
var sb = Encoding.UTF8.GetBytes(s);
data.AddInt(sb.Length);
data.AddRange(sb);
return data.ToArray();
}
public static string GetString(this byte[] data)
{
return Encoding.UTF8.GetString(data);
}
public static byte[] GetBytes(this Vector3 vec)
{
// 12 bytes
return new List<byte[]>() { BitConverter.GetBytes(vec.X), BitConverter.GetBytes(vec.Y), BitConverter.GetBytes(vec.Z) }.Join(4);
}
public static byte[] GetBytes(this Vector2 vec)
{
// 8 bytes
return new List<byte[]>() { BitConverter.GetBytes(vec.X), BitConverter.GetBytes(vec.Y) }.Join(4);
}
/// <summary>
///
/// </summary>
/// <param name="qua"></param>
/// <returns>An array of bytes with length 16</returns>
public static byte[] GetBytes(this Quaternion qua)
{
// 16 bytes
return new List<byte[]>() { BitConverter.GetBytes(qua.X), BitConverter.GetBytes(qua.Y), BitConverter.GetBytes(qua.Z), BitConverter.GetBytes(qua.W) }.Join(4);
}
public static bool HasPedFlag(this PedDataFlags flagToCheck, PedDataFlags flag)
{
return (flagToCheck & flag)!=0;
}
public static bool HasVehFlag(this VehicleDataFlags flagToCheck, VehicleDataFlags flag)
{
return (flagToCheck & flag)!=0;
}
public static bool HasConfigFlag(this PlayerConfigFlags flagToCheck, PlayerConfigFlags flag)
{
return (flagToCheck & flag)!=0;
}
public static Type GetActualType(this TypeCode code)
{
switch (code)
{
case TypeCode.Boolean:
return typeof(bool);
case TypeCode.Byte:
return typeof(byte);
case TypeCode.Char:
return typeof(char);
case TypeCode.DateTime:
return typeof(DateTime);
case TypeCode.DBNull:
return typeof(DBNull);
case TypeCode.Decimal:
return typeof(decimal);
case TypeCode.Double:
return typeof(double);
case TypeCode.Empty:
return null;
case TypeCode.Int16:
return typeof(short);
case TypeCode.Int32:
return typeof(int);
case TypeCode.Int64:
return typeof(long);
case TypeCode.Object:
return typeof(object);
case TypeCode.SByte:
return typeof(sbyte);
case TypeCode.Single:
return typeof(Single);
case TypeCode.String:
return typeof(string);
case TypeCode.UInt16:
return typeof(UInt16);
case TypeCode.UInt32:
return typeof(UInt32);
case TypeCode.UInt64:
return typeof(UInt64);
}
return null;
}
public static string DumpWithType(this IEnumerable<object> objects)
{
StringBuilder sb = new StringBuilder();
foreach(var obj in objects)
{
sb.Append(obj.GetType()+":"+obj.ToString()+"\n");
}
return sb.ToString();
}
public static string Dump<T>(this IEnumerable<T> objects)
{
return "{"+string.Join(",",objects)+"}";
}
public static void ForEach<T>(this IEnumerable<T> objects,Action<T> action)
{
foreach(var obj in objects)
{
action(obj);
}
}
public static byte[] ReadToEnd(this Stream stream)
{
if (stream is MemoryStream)
return ((MemoryStream)stream).ToArray();
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
public static byte[] Join(this List<byte[]> arrays,int lengthPerArray=-1)
{
if (arrays.Count==1) { return arrays[0]; }
var output = lengthPerArray== -1 ? new byte[arrays.Sum(arr => arr.Length)] : new byte[arrays.Count*lengthPerArray];
int writeIdx = 0;
foreach (var byteArr in arrays)
{
byteArr.CopyTo(output, writeIdx);
writeIdx += byteArr.Length;
}
return output;
}
public static bool IsSubclassOf(this Type type, string baseTypeName)
{
for (Type t = type.BaseType; t != null; t = t.BaseType)
if (t.FullName == baseTypeName)
return true;
return false;
}
}
}

239
RageCoop.Core/Logger.cs Normal file
View File

@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace RageCoop.Core
{
/// <summary>
///
/// </summary>
public class Logger : IDisposable
{
/// <summary>
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 0;
/// <summary>
/// Name of this logger
/// </summary>
public string Name { get; set; }
/// <summary>
/// Path to log file.
/// </summary>
public string LogPath;
/// <summary>
/// Whether to flush messages to console instead of log file
/// </summary>
public bool UseConsole = false;
private StreamWriter logWriter;
private string Buffer = "";
private Thread LoggerThread;
private bool Stopping = false;
private bool FlushImmediately;
internal Logger(bool flushImmediately = false, bool overwrite = true)
{
FlushImmediately = flushImmediately;
if (File.Exists(LogPath)&&overwrite) { File.Delete(LogPath); }
Name=Process.GetCurrentProcess().Id.ToString();
if (!flushImmediately)
{
LoggerThread=new Thread(() =>
{
if (!UseConsole)
{
while (LogPath==default)
{
Thread.Sleep(100);
}
if (File.Exists(LogPath)&&overwrite) { File.Delete(LogPath); }
}
while (!Stopping)
{
Flush();
Thread.Sleep(1000);
}
Flush();
});
LoggerThread.Start();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Info(string message)
{
if (LogLevel>2) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [INF] {1}", Date(), message, Name);
Buffer+=msg+"\r\n";
}
if (FlushImmediately)
{
Flush();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Warning(string message)
{
if (LogLevel>3) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [WRN] {1}", Date(), message, Name);
Buffer+=msg+"\r\n";
}
if (FlushImmediately)
{
Flush();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Error(string message)
{
if (LogLevel>4) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [ERR] {1}", Date(), message, Name);
Buffer+=msg+"\r\n";
}
if (FlushImmediately)
{
Flush();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="error"></param>
public void Error(string message, Exception error)
{
if (LogLevel>4) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [ERR] {1}:{3}", Date(), message, Name,error.Message);
Buffer+=msg+"\r\n";
Trace(error.ToString());
}
if (FlushImmediately)
{
Flush();
}
}
/// <summary>
///
/// </summary>
/// <param name="ex"></param>
public void Error(Exception ex)
{
if (LogLevel>4) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [ERR] {1}", Date(), "\r\n"+ex.Message, Name);
Buffer+=msg+"\r\n";
Trace(ex.ToString());
}
if (FlushImmediately)
{
Flush();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Debug(string message)
{
if (LogLevel>1) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [DBG] {1}", Date(), message, Name);
Buffer+=msg+"\r\n";
}
if (FlushImmediately)
{
Flush();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Trace(string message)
{
if (LogLevel>0) { return; }
lock (Buffer)
{
string msg = string.Format("[{0}][{2}] [TRC] {1}", Date(), message, Name);
Buffer+=msg+"\r\n";
}
if (FlushImmediately)
{
Flush();
}
}
private string Date()
{
return DateTime.Now.ToString();
}
/// <summary>
///
/// </summary>
public void Flush()
{
lock (Buffer)
{
if (Buffer!="")
{
if (UseConsole)
{
Console.Write(Buffer);
Buffer="";
}
else
{
try
{
logWriter=new StreamWriter(LogPath, true, Encoding.UTF8);
logWriter.Write(Buffer);
logWriter.Close();
Buffer="";
}
catch { }
}
}
}
}
/// <summary>
/// Stop backdround thread and flush all pending messages.
/// </summary>
public void Dispose()
{
Stopping=true;
LoggerThread?.Join();
}
}
}

View File

@ -0,0 +1,152 @@
using System;
using GTA.Math;
namespace RageCoop.Core
{
internal static class MathExtensions
{
public const float Deg2Rad=(float)(Math.PI* 2) / 360;
public const float Rad2Deg = 360 / (float)(Math.PI * 2);
/// <summary>
///
/// </summary>
public static Vector3 ToVector(this Quaternion vec)
{
return new Vector3()
{
X = vec.X,
Y = vec.Y,
Z = vec.Z
};
}
/// <summary>
///
/// </summary>
public static Quaternion ToQuaternion(this Vector3 vec, float vW = 0.0f)
{
return new Quaternion()
{
X = vec.X,
Y = vec.Y,
Z = vec.Z,
W = vW
};
}
public static float Denormalize(this float h)
{
return h < 0f ? h + 360f : h;
}
public static float ToRadians(this float val)
{
return (float)(Math.PI / 180) * val;
}
public static Vector3 ToRadians(this Vector3 i)
{
return new Vector3()
{
X = ToRadians(i.X),
Y = ToRadians(i.Y),
Z = ToRadians(i.Z),
};
}
public static Quaternion ToQuaternion(this Vector3 vect)
{
vect = new Vector3()
{
X = vect.X.Denormalize() * -1,
Y = vect.Y.Denormalize() - 180f,
Z = vect.Z.Denormalize() - 180f,
};
vect = vect.ToRadians();
float rollOver2 = vect.Z * 0.5f;
float sinRollOver2 = (float)Math.Sin((double)rollOver2);
float cosRollOver2 = (float)Math.Cos((double)rollOver2);
float pitchOver2 = vect.Y * 0.5f;
float sinPitchOver2 = (float)Math.Sin((double)pitchOver2);
float cosPitchOver2 = (float)Math.Cos((double)pitchOver2);
float yawOver2 = vect.X * 0.5f; // pitch
float sinYawOver2 = (float)Math.Sin((double)yawOver2);
float cosYawOver2 = (float)Math.Cos((double)yawOver2);
Quaternion result = new Quaternion()
{
X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2,
Y = cosYawOver2 * cosPitchOver2 * sinRollOver2 - sinYawOver2 * sinPitchOver2 * cosRollOver2,
Z = cosYawOver2 * sinPitchOver2 * cosRollOver2 + sinYawOver2 * cosPitchOver2 * sinRollOver2,
W = sinYawOver2 * cosPitchOver2 * cosRollOver2 - cosYawOver2 * sinPitchOver2 * sinRollOver2
};
return result;
}
public static double DegToRad(double deg)
{
return deg * Math.PI / 180.0;
}
public static Vector3 ToEulerRotation(this Vector3 dir, Vector3 up)
{
var rot = Quaternion.LookRotation(dir.Normalized, up).ToEulerAngles().ToDegree();
return rot;
}
public static Vector3 ToDegree(this Vector3 radian)
{
return radian*(float)(180/Math.PI);
}
public static Vector3 ToEulerAngles(this Quaternion q)
{
Vector3 angles = new Vector3();
// roll / x
double sinr_cosp = 2 * (q.W * q.X + q.Y * q.Z);
double cosr_cosp = 1 - 2 * (q.X * q.X + q.Y * q.Y);
angles.X = (float)Math.Atan2(sinr_cosp, cosr_cosp);
// pitch / y
double sinp = 2 * (q.W * q.Y - q.Z * q.X);
if (Math.Abs(sinp) >= 1)
{
angles.Y = CopySign(Math.PI / 2, sinp);
}
else
{
angles.Y = (float)Math.Asin(sinp);
}
// yaw / z
double siny_cosp = 2 * (q.W * q.Z + q.X * q.Y);
double cosy_cosp = 1 - 2 * (q.Y * q.Y + q.Z * q.Z);
angles.Z = (float)Math.Atan2(siny_cosp, cosy_cosp);
return angles;
}
private static float CopySign(double x, double y)
{
bool isPositive = y>=0;
if (isPositive)
{
if (x>=0) { return (float)x; } else { return (float)-x; }
}
else
{
if (x>=0) { return (float)-x; } else { return (float)x; }
}
}
public static double AngelTo(this Vector3 v1, Vector3 v2)
{
return Math.Acos(v1.GetCosTheta(v2));
}
public static float GetCosTheta(this Vector3 v1, Vector3 v2)
{
return Vector3.Dot(v1, v2)/(v1.Length()*v2.Length());
}
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class CustomEvent : Packet
{
public CustomEvent(Func<byte,BitReader,object> onResolve = null,bool queued=false)
{
_resolve= onResolve;
_queued= queued;
}
private bool _queued;
private Func<byte, BitReader, object> _resolve { get; set; }
public int Hash { get; set; }
public bool IsStaged { get; set; }=false;
public object[] Args { get; set; }
public override void Pack(NetOutgoingMessage message)
{
Args= Args ?? new object[] { };
message.Write(_queued ? (byte)PacketType.CustomEventQueued: (byte)PacketType.CustomEvent);
List<byte> result = new List<byte>();
result.AddInt(Hash);
result.AddInt(Args.Length);
(byte, byte[]) tup;
foreach (var arg in Args)
{
tup=CoreUtils.GetBytesFromObject(arg);
if (tup.Item1==0||tup.Item2==null)
{
throw new ArgumentException($"Object of type {arg.GetType()} is not supported");
}
result.Add(tup.Item1);
result.AddRange(tup.Item2);
}
message.Write(result.Count);
message.Write(result.ToArray());
}
public override void Unpack(byte[] array)
{
BitReader reader = new BitReader(array);
Hash = reader.ReadInt();
var len=reader.ReadInt();
Args=new object[len];
for (int i = 0; i < len; i++)
{
byte type = reader.ReadByte();
switch (type)
{
case 0x01:
Args[i]=reader.ReadByte(); break;
case 0x02:
Args[i]=reader.ReadShort(); break;
case 0x03:
Args[i]=reader.ReadUShort(); break;
case 0x04:
Args[i]=reader.ReadInt(); break;
case 0x05:
Args[i]=reader.ReadUInt(); break;
case 0x06:
Args[i]=reader.ReadLong(); break;
case 0x07:
Args[i]=reader.ReadULong(); break;
case 0x08:
Args[i]=reader.ReadFloat(); break;
case 0x09:
Args[i]=reader.ReadBool(); break;
case 0x10:
Args[i]=reader.ReadString(); break;
case 0x11:
Args[i]=reader.ReadVector3(); break;
case 0x12:
Args[i]=reader.ReadQuaternion(); break;
case 0x13:
Args[i]=(GTA.Model)reader.ReadInt(); break;
case 0x14:
Args[i]=reader.ReadVector2(); break;
default:
if (_resolve==null)
{
throw new InvalidOperationException($"Unexpected type:{type}\r\n{array.Dump()}");
}
else
{
Args[i]=_resolve(type, reader); break;
}
}
}
}
}
}
}

View File

@ -6,28 +6,37 @@ using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal enum FileResponse:byte
{
public class FileTransferRequest : Packet
NeedToDownload=0,
AlreadyExists=1,
Completed=2,
Loaded=3,
LoadFailed=4,
}
internal partial class Packets
{
internal class FileTransferRequest : Packet
{
public byte ID { get; set; }
public int ID { get; set; }
public string FileName { get; set; }
public string Name { get; set; }
public long FileLength { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.FileTransferRequest);
message.Write((byte)PacketType.FileTransferRequest);
List<byte> byteArray = new List<byte>();
// The ID from the download
byteArray.Add(ID);
byteArray.AddInt(ID);
// The name of the file
byte[] nameBytes = Encoding.UTF8.GetBytes(FileName);
byte[] nameBytes = Encoding.UTF8.GetBytes(Name);
byteArray.AddRange(BitConverter.GetBytes(nameBytes.Length));
byteArray.AddRange(nameBytes);
@ -46,29 +55,59 @@ namespace RageCoop.Core
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
ID = reader.ReadByte();
ID = reader.ReadInt();
int nameArrayLength = reader.ReadInt();
FileName = reader.ReadString(nameArrayLength);
Name = reader.ReadString(nameArrayLength);
FileLength = reader.ReadLong();
#endregion
}
}
public class FileTransferTick : Packet
internal class FileTransferResponse : Packet
{
public byte ID { get; set; }
public int ID { get; set; }
public FileResponse Response { get; set; }
public override void Pack(NetOutgoingMessage message)
{
message.Write((byte)PacketType.FileTransferResponse);
List<byte> byteArray = new List<byte>();
// The ID from the download
byteArray.AddInt(ID);
byteArray.Add((byte)Response);
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
}
public override void Unpack(byte[] array)
{
BitReader reader = new BitReader(array);
ID = reader.ReadInt();
Response = (FileResponse)reader.ReadByte();
}
}
internal class FileTransferChunk : Packet
{
public int ID { get; set; }
public byte[] FileChunk { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.FileTransferTick);
message.Write((byte)PacketType.FileTransferChunk);
List<byte> byteArray = new List<byte>();
// The ID from the download
byteArray.Add(ID);
byteArray.AddInt(ID);
// The chunk of the file
byteArray.AddRange(BitConverter.GetBytes(FileChunk.Length));
@ -86,26 +125,26 @@ namespace RageCoop.Core
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
ID = reader.ReadByte();
ID = reader.ReadInt();
int chunkLength = reader.ReadInt();
FileChunk = reader.ReadByteArray(chunkLength);
#endregion
}
}
public class FileTransferComplete : Packet
internal class FileTransferComplete : Packet
{
public byte ID { get; set; }
public int ID { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.FileTransferComplete);
message.Write((byte)PacketType.FileTransferComplete);
List<byte> byteArray = new List<byte>();
// The ID from the download
byteArray.Add(ID);
// The ID for the download
byteArray.AddInt(ID);
byte[] result = byteArray.ToArray();
@ -119,7 +158,24 @@ namespace RageCoop.Core
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
ID = reader.ReadByte();
ID = reader.ReadInt();
#endregion
}
}
internal class AllResourcesSent : Packet
{
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.AllResourcesSent);
message.Write(0);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
#endregion
}
}

View File

@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
using Newtonsoft.Json;
using GTA.Math;
namespace RageCoop.Core
{
internal enum PacketType:byte
{
Handshake=0,
PlayerConnect=1,
PlayerDisconnect=2,
PlayerInfoUpdate=3,
PublicKeyRequest=4,
PublicKeyResponse=5,
Request=6,
Response=7,
ChatMessage=10,
// NativeCall=11,
// NativeResponse=12,
// Mod=13,
// CleanUpWorld=14,
FileTransferChunk=11,
FileTransferRequest=12,
FileTransferResponse = 13,
FileTransferComplete =14,
AllResourcesSent=15,
CustomEvent = 16,
CustomEventQueued = 17,
#region Sync
#region INTERVAL
VehicleSync = 20,
VehicleStateSync = 21,
PedSync = 22,
PedStateSync = 23,
ProjectileSync=24,
#endregion
#region EVENT
PedKilled=30,
BulletShot=31,
EnteringVehicle=32,
LeaveVehicle = 33,
EnteredVehicle=34,
OwnerChanged=35,
VehicleBulletShot = 36,
NozzleTransform=37,
#endregion
#endregion
Unknown=255
}
internal static class PacketExtensions
{
internal static bool IsSyncEvent(this PacketType p)
{
return (30<=(byte)p)&&((byte)p<=40);
}
}
internal enum ConnectionChannel
{
Default = 0,
Chat = 5,
Native = 6,
Mod = 7,
File = 8,
Event = 9,
RequestResponse=10,
VehicleSync=20,
PedSync=21,
ProjectileSync = 22,
SyncEvents =30,
}
[Flags]
internal enum PedDataFlags:ushort
{
None=0,
IsAiming = 1 << 0,
IsInStealthMode = 1 << 1,
IsReloading = 1 << 2,
IsJumping = 1 << 3,
IsRagdoll = 1 << 4,
IsOnFire = 1 << 5,
IsInParachuteFreeFall = 1 << 6,
IsParachuteOpen = 1 << 7,
IsOnLadder = 1 << 8,
IsVaulting = 1 << 9,
IsInCover = 1 << 10,
}
#region ===== VEHICLE DATA =====
internal enum VehicleDataFlags:ushort
{
None=0,
IsEngineRunning = 1 << 0,
AreLightsOn = 1 << 1,
AreBrakeLightsOn = 1 << 2,
AreHighBeamsOn = 1 << 3,
IsSirenActive = 1 << 4,
IsDead = 1 << 5,
IsHornActive = 1 << 6,
IsTransformed = 1 << 7,
RoofOpened = 1 << 8,
OnTurretSeat = 1 << 9,
IsAircraft = 1 << 10,
IsDeluxoHovering=1 << 11,
HasRoof=1 << 12,
}
internal enum PlayerConfigFlags : byte
{
None = 0,
ShowBlip= 1 << 0,
ShowNameTag= 1 << 1
}
internal struct VehicleDamageModel
{
public byte BrokenDoors { get; set; }
public byte OpenedDoors { get; set; }
public byte BrokenWindows { get; set; }
public short BurstedTires { get; set; }
public byte LeftHeadLightBroken { get; set; }
public byte RightHeadLightBroken { get; set; }
}
#endregion
interface IPacket
{
void Pack(NetOutgoingMessage message);
void Unpack(byte[] array);
}
internal abstract class Packet : IPacket
{
public abstract void Pack(NetOutgoingMessage message);
public abstract void Unpack(byte[] array);
}
internal partial class Packets
{
internal class ChatMessage : Packet
{
public string Username { get; set; }
public string Message { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.ChatMessage);
List<byte> byteArray = new List<byte>();
byte[] usernameBytes = Encoding.UTF8.GetBytes(Username);
byte[] messageBytes = Encoding.UTF8.GetBytes(Message);
// Write UsernameLength
byteArray.AddRange(BitConverter.GetBytes(usernameBytes.Length));
// Write Username
byteArray.AddRange(usernameBytes);
// Write MessageLength
byteArray.AddRange(BitConverter.GetBytes(messageBytes.Length));
// Write Message
byteArray.AddRange(messageBytes);
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
// Read username
int usernameLength = reader.ReadInt();
Username = reader.ReadString(usernameLength);
// Read message
int messageLength = reader.ReadInt();
Message = reader.ReadString(messageLength);
#endregion
}
}
}
internal static class CoopSerializer
{
/// <summary>
/// ?
/// </summary>
public static byte[] Serialize(this object obj)
{
if (obj == null)
{
return null;
}
string jsonString = JsonConvert.SerializeObject(obj);
return System.Text.Encoding.UTF8.GetBytes(jsonString);
}
/// <summary>
/// ?
/// </summary>
public static T Deserialize<T>(this byte[] bytes) where T : class
{
if (bytes == null)
{
return null;
}
var jsonString = System.Text.Encoding.UTF8.GetString(bytes);
return JsonConvert.DeserializeObject<T>(jsonString);
}
}
}

View File

@ -1,36 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
using GTA.Math;
using GTA;
using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
/// <summary>
/// For non-critical properties, synced every 20 frames.
/// </summary>
public class PedStateSync : Packet
internal class PedStateSync : Packet
{
public int ID { get; set; }
public int ModelHash { get; set; }
public Dictionary<byte, short> Clothes { get; set; }
public byte[] Clothes { get; set; }
public int OwnerID { get; set; }
public Dictionary<uint, bool> WeaponComponents { get; set; }
public byte WeaponTint { get;set; }
public BlipColor BlipColor { get; set; } = (BlipColor)255;
public BlipSprite BlipSprite { get; set; }= 0;
public float BlipScale { get; set; } = 1;
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PedStateSync);
message.Write((byte)PacketType.PedStateSync);
List<byte> byteArray = new List<byte>();
@ -40,15 +44,7 @@ namespace RageCoop.Core
// Write model hash
byteArray.AddInt(ModelHash);
// Write player clothes
// Write the count of clothes
byteArray.AddRange(BitConverter.GetBytes((ushort)Clothes.Count));
// Loop the dictionary and add the values
foreach (KeyValuePair<byte, short> cloth in Clothes)
{
byteArray.Add(cloth.Key);
byteArray.AddRange(BitConverter.GetBytes(cloth.Value));
}
byteArray.AddRange(Clothes);
//Write OwnerID for this ped
byteArray.AddRange(BitConverter.GetBytes(OwnerID));
@ -70,6 +66,14 @@ namespace RageCoop.Core
byteArray.Add(0x00);
}
byteArray.Add(WeaponTint);
byteArray.Add((byte)BlipColor);
if ((byte)BlipColor!=255)
{
byteArray.AddUshort((ushort)BlipSprite);
byteArray.AddFloat(BlipScale);
}
byte[] result = byteArray.ToArray();
message.Write(result.Length);
@ -89,16 +93,7 @@ namespace RageCoop.Core
ModelHash = reader.ReadInt();
// Read player clothes
// Create new Dictionary
Clothes = new Dictionary<byte, short>();
// Read the count of clothes
ushort clothCount = reader.ReadUShort();
// For clothCount
for (ushort i = 0; i < clothCount; i++)
{
// Read cloth value
Clothes.Add(reader.ReadByte(), reader.ReadShort());
}
Clothes =reader.ReadByteArray(36);
// Read ped OwnerID
OwnerID= reader.ReadInt();
@ -113,30 +108,38 @@ namespace RageCoop.Core
WeaponComponents.Add(reader.ReadUInt(), reader.ReadBool());
}
}
WeaponTint=reader.ReadByte();
BlipColor=(BlipColor)reader.ReadByte();
if ((byte)BlipColor!=255)
{
BlipSprite=(BlipSprite)reader.ReadUShort();
BlipScale=reader.ReadFloat();
}
#endregion
}
}
public class PedSync : Packet
internal class PedSync : Packet
{
public int ID { get; set; }
public PedDataFlags Flag { get; set; }
public int Health { get; set; }
public LVector3 Position { get; set; }
public Vector3 Position { get; set; }
public LVector3 Rotation { get; set; }
public Vector3 Rotation { get; set; }
public LVector3 Velocity { get; set; }
public Vector3 Velocity { get; set; }
public LVector3 RotationVelocity { get; set; }
public Vector3 RotationVelocity { get; set; }
public byte Speed { get; set; }
public LVector3 AimCoords { get; set; }
public Vector3 AimCoords { get; set; }
public uint CurrentWeaponHash { get; set; }
@ -145,7 +148,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PedSync);
message.Write((byte)PacketType.PedSync);
List<byte> byteArray = new List<byte>();
@ -160,17 +163,17 @@ namespace RageCoop.Core
byteArray.AddRange(BitConverter.GetBytes(Health));
// Write ped position
byteArray.AddLVector3(Position);
byteArray.AddVector3(Position);
// Write ped rotation
byteArray.AddLVector3(Rotation);
byteArray.AddVector3(Rotation);
// Write ped velocity
byteArray.AddLVector3(Velocity);
byteArray.AddVector3(Velocity);
if (Flag.HasFlag(PedDataFlags.IsRagdoll))
if (Flag.HasPedFlag(PedDataFlags.IsRagdoll))
{
byteArray.AddLVector3(RotationVelocity);
byteArray.AddVector3(RotationVelocity);
}
// Write ped speed
@ -179,10 +182,10 @@ namespace RageCoop.Core
// Write ped weapon hash
byteArray.AddRange(BitConverter.GetBytes(CurrentWeaponHash));
if (Flag.HasFlag(PedDataFlags.IsAiming))
if (Flag.HasPedFlag(PedDataFlags.IsAiming))
{
// Write ped aim coords
byteArray.AddLVector3(AimCoords);
byteArray.AddVector3(AimCoords);
}
byteArray.AddFloat(Heading);
@ -209,18 +212,18 @@ namespace RageCoop.Core
Health = reader.ReadInt();
// Read player position
Position = reader.ReadLVector3();
Position = reader.ReadVector3();
// Read player rotation
Rotation = reader.ReadLVector3();
Rotation = reader.ReadVector3();
// Read player velocity
Velocity = reader.ReadLVector3();
Velocity = reader.ReadVector3();
// Read rotation velocity if in ragdoll
if (Flag.HasFlag(PedDataFlags.IsRagdoll))
if (Flag.HasPedFlag(PedDataFlags.IsRagdoll))
{
RotationVelocity=reader.ReadLVector3();
RotationVelocity=reader.ReadVector3();
}
// Read player speed
@ -230,10 +233,10 @@ namespace RageCoop.Core
CurrentWeaponHash = reader.ReadUInt();
// Try to read aim coords
if (Flag.HasFlag(PedDataFlags.IsAiming))
if (Flag.HasPedFlag(PedDataFlags.IsAiming))
{
// Read player aim coords
AimCoords = reader.ReadLVector3();
AimCoords = reader.ReadVector3();
}
Heading=reader.ReadFloat();

View File

@ -6,9 +6,9 @@ using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class Handshake : Packet
internal class Handshake : Packet
{
public int PedID { get; set; }
@ -16,12 +16,25 @@ namespace RageCoop.Core
public string ModVersion { get; set; }
public bool NPCsAllowed { get; set; }
/// <summary>
/// The asymetrically crypted Aes key
/// </summary>
public byte[] AesKeyCrypted;
/// <summary>
/// The asymetrically crypted Aes IV
/// </summary>
public byte[] AesIVCrypted;
/// <summary>
/// The password hash with client Aes
/// </summary>
public byte[] PassHashEncrypted { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.Handshake);
message.Write((byte)PacketType.Handshake);
List<byte> byteArray = new List<byte>();
@ -38,8 +51,15 @@ namespace RageCoop.Core
byteArray.AddRange(BitConverter.GetBytes(modVersionBytes.Length));
byteArray.AddRange(modVersionBytes);
// Write NpcsAllowed
byteArray.Add(NPCsAllowed ? (byte)0x01 : (byte)0x00);
// Write AesKeyCrypted
byteArray.AddArray(AesKeyCrypted);
// Write AesIVCrypted
byteArray.AddArray(AesIVCrypted);
// Write PassHash
byteArray.AddArray(PassHashEncrypted);
byte[] result = byteArray.ToArray();
@ -57,15 +77,17 @@ namespace RageCoop.Core
PedID = reader.ReadInt();
// Read Username
int usernameLength = reader.ReadInt();
Username = reader.ReadString(usernameLength);
Username = reader.ReadString(reader.ReadInt());
// Read ModVersion
int modVersionLength = reader.ReadInt();
ModVersion = reader.ReadString(modVersionLength);
ModVersion = reader.ReadString(reader.ReadInt());
// Read NPCsAllowed
NPCsAllowed = reader.ReadBool();
AesKeyCrypted=reader.ReadByteArray();
AesIVCrypted=reader.ReadByteArray();
PassHashEncrypted=reader.ReadByteArray();
#endregion
}
}
@ -79,7 +101,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PlayerConnect);
message.Write((byte)PacketType.PlayerConnect);
List<byte> byteArray = new List<byte>();
@ -124,7 +146,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PlayerDisconnect);
message.Write((byte)PacketType.PlayerDisconnect);
List<byte> byteArray = new List<byte>();
@ -146,7 +168,6 @@ namespace RageCoop.Core
#endregion
}
}
public class PlayerInfoUpdate : Packet
{
/// <summary>
@ -158,7 +179,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PlayerInfoUpdate);
message.Write((byte)PacketType.PlayerInfoUpdate);
List<byte> byteArray = new List<byte>();
@ -199,5 +220,52 @@ namespace RageCoop.Core
Latency=reader.ReadFloat();
}
}
public class PublicKeyResponse : Packet
{
public byte[] Modulus;
public byte[] Exponent;
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.PublicKeyResponse);
List<byte> byteArray = new List<byte>();
byteArray.AddArray(Modulus);
byteArray.AddArray(Exponent);
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
var reader=new BitReader(array);
Modulus=reader.ReadByteArray();
Exponent=reader.ReadByteArray();
#endregion
}
}
public class PublicKeyRequest : Packet
{
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.PublicKeyRequest);
#endregion
}
public override void Unpack(byte[] array)
{
}
}
}
}

View File

@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class ProjectileSync : Packet
internal class ProjectileSync : Packet
{
public int ID { get; set; }
@ -15,11 +16,11 @@ namespace RageCoop.Core
public int ShooterID { get; set; }
public uint WeaponHash { get; set; }
public LVector3 Position { get; set; }
public Vector3 Position { get; set; }
public LVector3 Rotation { get; set; }
public Vector3 Rotation { get; set; }
public LVector3 Velocity { get; set; }
public Vector3 Velocity { get; set; }
public bool Exploded { get; set; }
@ -28,7 +29,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.ProjectileSync);
message.Write((byte)PacketType.ProjectileSync);
List<byte> byteArray = new List<byte>();
@ -41,14 +42,14 @@ namespace RageCoop.Core
byteArray.AddUint(WeaponHash);
// Write position
byteArray.AddLVector3(Position);
byteArray.AddVector3(Position);
// Write rotation
byteArray.AddLVector3(Rotation);
byteArray.AddVector3(Rotation);
// Write velocity
byteArray.AddLVector3(Velocity);
byteArray.AddVector3(Velocity);
if (Exploded) { byteArray.Add(1); }
@ -73,13 +74,13 @@ namespace RageCoop.Core
WeaponHash= reader.ReadUInt();
// Read position
Position = reader.ReadLVector3();
Position = reader.ReadVector3();
// Read rotation
Rotation = reader.ReadLVector3();
Rotation = reader.ReadVector3();
// Read velocity
Velocity =reader.ReadLVector3();
Velocity =reader.ReadVector3();
if (reader.CanRead(1))
{

View File

@ -1,27 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class BulletShot : Packet
internal class BulletShot : Packet
{
public int OwnerID { get; set; }
public uint WeaponHash { get; set; }
public LVector3 StartPosition { get; set; }
public LVector3 EndPosition { get; set; }
public Vector3 StartPosition { get; set; }
public Vector3 EndPosition { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.BulletShot);
message.Write((byte)PacketType.BulletShot);
List<byte> byteArray = new List<byte>();
@ -32,10 +32,10 @@ namespace RageCoop.Core
byteArray.AddRange(BitConverter.GetBytes(WeaponHash));
// Write StartPosition
byteArray.AddLVector3(StartPosition);
byteArray.AddVector3(StartPosition);
// Write EndPosition
byteArray.AddLVector3(EndPosition);
byteArray.AddVector3(EndPosition);
byte[] result = byteArray.ToArray();
@ -57,10 +57,10 @@ namespace RageCoop.Core
WeaponHash=reader.ReadUInt();
// Read StartPosition
StartPosition=reader.ReadLVector3();
StartPosition=reader.ReadVector3();
// Read EndPosition
EndPosition=reader.ReadLVector3();
EndPosition=reader.ReadVector3();
#endregion
}
}

View File

@ -6,9 +6,9 @@ using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class EnteredVehicle : Packet
internal class EnteredVehicle : Packet
{
public int PedID { get; set; }
@ -19,7 +19,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.EnteredVehicle);
message.Write((byte)PacketType.EnteredVehicle);
List<byte> byteArray = new List<byte>();

View File

@ -6,9 +6,9 @@ using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class EnteringVehicle : Packet
internal class EnteringVehicle : Packet
{
public int PedID { get; set; }
@ -19,7 +19,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.EnteringVehicle);
message.Write((byte)PacketType.EnteringVehicle);
List<byte> byteArray = new List<byte>();

View File

@ -6,10 +6,10 @@ using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class LeaveVehicle : Packet
internal class LeaveVehicle : Packet
{
public int ID { get; set; }
@ -17,7 +17,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.LeaveVehicle);
message.Write((byte)PacketType.LeaveVehicle);
List<byte> byteArray = new List<byte>();

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Text;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class NozzleTransform : Packet
{
public int VehicleID { get; set; }
public bool Hover { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketType.NozzleTransform);
List<byte> byteArray = new List<byte>();
byteArray.AddInt(VehicleID);
if (Hover) { byteArray.Add(1); }
byte[] result = byteArray.ToArray();
message.Write(result.Length);
message.Write(result);
#endregion
}
public override void Unpack(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
VehicleID=reader.ReadInt();
Hover=reader.CanRead(1);
#endregion
}
}
}
}

View File

@ -6,10 +6,10 @@ using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class OwnerChanged : Packet
internal class OwnerChanged : Packet
{
public int ID { get; set; }
@ -18,7 +18,7 @@ namespace RageCoop.Core
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.OwnerChanged);
message.Write((byte)PacketType.OwnerChanged);
List<byte> byteArray = new List<byte>();

View File

@ -6,17 +6,17 @@ using Lidgren.Network;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
public class PedKilled : Packet
internal class PedKilled : Packet
{
public int VictimID { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.PedKilled);
message.Write((byte)PacketType.PedKilled);
List<byte> byteArray = new List<byte>();

View File

@ -2,16 +2,18 @@
using System.Collections.Generic;
using System.Text;
using GTA;
using GTA.Math;
using Lidgren.Network;
using System.Linq;
namespace RageCoop.Core
{
public partial class Packets
internal partial class Packets
{
/// <summary>
/// Non-critical stuff, such as damage model, landing gear, health, etc..
/// </summary>
public class VehicleStateSync : Packet
internal class VehicleStateSync : Packet
{
public int ID { get; set; }
@ -29,21 +31,26 @@ namespace RageCoop.Core
public VehicleDamageModel DamageModel { get; set; }
public byte LandingGear { get; set; }
public byte RoofState { get; set; }
public VehicleDataFlags Flag { get; set; }
public VehicleLockStatus LockStatus { get; set; }
public int Livery { get; set; } = -1;
/// <summary>
/// VehicleSeat,PedID
/// </summary>
public Dictionary<int, int> Passengers { get; set; }
public byte RadioStation { get; set; } = 255;
public string LicensePlate { get; set; }
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.VehicleStateSync);
message.Write((byte)PacketType.VehicleStateSync);
List<byte> byteArray = new List<byte>();
@ -61,10 +68,14 @@ namespace RageCoop.Core
byteArray.AddRange(BitConverter.GetBytes(EngineHealth));
// Check
if (Flag.HasFlag(VehicleDataFlags.IsAircraft))
if (Flag.HasVehFlag(VehicleDataFlags.IsAircraft))
{
// Write the vehicle landing gear
byteArray.AddRange(BitConverter.GetBytes(LandingGear));
byteArray.Add(LandingGear);
}
if (Flag.HasVehFlag(VehicleDataFlags.HasRoof))
{
byteArray.Add(RoofState);
}
// Write vehicle colors
@ -117,6 +128,22 @@ namespace RageCoop.Core
// Write LockStatus
byteArray.Add((byte)LockStatus);
// Write RadioStation
byteArray.Add(RadioStation);
// Write LicensePlate
while (LicensePlate.Length<8)
{
LicensePlate+=" ";
}
if (LicensePlate.Length>8)
{
LicensePlate=new string(LicensePlate.Take(8).ToArray());
}
byteArray.AddRange(Encoding.ASCII.GetBytes(LicensePlate));
byteArray.Add((byte)(Livery+1));
byte[] result = byteArray.ToArray();
message.Write(result.Length);
@ -143,10 +170,14 @@ namespace RageCoop.Core
// Check
if (Flag.HasFlag(VehicleDataFlags.IsAircraft))
if (Flag.HasVehFlag(VehicleDataFlags.IsAircraft))
{
// Read vehicle landing gear
LandingGear = (byte)reader.ReadShort();
LandingGear = reader.ReadByte();
}
if (Flag.HasVehFlag(VehicleDataFlags.HasRoof))
{
RoofState=reader.ReadByte();
}
// Read vehicle colors
@ -199,6 +230,13 @@ namespace RageCoop.Core
// Read LockStatus
LockStatus=(VehicleLockStatus)reader.ReadByte();
// Read RadioStation
RadioStation=reader.ReadByte();
LicensePlate=Encoding.ASCII.GetString(reader.ReadByteArray(8));
Livery=(int)(reader.ReadByte()-1);
#endregion
}
}
@ -207,23 +245,24 @@ namespace RageCoop.Core
{
public int ID { get; set; }
public LVector3 Position { get; set; }
public Vector3 Position { get; set; }
public LVector3 Rotation { get; set; }
public Quaternion Quaternion { get; set; }
// public Vector3 Rotation { get; set; }
public LVector3 Velocity { get; set; }
public Vector3 Velocity { get; set; }
public LVector3 RotationVelocity { get; set; }
public Vector3 RotationVelocity { get; set; }
public float ThrottlePower { get; set; }
public float BrakePower { get; set; }
public float SteeringAngle { get; set; }
public float DeluxoWingRatio { get; set; } = -1;
public override void Pack(NetOutgoingMessage message)
{
#region PacketToNetOutGoingMessage
message.Write((byte)PacketTypes.VehicleSync);
message.Write((byte)PacketType.VehicleSync);
List<byte> byteArray = new List<byte>();
@ -231,17 +270,18 @@ namespace RageCoop.Core
byteArray.AddInt(ID);
// Write position
byteArray.AddLVector3(Position);
byteArray.AddVector3(Position);
// Write rotation
byteArray.AddLVector3(Rotation);
// Write quaternion
//byteArray.AddVector3(Rotation);
byteArray.AddQuaternion(Quaternion);
// Write velocity
byteArray.AddLVector3(Velocity);
byteArray.AddVector3(Velocity);
// Write rotation velocity
byteArray.AddLVector3(RotationVelocity);
byteArray.AddVector3(RotationVelocity);
byteArray.AddFloat(ThrottlePower);
@ -251,6 +291,11 @@ namespace RageCoop.Core
// Write vehicle steering angle
byteArray.AddFloat(SteeringAngle);
if (DeluxoWingRatio!=-1)
{
byteArray.AddFloat(DeluxoWingRatio);
}
byte[] result = byteArray.ToArray();
@ -268,16 +313,17 @@ namespace RageCoop.Core
ID = reader.ReadInt();
// Read position
Position = reader.ReadLVector3();
Position = reader.ReadVector3();
// Read rotation
Rotation = reader.ReadLVector3();
// Read quaternion
// Rotation = reader.ReadVector3();
Quaternion=reader.ReadQuaternion();
// Read velocity
Velocity =reader.ReadLVector3();
Velocity =reader.ReadVector3();
// Read rotation velocity
RotationVelocity=reader.ReadLVector3();
RotationVelocity=reader.ReadVector3();
// Read throttle power
ThrottlePower=reader.ReadFloat();
@ -288,6 +334,11 @@ namespace RageCoop.Core
// Read steering angle
SteeringAngle = reader.ReadFloat();
if (reader.CanRead(4))
{
DeluxoWingRatio= reader.ReadFloat();
}
#endregion
}
}

View File

@ -0,0 +1,50 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<AssemblyVersion>0.1</AssemblyVersion>
<FileVersion>0.1</FileVersion>
<Version>0.1</Version>
<DebugType>embedded</DebugType>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutDir>..\bin\Debug\Core</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutDir>..\bin\Release\Core</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net6.0|AnyCPU'">
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Lidgren.Network\**" />
<EmbeddedResource Remove="Lidgren.Network\**" />
<None Remove="Lidgren.Network\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup>
<ItemGroup>
<Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
namespace RageCoop.Core.Scripting
{
/// <summary>
///
/// </summary>
public static class CustomEvents
{
static MD5 Hasher = MD5.Create();
static Dictionary<int,string> Hashed=new Dictionary<int,string>();
internal static readonly int SetWeather = Hash("RageCoop.SetWeather");
internal static readonly int OnPedDeleted = Hash("RageCoop.OnPedDeleted");
internal static readonly int OnVehicleDeleted = Hash("RageCoop.OnVehicleDeleted");
internal static readonly int SetAutoRespawn = Hash("RageCoop.SetAutoRespawn");
internal static readonly int SetDisplayNameTag = Hash("RageCoop.SetDisplayNameTag");
internal static readonly int NativeCall = Hash("RageCoop.NativeCall");
internal static readonly int NativeResponse = Hash("RageCoop.NativeResponse");
internal static readonly int AllResourcesSent = Hash("RageCoop.AllResourcesSent");
internal static readonly int ServerPropSync = Hash("RageCoop.ServerPropSync");
internal static readonly int ServerBlipSync = Hash("RageCoop.ServerBlipSync");
internal static readonly int SetEntity = Hash("RageCoop.SetEntity");
internal static readonly int DeleteServerProp = Hash("RageCoop.DeleteServerProp");
internal static readonly int UpdatePedBlip = Hash("RageCoop.UpdatePedBlip");
internal static readonly int DeleteEntity = Hash("RageCoop.DeleteEntity");
internal static readonly int DeleteServerBlip = Hash("RageCoop.DeleteServerBlip");
internal static readonly int CreateVehicle = Hash("RageCoop.CreateVehicle");
internal static readonly int WeatherTimeSync = Hash("RageCoop.WeatherTimeSync");
internal static readonly int IsHost = Hash("RageCoop.IsHost");
/// <summary>
/// Get a Int32 hash of a string.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">The exception is thrown when the name did not match a previously computed one and the hash was the same.</exception>
public static int Hash(string s)
{
var hash = BitConverter.ToInt32(Hasher.ComputeHash(Encoding.UTF8.GetBytes(s)), 0);
string name;
lock (Hashed)
{
if (Hashed.TryGetValue(hash, out name))
{
if (name!=s)
{
throw new ArgumentException($"Hashed value has collision with another name:{name}, hashed value:{hash}");
}
else
{
return hash;
}
}
else
{
Hashed.Add(hash, s);
return hash;
}
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace RageCoop.Core.Scripting
{
/// <summary>
///
/// </summary>
public class ResourceFile
{
/// <summary>
/// Full name with relative path of this file
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Whether this is a directory
/// </summary>
public bool IsDirectory { get; internal set; }
/// <summary>
/// Get a stream that can be used to read file content.
/// </summary>
public Func<Stream> GetStream { get; internal set; }
}
}

86
RageCoop.Core/Worker.cs Normal file
View File

@ -0,0 +1,86 @@
using System;
using System.Threading;
using System.Collections.Concurrent;
namespace RageCoop.Core
{
/// <summary>
/// A worker that constantly execute jobs in a background thread.
/// </summary>
public class Worker:IDisposable
{
private SemaphoreSlim _semaphoreSlim;
private Thread _workerThread;
private bool _stopping=false;
/// <summary>
/// Name of the worker
/// </summary>
public string Name { get; set; }
/// <summary>
/// Whether this worker is busy executing job(s).
/// </summary>
public bool IsBusy { get;private set; }
internal Worker(string name,Logger logger,int maxJobs = Int32.MaxValue)
{
Name = name;
_semaphoreSlim = new SemaphoreSlim(0,maxJobs);
_workerThread=new Thread(() =>
{
while (!_stopping)
{
IsBusy=false;
_semaphoreSlim.Wait();
if(Jobs.TryDequeue(out var job))
{
IsBusy=true;
try
{
job.Invoke();
}
catch (Exception ex)
{
logger.Error("Error occurred when executing queued job:");
logger.Error(ex);
}
}
else
{
throw new InvalidOperationException("Hmm... that's unexpected.");
}
}
IsBusy=false;
});
_workerThread.Start();
}
/// <summary>
/// Queue a job to be executed
/// </summary>
/// <param name="work"></param>
public void QueueJob(Action work)
{
Jobs.Enqueue(work);
_semaphoreSlim.Release();
}
/// <summary>
/// Finish current job and stop the worker.
/// </summary>
public void Stop()
{
_stopping=true;
QueueJob(() => { });
if (_workerThread.IsAlive)
{
_workerThread.Join();
}
}
/// <summary>
/// Finish current job and stop the worker.
/// </summary>
public void Dispose()
{
Stop();
_semaphoreSlim.Dispose();
}
private ConcurrentQueue<Action> Jobs=new ConcurrentQueue<Action>();
}
}

258
RageCoop.Server/Client.cs Normal file
View File

@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
using RageCoop.Core;
using Lidgren.Network;
using System.Linq;
using GTA;
using RageCoop.Core.Scripting;
using System.Security.Cryptography;
using RageCoop.Server.Scripting;
namespace RageCoop.Server
{
/// <summary>
/// Represent a player connected to this server.
/// </summary>
public class Client
{
private readonly Server Server;
internal Client(Server server)
{
Server=server;
}
/// <summary>
/// Th client's IP address and port.
/// </summary>
public System.Net.IPEndPoint EndPoint { get { return Connection?.RemoteEndPoint; } }
internal long NetID = 0;
internal NetConnection Connection { get;set; }
/// <summary>
/// The <see cref="ServerPed"/> instance representing the client's main character.
/// </summary>
public ServerPed Player { get; internal set; }
/// <summary>
/// The client's latency in seconds.
/// </summary>
public float Latency { get; internal set; }
internal readonly Dictionary<int, Action<object>> Callbacks = new();
internal byte[] PublicKey { get; set; }
/// <summary>
/// Indicates whether the client has succefully loaded all resources.
/// </summary>
public bool IsReady { get; internal set; }=false;
/// <summary>
/// The client's username.
/// </summary>
public string Username { get;internal set; } = "N/A";
private bool _autoRespawn=true;
/// <summary>
/// Gets or sets whether to enable automatic respawn for this client's main ped.
/// </summary>
public bool EnableAutoRespawn {
get { return _autoRespawn; }
set {
BaseScript.SetAutoRespawn(this,value);
_autoRespawn=value;
}
}
private bool _displayNameTag=true;
/// <summary>
/// Gets or sets whether to enable automatic respawn for this client's main ped.
/// </summary>
public bool DisplayNameTag
{
get { return _displayNameTag; }
set
{
Server.BaseScript.SetNameTag(this,value);
_displayNameTag=value;
}
}
#region CUSTOMDATA FUNCTIONS
/*
public void SetData<T>(string name, T data)
{
if (HasData(name))
{
_customData[name] = data;
}
else
{
_customData.Add(name, data);
}
}
public bool HasData(string name)
{
return _customData.ContainsKey(name);
}
public T GetData<T>(string name)
{
return HasData(name) ? (T)_customData[name] : default;
}
public void RemoveData(string name)
{
if (HasData(name))
{
_customData.Remove(name);
}
}
*/
#endregion
#region FUNCTIONS
/// <summary>
/// Kick this client
/// </summary>
/// <param name="reason"></param>
public void Kick(string reason="You have been kicked!")
{
Connection?.Disconnect(reason);
}
/// <summary>
/// Kick this client
/// </summary>
/// <param name="reasons">Reasons to kick</param>
public void Kick(params string[] reasons)
{
Kick(string.Join(" ", reasons));
}
/// <summary>
/// Send a chat messsage to this client, not visible to others.
/// </summary>
/// <param name="message"></param>
/// <param name="from"></param>
public void SendChatMessage(string message, string from = "Server")
{
try
{
NetConnection userConnection = Server.MainNetServer.Connections.Find(x => x.RemoteUniqueIdentifier == NetID);
if (userConnection == null)
{
return;
}
Server.SendChatMessage(from, message, userConnection);
}
catch (Exception e)
{
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
/// Send a native call to client and do a callback when the response received.
/// </summary>
/// <typeparam name="T">Type of the response</typeparam>
/// <param name="callBack"></param>
/// <param name="hash"></param>
/// <param name="args"></param>
public void SendNativeCall<T>(Action<object> callBack, GTA.Native.Hash hash, params object[] args)
{
var argsList= new List<object>(args);
argsList.InsertRange(0, new object[] { (byte)Type.GetTypeCode(typeof(T)), RequestNativeCallID<T>(callBack), (ulong)hash });
SendCustomEventQueued(CustomEvents.NativeCall, argsList.ToArray());
}
/// <summary>
/// Send a native call to client and ignore it's response.
/// </summary>
/// <param name="hash"></param>
/// <param name="args"></param>
public void SendNativeCall(GTA.Native.Hash hash, params object[] args)
{
var argsList = new List<object>(args);
argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty,(ulong)hash });
// Server.Logger?.Debug(argsList.DumpWithType());
SendCustomEventQueued(CustomEvents.NativeCall, argsList.ToArray());
}
private int RequestNativeCallID<T>(Action<object> callback)
{
int ID = 0;
lock (Callbacks)
{
while ((ID==0)
|| Callbacks.ContainsKey(ID))
{
byte[] rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
Callbacks.Add(ID, callback);
}
return ID;
}
/// <summary>
/// Trigger a CustomEvent for this client
/// </summary>
/// <param name="hash">An unique identifier of the event, you can use <see cref="CustomEvents.Hash(string)"/> to get it from a string</param>
/// <param name="args">Arguments</param>
public void SendCustomEvent(int hash,params object[] args)
{
if (!IsReady)
{
Server.Logger?.Warning($"Player \"{Username}\" is not ready!");
}
try
{
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
new Packets.CustomEvent()
{
Hash=hash,
Args=args
}.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Event);
}
catch (Exception ex)
{
Server.Logger?.Error(ex);
}
}
/// <summary>
/// Send a CustomEvent that'll be queued at client side and invoked from script thread
/// </summary>
/// <param name="hash"></param>
/// <param name="args"></param>
public void SendCustomEventQueued(int hash, params object[] args)
{
if (!IsReady)
{
Server.Logger?.Warning($"Player \"{Username}\" is not ready!");
}
try
{
NetOutgoingMessage outgoingMessage = Server.MainNetServer.CreateMessage();
new Packets.CustomEvent(null,true)
{
Hash=hash,
Args=args
}.Pack(outgoingMessage);
Server.MainNetServer.SendMessage(outgoingMessage, Connection, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Event);
}
catch (Exception ex)
{
Server.Logger?.Error(ex);
}
}
#endregion
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Server
{
internal class FileTransfer
{
public int ID { get; set; }
public float Progress { get; set; }
public string Name { get; set; }
public bool Cancel { get; set; }=false;
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using RageCoop.Core;
namespace RageCoop.Server
{
internal class HolePunch
{
}
}

View File

@ -0,0 +1,63 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using RageCoop.Core;
namespace RageCoop.Server
{
class Program
{
private static bool Stopping = false;
static void Main(string[] args)
{
var mainLogger= new Logger()
{
LogPath="RageCoop.Server.log",
UseConsole=true,
Name="Server"
};
try
{
Console.Title = "RAGECOOP";
var setting = Util.Read<ServerSettings>("Settings.xml");
#if DEBUG
setting.LogLevel=0;
#endif
var server = new Server(setting, mainLogger);
Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e)
{
mainLogger.Info("Initiating shutdown sequence...");
mainLogger.Info("Press Ctrl+C again to commence an emergency shutdown.");
if (e.SpecialKey == ConsoleSpecialKey.ControlC)
{
if (!Stopping)
{
e.Cancel = true;
Stopping = true;
server.Stop();
mainLogger.Info("Server stopped.");
mainLogger.Dispose();
Thread.Sleep(3000);
}
else
{
mainLogger.Flush();
Environment.Exit(1);
}
}
};
server.Start();
mainLogger?.Info("Please use CTRL + C if you want to stop the server!");
}
catch (Exception e)
{
mainLogger.Error(e);
mainLogger.Error($"Fatal error occurred, server shutting down.");
mainLogger.Flush();
Thread.Sleep(5000);
Environment.Exit(1);
}
}
}
}

View File

@ -0,0 +1,65 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>0.5</AssemblyVersion>
<FileVersion>0.5</FileVersion>
<RepositoryUrl>https://github.com/RAGECOOP/RAGECOOP-V</RepositoryUrl>
<PackageProjectUrl>https://ragecoop.online/</PackageProjectUrl>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Product>$(AssemblyName)</Product>
<PackageId>RageCoop.Server</PackageId>
<Authors>RAGECOOP</Authors>
<Version>0.5</Version>
<DebugType>embedded</DebugType>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Description>An library for hosting a RAGECOOP server or API reference for developing a resource.</Description>
<ApplicationIcon>icon.ico</ApplicationIcon>
<PackageIcon>icon.png</PackageIcon>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutDir>..\bin\Debug\Server</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutDir>..\bin\Release\Server</OutDir>
</PropertyGroup>
<ItemGroup>
<Content Include="icon.ico" />
</ItemGroup>
<ItemGroup>
<None Include="..\images\icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="McMaster.NETCore.Plugins">
<HintPath>..\libs\McMaster.NETCore.Plugins.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<_LastSelectedProfileId>M:\SandBox-Shared\repo\RageCoop\RageCoop-V\RageCoop.Server\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,353 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Reflection;
using System.Net;
namespace RageCoop.Server.Scripting
{
/// <summary>
///
/// </summary>
public class ServerEvents
{
private readonly Server Server;
internal ServerEvents(Server server)
{
Server = server;
}
#region INTERNAL
internal Dictionary<int, List<Action<CustomEventReceivedArgs>>> CustomEventHandlers = new();
#endregion
/// <summary>
/// Invoked when a chat message is received.
/// </summary>
public event EventHandler<ChatEventArgs> OnChatMessage;
/// <summary>
/// Will be invoked from main thread before registered handlers
/// </summary>
public event EventHandler<OnCommandEventArgs> OnCommandReceived;
/// <summary>
/// Will be invoked from main thread when a client is attempting to connect, use <see cref="HandshakeEventArgs.Deny(string)"/> to deny the connection request.
/// </summary>
public event EventHandler<HandshakeEventArgs> OnPlayerHandshake;
/// <summary>
/// Will be invoked when a player is connected, but this player might not be ready yet(client resources not loaded), using <see cref="OnPlayerReady"/> is recommended.
/// </summary>
public event EventHandler<Client> OnPlayerConnected;
/// <summary>
/// Will be invoked after the client connected and all resources(if any) have been loaded.
/// </summary>
public event EventHandler<Client> OnPlayerReady;
/// <summary>
/// Invoked when a player disconnected, all method won't be effective in this scope.
/// </summary>
public event EventHandler<Client> OnPlayerDisconnected;
/// <summary>
/// Invoked everytime a player's main ped has been updated
/// </summary>
public event EventHandler<Client> OnPlayerUpdate;
internal void ClearHandlers()
{
OnChatMessage=null;
OnPlayerHandshake=null;
OnPlayerConnected=null;
OnPlayerReady=null;
OnPlayerDisconnected=null;
// OnCustomEventReceived=null;
OnCommandReceived=null;
OnPlayerUpdate=null;
}
#region INVOKE
internal void InvokePlayerHandshake(HandshakeEventArgs args)
{ OnPlayerHandshake?.Invoke(this, args); }
internal void InvokeOnCommandReceived(string cmdName, string[] cmdArgs, Client sender)
{
var args = new OnCommandEventArgs()
{
Name=cmdName,
Args=cmdArgs,
Sender=sender
};
OnCommandReceived?.Invoke(this, args);
if (args.Cancel)
{
return;
}
if (Server.Commands.Any(x => x.Key.Name == cmdName))
{
string[] argsWithoutCmd = cmdArgs.Skip(1).ToArray();
CommandContext ctx = new()
{
Client = sender,
Args = argsWithoutCmd
};
KeyValuePair<Command, Action<CommandContext>> command = Server.Commands.First(x => x.Key.Name == cmdName);
command.Value.Invoke(ctx);
}
else
{
Server.SendChatMessage("Server", "Command not found!", sender.Connection);
}
}
internal void InvokeOnChatMessage(Packets.ChatMessage p, Client sender)
{
OnChatMessage?.Invoke(this, new ChatEventArgs()
{
Sender=sender,
Message=p.Message
});
}
internal void InvokePlayerConnected(Client client)
{ OnPlayerConnected?.Invoke(this,client); }
internal void InvokePlayerReady(Client client)
{ OnPlayerReady?.Invoke(this, client); }
internal void InvokePlayerDisconnected(Client client)
{ OnPlayerDisconnected?.Invoke(this,client); }
internal void InvokeCustomEventReceived(Packets.CustomEvent p, Client sender)
{
var args = new CustomEventReceivedArgs() { Hash=p.Hash, Args=p.Args, Sender=sender };
List<Action<CustomEventReceivedArgs>> handlers;
if (CustomEventHandlers.TryGetValue(p.Hash, out handlers))
{
handlers.ForEach((x) => { x.Invoke(args); });
}
}
internal void InvokePlayerUpdate(Client client)
{
OnPlayerUpdate?.Invoke(this, client);
}
#endregion
}
/// <summary>
/// An class that can be used to interact with RageCoop server.
/// </summary>
public class API
{
internal readonly Server Server;
internal API(Server server)
{
Server=server;
Events=new(server);
}
/// <summary>
/// Server side events
/// </summary>
public readonly ServerEvents Events;
/// <summary>
/// All synchronized entities on this server.
/// </summary>
public ServerEntities Entities { get { return Server.Entities; } }
#region FUNCTIONS
/// <summary>
/// Get a list of all Clients
/// </summary>
/// <returns>All clients as a dictionary indexed by NetID</returns>
public Dictionary<string, Client> GetAllClients()
{
return new(Server.ClientsByName);
}
/// <summary>
/// Get the client by its username
/// </summary>
/// <param name="username">The username to search for (non case-sensitive)</param>
/// <returns>The Client from this user or null</returns>
public Client GetClientByUsername(string username)
{
return Server.Clients.Values.FirstOrDefault(x => x.Username.ToLower() == username.ToLower());
}
/// <summary>
/// Send a chat message to all players, use <see cref="Client.SendChatMessage(string, string)"/> to send to an individual client.
/// </summary>
/// <param name="targets">The clients to send message, leave it null to send to all clients</param>
/// <param name="message">The chat message</param>
/// <param name="username">The username which send this message (default = "Server")</param>
public void SendChatMessage(string message, List<Client> targets = null, string username = "Server")
{
try
{
if (Server.MainNetServer.ConnectionsCount == 0)
{
return;
}
targets ??= new(Server.Clients.Values);
foreach(Client client in targets)
{
Server.SendChatMessage(username, message, client.Connection);
}
}
catch (Exception e)
{
Server.Logger?.Error($">> {e.Message} <<>> {e.Source ?? string.Empty} <<>> {e.StackTrace ?? string.Empty} <<");
}
}
/// <summary>
/// Send CleanUpWorld to all players to delete all objects created by the server
/// </summary>
/// <summary>
/// Register a new command chat command (Example: "/test")
/// </summary>
/// <param name="name">The name of the command (Example: "test" for "/test")</param>
/// <param name="usage">How to use this message (argsLength required!)</param>
/// <param name="argsLength">The length of args (Example: "/message USERNAME MESSAGE" = 2) (usage required!)</param>
/// <param name="callback">A callback to invoke when the command received.</param>
public void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{
Server.RegisterCommand(name, usage, argsLength, callback);
}
/// <summary>
/// Register a new command chat command (Example: "/test")
/// </summary>
/// <param name="name">The name of the command (Example: "test" for "/test")</param>
/// <param name="callback">A callback to invoke when the command received.</param>
public void RegisterCommand(string name, Action<CommandContext> callback)
{
Server.RegisterCommand(name, callback);
}
/// <summary>
/// Register all commands in a static class
/// </summary>
/// <typeparam name="T">Your static class with commands</typeparam>
public void RegisterCommands<T>()
{
Server.RegisterCommands<T>();
}
/// <summary>
/// Register all commands inside an class instance
/// </summary>
/// <param name="obj">The instance of type containing the commands</param>
public void RegisterCommands(object obj)
{
IEnumerable<MethodInfo> commands = obj.GetType().GetMethods().Where(method => method.GetCustomAttributes(typeof(Command), false).Any());
foreach (MethodInfo method in commands)
{
Command attribute = method.GetCustomAttribute<Command>(true);
RegisterCommand(attribute.Name, attribute.Usage, attribute.ArgsLength,
(ctx) => { method.Invoke(obj, new object[] { ctx }); });
}
}
/// <summary>
/// Send native call specified clients.
/// </summary>
/// <param name="hash"></param>
/// <param name="args"></param>
/// /// <param name="clients">Clients to send, null for all clients</param>
public void SendNativeCall(List<Client> clients , GTA.Native.Hash hash, params object[] args)
{
var argsList = new List<object>(args);
argsList.InsertRange(0, new object[] { (byte)TypeCode.Empty, (ulong)hash });
SendCustomEventQueued(clients, CustomEvents.NativeCall, argsList.ToArray());
}
/// <summary>
/// Send an event and data to the specified clients. Use <see cref="Client.SendCustomEvent(int,object[])"/> if you want to send event to individual client.
/// </summary>
/// <param name="eventHash">An unique identifier of the event, you can use <see cref="CustomEvents.Hash(string)"/> to get it from a string</param>
/// <param name="args">The objects conataing your data, see <see cref="Scripting.CustomEventReceivedArgs.Args"/> for supported types.</param>
/// <param name="targets">The target clients to send. Leave it null to send to all clients</param>
public void SendCustomEvent(List<Client> targets, int eventHash, params object[] args)
{
targets ??= new(Server.Clients.Values);
var p = new Packets.CustomEvent()
{
Args=args,
Hash=eventHash
};
foreach (var c in targets)
{
Server.Send(p, c, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
}
/// <summary>
/// Send a CustomEvent that'll be queued at client side and invoked from script thread
/// </summary>
/// <param name="targets"></param>
/// <param name="eventHash"></param>
/// <param name="args"></param>
public void SendCustomEventQueued(List<Client> targets, int eventHash, params object[] args)
{
targets ??= new(Server.Clients.Values);
var p = new Packets.CustomEvent(null,true)
{
Args=args,
Hash=eventHash
};
foreach (var c in targets)
{
Server.Send(p, c, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers.
/// </summary>
/// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="CustomEvents.Hash(string)"/></param>
/// <param name="handler">An handler to be invoked when the event is received from the server.</param>
public void RegisterCustomEventHandler(int hash,Action<CustomEventReceivedArgs> handler)
{
List<Action<CustomEventReceivedArgs>> handlers;
lock (Events.CustomEventHandlers)
{
if (!Events.CustomEventHandlers.TryGetValue(hash,out handlers))
{
Events.CustomEventHandlers.Add(hash, handlers = new List<Action<CustomEventReceivedArgs>>());
}
handlers.Add(handler);
}
}
/// <summary>
/// Register an event handler for specified event name.
/// </summary>
/// <param name="name">This value will be hashed to an int to reduce overhead</param>
/// <param name="handler">The handler to be invoked when the event is received</param>
public void RegisterCustomEventHandler(string name, Action<CustomEventReceivedArgs> handler)
{
RegisterCustomEventHandler(CustomEvents.Hash(name), handler);
}
/// <summary>
/// Get a <see cref="Core.Logger"/> that the server is currently using, you should use <see cref="ServerResource.Logger"/> to display resource-specific information.
/// </summary>
public Logger Logger { get { return Server.Logger; } }
/// <summary>
/// Gets or sets the client that is resposible for synchronizing time and weather
/// </summary>
public Client Host
{
get { return Server._hostClient; }
set {
if (Server._hostClient != value)
{
Server._hostClient?.SendCustomEvent(CustomEvents.IsHost, false);
value.SendCustomEvent(CustomEvents.IsHost, true);
Server._hostClient = value;
}
}
}
#endregion
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RageCoop.Core.Scripting;
using RageCoop.Core;
namespace RageCoop.Server.Scripting
{
internal class BaseScript:ServerScript
{
private readonly Server Server;
public BaseScript(Server server) { Server=server; }
public override void OnStart()
{
API.RegisterCustomEventHandler(CustomEvents.NativeResponse, NativeResponse);
API.RegisterCustomEventHandler(CustomEvents.OnVehicleDeleted, (e) =>
{
API.Entities.RemoveVehicle((int)e.Args[0]);
});
API.RegisterCustomEventHandler(CustomEvents.OnPedDeleted, (e) =>
{
API.Entities.RemovePed((int)e.Args[0]);
});
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, (e) =>
{
if (Server.Settings.WeatherTimeSync)
{
foreach (var c in API.GetAllClients().Values)
{
if (c==e.Sender)
{
continue;
}
c.SendCustomEventQueued(CustomEvents.WeatherTimeSync, e.Args);
}
}
});
}
public override void OnStop()
{
}
public static void SetAutoRespawn(Client c,bool toggle)
{
c.SendCustomEvent(CustomEvents.SetAutoRespawn, toggle );
}
public void SetNameTag(Client c, bool toggle)
{
foreach(var other in API.GetAllClients().Values)
{
if (c==other) { continue; }
other.SendCustomEvent(CustomEvents.SetDisplayNameTag,c.Player.ID, toggle);
}
}
public void SendServerPropsTo(List<ServerProp> objects,List<Client> clients=null)
{
foreach(var obj in objects)
{
API.SendCustomEventQueued(clients, CustomEvents.ServerPropSync,obj.ID, obj.Model ,obj.Position,obj.Rotation );
}
}
public void SendServerBlipsTo(List<ServerBlip> objects, List<Client> clients = null)
{
foreach (var obj in objects)
{
API.SendCustomEventQueued(clients, CustomEvents.ServerBlipSync, obj.ID, (ushort)obj.Sprite, (byte)obj.Color, obj.Scale,obj.Position,obj.Rotation,obj.Name );
}
}
void NativeResponse(CustomEventReceivedArgs e)
{
try
{
int id = (int)e.Args[0];
Action<object> callback;
lock (e.Sender.Callbacks)
{
if (e.Sender.Callbacks.TryGetValue(id, out callback))
{
callback(e.Args[1]);
e.Sender.Callbacks.Remove(id);
}
}
}
catch (Exception ex)
{
API.Logger.Error("Failed to parse NativeResponse");
API.Logger.Error(ex);
}
}
}
}

View File

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
namespace RageCoop.Server.Scripting
{
/// <summary>
///
/// </summary>
public class ChatEventArgs : EventArgs
{
/// <summary>
/// The client that sent this message
/// </summary>
public Client Sender { get; set; }
/// <summary>
/// Message
/// </summary>
public string Message { get; set; }
}
/// <summary>
///
/// </summary>
public class CustomEventReceivedArgs : EventArgs
{
/// <summary>
/// The <see cref="Client"/> that triggered this event
/// </summary>
public Client Sender { get; set; }
/// <summary>
/// The event hash
/// </summary>
public int Hash { get; set; }
/// <summary>
/// Supported types: byte, short, ushort, int, uint, long, ulong, float, bool, string, Vector3, Quaternion, Vector2 <see cref="ServerObject.Handle"/>
/// </summary>
public object[] Args { get; set; }
}
/// <summary>
///
/// </summary>
public class OnCommandEventArgs : EventArgs
{
/// <summary>
/// The <see cref="Client"/> that executed this command.
/// </summary>
public Client Sender { get; set; }
/// <summary>
/// The name of executed command
/// </summary>
public string Name { get; set; }
/// <summary>
/// Arguments
/// </summary>
public string[] Args { get; set; }
/// <summary>
/// If this value was set to true, corresponding handler registered with <see cref="API.RegisterCommand(string, Action{CommandContext})"/> will not be invoked.
/// </summary>
public bool Cancel { get; set; } = false;
}
/// <summary>
///
/// </summary>
public class HandshakeEventArgs : EventArgs
{
/// <summary>
/// The player's ID
/// </summary>
public int ID { get; set; }
/// <summary>
/// The claimed username
/// </summary>
public string Username { get; set; }
/// <summary>
/// The client password hashed with SHA256 algorithm.
/// </summary>
public string PasswordHash { get; set; }
/// <summary>
/// The <see cref="EndPoint"/> that sent the handshake request.
/// </summary>
public IPEndPoint EndPoint { get; set; }
/// <summary>
/// Deny the connection attempt
/// </summary>
/// <param name="reason"></param>
public void Deny(string reason)
{
DenyReason=reason;
Cancel=true;
}
internal string DenyReason { get; set; }
internal bool Cancel { get; set; }=false;
}
}

View File

@ -0,0 +1,204 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using RageCoop.Core.Scripting;
using RageCoop.Core;
using System.IO;
using ICSharpCode.SharpZipLib.Zip;
using System.Reflection;
using McMaster.NETCore.Plugins;
namespace RageCoop.Server.Scripting
{
internal class Resources
{
private Dictionary<string,ServerResource> LoadedResources=new();
private readonly Server Server;
private readonly Logger Logger;
public Resources(Server server)
{
Server = server;
Logger=server.Logger;
}
private List<string> ClientResourceZips=new List<string>();
public void LoadAll()
{
// Client
{
var path = Path.Combine("Resources", "Client");
var tmpDir = Path.Combine("Resources", "Temp","Client");
Directory.CreateDirectory(path);
if (Directory.Exists(tmpDir))
{
Directory.Delete(tmpDir, true);
}
Directory.CreateDirectory(tmpDir);
var resourceFolders = Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly);
if (resourceFolders.Length!=0)
{
foreach (var resourceFolder in resourceFolders)
{
// Pack client side resource as a zip file
Logger?.Info("Packing client-side resource: "+resourceFolder);
var zipPath = Path.Combine(tmpDir, Path.GetFileName(resourceFolder))+".zip";
try
{
using (ZipFile zip = ZipFile.Create(zipPath))
{
zip.BeginUpdate();
foreach (var dir in Directory.GetDirectories(resourceFolder, "*", SearchOption.AllDirectories))
{
zip.AddDirectory(dir.Substring(resourceFolder.Length+1));
}
foreach (var file in Directory.GetFiles(resourceFolder, "*", SearchOption.AllDirectories))
{
zip.Add(file, file.Substring(resourceFolder.Length+1));
}
zip.CommitUpdate();
zip.Close();
ClientResourceZips.Add(zipPath);
}
}
catch (Exception ex)
{
Logger?.Error($"Failed to pack client resource:{resourceFolder}");
Logger?.Error(ex);
}
}
}
var packed = Directory.GetFiles(path, "*.zip", SearchOption.TopDirectoryOnly);
if (packed.Length>0)
{
ClientResourceZips.AddRange(packed);
}
}
// Server
{
var path = Path.Combine("Resources", "Server");
var dataFolder = Path.Combine(path, "data");
Directory.CreateDirectory(path);
foreach (var resource in Directory.GetDirectories(path))
{
try
{
var name = Path.GetFileName(resource);
if (LoadedResources.ContainsKey(name))
{
Logger?.Warning($"Resource \"{name}\" has already been loaded, ignoring...");
continue;
}
if (name.ToLower()=="data") { continue; }
Logger?.Info($"Loading resource: {name}");
var r = ServerResource.LoadFrom(resource, dataFolder, Logger);
LoadedResources.Add(r.Name, r);
}
catch(Exception ex)
{
Logger?.Error($"Failed to load resource: {Path.GetFileName(resource)}");
Logger?.Error(ex);
}
}
foreach (var resource in Directory.GetFiles(path, "*.zip", SearchOption.TopDirectoryOnly))
{
try
{
var name = Path.GetFileNameWithoutExtension(resource);
if (LoadedResources.ContainsKey(name))
{
Logger?.Warning($"Resource \"{name}\" has already been loaded, ignoring...");
continue;
}
Logger?.Info($"Loading resource: name");
var r = ServerResource.LoadFromZip(resource, Path.Combine("Resources", "Temp", "Server"), dataFolder, Logger);
LoadedResources.Add(r.Name, r);
}
catch(Exception ex)
{
Logger?.Error($"Failed to load resource: {Path.GetFileNameWithoutExtension(resource)}");
Logger?.Error(ex);
}
}
// Start scripts
lock (LoadedResources)
{
foreach (var r in LoadedResources.Values)
{
foreach (ServerScript s in r.Scripts)
{
s.API=Server.API;
try
{
Logger?.Debug("Starting script:"+s.CurrentFile.Name);
s.OnStart();
}
catch (Exception ex) { Logger?.Error($"Failed to start resource: {r.Name}"); Logger?.Error(ex); }
}
}
}
}
}
public void UnloadAll()
{
lock (LoadedResources)
{
foreach (var d in LoadedResources.Values)
{
foreach (var s in d.Scripts)
{
try
{
s.OnStop();
}
catch(Exception ex)
{
Logger?.Error(ex);
}
}
try
{
d.Dispose();
}
catch(Exception ex)
{
Logger.Error($"Resource \"{d.Name}\" cannot be unloaded.");
Logger.Error(ex);
}
}
LoadedResources.Clear();
}
}
public void SendTo(Client client)
{
Task.Run(() =>
{
if (ClientResourceZips.Count!=0)
{
Logger?.Info($"Sending resources to client:{client.Username}");
foreach (var rs in ClientResourceZips)
{
using (var fs = File.OpenRead(rs))
{
Server.SendFile(rs, Path.GetFileName(rs), client);
}
}
Logger?.Info($"Resources sent to:{client.Username}");
}
if (Server.GetResponse<Packets.FileTransferResponse>(client, new Packets.AllResourcesSent())?.Response==FileResponse.Loaded)
{
client.IsReady=true;
Server.API.Events.InvokePlayerReady(client);
}
else
{
Logger?.Warning($"Client {client.Username} failed to load resource.");
}
});
}
}
}

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