496 Commits

Author SHA1 Message Date
1fcf47cebd Update master server, fix installer 2023-07-25 16:43:37 -03:00
826e80c5d8 Reintroduce compact vector types 2023-03-26 12:03:06 +08:00
3944d41e85 Merge branch '1.6-pre-alpha' of https://github.com/RAGECOOP/RAGECOOP-V into 1.6-pre-alpha 2023-03-23 22:30:42 +08:00
114e59a3bd Add toggle mods sync 2023-03-23 22:30:39 +08:00
db7146e00e Add Xenon Headlights Color Sync (#45) 2023-03-20 23:13:43 +08:00
e6718cec58 Don't sync non-existent extras 2023-03-20 18:20:07 +08:00
6f2c0077cb Add vehicle extras sync 2023-03-20 18:05:14 +08:00
5f1aadbdb5 Don't use dictionary to store vehicle mods 2023-03-20 17:14:52 +08:00
7cd0fd22af Use tuple to store vehicle colors
Array allocation is somewhat expensive
2023-03-20 17:02:56 +08:00
e6c6e5ceff Update some data stuff 2023-03-19 20:35:42 +08:00
bda1dac90b Smoother sync, I guess 2023-03-19 17:47:00 +08:00
0bc89a9bf3 Fix broken radio sync 2023-03-19 16:58:07 +08:00
7e019cc112 Don't throw if muzzle bone was not found 2023-03-19 15:22:38 +08:00
713e005975 Ditch Newtonsoft.Json in favor of System.Text.Json 2023-03-19 15:13:24 +08:00
0cb1098819 Weapon sync cleanup 1 2023-03-19 13:43:46 +08:00
ba8c2c741d Fix broken vehicle weapon sync when no impact is found 2023-03-19 13:26:18 +08:00
feb20c47f4 Back to manual assembly versioning 2023-03-18 16:02:40 +08:00
7f4aa47943 Update shvdn 2023-03-18 15:58:50 +08:00
525bd495b4 Fix weid nametag size 2023-03-18 15:58:41 +08:00
cd70256315 Tunable debug values 2023-03-18 15:58:31 +08:00
920b5d15ba New nametag rendering 2023-03-15 15:58:03 +08:00
fd4b5f8afb Fix wrong settings description 2023-03-15 11:42:32 +08:00
bdcdf75a8e Update shvdnc 2023-03-12 13:33:59 +08:00
f100261e88 Update stuff 2023-03-11 17:08:07 +08:00
cea49dae2b PInvoke code cleanup 2023-03-11 16:39:20 +08:00
f44715cdf4 Cleanup math and benchmark stuff 2023-03-11 16:09:22 +08:00
57c9c49757 Merge pull request #44 from RAGECOOP/net7-shvdnc
Use SHVDNC as new runtime
2023-03-11 15:30:14 +08:00
df9efb6210 Use quaternion differentiation to calibrate rotation
Hopefully fixes the wobbling issue
2023-03-11 15:22:25 +08:00
cc92d1ad2b Apply positional calibration force to center of mass 2023-03-08 18:33:40 +08:00
63f11053c7 Use InternalImpulse to calibrate entity position 2023-03-08 18:27:23 +08:00
11c7ee19a3 Dynamically resolve data path 2023-03-08 11:09:47 +08:00
211ab4ee7c Cleanup project file 2023-03-07 21:01:08 +08:00
83f7cbc963 Working client resource system 2023-03-07 20:17:53 +08:00
2451131e36 Prepare for CoreCLR version 2023-03-06 21:54:41 +08:00
0e5271b322 Basically working resource system 2023-02-27 11:54:02 +08:00
6e2b4aff2f Fix stuff 2023-02-16 18:56:56 +08:00
e5f426690f Introduce CustomEventHandler for more flexible invocation 2023-02-15 11:38:02 +08:00
e4f432b593 Cleanup and rewrite some bullshit 2023-02-13 20:44:50 +08:00
f555fef48d Optimize imports 2023-02-13 17:51:18 +08:00
ac07edfe68 Client API remoting stuff 2023-02-12 22:06:57 +08:00
fb654f0e56 Update submodules 2023-02-12 22:05:38 +08:00
718814c491 Fix crashing due to bug in BufferWriter.ToByteArray()
Renamed the serialisers and added CustomEvents to unit test to be more future-proof
2023-02-03 13:37:20 +08:00
b4f86719ce Join network thread before unload 2023-02-03 13:30:37 +08:00
6792559b5b Update Lidgren submodule 2023-02-03 13:13:59 +08:00
ffb31b0986 Initialise submodules first in build actions 2023-02-01 21:38:58 +08:00
f1b9bf0571 Some works for the new resource system
Rewrite some parts of CustomEvent
Expose some API as dll entry
2023-02-01 21:29:25 +08:00
d4df041c44 Add Lidgren.Network and SHVDNC as submodule 2023-02-01 13:23:09 +08:00
a7ea28281e Some fancy classes for faster serialization 2023-01-31 20:23:52 +08:00
6a1ebcf9fe Fix build commands 2023-01-28 23:19:05 +08:00
a79f9f2a69 Fix dll 2023-01-28 23:12:02 +08:00
798776c822 GC: avoid unnecessary instance creation 2023-01-28 22:25:11 +08:00
9cb7b28d8f Hardlink 2023-01-28 20:57:05 +08:00
cac2385c35 Initial migration commit to .NET 7
Menu, sync and other stuff except resource system should be working.
We're far from finished
2023-01-28 20:51:29 +08:00
0112140f0e Fix build copy 2022-12-04 21:11:33 +08:00
174f711e42 Delete subprocess folder after build 2022-12-04 20:57:42 +08:00
3a9068f060 Various weapon fixes and some change on vehicle sync 2022-12-04 19:34:54 +08:00
4cc5054b91 Fix installer 2022-11-30 20:25:07 +08:00
a9b05c62ff Cleanup and update installer 2022-11-30 20:17:06 +08:00
952ab4f7b3 Update to work with latest build of SHVDN 2022-11-30 19:08:52 +08:00
598790dedd Save blip and nametag settings 2022-11-20 16:29:11 +08:00
979be4cfa8 blah 2022-11-20 15:56:46 +08:00
7dec978e9a Improved vehicle sync 2022-11-20 15:55:11 +08:00
78f0dfaebb Add option to disable blip and nametag display at client side 2022-11-19 14:26:31 +08:00
e6c8d4df46 "stuff" 2022-11-19 12:54:22 +08:00
279492e0bd Hmm... 2022-11-19 12:05:21 +08:00
478305112f Changed some variables from ServerInfo to an integer. .NET updated to version 7.0
Not compatible with the current MasterServer. `https://test.ragecoop.online/` is compatible
2022-11-19 00:01:38 +01:00
745b212b42 cleanup 2022-11-16 17:40:07 +08:00
50153d860a Tidy up weapon code and data dumper 2022-11-16 17:37:31 +08:00
822a69573f Remove update menu 2022-11-13 17:06:27 +08:00
23e4151b2e Use Direct2D for CEF testing 2022-11-13 12:41:26 +08:00
71195e93c2 Okay boomer 2022-11-05 18:43:59 +08:00
8961eb102b Initial CEF and DirectX overlay implementation (PoC, unfinished)
Still pretty unstable, game might crash at times.
Revamp build system and other small fixes
2022-11-05 18:35:39 +08:00
2828b9b74f Clean up 2022-10-23 19:02:39 +08:00
6b34ab6e36 DataDumper 2022-10-21 19:41:38 +08:00
69419d41e0 Improved weapon and turret sync 2022-10-19 19:07:46 +08:00
a50ae062d8 Fix resource loading 2022-10-19 19:07:29 +08:00
9e0ce58bd2 Don't delete peds in vehicle 2022-10-15 18:23:12 +08:00
806f1d194a Longer waiting time for resource loading 2022-10-15 18:18:36 +08:00
4e6bab1b53 blah 2022-10-15 18:14:35 +08:00
64692bc161 Update 2022-10-15 17:16:47 +08:00
8d27c072ca Change package format to iso9660 2022-10-15 17:06:19 +08:00
d7c0abdfc2 Bump version 2022-10-15 15:54:46 +08:00
d0b6bbaa3a Update installer 2022-10-15 15:30:58 +08:00
411b199a98 Unload fix and small tweaks 2022-10-15 13:52:49 +08:00
b48b15b652 Tidy up 2022-10-15 12:15:19 +08:00
e83480b7f9 Update action 2022-10-15 11:57:26 +08:00
d1b4f23992 Restructure solution 2022-10-15 11:51:18 +08:00
42c0ef2159 Load in client in seperate domain
Rewrote logger for more flexible options
Complete client resource loading
2022-10-15 11:09:17 +08:00
8701ac703e blah 2022-10-10 16:45:58 +08:00
fe53e01a4a ClientScript loading logic 2022-10-09 23:35:30 +08:00
617dbc9812 Change settings and data directory 2022-10-09 22:07:52 +08:00
1606d25fed Queue action in WorldThread 2022-10-09 11:15:09 +08:00
5585876005 API remoting 2022-10-08 23:49:48 +08:00
54c7f4ce92 ResourceDomain 2022-10-08 12:43:24 +08:00
a062322dda Fix queued events 2022-10-05 18:10:56 +08:00
4599c558e4 Add back Hash method for compatabilit 2022-10-05 16:09:43 +08:00
71f7f4b257 Use implicit operator for CustomEventHash, add CustomEventFlags
Resource rebuild will be required, but no code change is needed
2022-10-05 15:53:57 +08:00
3f3b5fd2d0 Closes #40 2022-09-30 23:40:14 +08:00
9173e9a99e Fix non-ambient peds get deleted 2022-09-30 23:27:37 +08:00
6e64c458df Merge branch 'dev-nightly' of https://github.com/RAGECOOP/RAGECOOP-V into dev-nightly 2022-09-09 11:24:02 +08:00
76f959abe9 Stuff 2022-09-09 11:23:46 +08:00
f2e85d66ab Update build-test.yaml 2022-09-09 11:15:27 +08:00
e30ef1f4bd Update actions 2022-09-08 20:09:52 -07:00
6e8f6e78f6 Add build test action 2022-09-08 20:05:26 -07:00
f28c83ccbd Merge pull request #38 from RAGECOOP/dev-nightly
Bump to 1.5.4
2022-09-07 22:44:48 -07:00
a83821b3d2 Version bump 2022-09-08 13:19:09 -07:00
3b5436064e Don't announce if already present in master server 2022-09-08 13:15:34 -07:00
c4b321324e Filter out white space for reload key parsing 2022-09-08 12:59:14 -07:00
ba8d525ddf Merge pull request #37 from RAGECOOP/dev-nightly
Rewrite packet system
2022-09-07 21:45:14 -07:00
884e2f39f0 Yet another code cleanup 2022-09-08 12:41:56 -07:00
76c529f1d1 Rewrote packet system to reduce heap allocation 2022-09-08 12:37:06 -07:00
df0064bd38 Update nightly-build.yaml 2022-09-07 21:01:22 -07:00
f1fc96bbd7 Fix weird projectile shooting 2022-09-07 11:08:25 +08:00
23e9326f5f Fix stuff, add listening address display and projectile shoot prediction 2022-09-07 09:52:40 +08:00
4621fb4987 Some code cleanup and formatting 2022-09-06 21:46:35 +08:00
6c82895fa7 code cleaned up 2022-09-05 13:02:09 +02:00
84b040766f Simplify GetBytesFromObject 2022-08-27 14:23:28 +08:00
f44558cd3b Add some client API 2022-08-27 14:17:10 +08:00
accdbbcbc6 blah 2022-08-27 13:53:03 +08:00
2d4107f35e Fix traffic 2022-08-27 12:40:47 +08:00
bac53fd769 Load scripts from main assembly only 2022-08-26 22:58:03 +08:00
8f63fee5b5 Delete temp directory 2022-08-25 22:34:54 +08:00
8d0ad0b600 Show runtime information 2022-08-25 22:32:01 +08:00
dc08c0c1f6 Added support for loading runtime-specific libraries 2022-08-25 21:51:09 +08:00
faaa856aa5 Instructional buttons for popup 2022-08-25 00:58:21 +08:00
eab64f9254 Check fixed data every 100 frames 2022-08-24 22:38:50 +08:00
6c936cb8f9 Show help in popup 2022-08-24 19:26:40 +08:00
83a37f8556 blah 2022-08-24 18:48:21 +08:00
bb4eacce26 Don't disable traffic if not on server 2022-08-24 18:47:59 +08:00
d5b71db5d4 💩 2022-08-23 23:21:35 +08:00
89ebd0305c Fix ped running not displayed 2022-08-23 18:10:52 +08:00
b209202292 Add option to show entity owner 2022-08-23 17:43:24 +08:00
f199241ed8 Time for a release 2022-08-23 16:29:43 +08:00
50229116a7 blah 2022-08-23 14:04:18 +08:00
f192caeae1 Add sdk 2022-08-23 14:03:33 +08:00
6005b2ba11 Add slipstreaming 2022-08-23 13:51:38 +08:00
6e213611cc Fix deluxo wing 2022-08-23 13:45:45 +08:00
347d65af62 Expose chat meesage API to client script 2022-08-23 13:34:12 +08:00
636ee3a33f Fix memory stream close on send 2022-08-23 13:22:14 +08:00
8ff08e0804 Add resource package support 2022-08-23 12:21:17 +08:00
6d7fe58719 Ped sync tweaks 2022-08-23 10:55:54 +08:00
c7e14ef191 Fix disable traffic not always work 2022-08-22 21:59:01 +08:00
39eb5ef80a Hmm... 2022-08-22 19:38:04 +08:00
d25c2539e0 Tweaks 2022-08-22 19:27:25 +08:00
eb31a33104 Fix vibration on stopped vehicle 2022-08-22 19:25:03 +08:00
8b3f8ffd9b Check ped weapon on full sync only 2022-08-22 19:13:52 +08:00
cca09c4178 Fixed ped weapon display after exiting vehicle 2022-08-22 19:08:09 +08:00
871bb7e64e ver 2022-08-22 17:57:43 +08:00
02da3ce8af Fix voice on P2P mode 2022-08-22 17:55:58 +08:00
711ba62fc2 Update installer 2022-08-22 17:30:15 +08:00
8f7d3135db Revert "Fix output path overlap"
This reverts commit 2af9482da9.
2022-08-22 17:27:35 +08:00
cb563a1bf8 Revert "Update nightly-build.yaml"
This reverts commit 6720636d48.
2022-08-22 17:27:29 +08:00
a84ab42a42 blah 2022-08-22 17:01:08 +08:00
b3e285f92f Wait ten minutes before updating 2022-08-22 16:59:00 +08:00
01433f0de6 Restore damage model for aircraft 2022-08-22 11:36:52 +08:00
c034cd8aa9 Fix traffic 2022-08-22 10:01:09 +08:00
363c2ccb00 Don't disable vehicle enter/exit key 2022-08-22 09:40:02 +08:00
7380c162be Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-22 09:34:52 +08:00
f1c7192029 Change kicking options 2022-08-22 09:33:54 +08:00
6720636d48 Update nightly-build.yaml 2022-08-22 00:46:46 +08:00
2af9482da9 Fix output path overlap 2022-08-22 00:36:43 +08:00
bf22c17bba Add AntiAsshole and host display 2022-08-22 00:23:46 +08:00
01492a0c55 Auto server update should work now 2022-08-21 19:56:59 +08:00
c17b234057 Update AssemblyInfo.cs 2022-08-21 19:41:25 +08:00
739577bbf1 Hmm... 2022-08-21 19:30:27 +08:00
2d2da624e4 Add automatic server update (need testing) 2022-08-21 19:13:12 +08:00
9e66762061 Add ZeroTier check to installer 2022-08-21 15:13:36 +08:00
84d578366a Fix installer 2022-08-21 14:28:37 +08:00
03bc3a7742 blah 2022-08-21 14:22:41 +08:00
5bfc7f836e Fix vehicle seat sync 2022-08-21 14:15:01 +08:00
54977c79f1 Fix unintended carjacking 2022-08-21 14:12:44 +08:00
0352bfa328 Add menyoo and directory check for installer 2022-08-21 13:42:28 +08:00
6fbc386b45 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-21 12:37:47 +08:00
203e3b2528 Add Client.EntitiesCount 2022-08-21 12:37:37 +08:00
c9aa995224 Update README.md 2022-08-21 12:17:20 +08:00
c201b94897 Better vehicle rotation display 2022-08-20 22:36:48 +08:00
87f873d1a5 blah 2022-08-20 22:22:06 +08:00
6c355b109d Fix player blip (again) 2022-08-20 22:15:01 +08:00
2ef3012672 Fix installer not overwriting files 2022-08-20 17:27:05 +08:00
7229574ff4 Hmm 2022-08-20 17:17:49 +08:00
eff9fd50b1 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-20 15:31:03 +08:00
e2cbf26360 Fix installer output 2022-08-20 15:30:54 +08:00
b0f448fd5f Fix insyaller output 2022-08-20 15:29:44 +08:00
721c4d3dee Add client installer 2022-08-20 15:20:25 +08:00
87dfc18d91 Smoother ped heading calibration 2022-08-20 12:33:55 +08:00
fa4fe2c10f Fix player blip 2022-08-20 11:32:35 +08:00
3a088ddbfc Fix blip duplication and name issue 2022-08-20 11:08:51 +08:00
52cc9b647c Vehicle duplication prevention 2022-08-19 22:31:13 +08:00
1aa23901d5 Update lidgren again 2022-08-19 22:10:01 +08:00
52584d5774 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-19 21:43:34 +08:00
62bf15dede default LogLevel to 0 2022-08-19 21:43:23 +08:00
722ca16f57 Update README.md 2022-08-19 21:39:40 +08:00
3e61498366 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-19 21:35:08 +08:00
1ff2fa5691 Update lidgren 2022-08-19 21:35:05 +08:00
5088973474 Update README.md 2022-08-19 21:29:24 +08:00
b63378f4d0 Update README.md 2022-08-19 21:28:44 +08:00
f234830c54 Fix stuff 2022-08-18 22:08:06 +08:00
5075289657 ? 2022-08-18 21:48:42 +08:00
c20118aac1 Vehicle cleanup 2022-08-18 20:44:39 +08:00
c4312faf4a blah 2022-08-18 18:59:38 +08:00
7566423889 Security tweaks 2022-08-18 17:45:08 +08:00
be13e0d102 Small projectile fix 2022-08-18 08:43:10 +08:00
490802862a Huh? 2022-08-18 08:26:41 +08:00
e369a50b89 Add vehicle parachute sync 2022-08-18 08:25:15 +08:00
13a6431248 Add rocket boost sync 2022-08-17 23:18:06 +08:00
cb73db7146 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-17 22:56:27 +08:00
9dcd6bb363 Apply prediction on server entity update 2022-08-17 22:50:17 +08:00
842e1435f2 aargh..... 2022-08-17 01:54:14 +08:00
4d03f8e371 blahblahblah 2022-08-16 23:31:17 +08:00
e040d53cc2 blah 2022-08-16 23:18:34 +08:00
22bfa43ec5 Update version 2022-08-16 23:17:04 +08:00
7123bea8e5 Small fix 2022-08-16 22:23:32 +08:00
973f6873e4 Fix some control not being ignored on chat 2022-08-16 22:16:18 +08:00
efb759b9f9 blah 2022-08-16 20:07:25 +08:00
ad3ff96f61 This is it 2022-08-16 19:51:56 +08:00
e156b2195f Display ping (round-trip time) 2022-08-16 13:44:56 +08:00
73a99e033d Update DevTool 2022-08-15 22:43:32 +08:00
b44d722b5f Closes #24 2022-08-15 22:41:17 +08:00
307b6365f7 Closes #25 2022-08-15 22:38:31 +08:00
a49ee19d3d Closes #17 2022-08-15 22:36:43 +08:00
9c5c1da4c9 Add champion weapon, (closes #21) 2022-08-15 22:35:43 +08:00
0348296dc1 Add a bunch of vehicle weapons (closes #23) 2022-08-15 22:34:35 +08:00
adb9439226 Add BRUISER3, BRUTUS3, MONSTER5 weapon sync (closes #26) 2022-08-15 22:21:47 +08:00
ae10da874b Hmm... 2022-08-15 21:25:42 +08:00
cb91486848 Update SHVDN 2022-08-15 20:50:26 +08:00
3289c03fd2 Vehicle seat fix 2022-08-15 20:04:08 +08:00
d4ffb0c929 blah 2022-08-15 19:40:24 +08:00
3c089e2c47 Fix looking coordinate in first person 2022-08-15 19:39:36 +08:00
7a4e4182b5 Add drive-by weapon display
This still have some issues:
ped not aiming consistently
sometimes not working at all
doesn't work for throwable weapons
2022-08-15 18:25:43 +08:00
aa6c45b20d Voice tweaks 2022-08-15 16:13:53 +08:00
0943a7992f blah 2022-08-15 00:19:00 +02:00
d30e1f6bb3 Added mouth animation when speaking 2022-08-14 23:35:36 +02:00
0c71d4dbaa Assign Client.Player.Owner immediately 2022-08-15 00:12:07 +08:00
c78fbd89bc Update README.md 2022-08-14 19:10:45 +08:00
dc83cd5be9 Ped sync improvements 2022-08-14 19:06:51 +08:00
b5f6fc578d Projectile refactor 2022-08-14 17:08:43 +08:00
4acf964f0a Small ragdoll tweak 2022-08-14 13:56:10 +08:00
560f092304 Don't set heading when aiming 2022-08-14 13:21:13 +08:00
8305d9997a Better vehicle weapon handling and muzzle flash 2022-08-14 13:04:39 +08:00
d68941b3ac blah 2022-08-14 10:46:42 +08:00
67c4646198 Fix update not deleting old assemblies 2022-08-14 10:45:39 +08:00
a7dab1e0e1 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-14 10:36:08 +08:00
2b8a3db6bf Retore traffic on disconnect 2022-08-14 10:35:59 +08:00
f9609afd8a Small update for voice chat 2022-08-14 02:26:59 +02:00
609fd90561 Small tweaks 2022-08-13 16:59:09 +08:00
a7d64d241d New vehicle seat sync and projectile fix 2022-08-13 16:14:18 +08:00
b436abf131 Some hint 2022-08-13 11:42:29 +08:00
c14221f1a3 Remove json attributes 2022-08-13 11:42:15 +08:00
abbc871d1a Small fix 2022-08-13 11:28:35 +08:00
edb8838455 Update libs 2022-08-13 11:05:03 +08:00
4cca75ee35 Merge pull request #32 from RAGECOOP/voice-chat
Add voice chat
2022-08-13 10:55:16 +08:00
669b457275 Don't send voice back 2022-08-13 10:54:39 +08:00
2ef0060037 Added ClearAll() for voice 2022-08-13 04:03:01 +02:00
55649f26ae Use ConcurrentDictionary for ServerEntities 2022-08-13 09:47:13 +08:00
6bb7d5d0fd Merge branch 'voice-chat' of https://github.com/RAGECOOP/RAGECOOP-V into voice-chat 2022-08-13 03:39:16 +02:00
8f5deaa33b More voice changes 2022-08-13 03:39:11 +02:00
91b0afb6ef Remove Acceleration 2022-08-13 09:31:09 +08:00
4dc3cc5c28 Fix voice and add Server.Forward() and Server.SendToAll() 2022-08-13 08:56:05 +08:00
f9a411833a small fix 2022-08-13 02:39:18 +02:00
d24d786fd3 Better packet reuse 2022-08-13 08:22:14 +08:00
edae7a075a blub 2022-08-13 02:19:40 +02:00
85b028e3e7 Added NAudio and first voice test 2022-08-13 00:52:34 +02:00
36de46a2ce Fixed warnings 2022-08-12 20:48:31 +02:00
6e5f72ee70 blah 2022-08-12 20:58:17 +08:00
8d450813e1 ZeroTier intergration 2022-08-12 20:40:50 +08:00
495e5dc6b0 Better server info handling 2022-08-12 18:10:56 +08:00
0bd1d2c2a7 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-12 18:07:06 +08:00
339f817c7e ZeroTier helper 2022-08-12 18:02:43 +08:00
b583618e6c Merge pull request #31 from xEntenKoeniqx/main
Cleaned up (Client)
2022-08-11 21:27:18 +08:00
dc0fafbe9e Fixed failed build of github-actions 2022-08-11 15:22:36 +02:00
b9e22a880f Cleaned up (Client) 2022-08-11 14:59:09 +02:00
cb7f0290ec "Entities" cleaned up 2022-08-11 14:48:19 +02:00
d70c739208 Cleaned up 2022-08-11 14:29:18 +02:00
29202906e1 Fix stuff 2022-08-11 19:42:30 +08:00
1d3e53f734 Packet recycling 2022-08-11 18:25:01 +08:00
a26a799a9f Variables renaming 2022-08-11 16:29:29 +08:00
033d0a3f2e Move some stuff to core 2022-08-11 16:25:38 +08:00
95e302a698 Fix player disconnect 2022-08-11 15:38:19 +08:00
ff9e16bd5e Restore 2022-08-10 20:42:47 +08:00
01c668b159 blah 2022-08-08 18:07:22 +08:00
74e51c8070 Display remote blip 2022-08-08 17:47:09 +08:00
01524b1796 Move CurrentWeaponHash to full sync 2022-08-08 17:29:15 +08:00
83d79b0a17 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-08 17:03:53 +08:00
c3d7c2b335 P2P connectivity 2022-08-08 17:03:41 +08:00
6b6125eb09 Update DownloadManager.cs 2022-08-08 10:19:48 +08:00
09bb121ffe blah 2022-08-06 12:33:05 +08:00
a50be5b869 Some stuff for the upcoming change 2022-08-06 12:32:04 +08:00
ca5ef8cf4c read packet with BinaryReader 2022-08-06 11:40:38 +08:00
4e33956acd Faster way to get client by username 2022-08-06 10:58:24 +08:00
9aae315e11 Simplify 2022-08-06 10:49:55 +08:00
742e7ea998 Packet refactor 2022-08-06 10:43:24 +08:00
69bd4c837c Delete dll when installing update 2022-08-05 17:24:51 +08:00
8c10d24842 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-05 16:13:56 +08:00
6b681afdee Fix wheels 2022-08-05 16:13:40 +08:00
6186497999 blah 2022-08-04 19:18:22 +08:00
2571da594e Remove ResourceDomain 2022-08-04 19:13:39 +08:00
7c52cad569 Remove mscoree reference 2022-08-04 19:07:08 +08:00
303bc940f2 Move network info option to debug menu 2022-08-04 17:47:57 +08:00
eda903abde Sync every 30 ms 2022-08-04 17:38:28 +08:00
5d5bc668eb Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-04 17:14:11 +08:00
6e5653a168 Add latency simulation 2022-08-04 17:14:07 +08:00
87343b8255 Update README.md 2022-08-04 15:11:57 +08:00
cb21291983 Update README.md 2022-08-04 15:02:54 +08:00
8dd2241590 Update SyncedVehicle.cs 2022-08-04 08:33:18 +08:00
858fa81ea2 blah 2022-08-02 17:43:48 +08:00
8d6c70b75e Ok, this might look junky... 2022-08-02 17:43:18 +08:00
ffd6ce23c3 Add vehicle velocity prediction/acceleration 2022-08-02 16:42:40 +08:00
5a882d0bc8 Fix announce thread exception 2022-08-02 15:55:26 +08:00
cb1b4f4970 Update Program.cs 2022-08-01 23:20:22 +08:00
0de040548d Update FindScript() 2022-08-01 17:07:08 +08:00
40f4cf88c9 Back to velocity 2022-08-01 16:56:46 +08:00
275b39fb1e Add digits 2022-08-01 16:56:32 +08:00
fe5da3cf90 Check 2022-08-01 07:14:24 +08:00
4ed4e37604 Revert "Set client to null on disconnect"
This reverts commit 10552d119f.
2022-08-01 07:13:05 +08:00
10552d119f Set client to null on disconnect 2022-07-31 21:03:47 +08:00
d4e0e6f0ab Stricter username check 2022-07-31 20:56:51 +08:00
e2fce3cf9b Add some client API back 2022-07-31 20:47:04 +08:00
ad5ffc22ac blah 2022-07-30 21:55:57 +08:00
f980790bd7 blah 2022-07-30 21:54:45 +08:00
df8519961a blah 2022-07-30 21:53:42 +08:00
171a6be736 Revert "Squared"
This reverts commit 91035d9a3f.
2022-07-30 21:51:56 +08:00
91035d9a3f Squared 2022-07-30 21:37:15 +08:00
60e7d91409 Read ped speed 2022-07-30 15:45:27 +08:00
d05d1dac85 predict projectile positioin 2022-07-30 14:09:07 +08:00
8184b4d155 Predict projectile position 2022-07-30 13:43:54 +08:00
a0d5f9d2df stricter flip check 2022-07-30 12:50:04 +08:00
49f7f9e3e1 Calibrate position with force 2022-07-30 12:39:09 +08:00
911d8d5d64 Fix player PlayerInfoUpdate 2022-07-30 12:03:57 +08:00
3015ffe3ba Revert "flush"
This reverts commit 459cfccff5.
2022-07-30 11:44:14 +08:00
eb74bcf987 Update vehicle sync 2022-07-29 22:35:20 +08:00
b63898d8b9 Update vehicle sync 2022-07-29 22:26:19 +08:00
459cfccff5 flush 2022-07-29 21:53:26 +08:00
fdb66cba6d Fix chat message 2022-07-29 21:27:04 +08:00
93d705e402 Latency fix 2022-07-29 21:15:23 +08:00
c8bbdc69d0 Update latency detection method
Plus some server code refactoring
2022-07-29 20:35:39 +08:00
9b1587f334 Ignore API assemblies 2022-07-29 19:11:31 +08:00
3e67cc519e Ignore ready 2022-07-29 18:44:17 +08:00
eee6f614f1 Encrypt chat message 2022-07-29 18:17:45 +08:00
62e5bad6ac Resource interoperability 2022-07-29 17:44:02 +08:00
163f342d7a Change Sender to Client in EventArgs 2022-07-29 16:17:33 +08:00
370247eef2 Allow raising event when sending chat message wth API 2022-07-29 15:07:52 +08:00
f22ecb03f2 Fix welcome message 2022-07-29 14:51:52 +08:00
577857ab5a Hash password at server side 2022-07-29 14:51:17 +08:00
095d16a57e rix 2022-07-29 01:31:34 +08:00
862c2c0a71 Update Server.cs 2022-07-29 01:13:32 +08:00
d7e9ce8ff1 Don't invoke chat message if it's a command 2022-07-29 01:08:34 +08:00
e657a02f7a Update RageCoop.Client.csproj 2022-07-29 00:52:57 +08:00
182ff877f0 Update RageCoop.Client.csproj 2022-07-29 00:44:31 +08:00
521358abb5 Update README.md 2022-07-29 00:36:36 +08:00
b62aed5c5a Update RageCoop.Client.csproj 2022-07-29 00:30:33 +08:00
5c4e31806d cd /d 2022-07-28 18:00:13 +08:00
9f8d695d45 cd 2022-07-28 17:55:40 +08:00
e0e5f52d36 Send message back 2022-07-28 17:50:41 +08:00
9fda563438 Invoke constructor later 2022-07-28 17:46:34 +08:00
6d5bb285c9 Fix client copy 2022-07-28 17:03:29 +08:00
7ac405b8f9 Embed referenced assemblies 2022-07-28 15:46:15 +08:00
45d53bd38c Memory patch for weapon/radio slowdown/vignetting 2022-07-25 22:22:57 +08:00
cc52f66f5d Read quaternion and rotation directly from memory 2022-07-25 19:07:58 +08:00
e072c2aee8 Read entity position directly from memory 2022-07-23 23:45:20 +08:00
e52135c343 Allow sending chat message and command in console 2022-07-22 19:43:48 +08:00
cb53e8a664 Delay vehicle repair, fix vehicle if BodyHealth increased 2022-07-21 23:10:56 +08:00
44a8445b54 Delay vehicle repair 2022-07-21 22:42:29 +08:00
b0a197cd18 Back to old latency compensation 2022-07-21 09:53:07 +08:00
9ba52b332c Revert some vehicle sync change 2022-07-21 09:43:09 +08:00
ecfc77e57a Fix resource loading, add player died notification 2022-07-21 08:41:05 +08:00
2ba3e33d39 Don't follow position if velocity is greater than 3 2022-07-20 18:02:17 +08:00
71ecdbd5b9 code cleanup 2022-07-20 17:50:01 +08:00
bbe0b21aa4 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-07-20 09:32:34 +08:00
51dd445a31 Fix resource duplication 2022-07-20 09:32:26 +08:00
264158957c Send response in same sequence channel 2022-07-20 09:11:07 +08:00
9d8963222c Update nightly-build.yaml 2022-07-20 07:50:03 +08:00
c1a35dbab3 Update nightly-build.yaml 2022-07-20 07:44:52 +08:00
d3931cc57c Update nightly-build.yaml 2022-07-20 07:37:26 +08:00
3795ced530 Update nightly-build.yaml 2022-07-20 07:35:02 +08:00
1372a5c33f Update release configuration 2022-07-20 07:34:53 +08:00
9527da9785 Add ability to share server file 2022-07-19 17:15:53 +08:00
3072db0e21 Only load top-level assembly 2022-07-18 17:27:06 +08:00
444e6d7ffa Unify resource file path 2022-07-18 17:24:31 +08:00
809c935d37 Fix entity fire 2022-07-17 20:12:25 +08:00
10bd54a785 remove testing code 2022-07-17 19:19:58 +08:00
ece899f2a1 Better vehicle movement sync 2022-07-17 19:19:23 +08:00
5d1a182e47 Better ragdoll sync (maybe) 2022-07-17 18:44:16 +08:00
228ab9e727 Ped movement change 2022-07-17 14:23:19 +08:00
2605aafc4f Vehicle flag fix 2022-07-17 12:22:11 +08:00
582bb2a7d7 Ahhh... 2022-07-17 12:00:26 +08:00
0a3dbb1f59 Merge vehicle sync packet, comment cleanup 2022-07-17 11:59:37 +08:00
5d1832fcef Merge ped packet
Back to old project family (for debugging)
Fix blip name
Delete newly spawned traffic if limit exceeded
2022-07-17 11:15:02 +08:00
30ed509c55 Prevent entity spawning from interfering Tick event 2022-07-15 13:45:43 +08:00
2ad91011c3 blah 2022-07-14 18:52:04 +08:00
e40d4695ea Add QueueAction overload 2022-07-14 18:50:15 +08:00
e4c03f8ccc Add in-game update feature 2022-07-14 17:59:41 +08:00
e10a0f5415 Add Logger property for resource script 2022-07-14 16:44:35 +08:00
2a29011015 Better projectile check 2022-07-14 15:28:03 +08:00
a37e8869ac Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-07-14 10:33:34 +08:00
038860f158 Update nametag drawing method 2022-07-14 10:33:24 +08:00
51e678d760 Update README.md 2022-07-13 20:13:29 +08:00
1e2a9992d8 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-07-13 11:08:21 +08:00
5b7bb917ef add tls 2022-07-13 11:08:11 +08:00
72aff78718 Move logger 2022-07-13 11:08:04 +08:00
cdeef7279c coding style 2022-07-13 10:36:38 +08:00
9cb5c557c5 fix vehicle doors sync 2022-07-13 00:20:06 +08:00
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
425 changed files with 365744 additions and 37943 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.cs]
# CS0649: Field 'VehicleInfo.Name' is never assigned to, and will always have its default value null
dotnet_diagnostic.CS0649.severity = silent

13
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [RAGECOOP] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://www.buymeacoffee.com/xEntenKoeniqx', 'https://patreon.com/Sardelka']

53
.github/workflows/build-test.yaml vendored Normal file
View File

@ -0,0 +1,53 @@
name: Build test
on:
push:
branches:
- '**' # matches every branch
- '!main' # excludes main
- '!dev-nightly' # excludes nightly
pull_request:
branches: [ "main", "dev-nightly" ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: ['7.0.x']
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Initialise submodules
run: git submodule update --init
- name: Restore dependencies
run: dotnet restore
- name: Restore nuget packages
run: nuget restore
- name: Build
run: .\build.cmd
- name: Upload server
uses: actions/upload-artifact@v3
with:
name: RageCoop.Server
path: bin/Release/Server
- name: Upload Client
uses: actions/upload-artifact@v3
with:
name: RageCoop.Client
path: bin/Release/Client
- uses: actions/checkout@v2

View File

@ -2,9 +2,7 @@ name: Nightly-build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
branches: [ "dev-nightly" ]
jobs:
build:
@ -12,7 +10,7 @@ jobs:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: ['6.0.x']
dotnet-version: ['7.0.x']
steps:
- uses: actions/checkout@v3
@ -20,31 +18,72 @@ jobs:
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Initialise submodules
run: git submodule update --init
- name: Restore dependencies
run: dotnet restore
- name: Build client
run: dotnet build RageCoop.Client/RageCoop.Client.csproj --no-restore --configuration Release -o RageCoop.Client/bin/RageCoop
- name: Build server win-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r win-x64 -o RageCoop.Server/bin/win-x64 -c Release
- name: Build server linux-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-x64 -o RageCoop.Server/bin/linux-x64 -c Release
- name: Build server linux-arm
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-arm -o RageCoop.Server/bin/linux-arm -c Release
- uses: actions/upload-artifact@v3
- name: Restore nuget packages
run: nuget restore
- name: Build artifacts
run: .\build.cmd
- name: Build server for different platforms
run: .\publish-server.cmd
- uses: vimtor/action-zip@v1
with:
name: RageCoop.Client
path: RageCoop.Client/bin
- uses: actions/upload-artifact@v3
files: bin/Release/Client
dest: RageCoop.Client.zip
- uses: vimtor/action-zip@v1
with:
name: RageCoop.Server-win-x64
path: RageCoop.Server/bin/win-x64
- uses: actions/upload-artifact@v3
files: bin/API
dest: bin/Artifacts/SDK.zip
- uses: vimtor/action-zip@v1
with:
name: RageCoop.Server-linux-x64
path: RageCoop.Server/bin/linux-x64
- uses: actions/upload-artifact@v3
files: bin/Release/Server/win-x64
dest: bin/Artifacts/RageCoop.Server-win-x64.zip
- uses: vimtor/action-zip@v1
with:
name: RageCoop.Server-linux-arm
path: RageCoop.Server/bin/linux-arm
files: bin/Release/Server/win-x86
dest: bin/Artifacts/RageCoop.Server-win-x86.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-x64
dest: bin/Artifacts/RageCoop.Server-linux-x64.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm
dest: bin/Artifacts/RageCoop.Server-linux-arm.zip
- uses: vimtor/action-zip@v1
with:
files: bin/Release/Server/linux-arm64
dest: bin/Artifacts/RageCoop.Server-linux-arm64.zip
- name: Deploy binaries
uses: Sardelka9515/deploy-nightly@v0.7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/RAGECOOP/GTAV-RESOURCES/releases/75463254/assets{?name,label}
release_id: 75463254
asset_path: bin/Artifacts
asset_content_type: application/zip
max_releases: 7
- uses: actions/checkout@v2

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
**/bin
**/obj
**/packages
**/.vs
**/.vs
**/.vscode

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "libs/Lidgren.Network"]
path = libs/Lidgren.Network
url = https://github.com/RAGECOOP/lidgren-network-gen3
[submodule "libs/ScriptHookVDotNetCore"]
path = libs/ScriptHookVDotNetCore
url = https://github.com/Sardelka9515/scripthookvdotnetcore

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>

View File

@ -0,0 +1,314 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO.MemoryMappedFiles;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Threading;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.OffScreen;
using Microsoft.Win32.SafeHandles;
namespace RageCoop.Client.CefHost
{
public enum BufferMode
{
Full = 1,
Dirty = 2
}
public enum MouseButton
{
Left,
Middle,
Right
}
/// <summary>
/// Hosted by CefHost for managing cef instances.
/// </summary>
public class CefController : MarshalByRefObject, IDisposable
{
private static Process _host;
private static ActivatedClientTypeEntry _controllerEntry;
private static IpcChannel _adapterChannel;
public static Action<string> OnCefMessage;
private ChromiumWebBrowser _browser;
private MemoryMappedFile _mmf;
private string _mmfName;
private SafeMemoryMappedViewHandle _mmfView;
private BufferMode _mode;
private CefProcessor _processor;
public IntPtr PtrBuffer { get; private set; }
public int FrameRate
{
get => _browser.GetBrowserHost().WindowlessFrameRate;
set => _browser.GetBrowserHost().WindowlessFrameRate = value;
}
public void Dispose()
{
_browser?.Dispose();
_mmf?.Dispose();
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
_mmfView?.Dispose();
PtrBuffer = IntPtr.Zero;
_mmf = null;
_mmfView = null;
}
public static void Initialize(string fileName = "RageCoop.Client.CefHost.exe")
{
_host = new Process();
_host.StartInfo = new ProcessStartInfo
{
FileName = fileName,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
_host.EnableRaisingEvents = true;
_host.Start();
RegisterChannels(_host.StandardOutput.ReadLine());
Task.Run(() =>
{
while (_host?.HasExited == false) OnCefMessage?.Invoke("[CEF]: " + _host.StandardOutput.ReadLine());
});
Task.Run(() =>
{
while (_host?.HasExited == false)
OnCefMessage?.Invoke("[CEF][ERROR]: " + _host.StandardError.ReadLine());
});
}
public static void ShutDown()
{
if (_host == null) return;
_host.StandardInput.WriteLine("exit");
_host.WaitForExit(1000);
_host.Kill();
_host = null;
}
private static void RegisterChannels(string hostChannel)
{
var service = Guid.NewGuid().ToString();
Console.WriteLine("Registering adapter channel: " + service);
_adapterChannel = new IpcChannel(service);
ChannelServices.RegisterChannel(_adapterChannel, false);
_controllerEntry = new ActivatedClientTypeEntry(typeof(CefController), "ipc://" + hostChannel);
RemotingConfiguration.RegisterActivatedClientType(_controllerEntry);
Console.WriteLine("Registered controller entry: " + "ipc://" + hostChannel);
RemotingConfiguration.RegisterActivatedServiceType(typeof(CefAdapter));
Console.WriteLine("Registered service: " + nameof(CefAdapter));
_host.StandardInput.WriteLine("ipc://" + service);
}
/// <summary>
/// Called inside client process
/// </summary>
/// <param name="id"></param>
/// <param name="size"></param>
/// <param name="adapter"></param>
/// <param name="bufferMode"></param>
/// <param name="bufferSize"></param>
/// <returns></returns>
public static CefController Create(int id, Size size, out CefAdapter adapter, BufferMode bufferMode,
long bufferSize = 1024 * 1024 * 16)
{
if (RemotingConfiguration.IsRemotelyActivatedClientType(typeof(CefController)) == null)
throw new RemotingException();
var controller = new CefController();
controller.Activate(id, size, bufferMode, bufferSize);
adapter = CefAdapter.Adapters[id];
controller.Ping();
return controller;
}
public unsafe void Activate(int id, Size size, BufferMode mode = BufferMode.Dirty,
long sharedMemorySize = 1024 * 1024 * 16)
{
_mode = mode;
_mmfName = Guid.NewGuid().ToString();
// Set up shared memory
_mmf = MemoryMappedFile.CreateNew(_mmfName, sharedMemorySize);
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
byte* pBuf = null;
try
{
_mmfView.AcquirePointer(ref pBuf);
PtrBuffer = (IntPtr)pBuf;
}
catch
{
Dispose();
throw;
}
var adapter = new CefAdapter();
adapter.Register(id, mode, _mmfName);
_browser = new ChromiumWebBrowser();
_browser.RenderHandler = _processor = new CefProcessor(size, adapter, PtrBuffer, mode);
while (_browser.GetBrowserHost() == null) Thread.Sleep(20); // Wait till the browser is actually created
Console.WriteLine("CefController created: " + size);
}
public void LoadUrl(string url)
{
_browser.LoadUrl(url);
}
public void Resize(Size size)
{
_browser.Size = size;
_processor.Size = size;
}
public void SendMouseClick(int x, int y, int modifiers, MouseButton button, bool mouseUp, int clicks)
{
var e = new MouseEvent(x, y, (CefEventFlags)modifiers);
_browser.GetBrowserHost()
?.SendMouseClickEvent(e, (MouseButtonType)button, mouseUp, clicks);
}
public void SendMouseMove(int x, int y, bool leave = false)
{
var e = new MouseEvent(x, y, 0);
_browser.GetBrowserHost()?.SendMouseMoveEvent(e, leave);
}
public DateTime Ping()
{
return DateTime.UtcNow;
;
}
public override object InitializeLifetimeService()
{
return null;
}
}
/// <summary>
/// Hosted by client for receiving rendering data
/// </summary>
public class CefAdapter : MarshalByRefObject, IDisposable
{
public delegate void PaintDelegate(int bufferSize, Rectangle dirtyRect);
public delegate void ResizeDelegate(Size newSize);
public static Dictionary<int, CefAdapter> Adapters = new Dictionary<int, CefAdapter>();
private MemoryMappedFile _mmf;
private SafeMemoryMappedViewHandle _mmfView;
public int Id;
public CefAdapter()
{
Console.WriteLine("Adapter created");
}
public Size Size { get; private set; }
public IntPtr PtrBuffer { get; private set; }
/// <summary>
/// Maximum buffer size for a paint event, use this property to allocate memory.
/// </summary>
/// <remarks>Value is equal to <see cref="Size" />*4, therefore will change upon resize</remarks>
public int MaxBufferSize => Size.Height * Size.Width * 4;
public BufferMode BufferMode { get; private set; }
public void Dispose()
{
_mmf?.Dispose();
if (PtrBuffer != IntPtr.Zero) _mmfView?.ReleasePointer();
_mmfView?.Dispose();
PtrBuffer = IntPtr.Zero;
_mmf = null;
_mmfView = null;
lock (Adapters)
{
if (Adapters.ContainsKey(Id)) Adapters.Remove(Id);
}
}
public event PaintDelegate OnPaint;
public event ResizeDelegate OnResize;
public void Resized(Size newSize)
{
Size = newSize;
OnResize?.Invoke(newSize);
}
public void Paint(Rectangle dirtyRect)
{
var size = BufferMode == BufferMode.Dirty
? dirtyRect.Width * dirtyRect.Height * 4
: Size.Width * Size.Height * 4;
OnPaint?.Invoke(size, dirtyRect);
}
public override object InitializeLifetimeService()
{
return null;
}
public unsafe void Register(int id, BufferMode mode, string mmfName)
{
lock (Adapters)
{
if (Adapters.ContainsKey(id)) throw new ArgumentException("Specified id is already used", nameof(id));
// Set up shared memory
_mmf = MemoryMappedFile.OpenExisting(mmfName);
_mmfView = _mmf.CreateViewAccessor().SafeMemoryMappedViewHandle;
byte* pBuf = null;
try
{
_mmfView.AcquirePointer(ref pBuf);
PtrBuffer = (IntPtr)pBuf;
}
catch
{
Dispose();
throw;
}
Id = id;
BufferMode = mode;
Adapters.Add(id, this);
}
}
/// <summary>
/// Ensure ipc connection
/// </summary>
/// <returns></returns>
public DateTime Ping()
{
return DateTime.UtcNow;
}
}
}

View File

@ -0,0 +1,128 @@
using System;
using System.Drawing;
using BitmapUtil;
using CefSharp;
using CefSharp.Enums;
using CefSharp.OffScreen;
using CefSharp.Structs;
using Size = System.Drawing.Size;
namespace RageCoop.Client.CefHost
{
internal class CefProcessor : IRenderHandler
{
private readonly CefAdapter _adapter;
private readonly BufferMode _mode;
private readonly IntPtr _pSharedBuffer;
private Rect _rect;
public CefProcessor(Size size, CefAdapter adapter, IntPtr pSharedBuffer, BufferMode mode)
{
_adapter = adapter;
_rect = new Rect(0, 0, size.Width, size.Height);
_pSharedBuffer = pSharedBuffer;
_mode = mode;
_adapter?.Resized(size);
}
public Size Size
{
get => new Size(_rect.Width, _rect.Height);
set
{
_rect = new Rect(0, 0, value.Width, value.Height);
_adapter?.Resized(value);
}
}
public void Dispose()
{
}
public ScreenInfo? GetScreenInfo()
{
return null;
}
public Rect GetViewRect()
{
return _rect;
}
public bool GetScreenPoint(int viewX, int viewY, out int screenX, out int screenY)
{
screenX = viewX;
screenY = viewY;
return true;
}
public void OnAcceleratedPaint(PaintElementType type, Rect dirtyRect, IntPtr sharedHandle)
{
}
public void OnPaint(PaintElementType type, Rect dirtyRect, IntPtr buffer, int width, int height)
{
var dirty = new Rectangle
{
Width = dirtyRect.Width,
Height = dirtyRect.Height,
X = dirtyRect.X,
Y = dirtyRect.Y
};
var source = new BitmapInfo
{
Width = width,
Height = height,
BytesPerPixel = 4,
Scan0 = buffer
};
switch (_mode)
{
case BufferMode.Dirty:
Unsafe.CopyRegion(source, _pSharedBuffer, dirty);
break;
case BufferMode.Full:
{
var target = source;
target.Scan0 = _pSharedBuffer;
Unsafe.UpdateRegion(source, target, dirty, dirty.Location);
break;
}
}
_adapter?.Paint(dirty);
}
public void OnCursorChange(IntPtr cursor, CursorType type, CursorInfo customCursorInfo)
{
}
public bool StartDragging(IDragData dragData, DragOperationsMask mask, int x, int y)
{
return true;
}
public void UpdateDragCursor(DragOperationsMask operation)
{
}
public void OnPopupShow(bool show)
{
}
public void OnPopupSize(Rect rect)
{
}
public void OnImeCompositionRangeChanged(Range selectedRange, Rect[] characterBounds)
{
;
}
public void OnVirtualKeyboardRequested(IBrowser browser, TextInputMode inputMode)
{
;
}
}
}

130
Client/CefHost/Program.cs Normal file
View File

@ -0,0 +1,130 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Security.Permissions;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.OffScreen;
namespace RageCoop.Client.CefHost
{
internal static class Program
{
[SecurityPermission(SecurityAction.Demand)]
private static void Main(string[] args)
{
Cef.Initialize(new CefSettings
{
BackgroundColor = 0x00
});
var name = Guid.NewGuid().ToString();
var channel = new IpcChannel(name);
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterActivatedServiceType(typeof(CefController));
// Write to stdout so it can be read by the client
Console.WriteLine(name);
var adapterUrl = Console.ReadLine();
var adapterEntry = new ActivatedClientTypeEntry(typeof(CefAdapter), adapterUrl);
Console.WriteLine("Registered adapter entry: " + adapterUrl);
RemotingConfiguration.RegisterActivatedClientType(adapterEntry);
var channelData = (ChannelDataStore)channel.ChannelData;
foreach (var uri in channelData.ChannelUris) Console.WriteLine("Channel URI: {0}", uri);
Task.Run(() =>
{
try
{
Util.GetParentProcess().WaitForExit();
Console.WriteLine("Parent process terminated, exiting...");
Environment.Exit(0);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
});
while (true)
switch (Console.ReadLine())
{
case "exit":
Cef.Shutdown();
Environment.Exit(0);
break;
}
}
}
/// <summary>
/// A utility class to determine a process parent.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Util
{
// These members must match PROCESS_BASIC_INFORMATION
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass,
ref Util processInformation, int processInformationLength, out int returnLength);
/// <summary>
/// Gets the parent process of the current process.
/// </summary>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess()
{
return GetParentProcess(Process.GetCurrentProcess().Handle);
}
/// <summary>
/// Gets the parent process of specified process.
/// </summary>
/// <param name="id">The process id.</param>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess(int id)
{
var process = Process.GetProcessById(id);
return GetParentProcess(process.Handle);
}
/// <summary>
/// Gets the parent process of a specified process.
/// </summary>
/// <param name="handle">The process handle.</param>
/// <returns>An instance of the Process class.</returns>
public static Process GetParentProcess(IntPtr handle)
{
var pbi = new Util();
int returnLength;
var status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength);
if (status != 0)
throw new Win32Exception(status);
try
{
return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
catch (ArgumentException)
{
// not found
return null;
}
}
}
}

View File

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

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props"
Condition="Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props')" />
<Import Project="..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props"
Condition="Exists('..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props')" />
<Import Project="..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props"
Condition="Exists('..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BA750E08-5E41-4B56-8AD5-875716D2CCEA}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>RageCoop.Client.CefHost</RootNamespace>
<AssemblyName>RageCoop.Client.CefHost</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<OutDir>..\..\bin\$(Configuration)\Client\SubProcess</OutDir>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>
</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="BitmapUtil">
<HintPath>..\..\libs\BitmapUtil.dll</HintPath>
</Reference>
<Reference
Include="CefSharp, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
PrivateAssets="All">
<HintPath>..\..\packages\CefSharp.Common.106.0.290\lib\net452\CefSharp.dll</HintPath>
</Reference>
<Reference
Include="CefSharp.Core, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
PrivateAssets="All">
<HintPath>..\..\packages\CefSharp.Common.106.0.290\lib\net452\CefSharp.Core.dll</HintPath>
</Reference>
<Reference
Include="CefSharp.OffScreen, Version=106.0.290.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=MSIL"
PrivateAssets="All">
<HintPath>..\..\packages\CefSharp.OffScreen.106.0.290\lib\net462\CefSharp.OffScreen.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Remoting" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CefController.cs" />
<Compile Include="CefProcessor.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cef.redist.x64.106.0.29\build\cef.redist.x64.props'))" />
<Error Condition="!Exists('..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cef.redist.x86.106.0.29\build\cef.redist.x86.props'))" />
<Error Condition="!Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.props'))" />
<Error Condition="!Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets')"
Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets'))" />
</Target>
<Import Project="..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets"
Condition="Exists('..\..\packages\CefSharp.Common.106.0.290\build\CefSharp.Common.targets')" />
<PropertyGroup>
<PostBuildEvent>
if not exist "ref" mkdir "ref"
copy "$(TargetFileName)" "ref\$(TargetName).dll" /y
copy "CefSharp.OffScreen.dll" "ref\CefSharp.OffScreen.dll" /y
copy "CefSharp.dll" "ref\CefSharp.dll" /y
</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<StartAction>Program</StartAction>
<StartProgram>M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Client\SubProcess\RageCoop.Client.CefHost.exe</StartProgram>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="106.0.29" targetFramework="net48" />
<package id="cef.redist.x86" version="106.0.29" targetFramework="net48" />
<package id="CefSharp.Common" version="106.0.290" targetFramework="net48" />
<package id="CefSharp.OffScreen" version="106.0.290" targetFramework="net48" />
</packages>

335214
Client/Data/Animations.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
{
"Bullet": {
"2861067768": "VEHICLE_WEAPON_INSURGENT_MINIGUN",
"3683206664": "VEHICLE_WEAPON_TECHNICAL_MINIGUN",
"3048454573": "VEHICLE_WEAPON_AKULA_TURRET_SINGLE",
"476907586": "VEHICLE_WEAPON_AKULA_TURRET_DUAL",
"431576697": "VEHICLE_WEAPON_AKULA_MINIGUN",
"3405172033": "VEHICLE_WEAPON_ANNIHILATOR2_MINI",
"1000258817": "VEHICLE_WEAPON_BARRAGE_TOP_MINIGUN",
"525623141": "VEHICLE_WEAPON_BARRAGE_REAR_MINIGUN",
"1338760315": "VEHICLE_WEAPON_CARACARA_MINIGUN",
"490982948": "VEHICLE_WEAPON_DEATHBIKE_DUALMINIGUN",
"3909880809": "VEHICLE_WEAPON_DEATHBIKE2_MINIGUN_LASER",
"2600428406": "VEHICLE_WEAPON_DELUXO_MG",
"1416047217": "VEHICLE_WEAPON_DUNE_MINIGUN",
"3003147322": "VEHICLE_WEAPON_FLAMETHROWER",
"2182329506": "VEHICLE_WEAPON_FLAMETHROWER_SCIFI",
"855547631": "VEHICLE_WEAPON_HAVOK_MINIGUN",
"2263283790": "VEHICLE_WEAPON_POUNDER2_MINI",
"2431961420": "VEHICLE_WEAPON_SEASPARROW2_MINIGUN",
"2667462330": "VEHICLE_WEAPON_SPEEDO4_TURRET_MINI",
"3670375085": "VEHICLE_WEAPON_TAMPA_FIXEDMINIGUN",
"1744687076": "VEHICLE_WEAPON_TAMPA_DUALMINIGUN",
"1697521053": "VEHICLE_WEAPON_THRUSTER_MG",
"376489128": "VEHICLE_WEAPON_TULA_MINIGUN",
"4109257098": "VEHICLE_WEAPON_RCTANK_FLAME",
"3959029566": "VEHICLE_WEAPON_CANNON_BLAZER",
"1119849093": "WEAPON_MINIGUN",
"4256881901": "WEAPON_DIGISCANNER",
"1186503822": "VEHICLE_WEAPON_PLAYER_BUZZARD",
"3056410471": "WEAPON_RAYMINIGUN",
"729375873": "VEHICLE_WEAPON_TURRET_LIMO",
"2756787765": "VEHICLE_WEAPON_TURRET_VALKYRIE",
"50118905": "VEHICLE_WEAPON_RUINER_BULLET"
},
"Lazer": {
"955522731": "VEHICLE_WEAPON_STRIKEFORCE_CANNON",
"539292904": "WEAPON_EXPLOSION",
"1638077257": "VEHICLE_WEAPON_PLAYER_SAVAGE"
},
"Others": {
"3441901897": "WEAPON_BATTLEAXE",
"4192643659": "WEAPON_BOTTLE",
"2460120199": "WEAPON_DAGGER",
"2343591895": "WEAPON_FLASHLIGHT",
"3794977420": "WEAPON_GARBAGEBAG",
"3494679629": "WEAPON_HANDCUFFS",
"4191993645": "WEAPON_HATCHET",
"3638508604": "WEAPON_KNUCKLE",
"3713923289": "WEAPON_MACHETE",
"2484171525": "WEAPON_POOLCUE",
"2725352035": "WEAPON_UNARMED",
"4194021054": "WEAPON_ANIMAL",
"148160082": "WEAPON_COUGAR",
"2578778090": "WEAPON_KNIFE",
"1737195953": "WEAPON_NIGHTSTICK",
"1317494643": "WEAPON_HAMMER",
"2508868239": "WEAPON_BAT",
"1141786504": "WEAPON_GOLFCLUB",
"2227010557": "WEAPON_CROWBAR",
"101631238": "WEAPON_FIREEXTINGUISHER",
"883325847": "WEAPON_PETROLCAN",
"2294779575": "WEAPON_BRIEFCASE",
"28811031": "WEAPON_BRIEFCASE_02",
"3450622333": "VEHICLE_WEAPON_SEARCHLIGHT",
"3530961278": "VEHICLE_WEAPON_RADAR",
"1223143800": "WEAPON_BARBED_WIRE",
"4284007675": "WEAPON_DROWNING",
"1936677264": "WEAPON_DROWNING_IN_VEHICLE",
"2339582971": "WEAPON_BLEEDING",
"2461879995": "WEAPON_ELECTRIC_FENCE",
"3452007600": "WEAPON_FALL",
"910830060": "WEAPON_EXHAUSTION",
"3425972830": "WEAPON_HIT_BY_WATER_CANNON",
"133987706": "WEAPON_RAMMED_BY_CAR",
"2741846334": "WEAPON_RUN_OVER_BY_CAR",
"341774354": "WEAPON_HELI_CRASH",
"2971687502": "VEHICLE_WEAPON_ROTORS",
"3750660587": "WEAPON_FIRE",
"3854032506": "WEAPON_ANIMAL_RETRIEVER",
"3146768957": "WEAPON_SMALL_DOG",
"743550225": "WEAPON_TIGER_SHARK",
"3030980043": "WEAPON_HAMMERHEAD_SHARK",
"4198358245": "WEAPON_KILLER_WHALE",
"861723357": "WEAPON_BOAR",
"1205296881": "WEAPON_PIG",
"1161062353": "WEAPON_COYOTE",
"4106648222": "WEAPON_DEER",
"955837630": "WEAPON_HEN",
"2793925639": "WEAPON_RABBIT",
"3799318422": "WEAPON_CAT",
"94548753": "WEAPON_COW",
"940833800": "WEAPON_STONE_HATCHET",
"3756226112": "WEAPON_SWITCHBLADE",
"419712736": "WEAPON_WRENCH",
"406929569": "WEAPON_FERTILIZERCAN",
"3126027122": "WEAPON_HAZARDCAN"
}
}

329
Client/Data/WeaponHash.cs Normal file
View File

@ -0,0 +1,329 @@
public enum WeaponHash : uint
{
Advancedrifle = 0xAF113F99,
AirDefenceGun = 0x2C082D7D,
AirstrikeRocket = 0x13579279,
Animal = 0xF9FBAEBE,
AnimalRetriever = 0xE5B7DE7A,
Appistol = 0x22D8FE39,
ArenaHomingMissile = 0x648A81D0,
ArenaMachineGun = 0x34FDFF66,
Assaultrifle = 0xBFEFFF6D,
AssaultrifleMk2 = 0x394F415C,
Assaultshotgun = 0xE284C527,
Assaultsmg = 0xEFE7E2DF,
Autoshotgun = 0x12E82D3D,
Ball = 0x23C9F95C,
BarbedWire = 0x48E7B178,
Bat = 0x958A4A8F,
Battleaxe = 0xCD274149,
BirdCrap = 0x6D5E2801,
Bleeding = 0x8B7333FB,
Boar = 0x335CDADD,
Bottle = 0xF9E6AA4B,
Briefcase = 0x88C78EB7,
Briefcase02 = 0x1B79F17,
Bullpuprifle = 0x7F229F94,
BullpuprifleMk2 = 0x84D6FAFD,
Bullpupshotgun = 0x9D61E50F,
Bzgas = 0xA0973D5E,
Carbinerifle = 0x83BF0278,
CarbinerifleMk2 = 0xFAD1F1C9,
Cat = 0xE274FF96,
Ceramicpistol = 0x2B5EF5EC,
Combatmg = 0x7FD62962,
CombatmgMk2 = 0xDBBD7280,
Combatpdw = 0xA3D4D34,
Combatpistol = 0x5EF9FEC4,
Combatshotgun = 0x5A96BA4,
Compactlauncher = 0x781FE4A,
Compactrifle = 0x624FE830,
Cougar = 0x8D4BE52,
Cow = 0x5A2B311,
Coyote = 0x453467D1,
Crowbar = 0x84BD7BFD,
Dagger = 0x92A27487,
Dbshotgun = 0xEF951FBB,
Deer = 0xF4C67A9E,
Digiscanner = 0xFDBADCED,
Doubleaction = 0x97EA20B8,
Drowning = 0xFF58C4FB,
DrowningInVehicle = 0x736F5990,
ElectricFence = 0x92BD4EBB,
Emplauncher = 0xDB26713A,
Exhaustion = 0x364A29EC,
Explosion = 0x2024F4E8,
Fall = 0xCDC174B0,
Fertilizercan = 0x184140A1,
Fire = 0xDF8E89EB,
Fireextinguisher = 0x60EC506,
Firework = 0x7F7497E5,
Flare = 0x497FACC3,
Flaregun = 0x47757124,
Flashlight = 0x8BB05FD7,
Gadgetpistol = 0x57A4368C,
Garbagebag = 0xE232C28C,
Golfclub = 0x440E4788,
Grenade = 0x93E220BD,
Grenadelauncher = 0xA284510B,
GrenadelauncherSmoke = 0x4DD2DC56,
Gusenberg = 0x61012683,
Hammer = 0x4E875F73,
HammerheadShark = 0xB4A915CB,
Handcuffs = 0xD04C944D,
Hatchet = 0xF9DCBF2D,
Hazardcan = 0xBA536372,
Heavypistol = 0xD205520E,
Heavyrifle = 0xC78D71B4,
Heavyshotgun = 0x3AABBBAA,
Heavysniper = 0xC472FE2,
HeavysniperMk2 = 0xA914799,
HeliCrash = 0x145F1012,
Hen = 0x38F8ECBE,
HitByWaterCannon = 0xCC34325E,
Hominglauncher = 0x63AB0442,
KillerWhale = 0xFA3DDCE5,
Knife = 0x99B507EA,
Knuckle = 0xD8DF3C3C,
Machete = 0xDD5DF8D9,
Machinepistol = 0xDB1AA450,
Marksmanpistol = 0xDC4DB296,
Marksmanrifle = 0xC734385A,
MarksmanrifleMk2 = 0x6A6C02E0,
Mg = 0x9D07F764,
Microsmg = 0x13532244,
Militaryrifle = 0x9D1F17E6,
Minigun = 0x42BF8A85,
Minismg = 0xBD248B55,
Molotov = 0x24B17070,
Musket = 0xA89CB99E,
Navyrevolver = 0x917F6C8C,
Nightstick = 0x678B81B1,
PassengerRocket = 0x166218FF,
Petrolcan = 0x34A67B97,
Pig = 0x47D75EF1,
Pipebomb = 0xBA45E8B8,
Pistol = 0x1B06D571,
Pistol50 = 0x99AEEB3B,
PistolMk2 = 0xBFE256D4,
Poolcue = 0x94117305,
Proxmine = 0xAB564B93,
Pumpshotgun = 0x1D073A89,
PumpshotgunMk2 = 0x555AF99A,
Rabbit = 0xA687EC07,
Railgun = 0x6D544C99,
RammedByCar = 0x7FC7D7A,
Raycarbine = 0x476BF155,
Rayminigun = 0xB62D1F67,
Raypistol = 0xAF3696A1,
Remotesniper = 0x33058E22,
Revolver = 0xC1B3C3D1,
RevolverMk2 = 0xCB96392F,
Rpg = 0xB1CA77B1,
RunOverByCar = 0xA36D413E,
Sawnoffshotgun = 0x7846A318,
SmallDog = 0xBB8FE23D,
Smg = 0x2BE6766B,
SmgMk2 = 0x78A97CD0,
Smokegrenade = 0xFDBC8A50,
Sniperrifle = 0x5FC3C11,
Snowball = 0x787F0BB,
Snspistol = 0xBFD21232,
SnspistolMk2 = 0x88374054,
Specialcarbine = 0xC0A3098D,
SpecialcarbineMk2 = 0x969C3D67,
Stickybomb = 0x2C3731D9,
Stinger = 0x687652CE,
StoneHatchet = 0x3813FC08,
Stungun = 0x3656C8C1,
StungunMp = 0x45CD9CF3,
Switchblade = 0xDFE37640,
TigerShark = 0x2C51AD11,
Tranquilizer = 0x32A888BD,
Unarmed = 0xA2719263,
VehicleRocket = 0xBEFDC581,
Vintagepistol = 0x83839C4,
Wrench = 0x19044EE0,
}
public enum VehicleWeaponHash : uint
{
Invalid = 0xFFFFFFFF,
AkulaBarrage = 0x880D14F2,
AkulaMinigun = 0x19B95679,
AkulaMissile = 0x7CBE304C,
AkulaTurretDual = 0x1C6D0842,
AkulaTurretSingle = 0xB5B3B9AD,
Annihilator2Barrage = 0x35D8CC90,
Annihilator2Mini = 0xCAF6CD41,
Annihilator2Missile = 0xC76BC6B7,
ApcCannon = 0x138F71D8,
ApcMg = 0xB56E4E4,
ApcMissile = 0x44A56189,
ArdentMg = 0xC44E4341,
AvengerCannon = 0x9867203B,
BarrageRearGl = 0xA44C228D,
BarrageRearMg = 0x47894765,
BarrageRearMinigun = 0x1F545F65,
BarrageTopMg = 0xF7498994,
BarrageTopMinigun = 0x3B9EBD01,
Bomb = 0x9AF0B90C,
BombCluster = 0xD28BCA3,
BombGas = 0x5540A91E,
BombIncendiary = 0x6AF7A717,
BombStandardWide = 0x6EA548D0,
BombushkaCannon = 0xD8443A59,
BombushkaDualmg = 0x2C2B2D58,
BombWater = 0xF444C4C8,
Bruiser250calLaser = 0x3D6A0196,
Bruiser50cal = 0xD73DC601,
Brutus250calLaser = 0x68C7A4C3,
Brutus50cal = 0xEB5E5C0A,
CannonBlazer = 0xEBF9FF3E,
CaracaraMg = 0x6C516BA8,
CaracaraMinigun = 0x4FCBDC7B,
ChernoMissile = 0xA247D03E,
CometMg = 0xEAA835F3,
Deathbike2MinigunLaser = 0xE90C0BE9,
DeathbikeDualminigun = 0x1D43CE24,
DeluxoMg = 0x9AFF6376,
DeluxoMissile = 0xB4F96934,
DogfighterMg = 0x5F1834E2,
DogfighterMissile = 0xCA46F87D,
Dominator450cal = 0xF80C9B0F,
Dominator550calLaser = 0xB4246A5F,
DuneGrenadelauncher = 0xA0FC710D,
DuneMg = 0xD11507CF,
DuneMinigun = 0x54672A71,
EnemyLaser = 0x5D6660AB,
Flamethrower = 0xB300643A,
FlamethrowerScifi = 0x8213B4A2,
Granger2Mg = 0xEAE2E19A,
HackerMissile = 0x766FF7B1,
HackerMissileHoming = 0x77EACF96,
HalftrackDualmg = 0x4F6384FB,
HalftrackQuadmg = 0x491B2E74,
HavokMinigun = 0x32FE9EEF,
HunterBarrage = 0x2ED14835,
HunterCannon = 0x2A00AB1A,
HunterMg = 0x42BA80A7,
HunterMissile = 0x924A5F5,
Impaler250cal = 0x5F565C09,
Impaler350calLaser = 0x8CBDFC88,
Imperator250calLaser = 0x7817C526,
Imperator50cal = 0xB662C67B,
InsurgentMinigun = 0xAA886DF8,
Issi450cal = 0x7648E34D,
Issi550calLaser = 0x767F6925,
Jb700Mg = 0x373AD53C,
KhanjaliCannon = 0x1E3ACFA0,
KhanjaliCannonHeavy = 0x838B716D,
KhanjaliGl = 0x178605E2,
KhanjaliMg = 0x2A6F8E1D,
KosatkaTorpedo = 0x62E2140E,
MenacerMg = 0xDFCAF8A4,
MicrolightMg = 0xC4E0216C,
Mine = 0x59EAE9A4,
MineEmp = 0x69E10D60,
MineEmpRc = 0x5454B4C6,
MineKinetic = 0x3C09584E,
MineKineticRc = 0x252AF560,
MineSlick = 0x56FACAC7,
MineSlickRc = 0x84E87B17,
MineSpike = 0xD96DA06C,
MineSpikeRc = 0x7C2AFE51,
MineTar = 0xF4418BA0,
MineTarRc = 0x7D3474D6,
MobileopsCannon = 0xE53E69A4,
MogulDualnose = 0xE5F3AE2F,
MogulDualturret = 0xBA277C01,
MogulNose = 0xF6189F4A,
MogulTurret = 0xE2FD135E,
Monster3Glkin = 0xE5AE53DD,
MortarExplosive = 0xA1A8CCD2,
MortarKinetic = 0x632A22FD,
Mule4Mg = 0x84558727,
Mule4Missile = 0x4772F84B,
Mule4TurretGl = 0xDD124A65,
NightsharkMg = 0xA61AC574,
NoseTurretValkyrie = 0x4170E491,
Oppressor2Cannon = 0xD64D3469,
Oppressor2Mg = 0xE2451DD6,
Oppressor2Missile = 0x753A78F1,
OppressorMg = 0xD9322EDD,
OppressorMissile = 0x8BB7C63E,
Paragon2Mg = 0x2CAC4286,
PatrolboatDualmg = 0x4C2FB4E9,
PlaneRocket = 0xCF0896E0,
PlayerBullet = 0x4B139B2D,
PlayerBuzzard = 0x46B89C8E,
PlayerHunter = 0x9F1A91DE,
PlayerLaser = 0xEFFD014B,
PlayerLazer = 0xE2822A29,
PlayerSavage = 0x61A31349,
Pounder2Barrage = 0x926B8CE4,
Pounder2Gl = 0x9318FF16,
Pounder2Mini = 0x86E6F84E,
Pounder2Missile = 0x9A8EA9A,
Radar = 0xD276317E,
RctankFlame = 0xF4EE498A,
RctankGun = 0x52FCA619,
RctankLazer = 0x57F22C50,
RctankRocket = 0x76F744CB,
RevolterMg = 0xBD5E626A,
RogueCannon = 0xE72ABBC2,
RogueMg = 0x97273CD,
RogueMissile = 0x6C88E47D,
Rotors = 0xB1205A4E,
RuinerBullet = 0x2FCC0F9,
RuinerRocket = 0x50DC6AB,
SavestraMg = 0xEB41E84E,
Scarab250calLaser = 0xE22DEDCC,
Scarab50cal = 0x217FEF28,
ScramjetMg = 0xDCE6112,
ScramjetMissile = 0xBCE908DB,
SeabreezeMg = 0x51B8D4E8,
Searchlight = 0xCDAC517D,
Seasparrow2Minigun = 0x90F4C94C,
Slamvan450cal = 0x3AAB6E6B,
Slamvan550calLaser = 0x519543AE,
SpaceRocket = 0xF8A3939F,
Speedo4Mg = 0xC7FCF93C,
Speedo4TurretMg = 0xD6561141,
Speedo4TurretMini = 0x9EFE3EBA,
StrikeforceBarrage = 0x39BC6683,
StrikeforceCannon = 0x38F41EAB,
StrikeforceMissile = 0x1EF01D8A,
SubcarMg = 0x461DDDB0,
SubcarMissile = 0xD4897C0E,
SubcarTorpedo = 0xE783C3BA,
SubMissileHoming = 0xAAE74AC1,
TampaDualminigun = 0x67FDCFE4,
TampaFixedminigun = 0xDAC57AAD,
TampaMissile = 0x9E5840A2,
TampaMortar = 0x3C83C410,
Tank = 0x73F7C04B,
TechnicalMinigun = 0xDB894608,
ThrusterMg = 0x652E1D9D,
ThrusterMissile = 0x4635DD15,
TrailerDualaa = 0x808C4D4C,
TrailerMissile = 0x145599F7,
TrailerQuadmg = 0x4711B02C,
TulaDualmg = 0xB0D15C0B,
TulaMg = 0x488BD081,
TulaMinigun = 0x1670C4A8,
TulaNosemg = 0x419D8E15,
TurretBoxville = 0xB54F4918,
TurretDinghy550cal = 0xB3B155FD,
TurretInsurgent = 0x44DB5498,
TurretLimo = 0x2B796481,
TurretPatrolboat50cal = 0xDF4EA041,
TurretTechnical = 0x7FD2EA0B,
TurretValkyrie = 0xA4513E35,
VigilanteMg = 0xF4077EE7,
VigilanteMissile = 0x504DA665,
ViserisMg = 0x87A02E06,
VolatolDualmg = 0x4497AC40,
Zr380250calLaser = 0x220093BC,
Zr38050cal = 0x6AB93C82,
}

2891
Client/Data/Weapons.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
<Application x:Class="RageCoop.Client.Installer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,11 @@
using System.Windows;
namespace RageCoop.Client.Installer
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura />
</Weavers>

View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:all>
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
<xs:annotation>
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
<xs:annotation>
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableCompression" type="xs:boolean">
<xs:annotation>
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableCleanup" type="xs:boolean">
<xs:annotation>
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
<xs:annotation>
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
<xs:annotation>
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ExcludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged 32 bit assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged 64 bit assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="PreloadOrder" type="xs:string">
<xs:annotation>
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@ -0,0 +1,15 @@
<Window x:Class="RageCoop.Client.Installer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="376" Width="617" Background="#FFBABABA">
<Grid>
<Grid.Background>
<ImageBrush ImageSource="/bg.png" Opacity="0.2" />
</Grid.Background>
<Label x:Name="Status" FontSize="20" Foreground="#FF232323" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Window>

View File

@ -0,0 +1,200 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using RageCoop.Core;
using static RageCoop.Client.Shared;
using MessageBox = System.Windows.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
namespace RageCoop.Client.Installer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Choose();
}
private void Choose()
{
var od = new OpenFileDialog
{
Filter = "GTA 5 executable |GTA5.exe;PlayGTAV.exe",
Title = "Select you GTAV executable"
};
if (od.ShowDialog() ?? !true)
Task.Run(() =>
{
try
{
Install(Directory.GetParent(od.FileName).FullName);
}
catch (Exception ex)
{
MessageBox.Show("Installation failed: " + ex);
Environment.Exit(1);
}
});
else
Environment.Exit(0);
}
private void Install(string root)
{
UpdateStatus("Checking requirements");
var shvPath = Path.Combine(root, "ScriptHookV.dll");
var shvdnPath = Path.Combine(root, "ScriptHookVDotNetCore.dll");
var scriptsPath = Path.Combine(root, "Scripts");
var installPath = Path.Combine(root, "CoreScripts");
var legacyPath = Path.Combine(scriptsPath, "RageCoop");
if (Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName.StartsWith(installPath))
throw new InvalidOperationException(
"The installer is not meant to be run in the game folder, please extract the zip to somewhere else and run again.");
if (!File.Exists(shvPath))
{
MessageBox.Show("Please install ScriptHookV first!");
Environment.Exit(1);
}
Directory.CreateDirectory(installPath);
if (!File.Exists(shvdnPath))
{
MessageBox.Show("Please install ScriptHookVDotNetCore first!");
Environment.Exit(1);
}
var shvdnVer = GetVer(shvdnPath);
if (shvdnVer < new Version(1, 2, 1))
{
MessageBox.Show("Please update ScriptHookVDotNetCore to latest version!" +
$"\nCurrent version is {shvdnVer}, 1.2.1 or higher is required");
Environment.Exit(1);
}
UpdateStatus("Removing old versions");
if (Directory.Exists(scriptsPath))
foreach (var f in Directory.GetFiles(scriptsPath, "RageCoop.*", SearchOption.AllDirectories))
File.Delete(f);
// <= 1.5 installation check
if (Directory.Exists(legacyPath)) Directory.Delete(legacyPath, true);
foreach (var f in Directory.GetFiles(installPath, "*.dll", SearchOption.AllDirectories)) File.Delete(f);
if (File.Exists("Scripts/RageCoop.Core.dll") && File.Exists("Scripts/RageCoop.Client.dll"))
{
UpdateStatus("Installing...");
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()),
new DirectoryInfo(installPath));
Finish();
}
else
{
throw new Exception(
"Required files are missing, please re-download the installer from official website");
}
void Finish()
{
checkKeys:
UpdateStatus("Checking conflicts");
var menyooConfig = Path.Combine(root, @"menyooStuff\menyooConfig.ini");
var settingsPath = Path.Combine(installPath, "Data", "Setting.json");
ClientSettings settings = null;
try
{
settings = Util.ReadSettings(settingsPath);
}
catch
{
settings = new();
}
if (File.Exists(menyooConfig))
{
var lines = File.ReadAllLines(menyooConfig)
.Where(x => !x.StartsWith(";") && x.EndsWith(" = " + (int)settings.MenuKey));
if (lines.Any())
if (MessageBox.Show("Following menyoo config value will conflict with RAGECOOP menu key\n" +
string.Join("\n", lines)
+ "\nDo you wish to change the Menu Key?", "Warning!",
MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var ae = new AutoResetEvent(false);
UpdateStatus("Press the key you wish to change to");
Dispatcher.BeginInvoke(new Action(() =>
KeyDown += (s, e) =>
{
settings.MenuKey = (GTA.Keys)KeyInterop.VirtualKeyFromKey(e.Key);
ae.Set();
}));
ae.WaitOne();
if (!Util.SaveSettings(settingsPath, settings))
{
MessageBox.Show("Error occurred when saving settings");
Environment.Exit(1);
}
MessageBox.Show("Menu key changed to " + settings.MenuKey);
goto checkKeys;
}
}
UpdateStatus("Checking ZeroTier");
try
{
ZeroTierHelper.Check();
}
catch
{
if (MessageBox.Show(
"You can't join ZeroTier server unless ZeroTier is installed, do you want to download and install it?",
"Install ZeroTier", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var url = "https://download.zerotier.com/dist/ZeroTier%20One.msi";
UpdateStatus("Downloading ZeroTier from " + url);
try
{
HttpHelper.DownloadFile(url, "ZeroTier.msi",
p => UpdateStatus("Downloading ZeroTier " + p + "%"));
UpdateStatus("Installing ZeroTier");
Process.Start("ZeroTier.msi").WaitForExit();
}
catch
{
MessageBox.Show("Failed to download ZeroTier, please download it from official website");
Process.Start(url);
}
}
}
UpdateStatus("Completed!");
MessageBox.Show("Installation successful!");
Environment.Exit(0);
}
}
private void UpdateStatus(string status)
{
Dispatcher.BeginInvoke(new Action(() => Status.Content = status));
}
private Version GetVer(string location)
{
return Version.Parse(FileVersionInfo.GetVersionInfo(location).FileVersion);
}
}
}

View File

@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NoAotCompile>true</NoAotCompile>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AllowedReferenceRelatedFileExtensions>
-
</AllowedReferenceRelatedFileExtensions>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutDir>..\..\bin\Debug\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>DEBUG</DefineConstants>
<WarningLevel>4</WarningLevel>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutDir>..\..\bin\Release\Client</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Scripts\RageCoop.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="bg.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile; runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup />
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -0,0 +1,72 @@
<Project>
<PropertyGroup>
<AssemblyName>RageCoop.Client.Installer</AssemblyName>
<IntermediateOutputPath>obj\Debug\</IntermediateOutputPath>
<BaseIntermediateOutputPath>obj\</BaseIntermediateOutputPath>
<MSBuildProjectExtensionsPath>M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\</MSBuildProjectExtensionsPath>
<_TargetAssemblyProjectName>RageCoop.Client.Installer</_TargetAssemblyProjectName>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<None Remove="bg.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RageCoop.Client\RageCoop.Client.csproj" />
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
</ItemGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\sharpziplib\1.3.3\lib\net45\ICSharpCode.SharpZipLib.dll" />
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\microsoft.extensions.objectpool\6.0.8\lib\net461\Microsoft.Extensions.ObjectPool.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\mscorlib.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationCore.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\PresentationFramework.dll" />
<ReferencePath Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Client\RageCoop.Client.dll" />
<ReferencePath Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\bin\Debug\Core\RageCoop.Core.dll" />
<ReferencePath Include="C:\Users\Sardelka\.nuget\packages\system.buffers\4.5.1\ref\net45\System.Buffers.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Core.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Data.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Drawing.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.IO.Compression.FileSystem.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Numerics.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Runtime.Serialization.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Windows.Controls.Ribbon.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Windows.Forms.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xaml.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\System.Xml.Linq.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClient.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationClientsideProviders.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationProvider.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\UIAutomationTypes.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\WindowsBase.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\WindowsFormsIntegration.dll" />
<ReferencePath Include="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8\Facades\netstandard.dll" />
</ItemGroup>
<ItemGroup>
<Compile Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\Debug\net48\MainWindow.g.cs" />
<Compile Include="M:\SandBox-Shared\repos\RAGECOOP\RAGECOOP-V\RageCoop.Client.Installer\obj\Debug\net48\App.g.cs" />
</ItemGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

63
Client/Installer/Resource.Designer.cs generated Normal file
View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace RageCoop.Client.Installer {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RageCoop.Client.Installer.Resource", typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</root>

BIN
Client/Installer/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

24
Client/Loader/Console.cs Normal file
View File

@ -0,0 +1,24 @@
using System;
namespace GTA
{
internal class Console
{
private static readonly SHVDN.Console console = AppDomain.CurrentDomain.GetData("Console") as SHVDN.Console;
public static void Warning(object format, params object[] objects)
{
console.PrintInfo("[~o~WARNING~w~] ", format.ToString(), objects);
}
public static void Error(object format, params object[] objects)
{
console.PrintError("[~r~ERROR~w~] ", format.ToString(), objects);
}
public static void Info(object format, params object[] objects)
{
console.PrintWarning("[~b~INFO~w~] ", format.ToString(), objects);
}
}
}

View File

@ -0,0 +1,216 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using GTA.UI;
using SHVDN;
using Console = GTA.Console;
namespace RageCoop.Client.Loader
{
public class LoaderContext : MarshalByRefObject, IDisposable
{
#region PRIMARY-LOADING-LOGIC
public static ConcurrentDictionary<string, LoaderContext> LoadedDomains =>
new ConcurrentDictionary<string, LoaderContext>(_loadedDomains);
private static readonly ConcurrentDictionary<string, LoaderContext> _loadedDomains =
new ConcurrentDictionary<string, LoaderContext>();
public bool UnloadRequested;
public string BaseDirectory => AppDomain.CurrentDomain.BaseDirectory;
private ScriptDomain CurrentDomain => ScriptDomain.CurrentDomain;
public static void CheckForUnloadRequest()
{
lock (_loadedDomains)
{
foreach (var p in _loadedDomains.Values)
if (p.UnloadRequested)
Unload(p);
}
}
public static bool IsLoaded(string dir)
{
return _loadedDomains.ContainsKey(Path.GetFullPath(dir).ToLower());
}
public static LoaderContext Load(string dir)
{
lock (_loadedDomains)
{
dir = Path.GetFullPath(dir).ToLower();
if (IsLoaded(dir)) throw new Exception("Already loaded");
ScriptDomain newDomain = null;
try
{
dir = Path.GetFullPath(dir).ToLower();
Directory.CreateDirectory(dir);
// Delete API assemblies
Directory.GetFiles(dir, "ScriptHookVDotNet*", SearchOption.AllDirectories).ToList()
.ForEach(x => File.Delete(x));
newDomain = ScriptDomain.Load(
Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName, dir);
newDomain.AppDomain.SetData("Primary", ScriptDomain.CurrentDomain);
newDomain.AppDomain.SetData("Console",
ScriptDomain.CurrentDomain.AppDomain.GetData("Console"));
// Copy to target domain base directory
// Delete loader assembly
var loaderPath = Path.Combine(dir, Path.GetFileName(typeof(LoaderContext).Assembly.Location));
if (File.Exists(loaderPath)) File.Delete(loaderPath);
var context = (LoaderContext)newDomain.AppDomain.CreateInstanceFromAndUnwrap(
typeof(LoaderContext).Assembly.Location,
typeof(LoaderContext).FullName, false,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[] { }
, null, null);
newDomain.AppDomain.SetData("RageCoop.Client.LoaderContext", context);
newDomain.Start();
_loadedDomains.TryAdd(dir, context);
return _loadedDomains[dir];
}
catch (Exception ex)
{
Notification.Show(ex.ToString());
Console.Error(ex);
if (newDomain != null) ScriptDomain.Unload(newDomain);
throw;
}
}
}
private static Assembly ResolveLoader(object sender, ResolveEventArgs args)
{
Log.Message(Log.Level.Debug, "resolving assembly " + args.Name);
if (args.Name == typeof(LoaderContext).Assembly.GetName().Name) return typeof(LoaderContext).Assembly;
return null;
}
public static void Unload(LoaderContext domain)
{
lock (_loadedDomains)
{
var name = domain.CurrentDomain.Name;
Console.Info("Unloading domain: " + name);
if (!_loadedDomains.TryRemove(domain.BaseDirectory.ToLower(), out _))
throw new Exception("Failed to remove domain from list");
domain.Dispose();
ScriptDomain.Unload(domain.CurrentDomain);
Console.Info("Unloaded domain: " + name);
}
}
public static void Unload(string dir)
{
lock (_loadedDomains)
{
Unload(_loadedDomains[Path.GetFullPath(dir).ToLower()]);
}
}
public static void TickAll()
{
lock (_loadedDomains)
{
foreach (var c in _loadedDomains.Values) c.DoTick();
}
}
public static void KeyEventAll(Keys keys, bool status)
{
lock (_loadedDomains)
{
foreach (var c in _loadedDomains.Values) c.DoKeyEvent(keys, status);
}
}
public static void UnloadAll()
{
lock (_loadedDomains)
{
foreach (var d in _loadedDomains.Values.ToArray()) Unload(d);
}
}
#endregion
#region LOAD-CONTEXT
private readonly Action _domainDoTick;
private readonly Action<Keys, bool> _domainDoKeyEvent;
private LoaderContext()
{
AppDomain.CurrentDomain.DomainUnload += (s, e) => Dispose();
var tickMethod = typeof(ScriptDomain).GetMethod("DoTick", BindingFlags.Instance | BindingFlags.NonPublic);
var doKeyEventMethod =
typeof(ScriptDomain).GetMethod("DoKeyEvent", BindingFlags.Instance | BindingFlags.NonPublic);
// Create delegates to avoid using reflection to call method each time, which is slow
_domainDoTick = (Action)Delegate.CreateDelegate(typeof(Action), CurrentDomain, tickMethod);
_domainDoKeyEvent =
(Action<Keys, bool>)Delegate.CreateDelegate(typeof(Action<Keys, bool>), CurrentDomain,
doKeyEventMethod);
Console.Info(
$"Loaded domain: {AppDomain.CurrentDomain.FriendlyName}, {AppDomain.CurrentDomain.BaseDirectory}");
}
public static ScriptDomain PrimaryDomain => AppDomain.CurrentDomain.GetData("Primary") as ScriptDomain;
public static LoaderContext CurrentContext =>
(LoaderContext)AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext");
/// <summary>
/// Request the current domain to be unloaded
/// </summary>
public static void RequestUnload()
{
if (PrimaryDomain == null)
throw new NotSupportedException(
"Current domain not loaded by the loader therefore cannot be unloaded automatically");
CurrentContext.UnloadRequested = true;
}
public override object InitializeLifetimeService()
{
return null;
}
public void DoTick()
{
_domainDoTick();
}
public void DoKeyEvent(Keys key, bool status)
{
_domainDoKeyEvent(key, status);
}
public void Dispose()
{
lock (this)
{
if (PrimaryDomain == null) return;
AppDomain.CurrentDomain.SetData("Primary", null);
}
}
#endregion
}
}

51
Client/Loader/Main.cs Normal file
View File

@ -0,0 +1,51 @@
using System;
using System.IO;
using GTA;
using GTA.UI;
using SHVDN;
using Script = GTA.Script;
namespace RageCoop.Client.Loader
{
[ScriptAttributes(Author = "RageCoop", NoScriptThread = true,
SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
public class Main : Script
{
private static readonly string GameDir = Directory.GetParent(typeof(ScriptDomain).Assembly.Location).FullName;
private static readonly string ScriptsLocation = Path.Combine(GameDir, "RageCoop", "Scripts");
private bool _loaded;
public Main()
{
if (LoaderContext.PrimaryDomain != null) return;
Tick += OnTick;
KeyDown += (s, e) => LoaderContext.KeyEventAll(e.KeyCode, true);
KeyUp += (s, e) => LoaderContext.KeyEventAll(e.KeyCode, false);
Aborted += (s, e) => LoaderContext.UnloadAll();
}
private void OnTick(object sender, EventArgs e)
{
if (!_loaded)
{
_loaded = !Game.IsLoading;
return;
}
LoaderContext.CheckForUnloadRequest();
if (!LoaderContext.IsLoaded(ScriptsLocation))
{
if (!File.Exists(Path.Combine(ScriptsLocation, "RageCoop.Client.dll")))
{
Notification.Show("~r~Main assembly is missing, please re-install the client");
Abort();
return;
}
LoaderContext.Load(ScriptsLocation);
}
LoaderContext.TickAll();
}
}
}

View File

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

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FC8CBDBB-6DC3-43AF-B34D-092E476410A5}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RageCoop.Client.Loader</RootNamespace>
<AssemblyName>RageCoop.Client.Loader</AssemblyName>
<OutPutPath>..\..\bin\$(Configuration)\Client\Loader</OutPutPath>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ScriptHookVDotNet3, Version=3.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>True</SpecificVersion>
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\..\libs\ScriptHookVDotNet.asi</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Console.cs" />
<Compile Include="LoaderContext.cs" />
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,47 @@
namespace RageCoop.Client.Scripting
{
public static unsafe partial class APIBridge
{
public static class Config
{
public static System.String Username => GetConfig<System.String>("Username");
public static System.Boolean EnableAutoRespawn => GetConfig<System.Boolean>("EnableAutoRespawn");
public static GTA.BlipColor BlipColor => GetConfig<GTA.BlipColor>("BlipColor");
public static GTA.BlipSprite BlipSprite => GetConfig<GTA.BlipSprite>("BlipSprite");
public static System.Single BlipScale => GetConfig<System.Single>("BlipScale");
public static System.Boolean ShowPlayerNameTag => GetConfig<System.Boolean>("ShowPlayerNameTag");
}
#region PROPERTIES
public static System.Int32 LocalPlayerID => GetProperty<System.Int32>("LocalPlayerID");
public static System.Boolean IsOnServer => GetProperty<System.Boolean>("IsOnServer");
public static System.Net.IPEndPoint ServerEndPoint => GetProperty<System.Net.IPEndPoint>("ServerEndPoint");
public static System.Boolean IsMenuVisible => GetProperty<System.Boolean>("IsMenuVisible");
public static System.Boolean IsChatFocused => GetProperty<System.Boolean>("IsChatFocused");
public static System.Boolean IsPlayerListVisible => GetProperty<System.Boolean>("IsPlayerListVisible");
public static System.Version CurrentVersion => GetProperty<System.Version>("CurrentVersion");
public static System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo> Players => GetProperty<System.Collections.Generic.Dictionary<System.Int32, RageCoop.Client.Scripting.PlayerInfo>>("Players");
#endregion
#region FUNCTIONS
public static void Connect(System.String address) => InvokeCommand("Connect", address);
public static void Disconnect() => InvokeCommand("Disconnect");
public static System.Collections.Generic.List<RageCoop.Core.ServerInfo> ListServers() => InvokeCommand<System.Collections.Generic.List<RageCoop.Core.ServerInfo>>("ListServers");
public static void LocalChatMessage(System.String from, System.String message) => InvokeCommand("LocalChatMessage", from, message);
public static void SendChatMessage(System.String message) => InvokeCommand("SendChatMessage", message);
public static RageCoop.Client.Scripting.ClientResource GetResource(System.String name) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResource", name);
public static RageCoop.Client.Scripting.ClientResource GetResourceFromPath(System.String path) => InvokeCommand<RageCoop.Client.Scripting.ClientResource>("GetResourceFromPath", path);
public static System.Object GetConfig(System.String name) => InvokeCommand<System.Object>("GetConfig", name);
public static void SetConfig(System.String name, System.String jsonVal) => InvokeCommand("SetConfig", name, jsonVal);
public static void RegisterCustomEventHandler(RageCoop.Core.Scripting.CustomEventHash hash, RageCoop.Core.Scripting.CustomEventHandler handler) => InvokeCommand("RegisterCustomEventHandler", hash, handler);
#endregion
}
}

View File

@ -0,0 +1,161 @@
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("RageCoop.Client")] // For debugging
namespace RageCoop.Client.Scripting
{
public static unsafe partial class APIBridge
{
static readonly ThreadLocal<char[]> _resultBuf = new(() => new char[4096]);
static readonly List<CustomEventHandler> _handlers = new();
static APIBridge()
{
if (SHVDN.Core.GetPtr == null)
throw new InvalidOperationException("Game not running");
foreach(var fd in typeof(APIBridge).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
{
var importAttri = fd.GetCustomAttribute<ApiImportAttribute>();
if (importAttri == null)
continue;
importAttri.EntryPoint ??= fd.Name;
var key = $"RageCoop.Client.Scripting.API.{importAttri.EntryPoint}";
var fptr = SHVDN.Core.GetPtr(key);
if (fptr == default)
throw new KeyNotFoundException($"Failed to find function pointer: {key}");
fd.SetValue(null,fptr);
}
}
/// <summary>
/// Copy content of string to a sequential block of memory
/// </summary>
/// <param name="strs"></param>
/// <returns>Pointer to the start of the block, can be used as argv</returns>
/// <remarks>Call <see cref="Marshal.FreeHGlobal(nint)"/> with the returned pointer when finished using</remarks>
internal static char** StringArrayToMemory(string[] strs)
{
var argc = strs.Length;
var cbSize = sizeof(IntPtr) * argc + strs.Sum(s => (s.Length + 1) * sizeof(char));
var result = (char**)Marshal.AllocHGlobal(cbSize);
var pCurStr = (char*)(result + argc);
for (int i = 0; i < argc; i++)
{
result[i] = pCurStr;
var len = strs[i].Length;
var cbStrSize = (len + 1) * sizeof(char); // null terminator
fixed (char* pStr = strs[i])
{
System.Buffer.MemoryCopy(pStr, pCurStr, cbStrSize, cbStrSize);
}
pCurStr += len + 1;
}
return result;
}
internal static void InvokeCommand(string name, params object[] args)
=> InvokeCommandAsJson(name, args);
internal static T InvokeCommand<T>(string name, params object[] args)
=> JsonDeserialize<T>(InvokeCommandAsJson(name, args));
/// <summary>
/// Invoke command and get the return value as json
/// </summary>
/// <param name="name"></param>
/// <param name="args"></param>
/// <returns>The json representation of returned object</returns>
/// <exception cref="Exception"></exception>
internal static string InvokeCommandAsJson(string name, params object[] args)
{
var argc = args.Length;
var argv = StringArrayToMemory(args.Select(JsonSerialize).ToArray());
try
{
fixed(char* pName = name)
{
var resultLen = InvokeCommandAsJsonUnsafe(pName, argc, argv);
if (resultLen == 0)
throw new Exception(GetLastResult());
return GetLastResult();
}
}
finally
{
Marshal.FreeHGlobal((IntPtr)argv);
}
}
public static string GetLastResult()
{
var countCharsRequired = GetLastResultLenInChars() + 1;
if (countCharsRequired > _resultBuf.Value.Length)
{
_resultBuf.Value = new char[countCharsRequired];
}
var cbBufSize = _resultBuf.Value.Length * sizeof(char);
fixed (char* pBuf = _resultBuf.Value)
{
if (GetLastResultUnsafe(pBuf, cbBufSize) > 0)
{
return new string(pBuf);
}
return null;
}
}
public static void SendCustomEvent(CustomEventHash hash, params object[] args)
=> SendCustomEvent(CustomEventFlags.None, hash, args);
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash hash, params object[] args)
{
var writer = GetWriter();
CustomEvents.WriteObjects(writer, args);
SendCustomEventUnsafe(flags, hash, writer.Address, writer.Position);
}
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
internal static string GetPropertyAsJson(string name) => InvokeCommandAsJson("GetProperty", name);
internal static string GetConfigAsJson(string name) => InvokeCommandAsJson("GetConfig", name);
internal static T GetProperty<T>(string name) => JsonDeserialize<T>(GetPropertyAsJson(name));
internal static T GetConfig<T>(string name) => JsonDeserialize<T>(GetConfigAsJson(name));
internal static void SetProperty(string name, object val) => InvokeCommand("SetProperty", name, val);
internal static void SetConfig(string name, object val) => InvokeCommand("SetConfig", name, val);
[ApiImport]
public static delegate* unmanaged<char*, CustomEventHash> GetEventHash;
[ApiImport]
private static delegate* unmanaged<char*,void> SetLastResult;
[ApiImport(EntryPoint = "GetLastResult")]
private static delegate* unmanaged<char*, int, int> GetLastResultUnsafe;
[ApiImport(EntryPoint = "InvokeCommand")]
private static delegate* unmanaged<char*, int, char**, int> InvokeCommandAsJsonUnsafe;
[ApiImport(EntryPoint = "SendCustomEvent")]
private static delegate* unmanaged<CustomEventFlags, int, byte*, int, void> SendCustomEventUnsafe;
[ApiImport]
private static delegate* unmanaged<int> GetLastResultLenInChars;
[ApiImport]
public static delegate* unmanaged<LogLevel, char*, void> LogEnqueue;
}
[AttributeUsage(AttributeTargets.Field)]
class ApiImportAttribute : Attribute
{
public string EntryPoint;
}
}

View File

@ -0,0 +1,28 @@
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
public class ClientFile : ResourceFile
{
public ClientFile() {
GetStream = GetStreamMethod;
}
[JsonInclude]
public string FullPath { get; internal set; }
Stream GetStreamMethod()
{
if (IsDirectory)
{
return File.Open(FullPath, FileMode.Open);
}
throw new InvalidOperationException("Cannot open directory as file");
}
}
}

View File

@ -0,0 +1,49 @@
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
/// <summary>
/// </summary>
public class ClientResource
{
/// <summary>
/// Name of the resource
/// </summary>
[JsonInclude]
public string Name { get; internal set; }
/// <summary>
/// Directory where the scripts is loaded from
/// </summary>
[JsonInclude]
public string ScriptsDirectory { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
[JsonInclude]
public string DataFolder { get; internal set; }
/// <summary>
/// Get the <see cref="ClientFile" /> where this script is loaded from.
/// </summary>
[JsonInclude]
public Dictionary<string, ClientFile> Files { get; internal set; } = new Dictionary<string, ClientFile>();
/// <summary>
/// A <see cref="Core.Logger" /> instance that can be used to debug your resource.
/// </summary>
[JsonIgnore]
public ResourceLogger Logger => ResourceLogger.Default;
}
}

View File

@ -0,0 +1,62 @@
using GTA;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace RageCoop.Client.Scripting
{
[JsonDontSerialize]
[ScriptAttributes(NoDefaultInstance = true)]
public abstract class ClientScript : Script
{
readonly ConcurrentQueue<Func<bool>> _jobQueue = new();
readonly Queue<Func<bool>> _reAdd = new();
public ClientScript()
{
var dir = SHVDN.Core.CurrentDirectory;
CurrentResource = APIBridge.GetResourceFromPath(dir);
if (CurrentResource == null)
throw new Exception("No resource associated with this script is found");
CurrentFile = CurrentResource.Files.Values.FirstOrDefault(x => x?.FullPath?.ToLower() == FilePath?.ToLower());
if (CurrentFile == null)
{
Logger.Warning("No file associated with curent script was found");
}
}
protected void QueueAction(Func<bool> action) => _jobQueue.Enqueue(action);
protected void QueueAction(Action action) => QueueAction(() => { action(); return true; });
protected override void OnTick()
{
base.OnTick();
DoQueuedJobs();
}
private void DoQueuedJobs()
{
while (_reAdd.TryDequeue(out var toAdd))
_jobQueue.Enqueue(toAdd);
while (_jobQueue.TryDequeue(out var job))
{
if (!job())
_reAdd.Enqueue(job);
}
}
/// <summary>
/// Get the <see cref="ClientFile" /> instance where this script is loaded from.
/// </summary>
public ClientFile CurrentFile { get; }
/// <summary>
/// Get the <see cref="ClientResource" /> that this script belongs to.
/// </summary>
public ClientResource CurrentResource { get; }
/// <summary>
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
/// </summary>
public ResourceLogger Logger => CurrentResource.Logger;
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
public class PlayerInfo
{
public byte HolePunchStatus { get; internal set; }
public bool IsHost { get; internal set; }
public string Username { get; internal set; }
public int ID { get; internal set; }
public int EntityHandle { get; internal set; }
public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; internal set; }
public float Ping { get; internal set; }
public float PacketTravelTime { get; internal set; }
public bool DisplayNameTag { get; internal set; }
public bool HasDirectConnection { get; internal set; }
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<OutDir>..\..\bin\API</OutDir>
<DocumentationFile>..\..\bin\API\RageCoop.Client.Scripting.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
public class ResourceLogger : Core.Logger
{
public static readonly ResourceLogger Default = new();
public ResourceLogger()
{
FlushImmediately = true;
OnFlush += FlushToMainModule;
}
private unsafe void FlushToMainModule(LogLine line, string fomatted)
{
fixed (char* pMsg = line.Message)
{
APIBridge.LogEnqueue(line.LogLevel, pMsg);
}
}
}
}

View File

@ -0,0 +1,13 @@
global using static RageCoop.Core.Shared;
global using static RageCoop.Client.Scripting.Shared;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
internal class Shared
{
}
}

View File

@ -0,0 +1,92 @@

namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
public class ClientSettings
{
/// <summary>
/// LogLevel for RageCoop.
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 1;
/// <summary>
/// Don't use it!
/// </summary>
public string Username { get; set; } = "Player";
/// <summary>
/// The password used to authenticate when connecting to a server.
/// </summary>
public string Password { get; set; } = "";
/// <summary>
/// Don't use it!
/// </summary>
public string LastServerAddress { get; set; } = "127.0.0.1:4499";
/// <summary>
/// Don't use it!
/// </summary>
public string MasterServer { get; set; } = "https://test.ragecoop.com/";
/// <summary>
/// Don't use it!
/// </summary>
public bool FlipMenu { get; set; } = false;
/// <summary>
/// Don't use it!
/// </summary>
public bool Voice { get; set; } = false;
/// <summary>
/// The key to open menu
/// </summary>
public Keys MenuKey { get; set; } = Keys.F7;
/// <summary>
/// The key to enter a vehicle as passenger.
/// </summary>
public Keys PassengerKey { get; set; } = Keys.G;
/// <summary>
/// Disable world NPC traffic, mission entities won't be affected
/// </summary>
public bool DisableTraffic { get; set; } = false;
/// <summary>
/// Bring up pause menu but don't freeze time when FrontEndPauseAlternate(Esc) is pressed.
/// </summary>
public bool DisableAlternatePause { get; set; } = true;
/// <summary>
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary>
public int WorldVehicleSoftLimit { get; set; } = 20;
/// <summary>
/// The game won't spawn more NPC traffic if the limit is exceeded. -1 for unlimited (not recommended).
/// </summary>
public int WorldPedSoftLimit { get; set; } = 30;
/// <summary>
/// Show the owner name of the entity you're aiming at
/// </summary>
public bool ShowEntityOwnerName { get; set; } = false;
/// <summary>
/// Show other player's nametag on your screen, only effective if server didn't disable nametag display
/// </summary>
public bool ShowPlayerNameTag { get; set; } = true;
/// <summary>
/// Show other player's blip on map, can be overridden by server resource
/// </summary>
public bool ShowPlayerBlip { get; set; } = true;
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Drawing;
using GTA;
using RageCoop.Core;
namespace RageCoop.Client
{
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class DevTool : Script
{
public static Vehicle ToMark;
public static Script Instance;
public DevTool()
{
Instance = this;
Pause();
}
protected override void OnTick()
{
base.OnTick();
foreach (var p in World.GetAllPeds()) DrawWeaponBone(p);
if (ToMark == null) return;
if (WeaponUtil.VehicleWeapons.TryGetValue((uint)(int)ToMark.Model, out var info))
foreach (var ws in info.Weapons)
foreach (var w in ws.Value.Bones)
DrawBone(w.BoneName, ws.Value.Name + ":" + ws.Key.ToHex());
var P = Game.Player.Character;
var b = ToMark.GetMuzzleBone(P.VehicleWeapon);
if (b != null) World.DrawLine(b.Position, b.Position + b.ForwardVector * 5, Color.Brown);
}
public static void DrawWeaponBone(Ped p)
{
var wb = p.Weapons?.CurrentWeaponObject?.Bones["gun_muzzle"];
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.RightVector, Color.Blue);
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.ForwardVector, Color.Red);
if (wb?.IsValid == true) World.DrawLine(wb.Position, wb.Position + wb.UpVector, Color.Green);
}
private void FindAndDraw()
{
DrawBone("weapon_1a");
DrawBone("weapon_1b");
DrawBone("weapon_1c");
DrawBone("weapon_1d");
DrawBone("weapon_2a");
DrawBone("weapon_2b");
DrawBone("weapon_2c");
DrawBone("weapon_2d");
DrawBone("weapon_3a");
DrawBone("weapon_3b");
DrawBone("weapon_3c");
DrawBone("weapon_3d");
DrawBone("weapon_4a");
DrawBone("weapon_4b");
DrawBone("weapon_4c");
DrawBone("weapon_4d");
DrawBone("weapon_1e");
DrawBone("weapon_1f");
DrawBone("weapon_1g");
DrawBone("weapon_1h");
DrawBone("weapon_2e");
DrawBone("weapon_2f");
DrawBone("weapon_2g");
DrawBone("weapon_2h");
DrawBone("weapon_3e");
DrawBone("weapon_3f");
DrawBone("weapon_3g");
DrawBone("weapon_3h");
DrawBone("weapon_4e");
DrawBone("weapon_4f");
DrawBone("weapon_4g");
DrawBone("weapon_4h");
}
private void DrawBone(string name, string text = null)
{
text = text ?? name;
var b = ToMark.Bones[name];
if (b.IsValid)
{
var start = b.Position;
var end = b.Position + b.ForwardVector * 5;
World.DrawLine(start, end, Color.AliceBlue);
Util.DrawTextFromCoord(end, text, 0.35f);
}
}
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Drawing;
using DXHook.Hook.Common;
using GTA;
using RageCoop.Client.CefHost;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.GUI
{
public class CefClient
{
public readonly int Id;
internal CefAdapter Adapter;
internal CefController Controller;
internal ImageElement MainFrame;
internal CefClient(int id, Size size)
{
Id = id;
Controller = CefController.Create(id, size, out Adapter, BufferMode.Full);
MainFrame = new ImageElement(size.Width, size.Height, 4, Adapter.PtrBuffer);
Adapter.OnPaint += (len, dirty) =>
{
try
{
// Image is using same shared buffer, so just need to make it re-copied to GPU
MainFrame.Invalidate();
}
catch (Exception ex)
{
API.Logger.Error(ex);
}
};
}
internal void Destroy()
{
Controller.Dispose();
Adapter.Dispose();
MainFrame.Dispose();
}
public Point GetLocationInFrame(Point screenPos)
{
screenPos.X -= MainFrame.Location.X;
screenPos.Y -= MainFrame.Location.Y;
return screenPos;
}
public Point GetLocationInCef(Point screenPos)
{
var p = GetLocationInFrame(screenPos);
p.X = (int)(p.X / Scale);
p.Y = (int)(p.Y / Scale);
return p;
}
internal bool PointInArea(Point screen)
{
screen = GetLocationInFrame(screen);
return screen.X.IsBetween(0, Width) && screen.Y.IsBetween(0, Height);
}
internal void Tick()
{
var mousePos = Util.CursorPosition;
if (!PointInArea(mousePos)) return;
var pos = GetLocationInCef(mousePos);
if (Game.IsControlJustPressed(Control.CursorAccept))
Controller.SendMouseClick(pos.X, pos.Y, 0, MouseButton.Left, false, 1);
else if (Game.IsControlJustReleased(Control.CursorAccept))
Controller.SendMouseClick(pos.X, pos.Y, 0, MouseButton.Left, true, 1);
}
#region FRAME-APPERANCE
public float Scale
{
get => MainFrame.Scale;
set => MainFrame.Scale = value;
}
public Color Tint
{
get => MainFrame.Tint;
set => MainFrame.Tint = value;
}
public byte Opacity
{
get => MainFrame.Opacity;
set => MainFrame.Opacity = value;
}
public Point Location
{
get => MainFrame.Location;
set => MainFrame.Location = value;
}
public int Width => MainFrame.Width;
public int Height => MainFrame.Height;
public bool Hidden
{
get => MainFrame.Hidden;
set => MainFrame.Hidden = value;
}
#endregion
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using GTA;
using GTA.Native;
using RageCoop.Client.CefHost;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using static RageCoop.Client.Shared;
namespace RageCoop.Client.GUI
{
internal static class CefManager
{
private static readonly ConcurrentDictionary<int, CefClient> Clients =
new ConcurrentDictionary<int, CefClient>();
private static readonly Overlay CefOverlay = new Overlay
{
Elements = new List<IOverlayElement>(),
Hidden = false
};
static CefManager()
{
Main.CefRunning = true;
HookManager.Initialize();
CefController.Initialize(CefSubProcessPath);
CefController.OnCefMessage = m => API.Logger.Debug(m);
HookManager.AddOverLay(CefOverlay);
}
public static CefClient ActiveClient { get; set; }
public static void Tick()
{
if (ActiveClient != null)
{
Game.DisableAllControlsThisFrame();
Function.Call(Hash.SET_MOUSE_CURSOR_THIS_FRAME);
ActiveClient.Tick();
}
}
public static void KeyDown(Keys key)
{
}
public static void KeyUp(Keys key)
{
}
public static bool DestroyClient(CefClient client)
{
lock (Clients)
{
if (Clients.TryRemove(client.Id, out var c) && client == c)
{
client.Destroy();
CefOverlay.Elements.Remove(client.MainFrame);
if (ActiveClient == client) ActiveClient = null;
return true;
}
}
return false;
}
public static CefClient CreateClient(Size size)
{
lock (Clients)
{
var id = 0;
while (id == 0 || Clients.ContainsKey(id)) id = CoreUtils.RandInt(0, int.MaxValue);
var client = new CefClient(id, size);
if (Clients.TryAdd(id, client))
{
CefOverlay.Elements.Add(client.MainFrame);
return client;
}
API.Logger.Warning("Failed to create CefClient");
client.Destroy();
return null;
}
}
public static void CleanUp()
{
Main.CefRunning = false;
ActiveClient = null;
try
{
lock (Clients)
{
foreach (var c in Clients.Values) DestroyClient(c);
Clients.Clear();
}
CefController.ShutDown();
}
catch (Exception ex)
{
API.Logger.Error(ex);
}
}
}
}

View File

@ -0,0 +1,62 @@
using System.Collections.Generic;
using DXHook.Hook;
using DXHook.Hook.Common;
using DXHook.Interface;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.GUI
{
internal static class HookManager
{
public static readonly CaptureInterface Interface = new CaptureInterface();
private static DXHookD3D11 _hook;
public static Overlay DefaultOverlay = new Overlay();
public static bool Hooked => _hook != null;
public static void GetOverlays()
{
new List<IOverlay>(_hook.Overlays);
}
public static void AddOverLay(IOverlay ol)
{
_hook.Overlays.Add(ol);
_hook.IsOverlayUpdatePending = true;
}
public static void RemoveOverlay(IOverlay ol)
{
_hook.Overlays.Remove(ol);
_hook.IsOverlayUpdatePending = true;
}
public static void Initialize()
{
if (_hook != null) return;
_hook = new DXHookD3D11(Interface);
_hook.Config = new CaptureConfig
{
Direct3DVersion = Direct3DVersion.Direct3D11,
ShowOverlay = true
};
_hook.Overlays = new List<IOverlay>();
_hook.Hook();
_hook.OnPresent += Present;
DefaultOverlay.Elements = new List<IOverlayElement>();
AddOverLay(DefaultOverlay);
Interface.RemoteMessage += m => { API.Logger.Debug("DXHook: " + m.Message); };
API.Logger.Debug("Hooked DX3D11");
}
private static void Present()
{
}
public static void CleanUp()
{
_hook?.Cleanup();
_hook?.Dispose();
_hook = null;
}
}
}

420
Client/Scripts/Main.cs Normal file
View File

@ -0,0 +1,420 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.UI;
using LemonUI.Elements;
using LemonUI.Menus;
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using Control = GTA.Control;
namespace RageCoop.Client
{
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V", NoScriptThread = true)]
internal class Main : Script
{
internal static Version ModVersion = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID = 0;
internal static RelationshipGroup SyncedPedsGroup;
internal static ClientSettings Settings = null;
internal static Chat MainChat = null;
internal static Stopwatch Counter = new();
internal static Logger Log = null;
internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition;
internal static Resources MainRes = null;
public static Ped P;
public static Vehicle V;
public static Vehicle LastV;
public static float FPS;
private static bool _lastDead;
public static bool CefRunning;
public static bool IsUnloading { get; private set; }
public static Script Instance { get; private set; }
/// <summary>
/// Don't use it!
/// </summary>
public Main()
{
Instance = this;
Directory.CreateDirectory(DataPath);
try
{
Settings = Util.ReadSettings();
}
catch
{
Notification.Show("Malformed configuration, overwriting with default values...");
Settings = new();
Util.SaveSettings();
}
Log = new Logger()
{
FlushImmediately = true,
Writers = null,
#if DEBUG
LogLevel = 0,
#else
LogLevel = Settings.LogLevel,
#endif
};
Log.OnFlush += (line, formatted) =>
{
SHVDN.Logger.Write($"[RageCoop] {line.Message}", (uint)line.LogLevel);
};
// Run static constructor to register all function pointers and remoting entries
RuntimeHelpers.RunClassConstructor(typeof(API).TypeHandle);
}
protected override void OnAborted(AbortedEventArgs e)
{
base.OnAborted(e);
try
{
IsUnloading = e.IsUnloading;
CleanUp("Abort");
WorldThread.DoQueuedActions();
if (IsUnloading)
{
ThreadManager.OnUnload();
Log.Dispose();
}
}
catch (Exception ex)
{
Log.Error(ex);
}
}
protected override void OnStart()
{
base.OnStart();
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{
throw new NotSupportedException("Please update your GTA5 to v1.0.1290 or newer!");
}
MainRes = new();
Log.Info(
$"Main script initialized");
BaseScript.OnStart();
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral,
true);
MainChat = new Chat();
Util.NativeMemory();
Counter.Restart();
}
protected override void OnTick()
{
base.OnTick();
var lastVehicleHandle = Call<int>(GET_PLAYERS_LAST_VEHICLE);
var playerHandle = Call<int>(PLAYER_PED_ID);
if (LastV?.Handle != lastVehicleHandle)
LastV = Entity.FromHandle(lastVehicleHandle) as Vehicle;
if (P?.Handle != playerHandle)
P = (Ped)Entity.FromHandle(playerHandle);
var playerVehHandle = Call<int>(GET_VEHICLE_PED_IS_IN, P.Handle, false);
if (V?.Handle != playerVehHandle)
V = Entity.FromHandle(playerVehHandle) as Vehicle;
PlayerPosition = P.ReadPosition();
FPS = Game.FPS;
#if CEF
if (CefRunning)
{
CefManager.Tick();
}
#endif
if (!Networking.IsOnServer)
{
return;
}
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
Log.Error(ex);
}
if (Game.TimeScale != 1.0f)
{
Game.TimeScale = 1;
}
if (Networking.ShowNetworkInfo)
{
new ScaledText(new PointF(200, 0),
$"L: {Networking.Latency * 1000:N0}ms", 0.5f)
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(200, 30),
$"R: {NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/s", 0.5f)
{ Alignment = Alignment.Center }.Draw();
new ScaledText(new PointF(200, 60),
$"S: {NetUtility.ToHumanReadable(Statistics.BytesUpPerSecond)}/s", 0.5f)
{ Alignment = Alignment.Center }.Draw();
}
MainChat.Tick();
PlayerList.Tick();
if (!API.Config.EnableAutoRespawn)
{
Call(PAUSE_DEATH_ARREST_RESTART, true);
Call(IGNORE_NEXT_RESTART, true);
Call(FORCE_GAME_STATE_PLAYING);
Call(TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
if (P.IsDead)
{
Call(SET_FADE_OUT_AFTER_DEATH, false);
if (P.Health != 1)
{
P.Health = 1;
Game.Player.WantedLevel = 0;
Log.Debug("Player died.");
API.Events.InvokePlayerDied();
}
Screen.StopEffects();
}
else
{
Call(DISPLAY_HUD, true);
}
}
else if (P.IsDead && !_lastDead)
{
API.Events.InvokePlayerDied();
}
_lastDead = P.IsDead;
Ticked++;
}
protected override void OnKeyUp(GTA.KeyEventArgs e)
{
base.OnKeyUp(e);
#if CEF
if (CefRunning)
{
CefManager.KeyUp(e.KeyCode);
}
#endif
}
protected unsafe override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (MainChat.Focused)
{
MainChat.OnKeyDown(e.KeyCode);
return;
}
#if CEF
if (CefRunning)
{
CefManager.KeyDown(e.KeyCode);
}
#endif
if (Networking.IsOnServer)
{
if (Voice.WasInitialized())
{
if (Game.IsControlPressed(Control.PushToTalk))
{
Voice.StartRecording();
return;
}
if (Voice.IsRecording())
{
Voice.StopRecording();
return;
}
}
if (Game.IsControlPressed(Control.FrontendPause))
{
Call(ACTIVATE_FRONTEND_MENU,
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
if (Game.IsControlPressed(Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
{
Call(ACTIVATE_FRONTEND_MENU,
SHVDN.NativeMemory.GetHashKey("FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
}
if (e.KeyCode == Settings.MenuKey)
{
if (CoopMenu.MenuPool.AreAnyVisible)
{
CoopMenu.MenuPool.ForEach<NativeMenu>(x =>
{
if (x.Visible)
{
CoopMenu.LastMenu = x;
x.Visible = false;
}
});
}
else
{
CoopMenu.LastMenu.Visible = true;
}
}
else if (Game.IsControlJustPressed(Control.MpTextChatAll))
{
if (Networking.IsOnServer)
{
MainChat.Focused = true;
}
}
else if (MainChat.Focused)
{
return;
}
else if (Game.IsControlJustPressed(Control.MultiplayerInfo))
{
if (Networking.IsOnServer)
{
ulong currentTimestamp = Util.GetTickCount64();
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000
? (currentTimestamp - 6000)
: currentTimestamp;
}
}
else if (e.KeyCode == Settings.PassengerKey)
{
if (P == null || P.IsInVehicle())
{
return;
}
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
{
P.Task.ClearAll();
}
else
{
var V = World.GetClosestVehicle(P.ReadPosition(), 15);
if (V != null)
{
var seat = P.GetNearestSeat(V);
var p = V.GetPedOnSeat(seat);
if (p != null && !p.IsDead)
{
for (int i = -1; i < V.PassengerCapacity; i++)
{
seat = (VehicleSeat)i;
p = V.GetPedOnSeat(seat);
if (p == null || p.IsDead)
{
break;
}
}
}
P.Task.EnterVehicle(V, seat, -1, 5, EnterVehicleFlags.None);
}
}
}
}
internal static void Connected()
{
Memory.ApplyPatches();
if (Settings.Voice && !Voice.WasInitialized())
{
Voice.Init();
}
API.QueueAction(() =>
{
WorldThread.Traffic(!Settings.DisableTraffic);
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
CoopMenu.ConnectedMenuSetting();
MainChat.Init();
Notification.Show("~g~Connected!");
});
Log.Info(">> Connected <<");
}
private static readonly object _cleanupLock = new();
public static void CleanUp(string reason)
{
lock (_cleanupLock)
{
if (reason != "Abort")
{
Log.Info($">> Disconnected << reason: {reason}");
Notification.Show("~r~Disconnected: " + reason);
}
if (MainChat.Focused)
{
MainChat.Focused = false;
}
PlayerList.Cleanup();
MainChat.Clear();
EntityPool.Cleanup();
WorldThread.Traffic(true);
Call(SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting();
LocalPlayerID = default;
MainRes.Unload();
Memory.RestorePatches();
#if CEF
if (CefRunning)
{
CefManager.CleanUp();
}
#endif
DownloadManager.Cleanup();
Voice.ClearAll();
Networking.Peer?.Dispose();
Networking.Peer = null;
}
}
}
}

View File

@ -1,61 +1,53 @@
using GTA;
using System;
using System.Drawing;
using GTA;
using GTA.Native;
using GTA.UI;
using LemonUI;
using LemonUI.Elements;
using LemonUI.Menus;
using LemonUI.Scaleform;
namespace RageCoop.Client.Menus
{
/// <summary>
/// Don't use it!
/// Don't use it!
/// </summary>
internal static class CoopMenu
{
public static ObjectPool MenuPool = new ObjectPool();
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "MAIN")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static PopUp PopUp=new PopUp()
public static PopUp PopUp = new PopUp
{
Title="",
Prompt="",
Title = "",
Prompt = "",
Subtitle = "",
Error="",
Error = "",
ShowBackground = true,
Visible=false,
Visible = false
};
public static NativeMenu LastMenu { get; set; } = Menu;
#region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*',Main.Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
private static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.CurrentVersion.Replace("_", ".")) { LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
#endregion
/// <summary>
/// Don't use it!
/// Don't use it!
/// </summary>
static CoopMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated;
_passwordItem.Activated+=_passwordActivated;
_passwordItem.Activated += _passwordActivated;
ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
_serverConnectItem.Activated += (sender, item) =>
{
Networking.ToggleConnection(Settings.LastServerAddress);
};
Menu.AddSubMenu(ServersMenu.Menu);
@ -69,72 +61,89 @@ namespace RageCoop.Client.Menus
Menu.AddSubMenu(DevToolMenu.Menu);
Menu.AddSubMenu(DebugMenu.Menu);
MenuPool.Add(Menu);
MenuPool.Add(SettingsMenu.Menu);
MenuPool.Add(DevToolMenu.Menu);
MenuPool.Add(DebugMenu.Menu);
MenuPool.Add(DebugMenu.DiagnosticMenu);
MenuPool.Add(DebugMenu.TuneMenu);
MenuPool.Add(ServersMenu.Menu);
MenuPool.Add(PopUp);
Menu.Add(_aboutItem);
}
public static NativeMenu LastMenu { get; set; } = Menu;
public static bool ShowPopUp(string prompt, string title,string subtitle,string error,bool showbackground)
public static bool ShowPopUp(string prompt, string title, string subtitle, string error, bool showbackground)
{
PopUp.Prompt=prompt;
PopUp.Title=title;
PopUp.Subtitle=subtitle;
PopUp.Error=error;
PopUp.ShowBackground=showbackground;
PopUp.Visible=true;
PopUp.Prompt = prompt;
PopUp.Title = title;
PopUp.Subtitle = subtitle;
PopUp.Error = error;
PopUp.ShowBackground = showbackground;
PopUp.Visible = true;
Script.Yield();
while (true)
{
Game.DisableAllControlsThisFrame();
MenuPool.Process();
var scaleform = new Scaleform("instructional_buttons");
scaleform.CallFunction("CLEAR_ALL");
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
scaleform.CallFunction("CREATE_CONTAINER");
scaleform.CallFunction("SET_DATA_SLOT", 0,
Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
scaleform.CallFunction("SET_DATA_SLOT", 1,
Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
scaleform.Render2D();
if (Game.IsControlJustPressed(Control.FrontendAccept))
{
PopUp.Visible=false;
PopUp.Visible = false;
return true;
}
else if (Game.IsControlJustPressed(Control.FrontendCancel))
if (Game.IsControlJustPressed(Control.FrontendCancel))
{
PopUp.Visible = false;
return false;
}
Script.Yield();
Game.DisableAllControlsThisFrame();
}
}
public static void UsernameActivated(object a, System.EventArgs b)
public static void UsernameActivated(object a, EventArgs b)
{
string newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
var newUsername = Game.GetUserInput(WindowTitle.EnterMessage20, _usernameItem.AltTitle, 20);
if (!string.IsNullOrWhiteSpace(newUsername))
{
Main.Settings.Username = newUsername;
Settings.Username = newUsername;
Util.SaveSettings();
_usernameItem.AltTitle = newUsername;
}
}
private static void _passwordActivated(object sender, System.EventArgs e)
private static void _passwordActivated(object sender, EventArgs e)
{
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
if (!string.IsNullOrWhiteSpace(newPass))
{
Main.Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
var newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
public static void ServerIpActivated(object a, System.EventArgs b)
public static void ServerIpActivated(object a, EventArgs b)
{
string newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
var newServerIp = Game.GetUserInput(WindowTitle.EnterMessage60, ServerIpItem.AltTitle, 60);
if (!string.IsNullOrWhiteSpace(newServerIp) && newServerIp.Contains(":"))
{
Main.Settings.LastServerAddress = newServerIp;
Settings.LastServerAddress = newServerIp;
Util.SaveSettings();
ServerIpItem.AltTitle = newServerIp;
@ -162,5 +171,26 @@ namespace RageCoop.Client.Menus
_serverConnectItem.Enabled = true;
_serverConnectItem.Title = "Connect";
}
#region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username")
{ AltTitle = Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password")
{ AltTitle = new string('*', Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP")
{ AltTitle = Settings.LastServerAddress };
internal static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.ModVersion)
{ LeftBadge = new ScaledTexture("commonmenu", "shop_new_star") };
#endregion
}
}
}

View File

@ -0,0 +1,113 @@
using System;
using System.Drawing;
using System.Linq;
using System.Reflection;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class DebugMenu
{
public static NativeMenu Menu = new("RAGECOOP", "Debug", "Debug settings")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static NativeMenu DiagnosticMenu = new("RAGECOOP", "Diagnostic", "Performence and Diagnostic")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static NativeMenu TuneMenu = new("RAGECOOP", "Change tunable values")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
public static NativeItem SimulatedLatencyItem =
new("Simulated network latency", "Simulated network latency in ms (one way)", "0");
public static NativeCheckboxItem ShowOwnerItem = new("Show entity owner",
"Show the owner name of the entity you're aiming at", false);
private static readonly NativeCheckboxItem ShowNetworkInfoItem =
new("Show Network Info", Networking.ShowNetworkInfo);
static DebugMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
TuneMenu.Opening += (s, e) =>
{
TuneMenu.Clear();
foreach (var t in typeof(Main).Assembly.GetTypes())
{
foreach (var field in t.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.NonPublic))
{
var attri = field.GetCustomAttribute<DebugTunableAttribute>();
if (attri == null)
continue;
var item = new NativeItem(field.Name);
item.AltTitle = field.GetValue(null).ToString();
item.Activated += (s, e) =>
{
try
{
field.SetValue(null, Convert.ChangeType(Game.GetUserInput(), field.FieldType));
item.AltTitle = field.GetValue(null).ToString();
}
catch (Exception ex)
{
Log.Error(ex);
}
};
TuneMenu.Add(item);
}
}
};
DiagnosticMenu.Opening += (sender, e) =>
{
DiagnosticMenu.Clear();
DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug()));
// foreach (var pair in Debug.TimeStamps)
// DiagnosticMenu.Add(
// new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
};
ShowNetworkInfoItem.CheckboxChanged += (s, e) =>
{
Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked;
};
ShowOwnerItem.CheckboxChanged += (s, e) =>
{
Settings.ShowEntityOwnerName = ShowOwnerItem.Checked;
Util.SaveSettings();
};
#if DEBUG
SimulatedLatencyItem.Activated += (s, e) =>
{
try
{
SimulatedLatencyItem.AltTitle =
((Networking.SimulatedLatency =
int.Parse(Game.GetUserInput(SimulatedLatencyItem.AltTitle)) * 0.002f) * 500).ToString();
}
catch (Exception ex)
{
Log.Error(ex);
}
};
Menu.Add(SimulatedLatencyItem);
#endif
Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem);
Menu.AddSubMenu(DiagnosticMenu);
Menu.AddSubMenu(TuneMenu);
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Drawing;
using System.IO;
using GTA;
using GTA.Native;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class DevToolMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "DevTool", "Internal testing tools")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
private static readonly NativeCheckboxItem enableItem = new NativeCheckboxItem("Show weapon bones");
public static readonly NativeItem DumpFixItem = new NativeItem("Dump weapon fixes");
public static readonly NativeItem GetAnimItem = new NativeItem("Get current animation");
public static readonly NativeItem DumpVwHashItem = new NativeItem("Dump VehicleWeaponHash.cs");
static DevToolMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
enableItem.Activated += ShowBones;
enableItem.Checked = false;
DumpFixItem.Activated += (s, e) => WeaponUtil.DumpWeaponFix(WeaponFixDataPath);
GetAnimItem.Activated += (s, e) =>
{
if (File.Exists(AnimationsDataPath))
{
var anims = JsonDeserialize<AnimDic[]>(File.ReadAllText(AnimationsDataPath));
foreach (var anim in anims)
foreach (var a in anim.Animations)
if (Call<bool>(IS_ENTITY_PLAYING_ANIM, P, anim.DictionaryName, a, 3))
{
Console.PrintInfo(anim.DictionaryName + " : " + a);
Notification.Show(anim.DictionaryName + " : " + a);
}
}
else
{
Notification.Show($"~r~{AnimationsDataPath} not found");
}
};
Menu.Add(enableItem);
Menu.Add(DumpVwHashItem);
Menu.Add(DumpFixItem);
Menu.Add(GetAnimItem);
}
private static void ShowBones(object sender, EventArgs e)
{
if (enableItem.Checked)
{
DevTool.ToMark = Game.Player.Character.CurrentVehicle;
DevTool.Instance.Resume();
}
else
{
DevTool.Instance.Pause();
DevTool.ToMark = null;
}
}
}
}

View File

@ -1,71 +1,49 @@
using System;
using System.Net;
using System.Drawing;
using System.Collections.Generic;
using Newtonsoft.Json;
using LemonUI.Menus;
using System.ComponentModel;
using System.Drawing;
using System.Net;
using System.Net.Http;
using System.Threading;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client.Menus
{
internal class ServerListClass
{
[JsonProperty("address")]
public string Address { get; set; }
[JsonProperty("port")]
public string Port { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("players")]
public int Players { get; set; }
[JsonProperty("maxPlayers")]
public int MaxPlayers { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
}
/// <summary>
/// Don't use it!
/// Don't use it!
/// </summary>
internal static class ServersMenu
{
private static Thread GetServersThread;
internal static NativeMenu Menu = new NativeMenu("RAGECOOP", "Servers", "Go to the server list")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
internal static NativeItem ResultItem = null;
/// <summary>
/// Don't use it!
/// Don't use it!
/// </summary>
static ServersMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) =>
Menu.Opening += (object sender, CancelEventArgs e) =>
{
CleanUpList();
Menu.Add(ResultItem = new NativeItem("Loading..."));
// Prevent freezing
GetServersThread=new Thread(()=> GetAllServers());
GetServersThread.Start();
};
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
{
CleanUpList();
GetServersThread = ThreadManager.CreateThread(() => GetAllServers(),"GetServers");
};
Menu.Closing += (object sender, CancelEventArgs e) => { CleanUpList(); };
}
private static void CleanUpList()
@ -76,52 +54,74 @@ namespace RageCoop.Client.Menus
private static void GetAllServers()
{
List<ServerListClass> serverList = null;
var realUrl = Main.Settings.MasterServer=="[AUTO]" ? DownloadString("https://ragecoop.online/stuff/masterserver") : Main.Settings.MasterServer;
serverList = JsonConvert.DeserializeObject<List<ServerListClass>>(DownloadString(realUrl));
List<ServerInfo> serverList = null;
var realUrl = Settings.MasterServer;
serverList = null;
try { serverList = JsonDeserialize<List<ServerInfo>>(DownloadString(realUrl)); }
catch (Exception ex) { Log.Error(ex); }
// Need to be processed in main thread
Main.QueueAction(() =>
API.QueueAction(() =>
{
if (serverList == null)
{
ResultItem.Title = "Something went wrong!";
return;
}
if (serverList.Count == 0)
{
ResultItem.Title = "No server was found!";
return;
}
CleanUpList();
foreach (ServerListClass server in serverList)
foreach (ServerInfo server in serverList)
{
string address = $"{server.Address}:{server.Port}";
NativeItem tmpItem = new NativeItem($"[{server.Country}] {server.Name}", $"~b~{address}~s~~n~~g~Version {server.Version}.x~s~") { AltTitle = $"[{server.Players}/{server.MaxPlayers}]" };
string address = $"{server.address}:{server.port}";
NativeItem tmpItem =
new NativeItem($"[{server.country}] {server.name}",
$"~b~{address}~s~~n~~g~Version {server.version}.x~s~")
{ AltTitle = $"[{server.players}/{server.maxPlayers}]" };
tmpItem.Activated += (object sender, EventArgs e) =>
{
try
{
Menu.Visible = false;
if (server.useZT)
{
address = $"{server.ztAddress}:{server.port}";
Notification.Show($"~y~Joining ZeroTier network... {server.ztID}");
if (ZeroTierHelper.Join(server.ztID) == null)
{
throw new Exception("Failed to obtain ZeroTier network IP");
}
}
Networking.ToggleConnection(address);
Networking.ToggleConnection(address, null, null, PublicKey.FromServerInfo(server));
#if !NON_INTERACTIVE
CoopMenu.ServerIpItem.AltTitle = address;
CoopMenu.Menu.Visible = true;
#endif
Main.Settings.LastServerAddress = address;
Settings.LastServerAddress = address;
Util.SaveSettings();
}
catch (Exception ex)
{
GTA.UI.Notification.Show($"~r~{ex.Message}");
Notification.Show($"~r~{ex.Message}");
if (server.useZT)
{
Notification.Show(
$"Make sure ZeroTier is correctly installed, download it from https://www.zerotier.com/");
}
}
};
Menu.Add(tmpItem);
}
});
}
private static string DownloadString(string url)
{
try
@ -131,12 +131,12 @@ namespace RageCoop.Client.Menus
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
WebClient client = new WebClient();
return client.DownloadString(url);
var client = new HttpClient();
return client.GetStringAsync(url).GetAwaiter().GetResult();
}
catch (Exception ex)
{
Main.QueueAction(() =>
API.QueueAction(() =>
{
ResultItem.Title = "Download failed!";
ResultItem.Description = ex.Message;

View File

@ -0,0 +1,168 @@
using System;
using System.Drawing;
using GTA;
using GTA.UI;
using LemonUI.Menus;
using RageCoop.Client.Scripting;
namespace RageCoop.Client.Menus
{
internal static class SettingsMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
{
UseMouse = false,
Alignment = Settings.FlipMenu ? Alignment.Right : Alignment.Left
};
private static readonly NativeCheckboxItem _disableTrafficItem =
new("Disable Traffic (NPCs/Vehicles)", "Local traffic only",
Settings.DisableTraffic);
private static readonly NativeCheckboxItem _flipMenuItem =
new("Flip menu", Settings.FlipMenu);
private static readonly NativeCheckboxItem _disablePauseAlt = new("Disable Alternate Pause",
"Don't freeze game time when Esc pressed", Settings.DisableAlternatePause);
private static readonly NativeCheckboxItem _disableVoice = new("Enable voice",
"Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends",
Settings.Voice);
private static readonly NativeCheckboxItem _showBlip = new("Show player blip",
"Show other player's blip on map, can be overridden by server resource ",
Settings.ShowPlayerBlip);
private static readonly NativeCheckboxItem _showNametag = new("Show player nametag",
"Show other player's nametag on your screen, only effective if server didn't disable nametag display",
Settings.ShowPlayerNameTag);
private static readonly NativeItem _menuKey =
new("Menu Key", "The key to open menu", Settings.MenuKey.ToString());
private static readonly NativeItem _passengerKey = new("Passenger Key",
"The key to enter a vehicle as passenger", Settings.PassengerKey.ToString());
private static readonly NativeItem _vehicleSoftLimit = new("Vehicle limit (soft)",
"The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).",
Settings.WorldVehicleSoftLimit.ToString());
static SettingsMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
_disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged;
_disablePauseAlt.CheckboxChanged += DisablePauseAltCheckboxChanged;
_disableVoice.CheckboxChanged += DisableVoiceCheckboxChanged;
_flipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged;
_menuKey.Activated += ChaneMenuKey;
_passengerKey.Activated += ChangePassengerKey;
_vehicleSoftLimit.Activated += VehicleSoftLimitActivated;
_showBlip.Activated += (s, e) =>
{
Settings.ShowPlayerBlip = _showBlip.Checked;
Util.SaveSettings();
};
_showNametag.Activated += (s, e) =>
{
API.Config.ShowPlayerNameTag = _showNametag.Checked;
};
Menu.Add(_disableTrafficItem);
Menu.Add(_disablePauseAlt);
Menu.Add(_flipMenuItem);
Menu.Add(_disableVoice);
Menu.Add(_menuKey);
Menu.Add(_passengerKey);
Menu.Add(_vehicleSoftLimit);
Menu.Add(_showBlip);
Menu.Add(_showNametag);
}
private static void DisableVoiceCheckboxChanged(object sender, EventArgs e)
{
if (_disableVoice.Checked)
{
if (Networking.IsOnServer && !Voice.WasInitialized()) Voice.Init();
}
else
{
Voice.ClearAll();
}
Settings.Voice = _disableVoice.Checked;
Util.SaveSettings();
}
private static void DisablePauseAltCheckboxChanged(object sender, EventArgs e)
{
Settings.DisableAlternatePause = _disablePauseAlt.Checked;
Util.SaveSettings();
}
private static void VehicleSoftLimitActivated(object sender, EventArgs e)
{
try
{
Settings.WorldVehicleSoftLimit = int.Parse(
Game.GetUserInput(WindowTitle.EnterMessage20,
Settings.WorldVehicleSoftLimit.ToString(), 20));
_menuKey.AltTitle = Settings.WorldVehicleSoftLimit.ToString();
Util.SaveSettings();
}
catch
{
}
}
private static void ChaneMenuKey(object sender, EventArgs e)
{
try
{
Settings.MenuKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Settings.MenuKey.ToString(), 20));
_menuKey.AltTitle = Settings.MenuKey.ToString();
Util.SaveSettings();
}
catch
{
}
}
private static void ChangePassengerKey(object sender, EventArgs e)
{
try
{
Settings.PassengerKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Settings.PassengerKey.ToString(), 20));
_passengerKey.AltTitle = Settings.PassengerKey.ToString();
Util.SaveSettings();
}
catch
{
}
}
public static void DisableTrafficCheckboxChanged(object a, EventArgs b)
{
WorldThread.Traffic(!_disableTrafficItem.Checked);
Settings.DisableTraffic = _disableTrafficItem.Checked;
Util.SaveSettings();
}
public static void FlipMenuCheckboxChanged(object a, EventArgs b)
{
CoopMenu.Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
Menu.Alignment = _flipMenuItem.Checked ? Alignment.Right : Alignment.Left;
Settings.FlipMenu = _flipMenuItem.Checked;
Util.SaveSettings();
}
}
}

View File

@ -1,8 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using RageCoop.Core;
using GTA;
using GTA.Native;
@ -12,18 +10,21 @@ namespace RageCoop.Client
{
private readonly Scaleform MainScaleForm;
public Chat()
{
MainScaleForm = new Scaleform("multiplayer_chat");
}
public string CurrentInput { get; set; }
private bool CurrentFocused { get; set; }
public bool Focused
{
get { return CurrentFocused; }
get => CurrentFocused;
set
{
if (value && Hidden)
{
Hidden = false;
}
if (value && Hidden) Hidden = false;
MainScaleForm.CallFunction("SET_FOCUS", value ? 2 : 1, 2, "ALL");
@ -34,17 +35,15 @@ namespace RageCoop.Client
private ulong LastMessageTime { get; set; }
private bool CurrentHidden { get; set; }
private bool Hidden
{
get { return CurrentHidden; }
get => CurrentHidden;
set
{
if (value)
{
if (!CurrentHidden)
{
MainScaleForm.CallFunction("hide");
}
if (!CurrentHidden) MainScaleForm.CallFunction("hide");
}
else if (CurrentHidden)
{
@ -55,11 +54,6 @@ namespace RageCoop.Client
}
}
public Chat()
{
MainScaleForm = new Scaleform("multiplayer_chat");
}
public void Init()
{
MainScaleForm.CallFunction("SET_FOCUS", 2, 2, "ALL");
@ -73,22 +67,13 @@ namespace RageCoop.Client
public void Tick()
{
if ((Util.GetTickCount64() - LastMessageTime) > 15000 && !Focused && !Hidden)
{
Hidden = true;
}
if (Util.GetTickCount64() - LastMessageTime > 15000 && !Focused && !Hidden) Hidden = true;
if (!Hidden)
{
MainScaleForm.Render2D();
}
if (!Hidden) MainScaleForm.Render2D();
if (!CurrentFocused)
{
return;
}
if (!CurrentFocused) return;
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, 0);
Call(DISABLE_ALL_CONTROL_ACTIONS, 0);
}
public void AddMessage(string sender, string msg)
@ -106,22 +91,14 @@ namespace RageCoop.Client
CurrentInput = "";
return;
}
if (key == Keys.PageUp)
{
MainScaleForm.CallFunction("PAGE_UP");
}
else if (key == Keys.PageDown)
{
MainScaleForm.CallFunction("PAGE_DOWN");
}
else if (key == Keys.PageDown) MainScaleForm.CallFunction("PAGE_DOWN");
string keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
var keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
if (keyChar.Length == 0)
{
return;
}
if (keyChar.Length == 0) return;
switch (keyChar[0])
{
@ -131,14 +108,12 @@ namespace RageCoop.Client
CurrentInput = CurrentInput.Remove(CurrentInput.Length - 1);
MainScaleForm.CallFunction("DELETE_TEXT");
}
return;
case (char)13:
MainScaleForm.CallFunction("ADD_TEXT", "ENTER");
if (!string.IsNullOrWhiteSpace(CurrentInput))
{
Networking.SendChatMessage(CurrentInput);
}
if (!string.IsNullOrWhiteSpace(CurrentInput)) Networking.SendChatMessage(CurrentInput);
Focused = false;
CurrentInput = "";
@ -152,19 +127,19 @@ namespace RageCoop.Client
[DllImport("user32.dll")]
public static extern int ToUnicodeEx(uint virtualKeyCode, uint scanCode, byte[] keyboardState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
[Out] [MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)]
StringBuilder receivingBuffer,
int bufferSize, uint flags, IntPtr kblayout);
[DllImport("user32.dll")]
static extern IntPtr GetKeyboardLayout(uint idThread);
public static string GetCharFromKey(Keys key, bool shift, bool altGr)
{
StringBuilder buf = new StringBuilder(256);
byte[] keyboardState = new byte[256];
var buf = new StringBuilder(256);
var keyboardState = new byte[256];
if (shift)
{
keyboardState[(int)Keys.ShiftKey] = 0xff;
}
if (shift) keyboardState[(int)Keys.ShiftKey] = 0xff;
if (altGr)
{
@ -172,8 +147,8 @@ namespace RageCoop.Client
keyboardState[(int)Keys.Menu] = 0xff;
}
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, InputLanguage.CurrentInputLanguage.Handle);
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, GetKeyboardLayout(0));
return buf.ToString();
}
}
}
}

View File

@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using static RageCoop.Client.Shared;
namespace RageCoop.Client
{
internal static class DownloadManager
{
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
private static readonly HashSet<string> _resources = new HashSet<string>();
static DownloadManager()
{
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, data =>
{
var fr = new Packets.FileTransferRequest();
fr.Deserialize(data);
if (fr.Name.EndsWith(".res")) _resources.Add(fr.Name);
return new Packets.FileTransferResponse
{
ID = fr.ID,
Response = AddFile(fr.ID, fr.Name, fr.FileLength)
? FileResponse.NeedToDownload
: FileResponse.AlreadyExists
};
});
Networking.RequestHandlers.Add(PacketType.FileTransferComplete, data =>
{
var packet = new Packets.FileTransferComplete();
packet.Deserialize(data);
Log.Debug($"Finalizing download:{packet.ID}");
Complete(packet.ID);
// Inform the server that the download is completed
return new Packets.FileTransferResponse
{
ID = packet.ID,
Response = FileResponse.Completed
};
});
Networking.RequestHandlers.Add(PacketType.AllResourcesSent, data =>
{
try
{
Directory.CreateDirectory(ResourceFolder);
MainRes.Load(ResourceFolder, _resources.ToArray());
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.Loaded };
}
catch (Exception ex)
{
Log.Error("Error occurred when loading server resource");
Log.Error(ex);
return new Packets.FileTransferResponse { ID = 0, Response = FileResponse.LoadFailed };
}
});
}
public static string ResourceFolder => Path.GetFullPath(Path.Combine(DataPath, "Resources",
API.ServerEndPoint.ToString().Replace(":", ".")));
public static event EventHandler<string> DownloadCompleted;
public static bool AddFile(int id, string name, long length)
{
var path = $"{ResourceFolder}\\{name}";
Log.Debug($"Downloading file to {path} , id:{id}");
if (!Directory.Exists(Directory.GetParent(path).FullName))
Directory.CreateDirectory(Directory.GetParent(path).FullName);
if (FileAlreadyExists(ResourceFolder, name, length))
{
Log.Debug($"File already exists! canceling download:{name}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
return false;
}
/*
if (!name.EndsWith(".zip"))
{
Log.Error($"File download blocked! [{name}]");
return false;
}
*/
lock (InProgressDownloads)
{
InProgressDownloads.Add(id, new DownloadFile
{
FileID = id,
FileName = name,
FileLength = length,
Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
});
}
return true;
}
/// <summary>
/// Check if the file already exists and if the size correct otherwise delete this file
/// </summary>
/// <param name="folder"></param>
/// <param name="name"></param>
/// <param name="length"></param>
/// <returns></returns>
private static bool FileAlreadyExists(string folder, string name, long length)
{
var filePath = $"{folder}\\{name}";
if (File.Exists(filePath))
{
if (new FileInfo(filePath).Length == length) return true;
// Delete the file because the length is wrong (maybe the file was updated)
File.Delete(filePath);
}
return false;
}
public static void Write(int id, byte[] chunk)
{
lock (InProgressDownloads)
{
if (InProgressDownloads.TryGetValue(id, out var file))
file.Stream.Write(chunk, 0, chunk.Length);
else
Log.Trace($"Received unhandled file chunk:{id}");
}
}
public static void Complete(int id)
{
if (InProgressDownloads.TryGetValue(id, out var f))
{
InProgressDownloads.Remove(id);
f.Dispose();
Log.Info($"Download finished:{f.FileName}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
}
else
{
Log.Error($"Download not found! {id}");
}
}
public static void Cleanup()
{
lock (InProgressDownloads)
{
foreach (var file in InProgressDownloads.Values) file.Dispose();
InProgressDownloads.Clear();
}
_resources.Clear();
}
}
internal class DownloadFile : IDisposable
{
public int FileID { get; set; }
public string FileName { get; set; } = string.Empty;
public long FileLength { get; set; }
public long FileWritten { get; set; } = 0;
public FileStream Stream { get; set; }
public void Dispose()
{
if (Stream != null)
{
Stream.Flush();
Stream.Close();
Stream.Dispose();
}
}
}
}

View File

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Timers;
using Lidgren.Network;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class HolePunch
{
static HolePunch()
{
// Periodically send hole punch message as needed
var timer = new Timer(1000);
timer.Elapsed += DoPunch;
timer.Enabled = true;
}
private static void DoPunch(object sender, ElapsedEventArgs e)
{
try
{
if (!Networking.IsOnServer) return;
foreach (var p in PlayerList.Players.Values.ToArray())
if (p.InternalEndPoint != null && p.ExternalEndPoint != null && (p.Connection == null ||
p.Connection.Status == NetConnectionStatus.Disconnected))
{
Log.Trace(
$"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.ID}");
var msg = Networking.Peer.CreateMessage();
new Packets.HolePunch
{
Puncher = LocalPlayerID,
Status = p.HolePunchStatus
}.Pack(msg);
Networking.Peer.SendUnconnectedMessage(msg,
new List<IPEndPoint> { p.InternalEndPoint, p.ExternalEndPoint });
}
}
catch (Exception ex)
{
Log.Error(ex);
}
}
public static void Add(Packets.HolePunchInit p)
{
if (PlayerList.Players.TryGetValue(p.TargetID, out var player))
{
Log.Debug($"{p.TargetID},{player.Username} added to HolePunch target");
player.InternalEndPoint = CoreUtils.StringToEndPoint(p.TargetInternal);
player.ExternalEndPoint = CoreUtils.StringToEndPoint(p.TargetExternal);
player.ConnectWhenPunched = p.Connect;
}
else
{
Log.Warning("No player with specified TargetID found for hole punching:" + p.TargetID);
}
}
public static void Punched(Packets.HolePunch p, IPEndPoint from)
{
Log.Debug($"HolePunch message received from:{from}, status:{p.Status}");
if (PlayerList.Players.TryGetValue(p.Puncher, out var puncher))
{
Log.Debug("Puncher identified as: " + puncher.Username);
puncher.HolePunchStatus = (byte)(p.Status + 1);
if (p.Status >= 3)
{
Log.Debug("HolePunch sucess: " + from + ", " + puncher.ID);
if (puncher.ConnectWhenPunched && (puncher.Connection == null ||
puncher.Connection.Status == NetConnectionStatus.Disconnected))
{
Log.Debug("Connecting to peer: " + from);
var msg = Networking.Peer.CreateMessage();
new Packets.P2PConnect { ID = LocalPlayerID }.Pack(msg);
puncher.Connection = Networking.Peer.Connect(from, msg);
Networking.Peer.FlushSendQueue();
}
}
}
}
}
}

View File

@ -0,0 +1,233 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using GTA.UI;
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static partial class Networking
{
public static IPEndPoint _targetServerEP;
public static CoopPeer Peer;
public static bool ShowNetworkInfo = false;
public static Security Security = new();
public static NetConnection ServerConnection;
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new();
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new();
internal static float SimulatedLatency = 0;
public static float Latency => ServerConnection.AverageRoundtripTime / 2;
public static bool IsConnecting { get; private set; }
public static bool IsOnServer => ServerConnection?.Status == NetConnectionStatus.Connected;
public static void ToggleConnection(string address, string username = null, string password = null,
PublicKey publicKey = null)
{
CoopMenu.Menu.Visible = false;
if (IsConnecting)
{
_publicKeyReceived.Set();
IsConnecting = false;
API.QueueAction(() =>
Notification.Show("Connection has been canceled"));
Peer.Shutdown("bye");
}
else if (IsOnServer)
{
Peer.Shutdown("bye");
}
else
{
IsConnecting = true;
password ??= Settings.Password;
username ??= Settings.Username;
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
var config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{
AutoFlushSendQueue = false,
AcceptIncomingConnections = true,
MaximumConnections = 32,
PingInterval = 5
};
#if DEBUG
config.SimulatedMinimumLatency = SimulatedLatency;
config.SimulatedRandomLatency = 0;
#endif
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
var ip = new string[2];
var idx = address.LastIndexOf(':');
if (idx != -1)
{
ip[0] = address.Substring(0, idx);
ip[1] = address.Substring(idx + 1);
}
if (ip.Length != 2) throw new Exception("Malformed URL");
PlayerList.Cleanup();
EntityPool.AddPlayer();
if (publicKey == null && !string.IsNullOrEmpty(password) && !CoopMenu.ShowPopUp("", "WARNING",
"Server's IP can be spoofed when using direct connection, do you wish to continue?", "", true))
{
IsConnecting = false;
return;
}
ThreadManager.CreateThread(() =>
{
try
{
_targetServerEP = CoreUtils.StringToEndPoint(address);
// Ensure static constructor invocation
DownloadManager.Cleanup();
Peer = new CoopPeer(config,Log);
Peer.OnMessageReceived += (s, m) =>
{
try
{
ProcessMessage(m);
}
catch (Exception ex)
{
Log.Error(ex);
}
};
API.QueueAction(() => { Notification.Show("~y~Trying to connect..."); });
CoopMenu._serverConnectItem.Enabled = false;
Security.Regen();
if (publicKey == null)
{
if (!GetServerPublicKey(ip[0], int.Parse(ip[1])))
{
CoopMenu._serverConnectItem.Enabled = true;
throw new TimeoutException("Failed to retrive server's public key");
}
}
else
{
Security.SetServerPublicKey(publicKey.Modulus, publicKey.Exponent);
}
// Send handshake packet
var outgoingMessage = Peer.CreateMessage();
var handshake = new Packets.Handshake
{
PedID = LocalPlayerID,
Username = username,
ModVersion = Main.ModVersion.ToString(),
PasswordEncrypted = Security.Encrypt(password.GetBytes()),
InternalEndPoint = new IPEndPoint(CoreUtils.GetLocalAddress(ip[0]), Peer.Port)
};
Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted, out handshake.AesIVCrypted);
handshake.Pack(outgoingMessage);
ServerConnection = Peer.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
}
catch (Exception ex)
{
Log.Error("Cannot connect to server: ", ex);
API.QueueAction(() => Notification.Show("~r~Cannot connect to server: " + ex.Message));
}
IsConnecting = false;
}, "Connect");
}
}
private static int NewRequestID()
{
var ID = 0;
while (ID == 0 || PendingResponses.ContainsKey(ID))
{
var rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
return ID;
}
#region -- PLAYER --
private static void PlayerConnect(Packets.PlayerConnect packet)
{
var p = new Player
{
ID = packet.PedID,
Username = packet.Username
};
PlayerList.SetPlayer(packet.PedID, packet.Username);
Log.Debug($"player connected:{p.Username}");
API.QueueAction(() =>
Notification.Show($"~h~{p.Username}~h~ connected."));
}
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
{
var player = PlayerList.GetPlayer(packet.PedID);
if (player == null) return;
PlayerList.RemovePlayer(packet.PedID);
API.QueueAction(() =>
{
EntityPool.RemoveAllFromPlayer(packet.PedID);
Notification.Show($"~h~{player.Username}~h~ left.");
});
}
#endregion // -- PLAYER --
#region -- GET --
private static bool GetServerPublicKey(string host, int port, int timeout = 10000)
{
Security.ServerRSA = null;
var msg = Peer.CreateMessage();
new Packets.PublicKeyRequest().Pack(msg);
Peer.SendUnconnectedMessage(msg, host, port);
return _publicKeyReceived.WaitOne(timeout) && Security.ServerRSA != null;
}
public static void GetResponse<T>(Packet request, Action<T> callback,
ConnectionChannel channel = ConnectionChannel.RequestResponse) where T : Packet, new()
{
var received = new AutoResetEvent(false);
var id = NewRequestID();
PendingResponses.Add(id, (type, p) =>
{
var result = new T();
result.Deserialize(p);
callback(result);
});
var msg = Peer.CreateMessage();
msg.Write((byte)PacketType.Request);
msg.Write(id);
request.Pack(msg);
Peer.SendMessage(msg, ServerConnection, NetDeliveryMethod.ReliableOrdered, (int)channel);
}
#endregion
}
}

View File

@ -0,0 +1,390 @@
using System;
using System.Threading;
using GTA;
using GTA.UI;
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using RageCoop.Core.Scripting;
namespace RageCoop.Client
{
internal static partial class Networking
{
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
public static void ProcessMessage(NetIncomingMessage message)
{
if (message == null) return;
var _recycle = true;
switch (message.MessageType)
{
case NetIncomingMessageType.StatusChanged:
var status = (NetConnectionStatus)message.ReadByte();
var reason = message.ReadString();
switch (status)
{
case NetConnectionStatus.InitiatedConnect:
if (message.SenderConnection == ServerConnection) CoopMenu.InitiateConnectionMenuSetting();
break;
case NetConnectionStatus.Connected:
if (message.SenderConnection == ServerConnection)
{
var response = message.SenderConnection.RemoteHailMessage;
if ((PacketType)response.ReadByte() != PacketType.HandshakeSuccess)
throw new Exception("Invalid handshake response!");
var p = new Packets.HandshakeSuccess();
p.Deserialize(response);
foreach (var player in p.Players) PlayerList.SetPlayer(player.ID, player.Username);
Connected();
}
else
{
// Self-initiated connection
if (message.SenderConnection.RemoteHailMessage == null) return;
var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>();
if (PlayerList.Players.TryGetValue(p.ID, out var player))
{
player.Connection = message.SenderConnection;
Log.Debug($"Direct connection to {player.Username} established");
}
else
{
Log.Info(
$"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
message.SenderConnection.Disconnect("eat poop");
}
}
break;
case NetConnectionStatus.Disconnected:
if (message.SenderConnection == ServerConnection) API.QueueAction(() => CleanUp(reason));
break;
}
break;
case NetIncomingMessageType.Data:
{
if (message.LengthBytes == 0) break;
var packetType = PacketType.Unknown;
try
{
// Get packet type
packetType = (PacketType)message.ReadByte();
switch (packetType)
{
case PacketType.Response:
{
var id = message.ReadInt32();
if (PendingResponses.TryGetValue(id, out var callback))
{
callback((PacketType)message.ReadByte(), message);
PendingResponses.Remove(id);
}
break;
}
case PacketType.Request:
{
var id = message.ReadInt32();
var realType = (PacketType)message.ReadByte();
if (RequestHandlers.TryGetValue(realType, out var handler))
{
var response = Peer.CreateMessage();
response.Write((byte)PacketType.Response);
response.Write(id);
handler(message).Pack(response);
Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered,
message.SequenceChannel);
Peer.FlushSendQueue();
}
else
{
Log.Debug("Did not find a request handler of type: " + realType);
}
break;
}
default:
{
HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
break;
}
}
}
catch (Exception ex)
{
API.QueueAction(() =>
{
Notification.Show($"~r~~h~Packet Error {ex.Message}");
return true;
});
Log.Error($"[{packetType}] {ex.Message}");
Log.Error(ex);
Peer.Shutdown($"Packet Error [{packetType}]");
}
break;
}
case NetIncomingMessageType.UnconnectedData:
{
var packetType = (PacketType)message.ReadByte();
switch (packetType)
{
case PacketType.HolePunch:
{
HolePunch.Punched(message.GetPacket<Packets.HolePunch>(), message.SenderEndPoint);
break;
}
case PacketType.PublicKeyResponse:
{
if (message.SenderEndPoint.ToString() != _targetServerEP.ToString() || !IsConnecting) break;
var packet = message.GetPacket<Packets.PublicKeyResponse>();
Security.SetServerPublicKey(packet.Modulus, packet.Exponent);
_publicKeyReceived.Set();
break;
}
}
break;
}
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.ErrorMessage:
case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.VerboseDebugMessage:
Log.Trace(message.ReadString());
break;
}
if (_recycle) Peer.Recycle(message);
}
private static void HandlePacket(PacketType packetType, NetIncomingMessage msg, NetConnection senderConnection,
ref bool recycle)
{
switch (packetType)
{
case PacketType.HolePunchInit:
HolePunch.Add(msg.GetPacket<Packets.HolePunchInit>());
break;
case PacketType.PlayerConnect:
PlayerConnect(msg.GetPacket<Packets.PlayerConnect>());
break;
case PacketType.PlayerDisconnect:
PlayerDisconnect(msg.GetPacket<Packets.PlayerDisconnect>());
break;
case PacketType.PlayerInfoUpdate:
PlayerList.UpdatePlayer(msg.GetPacket<Packets.PlayerInfoUpdate>());
break;
case PacketType.VehicleSync:
ReceivedPackets.VehicelPacket.Deserialize(msg);
VehicleSync(ReceivedPackets.VehicelPacket);
break;
case PacketType.PedSync:
ReceivedPackets.PedPacket.Deserialize(msg);
PedSync(ReceivedPackets.PedPacket);
break;
case PacketType.ProjectileSync:
ReceivedPackets.ProjectilePacket.Deserialize(msg);
ProjectileSync(ReceivedPackets.ProjectilePacket);
break;
case PacketType.ChatMessage:
{
var packet = new Packets.ChatMessage(b => Security.Decrypt(b));
packet.Deserialize(msg);
API.QueueAction(() =>
{
MainChat.AddMessage(packet.Username, packet.Message);
return true;
});
}
break;
case PacketType.Voice:
{
if (Settings.Voice)
{
var packet = new Packets.Voice();
packet.Deserialize(msg);
var player = EntityPool.GetPedByID(packet.ID);
player.IsSpeaking = true;
player.LastSpeakingTime = Ticked;
Voice.AddVoiceData(packet.Buffer, packet.Recorded);
}
}
break;
case PacketType.CustomEvent:
{
var packet = new Packets.CustomEvent();
if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
{
recycle = false;
API.QueueAction(() =>
{
packet.Deserialize(msg);
API.Events.InvokeCustomEventReceived(packet);
Peer.Recycle(msg);
});
}
else
{
packet.Deserialize(msg);
API.Events.InvokeCustomEventReceived(packet);
}
}
break;
case PacketType.FileTransferChunk:
{
var packet = new Packets.FileTransferChunk();
packet.Deserialize(msg);
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
default:
if (packetType.IsSyncEvent())
{
recycle = false;
// Dispatch to script thread
API.QueueAction(() =>
{
SyncEvents.HandleEvent(packetType, msg);
return true;
});
}
break;
}
}
private static void PedSync(Packets.PedSync packet)
{
var c = EntityPool.GetPedByID(packet.ID);
if (c == null)
// Log.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
var flags = packet.Flags;
c.ID = packet.ID;
c.OwnerID = packet.OwnerID;
c.Health = packet.Health;
c.Rotation = packet.Rotation;
c.Velocity = packet.Velocity;
c.Speed = packet.Speed;
c.Flags = packet.Flags;
c.Heading = packet.Heading;
c.Position = packet.Position;
if (c.IsRagdoll)
{
c.HeadPosition = packet.HeadPosition;
c.RightFootPosition = packet.RightFootPosition;
c.LeftFootPosition = packet.LeftFootPosition;
}
else if (c.Speed >= 4)
{
c.VehicleID = packet.VehicleID;
c.Seat = packet.Seat;
}
if (c.IsAiming) c.AimCoords = packet.AimCoords;
bool full = packet.Flags.HasPedFlag(PedDataFlags.IsFullSync);
if (full)
{
if (packet.Speed == 4)
c.VehicleWeapon = packet.VehicleWeapon;
c.CurrentWeapon = packet.CurrentWeapon;
c.Clothes = packet.Clothes;
c.WeaponComponents = packet.WeaponComponents;
c.WeaponTint = packet.WeaponTint;
c.Model = packet.ModelHash;
c.BlipColor = packet.BlipColor;
c.BlipSprite = packet.BlipSprite;
c.BlipScale = packet.BlipScale;
}
c.SetLastSynced(full);
}
private static void VehicleSync(Packets.VehicleSync packet)
{
var v = EntityPool.GetVehicleByID(packet.ID);
if (v == null) EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
if (v.IsLocal) return;
v.ID = packet.ID;
v.OwnerID = packet.OwnerID;
v.Flags = packet.Flags;
v.Position = packet.Position;
v.Quaternion = packet.Quaternion;
v.SteeringAngle = packet.SteeringAngle;
v.ThrottlePower = packet.ThrottlePower;
v.BrakePower = packet.BrakePower;
v.Velocity = packet.Velocity;
v.RotationVelocity = packet.RotationVelocity;
v.DeluxoWingRatio = packet.DeluxoWingRatio;
bool full = packet.Flags.HasVehFlag(VehicleDataFlags.IsFullSync);
if (full)
{
v.DamageModel = packet.DamageModel;
v.EngineHealth = packet.EngineHealth;
v.Mods = packet.Mods;
v.ToggleModsMask = packet.ToggleModsMask;
v.Model = packet.ModelHash;
v.Colors = packet.Colors;
v.LandingGear = packet.LandingGear;
v.RoofState = (VehicleRoofState)packet.RoofState;
v.LockStatus = packet.LockStatus;
v.RadioStation = packet.RadioStation;
v.LicensePlate = packet.LicensePlate;
v.Livery = packet.Livery;
v.HeadlightColor = packet.HeadlightColor;
v.ExtrasMask = packet.ExtrasMask;
}
v.SetLastSynced(full);
}
private static void ProjectileSync(Packets.ProjectileSync packet)
{
var p = EntityPool.GetProjectileByID(packet.ID);
if (p == null)
{
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) return;
// Log.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID));
}
p.Flags = packet.Flags;
p.Position = packet.Position;
p.Rotation = packet.Rotation;
p.Velocity = packet.Velocity;
p.WeaponHash = (WeaponHash)packet.WeaponHash;
p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle)
? (SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID)
: EntityPool.GetPedByID(packet.ShooterID);
p.SetLastSynced(false);
}
/// <summary>
/// Reduce GC pressure by reusing frequently used packets
/// </summary>
private static class ReceivedPackets
{
public static readonly Packets.PedSync PedPacket = new Packets.PedSync();
public static readonly Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
public static readonly Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
}
}
}

View File

@ -0,0 +1,196 @@
using System.Collections.Generic;
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static partial class Networking
{
public static int SyncInterval = 30;
public static List<NetConnection> Targets = new List<NetConnection>();
public static void SendSync(Packet p, ConnectionChannel channel = ConnectionChannel.Default,
NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
Peer.SendTo(p, Targets, channel, method);
}
public static void SendPed(SyncedPed sp, bool full)
{
if (sp.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
var ped = sp.MainPed;
var p = SendPackets.PedPacket;
p.ID = sp.ID;
p.OwnerID = sp.OwnerID;
p.Health = ped.Health;
p.Rotation = ped.ReadRotation();
p.Velocity = ped.ReadVelocity();
p.Speed = ped.GetPedSpeed();
p.Flags = ped.GetPedFlags();
p.Heading = ped.Heading;
if (p.Flags.HasPedFlag(PedDataFlags.IsAiming)) p.AimCoords = ped.GetAimCoord();
if (p.Flags.HasPedFlag(PedDataFlags.IsRagdoll))
{
p.HeadPosition = ped.Bones[Bone.SkelHead].Position;
p.RightFootPosition = ped.Bones[Bone.SkelRightFoot].Position;
p.LeftFootPosition = ped.Bones[Bone.SkelLeftFoot].Position;
}
else
{
// Seat sync
if (p.Speed >= 4)
{
var veh = ped.CurrentVehicle?.GetSyncEntity() ??
ped.VehicleTryingToEnter?.GetSyncEntity() ?? ped.LastVehicle?.GetSyncEntity();
p.VehicleID = veh?.ID ?? 0;
if (p.VehicleID == 0) Log.Error("Invalid vehicle");
if (p.Speed == 5)
p.Seat = ped.GetSeatTryingToEnter();
else
p.Seat = ped.SeatIndex;
if (!veh.IsLocal && p.Speed == 4 && p.Seat == VehicleSeat.Driver)
{
veh.OwnerID = LocalPlayerID;
SyncEvents.TriggerChangeOwner(veh.ID, LocalPlayerID);
}
}
p.Position = ped.ReadPosition();
}
sp.LastSentStopWatch.Restart();
if (full)
{
if (p.Speed == 4)
p.VehicleWeapon = ped.VehicleWeapon;
p.CurrentWeapon = ped.Weapons.Current.Hash;
p.Flags |= PedDataFlags.IsFullSync;
p.Clothes = ped.GetPedClothes();
p.ModelHash = ped.Model.Hash;
p.WeaponComponents = ped.Weapons.Current.GetWeaponComponents();
p.WeaponTint = (byte)Call<int>(GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
Blip b;
if (sp.IsPlayer)
{
p.BlipColor = API.Config.BlipColor;
p.BlipSprite = API.Config.BlipSprite;
p.BlipScale = API.Config.BlipScale;
}
else if ((b = ped.AttachedBlip) != null)
{
p.BlipColor = b.Color;
p.BlipSprite = b.Sprite;
if (p.BlipSprite == BlipSprite.PoliceOfficer || p.BlipSprite == BlipSprite.PoliceOfficer2)
p.BlipScale = 0.5f;
}
else
{
p.BlipColor = (BlipColor)255;
}
}
SendSync(p, ConnectionChannel.PedSync);
}
public static void SendVehicle(SyncedVehicle v, bool full)
{
if (v.LastSentStopWatch.ElapsedMilliseconds < SyncInterval) return;
var veh = v.MainVehicle;
var packet = SendPackets.VehicelPacket;
packet.ID = v.ID;
packet.OwnerID = v.OwnerID;
packet.Flags = v.GetVehicleFlags();
packet.SteeringAngle = veh.SteeringAngle;
packet.Position = veh.ReadPosition();
packet.Velocity = veh.Velocity;
packet.Quaternion = veh.ReadQuaternion();
packet.RotationVelocity = veh.WorldRotationVelocity;
packet.ThrottlePower = veh.ThrottlePower;
packet.BrakePower = veh.BrakePower;
v.LastSentStopWatch.Restart();
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
packet.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio();
if (full)
{
byte primaryColor = 0;
byte secondaryColor = 0;
unsafe
{
Call<byte>(GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
}
packet.Flags |= VehicleDataFlags.IsFullSync;
packet.Colors = (primaryColor, secondaryColor);
packet.DamageModel = veh.GetVehicleDamageModel();
packet.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
packet.RoofState = (byte)veh.RoofState;
packet.Mods = v.GetVehicleMods(out packet.ToggleModsMask);
packet.ModelHash = veh.Model.Hash;
packet.EngineHealth = veh.EngineHealth;
packet.LockStatus = veh.LockStatus;
packet.LicensePlate = Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
packet.Livery = Call<int>(GET_VEHICLE_LIVERY, veh);
packet.HeadlightColor = (byte)Call<int>(GET_VEHICLE_XENON_LIGHT_COLOR_INDEX, veh);
packet.ExtrasMask = v.GetVehicleExtras();
packet.RadioStation = v.MainVehicle == LastV
? Util.GetPlayerRadioIndex() : byte.MaxValue;
if (packet.EngineHealth > v.LastEngineHealth) packet.Flags |= VehicleDataFlags.Repaired;
v.LastEngineHealth = packet.EngineHealth;
}
SendSync(packet, ConnectionChannel.VehicleSync);
}
public static void SendProjectile(SyncedProjectile sp)
{
sp.ExtractData(ref SendPackets.ProjectilePacket);
if (sp.MainProjectile.IsDead) EntityPool.RemoveProjectile(sp.ID, "Dead");
SendSync(SendPackets.ProjectilePacket, ConnectionChannel.ProjectileSync);
}
public static void SendChatMessage(string message)
{
Peer.SendTo(new Packets.ChatMessage(s => Security.Encrypt(s.GetBytes()))
{ Username = Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat,
NetDeliveryMethod.ReliableOrdered);
Peer.FlushSendQueue();
}
public static void SendVoiceMessage(byte[] buffer, int recorded)
{
SendSync(new Packets.Voice { ID = LocalPlayerID, Buffer = buffer, Recorded = recorded },
ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Reduce GC pressure by reusing frequently used packets
/// </summary>
private static class SendPackets
{
public static readonly Packets.PedSync PedPacket = new Packets.PedSync();
public static readonly Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
}
#region SYNC EVENTS
public static void SendBullet(int ownerID, uint weapon, Vector3 end)
{
SendSync(new Packets.BulletShot
{
EndPosition = end,
OwnerID = ownerID,
WeaponHash = weapon
}, ConnectionChannel.SyncEvents);
}
#endregion
}
}

View File

@ -0,0 +1,26 @@
using System.Threading;
using System.Threading.Tasks;
namespace RageCoop.Client
{
internal static class Statistics
{
static Statistics()
{
ThreadManager.CreateThread(() =>
{
while (!IsUnloading)
{
var bu = Networking.Peer.Statistics.SentBytes;
var bd = Networking.Peer.Statistics.ReceivedBytes;
Thread.Sleep(1000);
BytesUpPerSecond = Networking.Peer.Statistics.SentBytes - bu;
BytesDownPerSecond = Networking.Peer.Statistics.ReceivedBytes - bd;
}
},"Statistics");
}
public static int BytesDownPerSecond { get; private set; }
public static int BytesUpPerSecond { get; private set; }
}
}

View File

@ -0,0 +1,173 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.Json.Serialization;
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class PlayerList
{
private const float LEFT_POSITION = 0.122f;
private const float RIGHT_POSITION = 0.9f;
private static readonly Scaleform _mainScaleform = new Scaleform("mp_mm_card_freemode");
private static ulong _lastUpdate = Util.GetTickCount64();
public static bool LeftAlign = true;
public static Dictionary<int, Player> Players = new Dictionary<int, Player>();
public static ulong Pressed { get; set; }
public static void Tick()
{
if (!Networking.IsOnServer) return;
if (Util.GetTickCount64() - _lastUpdate >= 1000) Update();
if (Util.GetTickCount64() - Pressed < 5000 && !MainChat.Focused
#if !NON_INTERACTIVE
&& !CoopMenu.MenuPool.AreAnyVisible
#endif
)
Call(DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f,
0.28f, 0.6f,
255, 255, 255, 255, 0);
}
private static void Update()
{
_lastUpdate = Util.GetTickCount64();
_mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
var i = 0;
foreach (var player in Players.Values)
_mainScaleform.CallFunction("SET_DATA_SLOT", i++, $"{player.Ping * 1000:N0}ms",
player.Username + (player.IsHost ? " (Host)" : ""), 116, 0, i - 1, "", "", 2, "", "", ' ');
_mainScaleform.CallFunction("SET_TITLE", "Player list", $"{Players.Count} players");
_mainScaleform.CallFunction("DISPLAY_VIEW");
}
public static void SetPlayer(int id, string username, float latency = 0)
{
Log.Debug($"{id},{username},{latency}");
if (Players.TryGetValue(id, out var p))
{
p.Username = username;
p.ID = id;
p._latencyToServer = latency;
}
else
{
p = new Player { ID = id, Username = username, _latencyToServer = latency };
Players.Add(id, p);
}
}
public static void UpdatePlayer(Packets.PlayerInfoUpdate packet)
{
var p = GetPlayer(packet.PedID);
if (p != null)
{
p._latencyToServer = packet.Latency;
p.Position = packet.Position;
p.IsHost = packet.IsHost;
API.QueueAction(() =>
{
if (p.FakeBlip?.Exists() != true) p.FakeBlip = World.CreateBlip(p.Position);
if (EntityPool.PedExists(p.ID))
{
p.FakeBlip.DisplayType = BlipDisplayType.NoDisplay;
}
else
{
p.FakeBlip.Color = API.Config.BlipColor;
p.FakeBlip.Scale = API.Config.BlipScale;
p.FakeBlip.Sprite = API.Config.BlipSprite;
p.FakeBlip.DisplayType = BlipDisplayType.Default;
p.FakeBlip.Position = p.Position;
}
});
}
}
public static Player GetPlayer(int id)
{
Players.TryGetValue(id, out var p);
return p;
}
public static Player GetPlayer(SyncedPed p)
{
var player = GetPlayer(p.ID);
if (player != null) player.Character = p;
return player;
}
public static void RemovePlayer(int id)
{
if (Players.TryGetValue(id, out var player))
{
Players.Remove(id);
API.QueueAction(() => player.FakeBlip?.Delete());
}
}
public static void Cleanup()
{
foreach (var p in Players.Values.ToArray()) p.FakeBlip?.Delete();
Players = new Dictionary<int, Player>();
}
}
internal class Player
{
internal float _latencyToServer;
internal bool ConnectWhenPunched { get; set; }
[JsonIgnore]
public Blip FakeBlip { get; internal set; }
[JsonIgnore]
public Vector3 Position { get; internal set; }
[JsonIgnore]
public SyncedPed Character { get; internal set; }
[JsonIgnore]
public NetConnection Connection { get; internal set; }
public byte HolePunchStatus { get; internal set; } = 1;
public bool IsHost { get; internal set; }
public string Username { get; internal set; }
/// <summary>
/// Universal ped ID.
/// </summary>
public int ID { get; internal set; }
public int EntityHandle => Character?.MainPed?.Handle ?? 0;
public IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; internal set; }
/// <summary>
/// Player round-trip time in seconds, will be the rtt to server if not using P2P connection.
/// </summary>
public float Ping => LocalPlayerID == ID ? Networking.Latency * 2 :
HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2;
public float PacketTravelTime => HasDirectConnection
? Connection.AverageRoundtripTime / 2
: Networking.Latency + _latencyToServer;
public bool DisplayNameTag { get; set; } = true;
public bool HasDirectConnection => Connection?.Status == NetConnectionStatus.Connected;
}
}

View File

@ -0,0 +1,22 @@

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Resources;
// General Information
[assembly: AssemblyTitle("RageCoop.Client")]
[assembly: AssemblyDescription("RageCoop.Client")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("RAGECOOP")]
[assembly: AssemblyProduct("RageCoop.Client")]
[assembly: AssemblyCopyright("Copyright © 2022")]
[assembly: AssemblyTrademark("RAGECOOP")]
[assembly: AssemblyCulture("")]
// Version information
[assembly: AssemblyVersion("1.6.0.58")]
[assembly: AssemblyFileVersion("1.6.0.58")]
[assembly: NeutralResourcesLanguage( "en-US" )]

View File

@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableDynamicLoading>true</EnableDynamicLoading>
<NoAotCompile>false</NoAotCompile>
<TargetFramework>net7.0</TargetFramework>
<OutputType>Library</OutputType>
<OutDir>..\..\bin\$(Configuration)\Client\Scripts</OutDir>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AllowedReferenceRelatedFileExtensions>
.dll
.pdb
</AllowedReferenceRelatedFileExtensions>
<NoWarn>CS1591</NoWarn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<TrimmerRootDescriptor Include="Trimming.xml" />
<Compile Remove="GUI\**" />
<EmbeddedResource Remove="GUI\**" />
<None Remove="GUI\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Scripting\ClientScript.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="LemonUI.SHVDNC">
<HintPath>..\..\libs\LemonUI.SHVDNC.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
<ProjectReference Include="..\Scripting\RageCoop.Client.Scripting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NAudio" Version="2.1.0" />
<PackageReference Include="SharpZipLib" Version="1.4.0" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="4.3.0" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(SolutionDir)' != '*Undefined*' and '$(Configuration)' != 'API'">
<Exec Command="xcopy &quot;$(SolutionDir)Client\Data&quot; &quot;$(OutDir)..\Data&quot; /i /s /y" />
</Target>
</Project>

View File

@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=shared/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=util/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,140 @@
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using static RageCoop.Core.Scripting.CustomEvents;
namespace RageCoop.Client.Scripting
{
internal static unsafe partial class API
{
static API()
{
RegisterFunctionPointers();
}
static void RegisterFunctionPointers()
{
foreach (var method in typeof(API).GetMethods(BindingFlags.Public | BindingFlags.Static))
{
var attri = method.GetCustomAttribute<ApiExportAttribute>();
if (attri == null) continue;
attri.EntryPoint ??= method.Name;
SHVDN.Core.SetPtr($"{typeof(API).FullName}.{attri.EntryPoint}", method.MethodHandle.GetFunctionPointer());
Log.Debug($"Registered function pointer for {method.DeclaringType}.{method.Name}");
}
}
[ThreadStatic]
static string _lastResult;
[ApiExportAttribute(EntryPoint = nameof(GetLastResult))]
public static int GetLastResult(char* buf, int cbBufSize)
{
if (_lastResult == null)
return 0;
fixed (char* pErr = _lastResult)
{
var cbToCopy = sizeof(char) * (_lastResult.Length + 1);
System.Buffer.MemoryCopy(pErr, buf, cbToCopy, Math.Min(cbToCopy, cbBufSize));
if (cbToCopy > cbBufSize && cbBufSize > 0)
{
buf[cbBufSize / sizeof(char) - 1] = '\0'; // Always add null terminator
}
return _lastResult.Length;
}
}
public static void SetLastResult(string msg) => _lastResult = msg;
[ApiExportAttribute(EntryPoint = nameof(SetLastResult))]
public static void SetLastResult(char* msg)
{
try
{
SetLastResult(msg == null ? null : new string(msg));
}
catch (Exception ex)
{
SHVDN.PInvoke.MessageBoxA(default, ex.ToString(), "error", default);
}
}
[ApiExportAttribute(EntryPoint = nameof(GetEventHash))]
public static CustomEventHash GetEventHash(char* name) => new string(name);
[ApiExportAttribute(EntryPoint = nameof(SendCustomEvent))]
public static void SendCustomEvent(CustomEventFlags flags, int hash, byte* data, int cbData)
{
var payload = new byte[cbData];
Marshal.Copy((IntPtr)data, payload, 0, cbData);
Networking.Peer.SendTo(new Packets.CustomEvent()
{
Flags = flags,
Payload = payload,
Hash = hash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
[ApiExportAttribute(EntryPoint = nameof(InvokeCommand))]
public static int InvokeCommand(char* name, int argc, char** argv)
{
try
{
var args = new string[argc];
for (int i = 0; i < argc; i++)
{
args[i] = new(argv[i]);
}
_lastResult = _invokeCommand(new string(name), args);
return _lastResult.Length;
}
catch (Exception ex)
{
Log.Error(ex);
SetLastResult(ex.ToString());
return 0;
}
}
[ApiExportAttribute(EntryPoint = nameof(GetLastResultLenInChars))]
public static int GetLastResultLenInChars() => _lastResult?.Length ?? 0;
/// <summary>
/// Convert Entity ID to handle
/// </summary>
[ApiExportAttribute(EntryPoint = nameof(IdToHandle))]
public static int IdToHandle(byte type, int id)
{
return type switch
{
T_ID_PROP => EntityPool.GetPropByID(id)?.MainProp?.Handle ?? 0,
T_ID_PED => EntityPool.GetPedByID(id)?.MainPed?.Handle ?? 0,
T_ID_VEH => EntityPool.GetVehicleByID(id)?.MainVehicle?.Handle ?? 0,
T_ID_BLIP => EntityPool.GetBlipByID(id)?.Handle ?? 0,
_ => 0,
};
}
/// <summary>
/// Enqueue a message to the main logger
/// </summary>
/// <param name="level"></param>
/// <param name="msg"></param>
[ApiExportAttribute(EntryPoint = nameof(LogEnqueue))]
public static void LogEnqueue(LogLevel level, char* msg)
{
Log.Enqueue((int)level, new(msg));
}
class ApiExportAttribute : Attribute
{
public string EntryPoint;
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
internal class RemotingAttribute : Attribute
{
public bool GenBridge = true;
}
/// <summary>
/// Some local remoting implementation with json-based serialization, somewhar slow, but convenient
/// </summary>
internal static unsafe partial class API
{
static readonly MethodInfo[] _apiEntries =
typeof(API).GetMethods(BindingFlags.Static | BindingFlags.Public);
static readonly Dictionary<string, MethodInfo> _commands =
new(typeof(API).GetMethods().
Where(md => md.CustomAttributes.
Any(attri => attri.AttributeType == typeof(RemotingAttribute))).
Select(x => new KeyValuePair<string, MethodInfo>(x.Name, x)));
static string _invokeCommand(string name, string[] argsJson)
{
if (_commands.TryGetValue(name, out var method))
{
var ps = method.GetParameters();
if (argsJson.Length != ps.Length)
throw new ArgumentException($"Parameter count mismatch, expecting {ps.Length} parameters, got {argsJson.Length}", nameof(argsJson));
object[] args = new object[ps.Length];
for (int i = 0; i < ps.Length; i++)
{
args[i] = JsonDeserialize(argsJson[i], ps[i].ParameterType);
}
var result = method.Invoke(null, args);
if (method.ReturnType == typeof(void))
{
return "void";
}
return JsonSerialize(result);
}
throw new KeyNotFoundException($"Command {name} was not found");
}
}
}

View File

@ -0,0 +1,487 @@
#undef DEBUG
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
[assembly: InternalsVisibleTo("CodeGen")] // For generating api bridge
namespace RageCoop.Client.Scripting
{
/// <summary>
/// Provides vital functionality to interact with RAGECOOP
/// </summary>
internal static unsafe partial class API
{
#region INTERNAL
internal static Dictionary<int, List<CustomEventHandler>> CustomEventHandlers =
new();
#endregion
/// <summary>
/// Client configuration, this will conflict with server-side config.
/// </summary>
public static class Config
{
/// <summary>
/// Get or set local player's username, set won't be effective if already connected to a server.
/// </summary>
public static string Username
{
get => Settings.Username;
set
{
if (Networking.IsOnServer || string.IsNullOrEmpty(value)) return;
Settings.Username = value;
}
}
/// <summary>
/// Enable automatic respawn for this player.
/// </summary>
public static bool EnableAutoRespawn { get; set; } = true;
/// <summary>
/// Get or set player's blip color
/// </summary>
public static BlipColor BlipColor { get; set; } = BlipColor.White;
/// <summary>
/// Get or set player's blip sprite
/// </summary>
public static BlipSprite BlipSprite { get; set; } = BlipSprite.Standard;
/// <summary>
/// Get or set scale of player's blip
/// </summary>
public static float BlipScale { get; set; } = 1;
public static bool ShowPlayerNameTag
{
get => Settings.ShowPlayerNameTag;
set
{
if (value == ShowPlayerNameTag) return;
Settings.ShowPlayerNameTag = value;
Util.SaveSettings();
}
}
}
/// <summary>
/// Base events for RageCoop
/// </summary>
public static class Events
{
/// <summary>
/// The local player is dead
/// </summary>
public static event EmptyEvent OnPlayerDied;
/// <summary>
/// A local vehicle is spawned
/// </summary>
public static event EventHandler<SyncedVehicle> OnVehicleSpawned;
/// <summary>
/// A local vehicle is deleted
/// </summary>
public static event EventHandler<SyncedVehicle> OnVehicleDeleted;
/// <summary>
/// A local ped is spawned
/// </summary>
public static event EventHandler<SyncedPed> OnPedSpawned;
/// <summary>
/// A local ped is deleted
/// </summary>
public static event EventHandler<SyncedPed> OnPedDeleted;
#region DELEGATES
/// <summary>
/// </summary>
public delegate void EmptyEvent();
/// <summary>
/// </summary>
/// <param name="hash"></param>
/// <param name="args"></param>
public delegate void CustomEvent(int hash, List<object> args);
#endregion
#region INVOKE
internal static void InvokeVehicleSpawned(SyncedVehicle v)
{
OnVehicleSpawned?.Invoke(null, v);
}
internal static void InvokeVehicleDeleted(SyncedVehicle v)
{
OnVehicleDeleted?.Invoke(null, v);
}
internal static void InvokePedSpawned(SyncedPed p)
{
OnPedSpawned?.Invoke(null, p);
}
internal static void InvokePedDeleted(SyncedPed p)
{
OnPedDeleted?.Invoke(null, p);
}
internal static void InvokePlayerDied()
{
OnPlayerDied?.Invoke();
}
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
{
// Log.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
if (CustomEventHandlers.TryGetValue(p.Hash, out var handlers))
{
fixed (byte* pData = p.Payload)
{
foreach (var handler in handlers)
{
try
{
handler.Invoke(p.Hash, pData, p.Payload.Length);
}
catch (Exception ex)
{
Log.Error("InvokeCustomEvent", ex);
}
}
}
}
}
#endregion
}
#region PROPERTIES
/// <summary>
/// Get the local player's ID
/// </summary>
/// <returns>PlayerID</returns>
public static int LocalPlayerID => Main.LocalPlayerID;
/// <summary>
/// Check if player is connected to a server
/// </summary>
public static bool IsOnServer => Networking.IsOnServer;
/// <summary>
/// Get an <see cref="System.Net.IPEndPoint" /> that the player is currently connected to, or null if not connected to
/// the server
/// </summary>
public static IPEndPoint ServerEndPoint =>
Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null;
/// <summary>
/// Check if a RAGECOOP menu is visible
/// </summary>
public static bool IsMenuVisible => CoopMenu.MenuPool.AreAnyVisible;
/// <summary>
/// Check if the RAGECOOP chat is visible
/// </summary>
public static bool IsChatFocused => MainChat.Focused;
/// <summary>
/// Check if the RAGECOOP list of players is visible
/// </summary>
public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000;
/// <summary>
/// Get the version of RAGECOOP
/// </summary>
public static Version CurrentVersion => Main.ModVersion;
/// <summary>
/// Get all players indexed by their ID
/// </summary>
public static Dictionary<int, Player> Players => new(PlayerList.Players);
#endregion
#region FUNCTIONS
/// <summary>
/// Queue an action to be executed on next tick.
/// </summary>
/// <param name="a"></param>
public static void QueueAction(Action a)
{
WorldThread.QueueAction(a);
}
public static void QueueActionAndWait(Action a, int timeout = 15000)
{
var done = new AutoResetEvent(false);
Exception e = null;
QueueAction(() =>
{
try
{
a();
done.Set();
}
catch (Exception ex)
{
e = ex;
}
});
if (e != null)
throw e;
if (!done.WaitOne(timeout)) throw new TimeoutException();
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a">
/// An <see cref="Func{T, TResult}" /> to be executed with a return value indicating whether it can be
/// removed after execution.
/// </param>
public static void QueueAction(Func<bool> a)
{
WorldThread.QueueAction(a);
}
/// <summary>
/// Send an event and data to the server.
/// </summary>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">
/// The objects conataing your data, see <see cref="CustomEventReceivedArgs" /> for a list of supported
/// types
/// </param>
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
=> SendCustomEvent(CustomEventFlags.None, eventHash, args);
/// <summary>
/// Send an event and data to the server
/// </summary>
/// <param name="flags"></param>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">
/// The objects conataing your data, see <see cref="CustomEventReceivedArgs" /> for a list of supported
/// types
/// </param>
public static void SendCustomEvent(CustomEventFlags flags, CustomEventHash eventHash, params object[] args)
{
var writer = GetWriter();
CustomEvents.WriteObjects(writer, args);
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
{
Payload = writer.ToByteArray(writer.Position),
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
/// </summary>
/// <param name="hash">
/// An unique identifier of the event
/// </param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
=> RegisterCustomEventHandler(hash, (CustomEventHandler)handler);
/// <summary>
/// </summary>
/// <returns></returns>
public static void RequestSharedFile(string name, Action<string> callback)
{
EventHandler<string> handler = (s, e) =>
{
if (e.EndsWith(name)) callback(e);
};
DownloadManager.DownloadCompleted += handler;
Networking.GetResponse<Packets.FileTransferResponse>(new Packets.FileTransferRequest
{
Name = name
},
p =>
{
if (p.Response != FileResponse.Loaded)
{
DownloadManager.DownloadCompleted -= handler;
throw new ArgumentException("Requested file was not found on the server: " + name);
}
});
}
/// <summary>
/// Connect to a server
/// </summary>
/// <param name="address">Address of the server, e.g. 127.0.0.1:4499</param>
/// <exception cref="InvalidOperationException">When a connection is active or being established</exception>
[Remoting]
public static void Connect(string address)
{
if (Networking.IsOnServer || Networking.IsConnecting)
throw new InvalidOperationException("Cannot connect to server when another connection is active");
Networking.ToggleConnection(address);
}
/// <summary>
/// Disconnect from current server or cancel the connection attempt.
/// </summary>
[Remoting]
public static void Disconnect()
{
if (Networking.IsOnServer || Networking.IsConnecting) Networking.ToggleConnection(null);
}
/// <summary>
/// List all servers from master server address
/// </summary>
/// <returns></returns>
[Remoting]
public static List<ServerInfo> ListServers()
{
return JsonDeserialize<List<ServerInfo>>(
HttpHelper.DownloadString(Settings.MasterServer));
}
/// <summary>
/// Send a local chat message to this player
/// </summary>
/// <param name="from">Name of the sender</param>
/// <param name="message">The player's message</param>
[Remoting]
public static void LocalChatMessage(string from, string message)
{
MainChat.AddMessage(from, message);
}
/// <summary>
/// Send a chat message or command to server/other players
/// </summary>
/// <param name="message"></param>
[Remoting]
public static void SendChatMessage(string message)
{
if (!IsOnServer)
throw new InvalidOperationException("Not on server");
Networking.SendChatMessage(message);
}
/// <summary>
/// Get the <see cref="ClientResource"/> with this name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Remoting]
public static ClientResource GetResource(string name)
{
if (MainRes.LoadedResources.TryGetValue(name, out var resource))
return resource;
return null;
}
/// <summary>
/// Get <see cref="ClientResource"/> that contains the specified file or directory
/// </summary>
/// <returns></returns>
[Remoting]
public static ClientResource GetResourceFromPath(string path)
{
path = Path.GetFullPath(path).ToLower();
foreach (var res in MainRes.LoadedResources.Values)
{
if (res.ScriptsDirectory.ToLower() == path || res.Files.Any(file => file.Value.FullPath.ToLower() == path))
return res;
}
return null;
}
[Remoting(GenBridge = false)]
public static object GetProperty(string name)
=> typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting(GenBridge = false)]
public static void SetProperty(string name, string jsonVal)
{
var prop = typeof(API).GetProperty(name, BindingFlags.Static | BindingFlags.Public); ;
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
[Remoting]
public static object GetConfig(string name)
=> typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
[Remoting]
public static void SetConfig(string name, string jsonVal)
{
var prop = typeof(Config).GetProperty(name, BindingFlags.Static | BindingFlags.Public);
if (prop == null)
throw new KeyNotFoundException($"Property {name} was not found");
prop.SetValue(null, JsonDeserialize(jsonVal, prop.PropertyType));
}
/// <summary>
/// Register an handler to the specifed event hash, one event can have multiple handlers. This will be invoked from
/// backgound thread, use <see cref="QueueAction(Action)" /> in the handler to dispatch code to script thread.
/// </summary>
/// <param name="hash">
/// An unique identifier of the event
/// </param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
[Remoting]
public static void RegisterCustomEventHandler(CustomEventHash hash, CustomEventHandler handler)
{
if (handler.Directory == default)
throw new ArgumentException("Script directory not specified");
if (handler.FunctionPtr == default)
throw new ArgumentException("Function pointer not specified");
lock (CustomEventHandlers)
{
if (!CustomEventHandlers.TryGetValue(hash, out var handlers))
CustomEventHandlers.Add(hash, handlers = new());
handlers.Add(handler);
}
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,24 @@
using GTA;
using RageCoop.Core;
using RageCoop.Core.Scripting;
namespace RageCoop.Client.Scripting
{
public abstract class ClientScript : Script
{
/// <summary>
/// Get the <see cref="ResourceFile" /> instance where this script is loaded from.
/// </summary>
public ResourceFile CurrentFile { get; internal set; }
/// <summary>
/// Get the <see cref="ClientResource" /> that this script belongs to.
/// </summary>
public ClientResource CurrentResource { get; internal set; }
/// <summary>
/// Eqivalent of <see cref="ClientResource.Logger" /> in <see cref="CurrentResource" />
/// </summary>
public Logger Logger => CurrentResource.Logger;
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using ICSharpCode.SharpZipLib.Zip;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
namespace RageCoop.Client.Scripting
{
internal class Resources
{
public static string TempPath = Path.Combine(Path.GetTempPath(), "RageCoop");
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new();
static Resources()
{
if (Directory.Exists(TempPath))
try
{
Directory.Delete(TempPath, true);
}
catch
{
}
TempPath = CoreUtils.GetTempDirectory(TempPath);
Directory.CreateDirectory(TempPath);
}
public unsafe void Load(string path, string[] zips)
{
LoadedResources.Clear();
foreach (var zip in zips)
{
var zipPath = Path.Combine(path, zip);
Log?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
Unpack(zipPath, Path.Combine(path, "Data"));
}
}
public unsafe void Unload()
{
var dirs = LoadedResources.Values.Select(x => x.ScriptsDirectory);
foreach (var dir in dirs)
{
SHVDN.Core.RuntimeController.RequestUnload(dir);
}
// Unregister associated handler
foreach (var handlers in API.CustomEventHandlers.Values)
{
foreach (var handler in handlers.ToArray())
{
if (dirs.Contains(handler.Directory, StringComparer.OrdinalIgnoreCase))
{
handlers.Remove(handler);
Log.Debug($"Unregistered handler from script directory {handler.Directory}");
}
}
}
LoadedResources.Clear();
}
private unsafe ClientResource Unpack(string zipPath, string dataFolderRoot)
{
var r = new ClientResource
{
Name = Path.GetFileNameWithoutExtension(zipPath),
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
ScriptsDirectory = Path.Combine(TempPath, Path.GetFileNameWithoutExtension(zipPath))
};
Directory.CreateDirectory(r.DataFolder);
var scriptsDir = r.ScriptsDirectory;
if (Directory.Exists(scriptsDir))
Directory.Delete(scriptsDir, true);
else if (File.Exists(scriptsDir)) File.Delete(scriptsDir);
Directory.CreateDirectory(scriptsDir);
new FastZip().ExtractZip(zipPath, scriptsDir, null);
foreach (var dir in Directory.GetDirectories(scriptsDir, "*", SearchOption.AllDirectories))
r.Files.Add(dir, new ClientFile
{
IsDirectory = true,
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/'),
FullPath = dir
});
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
{
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
var rfile = new ClientFile
{
IsDirectory = false,
Name = relativeName,
FullPath = file
};
r.Files.Add(relativeName, rfile);
}
SHVDN.Core.RuntimeController.RequestLoad(scriptsDir);
LoadedResources.TryAdd(r.Name, r);
return r;
}
}
}

View File

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

50
Client/Scripts/Shared.cs Normal file
View File

@ -0,0 +1,50 @@
global using GTA;
global using GTA.Native;
global using static GTA.Native.Function;
global using static GTA.Native.Hash;
global using static RageCoop.Client.Shared;
global using static RageCoop.Client.Main;
global using Console = GTA.Console;
global using static RageCoop.Core.Shared;
using System.IO;
using System;
using SHVDN;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace RageCoop.Client
{
[AttributeUsage(AttributeTargets.Field)]
class DebugTunableAttribute : Attribute
{
}
internal static class Shared
{
private static unsafe string GetBasePath()
{
using var fs = File.OpenRead(Instance.FilePath);
var buf = stackalloc char[1024];
GetFinalPathNameByHandleW(fs.SafeFileHandle.DangerousGetHandle(), buf, 1024, 0);
ErrorCheck32();
var scriptDir = Directory.GetParent(Marshal.PtrToStringUni((IntPtr)buf)).FullName;
if (Path.GetFileName(scriptDir).ToLower() != "scripts")
throw new Exception("Unexpected script location");
var result = Directory.GetParent(scriptDir).FullName;
Logger.Debug($"Base path is: {result}");
return result;
}
public static string BasePath = GetBasePath();
public static string DataPath = Path.Combine(BasePath, "Data");
public static string SettingsPath = Path.Combine(DataPath, "Setting.json");
public static string VehicleWeaponDataPath = Path.Combine(DataPath, "VehicleWeapons.json");
public static string WeaponFixDataPath = Path.Combine(DataPath, "WeaponFixes.json");
public static string WeaponInfoDataPath = Path.Combine(DataPath, "Weapons.json");
public static string AnimationsDataPath = Path.Combine(DataPath, "Animations.json");
public static string CefSubProcessPath = Path.Combine(BasePath, "SubProcess", "RageCoop.Client.CefHost.exe");
}
}

View File

@ -0,0 +1,161 @@
using GTA;
using GTA.Native;
namespace RageCoop.Client
{
public partial class SyncedPed
{
private void DisplaySpeaking(bool speaking)
{
if (!MainPed.IsHuman)
return;
if (speaking)
{
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mic_chatter", "mp_facial");
return;
}
switch (MainPed.Gender)
{
case Gender.Male:
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@gen_male@variations@normal");
break;
case Gender.Female:
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@gen_female@variations@normal");
break;
default:
Call(PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1",
"facials@mime@variations@normal");
break;
}
}
private void DisplayInCover()
{
var ourAnim = GetCoverAnim();
var animDict = GetCoverIdleAnimDict();
if (ourAnim != null && animDict != null)
{
var flag = AnimationFlags.Loop;
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
{
if (LoadAnim(animDict) == null) return;
MainPed.Task.ClearAll();
Call(TASK_PLAY_ANIM, MainPed, animDict, ourAnim, 8f, 10f, -1, flag, -8f, 1, 1, 1);
}
}
}
internal string GetCoverAnim()
{
if (IsInCover)
{
if (IsBlindFiring)
{
if (IsInCover)
return IsInCoverFacingLeft ? "blindfire_low_l_aim_med" : "blindfire_low_r_aim_med";
return IsInCoverFacingLeft ? "blindfire_hi_l_aim_med" : "blindfire_hi_r_aim_med";
}
return IsInCoverFacingLeft ? "idle_l_corner" : "idle_r_corner";
}
return null;
}
internal string GetCoverIdleAnimDict()
{
if (!IsInCover) return "";
var altitude = IsInLowCover ? "low" : "high";
var hands = GetWeaponHandsHeld((uint)CurrentWeapon);
if (IsBlindFiring)
{
if (hands == 1) return "cover@weapon@1h";
if (hands == 2 || hands == 5) return "cover@weapon@2h";
}
if (hands == 1) return "cover@idles@1h@" + altitude + "@_a";
if (hands == 2 || hands == 5) return "cover@idles@2h@" + altitude + "@_a";
if (hands == 3 || hands == 4 || hands == 0) return "cover@idles@unarmed@" + altitude + "@_a";
return "";
}
internal int GetWeaponHandsHeld(uint weapon)
{
switch (weapon)
{
case (uint)WeaponHash.Unarmed:
return 0;
case (uint)WeaponHash.RPG:
case (uint)WeaponHash.HomingLauncher:
case (uint)WeaponHash.Firework:
return 5;
case (uint)WeaponHash.Minigun:
return 5;
case (uint)WeaponHash.GolfClub:
case (uint)WeaponHash.PoolCue:
case (uint)WeaponHash.Bat:
return 4;
case (uint)WeaponHash.Knife:
case (uint)WeaponHash.Nightstick:
case (uint)WeaponHash.Hammer:
case (uint)WeaponHash.Crowbar:
case (uint)WeaponHash.Wrench:
case (uint)WeaponHash.BattleAxe:
case (uint)WeaponHash.Dagger:
case (uint)WeaponHash.Hatchet:
case (uint)WeaponHash.KnuckleDuster:
case unchecked((uint)-581044007):
case unchecked((uint)-102323637):
case unchecked((uint)-538741184):
return 3;
case unchecked((uint)-1357824103):
case unchecked((uint)-1074790547):
case 2132975508:
case unchecked((uint)-2084633992):
case unchecked((uint)-952879014):
case 100416529:
case (uint)WeaponHash.Gusenberg:
case (uint)WeaponHash.MG:
case (uint)WeaponHash.CombatMG:
case (uint)WeaponHash.CombatPDW:
case (uint)WeaponHash.AssaultSMG:
case (uint)WeaponHash.SMG:
case (uint)WeaponHash.HeavySniper:
case (uint)WeaponHash.PumpShotgun:
case (uint)WeaponHash.HeavyShotgun:
case (uint)WeaponHash.Musket:
case (uint)WeaponHash.AssaultShotgun:
case (uint)WeaponHash.BullpupShotgun:
case (uint)WeaponHash.SawnOffShotgun:
case (uint)WeaponHash.SweeperShotgun:
case (uint)WeaponHash.CompactRifle:
return 2;
}
return 1;
}
private string LoadAnim(string anim)
{
if (!Call<bool>(HAS_ANIM_DICT_LOADED, anim))
{
Call(REQUEST_ANIM_DICT, anim);
return null;
}
return anim;
}
}
}

View File

@ -0,0 +1,81 @@
using System.Collections.Generic;
using GTA;
using GTA.Math;
using LemonUI.Elements;
using RageCoop.Core;
namespace RageCoop.Client
{
/// <summary>
/// ?
/// </summary>
public partial class SyncedPed : SyncedEntity
{
private readonly string[] _currentAnimation = new string[2] { "", "" };
private byte[] _lastClothes;
private bool _lastDriveBy;
private bool _lastInCover;
private bool _lastIsJumping;
private WeaponHash _lastWeaponHash;
private bool _lastMoving;
private bool _lastRagdoll;
private ulong _lastRagdollTime;
private Dictionary<uint, bool> _lastWeaponComponents;
private ScaledText _nameTag;
internal Entity WeaponObj;
internal BlipColor BlipColor = (BlipColor)255;
internal float BlipScale = 1;
internal BlipSprite BlipSprite = 0;
internal PedDataFlags Flags;
internal Blip PedBlip = null;
internal VehicleSeat Seat;
internal int VehicleID
{
get => CurrentVehicle?.ID ?? 0;
set
{
if (CurrentVehicle == null || value != CurrentVehicle?.ID)
CurrentVehicle = EntityPool.GetVehicleByID(value);
}
}
internal SyncedVehicle CurrentVehicle { get; private set; }
public bool IsPlayer => OwnerID == ID && ID != 0;
public Ped MainPed { get; internal set; }
internal int Health;
internal Vector3 HeadPosition;
internal Vector3 RightFootPosition;
internal Vector3 LeftFootPosition;
internal byte WeaponTint;
internal byte[] Clothes;
internal float Heading;
internal ulong LastSpeakingTime { get; set; } = 0;
internal bool IsSpeaking { get; set; } = false;
public byte Speed { get; set; }
internal bool IsAiming => Flags.HasPedFlag(PedDataFlags.IsAiming);
internal bool IsReloading => Flags.HasPedFlag(PedDataFlags.IsReloading);
internal bool IsJumping => Flags.HasPedFlag(PedDataFlags.IsJumping);
internal bool IsRagdoll => Flags.HasPedFlag(PedDataFlags.IsRagdoll);
internal bool IsOnFire => Flags.HasPedFlag(PedDataFlags.IsOnFire);
internal bool IsInParachuteFreeFall => Flags.HasPedFlag(PedDataFlags.IsInParachuteFreeFall);
internal bool IsParachuteOpen => Flags.HasPedFlag(PedDataFlags.IsParachuteOpen);
internal bool IsOnLadder => Flags.HasPedFlag(PedDataFlags.IsOnLadder);
internal bool IsVaulting => Flags.HasPedFlag(PedDataFlags.IsVaulting);
internal bool IsInCover => Flags.HasPedFlag(PedDataFlags.IsInCover);
internal bool IsInLowCover => Flags.HasPedFlag(PedDataFlags.IsInLowCover);
internal bool IsInCoverFacingLeft => Flags.HasPedFlag(PedDataFlags.IsInCoverFacingLeft);
internal bool IsBlindFiring => Flags.HasPedFlag(PedDataFlags.IsBlindFiring);
internal bool IsInStealthMode => Flags.HasPedFlag(PedDataFlags.IsInStealthMode);
internal Prop ParachuteProp = null;
internal WeaponHash CurrentWeapon = WeaponHash.Unarmed;
internal VehicleWeaponHash VehicleWeapon = VehicleWeaponHash.Invalid;
internal Dictionary<uint, bool> WeaponComponents = null;
internal Vector3 AimCoords;
}
}

View File

@ -0,0 +1,711 @@
using System;
using System.Drawing;
using System.Linq;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.NaturalMotion;
using GTA.UI;
using LemonUI.Elements;
using RageCoop.Core;
using Font = GTA.UI.Font;
namespace RageCoop.Client
{
/// <summary>
/// ?
/// </summary>
public partial class SyncedPed : SyncedEntity
{
/// <summary>
/// Create a local entity (outgoing sync)
/// </summary>
/// <param name="p"></param>
internal SyncedPed(Ped p)
{
ID = EntityPool.RequestNewID();
p.CanWrithe = false;
p.IsOnlyDamagedByPlayer = false;
MainPed = p;
OwnerID = LocalPlayerID;
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
}
/// <summary>
/// Create an empty character with ID
/// </summary>
internal SyncedPed(int id)
{
ID = id;
LastSynced = Ticked;
}
internal override void Update()
{
if (Owner == null)
{
OwnerID = OwnerID;
return;
}
if (IsPlayer) RenderNameTag();
// Check if all data available
if (!IsReady) return;
// Skip update if no new sync message has arrived.
if (!NeedUpdate) return;
if (MainPed == null || !MainPed.Exists())
if (!CreateCharacter())
return;
if (LastFullSynced >= LastUpdated)
{
if (MainPed != null && Model != MainPed.Model.Hash)
if (!CreateCharacter())
return;
if (!Settings.ShowPlayerBlip && (byte)BlipColor != 255) BlipColor = (BlipColor)255;
if ((byte)BlipColor == 255 && PedBlip != null)
{
PedBlip.Delete();
PedBlip = null;
}
else if ((byte)BlipColor != 255 && PedBlip == null)
{
PedBlip = MainPed.AddBlip();
PedBlip.Color = BlipColor;
PedBlip.Sprite = BlipSprite;
PedBlip.Scale = BlipScale;
}
if (PedBlip != null)
{
if (PedBlip.Color != BlipColor) PedBlip.Color = BlipColor;
if (PedBlip.Sprite != BlipSprite) PedBlip.Sprite = BlipSprite;
if (IsPlayer) PedBlip.Name = Owner.Username;
}
if (!Clothes.SequenceEqual(_lastClothes)) SetClothes();
CheckCurrentWeapon();
}
if (MainPed.IsDead)
{
if (Health > 0)
{
if (IsPlayer)
MainPed.Resurrect();
else
SyncEvents.TriggerPedKilled(this);
}
}
else if (IsPlayer && MainPed.Health != Health)
{
MainPed.Health = Health;
if (Health <= 0 && !MainPed.IsDead)
{
MainPed.IsInvincible = false;
MainPed.Kill();
return;
}
}
if (Speed >= 4)
{
DisplayInVehicle();
}
else
{
if (MainPed.IsInVehicle())
{
MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut);
return;
}
DisplayOnFoot();
}
if (IsSpeaking)
{
if (Ticked - LastSpeakingTime < 10)
{
DisplaySpeaking(true);
}
else
{
DisplaySpeaking(false);
IsSpeaking = false;
LastSpeakingTime = 0;
}
}
LastUpdated = Ticked;
}
private void RenderNameTag()
{
if (!Owner.DisplayNameTag || !Settings.ShowPlayerNameTag || MainPed == null || !MainPed.IsVisible)
return;
float dist = Call<Vector3>(GET_GAMEPLAY_CAM_COORD).DistanceToSquared(MainPed.Position);
if (dist > 10 * 10f)
return;
float scale = 1f - (0.8f * (float)Math.Sqrt(dist)) / 20f;
float fontSize = 0.6f * scale;
float frameTime = Call<float>(GET_FRAME_TIME);
Vector3 headPos = MainPed.Bones[Bone.IKHead].Position;
headPos.Z += 0.5f;
headPos += Velocity * frameTime;
Point toDraw = default;
if (Util.WorldToScreen(headPos, ref toDraw))
{
_nameTag ??= new ScaledText(toDraw, Owner.Username, fontSize, Font.ChaletLondon)
{
Alignment = Alignment.Center,
Outline = true
};
_nameTag.Position = toDraw;
_nameTag.Scale = fontSize;
_nameTag.Color = Owner.HasDirectConnection ? Color.FromArgb(179, 229, 252) : Color.White;
_nameTag.Draw();
}
}
private bool CreateCharacter()
{
if (MainPed != null)
{
if (MainPed.Exists())
{
// Log.Debug($"Removing ped {ID}. Reason:CreateCharacter");
MainPed.Kill();
MainPed.MarkAsNoLongerNeeded();
MainPed.Delete();
}
MainPed = null;
}
if (PedBlip != null && PedBlip.Exists())
{
PedBlip.Delete();
PedBlip = null;
}
if (!Model.IsLoaded)
{
Model.Request();
return false;
}
if ((MainPed = Util.CreatePed(Model, Position)) == null) return false;
Model.MarkAsNoLongerNeeded();
MainPed.BlockPermanentEvents = true;
MainPed.CanWrithe = false;
MainPed.CanBeDraggedOutOfVehicle = true;
MainPed.IsOnlyDamagedByPlayer = false;
MainPed.RelationshipGroup = SyncedPedsGroup;
MainPed.IsFireProof = false;
MainPed.IsExplosionProof = false;
Call(SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
Call(SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
Call(SET_PED_CAN_BE_TARGETTED_BY_PLAYER, MainPed.Handle, Game.Player, true);
Call(SET_PED_GET_OUT_UPSIDE_DOWN_VEHICLE, MainPed.Handle, false);
Call(SET_CAN_ATTACK_FRIENDLY, MainPed.Handle, true, true);
// Call(_SET_PED_CAN_PLAY_INJURED_ANIMS, false);
Call(SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableExplosionReactions, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_AvoidTearGas, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_IgnoreBeingOnFire, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableEvasiveDives, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisablePanicInVehicle, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_BlockNonTemporaryEvents, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableShockingEvents, true);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
SetClothes();
if (IsPlayer) MainPed.IsInvincible = true;
if (IsInvincible) MainPed.IsInvincible = true;
lock (EntityPool.PedsLock)
{
// Add to EntityPool so this Character can be accessed by handle.
EntityPool.Add(this);
}
return true;
}
private void SetClothes()
{
for (byte i = 0; i < 12; i++)
Call(SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i],
(int)Clothes[i + 12], (int)Clothes[i + 24]);
_lastClothes = Clothes;
}
private void DisplayOnFoot()
{
if (IsInParachuteFreeFall)
{
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
MainPed.Rotation = Rotation;
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
{
// Skip update if animation is not loaded
var dict = LoadAnim("skydive@base");
if (dict == null) return;
Call(TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
}
return;
}
if (IsParachuteOpen)
{
if (ParachuteProp == null)
{
Model model = 1740193300;
model.Request(1000);
if (model != null)
{
ParachuteProp = World.CreateProp(model, MainPed.ReadPosition(), MainPed.ReadRotation(), false,
false);
model.MarkAsNoLongerNeeded();
ParachuteProp.IsPositionFrozen = true;
ParachuteProp.IsCollisionEnabled = false;
ParachuteProp.AttachTo(MainPed.Bones[Bone.SkelSpine2], new Vector3(3.6f, 0f, 0f),
new Vector3(0f, 90f, 0f));
}
MainPed.Task.ClearAllImmediately();
MainPed.Task.ClearSecondary();
}
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
MainPed.Rotation = Rotation;
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person",
"chute_idle_right", 3))
{
var dict = LoadAnim("skydive@parachute@first_person");
if (dict == null) return;
Call(TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
}
return;
}
if (ParachuteProp != null)
{
if (ParachuteProp.Exists()) ParachuteProp.Delete();
ParachuteProp = null;
}
if (IsOnLadder)
{
if (Velocity.Z < 0)
{
var anim = Velocity.Z < -2f ? "slide_climb_down" : "climb_down";
if (_currentAnimation[1] != anim)
{
MainPed.Task.ClearAllImmediately();
_currentAnimation[1] = anim;
}
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", anim, 3))
MainPed.Task.PlayAnimation("laddersbase", anim, 8f, -1, AnimationFlags.Loop);
}
else
{
if (Math.Abs(Velocity.Z) < 0.5)
{
if (_currentAnimation[1] != "base_left_hand_up")
{
MainPed.Task.ClearAllImmediately();
_currentAnimation[1] = "base_left_hand_up";
}
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase",
"base_left_hand_up", 3))
MainPed.Task.PlayAnimation("laddersbase", "base_left_hand_up", 8f, -1, AnimationFlags.Loop);
}
else
{
if (_currentAnimation[1] != "climb_up")
{
MainPed.Task.ClearAllImmediately();
_currentAnimation[1] = "climb_up";
}
if (!Call<bool>(IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "laddersbase", "climb_up",
3)) MainPed.Task.PlayAnimation("laddersbase", "climb_up", 8f, -1, AnimationFlags.Loop);
}
}
SmoothTransition();
return;
}
if (MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
{
MainPed.Task.ClearAllImmediately();
_currentAnimation[1] = "";
}
if (IsVaulting)
{
if (!MainPed.IsVaulting) MainPed.Task.Climb();
SmoothTransition();
return;
}
if (!IsVaulting && MainPed.IsVaulting) MainPed.Task.ClearAllImmediately();
if (IsOnFire && !MainPed.IsOnFire)
Call(START_ENTITY_FIRE, MainPed);
else if (!IsOnFire && MainPed.IsOnFire) Call(STOP_ENTITY_FIRE, MainPed);
if (IsJumping)
{
if (!_lastIsJumping)
{
_lastIsJumping = true;
MainPed.Task.Jump();
}
SmoothTransition();
return;
}
_lastIsJumping = false;
if (IsRagdoll || Health == 0)
{
if (!MainPed.IsRagdoll) MainPed.Ragdoll();
SmoothTransition();
if (!_lastRagdoll)
{
_lastRagdoll = true;
_lastRagdollTime = Ticked;
}
return;
}
if (MainPed.IsRagdoll)
{
if (Speed == 0)
MainPed.CancelRagdoll();
else
MainPed.Task.ClearAllImmediately();
_lastRagdoll = false;
return;
}
if (IsReloading)
{
if (!MainPed.IsTaskActive(TaskType.CTaskReloadGun)) MainPed.Task.ReloadWeapon();
/*
if (!_isPlayingAnimation)
{
string[] reloadingAnim = MainPed.GetReloadingAnimation();
if (reloadingAnim != null)
{
_isPlayingAnimation = true;
_currentAnimation = reloadingAnim;
MainPed.Task.PlayAnimation(_currentAnimation[0], _currentAnimation[1], 8f, -1, AnimationFlags.AllowRotation | AnimationFlags.UpperBodyOnly);
}
}
*/
SmoothTransition();
}
else if (IsInCover)
{
if (!_lastInCover) Call(TASK_STAY_IN_COVER, MainPed.Handle);
_lastInCover = true;
if (IsAiming)
{
DisplayAiming();
_lastInCover = false;
}
else if (MainPed.IsInCover)
{
SmoothTransition();
}
}
else if (_lastInCover)
{
MainPed.Task.ClearAllImmediately();
_lastInCover = false;
}
else if (IsAiming)
{
DisplayAiming();
}
else if (MainPed.IsShooting)
{
MainPed.Task.ClearAllImmediately();
}
else
{
WalkTo();
}
}
private void CheckCurrentWeapon()
{
if (MainPed.VehicleWeapon != VehicleWeapon) MainPed.VehicleWeapon = VehicleWeapon;
var compChanged = WeaponComponents != null && WeaponComponents.Count != 0 && WeaponComponents != _lastWeaponComponents && !WeaponComponents.Compare(_lastWeaponComponents);
if (_lastWeaponHash != CurrentWeapon || compChanged)
{
if (_lastWeaponHash == WeaponHash.Unarmed && WeaponObj?.Exists() == true)
{
WeaponObj.Delete();
}
else
{
var model = Call<uint>(GET_WEAPONTYPE_MODEL, CurrentWeapon);
if (!Call<bool>(HAS_MODEL_LOADED, model))
{
Call(REQUEST_MODEL, model);
return;
}
if (WeaponObj?.Exists() == true)
WeaponObj.Delete();
MainPed.Weapons.RemoveAll();
WeaponObj = Entity.FromHandle(Call<int>(CREATE_WEAPON_OBJECT, CurrentWeapon, -1, Position.X, Position.Y, Position.Z, true, 0, 0));
}
if (compChanged)
{
foreach (var comp in WeaponComponents)
{
if (comp.Value)
{
Call(GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, WeaponObj.Handle, comp.Key);
}
}
_lastWeaponComponents = WeaponComponents;
}
Call(GIVE_WEAPON_OBJECT_TO_PED, WeaponObj.Handle, MainPed.Handle);
_lastWeaponHash = CurrentWeapon;
}
if (Call<int>(GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon) != WeaponTint)
Call<int>(SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeapon, WeaponTint);
}
private void DisplayAiming()
{
if (Velocity == default)
MainPed.Task.AimAt(AimCoords, 1000);
else
Call(TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
Position.X + Velocity.X, Position.Y + Velocity.Y, Position.Z + Velocity.Z,
AimCoords.X, AimCoords.Y, AimCoords.Z, 3f, false, 0x3F000000, 0x40800000, false, 512, false, 0);
SmoothTransition();
}
private void WalkTo()
{
Call(SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
var predictPosition = Predict(Position) + Velocity;
var range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
switch (Speed)
{
case 1:
if (!MainPed.IsWalking || range > 0.25f)
{
var nrange = range * 2;
if (nrange > 1.0f) nrange = 1.0f;
MainPed.Task.GoStraightTo(predictPosition);
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, nrange);
}
_lastMoving = true;
break;
case 2:
if (!MainPed.IsRunning || range > 0.50f)
{
MainPed.Task.RunTo(predictPosition, true);
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
}
_lastMoving = true;
break;
case 3:
if (!MainPed.IsSprinting || range > 0.75f)
{
Call(TASK_GO_STRAIGHT_TO_COORD, MainPed.Handle, predictPosition.X,
predictPosition.Y, predictPosition.Z, 3.0f, -1, 0.0f, 0.0f);
Call(SET_RUN_SPRINT_MULTIPLIER_FOR_PLAYER, MainPed.Handle, 1.49f);
Call(SET_PED_DESIRED_MOVE_BLEND_RATIO, MainPed.Handle, 1.0f);
}
_lastMoving = true;
break;
default:
if (_lastMoving)
{
MainPed.Task.StandStill(2000);
_lastMoving = false;
}
if (MainPed.IsTaskActive(TaskType.CTaskDiveToGround)) MainPed.Task.ClearAll();
break;
}
SmoothTransition();
}
private void SmoothTransition()
{
var localRagdoll = MainPed.IsRagdoll;
var predicted = Predict(Position);
var dist = predicted.DistanceTo(MainPed.ReadPosition());
if (IsOff(dist))
{
MainPed.PositionNoOffset = predicted;
return;
}
if (!(localRagdoll || MainPed.IsDead))
{
if (!IsAiming && !MainPed.IsGettingUp)
{
var cur = MainPed.Heading;
var diff = Heading - cur;
if (diff > 180)
diff -= 360;
else if (diff < -180) diff += 360;
MainPed.Heading = cur + diff / 2;
}
MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
}
else if (Ticked - _lastRagdollTime < 10)
{
}
else if (IsRagdoll)
{
var helper = new ApplyImpulseHelper(MainPed);
var head = MainPed.Bones[Bone.SkelHead];
var rightFoot = MainPed.Bones[Bone.SkelRightFoot];
var leftFoot = MainPed.Bones[Bone.SkelLeftFoot];
Vector3 amount;
// 20:head, 3:left foot, 6:right foot, 17:right hand,
amount = 20 * (Predict(HeadPosition) - head.Position);
if (amount.Length() > 50) amount = amount.Normalized * 50;
helper.EqualizeAmount = 1;
helper.PartIndex = 20;
helper.Impulse = amount;
helper.Start();
helper.Stop();
amount = 20 * (Predict(RightFootPosition) - rightFoot.Position);
if (amount.Length() > 50) amount = amount.Normalized * 50;
helper.EqualizeAmount = 1;
helper.PartIndex = 6;
helper.Impulse = amount;
helper.Start();
helper.Stop();
amount = 20 * (Predict(LeftFootPosition) - leftFoot.Position);
if (amount.Length() > 50) amount = amount.Normalized * 50;
helper.EqualizeAmount = 1;
helper.PartIndex = 3;
helper.Impulse = amount;
helper.Start();
helper.Stop();
}
else
{
// localRagdoll
var force = Velocity - MainPed.Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
if (force.Length() > 20) force = force.Normalized * 20;
MainPed.ApplyWorldForceCenterOfMass(force, ForceType.InternalImpulse, true);
}
}
private void DisplayInVehicle()
{
if (CurrentVehicle?.MainVehicle == null) return;
switch (Speed)
{
// In vehicle
case 4:
if (MainPed.CurrentVehicle != CurrentVehicle.MainVehicle || MainPed.SeatIndex != Seat ||
(!MainPed.IsSittingInVehicle() && !MainPed.IsBeingJacked))
MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat);
if (MainPed.IsOnTurretSeat())
Call(TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y,
AimCoords.Z);
// Drive-by
if (VehicleWeapon == VehicleWeaponHash.Invalid)
{
if (IsAiming)
{
Call(SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y,
AimCoords.Z);
if (!_lastDriveBy)
{
_lastDriveBy = true;
Call(TASK_DRIVE_BY, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z,
100000, 100, 1, FiringPattern.SingleShot);
}
}
else if (_lastDriveBy || MainPed.IsTaskActive(TaskType.CTaskAimGunVehicleDriveBy))
{
MainPed.Task.ClearAll();
_lastDriveBy = false;
}
}
break;
// Entering vehicle
case 5:
if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle ||
MainPed.GetSeatTryingToEnter() != Seat)
MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5,
EnterVehicleFlags.JackAnyone);
break;
// Leaving vehicle
case 6:
if (!MainPed.IsTaskActive(TaskType.CTaskExitVehicle))
MainPed.Task.LeaveVehicle(CurrentVehicle.Velocity.LengthSquared() > 5 * 5
? LeaveVehicleFlags.BailOut
: LeaveVehicleFlags.None);
break;
}
}
}
}

View File

@ -0,0 +1,131 @@
using System.Diagnostics;
using GTA;
using GTA.Math;
namespace RageCoop.Client
{
/// <summary>
/// </summary>
public abstract class SyncedEntity
{
private float _accumulatedOff;
private int _ownerID;
/// <summary>
/// Indicates whether the current player is responsible for syncing this entity.
/// </summary>
public bool IsLocal => OwnerID == LocalPlayerID;
/// <summary>
/// Network ID for this entity
/// </summary>
public int ID { get; internal set; }
/// <summary>
/// </summary>
public int OwnerID
{
get => _ownerID;
internal set
{
if (value == _ownerID && Owner != null) return;
_ownerID = value;
Owner = PlayerList.GetPlayer(value);
if (this is SyncedPed && Owner != null) Owner.Character = (SyncedPed)this;
}
}
internal virtual Player Owner { get; private set; }
/// <summary>
/// </summary>
public bool IsOutOfSync => Ticked - LastSynced > 200 && ID != 0;
internal bool IsReady => LastSynced > 0 || LastFullSynced == 0;
internal bool IsInvincible { get; set; } = false;
internal bool NeedUpdate => LastSynced >= LastUpdated;
public bool SendNextFrame { get; set; } = false;
public bool SendFullNextFrame { get; set; } = false;
internal Model Model { get; set; }
internal Vector3 Position { get; set; }
internal Vector3 Rotation { get; set; }
internal Quaternion Quaternion { get; set; }
internal Vector3 Velocity { get; set; }
internal abstract void Update();
internal void PauseUpdate(ulong frames)
{
LastUpdated = Ticked + frames;
}
protected Vector3 Predict(Vector3 input)
{
return Diff() + input;
}
protected Vector3 Diff()
{
return (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Velocity;
}
protected bool IsOff(float thisOff, float tolerance = 3, float limit = 30)
{
_accumulatedOff += thisOff - tolerance;
if (_accumulatedOff < 0)
{
_accumulatedOff = 0;
}
else if (_accumulatedOff >= limit)
{
_accumulatedOff = 0;
return true;
}
return false;
}
#region LAST STATE
public void SetLastSynced(bool full)
{
LastSyncInterval = LastSentStopWatch.ElapsedMilliseconds;
LastSynced = Ticked;
if (full)
{
LastFullSynced = Ticked;
}
LastSyncedStopWatch.Restart();
}
public Stopwatch LastSyncedStopWatch = new Stopwatch();
/// <summary>
/// The interval between last sync and the earlier one
/// </summary>
public long LastSyncInterval;
/// <summary>
/// Last time a new sync message arrived.
/// </summary>
public ulong LastSynced { get; set; } = 0;
/// <summary>
/// Last time a new sync message arrived.
/// </summary>
public ulong LastFullSynced { get; internal set; } = 0;
/// <summary>
/// Last time the local entity has been updated,
/// </summary>
public ulong LastUpdated { get; set; }
internal Stopwatch LastSentStopWatch { get; set; } = Stopwatch.StartNew();
#endregion
}
}

View File

@ -0,0 +1,138 @@
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Core;
namespace RageCoop.Client
{
internal class SyncedProjectile : SyncedEntity
{
public readonly Vector3 Origin;
private bool _firstSend;
public SyncedProjectile(Projectile p)
{
var owner = p.OwnerEntity;
if (owner == null)
{
IsValid = false;
return;
}
ID = EntityPool.RequestNewID();
MainProjectile = p;
Origin = p.Position;
if (EntityPool.PedsByHandle.TryGetValue(owner.Handle, out var shooter))
{
if (shooter.MainPed != null
&& (p.AttachedEntity == shooter.MainPed.Weapons.CurrentWeaponObject
|| p.AttachedEntity == shooter.MainPed))
{
// Reloading
IsValid = false;
return;
}
Shooter = shooter;
IsLocal = shooter.IsLocal;
}
else if (EntityPool.VehiclesByHandle.TryGetValue(owner.Handle, out var shooterVeh))
{
Shooter = shooterVeh;
IsLocal = shooterVeh.IsLocal;
}
else
{
IsValid = false;
}
}
public SyncedProjectile(int id)
{
ID = id;
IsLocal = false;
}
public ProjectileDataFlags Flags { private get; set; } = ProjectileDataFlags.None;
public bool IsValid { get; } = true;
public new bool IsLocal { get; }
public Projectile MainProjectile { get; set; }
public SyncedEntity Shooter { get; set; }
public bool Exploded => Flags.HasProjDataFlag(ProjectileDataFlags.Exploded);
internal override Player Owner => Shooter.Owner;
/// <summary>
/// Invalid property for projectile.
/// </summary>
private new int OwnerID
{
set { }
}
public WeaponHash WeaponHash { get; set; }
private WeaponAsset Asset { get; set; }
public void ExtractData(ref Packets.ProjectileSync p)
{
p.Position = MainProjectile.Position;
p.Velocity = MainProjectile.Velocity;
p.Rotation = MainProjectile.Rotation;
p.ID = ID;
p.ShooterID = Shooter.ID;
p.WeaponHash = (uint)MainProjectile.WeaponHash;
p.Flags = ProjectileDataFlags.None;
if (MainProjectile.IsDead) p.Flags |= ProjectileDataFlags.Exploded;
if (MainProjectile.AttachedEntity != null) p.Flags |= ProjectileDataFlags.IsAttached;
if (Shooter is SyncedVehicle) p.Flags |= ProjectileDataFlags.IsShotByVehicle;
if (_firstSend)
{
p.Flags |= ProjectileDataFlags.IsAttached;
_firstSend = false;
}
}
internal override void Update()
{
// Skip update if no new sync message has arrived.
if (!NeedUpdate) return;
if (MainProjectile == null || !MainProjectile.Exists())
{
CreateProjectile();
return;
}
MainProjectile.Velocity = Velocity + 10 * (Predict(Position) - MainProjectile.Position);
MainProjectile.Rotation = Rotation;
LastUpdated = Ticked;
}
private void CreateProjectile()
{
Asset = new WeaponAsset(WeaponHash);
if (!Asset.IsLoaded)
{
Asset.Request();
return;
}
if (Shooter == null) return;
Entity owner;
owner = (Shooter as SyncedPed)?.MainPed ?? (Entity)(Shooter as SyncedVehicle)?.MainVehicle;
Position = (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Shooter.Velocity +
Position;
var end = Position + Velocity;
Call(SHOOT_SINGLE_BULLET_BETWEEN_COORDS_IGNORE_ENTITY, Position.X, Position.Y, Position.Z,
end.X, end.Y, end.Z, 0, 1, WeaponHash, owner?.Handle ?? 0, 1, 0, -1);
var ps = World.GetAllProjectiles();
MainProjectile = ps[ps.Length - 1];
MainProjectile.Position = Position;
MainProjectile.Rotation = Rotation;
MainProjectile.Velocity = Velocity;
EntityPool.Add(this);
}
}
}

View File

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

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using GTA;
using GTA.Math;
using RageCoop.Core;
namespace RageCoop.Client
{
public partial class SyncedVehicle
{
public Vehicle MainVehicle { get; internal set; }
#region -- SYNC DATA --
internal Vector3 RotationVelocity { get; set; }
internal float SteeringAngle { get; set; }
internal float ThrottlePower { get; set; }
internal float BrakePower { get; set; }
internal float DeluxoWingRatio { get; set; } = -1;
internal byte LandingGear { get; set; }
internal VehicleRoofState RoofState { get; set; }
internal VehicleDamageModel DamageModel { get; set; }
internal (byte, byte) Colors { get; set; }
internal (int, int)[] Mods { get; set; }
internal float EngineHealth { get; set; }
internal VehicleLockStatus LockStatus { get; set; }
internal byte RadioStation = 255;
internal string LicensePlate { get; set; }
internal int Livery { get; set; } = -1;
internal byte HeadlightColor { get; set; } = 255;
internal VehicleDataFlags Flags { get; set; }
internal ushort ExtrasMask;
internal byte ToggleModsMask;
#endregion
#region FLAGS
internal bool EngineRunning => Flags.HasVehFlag(VehicleDataFlags.IsEngineRunning);
internal bool Transformed => Flags.HasVehFlag(VehicleDataFlags.IsTransformed);
internal bool HornActive => Flags.HasVehFlag(VehicleDataFlags.IsHornActive);
internal bool LightsOn => Flags.HasVehFlag(VehicleDataFlags.AreLightsOn);
internal bool BrakeLightsOn => Flags.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
internal bool HighBeamsOn => Flags.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
internal bool SireneActive => Flags.HasVehFlag(VehicleDataFlags.IsSirenActive);
internal bool IsDead => Flags.HasVehFlag(VehicleDataFlags.IsDead);
internal bool IsDeluxoHovering => Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering);
#endregion
#region FIXED-DATA
internal bool IsMotorcycle;
internal bool IsAircraft;
internal bool HasRocketBoost;
internal bool HasParachute;
internal bool HasRoof;
internal bool IsSubmarineCar;
internal bool IsDeluxo;
internal bool IsTrain;
internal ushort AvalibleExtras;
[DebugTunable]
static float RotCalMult = 10f;
#endregion
#region PRIVATE
private byte _lastToggleMods;
private (byte, byte) _lastVehicleColors;
private ushort _lastExtras;
private (int, int)[] _lastVehicleMods = Array.Empty<(int, int)>();
private bool _lastHornActive;
private bool _lastTransformed;
private int _lastLivery = -1;
private byte _lastHeadlightColor = 255;
private Vector3 _predictedPosition;
#endregion
#region OUTGOING
internal float LastNozzleAngle { get; set; }
internal float LastEngineHealth { get; set; }
internal Vector3 LastVelocity { get; set; }
#endregion
}
}

View File

@ -0,0 +1,340 @@
using System;
using System.Linq;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.UI;
using RageCoop.Core;
using static ICSharpCode.SharpZipLib.Zip.ExtendedUnixData;
namespace RageCoop.Client
{
/// <summary>
/// A synchronized vehicle instance
/// </summary>
public partial class SyncedVehicle : SyncedEntity
{
internal override void Update()
{
// Check if all data available
if (!IsReady || Owner == null) return;
// Check existence
if (MainVehicle == null || !MainVehicle.Exists() || MainVehicle.Model != Model)
if (!CreateVehicle())
return;
DisplayVehicle();
// Skip update if no new sync message has arrived.
if (!NeedUpdate) return;
if (SteeringAngle != MainVehicle.SteeringAngle)
MainVehicle.CustomSteeringAngle((float)(Math.PI / 180) * SteeringAngle);
MainVehicle.ThrottlePower = ThrottlePower;
MainVehicle.BrakePower = BrakePower;
if (IsDead)
{
if (MainVehicle.IsDead) return;
MainVehicle.Explode();
}
else
{
if (MainVehicle.IsDead)
WorldThread.Delay(() =>
{
if (MainVehicle.IsDead && !IsDead) MainVehicle.Repair();
}, 1000);
}
if (MainVehicle.IsOnFire)
{
if (!Flags.HasVehFlag(VehicleDataFlags.IsOnFire)) Call(STOP_ENTITY_FIRE, MainVehicle);
}
else if (Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
{
Call(START_ENTITY_FIRE, MainVehicle);
}
if (EngineRunning != MainVehicle.IsEngineRunning) MainVehicle.IsEngineRunning = EngineRunning;
if (LightsOn != MainVehicle.AreLightsOn) MainVehicle.AreLightsOn = LightsOn;
if (HighBeamsOn != MainVehicle.AreHighBeamsOn) MainVehicle.AreHighBeamsOn = HighBeamsOn;
if (IsAircraft)
{
if (LandingGear != (byte)MainVehicle.LandingGearState)
MainVehicle.LandingGearState = (VehicleLandingGearState)LandingGear;
}
else
{
if (MainVehicle.HasSiren && SireneActive != MainVehicle.IsSirenActive)
MainVehicle.IsSirenActive = SireneActive;
if (HornActive)
{
if (!_lastHornActive)
{
_lastHornActive = true;
MainVehicle.SoundHorn(99999);
}
}
else if (_lastHornActive)
{
_lastHornActive = false;
MainVehicle.SoundHorn(1);
}
if (HasRoof && MainVehicle.RoofState != RoofState) MainVehicle.RoofState = RoofState;
if (HasRocketBoost && Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive) !=
MainVehicle.IsRocketBoostActive)
MainVehicle.IsRocketBoostActive = Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive);
if (HasParachute && Flags.HasFlag(VehicleDataFlags.IsParachuteActive) &&
!MainVehicle.IsParachuteDeployed)
MainVehicle.StartParachuting(false);
if (IsSubmarineCar)
{
if (Transformed)
{
if (!_lastTransformed)
{
_lastTransformed = true;
Call(TRANSFORM_TO_SUBMARINE, MainVehicle.Handle, false);
}
}
else if (_lastTransformed)
{
_lastTransformed = false;
Call(TRANSFORM_TO_CAR, MainVehicle.Handle, false);
}
}
else if (IsDeluxo)
{
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
if (IsDeluxoHovering) MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
}
Call(SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
}
MainVehicle.LockStatus = LockStatus;
if (LastFullSynced >= LastUpdated)
{
if (Flags.HasVehFlag(VehicleDataFlags.Repaired)) MainVehicle.Repair();
if (Colors != _lastVehicleColors)
{
Call(SET_VEHICLE_COLOURS, MainVehicle, Colors.Item1, Colors.Item2);
_lastVehicleColors = Colors;
}
MainVehicle.EngineHealth = EngineHealth;
if (Mods != null && !Mods.SequenceEqual(_lastVehicleMods))
{
Call(SET_VEHICLE_MOD_KIT, MainVehicle, 0);
foreach (var mod in Mods) MainVehicle.Mods[(VehicleModType)mod.Item1].Index = mod.Item2;
_lastVehicleMods = Mods;
}
if (ToggleModsMask != _lastToggleMods)
{
for (int i = 0; i < 7; i++)
{
Call(TOGGLE_VEHICLE_MOD, MainVehicle.Handle, i + 17, (ToggleModsMask & (1 << i)) != 0);
}
_lastToggleMods = ToggleModsMask;
}
if (Call<string>(GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle) != LicensePlate)
Call(SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, LicensePlate);
if (_lastLivery != Livery)
{
Call(SET_VEHICLE_LIVERY, MainVehicle, Livery);
_lastLivery = Livery;
}
if (_lastHeadlightColor != HeadlightColor)
{
Call(SET_VEHICLE_XENON_LIGHT_COLOR_INDEX, MainVehicle.Handle, HeadlightColor);
_lastHeadlightColor = HeadlightColor;
}
MainVehicle.SetDamageModel(DamageModel);
if (MainVehicle.Handle == V?.Handle && Util.GetPlayerRadioIndex() != RadioStation)
Util.SetPlayerRadioIndex(MainVehicle.Handle, RadioStation);
if (_lastExtras != ExtrasMask)
{
for (int i = 1; i < 15; i++)
{
var flag = (ushort)(1 << i);
var hasExtra = (AvalibleExtras & (ushort)(1 << i)) != 0;
if (!hasExtra)
continue;
var on = (ExtrasMask & flag) != 0;
Call(SET_VEHICLE_EXTRA, MainVehicle.Handle, i, !on);
}
_lastExtras = ExtrasMask;
}
}
LastUpdated = Ticked;
}
private void DisplayVehicle()
{
_predictedPosition = Predict(Position);
var current = MainVehicle.ReadPosition();
var distSquared = current.DistanceToSquared(_predictedPosition);
var cali = _predictedPosition - current;
if (!IsTrain) cali += 0.5f * (Velocity - MainVehicle.Velocity);
if (distSquared > 10 * 10)
{
MainVehicle.Position = _predictedPosition;
MainVehicle.Velocity = Velocity;
MainVehicle.Quaternion = Quaternion;
return;
}
var stopped = Velocity == Vector3.Zero;
// Calibrate position
if (distSquared > 0.03 * 0.03)
{
if (IsTrain || distSquared > 20 * 20) MainVehicle.Velocity = Velocity + cali;
else MainVehicle.ApplyWorldForceCenterOfMass(cali, ForceType.InternalImpulse, true);
}
if (NeedUpdate || stopped)
{
// Calibrate rotation
var diff = Quaternion.Diff(MainVehicle.ReadQuaternion());
MainVehicle.WorldRotationVelocity = diff.ToEulerAngles() * RotCalMult;
}
}
private bool CreateVehicle()
{
MainVehicle?.Delete();
MainVehicle = Util.CreateVehicle(Model, Position);
if (!Model.IsInCdImage)
// GTA.UI.Notification.Show($"~r~(Vehicle)Model ({CurrentVehicleModelHash}) cannot be loaded!");
return false;
if (MainVehicle == null)
{
Model.Request();
return false;
}
lock (EntityPool.VehiclesLock)
{
EntityPool.Add(this);
}
MainVehicle.Quaternion = Quaternion;
if (MainVehicle.HasRoof) MainVehicle.RoofState = RoofState;
foreach (var w in MainVehicle.Wheels) w.Fix();
if (IsInvincible) MainVehicle.IsInvincible = true;
SetUpFixedData();
Model.MarkAsNoLongerNeeded();
return true;
}
#region -- CONSTRUCTORS --
/// <summary>
/// Create a local entity (outgoing sync)
/// </summary>
/// <param name="v"></param>
internal SyncedVehicle(Vehicle v)
{
ID = EntityPool.RequestNewID();
MainVehicle = v;
MainVehicle.CanPretendOccupants = false;
OwnerID = LocalPlayerID;
SetUpFixedData();
}
internal void SetUpFixedData()
{
if (MainVehicle == null) return;
IsAircraft = MainVehicle.IsAircraft;
IsMotorcycle = MainVehicle.IsMotorcycle;
HasRocketBoost = MainVehicle.HasRocketBoost;
HasParachute = MainVehicle.HasParachute;
HasRoof = MainVehicle.HasRoof;
IsSubmarineCar = MainVehicle.IsSubmarineCar;
IsDeluxo = MainVehicle.Model == 1483171323;
IsTrain = MainVehicle.IsTrain;
AvalibleExtras = 0;
for (int i = 1; i < 15; i++)
{
if (Call<bool>(DOES_EXTRA_EXIST, MainVehicle.Handle, i))
AvalibleExtras |= (ushort)(1 << i);
}
}
/// <summary>
/// Create an empty VehicleEntity
/// </summary>
internal SyncedVehicle()
{
}
internal SyncedVehicle(int id)
{
ID = id;
LastSynced = Ticked;
}
#endregion
#region -- PEDALING --
/*
* Thanks to @oldnapalm.
*/
private string PedalingAnimDict()
{
switch ((VehicleHash)Model)
{
case VehicleHash.Bmx:
return "veh@bicycle@bmx@front@base";
case VehicleHash.Cruiser:
return "veh@bicycle@cruiserfront@base";
case VehicleHash.Scorcher:
return "veh@bicycle@mountainfront@base";
default:
return "veh@bicycle@roadfront@base";
}
}
private string PedalingAnimName(bool fast)
{
return fast ? "fast_pedal_char" : "cruise_pedal_char";
}
private void StartPedalingAnim(bool fast)
{
MainVehicle.Driver?.Task.PlayAnimation(PedalingAnimDict(), PedalingAnimName(fast), 8.0f, -8.0f, -1,
AnimationFlags.Loop | AnimationFlags.Secondary, 1.0f);
}
private void StopPedalingAnim(bool fast)
{
MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast));
}
#endregion
}
}

View File

@ -0,0 +1,531 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Security.Cryptography;
using GTA;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using SHVDN;
namespace RageCoop.Client
{
internal static unsafe class EntityPool
{
public static object PedsLock = new object();
#region ACTIVE INSTANCES
public static Dictionary<int, SyncedPed> PedsByID = new Dictionary<int, SyncedPed>();
public static Dictionary<int, SyncedPed> PedsByHandle = new Dictionary<int, SyncedPed>();
public static Dictionary<int, SyncedVehicle> VehiclesByID = new Dictionary<int, SyncedVehicle>();
public static Dictionary<int, SyncedVehicle> VehiclesByHandle = new Dictionary<int, SyncedVehicle>();
public static Dictionary<int, SyncedProjectile> ProjectilesByID = new Dictionary<int, SyncedProjectile>();
public static Dictionary<int, SyncedProjectile> ProjectilesByHandle = new Dictionary<int, SyncedProjectile>();
public static Dictionary<int, SyncedProp> ServerProps = new Dictionary<int, SyncedProp>();
public static Dictionary<int, Blip> ServerBlips = new Dictionary<int, Blip>();
#endregion
#region LOCKS
public static object VehiclesLock = new object();
public static object ProjectilesLock = new object();
public static object PropsLock = new object();
public static object BlipsLock = new object();
#endregion
public static void Cleanup(bool keepPlayer = true, bool keepMine = true)
{
foreach (var ped in PedsByID.Values.ToArray())
{
if ((keepPlayer && ped.ID == LocalPlayerID) ||
(keepMine && ped.OwnerID == LocalPlayerID)) continue;
RemovePed(ped.ID);
}
PedsByID.Clear();
PedsByHandle.Clear();
foreach (var id in VehiclesByID.Keys.ToArray())
{
if (keepMine && VehiclesByID[id].OwnerID == LocalPlayerID) continue;
RemoveVehicle(id);
}
VehiclesByID.Clear();
VehiclesByHandle.Clear();
foreach (var p in ProjectilesByID.Values.ToArray())
if (p.Shooter.ID != LocalPlayerID && p.MainProjectile != null && p.MainProjectile.Exists())
p.MainProjectile.Delete();
ProjectilesByID.Clear();
ProjectilesByHandle.Clear();
foreach (var p in ServerProps.Values) p?.MainProp?.Delete();
ServerProps.Clear();
foreach (var b in ServerBlips.Values)
if (b.Exists())
b.Delete();
ServerBlips.Clear();
}
#region PEDS
public static SyncedPed GetPedByID(int id)
=> PedsByID.TryGetValue(id, out var p) ? p : null;
public static SyncedPed GetPedByHandle(int handle)
=> PedsByHandle.TryGetValue(handle, out var p) ? p : null;
public static List<int> GetPedIDs()
{
return new List<int>(PedsByID.Keys);
}
public static bool AddPlayer()
{
var p = Game.Player.Character;
// var clipset=p.Gender==Gender.Male? "MOVE_M@TOUGH_GUY@" : "MOVE_F@TOUGH_GUY@";
// Call(SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
var player = GetPedByID(LocalPlayerID);
if (player == null)
{
Log.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
var c = new SyncedPed(p);
LocalPlayerID = c.OwnerID = c.ID;
Add(c);
Log.Debug($"Local player ID is:{c.ID}");
PlayerList.SetPlayer(c.ID, Settings.Username);
return true;
}
if (player.MainPed != p)
{
// Player model changed
player.MainPed = p;
// Remove it from Handle_Characters
var pairs = PedsByHandle.Where(x => x.Value == player);
if (pairs.Any())
{
var pair = pairs.First();
// Re-add
PedsByHandle.Remove(pair.Key);
if (PedsByHandle.ContainsKey(p.Handle)) RemovePed(PedsByHandle[p.Handle].ID);
PedsByHandle.Add(p.Handle, player);
}
}
return false;
}
public static void Add(SyncedPed c)
{
if (PedsByID.ContainsKey(c.ID))
PedsByID[c.ID] = c;
else
PedsByID.Add(c.ID, c);
if (c.MainPed == null) return;
if (PedsByHandle.ContainsKey(c.MainPed.Handle))
PedsByHandle[c.MainPed.Handle] = c;
else
PedsByHandle.Add(c.MainPed.Handle, c);
if (c.IsLocal) API.Events.InvokePedSpawned(c);
}
public static void RemovePed(int id, string reason = "Cleanup")
{
if (PedsByID.ContainsKey(id))
{
var c = PedsByID[id];
var p = c.MainPed;
if (p != null)
{
if (PedsByHandle.ContainsKey(p.Handle)) PedsByHandle.Remove(p.Handle);
// Log.Debug($"Removing ped {c.ID}. Reason:{reason}");
p.AttachedBlip?.Delete();
p.Kill();
p.Model.MarkAsNoLongerNeeded();
p.MarkAsNoLongerNeeded();
p.Delete();
}
c.PedBlip?.Delete();
c.ParachuteProp?.Delete();
PedsByID.Remove(id);
if (c.IsLocal) API.Events.InvokePedDeleted(c);
}
}
#endregion
#region VEHICLES
public static SyncedVehicle GetVehicleByID(int id)
=> VehiclesByID.TryGetValue(id, out var v) ? v : null;
public static SyncedVehicle GetVehicleByHandle(int handle)
=> VehiclesByHandle.TryGetValue(handle, out var v) ? v : null;
public static List<int> GetVehicleIDs()
{
return new List<int>(VehiclesByID.Keys);
}
public static void Add(SyncedVehicle v)
{
if (VehiclesByID.ContainsKey(v.ID))
VehiclesByID[v.ID] = v;
else
VehiclesByID.Add(v.ID, v);
if (v.MainVehicle == null) return;
if (VehiclesByHandle.ContainsKey(v.MainVehicle.Handle))
VehiclesByHandle[v.MainVehicle.Handle] = v;
else
VehiclesByHandle.Add(v.MainVehicle.Handle, v);
if (v.IsLocal) API.Events.InvokeVehicleSpawned(v);
}
public static void RemoveVehicle(int id, string reason = "Cleanup")
{
if (VehiclesByID.ContainsKey(id))
{
var v = VehiclesByID[id];
var veh = v.MainVehicle;
if (veh != null)
{
if (VehiclesByHandle.ContainsKey(veh.Handle)) VehiclesByHandle.Remove(veh.Handle);
// Log.Debug($"Removing vehicle {v.ID}. Reason:{reason}");
veh.AttachedBlip?.Delete();
veh.Model.MarkAsNoLongerNeeded();
veh.MarkAsNoLongerNeeded();
veh.Delete();
}
VehiclesByID.Remove(id);
if (v.IsLocal) API.Events.InvokeVehicleDeleted(v);
}
}
#endregion
#region PROJECTILES
public static SyncedProjectile GetProjectileByID(int id)
=> ProjectilesByID.TryGetValue(id, out var p) ? p : null;
public static void Add(SyncedProjectile p)
{
if (!p.IsValid) return;
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank)
{
Networking.SendBullet(((SyncedVehicle)p.Shooter).MainVehicle.Driver.GetSyncEntity().ID, (uint)VehicleWeaponHash.Tank, p.Position + p.Velocity);
return;
}
if (ProjectilesByID.ContainsKey(p.ID))
ProjectilesByID[p.ID] = p;
else
ProjectilesByID.Add(p.ID, p);
if (p.MainProjectile == null) return;
if (ProjectilesByHandle.ContainsKey(p.MainProjectile.Handle))
ProjectilesByHandle[p.MainProjectile.Handle] = p;
else
ProjectilesByHandle.Add(p.MainProjectile.Handle, p);
}
public static void RemoveProjectile(int id, string reason)
{
if (ProjectilesByID.ContainsKey(id))
{
var sp = ProjectilesByID[id];
var p = sp.MainProjectile;
if (p != null)
{
if (ProjectilesByHandle.ContainsKey(p.Handle)) ProjectilesByHandle.Remove(p.Handle);
Log.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
p.Explode();
}
ProjectilesByID.Remove(id);
}
}
public static bool PedExists(int id)
{
return PedsByID.ContainsKey(id);
}
public static bool VehicleExists(int id)
{
return VehiclesByID.ContainsKey(id);
}
public static bool ProjectileExists(int id)
{
return ProjectilesByID.ContainsKey(id);
}
#endregion
#region SERVER OBJECTS
public static SyncedProp GetPropByID(int id)
=> ServerProps.TryGetValue(id, out var p) ? p : null;
public static Blip GetBlipByID(int id)
=> ServerBlips.TryGetValue(id, out var p) ? p : null;
#endregion
private static int vehStateIndex;
private static int pedStateIndex;
private static int vehStatesPerFrame;
private static int pedStatesPerFrame;
private static int i;
public static void DoSync()
{
UpdateTargets();
var allPeds = NativeMemory.GetPedHandles();
var allVehicles = NativeMemory.GetVehicleHandles();
var allProjectiles = NativeMemory.GetProjectileHandles();
vehStatesPerFrame = allVehicles.Length * 2 / (int)FPS + 1;
pedStatesPerFrame = allPeds.Length * 2 / (int)FPS + 1;
lock (ProjectilesLock)
{
foreach (var p in allProjectiles)
if (!ProjectilesByHandle.ContainsKey(p))
Add(new SyncedProjectile(Projectile.FromHandle(p)));
foreach (var p in ProjectilesByID.Values.ToArray())
// Outgoing sync
if (p.IsLocal)
{
if (p.MainProjectile.AttachedEntity == null)
{
// Prevent projectiles from exploding next to vehicle
if (p.WeaponHash == (WeaponHash)VehicleWeaponHash.Tank ||
(p.MainProjectile.OwnerEntity?.EntityType == EntityType.Vehicle &&
p.MainProjectile.Position.DistanceTo(p.Origin) < 2)) continue;
Networking.SendProjectile(p);
}
}
else // Incoming sync
{
if (p.Exploded || p.IsOutOfSync)
RemoveProjectile(p.ID, "OutOfSync | Exploded");
else
p.Update();
}
}
i = -1;
lock (PedsLock)
{
AddPlayer();
foreach (var p in allPeds)
{
var c = GetPedByHandle(p);
if (c == null && p != Game.Player.Character.Handle)
{
var type = Util.GetPopulationType(p);
if (allPeds.Length > Settings.WorldPedSoftLimit
&& type == EntityPopulationType.RandomAmbient
&& !Call<bool>(IS_PED_IN_ANY_VEHICLE, p, 0))
{
Util.DeleteEntity(p);
continue;
}
// Log.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
c = new SyncedPed((Ped)Entity.FromHandle(p));
Add(c);
}
}
var ps = PedsByID.Values.ToArray();
pedStateIndex += pedStatesPerFrame;
if (pedStateIndex >= ps.Length) pedStateIndex = 0;
foreach (var c in ps)
{
i++;
if (c.MainPed != null && !c.MainPed.Exists())
{
RemovePed(c.ID, "non-existent");
continue;
}
// Outgoing sync
if (c.IsLocal)
{
// event check
SyncEvents.Check(c);
Networking.SendPed(c, i - pedStateIndex < pedStatesPerFrame);
}
else // Incoming sync
{
c.Update();
if (c.IsOutOfSync) RemovePed(c.ID, "OutOfSync");
}
}
}
var check = Ticked % 100 == 0;
i = -1;
lock (VehiclesLock)
{
foreach (var veh in allVehicles)
if (!VehiclesByHandle.ContainsKey(veh))
{
var cveh = (Vehicle)Entity.FromHandle(veh);
if (cveh == null)
continue;
if (allVehicles.Length > Settings.WorldVehicleSoftLimit)
{
var type = cveh.PopulationType;
if (type == EntityPopulationType.RandomAmbient
|| type == EntityPopulationType.RandomParked)
{
foreach (var p in cveh.Occupants)
{
p.Delete();
var c = GetPedByHandle(p.Handle);
if (c != null) RemovePed(c.ID, "ThrottleTraffic");
}
cveh.Delete();
continue;
}
}
// Log.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
Add(new SyncedVehicle(cveh));
}
var vs = VehiclesByID.Values.ToArray();
vehStateIndex += vehStatesPerFrame;
if (vehStateIndex >= vs.Length) vehStateIndex = 0;
foreach (var v in vs)
{
i++;
if (v.MainVehicle != null && !v.MainVehicle.Exists())
{
RemoveVehicle(v.ID, "non-existent");
continue;
}
if (check) v.SetUpFixedData();
// Outgoing sync
if (v.IsLocal)
{
if (!v.MainVehicle.IsVisible) continue;
SyncEvents.Check(v);
Networking.SendVehicle(v, i - vehStateIndex < vehStatesPerFrame);
}
else // Incoming sync
{
v.Update();
if (v.IsOutOfSync) RemoveVehicle(v.ID, "OutOfSync");
}
}
}
Networking.Peer.FlushSendQueue();
}
private static void UpdateTargets()
{
Networking.Targets = new List<NetConnection>(PlayerList.Players.Count) { Networking.ServerConnection };
foreach (var p in PlayerList.Players.Values.ToArray())
if (p.HasDirectConnection && p.Position.DistanceTo(PlayerPosition) < 500)
Networking.Targets.Add(p.Connection);
}
public static void RemoveAllFromPlayer(int playerPedId)
{
foreach (var p in PedsByID.Values.ToArray())
if (p.OwnerID == playerPedId)
RemovePed(p.ID);
foreach (var v in VehiclesByID.Values.ToArray())
if (v.OwnerID == playerPedId)
RemoveVehicle(v.ID);
}
public static int RequestNewID()
{
var ID = 0;
while (ID == 0 || PedsByID.ContainsKey(ID) || VehiclesByID.ContainsKey(ID) ||
ProjectilesByID.ContainsKey(ID))
{
var rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
return ID;
}
public static string DumpDebug()
{
return $"\nID_Peds: {PedsByID.Count}" +
$"\nHandle_Peds: {PedsByHandle.Count}" +
$"\nID_Vehicles: {VehiclesByID.Count}" +
$"\nHandle_vehicles: {VehiclesByHandle.Count}" +
$"\nID_Projectiles: {ProjectilesByID.Count}" +
$"\nHandle_Projectiles: {ProjectilesByHandle.Count}" +
$"\npedStatesPerFrame: {pedStatesPerFrame}" +
$"\nvehStatesPerFrame: {vehStatesPerFrame}";
}
public static class ThreadSafe
{
public static void Add(SyncedVehicle v)
{
lock (VehiclesLock)
{
EntityPool.Add(v);
}
}
public static void Add(SyncedPed p)
{
lock (PedsLock)
{
EntityPool.Add(p);
}
}
public static void Add(SyncedProjectile sp)
{
lock (ProjectilesLock)
{
EntityPool.Add(sp);
}
}
}
}
}

View File

@ -0,0 +1,203 @@
using System;
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class SyncEvents
{
#region TRIGGER
public static void TriggerPedKilled(SyncedPed victim)
{
Networking.SendSync(new Packets.PedKilled { VictimID = victim.ID }, ConnectionChannel.SyncEvents);
}
public static void TriggerChangeOwner(int vehicleID, int newOwnerID)
{
Networking.SendSync(new Packets.OwnerChanged
{
ID = vehicleID,
NewOwnerID = newOwnerID
}, ConnectionChannel.SyncEvents, NetDeliveryMethod.ReliableOrdered);
}
public static void TriggerNozzleTransform(int vehID, bool hover)
{
Networking.SendSync(new Packets.NozzleTransform { VehicleID = vehID, Hover = hover },
ConnectionChannel.SyncEvents);
}
#endregion
#region HANDLE
public static ParticleEffectAsset CorePFXAsset = new ParticleEffectAsset("core");
private static void HandlePedKilled(Packets.PedKilled p)
{
EntityPool.GetPedByID(p.VictimID)?.MainPed?.Kill();
}
private static void HandleOwnerChanged(Packets.OwnerChanged p)
{
var v = EntityPool.GetVehicleByID(p.ID);
if (v == null) return;
v.OwnerID = p.NewOwnerID;
v.SetLastSynced(true);
v.Position = v.MainVehicle.Position;
v.Quaternion = v.MainVehicle.Quaternion;
}
private static void HandleNozzleTransform(Packets.NozzleTransform p)
{
EntityPool.GetVehicleByID(p.VehicleID)?.MainVehicle?.SetNozzleAngel(p.Hover ? 1 : 0);
}
private static void HandleBulletShot(int ownerID, uint weaponHash, Vector3 end)
{
var c = EntityPool.GetPedByID(ownerID);
var p = c?.MainPed;
if (p == null)
{
return;
// p = Game.Player.Character;
// Log.Warning("Failed to find owner for bullet");
}
var damage = (int)p.GetWeaponDamage(weaponHash);
// Some weapon hash has some firing issue, so we need to replace it with known good ones
weaponHash = WeaponUtil.GetWeaponFix(weaponHash);
// Request asset for muzzle flash
if (!CorePFXAsset.IsLoaded) CorePFXAsset.Request();
// Request asset for materialising the bullet
var asset = new WeaponAsset(weaponHash);
if (!asset.IsLoaded) asset.Request();
var vehWeap = p.VehicleWeapon;
bool isVeh = vehWeap != VehicleWeaponHash.Invalid;
var bone = isVeh ? c.MainPed.CurrentVehicle.GetMuzzleBone(vehWeap) : c.MainPed.GetMuzzleBone();
if (bone == null)
{
Log.Warning($"Failed to find muzzle bone for {(isVeh ? vehWeap : (WeaponHash)weaponHash)}, {(isVeh ? p.CurrentVehicle.DisplayName : "")}");
return;
}
World.ShootBullet(bone.Position, end, p, asset, damage);
World.CreateParticleEffectNonLooped(CorePFXAsset,
!isVeh && p.Weapons.Current.Components.GetSuppressorComponent().Active
? "muz_pistol_silencer"
: ((WeaponHash)weaponHash).GetFlashFX(isVeh), bone.Position, isVeh ? bone.GetRotation() : bone.Owner.Rotation);
}
public static void HandleEvent(PacketType type, NetIncomingMessage msg)
{
switch (type)
{
case PacketType.BulletShot:
{
var p = msg.GetPacket<Packets.BulletShot>();
HandleBulletShot(p.OwnerID, p.WeaponHash, p.EndPosition);
break;
}
case PacketType.OwnerChanged:
{
HandleOwnerChanged(msg.GetPacket<Packets.OwnerChanged>());
}
break;
case PacketType.PedKilled:
{
HandlePedKilled(msg.GetPacket<Packets.PedKilled>());
}
break;
case PacketType.NozzleTransform:
{
HandleNozzleTransform(msg.GetPacket<Packets.NozzleTransform>());
break;
}
}
Networking.Peer.Recycle(msg);
}
#endregion
#region CHECK EVENTS
public static void Check(SyncedPed c)
{
var subject = c.MainPed;
// Check bullets
if (subject.IsShooting && !subject.IsUsingProjectileWeapon())
{
var i = 0;
// Some weapon is not instant hit, so we may need to wait a few ticks to get the impact position
bool getBulletImpact()
{
var endPos = subject.LastWeaponImpactPosition;
var vehWeap = subject.VehicleWeapon;
if (vehWeap == VehicleWeaponHash.Invalid)
{
// Ped weapon sync
var pedWeap = subject.Weapons.Current.Hash;
if (endPos != default)
{
Networking.SendBullet(c.ID, (uint)pedWeap, endPos);
return true;
}
// Get impact in next tick
if (++i <= 5) return false;
// Exceeded maximum wait of 5 ticks, return (inaccurate) aim coordinate
endPos = subject.GetAimCoord();
Networking.SendBullet(c.ID, (uint)pedWeap, endPos);
return true;
}
else
{
// Veh weapon sync
if (endPos == default)
{
var veh = c.MainPed.CurrentVehicle;
var b = veh.GetMuzzleBone(vehWeap);
if (b == null)
{
Log.Warning($"Failed to find muzzle bone for {vehWeap}, {veh.DisplayName}");
return true;
}
endPos = b.Position + b.ForwardVector * 200;
}
Networking.SendBullet(c.ID, (uint)vehWeap, endPos);
return true;
}
}
if (!getBulletImpact()) API.QueueAction(getBulletImpact);
}
}
public static void Check(SyncedVehicle v)
{
if (v.MainVehicle == null || !v.MainVehicle.HasNozzle()) return;
if (v.LastNozzleAngle == 1 && v.MainVehicle.GetNozzleAngel() != 1)
TriggerNozzleTransform(v.ID, false);
else if (v.LastNozzleAngle == 0 && v.MainVehicle.GetNozzleAngel() != 0) TriggerNozzleTransform(v.ID, true);
v.LastNozzleAngle = v.MainVehicle.GetNozzleAngel();
}
#endregion
}
}

View File

@ -0,0 +1,99 @@
using System.Threading;
using NAudio.Wave;
namespace RageCoop.Client
{
internal static class Voice
{
private static bool _stopping = false;
private static WaveInEvent _waveIn;
private static readonly BufferedWaveProvider _waveProvider =
new BufferedWaveProvider(new WaveFormat(16000, 16, 1));
private static Thread _thread;
public static bool WasInitialized()
{
return _thread != null;
}
public static bool IsRecording()
{
return _waveIn != null;
}
public static void ClearAll()
{
_waveProvider.ClearBuffer();
StopRecording();
if (_thread != null && _thread.IsAlive)
{
_stopping = true;
_thread.Join();
_thread = null;
}
}
public static void StopRecording()
{
if (!IsRecording())
return;
_waveIn.StopRecording();
_waveIn.Dispose();
_waveIn = null;
}
public static void Init()
{
if (WasInitialized())
return;
// I tried without thread but the game will lag without
_thread = ThreadManager.CreateThread(() =>
{
while (!_stopping && !IsUnloading)
using (var wo = new WaveOutEvent())
{
wo.Init(_waveProvider);
wo.Play();
while (wo.PlaybackState == PlaybackState.Playing) Thread.Sleep(100);
}
},"Voice");
}
public static void StartRecording()
{
if (IsRecording())
return;
_waveIn = new WaveInEvent
{
DeviceNumber = 0,
BufferMilliseconds = 20,
NumberOfBuffers = 1,
WaveFormat = _waveProvider.WaveFormat
};
_waveIn.DataAvailable += WaveInDataAvailable;
_waveIn.StartRecording();
}
public static void AddVoiceData(byte[] buffer, int recorded)
{
_waveProvider.AddSamples(buffer, 0, recorded);
}
private static void WaveInDataAvailable(object sender, WaveInEventArgs e)
{
if (!IsRecording())
return;
Networking.SendVoiceMessage(e.Buffer, e.BytesRecorded);
}
}
}

View File

@ -0,0 +1,84 @@
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace RageCoop.Client
{
/// <summary>
/// Needed to properly stop all thread when the module unloads
/// </summary>
internal static class ThreadManager
{
private static readonly List<Thread> _threads = new();
private static readonly Thread _watcher = new(RemoveStopped);
static ThreadManager()
{
_watcher.Start();
}
private static void RemoveStopped()
{
while (!IsUnloading)
{
lock (_threads)
{
_threads.RemoveAll(t => !t.IsAlive);
}
Thread.Sleep(1000);
}
}
public static Thread CreateThread(Action callback, string name = "CoopThread", bool startNow = true)
{
lock (_threads)
{
var created = new Thread(() =>
{
try
{
callback();
}
catch (ThreadInterruptedException) { }
catch (Exception ex)
{
Log.Error($"Unhandled exception caught in thread {Environment.CurrentManagedThreadId}", ex);
}
finally
{
Log.Debug($"Thread stopped: " + Environment.CurrentManagedThreadId);
}
})
{
Name = name
};
Log.Debug($"Thread created: {name}, id: {created.ManagedThreadId}");
_threads.Add(created);
if (startNow) created.Start();
return created;
}
}
public static void OnUnload()
{
Log.Debug("Stopping background threads");
lock (_threads)
{
foreach (var thread in _threads)
{
if (thread.IsAlive)
{
Log.Debug($"Waiting for thread {thread.ManagedThreadId} to stop");
// thread.Interrupt(); PlatformNotSupportedException ?
thread.Join();
}
}
_threads.Clear();
}
Log.Debug("Stopping thread watcher");
_watcher.Join();
}
}
}

View File

@ -0,0 +1,9 @@
<linker>
<assembly fullname="RageCoop.Client" preserve="all" />
<assembly fullname="RageCoop.Client.Scripting" preserve="all" />
<assembly fullname="RageCoop.Core" preserve="all" />
<assembly fullname="System.Collections">
<type fullname="System.Collections.Generic.List`1" preserve="all" />
<type fullname="System.Collections.Generic.Dictionary`2" preserve="all" />
</assembly>
</linker>

View File

@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using GTA;
using GTA.Math;
using RageCoop.Core;
using NativeMemory = SHVDN.NativeMemory;
namespace RageCoop.Client
{
internal unsafe class MemPatch
{
private readonly IntPtr _address;
private readonly byte[] _data;
private readonly byte[] _orginal;
public MemPatch(byte* address, byte[] data)
{
_data = data;
_orginal = new byte[data.Length];
_address = (IntPtr)address;
Marshal.Copy((IntPtr)address, _orginal, 0, data.Length);
}
public void Install()
{
Marshal.Copy(_data, 0, _address, _data.Length);
}
public void Uninstall()
{
Marshal.Copy(_orginal, 0, _address, _orginal.Length);
}
}
internal static unsafe class Memory
{
public static MemPatch VignettingPatch;
public static MemPatch VignettingCallPatch;
public static MemPatch TimeScalePatch;
static Memory()
{
// Weapon/radio wheel slow-mo patch
// Thanks to @CamxxCore, https://github.com/CamxxCore/GTAVWeaponWheelMod
var result = NativeMemory.FindPattern("\x38\x51\x64\x74\x19", "xxxxx");
if (result == null)
throw new NotSupportedException("Can't find memory pattern to patch weapon/radio slowdown");
var address = result + 26;
address = address + *(int*)address + 4u;
VignettingPatch = new MemPatch(address, new byte[] { RET, 0x90, 0x90, 0x90, 0x90 });
VignettingCallPatch = new MemPatch(result + 8, new byte[] { 0x90, 0x90, 0x90, 0x90, 0x90 });
TimeScalePatch = new MemPatch(result + 34, new byte[] { XOR_32_64, 0xD2 });
}
public static void ApplyPatches()
{
VignettingPatch.Install();
VignettingCallPatch.Install();
TimeScalePatch.Install();
}
public static void RestorePatches()
{
VignettingPatch.Uninstall();
VignettingCallPatch.Uninstall();
TimeScalePatch.Uninstall();
}
public static Vector3 ReadPosition(this Entity e)
{
return ReadVector3(e.MemoryAddress + PositionOffset);
}
public static Quaternion ReadQuaternion(this Entity e)
{
return Quaternion.RotationMatrix(e.Matrix);
}
public static Vector3 ReadRotation(this Entity e)
{
return e.ReadQuaternion().ToEulerDegrees();
}
public static Vector3 ReadVelocity(this Ped e)
{
return ReadVector3(e.MemoryAddress + VelocityOffset);
}
public static Vector3 ReadVector3(IntPtr address)
{
var ptr = (float*)address.ToPointer();
return new Vector3
{
X = *ptr,
Y = ptr[1],
Z = ptr[2]
};
}
public static List<int> FindOffset(float toSearch, IntPtr start, int range = 1000, float tolerance = 0.01f)
{
var foundOffsets = new List<int>(100);
for (var i = 0; i <= range; i++)
{
var val = NativeMemory.ReadFloat(start + i);
if (Math.Abs(val - toSearch) < tolerance) foundOffsets.Add(i);
}
return foundOffsets;
}
#region PATCHES
#endregion
#region OFFSET-CONST
public const int PositionOffset = 144;
public const int VelocityOffset = 800;
public const int MatrixOffset = 96;
#endregion
#region OPCODE
private const byte XOR_32_64 = 0x31;
private const byte RET = 0xC3;
#endregion
}
}

View File

@ -0,0 +1,140 @@
using System;
using System.Runtime.InteropServices;
using GTA.Math;
using GTA.Native;
namespace RageCoop.Client
{
[StructLayout(LayoutKind.Explicit, Size = 80)]
public struct HeadBlendData
{
[FieldOffset(0)] public int ShapeFirst;
[FieldOffset(8)] public int ShapeSecond;
[FieldOffset(16)] public int ShapeThird;
[FieldOffset(24)] public int SkinFirst;
[FieldOffset(32)] public int SkinSecond;
[FieldOffset(40)] public int SkinThird;
[FieldOffset(48)] public float ShapeMix;
[FieldOffset(56)] public float SkinMix;
[FieldOffset(64)] public float ThirdMix;
}
[StructLayout(LayoutKind.Explicit, Size = 24)]
public struct NativeVector3
{
[FieldOffset(0)] public float X;
[FieldOffset(8)] public float Y;
[FieldOffset(16)] public float Z;
public static implicit operator Vector3(NativeVector3 vec)
{
return new Vector3 { X = vec.X, Y = vec.Y, Z = vec.Z };
}
public static implicit operator NativeVector3(Vector3 vec)
{
return new NativeVector3 { X = vec.X, Y = vec.Y, Z = vec.Z };
}
}
public static class NativeCaller
{
// These are borrowed from ScriptHookVDotNet's
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeInit@@YAX_K@Z")]
private static extern void NativeInit(ulong hash);
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativePush64@@YAX_K@Z")]
private static extern void NativePush64(ulong val);
[DllImport("ScriptHookV.dll", ExactSpelling = true, EntryPoint = "?nativeCall@@YAPEA_KXZ")]
private static extern unsafe ulong* NativeCall();
// These are from ScriptHookV's nativeCaller.h
private static unsafe void NativePush<T>(T val) where T : unmanaged
{
ulong val64 = 0;
*(T*)(&val64) = val;
NativePush64(val64);
}
public static unsafe R Invoke<R>(ulong hash) where R : unmanaged
{
NativeInit(hash);
return *(R*)NativeCall();
}
public static unsafe R Invoke<R>(Hash hash, params object[] args)
where R : unmanaged
{
NativeInit((ulong)hash);
var arguments = ConvertPrimitiveArguments(args);
foreach (var arg in arguments)
NativePush(arg);
return *(R*)NativeCall();
}
/// <summary>
/// Helper function that converts an array of primitive values to a native stack.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private static unsafe ulong[] ConvertPrimitiveArguments(object[] args)
{
var result = new ulong[args.Length];
for (var i = 0; i < args.Length; ++i)
{
if (args[i] is bool valueBool)
{
result[i] = valueBool ? 1ul : 0ul;
continue;
}
if (args[i] is byte valueByte)
{
result[i] = valueByte;
continue;
}
if (args[i] is int valueInt32)
{
result[i] = (ulong)valueInt32;
continue;
}
if (args[i] is ulong valueUInt64)
{
result[i] = valueUInt64;
continue;
}
if (args[i] is float valueFloat)
{
result[i] = *(ulong*)&valueFloat;
continue;
}
if (args[i] is IntPtr valueIntPtr)
{
result[i] = (ulong)valueIntPtr.ToInt64();
continue;
}
throw new ArgumentException("Unknown primitive type in native argument list", nameof(args));
}
return result;
}
}
}

View File

@ -0,0 +1,34 @@
global using static RageCoop.Client.PInvoke;
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace RageCoop.Client
{
internal partial class PInvoke
{
public static void ClearLastError()
{
SetLastErrorEx(0, 0);
}
/// <summary>
/// Check and throw if an error occurred during last winapi call in current thread
/// </summary>
public static void ErrorCheck32()
{
var error = Marshal.GetLastWin32Error();
if (error != 0)
{
ClearLastError();
throw new Win32Exception(error);
}
}
[LibraryImport("user32.dll", SetLastError = true)]
internal static partial void SetLastErrorEx(uint dwErrCode, uint dwType);
[LibraryImport("Kernel32.dll", SetLastError = true)]
public static unsafe partial uint GetFinalPathNameByHandleW(IntPtr hFile, char* lpszFilePath, uint cchFilePath, uint dwFlags);
}
}

View File

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

View File

@ -0,0 +1,418 @@
using System;
using System.Collections.Generic;
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class PedExtensions
{
public static bool IsBetween<T>(this T item, T start, T end)
{
return Comparer<T>.Default.Compare(item, start) >= 0 && Comparer<T>.Default.Compare(item, end) <= 0;
}
public static bool Compare<T, Y>(this Dictionary<T, Y> item, Dictionary<T, Y> item2)
{
if (item == null || item2 == null || item.Count != item2.Count) return false;
foreach (var pair in item)
{
if (item2.TryGetValue(pair.Key, out var value) && Equals(value, pair.Value)) continue;
// TryGetValue() or Equals failed
return false;
}
// No difference between item and item2
return true;
}
public static Vector3 RaycastEverything(Vector2 screenCoord)
{
var camPos = GameplayCamera.Position;
var camRot = GameplayCamera.Rotation;
const float raycastToDist = 100.0f;
const float raycastFromDist = 1f;
var target3D = ScreenRelToWorld(camPos, camRot, screenCoord);
var source3D = camPos;
Entity ignoreEntity = Game.Player.Character;
if (Game.Player.Character.IsInVehicle()) ignoreEntity = Game.Player.Character.CurrentVehicle;
var dir = target3D - source3D;
dir.Normalize();
var raycastResults = World.Raycast(source3D + dir * raycastFromDist,
source3D + dir * raycastToDist,
IntersectFlags.Everything,
ignoreEntity);
if (raycastResults.DidHit) return raycastResults.HitPosition;
return camPos + dir * raycastToDist;
}
public static Vector3 ScreenRelToWorld(Vector3 camPos, Vector3 camRot, Vector2 coord)
{
var camForward = camRot.ToDirection();
var rotUp = camRot + new Vector3(10, 0, 0);
var rotDown = camRot + new Vector3(-10, 0, 0);
var rotLeft = camRot + new Vector3(0, 0, -10);
var rotRight = camRot + new Vector3(0, 0, 10);
var camRight = rotRight.ToDirection() - rotLeft.ToDirection();
var camUp = rotUp.ToDirection() - rotDown.ToDirection();
double rollRad = -camRot.Y.ToRadians();
var camRightRoll = camRight * (float)Math.Cos(rollRad) - camUp * (float)Math.Sin(rollRad);
var camUpRoll = camRight * (float)Math.Sin(rollRad) + camUp * (float)Math.Cos(rollRad);
var point3D = camPos + camForward * 10.0f + camRightRoll + camUpRoll;
if (!WorldToScreenRel(point3D, out var point2D)) return camPos + camForward * 10.0f;
var point3DZero = camPos + camForward * 10.0f;
if (!WorldToScreenRel(point3DZero, out var point2DZero)) return camPos + camForward * 10.0f;
const double eps = 0.001;
if (Math.Abs(point2D.X - point2DZero.X) < eps || Math.Abs(point2D.Y - point2DZero.Y) < eps)
return camPos + camForward * 10.0f;
var scaleX = (coord.X - point2DZero.X) / (point2D.X - point2DZero.X);
var scaleY = (coord.Y - point2DZero.Y) / (point2D.Y - point2DZero.Y);
return camPos + camForward * 10.0f + camRightRoll * scaleX + camUpRoll * scaleY;
}
public static bool WorldToScreenRel(Vector3 worldCoords, out Vector2 screenCoords)
{
var num1 = new OutputArgument();
var num2 = new OutputArgument();
if (!Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, worldCoords.X, worldCoords.Y,
worldCoords.Z, num1, num2))
{
screenCoords = new Vector2();
return false;
}
screenCoords = new Vector2((num1.GetResult<float>() - 0.5f) * 2, (num2.GetResult<float>() - 0.5f) * 2);
return true;
}
public static void StayInCover(this Ped p)
{
Call(TASK_STAY_IN_COVER, p);
}
public static bool IsTurretSeat(this Vehicle veh, int seat)
{
if (Call<bool>(IS_TURRET_SEAT, veh, seat)) return true;
if (!Call<bool>(DOES_VEHICLE_HAVE_WEAPONS, veh.Handle)) return false;
switch (seat)
{
case -1:
return (VehicleHash)veh.Model.Hash == VehicleHash.Rhino
|| (VehicleHash)veh.Model.Hash == VehicleHash.Khanjari
|| (VehicleHash)veh.Model.Hash == VehicleHash.FireTruck
|| (VehicleHash)veh.Model.Hash == VehicleHash.Riot2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus3;
case 0:
return (VehicleHash)veh.Model.Hash == VehicleHash.Apc
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dune3;
case 1:
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Technical3
|| (VehicleHash)veh.Model.Hash == VehicleHash.HalfTrack
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
case 2:
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Barrage;
case 3:
return (VehicleHash)veh.Model.Hash == VehicleHash.Limo2
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dinghy5;
case 7:
return (VehicleHash)veh.Model.Hash == VehicleHash.Insurgent;
}
return false;
}
public static bool IsOnTurretSeat(this Ped P)
{
if (P.CurrentVehicle == null) return false;
return IsTurretSeat(P.CurrentVehicle, (int)P.SeatIndex);
}
#region PED
public static byte GetPedSpeed(this Ped ped)
{
if (ped.IsSittingInVehicle()) return 4;
if (ped.IsTaskActive(TaskType.CTaskEnterVehicle)) return 5;
if (ped.IsTaskActive(TaskType.CTaskExitVehicle)) return 6;
if (ped.IsWalking) return 1;
if (ped.IsRunning) return 2;
if (ped.IsSprinting) return 3;
return 0;
}
// Not sure whether component will always be lesser than 255, whatever...
public static byte[] GetPedClothes(this Ped ped)
{
var result = new byte[36];
for (byte i = 0; i < 12; i++)
{
result[i] = (byte)Call<short>(GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result[i + 12] = (byte)Call<short>(GET_PED_TEXTURE_VARIATION, ped.Handle, i);
result[i + 24] = (byte)Call<short>(GET_PED_PALETTE_VARIATION, ped.Handle, i);
}
return result;
}
public static PedDataFlags GetPedFlags(this Ped ped)
{
var flags = PedDataFlags.None;
if (ped.IsAiming || ped.IsOnTurretSeat()) flags |= PedDataFlags.IsAiming;
if (ped.IsReloading) flags |= PedDataFlags.IsReloading;
if (ped.IsJumping) flags |= PedDataFlags.IsJumping;
// Fake death
if (ped.IsRagdoll || (ped.Health == 1 && ped.IsPlayer)) flags |= PedDataFlags.IsRagdoll;
if (ped.IsOnFire) flags |= PedDataFlags.IsOnFire;
if (ped.IsInParachuteFreeFall) flags |= PedDataFlags.IsInParachuteFreeFall;
if (ped.ParachuteState == ParachuteState.Gliding) flags |= PedDataFlags.IsParachuteOpen;
var climbingLadder = ped.IsTaskActive(TaskType.CTaskGoToAndClimbLadder);
if (climbingLadder) flags |= PedDataFlags.IsOnLadder;
if (ped.IsVaulting && !climbingLadder) flags |= PedDataFlags.IsVaulting;
if (ped.IsInCover || ped.IsGoingIntoCover)
{
flags |= PedDataFlags.IsInCover;
if (ped.IsInCoverFacingLeft) flags |= PedDataFlags.IsInCover;
if (!Call<bool>(IS_PED_IN_HIGH_COVER, ped)) flags |= PedDataFlags.IsInLowCover;
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire)) flags |= PedDataFlags.IsBlindFiring;
}
if (ped.IsInvincible) flags |= PedDataFlags.IsInvincible;
if (Call<bool>(GET_PED_STEALTH_MOVEMENT, ped)) flags |= PedDataFlags.IsInStealthMode;
return flags;
}
public static string[] GetReloadingAnimation(this Ped ped)
{
switch (ped.Weapons.Current.Hash)
{
case WeaponHash.Revolver:
case WeaponHash.RevolverMk2:
case WeaponHash.DoubleActionRevolver:
case WeaponHash.NavyRevolver:
return new string[2] { "anim@weapons@pistol@revolver_str", "reload_aim" };
case WeaponHash.APPistol:
return new string[2] { "weapons@pistol@ap_pistol_str", "reload_aim" };
case WeaponHash.Pistol50:
return new string[2] { "weapons@pistol@pistol_50_str", "reload_aim" };
case WeaponHash.Pistol:
case WeaponHash.PistolMk2:
case WeaponHash.PericoPistol:
case WeaponHash.SNSPistol:
case WeaponHash.SNSPistolMk2:
case WeaponHash.HeavyPistol:
case WeaponHash.VintagePistol:
case WeaponHash.CeramicPistol:
case WeaponHash.MachinePistol:
return new string[2] { "weapons@pistol@pistol_str", "reload_aim" };
case WeaponHash.AssaultRifle:
case WeaponHash.AssaultrifleMk2:
return new string[2] { "weapons@rifle@hi@assault_rifle_str", "reload_aim" };
case WeaponHash.SniperRifle:
return new string[2] { "weapons@rifle@hi@sniper_rifle_str", "reload_aim" };
case WeaponHash.HeavySniper:
case WeaponHash.HeavySniperMk2:
return new string[2] { "weapons@rifle@lo@sniper_heavy_str", "reload_aim" };
case WeaponHash.PumpShotgun:
case WeaponHash.PumpShotgunMk2:
return new string[2] { "weapons@rifle@pump_str", "reload_aim" };
case WeaponHash.Railgun:
return new string[2] { "weapons@rifle@lo@rail_gun_str", "reload_aim" };
case WeaponHash.SawnOffShotgun:
return new string[2] { "weapons@rifle@lo@sawnoff_str", "reload_aim" };
case WeaponHash.AssaultShotgun:
return new string[2] { "weapons@rifle@lo@shotgun_assault_str", "reload_aim" };
case WeaponHash.BullpupShotgun:
return new string[2] { "weapons@rifle@lo@shotgun_bullpup_str", "reload_aim" };
case WeaponHash.AdvancedRifle:
return new string[2] { "weapons@submg@advanced_rifle_str", "reload_aim" };
case WeaponHash.CarbineRifle:
case WeaponHash.CarbineRifleMk2:
case WeaponHash.CompactRifle:
return new string[2] { "weapons@rifle@lo@carbine_str", "reload_aim" };
case WeaponHash.Gusenberg:
return new string[2] { "anim@weapons@machinegun@gusenberg_str", "reload_aim" };
case WeaponHash.Musket:
return new string[2] { "anim@weapons@musket@musket_str", "reload_aim" };
case WeaponHash.FlareGun:
return new string[2] { "anim@weapons@pistol@flare_str", "reload_aim" };
case WeaponHash.SpecialCarbine:
case WeaponHash.SpecialCarbineMk2:
return new string[2] { "anim@weapons@rifle@lo@spcarbine_str", "reload_aim" };
case WeaponHash.CombatPDW:
return new string[2] { "anim@weapons@rifle@lo@pdw_str", "reload_aim" };
case WeaponHash.BullpupRifle:
case WeaponHash.BullpupRifleMk2:
return new string[2] { "anim@weapons@submg@bullpup_rifle_str", "reload_aim" };
case WeaponHash.AssaultSMG:
return new string[2] { "weapons@submg@assault_smg_str", "reload_aim" };
case WeaponHash.MicroSMG:
case WeaponHash.MiniSMG:
return new string[2] { "weapons@submg@lo@micro_smg_str", "reload_aim" };
case WeaponHash.SMG:
case WeaponHash.SMGMk2:
return new string[2] { "weapons@rifle@smg_str", "reload_aim" };
case WeaponHash.GrenadeLauncher:
case WeaponHash.GrenadeLauncherSmoke:
case WeaponHash.CompactGrenadeLauncher:
return new string[2] { "weapons@heavy@grenade_launcher_str", "reload_aim" };
case WeaponHash.RPG:
case WeaponHash.Firework:
return new string[2] { "weapons@heavy@rpg_str", "reload_aim" };
case WeaponHash.CombatMG:
case WeaponHash.CombatMGMk2:
return new string[2] { "weapons@machinegun@combat_mg_str", "reload_aim" };
case WeaponHash.MG:
return new string[2] { "weapons@machinegun@mg_str", "reload_aim" };
default:
Log.Warning(
$"~r~Reloading failed! Weapon ~g~[{ped.Weapons.Current.Hash}]~r~ could not be found!");
return null;
}
}
static Dictionary<string, int> _vehicleDoors=new()
{
{ "door_dside_f", -1 },
{ "door_pside_f", 0 },
{ "door_dside_r", 1 },
{ "door_pside_r", 2 }
};
public static VehicleSeat GetNearestSeat(this Ped ped, Vehicle veh, float distanceToignoreDoors = 50f)
{
var num = 99f;
var result = -2;
foreach (var text in _vehicleDoors.Keys)
{
var flag = veh.Bones[text].Position != Vector3.Zero;
if (flag)
{
var num2 = ped.Position.DistanceTo(Call<Vector3>(GET_WORLD_POSITION_OF_ENTITY_BONE, veh, veh.Bones[text].Index));
var flag2 = num2 < distanceToignoreDoors && num2 < num &&
IsSeatUsableByPed(ped, veh, _vehicleDoors[text]);
if (flag2)
{
num = num2;
result = _vehicleDoors[text];
}
}
}
return (VehicleSeat)result;
}
public static bool IsSeatUsableByPed(Ped ped, Vehicle veh, int _seat)
{
var seat = (VehicleSeat)_seat;
var result = false;
var flag = veh.IsSeatFree(seat);
if (flag)
{
result = true;
}
else if (veh.GetPedOnSeat(seat) != null)
{
var isDead = veh.GetPedOnSeat(seat).IsDead;
if (isDead)
{
result = true;
}
else
{
var num = Call<int>(GET_RELATIONSHIP_BETWEEN_PEDS, ped, veh.GetPedOnSeat(seat));
var flag2 = num > 2;
if (flag2) result = true;
}
}
return result;
}
public static bool IsTaskActive(this Ped p, TaskType task)
{
return Call<bool>(GET_IS_TASK_ACTIVE, p.Handle, task);
}
public static Vector3 GetAimCoord(this Ped p)
{
Prop weapon;
EntityBone b;
if (p.IsOnTurretSeat())
{
if ((b = p.CurrentVehicle.GetMuzzleBone(p.VehicleWeapon)) != null)
return b.Position + b.ForwardVector * 50;
return GetLookingCoord(p);
}
if ((weapon = p.Weapons.CurrentWeaponObject) != null)
{
// Not very accurate, but doesn't matter
var dir = weapon.RightVector;
return weapon.Position + dir * 20;
}
return GetLookingCoord(p);
}
public static Vector3 GetLookingCoord(this Ped p)
{
if (p == P && Call<int>(GET_FOLLOW_PED_CAM_VIEW_MODE) == 4)
return RaycastEverything(default);
EntityBone b = p.Bones[Bone.FacialForehead];
var v = b.UpVector.Normalized;
return b.Position + 200 * v;
}
public static VehicleSeat GetSeatTryingToEnter(this Ped p)
{
return (VehicleSeat)Call<int>(GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
}
#endregion
}
}

View File

@ -0,0 +1,439 @@
// WARNING: values can change after a game update
// if R* adds in the middle!
// This is up-to-date for b2372
internal enum TaskType
{
CTaskHandsUp = 0,
CTaskClimbLadder = 1,
CTaskExitVehicle = 2,
CTaskCombatRoll = 3,
CTaskAimGunOnFoot = 4,
CTaskMovePlayer = 5,
CTaskPlayerOnFoot = 6,
CTaskWeapon = 8,
CTaskPlayerWeapon = 9,
CTaskPlayerIdles = 10,
CTaskAimGun = 12,
CTaskComplex = 12,
CTaskFSMClone = 12,
CTaskMotionBase = 12,
CTaskMove = 12,
CTaskMoveBase = 12,
CTaskNMBehaviour = 12,
CTaskNavBase = 12,
CTaskScenario = 12,
CTaskSearchBase = 12,
CTaskSearchInVehicleBase = 12,
CTaskShockingEvent = 12,
CTaskTrainBase = 12,
CTaskVehicleFSM = 12,
CTaskVehicleGoTo = 12,
CTaskVehicleMissionBase = 12,
CTaskVehicleTempAction = 12,
CTaskPause = 14,
CTaskDoNothing = 15,
CTaskGetUp = 16,
CTaskGetUpAndStandStill = 17,
CTaskFallOver = 18,
CTaskFallAndGetUp = 19,
CTaskCrawl = 20,
CTaskComplexOnFire = 25,
CTaskDamageElectric = 26,
CTaskTriggerLookAt = 28,
CTaskClearLookAt = 29,
CTaskSetCharDecisionMaker = 30,
CTaskSetPedDefensiveArea = 31,
CTaskUseSequence = 32,
CTaskMoveStandStill = 34,
CTaskComplexControlMovement = 35,
CTaskMoveSequence = 36,
CTaskAmbientClips = 38,
CTaskMoveInAir = 39,
CTaskNetworkClone = 40,
CTaskUseClimbOnRoute = 41,
CTaskUseDropDownOnRoute = 42,
CTaskUseLadderOnRoute = 43,
CTaskSetBlockingOfNonTemporaryEvents = 44,
CTaskForceMotionState = 45,
CTaskSlopeScramble = 46,
CTaskGoToAndClimbLadder = 47,
CTaskClimbLadderFully = 48,
CTaskRappel = 49,
CTaskVault = 50,
CTaskDropDown = 51,
CTaskAffectSecondaryBehaviour = 52,
CTaskAmbientLookAtEvent = 53,
CTaskOpenDoor = 54,
CTaskShovePed = 55,
CTaskSwapWeapon = 56,
CTaskGeneralSweep = 57,
CTaskPolice = 58,
CTaskPoliceOrderResponse = 59,
CTaskPursueCriminal = 60,
CTaskArrestPed = 62,
CTaskArrestPed2 = 63,
CTaskBusted = 64,
CTaskFirePatrol = 65,
CTaskHeliOrderResponse = 66,
CTaskHeliPassengerRappel = 67,
CTaskAmbulancePatrol = 68,
CTaskPoliceWantedResponse = 69,
CTaskSwat = 70,
CTaskSwatWantedResponse = 72,
CTaskSwatOrderResponse = 73,
CTaskSwatGoToStagingArea = 74,
CTaskSwatFollowInLine = 75,
CTaskWitness = 76,
CTaskGangPatrol = 77,
CTaskArmy = 78,
CTaskShockingEventWatch = 80,
CTaskShockingEventGoto = 82,
CTaskShockingEventHurryAway = 83,
CTaskShockingEventReactToAircraft = 84,
CTaskShockingEventReact = 85,
CTaskShockingEventBackAway = 86,
CTaskShockingPoliceInvestigate = 87,
CTaskShockingEventStopAndStare = 88,
CTaskShockingNiceCarPicture = 89,
CTaskShockingEventThreatResponse = 90,
CTaskTakeOffHelmet = 92,
CTaskCarReactToVehicleCollision = 93,
CTaskCarReactToVehicleCollisionGetOut = 95,
CTaskDyingDead = 97,
CTaskWanderingScenario = 100,
CTaskWanderingInRadiusScenario = 101,
CTaskMoveBetweenPointsScenario = 103,
CTaskChatScenario = 104,
CTaskCowerScenario = 106,
CTaskDeadBodyScenario = 107,
CTaskSayAudio = 114,
CTaskWaitForSteppingOut = 116,
CTaskCoupleScenario = 117,
CTaskUseScenario = 118,
CTaskUseVehicleScenario = 119,
CTaskUnalerted = 120,
CTaskStealVehicle = 121,
CTaskReactToPursuit = 122,
CTaskHitWall = 125,
CTaskCower = 126,
CTaskCrouch = 127,
CTaskMelee = 128,
CTaskMoveMeleeMovement = 129,
CTaskMeleeActionResult = 130,
CTaskMeleeUpperbodyAnims = 131,
CTaskMoVEScripted = 133,
CTaskScriptedAnimation = 134,
CTaskSynchronizedScene = 135,
CTaskComplexEvasiveStep = 137,
CTaskWalkRoundCarWhileWandering = 138,
CTaskComplexStuckInAir = 140,
CTaskWalkRoundEntity = 141,
CTaskMoveWalkRoundVehicle = 142,
CTaskReactToGunAimedAt = 144,
CTaskDuckAndCover = 146,
CTaskAggressiveRubberneck = 147,
CTaskInVehicleBasic = 150,
CTaskCarDriveWander = 151,
CTaskLeaveAnyCar = 152,
CTaskComplexGetOffBoat = 153,
CTaskCarSetTempAction = 155,
CTaskBringVehicleToHalt = 156,
CTaskCarDrive = 157,
CTaskPlayerDrive = 159,
CTaskEnterVehicle = 160,
CTaskEnterVehicleAlign = 161,
CTaskOpenVehicleDoorFromOutside = 162,
CTaskEnterVehicleSeat = 163,
CTaskCloseVehicleDoorFromInside = 164,
CTaskInVehicleSeatShuffle = 165,
CTaskExitVehicleSeat = 167,
CTaskCloseVehicleDoorFromOutside = 168,
CTaskControlVehicle = 169,
CTaskMotionInAutomobile = 170,
CTaskMotionOnBicycle = 171,
CTaskMotionOnBicycleController = 172,
CTaskMotionInVehicle = 173,
CTaskMotionInTurret = 174,
CTaskReactToBeingJacked = 175,
CTaskReactToBeingAskedToLeaveVehicle = 176,
CTaskTryToGrabVehicleDoor = 177,
CTaskGetOnTrain = 178,
CTaskGetOffTrain = 179,
CTaskRideTrain = 180,
CTaskMountThrowProjectile = 190,
CTaskGoToCarDoorAndStandStill = 195,
CTaskMoveGoToVehicleDoor = 196,
CTaskSetPedInVehicle = 197,
CTaskSetPedOutOfVehicle = 198,
CTaskVehicleMountedWeapon = 199,
CTaskVehicleGun = 200,
CTaskVehicleProjectile = 201,
CTaskSmashCarWindow = 204,
CTaskMoveGoToPoint = 205,
CTaskMoveAchieveHeading = 206,
CTaskMoveFaceTarget = 207,
CTaskComplexGoToPointAndStandStillTimed = 208,
CTaskMoveGoToPointAndStandStill = 208,
CTaskMoveFollowPointRoute = 209,
CTaskMoveSeekEntity_CEntitySeekPosCalculatorStandard = 210,
CTaskMoveSeekEntity_CEntitySeekPosCalculatorLastNavMeshIntersection = 211,
CTaskMoveSeekEntity_CEntitySeekPosCalculatorLastNavMeshIntersection2 = 212,
CTaskMoveSeekEntity_CEntitySeekPosCalculatorXYOffsetFixed = 213,
CTaskMoveSeekEntity_CEntitySeekPosCalculatorXYOffsetFixed2 = 214,
CTaskExhaustedFlee = 215,
CTaskGrowlAndFlee = 216,
CTaskScenarioFlee = 217,
CTaskSmartFlee = 218,
CTaskFlyAway = 219,
CTaskWalkAway = 220,
CTaskWander = 221,
CTaskWanderInArea = 222,
CTaskFollowLeaderInFormation = 223,
CTaskGoToPointAnyMeans = 224,
CTaskTurnToFaceEntityOrCoord = 225,
CTaskFollowLeaderAnyMeans = 226,
CTaskFlyToPoint = 228,
CTaskFlyingWander = 229,
CTaskGoToPointAiming = 230,
CTaskGoToScenario = 231,
CTaskSeekEntityAiming = 233,
CTaskSlideToCoord = 234,
CTaskSwimmingWander = 235,
CTaskMoveTrackingEntity = 237,
CTaskMoveFollowNavMesh = 238,
CTaskMoveGoToPointOnRoute = 239,
CTaskEscapeBlast = 240,
CTaskMoveWander = 241,
CTaskMoveBeInFormation = 242,
CTaskMoveCrowdAroundLocation = 243,
CTaskMoveCrossRoadAtTrafficLights = 244,
CTaskMoveWaitForTraffic = 245,
CTaskMoveGoToPointStandStillAchieveHeading = 246,
CTaskMoveGetOntoMainNavMesh = 251,
CTaskMoveSlideToCoord = 252,
CTaskMoveGoToPointRelativeToEntityAndStandStill = 253,
CTaskHelicopterStrafe = 254,
CTaskGetOutOfWater = 256,
CTaskMoveFollowEntityOffset = 259,
CTaskFollowWaypointRecording = 261,
CTaskMotionPed = 264,
CTaskMotionPedLowLod = 265,
CTaskHumanLocomotion = 268,
CTaskMotionBasicLocomotionLowLod = 269,
CTaskMotionStrafing = 270,
CTaskMotionTennis = 271,
CTaskMotionAiming = 272,
CTaskBirdLocomotion = 273,
CTaskFlightlessBirdLocomotion = 274,
CTaskFishLocomotion = 278,
CTaskQuadLocomotion = 279,
CTaskMotionDiving = 280,
CTaskMotionSwimming = 281,
CTaskMotionParachuting = 282,
CTaskMotionDrunk = 283,
CTaskRepositionMove = 284,
CTaskMotionAimingTransition = 285,
CTaskThrowProjectile = 286,
CTaskCover = 287,
CTaskMotionInCover = 288,
CTaskAimAndThrowProjectile = 289,
CTaskGun = 290,
CTaskAimFromGround = 291,
CTaskAimGunVehicleDriveBy = 295,
CTaskAimGunScripted = 296,
CTaskReloadGun = 298,
CTaskWeaponBlocked = 299,
CTaskEnterCover = 300,
CTaskExitCover = 301,
CTaskAimGunFromCoverIntro = 302,
CTaskAimGunFromCoverOutro = 303,
CTaskAimGunBlindFire = 304,
CTaskCombatClosestTargetInArea = 307,
CTaskCombatAdditionalTask = 308,
CTaskInCover = 309,
CTaskAimSweep = 313,
CTaskSharkCircle = 319,
CTaskSharkAttack = 320,
CTaskAgitated = 321,
CTaskAgitatedAction = 322,
CTaskConfront = 323,
CTaskIntimidate = 324,
CTaskShove = 325,
CTaskShoved = 326,
CTaskCrouchToggle = 328,
CTaskRevive = 329,
CTaskParachute = 335,
CTaskParachuteObject = 336,
CTaskTakeOffPedVariation = 337,
CTaskCombatSeekCover = 340,
CTaskCombatFlank = 342,
CTaskCombat = 343,
CTaskCombatMounted = 344,
CTaskMoveCircle = 345,
CTaskMoveCombatMounted = 346,
CTaskSearch = 347,
CTaskSearchOnFoot = 348,
CTaskSearchInAutomobile = 349,
CTaskSearchInBoat = 350,
CTaskSearchInHeli = 351,
CTaskThreatResponse = 352,
CTaskInvestigate = 353,
CTaskStandGuardFSM = 354,
CTaskPatrol = 355,
CTaskShootAtTarget = 356,
CTaskSetAndGuardArea = 357,
CTaskStandGuard = 358,
CTaskSeparate = 359,
CTaskStayInCover = 360,
CTaskVehicleCombat = 361,
CTaskVehiclePersuit = 362,
CTaskVehicleChase = 363,
CTaskDraggingToSafety = 364,
CTaskDraggedToSafety = 365,
CTaskVariedAimPose = 366,
CTaskMoveWithinAttackWindow = 367,
CTaskMoveWithinDefensiveArea = 368,
CTaskShootOutTire = 369,
CTaskShellShocked = 370,
CTaskBoatChase = 371,
CTaskBoatCombat = 372,
CTaskBoatStrafe = 373,
CTaskHeliChase = 374,
CTaskHeliCombat = 375,
CTaskSubmarineCombat = 376,
CTaskSubmarineChase = 377,
CTaskPlaneChase = 378,
CTaskTargetUnreachable = 379,
CTaskTargetUnreachableInInterior = 380,
CTaskTargetUnreachableInExterior = 381,
CTaskStealthKill = 382,
CTaskWrithe = 383,
CTaskAdvance = 384,
CTaskCharge = 385,
CTaskMoveToTacticalPoint = 386,
CTaskToHurtTransit = 387,
CTaskAnimatedHitByExplosion = 388,
CTaskNMRelax = 389,
CTaskNMPose = 391,
CTaskNMBrace = 392,
CTaskNMBuoyancy = 393,
CTaskNMInjuredOnGround = 394,
CTaskNMShot = 395,
CTaskNMHighFall = 396,
CTaskNMBalance = 397,
CTaskNMElectrocute = 398,
CTaskNMPrototype = 399,
CTaskNMExplosion = 400,
CTaskNMOnFire = 401,
CTaskNMScriptControl = 402,
CTaskNMJumpRollFromRoadVehicle = 403,
CTaskNMFlinch = 404,
CTaskNMSit = 405,
CTaskNMFallDown = 406,
CTaskBlendFromNM = 407,
CTaskNMControl = 408,
CTaskNMDangle = 409,
CTaskNMGenericAttach = 412,
CTaskNMDraggingToSafety = 414,
CTaskNMThroughWindscreen = 415,
CTaskNMRiverRapids = 416,
CTaskNMSimple = 417,
CTaskRageRagdoll = 418,
CTaskJumpVault = 421,
CTaskJump = 422,
CTaskFall = 423,
CTaskReactAimWeapon = 425,
CTaskChat = 426,
CTaskMobilePhone = 427,
CTaskReactToDeadPed = 428,
CTaskSearchForUnknownThreat = 430,
CTaskBomb = 432,
CTaskDetonator = 433,
CTaskAnimatedAttach = 435,
CTaskCutScene = 441,
CTaskReactToExplosion = 442,
CTaskReactToImminentExplosion = 443,
CTaskDiveToGround = 444,
CTaskReactAndFlee = 445,
CTaskSidestep = 446,
CTaskCallPolice = 447,
CTaskReactInDirection = 448,
CTaskReactToBuddyShot = 449,
CTaskVehicleGoToAutomobileNew = 454,
CTaskVehicleGoToPlane = 455,
CTaskVehicleGoToHelicopter = 456,
CTaskVehicleGoToSubmarine = 457,
CTaskVehicleGoToBoat = 458,
CTaskVehicleGoToPointAutomobile = 459,
CTaskVehicleGoToPointWithAvoidanceAutomobile = 460,
CTaskVehiclePursue = 461,
CTaskVehicleRam = 462,
CTaskVehicleSpinOut = 463,
CTaskVehicleApproach = 464,
CTaskVehicleThreePointTurn = 465,
CTaskVehicleDeadDriver = 466,
CTaskVehicleCruiseNew = 467,
CTaskVehicleCruiseBoat = 468,
CTaskVehicleStop = 469,
CTaskVehiclePullOver = 470,
CTaskVehiclePassengerExit = 471,
CTaskVehicleFlee = 472,
CTaskVehicleFleeAirborne = 473,
CTaskVehicleFleeBoat = 474,
CTaskVehicleFollowRecording = 475,
CTaskVehicleFollow = 476,
CTaskVehicleBlock = 477,
CTaskVehicleBlockCruiseInFront = 478,
CTaskVehicleBlockBrakeInFront = 479,
CTaskVehicleBlockBackAndForth = 478,
CTaskVehicleCrash = 481,
CTaskVehicleLand = 482,
CTaskVehicleLandPlane = 483,
CTaskVehicleHover = 484,
CTaskVehicleAttack = 485,
CTaskVehicleAttackTank = 486,
CTaskVehicleCircle = 487,
CTaskVehiclePoliceBehaviour = 488,
CTaskVehiclePoliceBehaviourHelicopter = 489,
CTaskVehiclePoliceBehaviourBoat = 490,
CTaskVehicleEscort = 491,
CTaskVehicleHeliProtect = 492,
CTaskVehiclePlayerDriveAutomobile = 494,
CTaskVehiclePlayerDriveBike = 495,
CTaskVehiclePlayerDriveBoat = 496,
CTaskVehiclePlayerDriveSubmarine = 497,
CTaskVehiclePlayerDriveSubmarineCar = 498,
CTaskVehiclePlayerDriveAmphibiousAutomobile = 499,
CTaskVehiclePlayerDrivePlane = 500,
CTaskVehiclePlayerDriveHeli = 501,
CTaskVehiclePlayerDriveAutogyro = 502,
CTaskVehiclePlayerDriveDiggerArm = 503,
CTaskVehiclePlayerDriveTrain = 504,
CTaskVehiclePlaneChase = 505,
CTaskVehicleNoDriver = 506,
CTaskVehicleAnimation = 507,
CTaskVehicleConvertibleRoof = 508,
CTaskVehicleParkNew = 509,
CTaskVehicleFollowWaypointRecording = 510,
CTaskVehicleGoToNavmesh = 511,
CTaskVehicleReactToCopSiren = 512,
CTaskVehicleGotoLongRange = 513,
CTaskVehicleWait = 514,
CTaskVehicleReverse = 515,
CTaskVehicleBrake = 516,
CTaskVehicleHandBrake = 517,
CTaskVehicleTurn = 518,
CTaskVehicleGoForward = 519,
CTaskVehicleSwerve = 520,
CTaskVehicleFlyDirection = 521,
CTaskVehicleHeadonCollision = 522,
CTaskVehicleBoostUseSteeringAngle = 523,
CTaskVehicleShotTire = 524,
CTaskVehicleBurnout = 525,
CTaskVehicleRevEngine = 526,
CTaskVehicleSurfaceInSubmarine = 527,
CTaskVehiclePullAlongside = 528,
CTaskVehicleTransformToSubmarine = 529,
CTaskAnimatedFallback = 530
}

275
Client/Scripts/Util/Util.cs Normal file
View File

@ -0,0 +1,275 @@
using System;
using System.Drawing;
using System.IO;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using GTA;
using GTA.Math;
using GTA.Native;
using GTA.UI;
using LemonUI.Elements;
using RageCoop.Core;
using static RageCoop.Client.Shared;
using Font = GTA.UI.Font;
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
namespace RageCoop.Client
{
internal static partial class Util
{
/// <summary>
/// The location of the cursor on screen between 0 and 1.
/// </summary>
public static PointF CursorPositionRelative
{
get
{
var cursorX = Game.IsControlEnabled(Control.CursorX)
? Game.GetControlValueNormalized(Control.CursorX)
: Game.GetDisabledControlValueNormalized(Control.CursorX);
var cursorY = Game.IsControlEnabled(Control.CursorY)
? Game.GetControlValueNormalized(Control.CursorY)
: Game.GetDisabledControlValueNormalized(Control.CursorY);
return new PointF(cursorX, cursorY);
}
}
public static Point CursorPosition
{
get
{
var p = CursorPositionRelative;
var res = Screen.Resolution;
return new Point((int)(p.X * res.Width), (int)(p.Y * res.Height));
}
}
public static SizeF ResolutionMaintainRatio
{
get
{
// Get the game width and height
var screenw = Screen.Resolution.Width;
var screenh = Screen.Resolution.Height;
// Calculate the ratio
var ratio = (float)screenw / screenh;
// And the width with that ratio
var width = 1080f * ratio;
// Finally, return a SizeF
return new SizeF(width, 1080f);
}
}
public static Vector3 GetRotation(this EntityBone b)
{
return b.ForwardVector.ToEulerRotation(b.UpVector);
}
public static void DrawTextFromCoord(Vector3 coord, string text, float scale = 0.5f, Point offset = default)
{
Point toDraw = default;
if (WorldToScreen(coord, ref toDraw))
{
toDraw.X += offset.X;
toDraw.Y += offset.Y;
new ScaledText(toDraw, text, scale, Font.ChaletLondon)
{
Outline = true,
Alignment = Alignment.Center,
Color = Color.White
}.Draw();
}
}
public static unsafe bool WorldToScreen(Vector3 pos, ref Point screenPos)
{
float x, y;
var res = ResolutionMaintainRatio;
if (Call<bool>(GET_SCREEN_COORD_FROM_WORLD_COORD, pos.X, pos.Y, pos.Z, &x, &y))
{
screenPos = new Point((int)(res.Width * x), (int)(y * 1080));
return true;
}
return false;
}
public static ClientSettings ReadSettings(string path = null)
{
path = path ?? SettingsPath;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
ClientSettings settings;
try
{
settings = JsonDeserialize<ClientSettings>(File.ReadAllText(path));
}
catch (Exception ex)
{
Log?.Error(ex);
File.WriteAllText(path, JsonSerialize(settings = new ClientSettings()));
}
return settings;
}
public static bool SaveSettings(string path = null, ClientSettings settings = null)
{
try
{
path = path ?? SettingsPath;
settings = settings ?? Settings;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
File.WriteAllText(path, JsonSerialize(settings));
return true;
}
catch (Exception ex)
{
Log?.Error(ex);
return false;
}
}
public static Vehicle CreateVehicle(Model model, Vector3 position, float heading = 0f)
{
if (!model.IsLoaded) return null;
return (Vehicle)Entity.FromHandle(Call<int>(CREATE_VEHICLE, model.Hash, position.X,
position.Y, position.Z, heading, false, false));
}
public static Ped CreatePed(Model model, Vector3 position, float heading = 0f)
{
if (!model.IsLoaded) return null;
return (Ped)Entity.FromHandle(Call<int>(CREATE_PED, 26, model.Hash, position.X, position.Y,
position.Z, heading, false, false));
}
public static void SetOnFire(this Entity e, bool toggle)
{
if (toggle)
Call(START_ENTITY_FIRE, e.Handle);
else
Call(STOP_ENTITY_FIRE, e.Handle);
}
public static void SetFrozen(this Entity e, bool toggle)
{
Call(FREEZE_ENTITY_POSITION, e, toggle);
}
public static SyncedPed GetSyncEntity(this Ped p)
{
if (p == null) return null;
var c = EntityPool.GetPedByHandle(p.Handle);
if (c == null) EntityPool.Add(c = new SyncedPed(p));
return c;
}
public static SyncedVehicle GetSyncEntity(this Vehicle veh)
{
if (veh == null) return null;
var v = EntityPool.GetVehicleByHandle(veh.Handle);
if (v == null) EntityPool.Add(v = new SyncedVehicle(veh));
return v;
}
/// <summary>
/// Get the current radio index for player, returns 255 if there's none
/// </summary>
/// <returns></returns>
public static byte GetPlayerRadioIndex()
{
return (byte)Call<int>(GET_PLAYER_RADIO_STATION_INDEX);
}
public static void SetPlayerRadioIndex(int playerVeh, int index)
{
if (playerVeh == 0)
playerVeh = Call<int>(GET_VEHICLE_PED_IS_IN, P.Handle, false);
if (index == byte.MaxValue)
Call(SET_VEH_RADIO_STATION, playerVeh, "OFF");
else
Call(SET_RADIO_TO_STATION_INDEX, index);
}
public static EntityPopulationType GetPopulationType(int handle)
=> (EntityPopulationType)Call<int>(GET_ENTITY_POPULATION_TYPE, handle);
public static unsafe void DeleteEntity(int handle)
{
Call(SET_ENTITY_AS_MISSION_ENTITY, handle, false, true);
Call(DELETE_ENTITY, &handle);
}
[LibraryImport("kernel32.dll")]
public static partial ulong GetTickCount64();
[LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
public static partial IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string lpModuleName);
#region -- POINTER --
private static int _steeringAngleOffset { get; set; }
public static unsafe void NativeMemory()
{
IntPtr address;
address = Game.FindPattern("\x74\x0A\xF3\x0F\x11\xB3\x1C\x09\x00\x00\xEB\x25", "xxxxxx????xx");
if (address != IntPtr.Zero) _steeringAngleOffset = *(int*)(address + 6) + 8;
}
public static unsafe void CustomSteeringAngle(this Vehicle veh, float value)
{
var address = new IntPtr((long)veh.MemoryAddress);
if (address == IntPtr.Zero || _steeringAngleOffset == 0) return;
*(float*)(address + _steeringAngleOffset).ToPointer() = value;
}
#endregion
#region MATH
public static Vector3 LinearVectorLerp(Vector3 start, Vector3 end, ulong currentTime, int duration)
{
return new Vector3
{
X = LinearFloatLerp(start.X, end.X, currentTime, duration),
Y = LinearFloatLerp(start.Y, end.Y, currentTime, duration),
Z = LinearFloatLerp(start.Z, end.Z, currentTime, duration)
};
}
public static float LinearFloatLerp(float start, float end, ulong currentTime, int duration)
{
return (end - start) * currentTime / duration + start;
}
public static float Lerp(float from, float to, float fAlpha)
{
return from * (1.0f - fAlpha) + to * fAlpha; //from + (to - from) * fAlpha
}
public static Vector3 RotationToDirection(Vector3 rotation)
{
var z = MathExtensions.DegToRad(rotation.Z);
var x = MathExtensions.DegToRad(rotation.X);
var num = Math.Abs(Math.Cos(x));
return new Vector3
{
X = (float)(-Math.Sin(z) * num),
Y = (float)(Math.Cos(z) * num),
Z = (float)Math.Sin(x)
};
}
#endregion
}
}

View File

@ -0,0 +1,221 @@
using System;
using System.Collections.Generic;
using GTA;
using GTA.Native;
using RageCoop.Core;
namespace RageCoop.Client
{
internal static class VehicleExtensions
{
#region VEHICLE
public static VehicleDataFlags GetVehicleFlags(this SyncedVehicle v)
{
var veh = v.MainVehicle;
VehicleDataFlags flags = 0;
if (veh.IsEngineRunning) flags |= VehicleDataFlags.IsEngineRunning;
if (veh.AreLightsOn) flags |= VehicleDataFlags.AreLightsOn;
if (veh.BrakePower >= 0.01f) flags |= VehicleDataFlags.AreBrakeLightsOn;
if (veh.AreHighBeamsOn) flags |= VehicleDataFlags.AreHighBeamsOn;
if (veh.IsSirenActive) flags |= VehicleDataFlags.IsSirenActive;
if (veh.IsDead) flags |= VehicleDataFlags.IsDead;
if (Call<bool>(IS_HORN_ACTIVE, veh.Handle)) flags |= VehicleDataFlags.IsHornActive;
if (v.IsSubmarineCar && Call<bool>(IS_VEHICLE_IN_SUBMARINE_MODE, veh.Handle))
flags |= VehicleDataFlags.IsTransformed;
if (v.IsAircraft) flags |= VehicleDataFlags.IsAircraft;
if (v.IsDeluxo && veh.IsDeluxoHovering()) flags |= VehicleDataFlags.IsDeluxoHovering;
if (v.HasRoof) flags |= VehicleDataFlags.HasRoof;
if (v.HasRocketBoost && veh.IsRocketBoostActive) flags |= VehicleDataFlags.IsRocketBoostActive;
if (v.HasParachute && veh.IsParachuteDeployed) flags |= VehicleDataFlags.IsParachuteActive;
if (veh.IsOnFire) flags |= VehicleDataFlags.IsOnFire;
return flags;
}
public static (int, int)[] GetVehicleMods(this SyncedVehicle sv, out byte togglesMask)
{
var modsArr = sv.MainVehicle.Mods.ToArray();
var result = new (int, int)[modsArr.Length];
for (int i = 0; i < modsArr.Length; i++)
{
result[i] = ((int)modsArr[i].Type, modsArr[i].Index);
}
togglesMask = 0;
for (int i = 0; i < 6; i++)
{
if (Call<bool>(IS_TOGGLE_MOD_ON, sv.MainVehicle.Handle, i + 17))
{
togglesMask |= (byte)(1 << i);
}
}
return result;
}
public static ushort GetVehicleExtras(this SyncedVehicle sv)
{
ushort result = 0;
for (int i = 1; i < 15; i++)
{
var flag = (ushort)(1 << i);
var hasExtra = (sv.AvalibleExtras & (ushort)(1 << i)) != 0;
if (hasExtra && Call<bool>(IS_VEHICLE_EXTRA_TURNED_ON, sv.MainVehicle.Handle, i))
result |= flag;
}
return result;
}
public static VehicleDamageModel GetVehicleDamageModel(this Vehicle veh)
{
// Broken windows
byte brokenWindows = 0;
for (var i = 0; i < 8; i++)
if (!veh.Windows[(VehicleWindowIndex)i].IsIntact)
brokenWindows |= (byte)(1 << i);
// Broken doors
byte brokenDoors = 0;
byte openedDoors = 0;
foreach (var door in veh.Doors)
if (door.IsBroken)
brokenDoors |= (byte)(1 << (byte)door.Index);
else if (door.IsOpen) openedDoors |= (byte)(1 << (byte)door.Index);
// Bursted tires
short burstedTires = 0;
foreach (var wheel in veh.Wheels.GetAllWheels())
if (wheel.IsBursted)
burstedTires |= (short)(1 << (int)wheel.BoneId);
return new VehicleDamageModel
{
BrokenDoors = brokenDoors,
OpenedDoors = openedDoors,
BrokenWindows = brokenWindows,
BurstedTires = burstedTires,
LeftHeadLightBroken = (byte)(veh.IsLeftHeadLightBroken ? 1 : 0),
RightHeadLightBroken = (byte)(veh.IsRightHeadLightBroken ? 1 : 0)
};
}
public static void SetDamageModel(this Vehicle veh, VehicleDamageModel model, bool leavedoors = true)
{
for (var i = 0; i < 8; i++)
{
var door = veh.Doors[(VehicleDoorIndex)i];
if ((model.BrokenDoors & (byte)(1 << i)) != 0)
{
if (!door.IsBroken) door.Break(leavedoors);
continue;
}
if (door.IsBroken)
{
// The vehicle can only fix a door if the vehicle was completely fixed
veh.Repair();
return;
}
if ((model.OpenedDoors & (byte)(1 << i)) != 0)
{
if (!door.IsOpen && !door.IsBroken) door.Open();
}
else if (door.IsOpen)
{
if (!door.IsBroken) door.Close();
}
if ((model.BrokenWindows & (byte)(1 << i)) != 0)
veh.Windows[(VehicleWindowIndex)i].Smash();
else if (!veh.Windows[(VehicleWindowIndex)i].IsIntact) veh.Windows[(VehicleWindowIndex)i].Repair();
}
foreach (var wheel in veh.Wheels)
if ((model.BurstedTires & (short)(1 << (int)wheel.BoneId)) != 0)
{
if (!wheel.IsBursted)
{
wheel.Puncture();
wheel.Burst();
}
}
else if (wheel.IsBursted)
{
wheel.Fix();
}
veh.IsLeftHeadLightBroken = model.LeftHeadLightBroken > 0;
veh.IsRightHeadLightBroken = model.RightHeadLightBroken > 0;
}
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
{
Call(SET_SPECIAL_FLIGHT_MODE_TARGET_RATIO, deluxo, hover ? 1f : 0f);
}
public static bool IsDeluxoHovering(this Vehicle deluxo)
{
return Math.Abs(deluxo.Bones[27].ForwardVector.GetCosTheta(deluxo.ForwardVector) - 1) > 0.05;
}
public static void SetDeluxoWingRatio(this Vehicle v, float ratio)
{
Call(SET_HOVER_MODE_WING_RATIO, v, ratio);
}
public static float GetDeluxoWingRatio(this Vehicle v)
{
return v.Bones[99].Position.DistanceTo(v.Bones[92].Position) - 1.43f;
}
public static float GetNozzleAngel(this Vehicle plane)
{
return Call<float>(GET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane);
}
public static bool HasNozzle(this Vehicle v)
{
switch (v.Model.Hash)
{
// Hydra
case 970385471:
return true;
// Avenger
case -2118308144:
return true;
// Tula
case 1043222410:
return true;
// Avenger
case 408970549:
return true;
}
return false;
}
public static void SetNozzleAngel(this Vehicle plane, float ratio)
{
Call(SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
}
#endregion
}
}

View File

@ -0,0 +1,196 @@
using System.Collections.Generic;
using System.IO;
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Client.Scripting;
using RageCoop.Core;
namespace RageCoop.Client
{
internal class WeaponFix
{
public Dictionary<uint, string> Bullet = new Dictionary<uint, string>();
public Dictionary<uint, string> Lazer = new Dictionary<uint, string>();
public Dictionary<uint, string> Others = new Dictionary<uint, string>();
}
internal static class WeaponUtil
{
public static Dictionary<uint, VehicleWeaponInfo> VehicleWeapons = new Dictionary<uint, VehicleWeaponInfo>();
public static WeaponFix WeaponFix;
public static Dictionary<uint, WeaponInfo> Weapons;
static WeaponUtil()
{
// Parse and load to memory
foreach (var w in JsonDeserialize<VehicleWeaponInfo[]>(
File.ReadAllText(VehicleWeaponDataPath))) VehicleWeapons.Add(w.Hash, w);
Weapons = JsonDeserialize<Dictionary<uint, WeaponInfo>>(
File.ReadAllText(WeaponInfoDataPath));
if (File.Exists(WeaponFixDataPath))
WeaponFix = JsonDeserialize<WeaponFix>(File.ReadAllText(WeaponFixDataPath));
else
Log.Warning("Weapon fix data not found");
}
public static void DumpWeaponFix(string path)
{
var P = Game.Player.Character;
var pos = P.Position + Vector3.WorldUp * 3;
var types = new HashSet<int> { 3 };
P.IsInvincible = true;
var fix = new WeaponFix();
foreach (var w in Weapons)
{
Console.PrintInfo("Testing " + w.Value.Name);
if (w.Value.FireType != "PROJECTILE")
{
var asset = new WeaponAsset(w.Key);
asset.Request(1000);
World.ShootBullet(pos, pos + Vector3.WorldUp, P, asset, 0, 1000);
if (!Call<bool>(IS_BULLET_IN_AREA, pos.X, pos.Y, pos.Z, 10f, true) &&
!Call<bool>(IS_PROJECTILE_IN_AREA, pos.X - 10, pos.Y - 10, pos.Z - 10, pos.X + 10,
pos.Y + 10, pos.Z + 10, true))
switch (w.Value.DamageType)
{
case "BULLET":
fix.Bullet.Add(w.Key, w.Value.Name);
break;
case "EXPLOSIVE":
fix.Lazer.Add(w.Key, w.Value.Name);
break;
default:
fix.Others.Add(w.Key, w.Value.Name);
break;
}
foreach (var p in World.GetAllProjectiles()) p.Delete();
Script.Wait(50);
}
}
File.WriteAllText(path, JsonSerialize(fix));
P.IsInvincible = false;
}
public static uint GetWeaponFix(uint hash)
{
if (WeaponFix.Bullet.TryGetValue(hash, out _)) return 0x461DDDB0;
if (WeaponFix.Lazer.TryGetValue(hash, out _)) return 0xE2822A29;
return hash;
}
public static Dictionary<uint, bool> GetWeaponComponents(this Weapon weapon)
{
Dictionary<uint, bool> result = null;
if (weapon.Components.Count > 0)
{
result = new Dictionary<uint, bool>();
foreach (var comp in weapon.Components) result.Add((uint)comp.ComponentHash, comp.Active);
}
return result;
}
public static EntityBone GetMuzzleBone(this Ped p)
{
return p.Weapons.CurrentWeaponObject?.Bones["gun_muzzle"];
}
public static float GetWeaponDamage(this Ped P, uint hash)
{
var comp = P.Weapons.Current.Components.GetSuppressorComponent();
return Call<float>(GET_WEAPON_DAMAGE, hash,
comp.Active ? comp.ComponentHash : WeaponComponentHash.Invalid);
}
public static int GetMuzzleIndex(this Vehicle v, VehicleWeaponHash hash)
{
if (VehicleWeapons.TryGetValue((uint)v.Model.Hash, out var veh) &&
veh.Weapons.TryGetValue((uint)hash, out var wp))
return (int)wp.Bones[CoreUtils.RandInt(0, wp.Bones.Length)].BoneIndex;
return -1;
}
public static EntityBone GetMuzzleBone(this Vehicle v, VehicleWeaponHash hash)
{
if ((uint)hash == 1422046295) hash = VehicleWeaponHash.WaterCannon;
var i = v.GetMuzzleIndex(hash);
if (i == -1) return null;
return v.Bones[i];
}
public static bool IsUsingProjectileWeapon(this Ped p)
{
var vp = p.VehicleWeapon;
return Weapons.TryGetValue(vp != VehicleWeaponHash.Invalid ? (uint)vp : (uint)p.Weapons.Current.Hash,
out var info)
&& info.FireType == "PROJECTILE";
}
public static string GetFlashFX(this WeaponHash w, bool veh)
{
if (veh)
switch ((VehicleWeaponHash)w)
{
case VehicleWeaponHash.Tank:
return "muz_tank";
default: return "muz_buzzard";
}
switch (w.GetWeaponGroup())
{
case WeaponGroup.SMG:
return "muz_smg";
case WeaponGroup.Shotgun:
return "muz_smg";
case WeaponGroup.AssaultRifle:
return "muz_assault_rifle";
case WeaponGroup.Pistol:
return "muz_pistol";
case WeaponGroup.Stungun:
return "muz_stungun";
case WeaponGroup.Heavy:
switch (w)
{
case WeaponHash.Minigun:
return "muz_minigun";
case WeaponHash.RPG:
return "muz_rpg";
default:
return "muz_minigun";
}
case WeaponGroup.Sniper:
return "muz_alternate_star";
case WeaponGroup.PetrolCan:
return "weap_petrol_can";
case WeaponGroup.FireExtinguisher:
return "weap_extinguisher";
default:
return "muz_assault_rifle";
}
}
public static WeaponGroup GetWeaponGroup(this WeaponHash hash)
{
return Call<WeaponGroup>(GET_WEAPONTYPE_GROUP, hash);
}
}
}

View File

@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using GTA;
using GTA.Native;
using GTA.UI;
using RageCoop.Client.Menus;
namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
[ScriptAttributes(Author = "RageCoop", SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class WorldThread : Script
{
public static Script Instance;
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
private static bool _trafficEnabled;
/// <summary>
/// Don't use it!
/// </summary>
public WorldThread()
{
Instance = this;
Aborted += (e) => { DoQueuedActions(); ChangeTraffic(true); };
}
protected override void OnStart()
{
base.OnStart();
while(Game.IsLoading)
Yield();
Notification.Show(NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!",
$"Press ~g~{Settings.MenuKey}~s~ to open the menu.");
}
protected override void OnTick()
{
base.OnTick();
if (Game.IsLoading) return;
try
{
CoopMenu.MenuPool.Process();
DoQueuedActions();
}
catch (Exception ex)
{
Log.Error(ex);
}
if (!Networking.IsOnServer) return;
Game.DisableControlThisFrame(Control.FrontendPause);
if (Settings.DisableAlternatePause) Game.DisableControlThisFrame(Control.FrontendPauseAlternate);
// Sets a value that determines how aggressive the ocean waves will be.
// Values of 2.0 or more make for very aggressive waves like you see during a thunderstorm.
Call(SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
if (Settings.ShowEntityOwnerName)
unsafe
{
int handle;
if (Call<bool>(GET_ENTITY_PLAYER_IS_FREE_AIMING_AT, 0, &handle))
{
var entity = Entity.FromHandle(handle);
if (entity != null)
{
var owner = "invalid";
if (entity.EntityType == EntityType.Vehicle)
owner = (entity as Vehicle).GetSyncEntity()?.Owner?.Username ?? "unknown";
if (entity.EntityType == EntityType.Ped)
owner = (entity as Ped).GetSyncEntity()?.Owner?.Username ?? "unknown";
Screen.ShowHelpTextThisFrame("Entity owner: " + owner);
}
}
}
if (!_trafficEnabled)
{
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
Call(SET_PED_POPULATION_BUDGET, 0);
Call(SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SET_RANDOM_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SET_PARKED_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
Call(SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
Call(SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
}
}
public static void Traffic(bool enable)
{
ChangeTraffic(enable);
_trafficEnabled = enable;
}
private static void ChangeTraffic(bool enable)
{
if (enable)
{
Call(REMOVE_SCENARIO_BLOCKING_AREAS);
Call(SET_CREATE_RANDOM_COPS, true);
Call(SET_RANDOM_TRAINS, true);
Call(SET_RANDOM_BOATS, true);
Call(SET_GARBAGE_TRUCKS, true);
Call(SET_PED_POPULATION_BUDGET, 3); // 0 - 3
Call(SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3
Call(SET_ALL_VEHICLE_GENERATORS_ACTIVE);
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
Call(SET_NUMBER_OF_PARKED_VEHICLES, -1);
Call(SET_DISTANT_CARS_ENABLED, true);
Call(DISABLE_VEHICLE_DISTANTLIGHTS, false);
}
else if (Networking.IsOnServer)
{
Call(ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f,
1000.0f, 0, 1, 1, 1);
Call(SET_CREATE_RANDOM_COPS, false);
Call(SET_RANDOM_TRAINS, false);
Call(SET_RANDOM_BOATS, false);
Call(SET_GARBAGE_TRUCKS, false);
Call(DELETE_ALL_TRAINS);
Call(SET_PED_POPULATION_BUDGET, 0);
Call(SET_VEHICLE_POPULATION_BUDGET, 0);
Call(SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
Call(SET_FAR_DRAW_VEHICLES, false);
Call(SET_NUMBER_OF_PARKED_VEHICLES, 0);
Call(SET_DISTANT_CARS_ENABLED, false);
Call(DISABLE_VEHICLE_DISTANTLIGHTS, true);
foreach (var ped in World.GetAllPeds())
{
if (ped == Game.Player.Character) continue;
var c = EntityPool.GetPedByHandle(ped.Handle);
if (c == null || (c.IsLocal && ped.Handle != Game.Player.Character.Handle &&
ped.PopulationType != EntityPopulationType.Mission))
{
Log.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
ped.CurrentVehicle?.Delete();
ped.Kill();
ped.Delete();
}
}
foreach (var veh in World.GetAllVehicles())
{
var v = veh.GetSyncEntity();
if (v.MainVehicle == Game.Player.LastVehicle ||
v.MainVehicle == Game.Player.Character.CurrentVehicle)
// Don't delete player's vehicle
continue;
if (v == null || (v.IsLocal && veh.PopulationType != EntityPopulationType.Mission))
// Log.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");
veh.Delete();
}
}
}
public static void Delay(Action a, int time)
{
Task.Run(() =>
{
Thread.Sleep(time);
QueueAction(a);
});
}
internal static void DoQueuedActions()
{
lock (QueuedActions)
{
foreach (var action in QueuedActions.ToArray())
try
{
if (action()) QueuedActions.Remove(action);
}
catch (Exception ex)
{
Log.Error(ex);
QueuedActions.Remove(action);
}
}
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a">
/// An action to be executed with a return value indicating whether the action can be removed after
/// execution.
/// </param>
internal static void QueueAction(Func<bool> a)
{
lock (QueuedActions)
{
QueuedActions.Add(a);
}
}
internal static void QueueAction(Action a)
{
lock (QueuedActions)
{
QueuedActions.Add(() =>
{
a();
return true;
});
}
}
/// <summary>
/// Clears all queued actions
/// </summary>
internal static void ClearQueuedActions()
{
lock (QueuedActions)
{
QueuedActions.Clear();
}
}
}
}

20
Client/Scripts/app.config Normal file
View File

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

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