323 Commits

Author SHA1 Message Date
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
193 changed files with 57660 additions and 10593 deletions

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']

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

@ -0,0 +1,48 @@
name: Build test
on:
push:
branches:
- '*' # matches every branch that doesn't contain a '/'
- '*/*' # matches every branch containing a single '/'
- '**' # matches every branch
- '!main' # excludes main
- '!dev-nightly' # excludes nightly
pull_request:
branches: [ "main", "dev-nightly" ]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
dotnet-version: ['6.0.x']
steps:
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v2
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Restore dependencies
run: dotnet restore
- name: Restore nuget packages
run: nuget restore
- name: Build client and installer
run: dotnet build Client/Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client
- name: Build server win-x64
run: dotnet build Server/RageCoop.Server.csproj -o bin/Release/Server
- name: Upload server
uses: actions/upload-artifact@v3
with:
name: RageCoop.Server
path: bin/Release/Server
- name: Upload Client
uses: actions/upload-artifact@v3
with:
name: RageCoop.Client
path: bin/Release/Client
- uses: actions/checkout@v2

View File

@ -2,9 +2,7 @@ name: Nightly-build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
branches: [ "dev-nightly" ]
jobs:
build:
@ -22,33 +20,34 @@ jobs:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Restore dependencies
run: dotnet restore
- name: Build client
run: dotnet publish RageCoop.Client/RageCoop.Client.csproj --no-restore --configuration Release -o RageCoop.Client/bin/RageCoop -f net48
- name: Restore nuget packages
run: nuget restore
- name: Build client and installer
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
- name: Build server win-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o RageCoop.Server/bin/win-x64 -c Release
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o bin/Release/Server/win-x64 -c Release
- name: Build server linux-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o RageCoop.Server/bin/linux-x64 -c Release
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o bin/Release/Server/linux-x64 -c Release
- name: Build server linux-arm
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o RageCoop.Server/bin/linux-arm -c Release
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-arm -o bin/Release/Server/linux-arm -c Release
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Client/bin
files: bin/Release/Client
dest: RageCoop.Client.zip
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Server/bin/win-x64
files: bin/Release/Server/win-x64
dest: RageCoop.Server-win-x64.zip
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Server/bin/linux-x64
files: bin/Release/Server/linux-x64
dest: RageCoop.Server-linux-x64.zip
- uses: vimtor/action-zip@v1
with:
files: RageCoop.Server/bin/linux-arm
files: bin/Release/Server/linux-arm
dest: RageCoop.Server-linux-arm.zip
- uses: WebFreak001/deploy-nightly@v1.1.0

3
.gitignore vendored
View File

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

View File

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

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

View File

@ -0,0 +1,192 @@
using RageCoop.Core;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using MessageBox = System.Windows.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
using Path = System.IO.Path;
namespace RageCoop.Client.Installer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Choose();
}
private void Choose()
{
var od = new OpenFileDialog()
{
Filter = "GTA 5 executable |GTA5.exe;PlayGTAV.exe",
Title = "Select you GTAV executable"
};
if (od.ShowDialog() ?? false == true)
{
Task.Run(() =>
{
try
{
Install(Directory.GetParent(od.FileName).FullName);
}
catch (Exception ex)
{
MessageBox.Show("Installation failed: " + ex.ToString());
Environment.Exit(1);
}
});
}
else
{
Environment.Exit(0);
}
}
private void Install(string root)
{
UpdateStatus("Checking requirements");
var shvPath = Path.Combine(root, "ScriptHookV.dll");
var scriptsPath = Path.Combine(root, "Scripts");
var installPath = Path.Combine(root, "RageCoop", "Scripts");
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);
File.Copy("ScriptHookVDotNet.dll", Path.Combine(root, "ScriptHookVDotNet.asi"), true);
File.Copy("ScriptHookVDotNet3.dll", Path.Combine(root, "ScriptHookVDotNet3.dll"), true);
UpdateStatus("Removing old versions");
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("RageCoop.Core.dll") && File.Exists("RageCoop.Client.dll") && File.Exists("RageCoop.Client.Loader.dll"))
{
UpdateStatus("Installing...");
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Directory.GetCurrentDirectory()), new DirectoryInfo(installPath));
File.Copy("RageCoop.Client.Loader.dll", Path.Combine(scriptsPath, "RageCoop.Client.Loader.dll"), true);
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(root, Util.SettingsPath);
Settings settings = null;
try
{
settings = Util.ReadSettings(settingsPath);
}
catch
{
settings = new Settings();
}
if (File.Exists(menyooConfig))
{
var lines = File.ReadAllLines(menyooConfig).Where(x => !x.StartsWith(";") && x.EndsWith(" = " + (int)settings.MenuKey));
if (lines.Any())
{
if (MessageBox.Show("Following menyoo config value will conflict with RAGECOOP menu key\n" +
string.Join("\n", lines)
+ "\nDo you wish to change the Menu Key?", "Warning!", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var ae = new AutoResetEvent(false);
UpdateStatus("Press the key you wish to change to");
Dispatcher.BeginInvoke(new Action(() =>
KeyDown += (s, e) =>
{
settings.MenuKey = (Keys)KeyInterop.VirtualKeyFromKey(e.Key);
ae.Set();
}));
ae.WaitOne();
if (!Util.SaveSettings(settingsPath, settings))
{
MessageBox.Show("Error occurred when saving settings");
Environment.Exit(1);
}
MessageBox.Show("Menu key changed to " + settings.MenuKey);
goto checkKeys;
}
}
}
UpdateStatus("Checking ZeroTier");
try
{
ZeroTierHelper.Check();
}
catch
{
if (MessageBox.Show("You can't join ZeroTier server unless ZeroTier is installed, do you want to download and install it?", "Install ZeroTier", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
var url = "https://download.zerotier.com/dist/ZeroTier%20One.msi";
UpdateStatus("Downloading ZeroTier from " + url);
try
{
HttpHelper.DownloadFile(url, "ZeroTier.msi", (p) => UpdateStatus("Downloading ZeroTier " + p + "%"));
UpdateStatus("Installing ZeroTier");
Process.Start("ZeroTier.msi").WaitForExit();
}
catch
{
MessageBox.Show("Failed to download ZeroTier, please download it from officail website");
Process.Start(url);
}
}
}
UpdateStatus("Completed!");
MessageBox.Show("Installation sucessful!");
Environment.Exit(0);
}
}
private void UpdateStatus(string status)
{
Dispatcher.BeginInvoke(new Action(() => Status.Content = status));
}
}
}

View File

@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<None Remove="bg.png" />
</ItemGroup>
<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="..\..\Core\RageCoop.Core.csproj" />
<ProjectReference Include="..\Loader\RageCoop.Client.Loader.csproj" />
<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>
<Folder Include="Resources\" />
</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

View File

@ -0,0 +1,205 @@
using SHVDN;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
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);
Exception e = null;
// Load domain in main thread
Main.QueueToMainThread(() =>
{
try
{
/*
var assemblies = new List<string>();
assemblies.Add(typeof(DomainLoader).Assembly.Location);
assemblies.AddRange(typeof(DomainLoader).Assembly.GetReferencedAssemblies()
.Select(x => Assembly.Load(x.FullName).Location)
.Where(x => !string.IsNullOrEmpty(x)));
*/
// Delete API assemblies
Directory.GetFiles(dir, "ScriptHookVDotNet*", SearchOption.AllDirectories).ToList().ForEach(x => File.Delete(x));
var ctxAsm = Path.Combine(dir, "RageCoop.Client.Loader.dll");
if (File.Exists(ctxAsm)) { File.Delete(ctxAsm); }
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"));
var context = (LoaderContext)newDomain.AppDomain.CreateInstanceFromAndUnwrap(
typeof(LoaderContext).Assembly.Location,
typeof(LoaderContext).FullName, ignoreCase: false,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[] { }
, null, null);
newDomain.AppDomain.SetData("RageCoop.Client.LoaderContext", context);
newDomain.Start();
_loadedDomains.TryAdd(dir, context);
}
catch (Exception ex)
{
e = ex;
}
});
// Wait till next tick
GTA.Script.Yield();
if (e != null) { throw e; }
return _loadedDomains[dir];
}
catch (Exception ex)
{
GTA.UI.Notification.Show(ex.ToString());
Console.Error(ex);
if (newDomain != null)
{
ScriptDomain.Unload(newDomain);
}
throw;
}
}
}
public static void Unload(LoaderContext domain)
{
lock (_loadedDomains)
{
Exception ex = null;
var name = domain.CurrentDomain.Name;
Console.Info("Unloading domain: " + name);
Main.QueueToMainThread(() =>
{
try
{
if (!_loadedDomains.TryRemove(domain.BaseDirectory.ToLower(), out _))
{
throw new Exception("Failed to remove domain from list");
}
domain.Dispose();
ScriptDomain.Unload(domain.CurrentDomain);
}
catch (Exception e)
{
ex = e;
GTA.UI.Notification.Show(ex.ToString());
}
});
GTA.Script.Yield();
if (ex != null)
{
throw ex;
}
Console.Info("Unloaded domain: " + name);
}
}
public static void Unload(string dir)
{
Unload(_loadedDomains[Path.GetFullPath(dir).ToLower()]);
}
public static void UnloadAll()
{
lock (_loadedDomains)
{
foreach (var d in _loadedDomains.Values.ToArray())
{
Unload(d);
}
}
}
#endregion
#region LOAD-CONTEXT
private LoaderContext()
{
AppDomain.CurrentDomain.DomainUnload += (s, e) => Dispose();
PrimaryDomain.Tick += Tick;
PrimaryDomain.KeyEvent += KeyEvent;
Console.Info($"Loaded domain: {AppDomain.CurrentDomain.FriendlyName}, {AppDomain.CurrentDomain.BaseDirectory}");
}
public static ScriptDomain PrimaryDomain => AppDomain.CurrentDomain.GetData("Primary") as ScriptDomain;
public static LoaderContext CurrentContext => AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext") as 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 therfore cannot be unloaded automatically");
}
CurrentContext.UnloadRequested = true;
}
private void Tick()
{
CurrentDomain.DoTick();
}
private void KeyEvent(Keys keys, bool status)
{
CurrentDomain.DoKeyEvent(keys, status);
}
public void Dispose()
{
lock (this)
{
if (PrimaryDomain == null) { return; }
PrimaryDomain.Tick -= Tick;
PrimaryDomain.KeyEvent -= KeyEvent;
AppDomain.CurrentDomain.SetData("Primary", null);
}
}
#endregion
}
}

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

@ -0,0 +1,69 @@
using GTA;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using Console = GTA.Console;
namespace RageCoop.Client.Loader
{
public class Main : GTA.Script
{
private static readonly string GameDir = Directory.GetParent(typeof(SHVDN.ScriptDomain).Assembly.Location).FullName;
private static readonly string ScriptsLocation = Path.Combine(GameDir, "RageCoop", "Scripts");
private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
private static int MainThreadID;
public Main()
{
if (LoaderContext.PrimaryDomain != null) { throw new InvalidOperationException("Improperly placed loader assembly, please re-install to fix this issue"); }
Tick += OnTick;
SHVDN.ScriptDomain.CurrentDomain.Tick += DomainTick;
Aborted += (s, e) => LoaderContext.UnloadAll();
}
private void OnTick(object sender, EventArgs e)
{
while (Game.IsLoading)
{
GTA.Script.Yield();
}
LoaderContext.CheckForUnloadRequest();
if (!LoaderContext.IsLoaded(ScriptsLocation))
{
if (!File.Exists(Path.Combine(ScriptsLocation, "RageCoop.Client.dll")))
{
GTA.UI.Notification.Show("~r~Main assembly is missing, please re-install the client");
Abort();
}
LoaderContext.Load(ScriptsLocation);
}
}
internal static void QueueToMainThread(Action task)
{
if (Thread.CurrentThread.ManagedThreadId != MainThreadID)
{
TaskQueue.Enqueue(task);
}
else
{
task();
}
}
private static void DomainTick()
{
if (MainThreadID == default) { MainThreadID = Thread.CurrentThread.ManagedThreadId; }
while (TaskQueue.TryDequeue(out var task))
{
try
{
task.Invoke();
}
catch (Exception ex)
{
Console.Error(ex.ToString());
}
}
}
}
}

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>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutPutPath>..\..\bin\Debug\Client.Loader</OutPutPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutPutPath>..\..\bin\Release\Client.Loader</OutPutPath>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\..\libs\ScriptHookVDotNet.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</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="LoaderContext.cs" />
<Compile Include="Main.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RageCoop.Client
{
@ -21,7 +18,7 @@ namespace RageCoop.Client
CheckProjectiles,
GetAllEntities,
Receive,
ProjectilesTotal,
}
internal static class Debug
{
@ -29,7 +26,7 @@ namespace RageCoop.Client
private static int _lastNfHandle;
static Debug()
{
foreach(TimeStamp t in Enum.GetValues(typeof(TimeStamp)))
foreach (TimeStamp t in Enum.GetValues(typeof(TimeStamp)))
{
TimeStamps.Add(t, 0);
}
@ -37,16 +34,16 @@ namespace RageCoop.Client
public static string Dump(this Dictionary<TimeStamp, long> d)
{
string s = "";
foreach(KeyValuePair<TimeStamp, long> kvp in d)
foreach (KeyValuePair<TimeStamp, long> kvp in d)
{
s+=kvp.Key+":"+kvp.Value+"\n";
s += kvp.Key + ":" + kvp.Value + "\n";
}
return s;
}
public static void ShowTimeStamps()
{
GTA.UI.Notification.Hide(_lastNfHandle);
_lastNfHandle=GTA.UI.Notification.Show(Debug.TimeStamps.Dump());
_lastNfHandle = GTA.UI.Notification.Show(Debug.TimeStamps.Dump());
}
}

View File

@ -1,33 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA;
using GTA;
using GTA.Math;
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.Windows.Forms;
namespace RageCoop.Client
{
internal class DevTool:Script
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class DevTool : Script
{
public static Vehicle ToMark;
public static bool UseSecondary=false;
public static bool UseSecondary = false;
public static int Current = 0;
public static int Secondary = 0;
public static MuzzleDir Direction = MuzzleDir.Forward;
public static Script Instance;
public DevTool()
{
Tick+=OnTick;
KeyDown+=OnKeyDown;
Util.StartUpCheck();
Instance = this;
Tick += OnTick;
KeyDown += OnKeyDown;
Pause();
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (ToMark==null||(!ToMark.Exists())) { return; }
if (DevToolMenu.Menu.SelectedItem==DevToolMenu.boneIndexItem) {
if (ToMark == null || (!ToMark.Exists())) { return; }
if (DevToolMenu.Menu.SelectedItem == DevToolMenu.boneIndexItem)
{
switch (e.KeyCode)
{
@ -39,7 +41,7 @@ namespace RageCoop.Client
break;
}
}
else if (DevToolMenu.Menu.SelectedItem==DevToolMenu.secondaryBoneIndexItem)
else if (DevToolMenu.Menu.SelectedItem == DevToolMenu.secondaryBoneIndexItem)
{
switch (e.KeyCode)
@ -57,28 +59,28 @@ namespace RageCoop.Client
private static void Update()
{
if (Current>ToMark.Bones.Count-1)
if (Current > ToMark.Bones.Count - 1)
{
Current=0;
Current = 0;
}
else if (Current< 0)
else if (Current < 0)
{
Current=ToMark.Bones.Count-1;
Current = ToMark.Bones.Count - 1;
}
DevToolMenu.boneIndexItem.AltTitle= Current.ToString();
if (Secondary>ToMark.Bones.Count-1)
DevToolMenu.boneIndexItem.AltTitle = Current.ToString();
if (Secondary > ToMark.Bones.Count - 1)
{
Secondary=0;
Secondary = 0;
}
else if (Secondary< 0)
else if (Secondary < 0)
{
Secondary=ToMark.Bones.Count-1;
Secondary = ToMark.Bones.Count - 1;
}
DevToolMenu.secondaryBoneIndexItem.AltTitle= Secondary.ToString();
DevToolMenu.secondaryBoneIndexItem.AltTitle = Secondary.ToString();
}
private static void OnTick(object sender, EventArgs e)
private void OnTick(object sender, EventArgs e)
{
if(ToMark == null || !ToMark.Exists()){ return;}
if (ToMark == null || !ToMark.Exists()) { return; }
Update();
Draw(Current);
if (UseSecondary)
@ -90,95 +92,93 @@ namespace RageCoop.Client
private static void Draw(int boneindex)
{
var bone = ToMark.Bones[boneindex];
World.DrawLine(bone.Position, bone.Position+2*bone.ForwardVector, Color.Blue);
World.DrawLine(bone.Position, bone.Position+2*bone.UpVector, Color.Green);
World.DrawLine(bone.Position, bone.Position+2*bone.RightVector, Color.Yellow);
World.DrawLine(bone.Position, bone.Position + 2 * bone.ForwardVector, Color.Blue);
World.DrawLine(bone.Position, bone.Position + 2 * bone.UpVector, Color.Green);
World.DrawLine(bone.Position, bone.Position + 2 * bone.RightVector, Color.Yellow);
Vector3 todraw = bone.ForwardVector;
switch ((byte)Direction)
{
case 0:
todraw=bone.ForwardVector;
todraw = bone.ForwardVector;
break;
case 1:
todraw=bone.RightVector;
todraw = bone.RightVector;
break;
case 2:
todraw=bone.UpVector;
todraw = bone.UpVector;
break;
case 3:
todraw=bone.ForwardVector*-1;
todraw = bone.ForwardVector * -1;
break;
case 4:
todraw=bone.RightVector*-1;
todraw = bone.RightVector * -1;
break;
case 5:
todraw=bone.UpVector*-1;
todraw = bone.UpVector * -1;
break;
}
World.DrawLine(bone.Position, bone.Position+10*todraw, Color.Red);
World.DrawLine(bone.Position, bone.Position + 10 * todraw, Color.Red);
}
public static void CopyToClipboard(MuzzleDir dir)
{
if (ToMark!=null)
if (ToMark != null)
{
string s;
if (UseSecondary)
{
if ((byte)dir<3)
if ((byte)dir < 3)
{
s=$@"
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
i=BulletsShot%2==0 ? {Current} : {Secondary};
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].{dir}Vector);
return BulletsShot%2==0 ? {Current} : {Secondary};
";
}
else
{
s=$@"
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
i=BulletsShot%2==0 ? {Current} : {Secondary};
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].{((MuzzleDir)(dir-3)).ToString()}Vector*-1);
return BulletsShot%2==0 ? {Current} : {Secondary};
";
}
}
else
{
if ((byte)dir<3)
if ((byte)dir < 3)
{
s=$@"
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
return new MuzzleInfo(v.Bones[{Current}].Position, v.Bones[{Current}].{dir}Vector);
return {Current};
";
}
else
{
s=$@"
s = $@"
// {ToMark.DisplayName}
case {ToMark.Model.Hash}:
return new MuzzleInfo(v.Bones[{Current}].Position, v.Bones[{Current}].{((MuzzleDir)(dir-3)).ToString()}Vector*-1);
return {Current};
";
}
}
Thread thread = new Thread(() => Clipboard.SetText(s));
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
GTA.UI.Notification.Show("Copied to clipboard, please paste it on the GitHub issue page!");
}
}
}
internal enum MuzzleDir:byte
internal enum MuzzleDir : byte
{
Forward=0,
Forward = 0,
Right = 1,
Up=2,
Backward=3,
Up = 2,
Backward = 3,
Left = 4,
Down=5,
Down = 5,
}
}

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

@ -0,0 +1,417 @@
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using SHVDN;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Console = GTA.Console;
using Script = GTA.Script;
namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class Main : Script
{
private static bool _gameLoaded = false;
internal static Version Version = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID = 0;
internal static RelationshipGroup SyncedPedsGroup;
internal static new Settings Settings = null;
#if !NON_INTERACTIVE
#endif
internal static Chat MainChat = null;
internal static Stopwatch Counter = new Stopwatch();
internal static Logger Logger = null;
internal static ulong Ticked = 0;
internal static Vector3 PlayerPosition;
internal static Resources Resources = null;
private static readonly ConcurrentQueue<Action> TaskQueue = new ConcurrentQueue<Action>();
public static string LogPath => $"{Settings.DataDirectory}\\RageCoop.Client.log";
/// <summary>
/// Don't use it!
/// </summary>
public Main()
{
Util.StartUpCheck();
Console.Info($"Starting {typeof(Main).FullName}, domain: {AppDomain.CurrentDomain.Id} {AppDomain.CurrentDomain.FriendlyName}");
try
{
Settings = Util.ReadSettings();
if (Settings.DataDirectory.StartsWith("Scripts"))
{
var defaultDir = new Settings().DataDirectory;
Console.Warning("Data directory must be outside scripts folder, migrating to default direcoty: " + defaultDir);
if (Directory.Exists(Settings.DataDirectory))
{
CoreUtils.CopyFilesRecursively(new DirectoryInfo(Settings.DataDirectory), new DirectoryInfo(defaultDir));
Directory.Delete(Settings.DataDirectory, true);
}
Settings.DataDirectory = defaultDir;
Util.SaveSettings();
}
}
catch
{
// GTA.UI.Notification.Show("Malformed configuration, overwriting with default values...");
Settings = new Settings();
Util.SaveSettings();
}
Directory.CreateDirectory(Settings.DataDirectory);
Logger = new Logger()
{
Writers = new List<StreamWriter> { CoreUtils.OpenWriter(LogPath) },
#if DEBUG
LogLevel = 0,
#else
LogLevel = Settings.LogLevel,
#endif
};
Logger.OnFlush += (line, formatted) =>
{
switch (line.LogLevel)
{
#if DEBUG
// case LogLevel.Trace:
case LogLevel.Debug:
Console.Info(line.Message);
break;
#endif
case LogLevel.Info:
Console.Info(line.Message);
break;
case LogLevel.Warning:
Console.Warning(line.Message);
break;
case LogLevel.Error:
Console.Error(line.Message);
break;
}
};
ScriptDomain.CurrentDomain.Tick += DomainTick;
Resources = new Resources();
if (Game.Version < GameVersion.v1_0_1290_1_Steam)
{
Tick += (object sender, EventArgs e) =>
{
if (Game.IsLoading)
{
return;
}
if (!_gameLoaded)
{
GTA.UI.Notification.Show("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
_gameLoaded = true;
}
};
return;
}
BaseScript.OnStart();
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral, true);
#if !NON_INTERACTIVE
#endif
MainChat = new Chat();
Aborted += OnAborted;
Tick += OnTick;
KeyDown += OnKeyDown;
Util.NativeMemory();
Counter.Restart();
}
private static void OnAborted(object sender, EventArgs e)
{
try
{
WorldThread.Instance?.Abort();
DevTool.Instance?.Abort();
ScriptDomain.CurrentDomain.Tick -= DomainTick;
Disconnected("Abort");
WorldThread.DoQueuedActions();
}
catch (Exception ex)
{
Logger.Error(ex);
}
}
private static void DomainTick()
{
while (TaskQueue.TryDequeue(out var task))
{
try
{
task.Invoke();
}
catch (Exception ex)
{
Logger.Error(ex);
}
}
if (Networking.IsOnServer)
{
try
{
EntityPool.DoSync();
}
catch (Exception ex)
{
Logger.Error(ex);
}
}
}
/// <summary>
/// Queue an action to main thread and wait for execution to complete, must be called from script thread.
/// </summary>
/// <param name="task"></param>
internal static void QueueToMainThreadAndWait(Action task)
{
Exception e = null;
TaskQueue.Enqueue(() => { try { task(); } catch (Exception ex) { e = ex; } });
Yield();
if (e != null) { throw e; }
}
public static Ped P;
public static float FPS;
private static bool _lastDead;
private static void OnTick(object sender, EventArgs e)
{
P = Game.Player.Character;
PlayerPosition = P.ReadPosition();
FPS = Game.FPS;
if (Game.IsLoading)
{
return;
}
else if (!_gameLoaded && (_gameLoaded = true))
{
#if !NON_INTERACTIVE
GTA.UI.Notification.Show(GTA.UI.NotificationIcon.AllPlayersConf, "RAGECOOP", "Welcome!", $"Press ~g~{Main.Settings.MenuKey}~s~ to open the menu.");
#endif
}
#if !NON_INTERACTIVE
CoopMenu.MenuPool.Process();
#endif
if (!Networking.IsOnServer)
{
return;
}
if (Game.TimeScale != 1)
{
Game.TimeScale = 1;
}
if (Networking.ShowNetworkInfo)
{
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 0), $"L: {Networking.Latency * 1000:N0}ms", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {Lidgren.Network.NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60), $"S: {Lidgren.Network.NetUtility.ToHumanReadable(Statistics.BytesUpPerSecond)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
}
MainChat.Tick();
PlayerList.Tick();
if (!API.Config.EnableAutoRespawn)
{
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
Function.Call(Hash.IGNORE_NEXT_RESTART, true);
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
Function.Call(Hash.TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
if (P.IsDead)
{
Function.Call(Hash.SET_FADE_OUT_AFTER_DEATH, false);
if (P.Health != 1)
{
P.Health = 1;
Game.Player.WantedLevel = 0;
Logger.Debug("Player died.");
API.Events.InvokePlayerDied();
}
GTA.UI.Screen.StopEffects();
}
else
{
Function.Call(Hash.DISPLAY_HUD, true);
}
}
else if (P.IsDead && !_lastDead)
{
API.Events.InvokePlayerDied();
}
_lastDead = P.IsDead;
Ticked++;
}
private static void OnKeyDown(object sender, KeyEventArgs e)
{
if (MainChat.Focused)
{
MainChat.OnKeyDown(e.KeyCode);
return;
}
if (Networking.IsOnServer)
{
if (Voice.WasInitialized())
{
if (Game.IsControlPressed(GTA.Control.PushToTalk))
{
Voice.StartRecording();
return;
}
else if (Voice.IsRecording())
{
Voice.StopRecording();
return;
}
}
if (Game.IsControlPressed(GTA.Control.FrontendPause))
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
if (Game.IsControlPressed(GTA.Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
}
if (e.KeyCode == Settings.MenuKey)
{
if (CoopMenu.MenuPool.AreAnyVisible)
{
CoopMenu.MenuPool.ForEach<LemonUI.Menus.NativeMenu>(x =>
{
if (x.Visible)
{
CoopMenu.LastMenu = x;
x.Visible = false;
}
});
}
else
{
CoopMenu.LastMenu.Visible = true;
}
}
else if (Game.IsControlJustPressed(GTA.Control.MpTextChatAll))
{
if (Networking.IsOnServer)
{
MainChat.Focused = true;
}
}
else if (MainChat.Focused) { return; }
else if (Game.IsControlJustPressed(GTA.Control.MultiplayerInfo))
{
if (Networking.IsOnServer)
{
ulong currentTimestamp = Util.GetTickCount64();
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000 ? (currentTimestamp - 6000) : currentTimestamp;
}
}
else if (e.KeyCode == Settings.PassengerKey)
{
var P = Game.Player.Character;
if (!P.IsInVehicle())
{
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
{
P.Task.ClearAll();
}
else
{
var V = World.GetClosestVehicle(P.ReadPosition(), 50);
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);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
CoopMenu.ConnectedMenuSetting();
MainChat.Init();
GTA.UI.Notification.Show("~g~Connected!");
});
Logger.Info(">> Connected <<");
}
public static void Disconnected(string reason)
{
Logger.Info($">> Disconnected << reason: {reason}");
API.QueueAction(() =>
{
if (MainChat.Focused)
{
MainChat.Focused = false;
}
PlayerList.Cleanup();
MainChat.Clear();
EntityPool.Cleanup();
WorldThread.Traffic(true);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting();
GTA.UI.Notification.Show("~r~Disconnected: " + reason);
LocalPlayerID = default;
Resources.Unload();
});
Memory.RestorePatches();
DownloadManager.Cleanup();
Voice.ClearAll();
}
}
}

View File

@ -1,10 +1,9 @@
using GTA;
using System.Drawing;
using GTA.Native;
using LemonUI;
using LemonUI.Menus;
using LemonUI.Scaleform;
using System.Drawing;
namespace RageCoop.Client.Menus
{
@ -19,27 +18,28 @@ namespace RageCoop.Client.Menus
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
public static PopUp PopUp=new PopUp()
public static PopUp PopUp = new PopUp()
{
Title="",
Prompt="",
Title = "",
Prompt = "",
Subtitle = "",
Error="",
Error = "",
ShowBackground = true,
Visible=false,
Visible = false,
};
public static NativeMenu LastMenu { get; set; } = Menu;
#region ITEMS
private static readonly NativeItem _usernameItem = new NativeItem("Username") { AltTitle = Main.Settings.Username };
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*',Main.Settings.Password.Length) };
private static readonly NativeItem _passwordItem = new NativeItem("Password") { AltTitle = new string('*', Main.Settings.Password.Length) };
public static readonly NativeItem ServerIpItem = new NativeItem("Server IP") { AltTitle = Main.Settings.LastServerAddress };
internal static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.CurrentVersion.Replace("_", ".")) { LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
Main.Version)
{ LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
#endregion
@ -53,7 +53,7 @@ namespace RageCoop.Client.Menus
Menu.Title.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated;
_passwordItem.Activated+=_passwordActivated;
_passwordItem.Activated += _passwordActivated;
ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
@ -68,7 +68,7 @@ namespace RageCoop.Client.Menus
Menu.AddSubMenu(SettingsMenu.Menu);
Menu.AddSubMenu(DevToolMenu.Menu);
Menu.AddSubMenu(DebugMenu.Menu);
Menu.AddSubMenu(UpdateMenu.Menu);
MenuPool.Add(Menu);
MenuPool.Add(SettingsMenu.Menu);
@ -76,27 +76,39 @@ namespace RageCoop.Client.Menus
MenuPool.Add(DebugMenu.Menu);
MenuPool.Add(DebugMenu.DiagnosticMenu);
MenuPool.Add(ServersMenu.Menu);
MenuPool.Add(UpdateMenu.Menu);
MenuPool.Add(PopUp);
Menu.Add(_aboutItem);
}
public static bool ShowPopUp(string prompt, string title,string subtitle,string error,bool showbackground)
public static bool ShowPopUp(string prompt, string title, string subtitle, string error, bool showbackground)
{
PopUp.Prompt=prompt;
PopUp.Title=title;
PopUp.Subtitle=subtitle;
PopUp.Error=error;
PopUp.ShowBackground=showbackground;
PopUp.Visible=true;
PopUp.Prompt = prompt;
PopUp.Title = title;
PopUp.Subtitle = subtitle;
PopUp.Error = error;
PopUp.ShowBackground = showbackground;
PopUp.Visible = true;
Script.Yield();
while (true)
{
Game.DisableAllControlsThisFrame();
MenuPool.Process();
var scaleform = new Scaleform("instructional_buttons");
scaleform.CallFunction("CLEAR_ALL");
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
scaleform.CallFunction("CREATE_CONTAINER");
scaleform.CallFunction("SET_DATA_SLOT", 0, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
scaleform.CallFunction("SET_DATA_SLOT", 1, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
scaleform.Render2D();
if (Game.IsControlJustPressed(Control.FrontendAccept))
{
PopUp.Visible=false;
PopUp.Visible = false;
return true;
}
else if (Game.IsControlJustPressed(Control.FrontendCancel))
@ -105,6 +117,8 @@ namespace RageCoop.Client.Menus
return false;
}
Script.Yield();
Game.DisableAllControlsThisFrame();
}
}
public static void UsernameActivated(object a, System.EventArgs b)
@ -122,12 +136,9 @@ namespace RageCoop.Client.Menus
private static void _passwordActivated(object sender, System.EventArgs e)
{
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
if (!string.IsNullOrWhiteSpace(newPass))
{
Main.Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
Main.Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
public static void ServerIpActivated(object a, System.EventArgs b)
{

View File

@ -0,0 +1,64 @@
using GTA;
using LemonUI.Menus;
using System;
using System.Drawing;
namespace RageCoop.Client
{
internal static class DebugMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
public static NativeMenu DiagnosticMenu = new NativeMenu("RAGECOOP", "Diagnostic", "Performence and Diagnostic")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
public static NativeItem ReloadItem = new NativeItem("Reload", "Reload RAGECOOP and associated scripts");
public static NativeItem SimulatedLatencyItem = new NativeItem("Simulated network latency", "Simulated network latency in ms (one way)", "0");
public static NativeCheckboxItem ShowOwnerItem = new NativeCheckboxItem("Show entity owner", "Show the owner name of the entity you're aiming at", false);
private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
static DebugMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
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()));
}
};
SimulatedLatencyItem.Activated += (s, e) =>
{
try
{
SimulatedLatencyItem.AltTitle = ((Networking.SimulatedLatency = int.Parse(Game.GetUserInput(SimulatedLatencyItem.AltTitle)) * 0.002f) * 500).ToString();
}
catch (Exception ex) { Main.Logger.Error(ex); }
};
ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; };
ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); };
ReloadItem.Activated += ReloadDomain;
Menu.Add(SimulatedLatencyItem);
Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem);
Menu.Add(ReloadItem);
Menu.AddSubMenu(DiagnosticMenu);
}
private static void ReloadDomain(object sender, EventArgs e)
{
Loader.LoaderContext.RequestUnload();
}
}
}

View File

@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA;
using LemonUI.Menus;
using GTA;
using System;
using System.Drawing;
namespace RageCoop.Client
@ -16,9 +12,9 @@ namespace RageCoop.Client
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
private static NativeCheckboxItem enableItem = new NativeCheckboxItem("Enable");
private static readonly NativeCheckboxItem enableItem = new NativeCheckboxItem("Enable");
private static NativeCheckboxItem enableSecondaryItem = new NativeCheckboxItem("Secondary","Enable if this vehicle have two muzzles");
private static readonly NativeCheckboxItem enableSecondaryItem = new NativeCheckboxItem("Secondary", "Enable if this vehicle have two muzzles");
public static NativeItem boneIndexItem = new NativeItem("Current bone index");
public static NativeItem secondaryBoneIndexItem = new NativeItem("Secondary bone index");
public static NativeItem clipboardItem = new NativeItem("Copy to clipboard");
@ -28,18 +24,18 @@ namespace RageCoop.Client
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
enableItem.Activated+=enableItem_Activated;
enableItem.Checked=false;
enableSecondaryItem.CheckboxChanged+=EnableSecondaryItem_Changed;
enableItem.Activated += enableItem_Activated;
enableItem.Checked = false;
enableSecondaryItem.CheckboxChanged += EnableSecondaryItem_Changed;
secondaryBoneIndexItem.Enabled=false;
clipboardItem.Activated+=ClipboardItem_Activated;
dirItem.ItemChanged+=DirItem_ItemChanged;
secondaryBoneIndexItem.Enabled = false;
clipboardItem.Activated += ClipboardItem_Activated;
dirItem.ItemChanged += DirItem_ItemChanged;
foreach (var d in Enum.GetValues(typeof(MuzzleDir)))
{
dirItem.Items.Add((MuzzleDir)d);
}
dirItem.SelectedIndex=0;
dirItem.SelectedIndex = 0;
Menu.Add(enableItem);
Menu.Add(boneIndexItem);
@ -53,19 +49,19 @@ namespace RageCoop.Client
{
if (enableSecondaryItem.Checked)
{
DevTool.UseSecondary=true;
secondaryBoneIndexItem.Enabled=true;
DevTool.UseSecondary = true;
secondaryBoneIndexItem.Enabled = true;
}
else
{
DevTool.UseSecondary=false;
secondaryBoneIndexItem.Enabled=false;
DevTool.UseSecondary = false;
secondaryBoneIndexItem.Enabled = false;
}
}
private static void DirItem_ItemChanged(object sender, ItemChangedEventArgs<MuzzleDir> e)
{
DevTool.Direction=dirItem.SelectedItem;
DevTool.Direction = dirItem.SelectedItem;
}
private static void ClipboardItem_Activated(object sender, EventArgs e)
@ -77,11 +73,13 @@ namespace RageCoop.Client
{
if (enableItem.Checked)
{
DevTool.ToMark=Game.Player.Character.CurrentVehicle;
DevTool.Instance.Resume();
DevTool.ToMark = Game.Player.Character.CurrentVehicle;
}
else
{
DevTool.ToMark=null;
DevTool.Instance.Pause();
DevTool.ToMark = null;
}
}
}

View File

@ -1,36 +1,16 @@
using System;
using System.Net;
using System.Drawing;
using System.Collections.Generic;
using Newtonsoft.Json;
using GTA.UI;
using LemonUI.Menus;
using Newtonsoft.Json;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Threading;
namespace RageCoop.Client.Menus
{
internal class ServerListClass
{
[JsonProperty("address")]
public string Address { get; set; }
[JsonProperty("port")]
public string Port { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("players")]
public int Players { get; set; }
[JsonProperty("maxPlayers")]
public int MaxPlayers { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
}
/// <summary>
/// Don't use it!
@ -59,7 +39,7 @@ namespace RageCoop.Client.Menus
Menu.Add(ResultItem = new NativeItem("Loading..."));
// Prevent freezing
GetServersThread=new Thread(()=> GetAllServers());
GetServersThread = new Thread(() => GetAllServers());
GetServersThread.Start();
};
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
@ -76,12 +56,12 @@ namespace RageCoop.Client.Menus
private static void GetAllServers()
{
List<ServerListClass> serverList = null;
List<ServerInfo> serverList = null;
var realUrl = Main.Settings.MasterServer;
serverList = JsonConvert.DeserializeObject<List<ServerListClass>>(DownloadString(realUrl));
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
// Need to be processed in main thread
Main.QueueAction(() =>
API.QueueAction(() =>
{
if (serverList == null)
{
@ -94,17 +74,25 @@ namespace RageCoop.Client.Menus
return;
}
CleanUpList();
foreach (ServerListClass server in serverList)
foreach (ServerInfo server in serverList)
{
string address = $"{server.Address}:{server.Port}";
NativeItem tmpItem = new NativeItem($"[{server.Country}] {server.Name}", $"~b~{address}~s~~n~~g~Version {server.Version}.x~s~") { AltTitle = $"[{server.Players}/{server.MaxPlayers}]" };
string address = $"{server.address}:{server.port}";
NativeItem tmpItem = new NativeItem($"[{server.country}] {server.name}", $"~b~{address}~s~~n~~g~Version {server.version}.x~s~") { AltTitle = $"[{server.players}/{server.maxPlayers}]" };
tmpItem.Activated += (object sender, EventArgs e) =>
{
try
{
Menu.Visible = false;
Networking.ToggleConnection(address);
if (server.useZT)
{
address = $"{server.ztAddress}:{server.port}";
Notification.Show($"~y~Joining ZeroTier network... {server.ztID}");
if (ZeroTierHelper.Join(server.ztID) == null)
{
throw new Exception("Failed to obtain ZeroTier network IP");
}
}
Networking.ToggleConnection(address, null, null, PublicKey.FromServerInfo(server));
#if !NON_INTERACTIVE
CoopMenu.ServerIpItem.AltTitle = address;
@ -115,7 +103,11 @@ namespace RageCoop.Client.Menus
}
catch (Exception ex)
{
GTA.UI.Notification.Show($"~r~{ex.Message}");
Notification.Show($"~r~{ex.Message}");
if (server.useZT)
{
Notification.Show($"Make sure ZeroTier is correctly installed, download it from https://www.zerotier.com/");
}
}
};
Menu.Add(tmpItem);
@ -136,7 +128,7 @@ namespace RageCoop.Client.Menus
}
catch (Exception ex)
{
Main.QueueAction(() =>
API.QueueAction(() =>
{
ResultItem.Title = "Download failed!";
ResultItem.Description = ex.Message;

View File

@ -1,14 +1,11 @@
using System.Drawing;
using System;
using System.Windows.Forms;
using GTA;
using GTA;
using LemonUI.Menus;
using System;
using System.Drawing;
using System.Windows.Forms;
namespace RageCoop.Client.Menus
{
/// <summary>
/// Don't use it!
/// </summary>
internal static class SettingsMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
@ -19,54 +16,66 @@ namespace RageCoop.Client.Menus
private static readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic);
private static readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu);
private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableTraffic);
private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableAlternatePause);
private static readonly NativeCheckboxItem _disableVoice = new NativeCheckboxItem("Enable voice", "Check your GTA:V settings to find the right key on your keyboard for PushToTalk and talk to your friends", Main.Settings.Voice);
private static readonly NativeCheckboxItem _showNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
private static NativeItem _menuKey = new NativeItem("Menu Key","The key to open menu", Main.Settings.MenuKey.ToString());
private static NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
private static NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).",Main.Settings.WorldVehicleSoftLimit.ToString());
private static readonly NativeItem _menuKey = new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString());
private static readonly NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
private static readonly NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldVehicleSoftLimit.ToString());
/// <summary>
/// Don't use it!
/// </summary>
static SettingsMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
_disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged;
_disablePauseAlt.CheckboxChanged+=_disablePauseAlt_CheckboxChanged;
_disablePauseAlt.CheckboxChanged += DisablePauseAltCheckboxChanged;
_disableVoice.CheckboxChanged += DisableVoiceCheckboxChanged;
_flipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged;
_showNetworkInfoItem.CheckboxChanged += ShowNetworkInfoCheckboxChanged;
_menuKey.Activated+=ChaneMenuKey;
_passengerKey.Activated+=ChangePassengerKey;
_vehicleSoftLimit.Activated+=vehicleSoftLimit_Activated;
_menuKey.Activated += ChaneMenuKey;
_passengerKey.Activated += ChangePassengerKey;
_vehicleSoftLimit.Activated += VehicleSoftLimitActivated;
Menu.Add(_disableTrafficItem);
Menu.Add(_disablePauseAlt);
Menu.Add(_flipMenuItem);
Menu.Add(_showNetworkInfoItem);
Menu.Add(_disableVoice);
Menu.Add(_menuKey);
Menu.Add(_passengerKey);
Menu.Add(_vehicleSoftLimit);
}
private static void _disablePauseAlt_CheckboxChanged(object sender, EventArgs e)
private static void DisableVoiceCheckboxChanged(object sender, EventArgs e)
{
Main.Settings.DisableAlternatePause=_disablePauseAlt.Checked;
if (_disableVoice.Checked)
{
if (Networking.IsOnServer && !Voice.WasInitialized())
{
Voice.Init();
}
}
else
{
Voice.ClearAll();
}
Main.Settings.Voice = _disableVoice.Checked;
Util.SaveSettings();
}
private static void vehicleSoftLimit_Activated(object sender, EventArgs e)
private static void DisablePauseAltCheckboxChanged(object sender, EventArgs e)
{
Main.Settings.DisableAlternatePause = _disablePauseAlt.Checked;
Util.SaveSettings();
}
private static void VehicleSoftLimitActivated(object sender, EventArgs e)
{
try
{
Main.Settings.WorldVehicleSoftLimit =int.Parse(
Main.Settings.WorldVehicleSoftLimit = int.Parse(
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.WorldVehicleSoftLimit.ToString(), 20));
_menuKey.AltTitle=Main.Settings.WorldVehicleSoftLimit.ToString();
_menuKey.AltTitle = Main.Settings.WorldVehicleSoftLimit.ToString();
Util.SaveSettings();
}
catch { }
@ -75,11 +84,11 @@ namespace RageCoop.Client.Menus
{
try
{
Main.Settings.MenuKey =(Keys)Enum.Parse(
Main.Settings.MenuKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.MenuKey.ToString(), 20));
_menuKey.AltTitle=Main.Settings.MenuKey.ToString();
_menuKey.AltTitle = Main.Settings.MenuKey.ToString();
Util.SaveSettings();
}
catch { }
@ -89,11 +98,11 @@ namespace RageCoop.Client.Menus
{
try
{
Main.Settings.PassengerKey =(Keys)Enum.Parse(
Main.Settings.PassengerKey = (Keys)Enum.Parse(
typeof(Keys),
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.PassengerKey.ToString(), 20));
_passengerKey.AltTitle=Main.Settings.PassengerKey.ToString();
_passengerKey.AltTitle = Main.Settings.PassengerKey.ToString();
Util.SaveSettings();
}
catch { }
@ -101,8 +110,9 @@ namespace RageCoop.Client.Menus
public static void DisableTrafficCheckboxChanged(object a, System.EventArgs b)
{
WorldThread.Traffic(!_disableTrafficItem.Checked);
Main.Settings.DisableTraffic = _disableTrafficItem.Checked;
Util.SaveSettings() ;
Util.SaveSettings();
}
public static void FlipMenuCheckboxChanged(object a, System.EventArgs b)
@ -113,11 +123,5 @@ namespace RageCoop.Client.Menus
Main.Settings.FlipMenu = _flipMenuItem.Checked;
Util.SaveSettings();
}
public static void ShowNetworkInfoCheckboxChanged(object a, System.EventArgs b)
{
Networking.ShowNetworkInfo = _showNetworkInfoItem.Checked;
}
}
}

View File

@ -0,0 +1,110 @@
using ICSharpCode.SharpZipLib.Zip;
using LemonUI.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace RageCoop.Client.Menus
{
internal class UpdateMenu
{
public static bool IsUpdating { get; private set; } = false;
private static readonly NativeItem _updatingItem = new NativeItem("Updating...");
private static readonly NativeItem _downloadItem = new NativeItem("Download", "Download and update to latest nightly");
private static readonly string _downloadPath = Path.Combine(Main.Settings.DataDirectory, "RageCoop.Client.zip");
public static NativeMenu Menu = new NativeMenu("Update", "Update", "Download and install latest nightly build from GitHub")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
static UpdateMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.Opening += Opening;
_downloadItem.Activated += StartUpdate;
}
private static void StartUpdate(object sender, EventArgs e)
{
if (CoreUtils.GetLatestVersion() < Main.Version)
{
GTA.UI.Notification.Show("Local version is newer than remote version, update can't continue");
return;
}
IsUpdating = true;
Menu.Clear();
Menu.Add(_updatingItem);
Task.Run(() =>
{
try
{
if (File.Exists(_downloadPath)) { File.Delete(_downloadPath); }
WebClient client = new WebClient();
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
client.DownloadProgressChanged += (s, e1) => { API.QueueAction(() => { _updatingItem.AltTitle = $"{e1.ProgressPercentage}%"; }); };
client.DownloadFileCompleted += (s, e2) => { Install(); };
client.DownloadFileAsync(new Uri("https://github.com/RAGECOOP/RAGECOOP-V/releases/download/nightly/RageCoop.Client.zip"), _downloadPath);
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
});
}
private static void Install()
{
try
{
API.QueueAction(() =>
{
_updatingItem.AltTitle = "Installing...";
});
var insatllPath = @"RageCoop\Scripts";
Directory.CreateDirectory(insatllPath);
foreach (var f in Directory.GetFiles(insatllPath, "*.dll", SearchOption.AllDirectories))
{
try { File.Delete(f); }
catch { }
}
new FastZip().ExtractZip(_downloadPath, insatllPath, FastZip.Overwrite.Always, null, null, null, true);
try { File.Delete(_downloadPath); } catch { }
try { File.Delete(Path.Combine(insatllPath, "RageCoop.Client.Installer.exe")); } catch { }
Loader.LoaderContext.RequestUnload();
IsUpdating = false;
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
}
private static void Opening(object sender, System.ComponentModel.CancelEventArgs e)
{
Menu.Clear();
if (Networking.IsOnServer)
{
Menu.Add(new NativeItem("Disconnect from the server first"));
}
else if (IsUpdating)
{
Menu.Add(_updatingItem);
}
else
{
Menu.Add(_downloadItem);
}
}
}
}

View File

@ -1,10 +1,9 @@
using System;
using GTA;
using GTA.Native;
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using RageCoop.Core;
using GTA;
using GTA.Native;
namespace RageCoop.Client
{
@ -17,7 +16,7 @@ namespace RageCoop.Client
private bool CurrentFocused { get; set; }
public bool Focused
{
get { return CurrentFocused; }
get => CurrentFocused;
set
{
if (value && Hidden)
@ -36,7 +35,7 @@ namespace RageCoop.Client
private bool CurrentHidden { get; set; }
private bool Hidden
{
get { return CurrentHidden; }
get => CurrentHidden;
set
{
if (value)
@ -106,7 +105,7 @@ namespace RageCoop.Client
CurrentInput = "";
return;
}
if (key == Keys.PageUp)
{
MainScaleForm.CallFunction("PAGE_UP");

View File

@ -1,33 +1,34 @@
using System.IO;
using System.Linq;
using System.Collections.Generic;
using RageCoop.Core;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace RageCoop.Client
{
internal static class DownloadManager
{
public static event EventHandler<string> DownloadCompleted;
static DownloadManager()
{
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, (data) =>
{
var fr = new Packets.FileTransferRequest();
fr.Unpack(data);
if (fr.Name.EndsWith(".zip"))
fr.Deserialize(data);
if (fr.Name.EndsWith(".res"))
{
_zips.Add(fr.Name);
_resources.Add(fr.Name);
}
return new Packets.FileTransferResponse()
{
ID= fr.ID,
Response=AddFile(fr.ID,fr.Name,fr.FileLength) ? FileResponse.NeedToDownload : FileResponse.AlreadyExists
ID = fr.ID,
Response = AddFile(fr.ID, fr.Name, fr.FileLength) ? FileResponse.NeedToDownload : FileResponse.AlreadyExists
};
});
Networking.RequestHandlers.Add(PacketType.FileTransferComplete, (data) =>
{
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
packet.Unpack(data);
packet.Deserialize(data);
Main.Logger.Debug($"Finalizing download:{packet.ID}");
Complete(packet.ID);
@ -35,52 +36,50 @@ namespace RageCoop.Client
// Inform the server that the download is completed
return new Packets.FileTransferResponse()
{
ID= packet.ID,
Response=FileResponse.Completed
ID = packet.ID,
Response = FileResponse.Completed
};
});
Networking.RequestHandlers.Add(PacketType.AllResourcesSent, (data) =>
{
try
{
Main.Resources.Load(ResourceFolder,_zips.ToArray());
return new Packets.FileTransferResponse() { ID=0, Response=FileResponse.Loaded };
Main.Resources.Load(ResourceFolder, _resources.ToArray());
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.Loaded };
}
catch(Exception ex)
catch (Exception ex)
{
Main.Logger.Error("Error occurred when loading server resource:");
Main.Logger.Error("Error occurred when loading server resource");
Main.Logger.Error(ex);
return new Packets.FileTransferResponse() { ID=0, Response=FileResponse.LoadFailed };
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed };
}
});
}
public static string ResourceFolder {
get {
return Path.Combine(Main.Settings.DataDirectory,"Resources", Main.Settings.LastServerAddress.Replace(":", "."));
}
}
public static string ResourceFolder => Path.GetFullPath(Path.Combine(Main.Settings.DataDirectory, "Resources", Main.Settings.LastServerAddress.Replace(":", ".")));
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
private static readonly List<string> _zips = new List<string>();
private static readonly HashSet<string> _resources = new HashSet<string>();
public static bool AddFile(int id, string name, long length)
{
Main.Logger.Debug($"Downloading file to {ResourceFolder}\\{name} , id:{id}");
if (!Directory.Exists(ResourceFolder))
var path = $"{ResourceFolder}\\{name}";
Main.Logger.Debug($"Downloading file to {path} , id:{id}");
if (!Directory.Exists(Directory.GetParent(path).FullName))
{
Directory.CreateDirectory(ResourceFolder);
Directory.CreateDirectory(Directory.GetParent(path).FullName);
}
if (FileAlreadyExists(ResourceFolder, name, length))
{
Main.Logger.Debug($"File already exists! canceling download:{name}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
return false;
}
/*
if (!name.EndsWith(".zip"))
{
Main.Logger.Error($"File download blocked! [{name}]");
return false;
}
*/
lock (InProgressDownloads)
{
InProgressDownloads.Add(id, new DownloadFile()
@ -88,7 +87,7 @@ namespace RageCoop.Client
FileID = id,
FileName = name,
FileLength = length,
Stream = new FileStream($"{ResourceFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
});
}
return true;
@ -123,8 +122,7 @@ namespace RageCoop.Client
{
lock (InProgressDownloads)
{
DownloadFile file;
if (InProgressDownloads.TryGetValue(id, out file))
if (InProgressDownloads.TryGetValue(id, out DownloadFile file))
{
file.Stream.Write(chunk, 0, chunk.Length);
@ -134,22 +132,17 @@ namespace RageCoop.Client
Main.Logger.Trace($"Received unhandled file chunk:{id}");
}
}
}
public static void Complete(int id)
{
DownloadFile f;
if (InProgressDownloads.TryGetValue(id, out f))
if (InProgressDownloads.TryGetValue(id, out DownloadFile f))
{
InProgressDownloads.Remove(id);
f.Dispose();
if (f.FileName.EndsWith(".zip"))
{
_zips.Add(f.FileName);
}
Main.Logger.Info($"Download finished:{f.FileName}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
}
else
{
@ -167,20 +160,12 @@ namespace RageCoop.Client
}
InProgressDownloads.Clear();
}
if (Directory.Exists(ResourceFolder))
{
foreach (var zip in Directory.GetDirectories(ResourceFolder, "*.zip"))
{
File.Delete(zip);
}
}
_zips.Clear();
_resources.Clear();
}
}
internal class DownloadFile: System.IDisposable
internal class DownloadFile : IDisposable
{
public int FileID { get; set; } = 0;
public string FileName { get; set; } = string.Empty;
@ -189,7 +174,7 @@ namespace RageCoop.Client
public FileStream Stream { get; set; }
public void Dispose()
{
if(Stream!= null)
if (Stream != null)
{
Stream.Flush();
Stream.Close();

View File

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

View File

@ -0,0 +1,217 @@
using GTA.UI;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
namespace RageCoop.Client
{
internal static partial class Networking
{
public static CoopPeer Peer;
public static float Latency => ServerConnection.AverageRoundtripTime / 2;
public static bool ShowNetworkInfo = false;
public static Security Security;
public static NetConnection ServerConnection;
private static readonly Dictionary<int, Action<PacketType, NetIncomingMessage>> PendingResponses = new Dictionary<int, Action<PacketType, NetIncomingMessage>>();
internal static readonly Dictionary<PacketType, Func<NetIncomingMessage, Packet>> RequestHandlers = new Dictionary<PacketType, Func<NetIncomingMessage, Packet>>();
internal static float SimulatedLatency = 0;
public static bool IsConnecting { get; private set; }
public static IPEndPoint _targetServerEP;
static Networking()
{
Security = new Security(Main.Logger);
Packets.CustomEvent.ResolveHandle = _resolveHandle;
}
public static void ToggleConnection(string address, string username = null, string password = null, PublicKey publicKey = null)
{
Menus.CoopMenu.Menu.Visible = false;
Peer?.Shutdown("Bye");
if (IsOnServer)
{
// ?
}
else if (IsConnecting)
{
_publicKeyReceived.Set();
IsConnecting = false;
Notification.Show("Connection has been canceled");
}
else
{
Peer?.Dispose();
IsConnecting = true;
password = password ?? Main.Settings.Password;
username = username ?? Main.Settings.Username;
// 623c92c287cc392406e7aaaac1c0f3b0 = RAGECOOP
NetPeerConfiguration config = new NetPeerConfiguration("623c92c287cc392406e7aaaac1c0f3b0")
{
AutoFlushSendQueue = false,
SimulatedMinimumLatency = SimulatedLatency,
SimulatedRandomLatency = 0,
AcceptIncomingConnections = true,
MaximumConnections = 32,
PingInterval = 5
};
config.EnableMessageType(NetIncomingMessageType.UnconnectedData);
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
string[] ip = new string[2];
int idx = address.LastIndexOf(':');
if (idx != -1)
{
ip[0] = address.Substring(0, idx);
ip[1] = address.Substring(idx + 1);
}
if (ip.Length != 2)
{
throw new Exception("Malformed URL");
}
PlayerList.Cleanup();
EntityPool.AddPlayer();
if (publicKey == null && !string.IsNullOrEmpty(password) && !Menus.CoopMenu.ShowPopUp("", "WARNING", "Server's IP can be spoofed when using direct connection, do you wish to continue?", "", true))
{
IsConnecting = false;
return;
}
Task.Run(() =>
{
try
{
_targetServerEP = CoreUtils.StringToEndPoint(address);
// Ensure static constructor invocation
DownloadManager.Cleanup();
Peer = new CoopPeer(config);
Peer.OnMessageReceived += (s, m) =>
{
try { ProcessMessage(m); }
catch (Exception ex) { Main.Logger.Error(ex); }
};
API.QueueAction(() => { Notification.Show($"~y~Trying to connect..."); });
Menus.CoopMenu._serverConnectItem.Enabled = false;
Security.Regen();
if (publicKey == null)
{
if (!GetServerPublicKey(ip[0], int.Parse(ip[1])))
{
Menus.CoopMenu._serverConnectItem.Enabled = true;
throw new TimeoutException("Failed to retrive server's public key");
}
}
else
{
Security.SetServerPublicKey(publicKey.Modulus, publicKey.Exponent);
}
// Send handshake packet
NetOutgoingMessage outgoingMessage = Peer.CreateMessage();
var handshake = new Packets.Handshake()
{
PedID = Main.LocalPlayerID,
Username = username,
ModVersion = Main.Version.ToString(),
PasswordEncrypted = Security.Encrypt(password.GetBytes()),
InternalEndPoint = new System.Net.IPEndPoint(CoreUtils.GetLocalAddress(ip[0]), Peer.Port)
};
Security.GetSymmetricKeysCrypted(out handshake.AesKeyCrypted, out handshake.AesIVCrypted);
handshake.Pack(outgoingMessage);
ServerConnection = Peer.Connect(ip[0], short.Parse(ip[1]), outgoingMessage);
}
catch (Exception ex)
{
Main.Logger.Error("Cannot connect to server: ", ex);
API.QueueAction(() => Notification.Show("Cannot connect to server: " + ex.Message));
}
IsConnecting = false;
});
}
}
public static bool IsOnServer => ServerConnection?.Status == NetConnectionStatus.Connected;
#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);
Main.Logger.Debug($"player connected:{p.Username}");
API.QueueAction(() =>
GTA.UI.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);
GTA.UI.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
private static int NewRequestID()
{
int ID = 0;
while ((ID == 0) || PendingResponses.ContainsKey(ID))
{
byte[] rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
return ID;
}
}
}

View File

@ -0,0 +1,409 @@
using GTA;
using Lidgren.Network;
using RageCoop.Client.Menus;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using System;
using System.Threading;
namespace RageCoop.Client
{
internal static partial class Networking
{
/// <summary>
/// Reduce GC pressure by reusing frequently used packets
/// </summary>
private static class ReceivedPackets
{
public static Packets.PedSync PedPacket = new Packets.PedSync();
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
}
/// <summary>
/// Used to reslove entity handle in a <see cref="Packets.CustomEvent"/>
/// </summary>
private static readonly Func<byte, NetIncomingMessage, object> _resolveHandle = (t, reader) =>
{
switch (t)
{
case 50:
return EntityPool.ServerProps[reader.ReadInt32()].MainProp?.Handle;
case 51:
return EntityPool.GetPedByID(reader.ReadInt32())?.MainPed?.Handle;
case 52:
return EntityPool.GetVehicleByID(reader.ReadInt32())?.MainVehicle?.Handle;
case 60:
return EntityPool.ServerBlips[reader.ReadInt32()].Handle;
default:
throw new ArgumentException("Cannot resolve server side argument: " + t);
}
};
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
private static bool _recycle;
public static void ProcessMessage(NetIncomingMessage message)
{
if (message == null) { return; }
_recycle = true;
switch (message.MessageType)
{
case NetIncomingMessageType.StatusChanged:
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
string 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);
}
Main.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;
Main.Logger.Debug($"Direct connection to {player.Username} established");
}
else
{
Main.Logger.Info($"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
message.SenderConnection.Disconnect("eat poop");
}
}
break;
case NetConnectionStatus.Disconnected:
if (message.SenderConnection == ServerConnection)
{
Main.Disconnected(reason);
}
break;
}
break;
case NetIncomingMessageType.Data:
{
if (message.LengthBytes == 0) { break; }
var packetType = PacketType.Unknown;
try
{
// Get packet type
packetType = (PacketType)message.ReadByte();
switch (packetType)
{
case PacketType.Response:
{
int id = message.ReadInt32();
if (PendingResponses.TryGetValue(id, out var callback))
{
callback((PacketType)message.ReadByte(), message);
PendingResponses.Remove(id);
}
break;
}
case PacketType.Request:
{
int id = message.ReadInt32();
var realType = (PacketType)message.ReadByte();
if (RequestHandlers.TryGetValue(realType, out var handler))
{
var response = Peer.CreateMessage();
response.Write((byte)PacketType.Response);
response.Write(id);
handler(message).Pack(response);
Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered, message.SequenceChannel);
Peer.FlushSendQueue();
}
else
{
Main.Logger.Debug("Did not find a request handler of type: " + realType);
}
break;
}
default:
{
HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
break;
}
}
}
catch (Exception ex)
{
API.QueueAction(() =>
{
GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}");
return true;
});
Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.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:
Main.Logger.Trace(message.ReadString());
break;
default:
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:
{
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
packet.Deserialize(msg);
API.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketType.Voice:
{
if (Main.Settings.Voice)
{
Packets.Voice packet = new Packets.Voice();
packet.Deserialize(msg);
SyncedPed player = EntityPool.GetPedByID(packet.ID);
player.IsSpeaking = true;
player.LastSpeakingTime = Main.Ticked;
Voice.AddVoiceData(packet.Buffer, packet.Recorded);
}
}
break;
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent();
if (((CustomEventFlags)msg.PeekByte()).HasEventFlag(CustomEventFlags.Queued))
{
recycle = false;
API.QueueAction(() =>
{
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
Peer.Recycle(msg);
});
}
else
{
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
}
break;
case PacketType.FileTransferChunk:
{
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
packet.Deserialize(msg);
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
default:
if (packetType.IsSyncEvent())
{
recycle = false;
// Dispatch to script thread
API.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); Peer.Recycle(msg); return true; });
}
break;
}
}
private static void PedSync(Packets.PedSync packet)
{
SyncedPed c = EntityPool.GetPedByID(packet.ID);
if (c == null)
{
// Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
}
PedDataFlags 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;
c.LastSyncedStopWatch.Restart();
if (c.IsRagdoll)
{
c.HeadPosition = packet.HeadPosition;
c.RightFootPosition = packet.RightFootPosition;
c.LeftFootPosition = packet.LeftFootPosition;
}
else if (c.Speed >= 4)
{
c.VehicleID = packet.VehicleID;
c.Seat = packet.Seat;
}
c.LastSynced = Main.Ticked;
if (c.IsAiming)
{
c.AimCoords = packet.AimCoords;
}
if (packet.Flags.HasPedFlag(PedDataFlags.IsFullSync))
{
c.CurrentWeaponHash = packet.CurrentWeaponHash;
c.Clothes = packet.Clothes;
c.WeaponComponents = packet.WeaponComponents;
c.WeaponTint = packet.WeaponTint;
c.Model = packet.ModelHash;
c.BlipColor = packet.BlipColor;
c.BlipSprite = packet.BlipSprite;
c.BlipScale = packet.BlipScale;
c.LastFullSynced = Main.Ticked;
}
}
private static void VehicleSync(Packets.VehicleSync packet)
{
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
if (v == null)
{
EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
}
if (v.IsLocal) { return; }
v.ID = packet.ID;
v.OwnerID = packet.OwnerID;
v.Flags = packet.Flags;
v.Position = packet.Position;
v.Quaternion = packet.Quaternion;
v.SteeringAngle = packet.SteeringAngle;
v.ThrottlePower = packet.ThrottlePower;
v.BrakePower = packet.BrakePower;
v.Velocity = packet.Velocity;
v.RotationVelocity = packet.RotationVelocity;
v.DeluxoWingRatio = packet.DeluxoWingRatio;
v.LastSynced = Main.Ticked;
v.LastSyncedStopWatch.Restart();
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
v.DamageModel = packet.DamageModel;
v.EngineHealth = packet.EngineHealth;
v.Mods = packet.Mods;
v.Model = packet.ModelHash;
v.Colors = packet.Colors;
v.LandingGear = packet.LandingGear;
v.RoofState = (VehicleRoofState)packet.RoofState;
v.LockStatus = packet.LockStatus;
v.RadioStation = packet.RadioStation;
v.LicensePlate = packet.LicensePlate;
v.Livery = packet.Livery;
v.LastFullSynced = Main.Ticked;
}
}
private static void ProjectileSync(Packets.ProjectileSync packet)
{
var p = EntityPool.GetProjectileByID(packet.ID);
if (p == null)
{
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) { return; }
// Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID));
}
p.Flags = packet.Flags;
p.Position = packet.Position;
p.Rotation = packet.Rotation;
p.Velocity = packet.Velocity;
p.WeaponHash = (WeaponHash)packet.WeaponHash;
p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle) ?
(SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID) : EntityPool.GetPedByID(packet.ShooterID);
p.LastSynced = Main.Ticked;
p.LastSyncedStopWatch.Restart();
}
}
}

View File

@ -0,0 +1,208 @@
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System;
using System.Collections.Generic;
namespace RageCoop.Client
{
internal static partial class Networking
{
/// <summary>
/// Reduce GC pressure by reusing frequently used packets
/// </summary>
private static class SendPackets
{
public static Packets.PedSync PedPacket = new Packets.PedSync();
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
}
public static int SyncInterval = 30;
public static List<NetConnection> Targets = new List<NetConnection>();
public static void SendSync(Packet p, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
Peer.SendTo(p, Targets, channel, method);
}
public static void SendPed(SyncedPed sp, bool full)
{
if (sp.LastSentStopWatch.ElapsedMilliseconds < SyncInterval)
{
return;
}
Ped ped = sp.MainPed;
var p = SendPackets.PedPacket;
p.ID = sp.ID;
p.OwnerID = sp.OwnerID;
p.Health = ped.Health;
p.Rotation = ped.ReadRotation();
p.Velocity = ped.ReadVelocity();
p.Speed = ped.GetPedSpeed();
p.Flags = ped.GetPedFlags();
p.Heading = ped.Heading;
if (p.Flags.HasPedFlag(PedDataFlags.IsAiming))
{
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) { Main.Logger.Error("Invalid vehicle"); }
if (p.Speed == 5)
{
p.Seat = ped.GetSeatTryingToEnter();
}
else
{
p.Seat = ped.SeatIndex;
}
if (!veh.IsLocal && p.Speed == 4 && p.Seat == VehicleSeat.Driver)
{
veh.OwnerID = Main.LocalPlayerID;
SyncEvents.TriggerChangeOwner(veh.ID, Main.LocalPlayerID);
}
}
p.Position = ped.ReadPosition();
}
sp.LastSentStopWatch.Restart();
if (full)
{
var w = ped.VehicleWeapon;
p.CurrentWeaponHash = (w != VehicleWeaponHash.Invalid) ? (uint)w : (uint)ped.Weapons.Current.Hash;
p.Flags |= PedDataFlags.IsFullSync;
p.Clothes = ped.GetPedClothes();
p.ModelHash = ped.Model.Hash;
p.WeaponComponents = ped.Weapons.Current.GetWeaponComponents();
p.WeaponTint = (byte)Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
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;
}
Vehicle 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.RotationVelocity;
packet.ThrottlePower = veh.ThrottlePower;
packet.BrakePower = veh.BrakePower;
v.LastSentStopWatch.Restart();
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering)) { packet.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio(); }
if (full)
{
byte primaryColor = 0;
byte secondaryColor = 0;
unsafe
{
Function.Call<byte>(Hash.GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
}
packet.Flags |= VehicleDataFlags.IsFullSync;
packet.Colors = new byte[] { primaryColor, secondaryColor };
packet.DamageModel = veh.GetVehicleDamageModel();
packet.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
packet.RoofState = (byte)veh.RoofState;
packet.Mods = veh.Mods.GetVehicleMods();
packet.ModelHash = veh.Model.Hash;
packet.EngineHealth = veh.EngineHealth;
packet.LockStatus = veh.LockStatus;
packet.LicensePlate = Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
packet.Livery = Function.Call<int>(Hash.GET_VEHICLE_LIVERY, veh);
if (v.MainVehicle == Game.Player.LastVehicle)
{
packet.RadioStation = Util.GetPlayerRadioIndex();
}
if (packet.EngineHealth > v.LastEngineHealth)
{
packet.Flags |= VehicleDataFlags.Repaired;
}
v.LastEngineHealth = packet.EngineHealth;
}
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);
}
#region SYNC EVENTS
public static void SendBullet(Vector3 start, Vector3 end, uint weapon, int ownerID)
{
SendSync(new Packets.BulletShot()
{
StartPosition = start,
EndPosition = end,
OwnerID = ownerID,
WeaponHash = weapon,
}, ConnectionChannel.SyncEvents);
}
public static void SendVehicleBullet(uint hash, SyncedPed owner, EntityBone b)
{
SendSync(new Packets.VehicleBulletShot
{
StartPosition = b.Position,
EndPosition = b.Position + b.ForwardVector,
OwnerID = owner.ID,
Bone = (ushort)b.Index,
WeaponHash = hash
});
}
#endregion
public static void SendChatMessage(string message)
{
Peer.SendTo(new Packets.ChatMessage(new Func<string, byte[]>((s) => Security.Encrypt(s.GetBytes())))
{ Username = Main.Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat, NetDeliveryMethod.ReliableOrdered);
Peer.FlushSendQueue();
}
public static void SendVoiceMessage(byte[] buffer, int recorded)
{
SendSync(new Packets.Voice() { ID = Main.LocalPlayerID, Buffer = buffer, Recorded = recorded }, ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
}
}
}

View File

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

View File

@ -0,0 +1,169 @@
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using RageCoop.Core;
using System.Collections.Generic;
using System.Linq;
using System.Net;
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 ulong Pressed { get; set; }
public static bool LeftAlign = true;
public static Dictionary<int, Player> Players = new Dictionary<int, Player> { };
public static void Tick()
{
if (!Networking.IsOnServer)
{
return;
}
if ((Util.GetTickCount64() - _lastUpdate) >= 1000)
{
Update();
}
if ((Util.GetTickCount64() - Pressed) < 5000 && !Main.MainChat.Focused
#if !NON_INTERACTIVE
&& !Menus.CoopMenu.MenuPool.AreAnyVisible
#endif
)
{
Function.Call(Hash.DRAW_SCALEFORM_MOVIE, _mainScaleform.Handle,
LeftAlign ? LEFT_POSITION : RIGHT_POSITION, 0.3f,
0.28f, 0.6f,
255, 255, 255, 255, 0);
}
}
private static void Update()
{
_lastUpdate = Util.GetTickCount64();
_mainScaleform.CallFunction("SET_DATA_SLOT_EMPTY", 0);
int 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)
{
Main.Logger.Debug($"{id},{username},{latency}");
if (Players.TryGetValue(id, out Player 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 Player 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> { };
}
}
public class Player
{
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 IPEndPoint InternalEndPoint { get; internal set; }
public IPEndPoint ExternalEndPoint { get; internal set; }
internal bool ConnectWhenPunched { get; set; }
public Blip FakeBlip { get; internal set; }
public Vector3 Position { get; internal set; }
public SyncedPed Character { 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 => Main.LocalPlayerID == ID ? Networking.Latency * 2 : (HasDirectConnection ? Connection.AverageRoundtripTime : _latencyToServer * 2);
public float PacketTravelTime => HasDirectConnection ? Connection.AverageRoundtripTime / 2 : Networking.Latency + _latencyToServer;
internal float _latencyToServer = 0;
public bool DisplayNameTag { get; set; } = true;
public NetConnection Connection { get; internal set; }
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 informationr(
[assembly: AssemblyVersion("1.5.6.1")]
[assembly: AssemblyFileVersion("1.5.6.1")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]

View File

@ -0,0 +1,46 @@
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#
string output = File.ReadAllText(this.Host.ResolvePath("AssemblyInfo.cs"));
Regex pattern = new Regex("AssemblyVersion\\(\"(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<revision>\\d+)\\.(?<build>\\d+)\"\\)");
MatchCollection matches = pattern.Matches(output);
if( matches.Count == 1 )
{
major = Convert.ToInt32(matches[0].Groups["major"].Value);
minor = Convert.ToInt32(matches[0].Groups["minor"].Value);
build = Convert.ToInt32(matches[0].Groups["build"].Value) + 1;
revision = Convert.ToInt32(matches[0].Groups["revision"].Value);
if( this.Host.ResolveParameterValue("-","-","BuildConfiguration") == "Release" )
revision++;
}
#>
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 informationr(
[assembly: AssemblyVersion("<#= this.major #>.<#= this.minor #>.<#= this.revision #>.<#= this.build #>")]
[assembly: AssemblyFileVersion("<#= this.major #>.<#= this.minor #>.<#= this.revision #>.<#= this.build #>")]
[assembly: NeutralResourcesLanguageAttribute( "en-US" )]
<#+
int major = 1;
int minor = 0;
int revision = 0;
int build = 0;
#>

View File

@ -0,0 +1,157 @@
<?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>
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RageCoop.Client</RootNamespace>
<AssemblyName>RageCoop.Client</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<OutPutPath>..\..\bin\Debug\Client</OutPutPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>DEBUG</DefineConstants>
<WarningLevel>4</WarningLevel>
<NoWarn>1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutPutPath>..\..\bin\Release\Client</OutPutPath>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="Debug.cs" />
<Compile Include="DevTools\DevTool.cs" />
<Compile Include="Main.cs" />
<Compile Include="Menus\CoopMenu.cs" />
<Compile Include="Menus\Sub\DebugMenu.cs" />
<Compile Include="Menus\Sub\DevToolMenu.cs" />
<Compile Include="Menus\Sub\ServersMenu.cs" />
<Compile Include="Menus\Sub\SettingsMenu.cs" />
<Compile Include="Menus\Sub\UpdateMenu.cs" />
<Compile Include="Networking\Chat.cs" />
<Compile Include="Networking\DownloadManager.cs" />
<Compile Include="Networking\HolePunch.cs" />
<Compile Include="Networking\Networking.cs" />
<Compile Include="Networking\Receive.cs" />
<Compile Include="Networking\Send.cs" />
<Compile Include="Networking\Statistics.cs" />
<Compile Include="PlayerList.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AssemblyInfo.tt</DependentUpon>
</Compile>
<Compile Include="Scripting\API.cs" />
<Compile Include="Scripting\BaseScript.cs" />
<Compile Include="Scripting\ClientScript.cs" />
<Compile Include="Scripting\Resources.cs" />
<Compile Include="Security.cs" />
<Compile Include="Settings.cs" />
<Compile Include="Sync\Entities\Ped\SyncedPed.Members.cs" />
<Compile Include="Sync\Entities\Ped\SyncedPed.Animations.cs" />
<Compile Include="Sync\Entities\SyncedEntity.cs" />
<Compile Include="Sync\Entities\Ped\SyncedPed.cs" />
<Compile Include="Sync\Entities\SyncedProjectile.cs" />
<Compile Include="Sync\Entities\SyncedProp.cs" />
<Compile Include="Sync\Entities\Vehicle\SyncedVehicle.cs" />
<Compile Include="Sync\Entities\Vehicle\SyncedVehicle.Members.cs" />
<Compile Include="Sync\EntityPool.cs" />
<Compile Include="Sync\SyncEvents.cs" />
<Compile Include="Sync\Voice.cs" />
<Compile Include="Util\AddOnDataProvider.cs" />
<Compile Include="Util\Memory.cs" />
<Compile Include="Util\NativeCaller.cs" />
<Compile Include="Util\PedConfigFlags.cs" />
<Compile Include="Util\PedExtensions.cs" />
<Compile Include="Util\TaskType.cs" />
<Compile Include="Util\Util.cs" />
<Compile Include="Util\VehicleExtensions.cs" />
<Compile Include="Util\WeaponUtil.cs" />
<Compile Include="WorldThread.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.4.0.12, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\..\packages\SharpZipLib.1.4.0\lib\netstandard2.0\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="LemonUI.SHVDN3">
<HintPath>..\..\libs\LemonUI.SHVDN3.dll</HintPath>
</Reference>
<Reference Include="Lidgren.Network">
<HintPath>..\..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Extensions.ObjectPool, Version=6.0.8.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.ObjectPool.6.0.8\lib\net461\Microsoft.Extensions.ObjectPool.dll</HintPath>
</Reference>
<Reference Include="NAudio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.2.1.0\lib\net472\NAudio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Asio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Asio.2.1.0\lib\netstandard2.0\NAudio.Asio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Core.2.1.0\lib\netstandard2.0\NAudio.Core.dll</HintPath>
</Reference>
<Reference Include="NAudio.Midi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Midi.2.1.0\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
</Reference>
<Reference Include="NAudio.Wasapi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.Wasapi.2.1.0\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinForms, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.WinForms.2.1.0\lib\net472\NAudio.WinForms.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinMM, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\..\packages\NAudio.WinMM.2.1.0\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet">
<HintPath>..\..\libs\ScriptHookVDotNet.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Properties\AssemblyInfo.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
</Content>
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj">
<Project>{cc2e8102-e568-4524-aa1f-f8e0f1cfe58a}</Project>
<Name>RageCoop.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Loader\RageCoop.Client.Loader.csproj">
<Project>{fc8cbdbb-6dc3-43af-b34d-092e476410a5}</Project>
<Name>RageCoop.Client.Loader</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup Condition=" '$(DevEnvDir)' != '*Undefined*'">
<PostBuildEvent>"$(DevEnvDir)TextTransform.exe" -a !!BuildConfiguration!$(Configuration) "$(ProjectDir)Properties\AssemblyInfo.tt"</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -1,10 +1,13 @@
#undef DEBUG
using System.Collections.Generic;
using System;
using System.Linq;
using RageCoop.Core;
using System.Windows.Forms;
using GTA;
using Newtonsoft.Json;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
namespace RageCoop.Client.Scripting
{
@ -40,7 +43,7 @@ namespace RageCoop.Client.Scripting
/// </summary>
public static string Username
{
get { return Main.Settings.Username; }
get => Main.Settings.Username;
set
{
if (Networking.IsOnServer || string.IsNullOrEmpty(value))
@ -64,7 +67,7 @@ namespace RageCoop.Client.Scripting
/// 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>
@ -116,16 +119,22 @@ namespace RageCoop.Client.Scripting
/// <summary>
/// This is equivalent of <see cref="GTA.Script.Tick"/>.
/// </summary>
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.Tick"/> instead.</remarks>
[Obsolete]
public static event EmptyEvent OnTick;
/// <summary>
/// This is equivalent of <see cref="Script.KeyDown"/>
/// </summary>
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyDown"/> instead.</remarks>
[Obsolete]
public static KeyEventHandler OnKeyDown;
/// <summary>
/// This is equivalent of <see cref="Script.KeyUp"/>
/// </summary>
/// <remarks>Calling <see cref="GTA.Script.Yield"/> in the handler will interfer other scripts, subscribe to <see cref="GTA.Script.KeyUp"/> instead.</remarks>
[Obsolete]
public static KeyEventHandler OnKeyUp;
#region INVOKE
@ -136,18 +145,17 @@ namespace RageCoop.Client.Scripting
internal static void InvokePlayerDied() { OnPlayerDied?.Invoke(); }
internal static void InvokeTick() { OnTick?.Invoke(); }
internal static void InvokeKeyDown(object s,KeyEventArgs e) { OnKeyDown?.Invoke(s,e); }
internal static void InvokeKeyDown(object s, KeyEventArgs e) { OnKeyDown?.Invoke(s, e); }
internal static void InvokeKeyUp(object s, KeyEventArgs e) { OnKeyUp?.Invoke(s, e); }
internal static void InvokeCustomEventReceived(Packets.CustomEvent p)
{
var args = new CustomEventReceivedArgs() { Hash=p.Hash, Args=p.Args};
var args = new CustomEventReceivedArgs() { Hash = p.Hash, Args = p.Args };
// Main.Logger.Debug($"CustomEvent:\n"+args.Args.DumpWithType());
List<Action<CustomEventReceivedArgs>> handlers;
if (CustomEventHandlers.TryGetValue(p.Hash, out handlers))
if (CustomEventHandlers.TryGetValue(p.Hash, out List<Action<CustomEventReceivedArgs>> handlers))
{
handlers.ForEach((x) => { x.Invoke(args); });
}
@ -161,58 +169,84 @@ namespace RageCoop.Client.Scripting
/// Get the local player's ID
/// </summary>
/// <returns>PlayerID</returns>
public static int LocalPlayerID
{
get { return Main.LocalPlayerID; }
}
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 System.Net.IPEndPoint ServerEndPoint => Networking.IsOnServer ? Networking.ServerConnection?.RemoteEndPoint : null;
/// <summary>
/// Check if a RAGECOOP menu is visible
/// </summary>
public static bool IsMenuVisible
{
get { return Menus.CoopMenu.MenuPool.AreAnyVisible; }
}
public static bool IsMenuVisible => Menus.CoopMenu.MenuPool.AreAnyVisible;
/// <summary>
/// Check if the RAGECOOP chat is visible
/// </summary>
public static bool IsChatFocused
{
get { return Main.MainChat.Focused; }
}
public static bool IsChatFocused => Main.MainChat.Focused;
/// <summary>
/// Check if the RAGECOOP list of players is visible
/// </summary>
public static bool IsPlayerListVisible
{
get { return Util.GetTickCount64() - PlayerList.Pressed < 5000; }
}
public static bool IsPlayerListVisible => Util.GetTickCount64() - PlayerList.Pressed < 5000;
/// <summary>
/// Get the version of RAGECOOP
/// </summary>
public static string CurrentVersion
{
get { return Main.CurrentVersion; }
}
public static Version CurrentVersion => Main.Version;
/// <summary>
/// Get a <see cref="Core.Logger"/> that RAGECOOP is currently using.
/// </summary>
/// <returns></returns>
public static Logger Logger
{
get
{
return Main.Logger;
}
}
public static Logger Logger => Main.Logger;
/// <summary>
/// Get all players indexed by their ID
/// </summary>
public static Dictionary<int, Player> Players => new Dictionary<int, Player>(PlayerList.Players);
#endregion
#region FUNCTIONS
/// <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>
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>
public static void Disconnect()
{
if (Networking.IsOnServer || Networking.IsConnecting)
{
Networking.ToggleConnection(null);
}
}
/// <summary>
/// List all servers from master server address
/// </summary>
/// <returns></returns>
public static List<ServerInfo> ListServers()
{
return JsonConvert.DeserializeObject<List<ServerInfo>>(HttpHelper.DownloadString(Main.Settings.MasterServer));
}
/// <summary>
/// Send a local chat message to this player
/// </summary>
@ -224,19 +258,12 @@ namespace RageCoop.Client.Scripting
}
/// <summary>
/// Queue an action to be executed on next tick.
/// Send a chat message or command to server/other players
/// </summary>
/// <param name="a"></param>
public static void QueueAction(Action a)
/// <param name="message"></param>
public static void SendChatMessage(string message)
{
Main.QueueAction(a);
}
/// <summary>
/// Disconnect from the server
/// </summary>
public static void Disconnect()
{
Networking.ToggleConnection(null);
Networking.SendChatMessage(message);
}
/// <summary>
@ -244,14 +271,28 @@ namespace RageCoop.Client.Scripting
/// </summary>
/// <param name="eventHash">An unique identifier of the event</param>
/// <param name="args">The objects conataing your data, see <see cref="CustomEventReceivedArgs"/> for a list of supported types</param>
public static void SendCustomEvent(int eventHash, params object[] args)
public static void SendCustomEvent(CustomEventHash eventHash, params object[] args)
{
var p = new Packets.CustomEvent()
Networking.Peer.SendTo(new Packets.CustomEvent()
{
Args=args,
Hash=eventHash
};
Networking.Send(p, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
Args = args,
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <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)
{
Networking.Peer.SendTo(new Packets.CustomEvent(flags)
{
Args = args,
Hash = eventHash
}, Networking.ServerConnection, ConnectionChannel.Event, Lidgren.Network.NetDeliveryMethod.ReliableOrdered);
}
/// <summary>
@ -259,7 +300,7 @@ namespace RageCoop.Client.Scripting
/// </summary>
/// <param name="hash">An unique identifier of the event, you can hash your event name with <see cref="Core.Scripting.CustomEvents.Hash(string)"/></param>
/// <param name="handler">An handler to be invoked when the event is received from the server. </param>
public static void RegisterCustomEventHandler(int hash, Action<CustomEventReceivedArgs> handler)
public static void RegisterCustomEventHandler(CustomEventHash hash, Action<CustomEventReceivedArgs> handler)
{
lock (CustomEventHandlers)
{
@ -270,6 +311,70 @@ namespace RageCoop.Client.Scripting
handlers.Add(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);
}
});
}
#endregion
/// <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; }
else 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);
}
}
}

View File

@ -1,27 +1,26 @@
using System;
using System.Collections.Generic;
using GTA.Native;
using GTA;
using GTA.Math;
using GTA;
using RageCoop.Core;
using GTA.Native;
using RageCoop.Core.Scripting;
using System.Linq;
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace RageCoop.Client.Scripting
{
internal class BaseScript : ClientScript
internal static class BaseScript
{
private bool _isHost=false;
public override void OnStart()
private static bool _isHost = false;
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.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.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);
@ -30,8 +29,9 @@ namespace RageCoop.Client.Scripting
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.IsHost, (e) => { _isHost = (bool)e.Args[0]; });
API.RegisterCustomEventHandler(CustomEvents.WeatherTimeSync, WeatherTimeSync);
API.RegisterCustomEventHandler(CustomEvents.OnPlayerDied, (e) => { GTA.UI.Notification.Show($"~h~{e.Args[0]}~h~ died."); });
Task.Run(() =>
{
while (true)
@ -57,59 +57,58 @@ namespace RageCoop.Client.Scripting
});
}
private void WeatherTimeSync(CustomEventReceivedArgs e)
private static void WeatherTimeSync(CustomEventReceivedArgs e)
{
World.CurrentTimeOfDay=new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
World.CurrentTimeOfDay = new TimeSpan((int)e.Args[0], (int)e.Args[1], (int)e.Args[2]);
Function.Call(Hash._SET_WEATHER_TYPE_TRANSITION, (int)e.Args[3], (int)e.Args[4], (float)e.Args[5]);
}
private void SetDisplayNameTag(CustomEventReceivedArgs e)
private static void SetDisplayNameTag(CustomEventReceivedArgs e)
{
var p = PlayerList.GetPlayer((int)e.Args[0]);
if(p != null) { p.DisplayNameTag=(bool)e.Args[1]; }
if (p != null) { p.DisplayNameTag = (bool)e.Args[1]; }
}
private void UpdatePedBlip(CustomEventReceivedArgs e)
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)
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];
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];
if (b == null) { b = p.AddBlip(); }
b.Color = (BlipColor)(byte)e.Args[1];
b.Sprite = (BlipSprite)(ushort)e.Args[2];
b.Scale = (float)e.Args[3];
}
}
private void CreateVehicle(CustomEventReceivedArgs e)
private static void CreateVehicle(CustomEventReceivedArgs e)
{
var vehicleModel = (Model)e.Args[1];
vehicleModel.Request(1000);
Vehicle veh= World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
while (veh==null)
Vehicle veh;
while ((veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3])) == null)
{
veh = World.CreateVehicle(vehicleModel, (Vector3)e.Args[2], (float)e.Args[3]);
Thread.Sleep(10);
}
veh.CanPretendOccupants=false;
veh.CanPretendOccupants = false;
var v = new SyncedVehicle()
{
ID=(int)e.Args[0],
MainVehicle=veh,
OwnerID=Main.LocalPlayerID,
ID = (int)e.Args[0],
MainVehicle = veh,
OwnerID = Main.LocalPlayerID,
};
EntityPool.Add(v);
}
private void DeleteServerBlip(CustomEventReceivedArgs e)
private static void DeleteServerBlip(CustomEventReceivedArgs e)
{
if (EntityPool.ServerBlips.TryGetValue((int)e.Args[0], out var blip))
{
@ -118,19 +117,18 @@ namespace RageCoop.Client.Scripting
}
}
private void ServerBlipSync(CustomEventReceivedArgs obj)
private static void ServerBlipSync(CustomEventReceivedArgs obj)
{
int id= (int)obj.Args[0];
var sprite=(BlipSprite)(ushort)obj.Args[1];
int id = (int)obj.Args[0];
var sprite = (BlipSprite)(ushort)obj.Args[1];
var color = (BlipColor)(byte)obj.Args[2];
var scale=(float)obj.Args[3];
var pos=(Vector3)obj.Args[4];
int rot= (int)obj.Args[5];
var name=(string)obj.Args[6];
Blip blip;
if (!EntityPool.ServerBlips.TryGetValue(id, out blip))
var scale = (float)obj.Args[3];
var pos = (Vector3)obj.Args[4];
int rot = (int)obj.Args[5];
var name = (string)obj.Args[6];
if (!EntityPool.ServerBlips.TryGetValue(id, out Blip blip))
{
EntityPool.ServerBlips.Add(id, blip=World.CreateBlip(pos));
EntityPool.ServerBlips.Add(id, blip = World.CreateBlip(pos));
}
blip.Sprite = sprite;
blip.Color = color;
@ -141,27 +139,24 @@ namespace RageCoop.Client.Scripting
}
private void DeleteEntity(CustomEventReceivedArgs e)
private static void DeleteEntity(CustomEventReceivedArgs e)
{
Entity.FromHandle((int)e.Args[0])?.Delete();
}
public override void OnStop()
private static void SetNameTag(CustomEventReceivedArgs e)
{
}
private void SetNameTag(CustomEventReceivedArgs e)
{
var p =PlayerList.GetPlayer((int)e.Args[0]);
if(p!= null)
var p = PlayerList.GetPlayer((int)e.Args[0]);
if (p != null)
{
p.DisplayNameTag=(bool)e.Args[1];
p.DisplayNameTag = (bool)e.Args[1];
}
}
private void SetAutoRespawn(CustomEventReceivedArgs args)
private static void SetAutoRespawn(CustomEventReceivedArgs args)
{
API.Config.EnableAutoRespawn=(bool)args.Args[0];
API.Config.EnableAutoRespawn = (bool)args.Args[0];
}
private void DeleteServerProp(CustomEventReceivedArgs e)
private static void DeleteServerProp(CustomEventReceivedArgs e)
{
var id = (int)e.Args[0];
if (EntityPool.ServerProps.TryGetValue(id, out var prop))
@ -169,9 +164,9 @@ namespace RageCoop.Client.Scripting
EntityPool.ServerProps.Remove(id);
prop?.MainProp?.Delete();
}
}
private void ServerObjectSync(CustomEventReceivedArgs e)
private static void ServerObjectSync(CustomEventReceivedArgs e)
{
SyncedProp prop;
var id = (int)e.Args[0];
@ -179,29 +174,30 @@ namespace RageCoop.Client.Scripting
{
if (!EntityPool.ServerProps.TryGetValue(id, out prop))
{
EntityPool.ServerProps.Add(id, prop=new SyncedProp(id));
EntityPool.ServerProps.Add(id, prop = new SyncedProp(id));
}
}
prop.LastSynced=Main.Ticked+1;
prop.ModelHash= (Model)e.Args[1];
prop.Position=(Vector3)e.Args[2];
prop.Rotation=(Vector3)e.Args[3];
prop.LastSynced = Main.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 void NativeCall(CustomEventReceivedArgs e)
private static void NativeCall(CustomEventReceivedArgs e)
{
List<InputArgument> arguments = new List<InputArgument>();
int i;
var ty = (byte)e.Args[0];
TypeCode returnType=(TypeCode)ty;
i = returnType==TypeCode.Empty ? 1 : 2;
TypeCode returnType = (TypeCode)ty;
i = returnType == TypeCode.Empty ? 1 : 2;
var hash = (Hash)e.Args[i++];
for(; i<e.Args.Length;i++)
for (; i < e.Args.Length; i++)
{
arguments.Add(GetInputArgument(e.Args[i]));
}
if (returnType==TypeCode.Empty)
if (returnType == TypeCode.Empty)
{
Function.Call(hash, arguments.ToArray());
return;
@ -272,7 +268,7 @@ namespace RageCoop.Client.Scripting
}
}
private InputArgument GetInputArgument(object obj)
private static InputArgument GetInputArgument(object obj)
{
// Implicit conversion
switch (obj)
@ -300,6 +296,6 @@ namespace RageCoop.Client.Scripting
default:
return null;
}
}
}
}
}

View File

@ -5,15 +5,16 @@ namespace RageCoop.Client.Scripting
/// <summary>
/// Inherit from this class, constructor will be called automatically, but other scripts might have yet been loaded, you should use <see cref="OnStart"/>. to initiate your script.
/// </summary>
public abstract class ClientScript
public abstract class ClientScript : GTA.Script
{
/// <summary>
/// This method would be called from background thread, call <see cref="API.QueueAction(System.Action)"/> to dispatch it to main thread.
/// This method would be called from main thread, right after all script constructors are invoked.
/// </summary>
public abstract void OnStart();
/// <summary>
/// This method would be called from background thread when the client disconnected from the server, you MUST terminate all background jobs/threads in this method.
/// This method would be called from main thread right before the whole <see cref="System.AppDomain"/> is unloded but prior to <see cref="GTA.Script.Aborted"/>.
/// </summary>
public abstract void OnStop();
@ -27,5 +28,10 @@ namespace RageCoop.Client.Scripting
/// </summary>
public ClientResource CurrentResource { get; internal set; }
/// <summary>
/// Eqivalent of <see cref="ClientResource.Logger"/> in <see cref="CurrentResource"/>
/// </summary>
public Core.Logger Logger => CurrentResource.Logger;
}
}

View File

@ -0,0 +1,190 @@
using ICSharpCode.SharpZipLib.Zip;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using SHVDN;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace RageCoop.Client.Scripting
{
/// <summary>
///
/// </summary>
public class ClientResource
{
/// <summary>
/// Name of the resource
/// </summary>
public string Name { get; internal set; }
/// <summary>
/// Directory where the scripts is loaded from
/// </summary>
public string ScriptsDirectory { get; internal set; }
/// <summary>
/// A resource-specific folder that can be used to store your files.
/// </summary>
public string DataFolder { get; internal set; }
/// <summary>
/// Get all <see cref="ClientScript"/> instance in this resource.
/// </summary>
public List<ClientScript> Scripts { get; internal set; } = new List<ClientScript>();
/// <summary>
/// Get the <see cref="ResourceFile"/> where this script is loaded from.
/// </summary>
public Dictionary<string, ResourceFile> Files { get; internal set; } = new Dictionary<string, ResourceFile>();
/// <summary>
/// A <see cref="Core.Logger"/> instance that can be used to debug your resource.
/// </summary>
public Logger Logger { get; internal set; }
}
internal class Resources
{
internal readonly ConcurrentDictionary<string, ClientResource> LoadedResources = new ConcurrentDictionary<string, ClientResource>();
private Logger Logger { get; set; }
public Resources()
{
Logger = Main.Logger;
}
public void Load(string path, string[] zips)
{
LoadedResources.Clear();
foreach (var zip in zips)
{
var zipPath = Path.Combine(path, zip);
Logger?.Info($"Loading resource: {Path.GetFileNameWithoutExtension(zip)}");
Unpack(zipPath, Path.Combine(path, "Data"));
}
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).Where(x => x.CanBeIgnored()).ForEach(x => File.Delete(x));
// Load it in main thread
API.QueueActionAndWait(() =>
{
Main.QueueToMainThreadAndWait(() =>
{
Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories).ForEach(x => ScriptDomain.CurrentDomain.StartScripts(x));
SetupScripts();
});
});
}
public void Unload()
{
StopScripts();
LoadedResources.Clear();
Loader.LoaderContext.RequestUnload();
}
private void Unpack(string zipPath, string dataFolderRoot)
{
var r = new ClientResource()
{
Logger = API.Logger,
Scripts = new List<ClientScript>(),
Name = Path.GetFileNameWithoutExtension(zipPath),
DataFolder = Path.Combine(dataFolderRoot, Path.GetFileNameWithoutExtension(zipPath)),
ScriptsDirectory = Path.Combine(Directory.GetParent(zipPath).FullName, 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 ResourceFile()
{
IsDirectory = true,
Name = dir.Substring(scriptsDir.Length + 1).Replace('\\', '/')
});
}
var assemblies = new Dictionary<ResourceFile, Assembly>();
foreach (var file in Directory.GetFiles(scriptsDir, "*", SearchOption.AllDirectories))
{
if (Path.GetFileName(file).CanBeIgnored())
{
try
{
File.Delete(file);
}
catch (Exception ex)
{
API.Logger.Warning($"Failed to delete API assembly: {file}. This may or may cause some unexpected behaviours.\n{ex}");
}
continue;
}
var relativeName = file.Substring(scriptsDir.Length + 1).Replace('\\', '/');
var rfile = new ResourceFile()
{
GetStream = () => { return new FileStream(file, FileMode.Open, FileAccess.Read); },
IsDirectory = false,
Name = relativeName
};
r.Files.Add(relativeName, rfile);
}
LoadedResources.TryAdd(r.Name, r);
}
private void SetupScripts()
{
foreach (var s in GetClientScripts())
{
try
{
API.Logger.Debug("Starting script: " + s.GetType().FullName);
var script = (ClientScript)s;
if (LoadedResources.TryGetValue(Directory.GetParent(script.Filename).Name, out var r))
{
script.CurrentResource = r;
}
else
{
API.Logger.Warning("Failed to locate resource for script: " + script.Filename);
}
var res = script.CurrentResource;
script.CurrentFile = res?.Files.Values.Where(x => x.Name.ToLower() == script.Filename.Substring(res.ScriptsDirectory.Length + 1).Replace('\\', '/')).FirstOrDefault();
res?.Scripts.Add(script);
script.OnStart();
}
catch (Exception ex)
{
API.Logger.Error($"Failed to start {s.GetType().FullName}", ex);
}
API.Logger.Debug("Started script: " + s.GetType().FullName);
}
}
private void StopScripts()
{
foreach (var s in GetClientScripts())
{
try
{
API.Logger.Debug("Stopping script: " + s.GetType().FullName);
((ClientScript)s).OnStop();
}
catch (Exception ex)
{
API.Logger.Error($"Failed to stop {s.GetType().FullName}", ex);
}
}
}
public static object[] GetClientScripts()
{
return ScriptDomain.CurrentDomain.RunningScripts.Where(x =>
x.ScriptInstance.GetType().IsScript(typeof(ClientScript))).Select(x => x.ScriptInstance).ToArray();
}
}
}

View File

@ -1,44 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using RageCoop.Core;
using System.IO;
using RageCoop.Core;
using System.Security.Cryptography;
namespace RageCoop.Client
{
internal class Security
{
public RSA ServerRSA { get; set; }
public Aes ClientAes { get; set; }=Aes.Create();
private Logger Logger;
public Aes ClientAes { get; set; } = Aes.Create();
private readonly Logger Logger;
public Security(Logger logger)
{
Logger = logger;
ClientAes.GenerateKey();
ClientAes.GenerateIV();
}
public void GetSymmetricKeysCrypted(out byte[] cryptedKey,out byte[] cryptedIV)
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();
}
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 = Aes.Create();
ClientAes.GenerateKey();
ClientAes.GenerateIV();
}

View File

@ -27,12 +27,16 @@ namespace RageCoop.Client
/// Don't use it!
/// </summary>
public bool FlipMenu { get; set; } = false;
/// <summary>
/// Don't use it!
/// </summary>
public bool Voice { get; set; } = false;
/// <summary>
/// LogLevel for RageCoop.
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 2;
public int LogLevel = 1;
/// <summary>
/// The key to open menu
@ -42,7 +46,7 @@ namespace RageCoop.Client
/// <summary>
/// The key to enter a vehicle as passenger.
/// </summary>
public Keys PassengerKey { get; set; }=Keys.G;
public Keys PassengerKey { get; set; } = Keys.G;
/// <summary>
/// Disable world NPC traffic, mission entities won't be affected
@ -57,15 +61,20 @@ namespace RageCoop.Client
/// <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; } = 35;
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; } = 50;
public int WorldPedSoftLimit { get; set; } = 30;
/// <summary>
/// The directory where log and resources downloaded from server will be placed.
/// </summary>
public string DataDirectory { get; set; } = "Scripts\\RageCoop\\Data";
public string DataDirectory { get; set; } = "RageCoop\\Data";
/// <summary>
/// Show the owner name of the entity you're aiming at
/// </summary>
public bool ShowEntityOwnerName { get; set; } = false;
}
}

View File

@ -0,0 +1,156 @@
using GTA;
using GTA.Native;
namespace RageCoop.Client
{
public partial class SyncedPed
{
private void DisplaySpeaking(bool speaking)
{
if (!MainPed.IsHuman)
return;
if (speaking)
{
Function.Call(Hash.PLAY_FACIAL_ANIM, MainPed.Handle, "mic_chatter", "mp_facial");
return;
}
switch (MainPed.Gender)
{
case Gender.Male:
Function.Call(Hash.PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1", "facials@gen_male@variations@normal");
break;
case Gender.Female:
Function.Call(Hash.PLAY_FACIAL_ANIM, MainPed.Handle, "mood_normal_1", "facials@gen_female@variations@normal");
break;
default:
Function.Call(Hash.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 (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed, animDict, ourAnim, 3))
{
if (LoadAnim(animDict) == null) { return; }
MainPed.Task.ClearAll();
Function.Call(Hash.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(CurrentWeaponHash);
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 unchecked((uint)WeaponHash.Unarmed):
return 0;
case unchecked((uint)WeaponHash.RPG):
case unchecked((uint)WeaponHash.HomingLauncher):
case unchecked((uint)WeaponHash.Firework):
return 5;
case unchecked((uint)WeaponHash.Minigun):
return 5;
case unchecked((uint)WeaponHash.GolfClub):
case unchecked((uint)WeaponHash.PoolCue):
case unchecked((uint)WeaponHash.Bat):
return 4;
case unchecked((uint)WeaponHash.Knife):
case unchecked((uint)WeaponHash.Nightstick):
case unchecked((uint)WeaponHash.Hammer):
case unchecked((uint)WeaponHash.Crowbar):
case unchecked((uint)WeaponHash.Wrench):
case unchecked((uint)WeaponHash.BattleAxe):
case unchecked((uint)WeaponHash.Dagger):
case unchecked((uint)WeaponHash.Hatchet):
case unchecked((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 unchecked(2132975508):
case unchecked((uint)-2084633992):
case unchecked((uint)-952879014):
case unchecked(100416529):
case unchecked((uint)WeaponHash.Gusenberg):
case unchecked((uint)WeaponHash.MG):
case unchecked((uint)WeaponHash.CombatMG):
case unchecked((uint)WeaponHash.CombatPDW):
case unchecked((uint)WeaponHash.AssaultSMG):
case unchecked((uint)WeaponHash.SMG):
case unchecked((uint)WeaponHash.HeavySniper):
case unchecked((uint)WeaponHash.PumpShotgun):
case unchecked((uint)WeaponHash.HeavyShotgun):
case unchecked((uint)WeaponHash.Musket):
case unchecked((uint)WeaponHash.AssaultShotgun):
case unchecked((uint)WeaponHash.BullpupShotgun):
case unchecked((uint)WeaponHash.SawnOffShotgun):
case unchecked((uint)WeaponHash.SweeperShotgun):
case unchecked((uint)WeaponHash.CompactRifle):
return 2;
}
return 1;
}
private string LoadAnim(string anim)
{
if (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
{
Function.Call(Hash.REQUEST_ANIM_DICT, anim);
return null;
}
return anim;
}
}
}

View File

@ -0,0 +1,81 @@
using GTA;
using GTA.Math;
using RageCoop.Core;
using System.Collections.Generic;
namespace RageCoop.Client
{
/// <summary>
/// ?
/// </summary>
public partial class SyncedPed : SyncedEntity
{
internal Blip PedBlip = null;
internal BlipColor BlipColor = (BlipColor)255;
internal BlipSprite BlipSprite = 0;
internal float BlipScale = 1;
internal int VehicleID
{
get => CurrentVehicle?.ID ?? 0;
set
{
if (CurrentVehicle == null || value != CurrentVehicle?.ID)
{
CurrentVehicle = EntityPool.GetVehicleByID(value);
}
}
}
internal SyncedVehicle CurrentVehicle { get; private set; }
internal VehicleSeat Seat;
public bool IsPlayer { get => OwnerID == ID && ID != 0; }
public Ped MainPed { get; internal set; }
internal int Health { get; set; }
internal Vector3 HeadPosition { get; set; }
internal Vector3 RightFootPosition { get; set; }
internal Vector3 LeftFootPosition { get; set; }
internal byte WeaponTint { get; set; }
private bool _lastRagdoll = false;
private ulong _lastRagdollTime = 0;
private bool _lastInCover = false;
private byte[] _lastClothes = null;
internal byte[] Clothes { get; set; }
internal float Heading { get; set; }
internal ulong LastSpeakingTime { get; set; } = 0;
internal bool IsSpeaking { get; set; } = false;
public byte Speed { get; set; }
private bool _lastIsJumping = false;
internal PedDataFlags Flags;
internal bool IsAiming => Flags.HasPedFlag(PedDataFlags.IsAiming);
internal bool _lastDriveBy;
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 { get; set; } = null;
internal uint CurrentWeaponHash { get; set; }
private Dictionary<uint, bool> _lastWeaponComponents = null;
internal Dictionary<uint, bool> WeaponComponents { get; set; } = null;
private Entity _weaponObj;
internal Vector3 AimCoords { get; set; }
private readonly string[] _currentAnimation = new string[2] { "", "" };
private bool LastMoving;
}
}

View File

@ -1,22 +1,20 @@
using System;
using System.Linq;
using System.Drawing;
using System.Collections.Generic;
using RageCoop.Core;
using GTA;
using GTA.Native;
using GTA;
using GTA.Math;
using GTA.Native;
using LemonUI.Elements;
using System.Security.Cryptography;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
namespace RageCoop.Client
{
/// <summary>
/// ?
/// </summary>
public class SyncedPed:SyncedEntity
public partial class SyncedPed : SyncedEntity
{
#region CONSTRUCTORS
/// <summary>
/// Create a local entity (outgoing sync)
@ -24,11 +22,11 @@ namespace RageCoop.Client
/// <param name="p"></param>
internal SyncedPed(Ped p)
{
ID=EntityPool.RequestNewID();
p.CanWrithe=false;
p.IsOnlyDamagedByPlayer=false;
MainPed=p;
OwnerID=Main.LocalPlayerID;
ID = EntityPool.RequestNewID();
p.CanWrithe = false;
p.IsOnlyDamagedByPlayer = false;
MainPed = p;
OwnerID = Main.LocalPlayerID;
Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DisableHurt, true);
@ -41,53 +39,15 @@ namespace RageCoop.Client
/// </summary>
internal SyncedPed(int id)
{
ID=id;
LastSynced=Main.Ticked;
ID = id;
LastSynced = Main.Ticked;
}
#endregion
#region PLAYER -- ONLY
internal Blip PedBlip = null;
internal BlipColor BlipColor = (BlipColor)255;
internal BlipSprite BlipSprite = (BlipSprite)0;
internal float BlipScale=1;
internal PlayerData Player;
#endregion
/// <summary>
/// Indicates whether this ped is a player
/// </summary>
public bool IsPlayer { get { return (OwnerID==ID)&&(ID!=0); } }
/// <summary>
/// real entity
/// </summary>
public Ped MainPed { get; internal set; }
internal int Health { get; set; }
internal bool IsInStealthMode { get; set; }
internal byte WeaponTint { get; set; }
internal bool _lastEnteringVehicle=false;
internal bool _lastSittingInVehicle=false;
private bool _lastRagdoll=false;
private ulong _lastRagdollTime=0;
private bool _lastInCover = false;
private byte[] _lastClothes = null;
internal byte[] Clothes { get; set; }
internal float Heading { get; set; }
internal Vector3 RotationVelocity { get; set; }
internal Vector3 AimCoords { get; set; }
private WeaponAsset WeaponAsset { get; set; }
internal override void Update()
{
if (Owner == null) { OwnerID = OwnerID; return; }
if (IsPlayer)
{
if (Player==null)
{
Player = PlayerList.GetPlayer(this);
return;
}
RenderNameTag();
}
@ -95,78 +55,68 @@ namespace RageCoop.Client
if (!IsReady) { return; }
// Skip update if no new sync message has arrived.
if (!NeedUpdate)
if (!NeedUpdate) { return; }
if (MainPed == null || !MainPed.Exists())
{
return;
}
bool characterExist = (MainPed != null) && MainPed.Exists();
if (!characterExist)
{
CreateCharacter();
return;
}
if (((byte)BlipColor==255) && (PedBlip!=null))
{
PedBlip.Delete();
PedBlip=null;
}
else if (((byte)BlipColor != 255) && PedBlip==null)
{
PedBlip=MainPed.AddBlip();
if (IsPlayer)
if (!CreateCharacter())
{
Main.Logger.Debug("blip:"+Player.Username);
PedBlip.Name=Player.Username;
return;
}
PedBlip.Color=BlipColor;
PedBlip.Sprite=BlipSprite;
PedBlip.Scale=BlipScale;
}
// Need to update state
if (LastStateSynced>=LastUpdated)
if (LastFullSynced >= LastUpdated)
{
if (MainPed!=null&& (ModelHash != MainPed.Model.Hash))
if (MainPed != null && (Model != MainPed.Model.Hash))
{
CreateCharacter();
return;
if (!CreateCharacter())
{
return;
}
}
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();
}
var b = MainPed.AttachedBlip;
if (b==null || b.Color!=BlipColor || b.Sprite!=BlipSprite)
{
PedBlip?.Delete();
PedBlip=MainPed.AddBlip();
PedBlip.Color=BlipColor;
PedBlip.Sprite =BlipSprite;
if (IsPlayer)
{
Main.Logger.Debug("blip:"+Player.Username);
b.Name=Player.Username;
}
}
CheckCurrentWeapon();
}
if (MainPed.IsDead)
{
if (Health>0)
if (Health > 0)
{
if (IsPlayer)
{
@ -178,7 +128,7 @@ namespace RageCoop.Client
}
}
}
else if (IsPlayer&&(MainPed.Health != Health))
else if (IsPlayer && (MainPed.Health != Health))
{
MainPed.Health = Health;
@ -190,55 +140,67 @@ namespace RageCoop.Client
}
}
if (MainPed.IsInVehicle()||MainPed.IsGettingIntoVehicle)
if (Speed >= 4)
{
DisplayInVehicle();
}
else
{
if (MainPed.IsInVehicle()) { MainPed.Task.LeaveVehicle(LeaveVehicleFlags.WarpOut); return; }
DisplayOnFoot();
}
LastUpdated=Main.Ticked;
return;
if (IsSpeaking)
{
if (Main.Ticked - LastSpeakingTime < 10)
{
DisplaySpeaking(true);
}
else
{
DisplaySpeaking(false);
IsSpeaking = false;
LastSpeakingTime = 0;
}
}
LastUpdated = Main.Ticked;
}
private void RenderNameTag()
{
if (!Player.DisplayNameTag || (MainPed==null) || !MainPed.IsVisible || !MainPed.IsInRange(Game.Player.Character.Position, 20f))
if (!Owner.DisplayNameTag || (MainPed == null) || !MainPed.IsVisible || !MainPed.IsInRange(Main.PlayerPosition, 40f))
{
return;
}
string renderText = IsOutOfSync ? "~r~AFK" : Player.Username;
Vector3 targetPos = MainPed.Bones[Bone.IKHead].Position + new Vector3(0, 0, 0.35f);
Function.Call(Hash.SET_DRAW_ORIGIN, targetPos.X, targetPos.Y, targetPos.Z, 0);
float dist = (GameplayCamera.Position - MainPed.Position).Length();
var sizeOffset = Math.Max(1f - (dist / 30f), 0.3f);
new ScaledText(new PointF(0, 0), renderText, 0.4f * sizeOffset, GTA.UI.Font.ChaletLondon)
Vector3 targetPos = MainPed.Bones[Bone.IKHead].Position;
Point toDraw = default;
if (Util.WorldToScreen(targetPos, ref toDraw))
{
Outline = true,
Alignment = GTA.UI.Alignment.Center
}.Draw();
Function.Call(Hash.CLEAR_DRAW_ORIGIN);
toDraw.Y -= 100;
new ScaledText(toDraw, Owner.Username, 0.4f, GTA.UI.Font.ChaletLondon)
{
Outline = true,
Alignment = GTA.UI.Alignment.Center,
Color = Owner.HasDirectConnection ? Color.FromArgb(179, 229, 252) : Color.White,
}.Draw();
}
}
private void CreateCharacter()
private bool CreateCharacter()
{
if (MainPed != null)
{
if (MainPed.Exists())
{
Main.Logger.Debug($"Removing ped {ID}. Reason:CreateCharacter");
// Main.Logger.Debug($"Removing ped {ID}. Reason:CreateCharacter");
MainPed.Kill();
MainPed.MarkAsNoLongerNeeded();
MainPed.Delete();
}
MainPed = null;
}
@ -247,29 +209,26 @@ namespace RageCoop.Client
PedBlip.Delete();
PedBlip = null;
}
Model characterModel = ModelHash.ModelRequest();
if (characterModel == null)
if (!Model.IsLoaded)
{
return;
Model.Request();
return false;
}
MainPed = World.CreatePed(characterModel, Position);
characterModel.MarkAsNoLongerNeeded();
if (MainPed == null)
if ((MainPed = Util.CreatePed(Model, Position)) == null)
{
return;
return false;
}
Model.MarkAsNoLongerNeeded();
MainPed.BlockPermanentEvents = true;
MainPed.CanWrithe=false;
MainPed.CanWrithe = false;
MainPed.CanBeDraggedOutOfVehicle = true;
MainPed.IsOnlyDamagedByPlayer = false;
MainPed.RelationshipGroup=Main.SyncedPedsGroup;
MainPed.IsFireProof=false;
MainPed.IsExplosionProof=false;
MainPed.RelationshipGroup = Main.SyncedPedsGroup;
MainPed.IsFireProof = false;
MainPed.IsExplosionProof = false;
Function.Call(Hash.SET_PED_DROPS_WEAPONS_WHEN_DEAD, MainPed.Handle, false);
Function.Call(Hash.SET_PED_CAN_BE_TARGETTED, MainPed.Handle, true);
@ -279,70 +238,55 @@ namespace RageCoop.Client
Function.Call(Hash._SET_PED_CAN_PLAY_INJURED_ANIMS, false);
Function.Call(Hash.SET_PED_CAN_EVASIVE_DIVE, MainPed.Handle, false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_DrownsInWater,false);
MainPed.SetConfigFlag((int)PedConfigFlags.CPED_CONFIG_FLAG_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; }
if (IsPlayer) { MainPed.IsInvincible = true; }
if (IsInvincible) { MainPed.IsInvincible = true; }
// Add to EntityPool so this Character can be accessed by handle.
EntityPool.Add(this);
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++)
{
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i], (int)Clothes[i+12], (int)Clothes[i+24]);
Function.Call(Hash.SET_PED_COMPONENT_VARIATION, MainPed.Handle, i, (int)Clothes[i], (int)Clothes[i + 12], (int)Clothes[i + 24]);
}
_lastClothes = Clothes;
}
#region ONFOOT
#region -- VARIABLES --
/// <summary>
/// The latest character rotation (may not have been applied yet)
/// </summary>
public byte Speed { get; set; }
private bool _lastIsJumping = false;
internal bool IsJumping { get; set; }
internal bool IsOnLadder { get; set; }
internal bool IsVaulting { get; set; }
internal bool IsInParachuteFreeFall { get; set; }
internal bool IsParachuteOpen { get; set; }
internal Prop ParachuteProp { get; set; } = null;
internal bool IsRagdoll { get; set; }
internal bool IsOnFire { get; set; }
internal bool IsAiming { get; set; }
internal bool IsReloading { get; set; }
internal bool IsInCover { get; set; }
internal uint CurrentWeaponHash { get; set; }
private Dictionary<uint, bool> _lastWeaponComponents = null;
internal Dictionary<uint, bool> WeaponComponents { get; set; } = null;
private int _lastWeaponObj = 0;
#endregion
private string[] _currentAnimation = new string[2] { "", "" };
private void DisplayOnFoot()
{
if (IsInParachuteFreeFall)
{
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.Position, Position + Velocity, 0.5f);
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
MainPed.Quaternion = Rotation.ToQuaternion();
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@base", "free_idle", 3))
{
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, LoadAnim("skydive@base"), "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
// Skip update if animation is not loaded
var dict = LoadAnim("skydive@base");
if (dict == null) { return; }
Function.Call(Hash.TASK_PLAY_ANIM, MainPed.Handle, dict, "free_idle", 8f, 10f, -1, 0, -8f, 1, 1, 1);
}
return;
}
@ -351,10 +295,11 @@ namespace RageCoop.Client
{
if (ParachuteProp == null)
{
Model model = 1740193300.ModelRequest();
Model model = 1740193300;
model.Request(1000);
if (model != null)
{
ParachuteProp = World.CreateProp(model, MainPed.Position, MainPed.Rotation, false, false);
ParachuteProp = World.CreateProp(model, MainPed.ReadPosition(), MainPed.ReadRotation(), false, false);
model.MarkAsNoLongerNeeded();
ParachuteProp.IsPositionFrozen = true;
ParachuteProp.IsCollisionEnabled = false;
@ -365,12 +310,14 @@ namespace RageCoop.Client
MainPed.Task.ClearSecondary();
}
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.Position, Position + Velocity, 0.5f);
MainPed.PositionNoOffset = Vector3.Lerp(MainPed.ReadPosition(), Position + Velocity, 0.5f);
MainPed.Quaternion = Rotation.ToQuaternion();
if (!Function.Call<bool>(Hash.IS_ENTITY_PLAYING_ANIM, MainPed.Handle, "skydive@parachute@first_person", "chute_idle_right", 3))
{
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, LoadAnim("skydive@parachute@first_person"), "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
var dict = LoadAnim("skydive@parachute@first_person");
if (dict == null) { return; }
Function.Call(Hash.TASK_PLAY_ANIM, MainPed, dict, "chute_idle_right", 8f, 10f, -1, 0, -8f, 1, 1, 1);
}
return;
@ -433,7 +380,7 @@ namespace RageCoop.Client
SmoothTransition();
return;
}
if (!IsOnLadder && MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
else if (MainPed.IsTaskActive(TaskType.CTaskGoToAndClimbLadder))
{
MainPed.Task.ClearAllImmediately();
_currentAnimation[1] = "";
@ -457,12 +404,11 @@ namespace RageCoop.Client
if (IsOnFire && !MainPed.IsOnFire)
{
MainPed.SetOnFire(true);
Function.Call(Hash.START_ENTITY_FIRE, MainPed);
}
else if (!IsOnFire && MainPed.IsOnFire)
{
MainPed.SetOnFire(false);
Function.Call(Hash.STOP_ENTITY_FIRE, MainPed);
}
if (IsJumping)
@ -478,48 +424,34 @@ namespace RageCoop.Client
}
_lastIsJumping = false;
if (IsRagdoll || Health==0)
if (IsRagdoll || Health == 0)
{
if (!MainPed.IsRagdoll)
{
MainPed.Ragdoll();
}
SmoothTransition();
if (!_lastRagdoll)
{
_lastRagdoll = true;
_lastRagdollTime=Main.Ticked;
_lastRagdollTime = Main.Ticked;
}
/*
if((Main.Ticked-_lastRagdollTime>30)&&((Position.DistanceTo(MainPed.Position)>2)||MainPed.Velocity.Length()<3f))
{
MainPed.ApplyForce((Position-MainPed.Position)*0.2f, (RotationVelocity-MainPed.RotationVelocity)*0.1f);
}*/
return;
}
else
if (MainPed.IsRagdoll)
{
if (MainPed.IsRagdoll)
if (Speed == 0)
{
if (Speed==0)
{
MainPed.CancelRagdoll();
}
else
{
MainPed.Task.ClearAllImmediately();
}
return;
MainPed.CancelRagdoll();
}
else
{
_lastRagdoll = false;
MainPed.Task.ClearAllImmediately();
}
_lastRagdoll = false;
return;
}
if (IsReloading)
{
if (!MainPed.IsTaskActive(TaskType.CTaskReloadGun))
@ -542,29 +474,26 @@ namespace RageCoop.Client
}
else if (IsInCover)
{
if (!_lastInCover)
{
Function.Call(Hash.TASK_STAY_IN_COVER, MainPed.Handle);
}
_lastInCover=true;
_lastInCover = true;
if (IsAiming)
{
DisplayAiming();
_lastInCover=false;
_lastInCover = false;
}
else if (MainPed.IsInCover)
{
SmoothTransition();
}
return;
}
else if (_lastInCover)
{
MainPed.Task.ClearAllImmediately();
_lastInCover=false;
_lastInCover = false;
}
else if (IsAiming)
{
@ -580,38 +509,32 @@ namespace RageCoop.Client
}
}
#region WEAPON
private void CheckCurrentWeapon()
{
if (MainPed.Weapons.Current.Hash != (WeaponHash)CurrentWeaponHash || !WeaponComponents.Compare(_lastWeaponComponents))
if (MainPed.Weapons.Current.Hash != (WeaponHash)CurrentWeaponHash || !WeaponComponents.Compare(_lastWeaponComponents) || (Speed <= 3 && _weaponObj?.IsVisible != true))
{
if (WeaponAsset!=null) { WeaponAsset.MarkAsNoLongerNeeded(); }
WeaponAsset=new WeaponAsset(CurrentWeaponHash);
if (!WeaponAsset.IsLoaded) { WeaponAsset.Request(); }
MainPed.Weapons.RemoveAll();
_lastWeaponObj = Function.Call<int>(Hash.CREATE_WEAPON_OBJECT, CurrentWeaponHash, -1, Position.X, Position.Y, Position.Z, true, 0, 0);
new WeaponAsset(CurrentWeaponHash).Request();
MainPed.Weapons.RemoveAll();
_weaponObj = Entity.FromHandle(Function.Call<int>(Hash.CREATE_WEAPON_OBJECT, CurrentWeaponHash, -1, Position.X, Position.Y, Position.Z, true, 0, 0));
if (_weaponObj == null) { return; }
if (CurrentWeaponHash != (uint)WeaponHash.Unarmed)
{
if (WeaponComponents != null && WeaponComponents.Count != 0)
{
foreach (KeyValuePair<uint, bool> comp in WeaponComponents)
{
if (comp.Value)
{
Function.Call(Hash.GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, _lastWeaponObj, comp.Key);
Function.Call(Hash.GIVE_WEAPON_COMPONENT_TO_WEAPON_OBJECT, _weaponObj, comp.Key);
}
}
}
Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, _lastWeaponObj, MainPed.Handle);
Function.Call(Hash.GIVE_WEAPON_OBJECT_TO_PED, _weaponObj, MainPed.Handle);
}
_lastWeaponComponents = WeaponComponents;
}
if (Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX,MainPed,CurrentWeaponHash)!=WeaponTint)
if (Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeaponHash) != WeaponTint)
{
Function.Call<int>(Hash.SET_PED_WEAPON_TINT_INDEX, MainPed, CurrentWeaponHash, WeaponTint);
}
@ -619,26 +542,26 @@ namespace RageCoop.Client
private void DisplayAiming()
{
if (Velocity==default)
if (Velocity == default)
{
MainPed.Task.AimAt(AimCoords,1000);
MainPed.Task.AimAt(AimCoords, 1000);
}
else
{
Function.Call(Hash.TASK_GO_TO_COORD_WHILE_AIMING_AT_COORD, MainPed.Handle,
Position.X+Velocity.X, Position.Y+Velocity.Y, Position.Z+Velocity.Z,
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();
}
#endregion
private bool LastMoving;
private void WalkTo()
{
Vector3 predictPosition = Position + (Position - MainPed.Position) + Velocity * 0.5f;
float range = predictPosition.DistanceToSquared(MainPed.Position);
MainPed.Task.ClearAll();
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, MainPed, IsInStealthMode, 0);
Vector3 predictPosition = Predict(Position) + Velocity;
float range = predictPosition.DistanceToSquared(MainPed.ReadPosition());
switch (Speed)
{
@ -687,52 +610,128 @@ namespace RageCoop.Client
private void SmoothTransition()
{
var localRagdoll = MainPed.IsRagdoll;
var dist = Position.DistanceTo(MainPed.Position);
if (dist>3)
var predicted = Predict(Position);
var dist = predicted.DistanceTo(MainPed.ReadPosition());
if (IsOff(dist))
{
MainPed.PositionNoOffset=Position;
MainPed.PositionNoOffset = predicted;
return;
}
var f = dist*(Position+SyncParameters.PositioinPredictionDefault*Velocity-MainPed.Position)+(Velocity-MainPed.Velocity)*0.2f;
if (!localRagdoll) { f*=5; }
if (!(localRagdoll|| MainPed.IsDead))
if (!(localRagdoll || MainPed.IsDead))
{
MainPed.Rotation=Rotation;
if (MainPed.Speed<0.05) { f*=10; MainPed.Heading=Heading; }
}
else if (Main.Ticked-_lastRagdollTime<10)
{
return;
}
MainPed.ApplyForce(f);
}
private string LoadAnim(string anim)
{
ulong startTime = Util.GetTickCount64();
while (!Function.Call<bool>(Hash.HAS_ANIM_DICT_LOADED, anim))
{
Script.Yield();
Function.Call(Hash.REQUEST_ANIM_DICT, anim);
if (Util.GetTickCount64() - startTime >= 1000)
if (!IsAiming && !MainPed.IsGettingUp)
{
break;
}
}
var cur = MainPed.Heading;
var diff = Heading - cur;
if (diff > 180) { diff -= 360; }
else if (diff < -180) { diff += 360; }
return anim;
MainPed.Heading = cur + diff / 2;
}
MainPed.Velocity = Velocity + 5 * dist * (predicted - MainPed.ReadPosition());
}
else if (Main.Ticked - _lastRagdollTime < 10)
{
return;
}
else if (IsRagdoll)
{
var helper = new GTA.NaturalMotion.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.ApplyForce(force);
}
}
#endregion
private void DisplayInVehicle()
{
if (MainPed.IsOnTurretSeat())
if (CurrentVehicle?.MainVehicle == null) { return; }
switch (Speed)
{
Function.Call(Hash.SET_VEHICLE_TURRET_SPEED_THIS_FRAME, MainPed.CurrentVehicle, 100);
Function.Call(Hash.TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y, AimCoords.Z);
case 4:
if (MainPed.CurrentVehicle != CurrentVehicle.MainVehicle || MainPed.SeatIndex != Seat || (!MainPed.IsSittingInVehicle() && !MainPed.IsBeingJacked))
{
MainPed.SetIntoVehicle(CurrentVehicle.MainVehicle, Seat);
}
if (MainPed.IsOnTurretSeat())
{
// Function.Call(Hash.SET_VEHICLE_TURRET_SPEED_THIS_FRAME, MainPed.CurrentVehicle, 100);
Function.Call(Hash.TASK_VEHICLE_AIM_AT_COORD, MainPed.Handle, AimCoords.X, AimCoords.Y, AimCoords.Z);
}
if (MainPed.VehicleWeapon == VehicleWeaponHash.Invalid)
{
// World.DrawMarker(MarkerType.DebugSphere,AimCoords,default,default,new Vector3(0.2f,0.2f,0.2f),Color.AliceBlue);
if (IsAiming)
{
Function.Call(Hash.SET_DRIVEBY_TASK_TARGET, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z);
if (!_lastDriveBy)
{
_lastDriveBy = true;
Function.Call(Hash.TASK_DRIVE_BY, MainPed, 0, 0, AimCoords.X, AimCoords.Y, AimCoords.Z, 1, 100, 1, FiringPattern.SingleShot);
}
}
else if (_lastDriveBy || MainPed.IsTaskActive(TaskType.CTaskAimGunVehicleDriveBy))
{
MainPed.Task.ClearAll();
_lastDriveBy = false;
}
}
else if (MainPed.VehicleWeapon != (VehicleWeaponHash)CurrentWeaponHash)
{
MainPed.VehicleWeapon = (VehicleWeaponHash)CurrentWeaponHash;
}
break;
case 5:
if (MainPed.VehicleTryingToEnter != CurrentVehicle.MainVehicle || MainPed.GetSeatTryingToEnter() != Seat)
{
MainPed.Task.EnterVehicle(CurrentVehicle.MainVehicle, Seat, -1, 5, EnterVehicleFlags.AllowJacking);
}
break;
case 6:
if (!MainPed.IsTaskActive(TaskType.CTaskExitVehicle))
{
MainPed.Task.LeaveVehicle(CurrentVehicle.Velocity.Length() > 5f ? LeaveVehicleFlags.BailOut : LeaveVehicleFlags.None);
}
break;
}
/*
Function.Call(Hash.TASK_SWEEP_AIM_ENTITY,P, "random@paparazzi@pap_anims", "sweep_low", "sweep_med", "sweep_high", -1,V, 1.57f, 0.25f);
Function.Call(Hash.SET_PED_STEALTH_MOVEMENT, P,true, 0);
return Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, P);
*/

View File

@ -0,0 +1,116 @@
using GTA;
using GTA.Math;
using System.Diagnostics;
namespace RageCoop.Client
{
/// <summary>
///
/// </summary>
public abstract class SyncedEntity
{
/// <summary>
/// Indicates whether the current player is responsible for syncing this entity.
/// </summary>
public bool IsLocal
{
get => OwnerID == Main.LocalPlayerID;
}
/// <summary>
/// Network ID for this entity
/// </summary>
public int ID { get; internal set; }
private int _ownerID;
/// <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
{
get => Main.Ticked - LastSynced > 200 && ID != 0;
}
internal bool IsReady
{
get => LastSynced > 0 || LastFullSynced == 0;
}
internal bool IsInvincible { get; set; } = false;
internal bool NeedUpdate
{
get => LastSynced >= LastUpdated;
}
#region LAST STATE
/// <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; } = 0;
internal Stopwatch LastSentStopWatch { get; set; } = Stopwatch.StartNew();
#endregion
public bool SendNextFrame { get; set; } = false;
public bool SendFullNextFrame { get; set; } = false;
/// <summary>
///
/// </summary>
protected internal bool _lastFrozen = 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; }
public Stopwatch LastSyncedStopWatch = new Stopwatch();
internal abstract void Update();
internal void PauseUpdate(ulong frames)
{
LastUpdated = Main.Ticked + frames;
}
protected Vector3 Predict(Vector3 input)
{
return (Owner.PacketTravelTime + 0.001f * LastSyncedStopWatch.ElapsedMilliseconds) * Velocity + input;
}
private float _accumulatedOff = 0;
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;
}
}
}

View File

@ -0,0 +1,126 @@
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Core;
namespace RageCoop.Client
{
internal class SyncedProjectile : SyncedEntity
{
public ProjectileDataFlags Flags { private get; set; } = ProjectileDataFlags.None;
public readonly Vector3 Origin;
private bool _firstSend = false;
public bool IsValid { get; private set; } = true;
public new bool IsLocal { get; private set; } = false;
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;
}
}
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;
}
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 = Main.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;
Function.Call(Hash.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

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

View File

@ -0,0 +1,82 @@
using GTA;
using GTA.Math;
using RageCoop.Core;
using System.Collections.Generic;
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[] Colors { get; set; }
internal Dictionary<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 VehicleDataFlags Flags { get; set; }
#endregion
#region FLAGS
internal bool EngineRunning { get => Flags.HasVehFlag(VehicleDataFlags.IsEngineRunning); }
internal bool Transformed { get => Flags.HasVehFlag(VehicleDataFlags.IsTransformed); }
internal bool HornActive { get => Flags.HasVehFlag(VehicleDataFlags.IsHornActive); }
internal bool LightsOn { get => Flags.HasVehFlag(VehicleDataFlags.AreLightsOn); }
internal bool BrakeLightsOn { get => Flags.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn); }
internal bool HighBeamsOn { get => Flags.HasVehFlag(VehicleDataFlags.AreHighBeamsOn); }
internal bool SireneActive { get => Flags.HasVehFlag(VehicleDataFlags.IsSirenActive); }
internal bool IsDead { get => Flags.HasVehFlag(VehicleDataFlags.IsDead); }
internal bool IsDeluxoHovering { get => Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering); }
#endregion
#region FIXED-DATA
internal bool IsFlipped
{
get => IsMotorcycle || ((Quaternion * Vector3.RelativeTop).Z - (Quaternion * Vector3.RelativeBottom).Z) < 0.5;
}
internal bool IsMotorcycle;
internal bool IsAircraft;
internal bool HasRocketBoost;
internal bool HasParachute;
internal bool HasRoof;
internal bool IsSubmarineCar;
internal bool IsDeluxo;
#endregion
#region PRIVATE
private byte[] _lastVehicleColors = new byte[] { 0, 0 };
private Dictionary<int, int> _lastVehicleMods = new Dictionary<int, int>();
private bool _lastHornActive = false;
private bool _lastTransformed = false;
internal int _lastLivery = -1;
private readonly List<Vector3> _predictedTrace = new List<Vector3>();
private readonly List<Vector3> _orgTrace = new List<Vector3>();
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,377 @@
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Core;
using System;
using System.Collections.Generic;
namespace RageCoop.Client
{
/// <summary>
/// A synchronized vehicle instance
/// </summary>
public partial class SyncedVehicle : SyncedEntity
{
#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 = Main.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;
}
/// <summary>
/// Create an empty VehicleEntity
/// </summary>
internal SyncedVehicle()
{
}
internal SyncedVehicle(int id)
{
ID = id;
LastSynced = Main.Ticked;
}
#endregion
/// <summary>
/// VehicleSeat,ID
/// </summary>
internal override void Update()
{
#if DEBUG_VEH
foreach(var s in _predictedTrace)
{
World.DrawMarker(MarkerType.DebugSphere, s, default, default, new Vector3(0.3f, 0.3f, 0.3f), Color.AliceBlue);
}
foreach (var s in _orgTrace)
{
World.DrawMarker(MarkerType.DebugSphere, s, default, default, new Vector3(0.3f, 0.3f, 0.3f), Color.Orange);
}
#endif
// Check if all data avalible
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))
{
Function.Call(Hash.STOP_ENTITY_FIRE, MainVehicle);
}
}
else if (Flags.HasVehFlag(VehicleDataFlags.IsOnFire))
{
Function.Call(Hash.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.SetRocketBoostActive(Flags.HasFlag(VehicleDataFlags.IsRocketBoostActive));
}
if (HasParachute && Flags.HasFlag(VehicleDataFlags.IsParachuteActive) != MainVehicle.IsParachuteActive())
{
MainVehicle.SetParachuteActive(Flags.HasFlag(VehicleDataFlags.IsParachuteActive));
}
if (IsSubmarineCar)
{
if (Transformed)
{
if (!_lastTransformed)
{
_lastTransformed = true;
Function.Call(Hash._TRANSFORM_VEHICLE_TO_SUBMARINE, MainVehicle.Handle, false);
}
}
else if (_lastTransformed)
{
_lastTransformed = false;
Function.Call(Hash._TRANSFORM_SUBMARINE_TO_VEHICLE, MainVehicle.Handle, false);
}
}
else if (IsDeluxo)
{
MainVehicle.SetDeluxoHoverState(IsDeluxoHovering);
if (IsDeluxoHovering)
{
MainVehicle.SetDeluxoWingRatio(DeluxoWingRatio);
}
}
Function.Call(Hash.SET_VEHICLE_BRAKE_LIGHTS, MainVehicle.Handle, BrakeLightsOn);
}
MainVehicle.LockStatus = LockStatus;
if (LastFullSynced >= LastUpdated)
{
if (Flags.HasVehFlag(VehicleDataFlags.Repaired))
{
MainVehicle.Repair();
}
if (Colors != null && Colors != _lastVehicleColors)
{
Function.Call(Hash.SET_VEHICLE_COLOURS, MainVehicle, Colors[0], Colors[1]);
_lastVehicleColors = Colors;
}
MainVehicle.EngineHealth = EngineHealth;
if (Mods != null && !Mods.Compare(_lastVehicleMods))
{
Function.Call(Hash.SET_VEHICLE_MOD_KIT, MainVehicle, 0);
foreach (KeyValuePair<int, int> mod in Mods)
{
MainVehicle.Mods[(VehicleModType)mod.Key].Index = mod.Value;
}
_lastVehicleMods = Mods;
}
if (Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle) != LicensePlate)
{
Function.Call(Hash.SET_VEHICLE_NUMBER_PLATE_TEXT, MainVehicle, LicensePlate);
}
if (_lastLivery != Livery)
{
Function.Call(Hash.SET_VEHICLE_LIVERY, MainVehicle, Livery);
_lastLivery = Livery;
}
MainVehicle.SetDamageModel(DamageModel);
}
LastUpdated = Main.Ticked;
}
private void DisplayVehicle()
{
_predictedPosition = Predict(Position);
var current = MainVehicle.ReadPosition();
var dist = current.DistanceTo(_predictedPosition);
var cali = dist * (_predictedPosition - current);
if (Velocity.Length() < 0.1) { cali *= 10; }
if (dist > 10)
{
MainVehicle.Position = _predictedPosition;
MainVehicle.Velocity = Velocity;
MainVehicle.Quaternion = Quaternion;
return;
}
if (dist > 0.03)
{
MainVehicle.Velocity = Velocity + cali;
}
Vector3 calirot;
if (IsFlipped || (calirot = GetCalibrationRotation()).Length() > 50)
{
MainVehicle.Quaternion = Quaternion.Slerp(MainVehicle.ReadQuaternion(), Quaternion, 0.5f);
MainVehicle.RotationVelocity = RotationVelocity;
return;
}
MainVehicle.RotationVelocity = RotationVelocity + calirot * 0.2f;
}
private Vector3 GetCalibrationRotation()
{
var rot = Quaternion.LookRotation(Quaternion * Vector3.RelativeFront, Quaternion * Vector3.RelativeTop).ToEulerAngles();
var curRot = Quaternion.LookRotation(MainVehicle.ReadQuaternion() * Vector3.RelativeFront, MainVehicle.ReadQuaternion() * Vector3.RelativeTop).ToEulerAngles();
var r = (rot - curRot).ToDegree();
if (r.X > 180) { r.X = r.X - 360; }
else if (r.X < -180) { r.X = 360 + r.X; }
if (r.Y > 180) { r.Y = r.Y - 360; }
else if (r.Y < -180) { r.Y = 360 + r.Y; }
if (r.Z > 180) { r.Z = r.Z - 360; }
else if (r.Z < -180) { r.Z = 360 + r.Z; }
return r;
}
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 -- 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.AllowRotation, 1.0f);
}
private void StopPedalingAnim(bool fast)
{
MainVehicle.Driver.Task.ClearAnimation(PedalingAnimDict(), PedalingAnimName(fast));
}
#endregion
}
}

View File

@ -0,0 +1,596 @@
using GTA;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Client.Scripting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
namespace RageCoop.Client
{
internal class EntityPool
{
public static object PedsLock = new object();
#if BENCHMARK
private static Stopwatch PerfCounter=new Stopwatch();
private static Stopwatch PerfCounter2=Stopwatch.StartNew();
#endif
#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)
{
if ((keepPlayer && (ped.ID == Main.LocalPlayerID)) || (keepMine && (ped.OwnerID == Main.LocalPlayerID))) { continue; }
RemovePed(ped.ID);
}
PedsByID.Clear();
PedsByHandle.Clear();
foreach (int id in new List<int>(VehiclesByID.Keys))
{
if (keepMine && (VehiclesByID[id].OwnerID == Main.LocalPlayerID)) { continue; }
RemoveVehicle(id);
}
VehiclesByID.Clear();
VehiclesByHandle.Clear();
foreach (var p in ProjectilesByID.Values)
{
if (p.Shooter.ID != Main.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() => new List<int>(PedsByID.Keys);
public static bool AddPlayer()
{
Ped p = Game.Player.Character;
// var clipset=p.Gender==Gender.Male? "MOVE_M@TOUGH_GUY@" : "MOVE_F@TOUGH_GUY@";
// Function.Call(Hash.SET_PED_MOVEMENT_CLIPSET,p,clipset,1f);
SyncedPed player = GetPedByID(Main.LocalPlayerID);
if (player == null)
{
Main.Logger.Debug($"Creating SyncEntity for player, handle:{p.Handle}");
SyncedPed c = new SyncedPed(p);
Main.LocalPlayerID = c.OwnerID = c.ID;
Add(c);
Main.Logger.Debug($"Local player ID is:{c.ID}");
PlayerList.SetPlayer(c.ID, Main.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))
{
SyncedPed c = PedsByID[id];
var p = c.MainPed;
if (p != null)
{
if (PedsByHandle.ContainsKey(p.Handle))
{
PedsByHandle.Remove(p.Handle);
}
// Main.Logger.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() => 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))
{
SyncedVehicle v = VehiclesByID[id];
var veh = v.MainVehicle;
if (veh != null)
{
if (VehiclesByHandle.ContainsKey(veh.Handle))
{
VehiclesByHandle.Remove(veh.Handle);
}
// Main.Logger.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)
{
return 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(p.Position, p.Position + p.Velocity, (uint)VehicleWeaponHash.Tank, ((SyncedVehicle)p.Shooter).MainVehicle.Driver.GetSyncEntity().ID);
}
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))
{
SyncedProjectile sp = ProjectilesByID[id];
var p = sp.MainProjectile;
if (p != null)
{
if (ProjectilesByHandle.ContainsKey(p.Handle))
{
ProjectilesByHandle.Remove(p.Handle);
}
Main.Logger.Debug($"Removing projectile {sp.ID}. Reason:{reason}");
p.Explode();
}
ProjectilesByID.Remove(id);
}
}
public static bool PedExists(int id) => PedsByID.ContainsKey(id);
public static bool VehicleExists(int id) => VehiclesByID.ContainsKey(id);
public static bool ProjectileExists(int id) => ProjectilesByID.ContainsKey(id);
#endregion
private static int vehStateIndex;
private static int pedStateIndex;
private static int vehStatesPerFrame;
private static int pedStatesPerFrame;
private static int i;
public static Ped[] allPeds = new Ped[0];
public static Vehicle[] allVehicles = new Vehicle[0];
public static Projectile[] allProjectiles = new Projectile[0];
public static void DoSync()
{
UpdateTargets();
#if BENCHMARK
PerfCounter.Restart();
Debug.TimeStamps[TimeStamp.CheckProjectiles]=PerfCounter.ElapsedTicks;
#endif
allPeds = World.GetAllPeds();
allVehicles = World.GetAllVehicles();
allProjectiles = World.GetAllProjectiles();
vehStatesPerFrame = allVehicles.Length * 2 / (int)Game.FPS + 1;
pedStatesPerFrame = allPeds.Length * 2 / (int)Game.FPS + 1;
#if BENCHMARK
Debug.TimeStamps[TimeStamp.GetAllEntities]=PerfCounter.ElapsedTicks;
#endif
lock (ProjectilesLock)
{
foreach (Projectile p in allProjectiles)
{
if (!ProjectilesByHandle.ContainsKey(p.Handle))
{
Add(new SyncedProjectile(p));
}
}
foreach (SyncedProjectile 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 (Ped p in allPeds)
{
SyncedPed c = GetPedByHandle(p.Handle);
if (c == null && (p != Game.Player.Character))
{
if (allPeds.Length > Main.Settings.WorldPedSoftLimit && p.PopulationType == EntityPopulationType.RandomAmbient)
{
p.Delete();
continue;
}
// Main.Logger.Trace($"Creating SyncEntity for ped, handle:{p.Handle}");
c = new SyncedPed(p);
Add(c);
}
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.AddPeds]=PerfCounter.ElapsedTicks;
#endif
var ps = PedsByID.Values.ToArray();
pedStateIndex += pedStatesPerFrame;
if (pedStateIndex >= ps.Length)
{
pedStateIndex = 0;
}
foreach (SyncedPed c in ps)
{
i++;
if ((c.MainPed != null) && (!c.MainPed.Exists()))
{
RemovePed(c.ID, "non-existent");
continue;
}
// Outgoing sync
if (c.IsLocal)
{
#if BENCHMARK
var start = PerfCounter2.ElapsedTicks;
#endif
// event check
SyncEvents.Check(c);
Networking.SendPed(c, (i - pedStateIndex) < pedStatesPerFrame);
#if BENCHMARK
Debug.TimeStamps[TimeStamp.SendPed]=PerfCounter2.ElapsedTicks-start;
#endif
}
else // Incoming sync
{
#if BENCHMARK
var start = PerfCounter2.ElapsedTicks;
#endif
c.Update();
if (c.IsOutOfSync)
{
RemovePed(c.ID, "OutOfSync");
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.UpdatePed]=PerfCounter2.ElapsedTicks-start;
#endif
}
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.PedTotal]=PerfCounter.ElapsedTicks;
#endif
}
var check = Main.Ticked % 100 == 0;
i = -1;
lock (VehiclesLock)
{
foreach (Vehicle veh in allVehicles)
{
if (!VehiclesByHandle.ContainsKey(veh.Handle))
{
if (allVehicles.Length > Main.Settings.WorldVehicleSoftLimit)
{
var type = veh.PopulationType;
if (type == EntityPopulationType.RandomAmbient || type == EntityPopulationType.RandomParked)
{
foreach (var p in veh.Occupants)
{
p.Delete();
var c = GetPedByHandle(p.Handle);
if (c != null)
{
RemovePed(c.ID, "ThrottleTraffic");
}
}
veh.Delete();
continue;
}
}
// Main.Logger.Debug($"Creating SyncEntity for vehicle, handle:{veh.Handle}");
Add(new SyncedVehicle(veh));
}
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.AddVehicles]=PerfCounter.ElapsedTicks;
#endif
var vs = VehiclesByID.Values.ToArray();
vehStateIndex += vehStatesPerFrame;
if (vehStateIndex >= vs.Length)
{
vehStateIndex = 0;
}
foreach (SyncedVehicle 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");
}
}
}
#if BENCHMARK
Debug.TimeStamps[TimeStamp.VehicleTotal]=PerfCounter.ElapsedTicks;
#endif
}
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(Main.PlayerPosition) < 500)
{
Networking.Targets.Add(p.Connection);
}
}
}
public static void RemoveAllFromPlayer(int playerPedId)
{
foreach (SyncedPed p in PedsByID.Values.ToArray())
{
if (p.OwnerID == playerPedId)
{
RemovePed(p.ID);
}
}
foreach (SyncedVehicle v in VehiclesByID.Values.ToArray())
{
if (v.OwnerID == playerPedId)
{
RemoveVehicle(v.ID);
}
}
}
public static int RequestNewID()
{
int ID = 0;
while ((ID == 0) || PedsByID.ContainsKey(ID) || VehiclesByID.ContainsKey(ID) || ProjectilesByID.ContainsKey(ID))
{
byte[] rngBytes = new byte[4];
RandomNumberGenerator.Create().GetBytes(rngBytes);
// Convert the bytes into an integer
ID = BitConverter.ToInt32(rngBytes, 0);
}
return ID;
}
private static void SetBudget(int b)
{
Function.Call(Hash.SET_PED_POPULATION_BUDGET, b); // 0 - 3
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, b); // 0 - 3
}
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,289 @@
using GTA;
using GTA.Math;
using Lidgren.Network;
using RageCoop.Core;
using System;
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 TriggerBulletShot(uint hash, SyncedPed owner, Vector3 impactPosition)
{
// Main.Logger.Trace($"bullet shot:{(WeaponHash)hash}");
var start = owner.MainPed.GetMuzzlePosition();
if (owner.MainPed.IsOnTurretSeat()) { start = owner.MainPed.Bones[Bone.SkelHead].Position; }
if (start.DistanceTo(impactPosition) > 10)
{
// Reduce latency
start = impactPosition - (impactPosition - start).Normalized * 10;
}
Networking.SendBullet(start, impactPosition, hash, owner.ID);
}
public static void TriggerVehBulletShot(uint hash, Vehicle veh, SyncedPed owner)
{
int i;
// ANNIHL
if (veh.Model.Hash == 837858166)
{
Networking.SendVehicleBullet(hash, owner, veh.Bones[35]);
Networking.SendVehicleBullet(hash, owner, veh.Bones[36]);
Networking.SendVehicleBullet(hash, owner, veh.Bones[37]);
Networking.SendVehicleBullet(hash, owner, veh.Bones[38]);
}
else if ((i = veh.GetMuzzleIndex()) != -1)
{
Networking.SendVehicleBullet(hash, owner, veh.Bones[i]);
}
else
{
Main.Logger.Warning($"Failed to get muzzle info for vehicle:{veh.DisplayName}");
}
}
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 WeaponAsset _weaponAsset = default;
private static uint _lastWeaponHash;
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.LastSynced = Main.Ticked;
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(Vector3 start, Vector3 end, uint weaponHash, int ownerID)
{
switch (weaponHash)
{
// Minigun, not working for some reason
case (uint)WeaponHash.Minigun:
weaponHash = 1176362416;
break;
// Valkyire, not working for some reason
case 2756787765:
weaponHash = 1176362416;
break;
// Tampa3, not working for some reason
case 3670375085:
weaponHash = 1176362416;
break;
// Ruiner2, not working for some reason
case 50118905:
weaponHash = 1176362416;
break;
// SAVAGE
case 1638077257:
weaponHash = (uint)VehicleWeaponHash.PlayerLazer;
break;
case (uint)VehicleWeaponHash.PlayerBuzzard:
weaponHash = 1176362416;
break;
}
var p = EntityPool.GetPedByID(ownerID)?.MainPed;
if (p == null) { p = Game.Player.Character; Main.Logger.Warning("Failed to find owner for bullet"); }
if (!CorePFXAsset.IsLoaded) { CorePFXAsset.Request(); }
if (_lastWeaponHash != weaponHash)
{
_weaponAsset.MarkAsNoLongerNeeded();
_weaponAsset = new WeaponAsset(weaponHash);
_lastWeaponHash = weaponHash;
}
if (!_weaponAsset.IsLoaded) { _weaponAsset.Request(); }
World.ShootBullet(start, end, p, _weaponAsset, (int)p.GetWeaponDamage(weaponHash));
Prop w;
if (((w = p.Weapons.CurrentWeaponObject) != null) && (p.VehicleWeapon == VehicleWeaponHash.Invalid))
{
if (p.Weapons.Current.Components.GetSuppressorComponent().Active)
{
World.CreateParticleEffectNonLooped(CorePFXAsset, "muz_pistol_silencer", p.GetMuzzlePosition(), w.Rotation, 1);
}
else
{
World.CreateParticleEffectNonLooped(CorePFXAsset, WeaponUtil.GetFlashFX((WeaponHash)weaponHash), p.GetMuzzlePosition(), w.Rotation, 1);
}
}
}
public static void HandleVehicleBulletShot(Packets.VehicleBulletShot p)
{
HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID);
var v = EntityPool.GetPedByID(p.OwnerID)?.MainPed.CurrentVehicle;
if (v == null) { return; }
var b = v.Bones[p.Bone];
World.CreateParticleEffectNonLooped(CorePFXAsset,
WeaponUtil.GetFlashFX((WeaponHash)p.WeaponHash),
b.Position, b.ForwardVector.ToEulerRotation(v.Bones[35].UpVector), 1);
}
public static void HandleEvent(PacketType type, NetIncomingMessage msg)
{
switch (type)
{
case PacketType.BulletShot:
{
Packets.BulletShot p = new Packets.BulletShot();
p.Deserialize(msg);
HandleBulletShot(p.StartPosition, p.EndPosition, p.WeaponHash, p.OwnerID);
break;
}
case PacketType.VehicleBulletShot:
{
HandleVehicleBulletShot(msg.GetPacket<Packets.VehicleBulletShot>());
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)
{
Ped subject = c.MainPed;
// Check bullets
if (subject.IsShooting)
{
if (!subject.IsUsingProjectileWeapon())
{
int i = 0;
Func<bool> getBulletImpact = (() =>
{
Vector3 endPos = subject.LastWeaponImpactPosition;
if (endPos == default)
{
if (++i <= 5) { return false; }
endPos = subject.GetAimCoord();
if (subject.IsInVehicle() && subject.VehicleWeapon != VehicleWeaponHash.Invalid)
{
if (subject.IsOnTurretSeat())
{
TriggerBulletShot((uint)subject.VehicleWeapon, c, endPos);
}
else
{
TriggerVehBulletShot((uint)subject.VehicleWeapon, subject.CurrentVehicle, c);
}
}
else
{
TriggerBulletShot((uint)subject.Weapons.Current.Hash, c, endPos);
}
return true;
}
if (subject.IsInVehicle() && subject.VehicleWeapon != VehicleWeaponHash.Invalid)
{
if (subject.IsOnTurretSeat())
{
TriggerBulletShot((uint)subject.VehicleWeapon, c, endPos);
}
else
{
TriggerVehBulletShot((uint)subject.VehicleWeapon, subject.CurrentVehicle, c);
}
}
else
{
TriggerBulletShot((uint)subject.Weapons.Current.Hash, c, endPos);
}
return true;
});
if (!getBulletImpact())
{
Scripting.API.QueueAction(getBulletImpact);
}
}
else if (subject.VehicleWeapon == VehicleWeaponHash.Tank && subject.LastWeaponImpactPosition != default)
{
TriggerBulletShot((uint)VehicleWeaponHash.Tank, c, subject.LastWeaponImpactPosition);
}
}
}
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,93 @@
using NAudio.Wave;
using System.Threading;
namespace RageCoop.Client
{
internal static class Voice
{
private static WaveInEvent _waveIn;
private static readonly BufferedWaveProvider _waveProvider = new BufferedWaveProvider(new WaveFormat(16000, 16, 1));
private static Thread _thread;
public static bool WasInitialized() => _thread != null;
public static bool IsRecording() => _waveIn != null;
public static void ClearAll()
{
_waveProvider.ClearBuffer();
StopRecording();
if (_thread != null && _thread.IsAlive)
{
_thread.Abort();
_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 = new Thread(new ThreadStart(() =>
{
while (true)
{
using (var wo = new WaveOutEvent())
{
wo.Init(_waveProvider);
wo.Play();
while (wo.PlaybackState == PlaybackState.Playing)
{
Thread.Sleep(100);
}
}
}
}));
_thread.Start();
}
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,59 @@
using GTA;
namespace RageCoop.Client
{
/// <summary>
/// Class providing support for addon mods
/// </summary>
internal class AddOnDataProvider
{
public static int GetMuzzleIndex(Model model)
{
switch (model.Hash)
{
// f14a2
case -848721350:
return 48;
// f15e
case 881261972:
return 32;
// f16c
case -2051171080:
return 25;
// F22A
case 2061630439:
return 14;
// f35c
case -343547392:
return 44;
// mig29a
case 513887552:
return 18;
// su30sm
case -733985185:
return 34;
// su33
case -722216722:
return 34;
// su35s
case -268602544:
return 28;
// su57
case 1490050781:
return 21;
default:
return -1;
}
}
}
}

View File

@ -0,0 +1,103 @@

using GTA;
using GTA.Math;
using RageCoop.Core;
using SHVDN;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace RageCoop.Client
{
internal unsafe class MemPatch
{
private readonly byte[] _data;
private readonly byte[] _orginal;
private readonly IntPtr _address;
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 @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 slow-mo"); }
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();
}
#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
public static Vector3 ReadPosition(this Entity e) => ReadVector3(e.MemoryAddress + PositionOffset);
public static Quaternion ReadQuaternion(this Entity e) => Quaternion.RotationMatrix(e.Matrix);
public static Vector3 ReadRotation(this Entity e) => e.ReadQuaternion().ToEulerDegrees();
public static Vector3 ReadVelocity(this Ped e) => ReadVector3(e.MemoryAddress + VelocityOffset);
public static Vector3 ReadVector3(IntPtr address)
{
float* 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 (int i = 0; i <= range; i++)
{
var val = NativeMemory.ReadFloat(start + i);
if (Math.Abs(val - toSearch) < tolerance)
{
foundOffsets.Add(i);
}
}
return foundOffsets;
}
}
}

View File

@ -0,0 +1,145 @@
using GTA.Math;
using GTA.Native;
using System;
using System.Runtime.InteropServices;
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 (int 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,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

@ -1,20 +1,15 @@
using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using RageCoop.Core;
using GTA;
using GTA.Native;
using GTA;
using GTA.Math;
using System.Linq;
using System.Diagnostics;
using GTA.Native;
using RageCoop.Core;
using System;
using System.Collections.Generic;
namespace RageCoop.Client
{
internal static partial class PedExtensions
{
public static bool IsBetween<T>(this T item, T start, T end)
{
return Comparer<T>.Default.Compare(item, start) >= 0 && Comparer<T>.Default.Compare(item, end) <= 0;
@ -42,26 +37,40 @@ namespace RageCoop.Client
return true;
}
#region PED
public static byte GetPedSpeed(this Ped ped)
{
if (ped.IsSprinting)
if (ped.IsSittingInVehicle())
{
return 3;
return 4;
}
if (ped.IsRunning)
if (ped.IsTaskActive(TaskType.CTaskEnterVehicle))
{
return 2;
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;
}
@ -71,9 +80,9 @@ namespace RageCoop.Client
var result = new byte[36];
for (byte i = 0; i < 12; i++)
{
result[i]=(byte)Function.Call<short>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result[i+12]=(byte)Function.Call<short>(Hash.GET_PED_TEXTURE_VARIATION, ped.Handle, i);
result[i+24]=(byte)Function.Call<short>(Hash.GET_PED_PALETTE_VARIATION, ped.Handle, i);
result[i] = (byte)Function.Call<short>(Hash.GET_PED_DRAWABLE_VARIATION, ped.Handle, i);
result[i + 12] = (byte)Function.Call<short>(Hash.GET_PED_TEXTURE_VARIATION, ped.Handle, i);
result[i + 24] = (byte)Function.Call<short>(Hash.GET_PED_PALETTE_VARIATION, ped.Handle, i);
}
return result;
}
@ -99,7 +108,7 @@ namespace RageCoop.Client
}
// Fake death
if (ped.IsRagdoll || (ped.Health==1 && ped.IsPlayer))
if (ped.IsRagdoll || (ped.Health == 1 && ped.IsPlayer))
{
flags |= PedDataFlags.IsRagdoll;
}
@ -132,14 +141,32 @@ namespace RageCoop.Client
if (ped.IsInCover || ped.IsGoingIntoCover)
{
flags |=PedDataFlags.IsInCover;
flags |= PedDataFlags.IsInCover;
if (ped.IsInCoverFacingLeft)
{
flags |= PedDataFlags.IsInCover;
}
if (!Function.Call<bool>(Hash.IS_PED_IN_HIGH_COVER, ped))
{
flags |= PedDataFlags.IsInLowCover;
}
if (ped.IsTaskActive(TaskType.CTaskAimGunBlindFire))
{
flags |= PedDataFlags.IsBlindFiring;
}
}
if(Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped))
if (ped.IsInvincible)
{
flags |= PedDataFlags.IsInvincible;
}
if (Function.Call<bool>(Hash.GET_PED_STEALTH_MOVEMENT, ped))
{
flags |= PedDataFlags.IsInStealthMode;
}
return flags;
}
@ -250,7 +277,7 @@ namespace RageCoop.Client
veh,
veh.Bones[text].Index
}));
bool flag2 = (num2 < distanceToignoreDoors) && (num2 < num)&& IsSeatUsableByPed(ped, veh, dictionary[text]);
bool flag2 = (num2 < distanceToignoreDoors) && (num2 < num) && IsSeatUsableByPed(ped, veh, dictionary[text]);
if (flag2)
{
num = num2;
@ -294,7 +321,7 @@ namespace RageCoop.Client
}
return result;
}
public static bool IsTaskActive(this Ped p,TaskType task)
public static bool IsTaskActive(this Ped p, TaskType task)
{
return Function.Call<bool>(Hash.GET_IS_TASK_ACTIVE, p.Handle, task);
}
@ -304,47 +331,132 @@ namespace RageCoop.Client
var v = p.CurrentVehicle;
// Rhino
if (v!=null && v.Model.Hash==782665360)
if (v != null && v.Model.Hash == 782665360)
{
return v.Bones[35].Position+v.Bones[35].ForwardVector*100;
return v.Bones[35].Position + v.Bones[35].ForwardVector * 100;
}
if (p.IsOnTurretSeat()) { return p.GetLookingCoord(); }
if (weapon!=null)
if (weapon != null)
{
// Not very accurate, but doesn't matter
Vector3 dir = weapon.RightVector;
return weapon.Position+dir*20;
return weapon.Position + dir * 20;
}
return GetLookingCoord(p);
}
public static Vector3 GetLookingCoord(this Ped p)
{
if (p == Main.P && Function.Call<int>(Hash.GET_FOLLOW_PED_CAM_VIEW_MODE) == 4)
{
return RaycastEverything(default);
}
EntityBone b = p.Bones[Bone.FacialForehead];
Vector3 v = b.UpVector.Normalized;
return b.Position+200*v;
}
public static void StayInCover(this Ped p)
{
Function.Call(Hash.TASK_STAY_IN_COVER, p);
return b.Position + 200 * v;
}
public static VehicleSeat GetSeatTryingToEnter(this Ped p)
{
return (VehicleSeat)Function.Call<int>(Hash.GET_SEAT_PED_IS_TRYING_TO_ENTER, p);
}
#endregion
public static Vector3 RaycastEverything(Vector2 screenCoord)
{
Vector3 camPos = GameplayCamera.Position;
Vector3 camRot = GameplayCamera.Rotation;
const float raycastToDist = 100.0f;
const float raycastFromDist = 1f;
Vector3 target3D = ScreenRelToWorld(camPos, camRot, screenCoord);
Vector3 source3D = camPos;
Entity ignoreEntity = Game.Player.Character;
if (Game.Player.Character.IsInVehicle())
{
ignoreEntity = Game.Player.Character.CurrentVehicle;
}
Vector3 dir = target3D - source3D;
dir.Normalize();
RaycastResult 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)
{
Vector3 camForward = camRot.ToDirection();
Vector3 rotUp = camRot + new Vector3(10, 0, 0);
Vector3 rotDown = camRot + new Vector3(-10, 0, 0);
Vector3 rotLeft = camRot + new Vector3(0, 0, -10);
Vector3 rotRight = camRot + new Vector3(0, 0, 10);
Vector3 camRight = rotRight.ToDirection() - rotLeft.ToDirection();
Vector3 camUp = rotUp.ToDirection() - rotDown.ToDirection();
double rollRad = -camRot.Y.ToRadians();
Vector3 camRightRoll = camRight * (float)Math.Cos(rollRad) - camUp * (float)Math.Sin(rollRad);
Vector3 camUpRoll = camRight * (float)Math.Sin(rollRad) + camUp * (float)Math.Cos(rollRad);
Vector3 point3D = camPos + camForward * 10.0f + camRightRoll + camUpRoll;
if (!WorldToScreenRel(point3D, out Vector2 point2D))
{
return camPos + camForward * 10.0f;
}
Vector3 point3DZero = camPos + camForward * 10.0f;
if (!WorldToScreenRel(point3DZero, out Vector2 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;
}
float scaleX = (coord.X - point2DZero.X) / (point2D.X - point2DZero.X);
float 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)
{
OutputArgument num1 = new OutputArgument();
OutputArgument num2 = new OutputArgument();
if (!Function.Call<bool>(Hash.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)
{
Function.Call(Hash.TASK_STAY_IN_COVER, p);
}
public static bool IsTurretSeat(this Vehicle veh, int seat)
{
if (!Function.Call<bool>(Hash.DOES_VEHICLE_HAVE_WEAPONS, veh.Handle))
@ -364,7 +476,7 @@ namespace RageCoop.Client
|| (VehicleHash)veh.Model.Hash == VehicleHash.Cerberus3;
case 0:
return (VehicleHash)veh.Model.Hash == VehicleHash.Apc
|| (VehicleHash)veh.Model.Hash==VehicleHash.Dune3;
|| (VehicleHash)veh.Model.Hash == VehicleHash.Dune3;
case 1:
return (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie
|| (VehicleHash)veh.Model.Hash == VehicleHash.Valkyrie2
@ -393,7 +505,7 @@ namespace RageCoop.Client
}
}
}

View File

@ -0,0 +1,438 @@
// 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
};

View File

@ -1,45 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using GTA;
using GTA.Math;
using GTA;
using RageCoop.Core;
using GTA.Native;
using Newtonsoft.Json;
using RageCoop.Core;
using System;
using System.Drawing;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
namespace RageCoop.Client
{
internal static class Util
{
public static void StartUpCheck()
{
if (AppDomain.CurrentDomain.GetData("RageCoop.Client.LoaderContext") == null)
{
var error = $"Client not loaded with loader, please re-install using the installer to fix this issue";
try
{
GTA.UI.Notification.Show("~r~" + error);
}
catch { }
throw new Exception(error);
}
}
public static SizeF ResolutionMaintainRatio
{
get
{
// Get the game width and height
int screenw = GTA.UI.Screen.Resolution.Width;
int screenh = GTA.UI.Screen.Resolution.Height;
// Calculate the ratio
float ratio = (float)screenw / screenh;
// And the width with that ratio
float width = 1080f * ratio;
// Finally, return a SizeF
return new SizeF(width, 1080f);
}
}
public static bool WorldToScreen(Vector3 pos, ref Point screenPos)
{
float x, y;
unsafe
{
var res = ResolutionMaintainRatio;
if (Function.Call<bool>(Hash.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;
}
#region -- POINTER --
private static int _steeringAngleOffset { get; set; }
public static unsafe void NativeMemory()
{
IntPtr address;
address = Game.FindPattern("\x74\x0A\xF3\x0F\x11\xB3\x1C\x09\x00\x00\xEB\x25", "xxxxxx????xx");
if (address != IntPtr.Zero)
{
_steeringAngleOffset = *(int*)(address + 6) + 8;
}
// breaks some stuff.
/*
address = Game.FindPattern("\x32\xc0\xf3\x0f\x11\x09", "xxxxxx"); // Weapon / Radio slowdown
if (address != IntPtr.Zero)
{
for (int i = 0; i < 6; i++)
{
*(byte*)(address + i).ToPointer() = 0x90;
}
}
*/
}
public static unsafe void CustomSteeringAngle(this Vehicle veh, float value)
@ -90,77 +121,53 @@ namespace RageCoop.Client
#endregion
public static string SettingsPath= "Scripts\\RageCoop\\Data\\RageCoop.Client.Settings.xml";
public static Settings ReadSettings()
public static string SettingsPath = "RageCoop\\Settings.json";
public static Settings ReadSettings(string path = null)
{
XmlSerializer ser = new XmlSerializer(typeof(Settings));
path = path ?? SettingsPath;
string path = SettingsPath;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
Settings settings = null;
if (File.Exists(path))
Settings settings;
try
{
using (FileStream stream = File.OpenRead(path))
{
settings = (RageCoop.Client.Settings)ser.Deserialize(stream);
}
using (FileStream stream = new FileStream(path, FileMode.Truncate, FileAccess.ReadWrite))
{
ser.Serialize(stream, settings);
}
settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(path));
}
else
catch (Exception ex)
{
using (FileStream stream = File.OpenWrite(path))
{
ser.Serialize(stream, settings = new Settings());
}
Main.Logger?.Error(ex);
File.WriteAllText(path, JsonConvert.SerializeObject(settings = new Settings(), Formatting.Indented));
}
return settings;
}
public static void SaveSettings()
public static bool SaveSettings(string path = null, Settings settings = null)
{
try
{
string path = SettingsPath ;
path = path ?? SettingsPath;
settings = settings ?? Main.Settings;
Directory.CreateDirectory(Directory.GetParent(path).FullName);
using (FileStream stream = new FileStream(path, File.Exists(path) ? FileMode.Truncate : FileMode.Create, FileAccess.ReadWrite))
{
XmlSerializer ser = new XmlSerializer(typeof(Settings));
ser.Serialize(stream, Main.Settings);
}
File.WriteAllText(path, JsonConvert.SerializeObject(settings, Formatting.Indented));
return true;
}
catch (Exception ex)
{
GTA.UI.Notification.Show("Error saving player settings: " + ex.Message);
Main.Logger?.Error(ex);
return false;
}
}
public static Vector3 PredictPosition(this Entity e, bool applyDefault = true)
public static Vehicle CreateVehicle(Model model, Vector3 position, float heading = 0f)
{
return e.Position+e.Velocity*((applyDefault ? SyncParameters.PositioinPredictionDefault : 0)+Networking.Latency);
if (!model.IsLoaded) { return null; }
return (Vehicle)Entity.FromHandle(Function.Call<int>(Hash.CREATE_VEHICLE, model.Hash, position.X, position.Y, position.Z, heading, false, false));
}
public static Model ModelRequest(this int hash)
public static Ped CreatePed(Model model, Vector3 position, float heading = 0f)
{
Model model = new Model(hash);
if (!model.IsValid)
{
//GTA.UI.Notification.Show("~y~Not valid!");
return null;
}
if (!model.IsLoaded)
{
return model.Request(1000) ? model : null;
}
return model;
if (!model.IsLoaded) { return null; }
return (Ped)Entity.FromHandle(Function.Call<int>(Hash.CREATE_PED, 26, model.Hash, position.X, position.Y, position.Z, heading, false, false));
}
public static void SetOnFire(this Entity e, bool toggle)
{
@ -173,7 +180,7 @@ namespace RageCoop.Client
Function.Call(Hash.STOP_ENTITY_FIRE, e.Handle);
}
}
public static void SetFrozen(this Entity e,bool toggle)
public static void SetFrozen(this Entity e, bool toggle)
{
Function.Call(Hash.FREEZE_ENTITY_POSITION, e, toggle);
}
@ -182,7 +189,7 @@ namespace RageCoop.Client
{
if (p == null) { return null; }
var c = EntityPool.GetPedByHandle(p.Handle);
if (c==null) { EntityPool.Add(c=new SyncedPed(p)); }
if (c == null) { EntityPool.Add(c = new SyncedPed(p)); }
return c;
}
@ -190,10 +197,14 @@ namespace RageCoop.Client
{
if (veh == null) { return null; }
var v = EntityPool.GetVehicleByHandle(veh.Handle);
if (v==null) { EntityPool.Add(v=new SyncedVehicle(veh)); }
if (v == null) { EntityPool.Add(v = new SyncedVehicle(veh)); }
return v;
}
public static void ApplyForce(this Entity e, int boneIndex, Vector3 direction, Vector3 rotation = default(Vector3), ForceType forceType = ForceType.MaxForceRot2)
{
Function.Call(Hash.APPLY_FORCE_TO_ENTITY, e.Handle, forceType, direction.X, direction.Y, direction.Z, rotation.X, rotation.Y, rotation.Z, boneIndex, false, true, true, false, true);
}
public static byte GetPlayerRadioIndex()
{
return (byte)Function.Call<int>(Hash.GET_PLAYER_RADIO_STATION_INDEX);
@ -202,47 +213,6 @@ namespace RageCoop.Client
{
Function.Call(Hash.SET_RADIO_TO_STATION_INDEX, index);
}
public static byte[] GetHash(this string inputString)
{
using (HashAlgorithm algorithm = SHA256.Create())
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
}
const UInt32 WM_KEYDOWN = 0x0100;
public static void Reload()
{
string reloadKey="None";
var lines = File.ReadAllLines("ScriptHookVDotNet.ini");
foreach (var l in lines)
{
var ss = l.Split('=');
if(ss.Length > 0 && ss[0]=="ReloadKey")
{
reloadKey = ss[1];
}
}
var lineList = lines.ToList();
if (reloadKey=="None")
{
foreach (var l in lines)
{
var ss = l.Split('=');
if (ss.Length > 0 && ss[0]=="ReloadKey")
{
reloadKey = ss[1];
lineList.Remove(l);
}
}
lineList.Add("ReloadKey=Insert");
File.WriteAllLines("ScriptHookVDotNet.ini",lineList.ToArray());
}
Keys key = (Keys)Enum.Parse(typeof(Keys), reloadKey, true);
PostMessage(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle, WM_KEYDOWN, (int)key, 0);
}
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);
[DllImport("kernel32.dll")]

View File

@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GTA;
using GTA;
using GTA.Native;
using RageCoop.Core;
using GTA.Math;
using System;
using System.Collections.Generic;
namespace RageCoop.Client
{
@ -14,8 +10,9 @@ namespace RageCoop.Client
{
#region VEHICLE
public static VehicleDataFlags GetVehicleFlags(this Vehicle veh)
public static VehicleDataFlags GetVehicleFlags(this SyncedVehicle v)
{
var veh = v.MainVehicle;
VehicleDataFlags flags = 0;
if (veh.IsEngineRunning)
@ -53,34 +50,59 @@ namespace RageCoop.Client
flags |= VehicleDataFlags.IsHornActive;
}
if (veh.IsSubmarineCar && Function.Call<bool>(Hash._GET_IS_SUBMARINE_VEHICLE_TRANSFORMED, veh.Handle))
if (v.IsSubmarineCar && Function.Call<bool>(Hash._GET_IS_SUBMARINE_VEHICLE_TRANSFORMED, veh.Handle))
{
flags |= VehicleDataFlags.IsTransformed;
}
if (veh.HasRoof && (veh.RoofState == VehicleRoofState.Opened || veh.RoofState == VehicleRoofState.Opening))
{
flags |= VehicleDataFlags.RoofOpened;
}
if (veh.IsAircraft)
if (v.IsAircraft)
{
flags |= VehicleDataFlags.IsAircraft;
}
if (veh.Model.Hash==1483171323 && veh.IsDeluxoHovering())
if (v.IsDeluxo && veh.IsDeluxoHovering())
{
flags|= VehicleDataFlags.IsDeluxoHovering;
}
if (veh.HasRoof)
{
flags|=VehicleDataFlags.HasRoof;
flags |= VehicleDataFlags.IsDeluxoHovering;
}
if (v.HasRoof)
{
flags |= VehicleDataFlags.HasRoof;
}
if (v.HasRocketBoost && veh.IsRocketBoostActive())
{
flags |= VehicleDataFlags.IsRocketBoostActive;
}
if (v.HasParachute && veh.IsParachuteActive())
{
flags |= VehicleDataFlags.IsParachuteActive;
}
if (veh.IsOnFire)
{
flags |= VehicleDataFlags.IsOnFire;
}
return flags;
}
public static bool IsRocketBoostActive(this Vehicle veh)
{
return Function.Call<bool>(Hash._IS_VEHICLE_ROCKET_BOOST_ACTIVE, veh);
}
public static bool IsParachuteActive(this Vehicle veh)
{
return Function.Call<bool>((Hash)0x3DE51E9C80B116CF, veh);
}
public static void SetRocketBoostActive(this Vehicle veh, bool toggle)
{
Function.Call(Hash._SET_VEHICLE_ROCKET_BOOST_ACTIVE, veh, toggle);
}
public static void SetParachuteActive(this Vehicle veh, bool toggle)
{
Function.Call((Hash)0x0BFFB028B3DD0A97, veh, toggle);
}
public static Dictionary<int, int> GetVehicleMods(this VehicleModCollection mods)
{
Dictionary<int, int> result = new Dictionary<int, int>();
@ -118,7 +140,6 @@ namespace RageCoop.Client
}
}
// Bursted tires
short burstedTires = 0;
foreach (VehicleWheel wheel in veh.Wheels.GetAllWheels())
@ -139,7 +160,7 @@ namespace RageCoop.Client
RightHeadLightBroken = (byte)(veh.IsRightHeadLightBroken ? 1 : 0)
};
}
public static void SetDamageModel(this Vehicle veh, VehicleDamageModel model, bool leavedoors = true)
{
for (int i = 0; i < 8; i++)
@ -151,6 +172,7 @@ namespace RageCoop.Client
{
door.Break(leavedoors);
}
continue;
}
else if (door.IsBroken)
{
@ -160,7 +182,7 @@ namespace RageCoop.Client
}
if ((model.OpenedDoors & (byte)(1 << i)) != 0)
{
if ((!door.IsOpen)&&(!door.IsBroken))
if ((!door.IsOpen) && (!door.IsBroken))
{
door.Open();
}
@ -204,7 +226,7 @@ namespace RageCoop.Client
{
Dictionary<int, int> ps = new Dictionary<int, int>();
var d = veh.Driver;
if (d!=null&&d.IsSittingInVehicle())
if (d != null && d.IsSittingInVehicle())
{
ps.Add(-1, d.GetSyncEntity().ID);
}
@ -212,20 +234,19 @@ namespace RageCoop.Client
{
if (p.IsSittingInVehicle())
{
ps.Add((int)p.SeatIndex, (int)p.GetSyncEntity().ID);
ps.Add((int)p.SeatIndex, p.GetSyncEntity().ID);
}
}
return ps;
}
public static void SetDeluxoHoverState(this Vehicle deluxo, bool hover)
{
Function.Call(Hash._SET_VEHICLE_HOVER_TRANSFORM_PERCENTAGE, deluxo, hover ? 1f : 0f);
}
public static bool IsDeluxoHovering(this Vehicle deluxo)
{
return Math.Abs(deluxo.Bones[27].ForwardVector.GetCosTheta(deluxo.ForwardVector)-1)>0.05;
return Math.Abs(deluxo.Bones[27].ForwardVector.GetCosTheta(deluxo.ForwardVector) - 1) > 0.05;
}
public static void SetDeluxoWingRatio(this Vehicle v, float ratio)
{
@ -233,7 +254,7 @@ namespace RageCoop.Client
}
public static float GetDeluxoWingRatio(this Vehicle v)
{
return v.Bones[99].Position.DistanceTo(v.Bones[92].Position)-1.43f;
return v.Bones[99].Position.DistanceTo(v.Bones[92].Position) - 1.43f;
}
public static float GetNozzleAngel(this Vehicle plane)
{
@ -267,7 +288,6 @@ namespace RageCoop.Client
{
Function.Call(Hash.SET_VEHICLE_FLIGHT_NOZZLE_POSITION, plane, ratio);
}
#endregion
}
}

View File

@ -0,0 +1,543 @@
using GTA;
using GTA.Math;
using GTA.Native;
using System.Collections.Generic;
namespace RageCoop.Client
{
internal class MuzzleInfo
{
public MuzzleInfo(Vector3 pos, Vector3 forward)
{
Position = pos;
ForawardVector = forward;
}
public Vector3 Position;
public Vector3 ForawardVector;
}
internal static class WeaponUtil
{
public static Dictionary<uint, bool> GetWeaponComponents(this Weapon weapon)
{
Dictionary<uint, bool> result = null;
if (weapon.Components.Count > 0)
{
result = new Dictionary<uint, bool>();
foreach (var comp in weapon.Components)
{
result.Add((uint)comp.ComponentHash, comp.Active);
}
}
return result;
}
public static Vector3 GetMuzzlePosition(this Ped p)
{
var w = p.Weapons.CurrentWeaponObject;
if (w != null)
{
var hash = p.Weapons.Current.Hash;
if (MuzzleBoneIndexes.ContainsKey(hash)) { return w.Bones[MuzzleBoneIndexes[hash]].Position; }
return w.Position;
}
return p.Bones[Bone.SkelRightHand].Position;
}
private static long BulletsShot = 0;
public static float GetWeaponDamage(this Ped P, uint hash)
{
var comp = P.Weapons.Current.Components.GetSuppressorComponent();
return Function.Call<float>(Hash.GET_WEAPON_DAMAGE, hash, comp.Active ? comp.ComponentHash : WeaponComponentHash.Invalid);
/*
if (P.IsInVehicle() && (hash!=(uint)P.Weapons.Current.Hash))
{
// This is a vehicle weapon
P.VehicleWeapon=(VehicleWeaponHash)hash;
return 100;
}
switch (P.Weapons.Current.Group)
{
case WeaponGroup.Pistol: return 30;
case WeaponGroup.AssaultRifle: return 30;
case WeaponGroup.SMG: return 20;
case WeaponGroup.MG: return 40;
case WeaponGroup.Shotgun: return 30;
case WeaponGroup.Sniper: return 200;
case WeaponGroup.Heavy: return 30;
}
return 0;
*/
}
public static int GetMuzzleIndex(this Vehicle v)
{
BulletsShot++;
switch (v.Model.Hash)
{
// cerberus3
case 1909700336:
return 53;
// cerberus2
case 679453769:
return 54;
// cerberus
case -801550069:
return 90;
/*
// cerberus (flame)
case -801550069:
i=BulletsShot%2==0 ? 89 : 88;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
// cerberus (passenger flame)
case -801550069:
i=BulletsShot%2==0 ? 76 : 75;
return new MuzzleInfo(v.Bones[i].Position, v.Bones[i].ForwardVector);
*/
// ISSI6
case 1239571361:
return BulletsShot % 2 == 0 ? 12 : 14;
// ISSI5
case 1537277726:
return BulletsShot % 2 == 0 ? 30 : 32;
// ISSI4
case 628003514:
return BulletsShot % 2 == 0 ? 14 : 12;
// DOMINATOR6
case -1293924613:
return BulletsShot % 2 == 0 ? 51 : 55;
// IMPALER4
case -1744505657:
return BulletsShot % 2 == 0 ? 64 : 63;
// IMPERATOR3
case -755532233:
return BulletsShot % 2 == 0 ? 86 : 88;
// SLAMVAN6
case 1742022738:
return BulletsShot % 2 == 0 ? 78 : 76;
// CHAMPION
case -915234475:
return BulletsShot % 2 == 0 ? 60 : 61;
// MONSTER4
case 840387324:
return BulletsShot % 2 == 0 ? 63 : 65;
// BRUTUS2
case -1890996696:
return 67;
// BRUISER2
case -1694081890:
return BulletsShot % 2 == 0 ? 45 : 51;
// TECHNICAL3
case 1356124575:
return 67;
// TECHNICAL2
case 1180875963:
return 54;
// TECHNICAL
case -2096818938:
return 63;
// PATRIOT3
case -670086588:
return BulletsShot % 2 == 0 ? 87 : 89;
// NIGHTSHARK
case 433954513:
return BulletsShot % 2 == 0 ? 1 : 2;
/*
// NIGHTSHARK (second)
case 433954513:
return BulletsShot%2==0 ? 3 : 4;
*/
// MENACER
case 2044532910:
return BulletsShot % 2 == 0 ? 91 : 90;
/*
// MENACER
case 2044532910:
return new MuzzleInfo(v.Bones[75].Position, v.Bones[75].ForwardVector);
// MENACER
case 2044532910:
return new MuzzleInfo(v.Bones[78].Position, v.Bones[78].ForwardVector);
*/
// CARACARA
case 1254014755:
return 83;
/*
// CARACARA
case 1254014755:
return BulletsShot%2==0 ? 93 : 94;
*/
// INSURGENT
case -1860900134:
return 49;
// INSURGENT3
case -1924433270:
return 81;
/*
// INSURGENT3
case -1924433270:
return BulletsShot%2==0 ? 86 : 91;
*/
// BLAZER5
case -1590337689:
return BulletsShot % 2 == 0 ? 17 : 18;
// BRUISER
case 668439077:
return BulletsShot % 2 == 0 ? 66 : 68;
// BRUTUS
case 2139203625:
return 84;
// MONSTER3
case 1721676810:
return BulletsShot % 2 == 0 ? 53 : 55;
// BRUISER3
case -2042350822:
return BulletsShot % 2 == 0 ? 52 : 50;
// BRUTUS3
case 2038858402:
return 84;
// MONSTER5
case -715746948:
return BulletsShot % 2 == 0 ? 63 : 65;
// JB7002
case 394110044:
return BulletsShot % 2 == 0 ? 54 : 53;
// DOMINATOR5
case -1375060657:
return BulletsShot % 2 == 0 ? 35 : 36;
// IMPALER3
case -1924800695:
return BulletsShot % 2 == 0 ? 75 : 76;
// IMPERATOR2
case 1637620610:
return BulletsShot % 2 == 0 ? 97 : 99;
// SLAMVAN5
case 373261600:
return BulletsShot % 2 == 0 ? 51 : 53;
// RUINER2
case 941494461:
return BulletsShot % 2 == 0 ? 65 : 66;
// TAMPA3
case -1210451983:
return 87;
// SCRAMJET
case -638562243:
return BulletsShot % 2 == 0 ? 44 : 45;
// VIGILANTE
case -1242608589:
return BulletsShot % 2 == 0 ? 42 : 43;
// ZR380
case 540101442:
return BulletsShot % 2 == 0 ? 57 : 63;
// ZR3802
case -1106120762:
return BulletsShot % 2 == 0 ? 57 : 63;
// ZR3803
case -1478704292:
return BulletsShot % 2 == 0 ? 53 : 59;
// STROMBERG
case 886810209:
return BulletsShot % 2 == 0 ? 85 : 84;
// SLAMVAN4
case -2061049099:
return BulletsShot % 2 == 0 ? 76 : 78;
// IMPERATOR
case 444994115:
return BulletsShot % 2 == 0 ? 88 : 86;
// IMPALER2
case 1009171724:
return BulletsShot % 2 == 0 ? 63 : 64;
// DOMINATOR4
case -688189648:
return BulletsShot % 2 == 0 ? 59 : 60;
// SAVAGE
case -82626025:
return 30;
// BUZZARD
case 788747387:
return BulletsShot % 2 == 0 ? 28 : 23;
// ANNIHL
case 837858166:
return (int)BulletsShot % 4 + 35;
// HYDRA
case 970385471:
return BulletsShot % 2 == 0 ? 29 : 28;
// STARLING
case -1700874274:
return BulletsShot % 2 == 0 ? 24 : 12;
// RHINO
case 782665360:
return 30;
default:
return AddOnDataProvider.GetMuzzleIndex(v.Model.Hash);
}
}
public static bool IsUsingProjectileWeapon(this Ped p)
{
var vp = p.VehicleWeapon;
var type = Function.Call<int>(Hash.GET_WEAPON_DAMAGE_TYPE, vp);
if (vp != VehicleWeaponHash.Invalid)
{
return type == 3 ? false : VehicleProjectileWeapons.Contains(vp) || (type == 5 && !ExplosiveBullets.Contains((uint)vp));
}
var w = p.Weapons.Current;
return w.Group == WeaponGroup.Thrown || ProjectileWeapons.Contains(w.Hash);
}
public static readonly HashSet<uint> ExplosiveBullets = new HashSet<uint>
{
(uint)VehicleWeaponHash.PlayerLazer,
(uint)WeaponHash.Railgun,
1638077257
};
public static readonly Dictionary<WeaponHash, int> MuzzleBoneIndexes = new Dictionary<WeaponHash, int>
{
{WeaponHash.HeavySniper,6},
{WeaponHash.MarksmanRifle,9},
{WeaponHash.SniperRifle,9},
{WeaponHash.AdvancedRifle,5},
{WeaponHash.SpecialCarbine,9},
{WeaponHash.BullpupRifle,7},
{WeaponHash.AssaultRifle,9},
{WeaponHash.CarbineRifle,6},
{WeaponHash.MachinePistol,5},
{WeaponHash.SMG,5},
{WeaponHash.AssaultSMG,6},
{WeaponHash.CombatPDW,5},
{WeaponHash.MG,6},
{WeaponHash.CombatMG,7},
{WeaponHash.Gusenberg,7},
{WeaponHash.MicroSMG,10},
{WeaponHash.APPistol,8},
{WeaponHash.StunGun,4},
{WeaponHash.Pistol,8},
{WeaponHash.CombatPistol,8},
{WeaponHash.Pistol50,7},
{WeaponHash.SNSPistol,8},
{WeaponHash.HeavyPistol,8},
{WeaponHash.VintagePistol,8},
{WeaponHash.Railgun,9},
{WeaponHash.Minigun,5},
{WeaponHash.Musket,3},
{WeaponHash.HeavyShotgun,10},
{WeaponHash.PumpShotgun,11},
{WeaponHash.SawnOffShotgun,8},
{WeaponHash.BullpupShotgun,8},
{WeaponHash.AssaultShotgun,9},
{WeaponHash.HeavySniperMk2,11},
{WeaponHash.MarksmanRifleMk2,9},
{WeaponHash.CarbineRifleMk2,13},
{WeaponHash.SpecialCarbineMk2,16},
{WeaponHash.BullpupRifleMk2,8},
{WeaponHash.CompactRifle,7},
{WeaponHash.MilitaryRifle,11},
{WeaponHash.AssaultrifleMk2,17},
{WeaponHash.MiniSMG,5},
{WeaponHash.SMGMk2,6},
{WeaponHash.CombatMGMk2,16},
{WeaponHash.UnholyHellbringer,4},
{WeaponHash.PistolMk2,12},
{WeaponHash.SNSPistolMk2,15},
{WeaponHash.CeramicPistol,10},
{WeaponHash.MarksmanPistol,4},
{WeaponHash.Revolver,7},
{WeaponHash.RevolverMk2,7},
{WeaponHash.DoubleActionRevolver,7},
{WeaponHash.NavyRevolver,7},
{WeaponHash.PericoPistol,4},
{WeaponHash.FlareGun,4},
{WeaponHash.UpNAtomizer,4},
{WeaponHash.HomingLauncher,5},
{WeaponHash.CompactGrenadeLauncher,8},
{WeaponHash.Widowmaker,6},
{WeaponHash.GrenadeLauncher,3},
{WeaponHash.RPG,9},
{WeaponHash.DoubleBarrelShotgun,8},
{WeaponHash.SweeperShotgun,7},
{WeaponHash.CombatShotgun,7},
{WeaponHash.PumpShotgunMk2,7},
};
public static readonly HashSet<WeaponHash> ProjectileWeapons = new HashSet<WeaponHash> {
WeaponHash.HomingLauncher,
WeaponHash.RPG,
WeaponHash.Firework,
WeaponHash.UpNAtomizer,
WeaponHash.GrenadeLauncher,
WeaponHash.GrenadeLauncherSmoke,
WeaponHash.CompactGrenadeLauncher,
WeaponHash.FlareGun,
};
public static readonly HashSet<VehicleWeaponHash> VehicleProjectileWeapons = new HashSet<VehicleWeaponHash> {
VehicleWeaponHash.PlaneRocket,
VehicleWeaponHash.SpaceRocket,
VehicleWeaponHash.Tank,
(VehicleWeaponHash)3565779982, // STROMBERG missiles
(VehicleWeaponHash)3169388763, // SCRAMJET missiles
};
public static string GetFlashFX(this WeaponHash w)
{
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";
}
switch ((VehicleWeaponHash)w)
{
case VehicleWeaponHash.Tank:
return "muz_tank";
case VehicleWeaponHash.PlayerBuzzard:
return "muz_buzzard";
}
return "muz_assault_rifle";
}
public static WeaponGroup GetWeaponGroup(this WeaponHash hash)
{
return Function.Call<WeaponGroup>(Hash.GET_WEAPONTYPE_GROUP, hash);
}
}
/*
class WeaponInfo
{
public string Name;
public string MuzzleFx;
}
public class AimingInfo
{
public string Name;
public float HeadingLimit;
public float SweepPitchMin;
public float SweepPitchMax;
}
*/
}

View File

@ -1,63 +1,80 @@
using System;
using System.Linq;
using RageCoop.Core;
using GTA;
using GTA;
using GTA.Native;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace RageCoop.Client
{
/// <summary>
/// Don't use it!
/// </summary>
public class WorldThread : Script
[ScriptAttributes(Author = "RageCoop", NoDefaultInstance = false, SupportURL = "https://github.com/RAGECOOP/RAGECOOP-V")]
internal class WorldThread : Script
{
private static bool _lastDisableTraffic = false;
public static Script Instance;
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
/// <summary>
/// Don't use it!
/// </summary>
public WorldThread()
{
Util.StartUpCheck();
Instance = this;
Tick += OnTick;
Aborted += (sender, e) =>
{
if (_lastDisableTraffic)
{
Traffic(true);
}
ChangeTraffic(true);
};
}
private static bool _trafficEnabled;
private void OnTick(object sender, EventArgs e)
{
if (Game.IsLoading)
DoQueuedActions();
if (Game.IsLoading || !Networking.IsOnServer)
{
return;
}
if (!Networking.IsOnServer)
{
return ;
}
Game.DisableControlThisFrame(Control.FrontendPause);
if (Main.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.
Function.Call(Hash.SET_DEEP_OCEAN_SCALER, 0.0f); // Works only ~200 meters around the player
// Function.Call(Hash.SET_CAN_ATTACK_FRIENDLY, Game.Player.Character.Handle, true, false);
if (Main.Settings==null) { return; }
if (Main.Settings.DisableTraffic)
if (Main.Settings.ShowEntityOwnerName)
{
if (!_lastDisableTraffic)
unsafe
{
Traffic(false);
}
int handle;
if (Function.Call<bool>(Hash.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";
}
GTA.UI.Screen.ShowHelpTextThisFrame("Entity owner: " + owner);
}
}
}
}
if (!_trafficEnabled)
{
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_VEHICLE_DENSITY_MULTIPLIER_THIS_FRAME, 0f);
@ -66,15 +83,13 @@ namespace RageCoop.Client
Function.Call(Hash.SUPPRESS_SHOCKING_EVENTS_NEXT_FRAME);
Function.Call(Hash.SUPPRESS_AGITATION_EVENTS_NEXT_FRAME);
}
else if (_lastDisableTraffic)
{
Traffic(true);
}
_lastDisableTraffic = Main.Settings.DisableTraffic;
}
private void Traffic(bool enable)
public static void Traffic(bool enable)
{
ChangeTraffic(enable);
_trafficEnabled = enable;
}
private static void ChangeTraffic(bool enable)
{
if (enable)
{
@ -83,15 +98,15 @@ namespace RageCoop.Client
Function.Call(Hash.SET_RANDOM_TRAINS, true);
Function.Call(Hash.SET_RANDOM_BOATS, true);
Function.Call(Hash.SET_GARBAGE_TRUCKS, true);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 1); // 0 - 3
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 1); // 0 - 3
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 3); // 0 - 3
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 3); // 0 - 3
Function.Call(Hash.SET_ALL_VEHICLE_GENERATORS_ACTIVE);
Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, true);
Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, -1);
Function.Call(Hash.SET_DISTANT_CARS_ENABLED, true);
Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, false);
}
else
else if (Networking.IsOnServer)
{
Function.Call(Hash.ADD_SCENARIO_BLOCKING_AREA, -10000.0f, -10000.0f, -1000.0f, 10000.0f, 10000.0f, 1000.0f, 0, 1, 1, 1);
Function.Call(Hash.SET_CREATE_RANDOM_COPS, false);
@ -99,46 +114,99 @@ namespace RageCoop.Client
Function.Call(Hash.SET_RANDOM_BOATS, false);
Function.Call(Hash.SET_GARBAGE_TRUCKS, false);
Function.Call(Hash.DELETE_ALL_TRAINS);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 3);
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 3);
Function.Call(Hash.SET_PED_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_VEHICLE_POPULATION_BUDGET, 0);
Function.Call(Hash.SET_ALL_LOW_PRIORITY_VEHICLE_GENERATORS_ACTIVE, false);
Function.Call(Hash.SET_FAR_DRAW_VEHICLES, false);
Function.Call(Hash.SET_NUMBER_OF_PARKED_VEHICLES, 0);
Function.Call(Hash.SET_DISTANT_CARS_ENABLED, false);
Function.Call(Hash.DISABLE_VEHICLE_DISTANTLIGHTS, true);
foreach (Ped ped in World.GetAllPeds())
{
if (ped == Game.Player.Character) { continue; }
SyncedPed c = EntityPool.GetPedByHandle(ped.Handle);
if ((c==null) || (c.IsLocal && (ped.Handle!=Game.Player.Character.Handle)&&ped.PopulationType!=EntityPopulationType.Mission))
if ((c == null) || (c.IsLocal && (ped.Handle != Game.Player.Character.Handle) && ped.PopulationType != EntityPopulationType.Mission))
{
if (ped.Handle==Game.Player.Character.Handle) { continue; }
Main.Logger.Trace($"Removing ped {ped.Handle}. Reason:RemoveTraffic");
ped.CurrentVehicle?.Delete();
ped.Kill();
ped.Delete();
}
}
foreach (Vehicle veh in World.GetAllVehicles())
{
SyncedVehicle v = veh.GetSyncEntity();
if (v.MainVehicle==Game.Player.LastVehicle)
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))
if ((v == null) || (v.IsLocal && veh.PopulationType != EntityPopulationType.Mission))
{
Main.Logger.Debug($"Removing Vehicle {veh.Handle}. Reason:ClearTraffic");
// Main.Logger.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)
{
Main.Logger.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(); }
}
}
}

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

@ -0,0 +1,11 @@
<?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>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Extensions.ObjectPool" version="6.0.8" targetFramework="net48" />
<package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net48" />
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net48" />
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" />
<package id="NAudio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Asio" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Core" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Midi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.Wasapi" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinForms" version="2.1.0" targetFramework="net48" />
<package id="NAudio.WinMM" version="2.1.0" targetFramework="net48" />
<package id="NETStandard.Library" version="1.6.1" targetFramework="net48" />
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net48" />
<package id="SharpZipLib" version="1.4.0" targetFramework="net48" />
<package id="System.AppContext" version="4.3.0" targetFramework="net48" />
<package id="System.Buffers" version="4.5.1" targetFramework="net48" />
<package id="System.Collections" version="4.3.0" targetFramework="net48" />
<package id="System.Collections.Concurrent" version="4.3.0" targetFramework="net48" />
<package id="System.Console" version="4.3.0" targetFramework="net48" />
<package id="System.Diagnostics.Debug" version="4.3.0" targetFramework="net48" />
<package id="System.Diagnostics.DiagnosticSource" version="4.3.0" targetFramework="net48" />
<package id="System.Diagnostics.Tools" version="4.3.0" targetFramework="net48" />
<package id="System.Diagnostics.Tracing" version="4.3.0" targetFramework="net48" />
<package id="System.Globalization" version="4.3.0" targetFramework="net48" />
<package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net48" />
<package id="System.IO" version="4.3.0" targetFramework="net48" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net48" />
<package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net48" />
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net48" />
<package id="System.IO.FileSystem.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Linq" version="4.3.0" targetFramework="net48" />
<package id="System.Linq.Expressions" version="4.3.0" targetFramework="net48" />
<package id="System.Memory" version="4.5.4" targetFramework="net48" />
<package id="System.Net.Http" version="4.3.0" targetFramework="net48" />
<package id="System.Net.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Net.Sockets" version="4.3.0" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
<package id="System.ObjectModel" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Reflection.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Resources.ResourceManager" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.3" targetFramework="net48" />
<package id="System.Runtime.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.Handles" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net48" />
<package id="System.Security.AccessControl" version="4.7.0" targetFramework="net481" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net481" />
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net48" />
<package id="System.Threading" version="4.3.0" targetFramework="net48" />
<package id="System.Threading.Tasks" version="4.3.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.5.2" targetFramework="net48" />
<package id="System.Threading.Timer" version="4.3.0" targetFramework="net48" />
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net48" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net48" />
</packages>

57
Core/BitReader.cs Normal file
View File

@ -0,0 +1,57 @@
using GTA.Math;
using System.IO;
using System.Text;
namespace RageCoop.Core
{
internal class BitReader : BinaryReader
{
public BitReader(byte[] array) : base(new MemoryStream(array))
{
}
~BitReader()
{
Close();
Dispose();
}
public byte[] ReadByteArray()
{
return base.ReadBytes(ReadInt32());
}
public override string ReadString()
{
return Encoding.UTF8.GetString(ReadBytes(ReadInt32()));
}
public Vector3 ReadVector3()
{
return new Vector3()
{
X = ReadSingle(),
Y = ReadSingle(),
Z = ReadSingle()
};
}
public Vector2 ReadVector2()
{
return new Vector2()
{
X = ReadSingle(),
Y = ReadSingle()
};
}
public Quaternion ReadQuaternion()
{
return new Quaternion()
{
X = ReadSingle(),
Y = ReadSingle(),
Z = ReadSingle(),
W = ReadSingle()
};
}
}
}

494
Core/CoreUtils.cs Normal file
View File

@ -0,0 +1,494 @@
using GTA.Math;
using Lidgren.Network;
using Newtonsoft.Json;
using RageCoop.Core.Scripting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
[assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")]
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
[assembly: InternalsVisibleTo("RageCoop.ResourceBuilder")]
namespace RageCoop.Core
{
internal static class CoreUtils
{
private static readonly HashSet<string> ToIgnore = new HashSet<string>()
{
"RageCoop.Client",
"RageCoop.Client.Loader",
"RageCoop.Client.Installer",
"RageCoop.Core",
"RageCoop.Server",
"ScriptHookVDotNet2",
"ScriptHookVDotNet3",
"ScriptHookVDotNet"
};
public static void GetDependencies(Assembly assembly, ref HashSet<string> existing)
{
if (assembly.FullName.StartsWith("System")) { return; }
foreach(var name in assembly.GetReferencedAssemblies())
{
if (name.FullName.StartsWith("System")) { continue; }
try
{
var asm = Assembly.Load(name);
GetDependencies(asm,ref existing);
}
catch { }
}
if (!existing.Contains(assembly.FullName))
{
Console.WriteLine(assembly.FullName);
existing.Add(assembly.FullName);
}
}
public static Version GetLatestVersion(string branch = "dev-nightly")
{
var url = $"https://raw.githubusercontent.com/RAGECOOP/RAGECOOP-V/{branch}/RageCoop.Server/Properties/AssemblyInfo.cs";
var versionLine = HttpHelper.DownloadString(url).Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries).Where(x => x.Contains("[assembly: AssemblyVersion(")).First();
var start = versionLine.IndexOf('\"') + 1;
var end = versionLine.LastIndexOf('\"');
return Version.Parse(versionLine.Substring(start, end - start));
}
public static bool CanBeIgnored(this string name)
{
return ToIgnore.Contains(Path.GetFileNameWithoutExtension(name));
}
public static string ToFullPath(this string path)
{
return Path.GetFullPath(path);
}
public static void GetBytesFromObject(object obj, NetOutgoingMessage m)
{
switch (obj)
{
case byte value:
m.Write((byte)0x01); m.Write(value); break;
case short value:
m.Write((byte)0x02); m.Write(value); break;
case ushort value:
m.Write((byte)0x03); m.Write(value); break;
case int value:
m.Write((byte)0x04); m.Write(value); break;
case uint value:
m.Write((byte)0x05); m.Write(value); break;
case long value:
m.Write((byte)0x06); m.Write(value); break;
case ulong value:
m.Write((byte)0x07); m.Write(value); break;
case float value:
m.Write((byte)0x08); m.Write(value); break;
case bool value:
m.Write((byte)0x09); m.Write(value); break;
case string value:
m.Write((byte)0x10); m.Write(value); break;
case Vector3 value:
m.Write((byte)0x11); m.Write(value); break;
case Quaternion value:
m.Write((byte)0x12); m.Write(value); break;
case GTA.Model value:
m.Write((byte)0x13); m.Write(value); break;
case Vector2 value:
m.Write((byte)0x14); m.Write(value); break;
case byte[] value:
m.Write((byte)0x15); m.WriteByteArray(value); break;
case Tuple<byte, byte[]> value:
m.Write(value.Item1); m.Write(value.Item2); break;
default:
throw new Exception("Unsupported object type: " + obj.GetType());
}
}
public static IPEndPoint StringToEndPoint(string endpointstring)
{
return StringToEndPoint(endpointstring, -1);
}
public static IPEndPoint StringToEndPoint(string endpointstring, int defaultport)
{
if (string.IsNullOrEmpty(endpointstring)
|| endpointstring.Trim().Length == 0)
{
throw new ArgumentException("Endpoint descriptor may not be empty.");
}
if (defaultport != -1 &&
(defaultport < IPEndPoint.MinPort
|| defaultport > IPEndPoint.MaxPort))
{
throw new ArgumentException(string.Format("Invalid default port '{0}'", defaultport));
}
string[] values = endpointstring.Split(new char[] { ':' });
IPAddress ipaddy;
int port = -1;
//check if we have an IPv6 or ports
if (values.Length <= 2) // ipv4 or hostname
{
if (values.Length == 1)
//no port is specified, default
port = defaultport;
else
port = getPort(values[1]);
//try to use the address as IPv4, otherwise get hostname
if (!IPAddress.TryParse(values[0], out ipaddy))
ipaddy = GetIPfromHost(values[0]);
}
else if (values.Length > 2) //ipv6
{
//could [a:b:c]:d
if (values[0].StartsWith("[") && values[values.Length - 2].EndsWith("]"))
{
string ipaddressstring = string.Join(":", values.Take(values.Length - 1).ToArray());
ipaddy = IPAddress.Parse(ipaddressstring);
port = getPort(values[values.Length - 1]);
}
else //[a:b:c] or a:b:c
{
ipaddy = IPAddress.Parse(endpointstring);
port = defaultport;
}
}
else
{
throw new FormatException(string.Format("Invalid endpoint ipaddress '{0}'", endpointstring));
}
if (port == -1)
throw new ArgumentException(string.Format("No port specified: '{0}'", endpointstring));
return new IPEndPoint(ipaddy, port);
}
private static int getPort(string p)
{
if (!int.TryParse(p, out int port)
|| port < IPEndPoint.MinPort
|| port > IPEndPoint.MaxPort)
{
throw new FormatException(string.Format("Invalid end point port '{0}'", p));
}
return port;
}
public static IPAddress GetLocalAddress(string target = "8.8.8.8")
{
using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0))
{
socket.Connect(target, 65530);
IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
return endPoint.Address;
}
}
public static IPAddress GetIPfromHost(string p)
{
var hosts = Dns.GetHostAddresses(p);
if (hosts == null || hosts.Length == 0)
throw new ArgumentException(string.Format("Host not found: {0}", p));
return hosts[0];
}
public static IpInfo GetIPInfo()
{
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var httpClient = new HttpClient();
HttpResponseMessage response = httpClient.GetAsync("https://ipinfo.io/json").GetAwaiter().GetResult();
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"IPv4 request failed! [{(int)response.StatusCode}/{response.ReasonPhrase}]");
}
string content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
return JsonConvert.DeserializeObject<IpInfo>(content);
}
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
foreach (DirectoryInfo dir in source.GetDirectories())
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
foreach (FileInfo file in source.GetFiles())
file.CopyTo(Path.Combine(target.FullName, file.Name), true);
}
public static string GetInvariantRID()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return "win-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return "linux-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return "osx-" + RuntimeInformation.OSArchitecture.ToString().ToLower();
}
return "unknown";
}
/// <summary>
/// Get local ip addresses on all network interfaces
/// </summary>
/// <returns></returns>
public static List<IPAddress> GetLocalAddress()
{
var addresses = new List<IPAddress>();
foreach (NetworkInterface netInterface in NetworkInterface.GetAllNetworkInterfaces())
{
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
foreach (UnicastIPAddressInformation addr in ipProps.UnicastAddresses)
{
addresses.Add(addr.Address);
}
}
return addresses;
}
public static StreamWriter OpenWriter(string path, FileMode mode = FileMode.Create, FileAccess access = FileAccess.Write, FileShare share = FileShare.ReadWrite)
{
return new StreamWriter(File.Open(path, mode, access, share));
}
}
internal class IpInfo
{
[JsonProperty("ip")]
public string Address { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
}
internal static class Extensions
{
public static byte[] GetBytes(this string s)
{
return Encoding.UTF8.GetBytes(s);
}
public static string GetString(this byte[] data)
{
return Encoding.UTF8.GetString(data);
}
public static byte[] GetBytes(this Vector3 vec)
{
// 12 bytes
return new List<byte[]>() { BitConverter.GetBytes(vec.X), BitConverter.GetBytes(vec.Y), BitConverter.GetBytes(vec.Z) }.Join(4);
}
public static byte[] GetBytes(this Vector2 vec)
{
// 8 bytes
return new List<byte[]>() { BitConverter.GetBytes(vec.X), BitConverter.GetBytes(vec.Y) }.Join(4);
}
/// <summary>
///
/// </summary>
/// <param name="qua"></param>
/// <returns>An array of bytes with length 16</returns>
public static byte[] GetBytes(this Quaternion qua)
{
// 16 bytes
return new List<byte[]>() { BitConverter.GetBytes(qua.X), BitConverter.GetBytes(qua.Y), BitConverter.GetBytes(qua.Z), BitConverter.GetBytes(qua.W) }.Join(4);
}
public static T GetPacket<T>(this NetIncomingMessage msg) where T : Packet, new()
{
var p = new T();
p.Deserialize(msg);
return p;
}
public static bool HasPedFlag(this PedDataFlags flags, PedDataFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasProjDataFlag(this ProjectileDataFlags flags, ProjectileDataFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasVehFlag(this VehicleDataFlags flags, VehicleDataFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasConfigFlag(this PlayerConfigFlags flags, PlayerConfigFlags flag)
{
return (flags & flag) != 0;
}
public static bool HasEventFlag(this CustomEventFlags flags, CustomEventFlags flag)
{
return (flags & flag) != 0;
}
public static Type GetActualType(this TypeCode code)
{
switch (code)
{
case TypeCode.Boolean:
return typeof(bool);
case TypeCode.Byte:
return typeof(byte);
case TypeCode.Char:
return typeof(char);
case TypeCode.DateTime:
return typeof(DateTime);
case TypeCode.DBNull:
return typeof(DBNull);
case TypeCode.Decimal:
return typeof(decimal);
case TypeCode.Double:
return typeof(double);
case TypeCode.Empty:
return null;
case TypeCode.Int16:
return typeof(short);
case TypeCode.Int32:
return typeof(int);
case TypeCode.Int64:
return typeof(long);
case TypeCode.Object:
return typeof(object);
case TypeCode.SByte:
return typeof(sbyte);
case TypeCode.Single:
return typeof(Single);
case TypeCode.String:
return typeof(string);
case TypeCode.UInt16:
return typeof(UInt16);
case TypeCode.UInt32:
return typeof(UInt32);
case TypeCode.UInt64:
return typeof(UInt64);
}
return null;
}
public static string DumpWithType(this IEnumerable<object> objects)
{
StringBuilder sb = new StringBuilder();
foreach (var obj in objects)
{
sb.Append(obj.GetType() + ":" + obj.ToString() + "\n");
}
return sb.ToString();
}
public static string Dump<T>(this IEnumerable<T> objects)
{
return $"{{{string.Join(",", objects)}}}";
}
public static void ForEach<T>(this IEnumerable<T> objects, Action<T> action)
{
foreach (var obj in objects)
{
action(obj);
}
}
public static byte[] ReadToEnd(this Stream stream)
{
if (stream is MemoryStream)
return ((MemoryStream)stream).ToArray();
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
public static MemoryStream ToMemStream(this Stream stream)
{
var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream;
}
public static byte[] Join(this List<byte[]> arrays, int lengthPerArray = -1)
{
if (arrays.Count == 1) { return arrays[0]; }
var output = lengthPerArray == -1 ? new byte[arrays.Sum(arr => arr.Length)] : new byte[arrays.Count * lengthPerArray];
int writeIdx = 0;
foreach (var byteArr in arrays)
{
byteArr.CopyTo(output, writeIdx);
writeIdx += byteArr.Length;
}
return output;
}
public static bool IsScript(this Type type, Type scriptType)
{
return !type.IsAbstract && type.IsSubclassOf(scriptType);
}
}
/// <summary>
/// Some extension methods provided by RageCoop
/// </summary>
public static class PublicExtensions
{
/// <summary>
/// Get a SHA256 hashed byte array of the input string, internally used to hash password at client side.
/// </summary>
/// <param name="inputString"></param>
/// <returns></returns>
public static byte[] GetSHA256Hash(this string inputString)
{
using (HashAlgorithm algorithm = SHA256.Create())
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
}
/// <summary>
/// Convert a byte array to hex-encoded string, internally used to trigger handshake event
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public static string ToHexString(this byte[] data)
{
return BitConverter.ToString(data).Replace("-", String.Empty);
}
/// <summary>
/// Convert a string to IP address
/// </summary>
/// <param name="ip"></param>
/// <returns></returns>
public static IPAddress ToIP(this string ip)
{
return IPAddress.Parse(ip);
}
}
}

4
Core/FodyWeavers.xml Normal file
View File

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

141
Core/FodyWeavers.xsd Normal file
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>

176
Core/Logger.cs Normal file
View File

@ -0,0 +1,176 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace RageCoop.Core
{
public enum LogLevel
{
Trace = 0,
Debug = 1,
Info = 2,
Warning = 3,
Error = 4
}
/// <summary>
///
/// </summary>
public class Logger : MarshalByRefObject, IDisposable
{
public class LogLine
{
internal LogLine() { }
public DateTime TimeStamp;
public LogLevel LogLevel;
public string Message;
}
/// <summary>
/// 0:Trace, 1:Debug, 2:Info, 3:Warning, 4:Error
/// </summary>
public int LogLevel = 0;
/// <summary>
/// Name of this logger
/// </summary>
public string Name = "Logger";
public readonly string DateTimeFormat = "HH:mm:ss";
/// <summary>
/// Whether to use UTC time for timestamping the log
/// </summary>
public readonly bool UseUtc = false;
public List<StreamWriter> Writers = new List<StreamWriter> { new StreamWriter(Console.OpenStandardOutput()) };
public int FlushInterval = 1000;
public event FlushDelegate OnFlush;
public bool FlushImmediately = false;
public delegate void FlushDelegate(LogLine line, string fomatted);
private readonly Thread LoggerThread;
private bool Stopping = false;
private readonly ConcurrentQueue<LogLine> _queuedLines = new ConcurrentQueue<LogLine>();
internal Logger()
{
Name = Process.GetCurrentProcess().Id.ToString();
if (!FlushImmediately)
{
LoggerThread = new Thread(() =>
{
while (!Stopping)
{
Flush();
Thread.Sleep(1000);
}
Flush();
});
LoggerThread.Start();
}
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Info(string message)
{
Enqueue(2, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Warning(string message)
{
Enqueue(3, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Error(string message)
{
Enqueue(4, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="error"></param>
public void Error(string message, Exception error)
{
Enqueue(4, $"{message}:\n {error}");
}
/// <summary>
///
/// </summary>
/// <param name="ex"></param>
public void Error(Exception ex)
{
Enqueue(4, ex.ToString());
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Debug(string message)
{
Enqueue(1, message);
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public void Trace(string message)
{
Enqueue(0, message);
}
public void Enqueue(int level, string message)
{
if (level < LogLevel) { return; }
_queuedLines.Enqueue(new LogLine()
{
Message = message,
TimeStamp = UseUtc ? DateTime.UtcNow : DateTime.Now,
LogLevel = (LogLevel)level
});
if (FlushImmediately)
{
Flush();
}
}
private string Format(LogLine line)
{
return string.Format("[{0}][{2}] [{3}] {1}", line.TimeStamp.ToString(DateTimeFormat), line.Message, Name, line.LogLevel.ToString());
}
/// <summary>
///
/// </summary>
public void Flush()
{
lock (_queuedLines)
{
try
{
while (_queuedLines.TryDequeue(out var line))
{
var formatted = Format(line);
Writers.ForEach(x => { x.WriteLine(formatted); x.Flush(); });
OnFlush?.Invoke(line, formatted);
}
}
catch { }
}
}
/// <summary>
/// Stop backdround thread and flush all pending messages.
/// </summary>
public void Dispose()
{
Stopping = true;
LoggerThread?.Join();
}
}
}

View File

@ -1,13 +1,27 @@
using System;
using GTA.Math;
using GTA.Math;
using System;
namespace RageCoop.Core
{
internal static class MathExtensions
{
public const float Deg2Rad=(float)(Math.PI* 2) / 360;
public const float Deg2Rad = (float)(Math.PI * 2) / 360;
public const float Rad2Deg = 360 / (float)(Math.PI * 2);
public static Vector3 ToDirection(this Vector3 rotation)
{
double z = DegToRad(rotation.Z);
double x = DegToRad(rotation.X);
double num = Math.Abs(Math.Cos(x));
return new Vector3
{
X = (float)(-Math.Sin(z) * num),
Y = (float)(Math.Cos(z) * num),
Z = (float)Math.Sin(x)
};
}
/// <summary>
///
/// </summary>
@ -67,14 +81,14 @@ namespace RageCoop.Core
vect = vect.ToRadians();
float rollOver2 = vect.Z * 0.5f;
float sinRollOver2 = (float)Math.Sin((double)rollOver2);
float cosRollOver2 = (float)Math.Cos((double)rollOver2);
float sinRollOver2 = (float)Math.Sin(rollOver2);
float cosRollOver2 = (float)Math.Cos(rollOver2);
float pitchOver2 = vect.Y * 0.5f;
float sinPitchOver2 = (float)Math.Sin((double)pitchOver2);
float cosPitchOver2 = (float)Math.Cos((double)pitchOver2);
float sinPitchOver2 = (float)Math.Sin(pitchOver2);
float cosPitchOver2 = (float)Math.Cos(pitchOver2);
float yawOver2 = vect.X * 0.5f; // pitch
float sinYawOver2 = (float)Math.Sin((double)yawOver2);
float cosYawOver2 = (float)Math.Cos((double)yawOver2);
float sinYawOver2 = (float)Math.Sin(yawOver2);
float cosYawOver2 = (float)Math.Cos(yawOver2);
Quaternion result = new Quaternion()
{
X = cosYawOver2 * cosPitchOver2 * cosRollOver2 + sinYawOver2 * sinPitchOver2 * sinRollOver2,
@ -96,7 +110,11 @@ namespace RageCoop.Core
}
public static Vector3 ToDegree(this Vector3 radian)
{
return radian*(float)(180/Math.PI);
return radian * (float)(180 / Math.PI);
}
public static Vector3 ToEulerDegrees(this Quaternion q)
{
return q.ToEulerAngles().ToDegree();
}
public static Vector3 ToEulerAngles(this Quaternion q)
{
@ -127,16 +145,12 @@ namespace RageCoop.Core
}
private static float CopySign(double x, double y)
{
bool isPositive = y>=0;
if (isPositive)
if (y >= 0)
{
if (x>=0) { return (float)x; } else { return (float)-x; }
return x >= 0 ? (float)x : (float)-x;
}
else
{
if (x>=0) { return (float)-x; } else { return (float)x; }
}
return x >= 0 ? (float)-x : (float)x;
}
public static double AngelTo(this Vector3 v1, Vector3 v2)
{
@ -144,8 +158,7 @@ namespace RageCoop.Core
}
public static float GetCosTheta(this Vector3 v1, Vector3 v2)
{
return Vector3.Dot(v1, v2)/(v1.Length()*v2.Length());
return Vector3.Dot(v1, v2) / (v1.Length() * v2.Length());
}
}

View File

@ -0,0 +1,60 @@
using Lidgren.Network;
using System;
using System.Collections.Generic;
using System.Threading;
namespace RageCoop.Core
{
internal class CoopPeer : NetPeer, IDisposable
{
public EventHandler<NetIncomingMessage> OnMessageReceived;
private readonly Thread ListenerThread;
private bool _stopping = false;
public CoopPeer(NetPeerConfiguration config) : base(config)
{
Start();
NetIncomingMessage msg;
ListenerThread = new Thread(() =>
{
while (!_stopping)
{
msg = WaitMessage(200);
if (msg != null)
{
OnMessageReceived?.Invoke(this, msg);
}
}
});
ListenerThread.Start();
}
/// <summary>
/// Terminate all connections and background thread
/// </summary>
public void Dispose()
{
_stopping = true;
Shutdown("Bye!");
ListenerThread.Join();
}
public void SendTo(Packet p, NetConnection connection, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = CreateMessage();
p.Pack(outgoingMessage);
SendMessage(outgoingMessage, connection, method, (int)channel);
}
public void SendTo(Packet p, IList<NetConnection> connections, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = CreateMessage();
p.Pack(outgoingMessage);
SendMessage(outgoingMessage, connections, method, (int)channel);
}
public void Send(Packet p, IList<NetConnection> cons, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = CreateMessage();
p.Pack(outgoingMessage);
SendMessage(outgoingMessage, cons, method, (int)channel);
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.IO;
using System.Net;
using System.Threading;
namespace RageCoop.Core
{
internal static class HttpHelper
{
public static void DownloadFile(string url, string destination, Action<int> progressCallback)
{
if (File.Exists(destination)) { File.Delete(destination); }
AutoResetEvent ae = new AutoResetEvent(false);
WebClient client = new WebClient();
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
client.DownloadProgressChanged += (s, e1) => progressCallback?.Invoke(e1.ProgressPercentage);
client.DownloadFileCompleted += (s, e2) =>
{
ae.Set();
};
client.DownloadFileAsync(new Uri(url), destination);
ae.WaitOne();
}
public static string DownloadString(string url)
{
// TLS only
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
WebClient client = new WebClient();
return client.DownloadString(url);
}
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace RageCoop.Core
{
internal class PublicKey
{
public PublicKey()
{
}
public static PublicKey FromServerInfo(ServerInfo info)
{
return new PublicKey
{
Modulus = Convert.FromBase64String(info.publicKeyModulus),
Exponent = Convert.FromBase64String(info.publicKeyExponent)
};
}
public byte[] Modulus;
public byte[] Exponent;
}
}

View File

@ -0,0 +1,35 @@
using System;
namespace RageCoop.Core
{
/// <summary>
/// A json object representing a server's information as annouced to master server.
/// </summary>
[Serializable]
public class ServerInfo
{
#pragma warning disable 1591
public string address { get; set; }
public string port { get; set; }
public string name { get; set; }
public string version { get; set; }
public string players { get; set; }
public string maxPlayers { get; set; }
public string country { get; set; }
public string description { get; set; }
public string website { get; set; }
public string gameMode { get; set; }
public string language { get; set; }
public bool useP2P { get; set; }
public bool useZT { get; set; }
public string ztID { get; set; }
public string ztAddress { get; set; }
public string publicKeyModulus { get; set; }
public string publicKeyExponent { get; set; }
}
}

View File

@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace RageCoop.Core
{
internal class ZeroTierNetwork
{
public ZeroTierNetwork(string line)
{
// <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>
var v = Regex.Split(line, " ").Skip(2).ToArray();
ID = v[0];
Name = v[1];
Mac = v[2];
Status = v[3];
Type = v[4];
Device = v[5];
foreach (var i in v[6].Split(','))
{
Addresses.Add(i.Split('/')[0]);
}
}
public string ID;
public string Name;
public string Mac;
public string Status;
public string Type;
public string Device;
public List<string> Addresses = new List<string>();
}
internal static class ZeroTierHelper
{
private static readonly string _path = "zerotier-cli";
private static readonly string _arg = "";
static ZeroTierHelper()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var batpath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "ZeroTier", "One", "zerotier-cli.bat");
_arg = $"/c \"{batpath}\" ";
_path = "cmd.exe";
}
var status = RunCommand("status");
if (!status.StartsWith("200"))
{
throw new Exception("ZeroTier not ready: " + status);
}
}
public static ZeroTierNetwork Join(string networkId, int timeout = 10000)
{
var p = Run("join " + networkId);
var o = p.StandardOutput.ReadToEnd();
if (!o.StartsWith("200 join OK"))
{
throw new Exception(o + p.StandardError.ReadToEnd());
}
if (timeout == 0) { return Networks[networkId]; }
int i = 0;
while (i <= timeout)
{
i += 100;
if (Networks.TryGetValue(networkId, out var n))
{
if (n.Addresses.Count != 0 && (!n.Addresses.Where(x => x == "-").Any()))
{
return n;
}
System.Threading.Thread.Sleep(100);
}
else
{
break;
}
}
return null;
}
public static void Leave(string networkId)
{
var p = Run("leave " + networkId);
var o = p.StandardOutput.ReadToEnd();
if (!o.StartsWith("200 leave OK"))
{
throw new Exception(o + p.StandardError.ReadToEnd());
}
}
public static Dictionary<string, ZeroTierNetwork> Networks
{
get
{
Dictionary<string, ZeroTierNetwork> networks = new Dictionary<string, ZeroTierNetwork>();
var p = Run("listnetworks");
var lines = Regex.Split(p.StandardOutput.ReadToEnd(), "\n").Skip(1);
foreach (var line in lines)
{
var l = line.Replace("\r", "");
if (!string.IsNullOrWhiteSpace(l))
{
var n = new ZeroTierNetwork(l);
networks.Add(n.ID, n);
}
}
return networks;
}
}
private static Process Run(string args)
{
var p = new Process();
p.StartInfo = new ProcessStartInfo()
{
FileName = _path,
Arguments = _arg + args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
};
p.Start();
p.WaitForExit();
return p;
}
private static string RunCommand(string command)
{
var p = Run(command);
return p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd();
}
public static void Check()
{
}
}
}

68
Core/PacketExtensions.cs Normal file
View File

@ -0,0 +1,68 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal static class PacketExtensions
{
#region MESSAGE-READ
public static Vector3 ReadVector3(this NetIncomingMessage m)
{
return new Vector3
{
X = m.ReadFloat(),
Y = m.ReadFloat(),
Z = m.ReadFloat(),
};
}
public static Vector2 ReadVector2(this NetIncomingMessage m)
{
return new Vector2
{
X = m.ReadFloat(),
Y = m.ReadFloat(),
};
}
public static Quaternion ReadQuaternion(this NetIncomingMessage m)
{
return new Quaternion
{
X = m.ReadFloat(),
Y = m.ReadFloat(),
Z = m.ReadFloat(),
W = m.ReadFloat(),
};
}
public static byte[] ReadByteArray(this NetIncomingMessage m)
{
return m.ReadBytes(m.ReadInt32());
}
#endregion
#region MESSAGE-WRITE
public static void Write(this NetOutgoingMessage m, Vector3 v)
{
m.Write(v.X);
m.Write(v.Y);
m.Write(v.Z);
}
public static void Write(this NetOutgoingMessage m, Quaternion q)
{
m.Write(q.X);
m.Write(q.Y);
m.Write(q.Z);
m.Write(q.W);
}
public static void WriteByteArray(this NetOutgoingMessage m, byte[] b)
{
m.Write(b.Length);
m.Write(b);
}
#endregion
internal static bool IsSyncEvent(this PacketType p)
{
return (30 <= (byte)p) && ((byte)p <= 40);
}
}
}

View File

@ -0,0 +1,56 @@
using Lidgren.Network;
using System;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class ChatMessage : Packet
{
public override PacketType Type => PacketType.ChatMessage;
private readonly Func<string, byte[]> crypt;
private readonly Func<byte[], byte[]> decrypt;
public ChatMessage(Func<string, byte[]> crypter)
{
crypt = crypter;
}
public ChatMessage(Func<byte[], byte[]> decrypter)
{
decrypt = decrypter;
}
public string Username { get; set; }
public string Message { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write Username
m.Write(Username);
// Write Message
m.WriteByteArray(crypt(Message));
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read username
Username = m.ReadString();
Message = decrypt(m.ReadByteArray()).GetString();
#endregion
}
}
}
}

View File

@ -0,0 +1,88 @@
using Lidgren.Network;
using RageCoop.Core.Scripting;
using System;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class CustomEvent : Packet
{
public static Func<byte, NetIncomingMessage, object> ResolveHandle = null;
public CustomEventFlags Flags;
public override PacketType Type => PacketType.CustomEvent;
public CustomEvent(CustomEventFlags flags = CustomEventFlags.None)
{
Flags = flags;
}
public int Hash { get; set; }
public object[] Args { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
Args = Args ?? new object[] { };
m.Write((byte)Flags);
m.Write(Hash);
m.Write(Args.Length);
foreach (var arg in Args)
{
CoreUtils.GetBytesFromObject(arg, m);
}
}
public override void Deserialize(NetIncomingMessage m)
{
Flags = (CustomEventFlags)m.ReadByte();
Hash = m.ReadInt32();
Args = new object[m.ReadInt32()];
for (int i = 0; i < Args.Length; i++)
{
byte type = m.ReadByte();
switch (type)
{
case 0x01:
Args[i] = m.ReadByte(); break;
case 0x02:
Args[i] = m.ReadInt32(); break;
case 0x03:
Args[i] = m.ReadUInt16(); break;
case 0x04:
Args[i] = m.ReadInt32(); break;
case 0x05:
Args[i] = m.ReadUInt32(); break;
case 0x06:
Args[i] = m.ReadInt64(); break;
case 0x07:
Args[i] = m.ReadUInt64(); break;
case 0x08:
Args[i] = m.ReadFloat(); break;
case 0x09:
Args[i] = m.ReadBoolean(); break;
case 0x10:
Args[i] = m.ReadString(); break;
case 0x11:
Args[i] = m.ReadVector3(); break;
case 0x12:
Args[i] = m.ReadQuaternion(); break;
case 0x13:
Args[i] = (GTA.Model)m.ReadInt32(); break;
case 0x14:
Args[i] = m.ReadVector2(); break;
case 0x15:
Args[i] = m.ReadByteArray(); break;
default:
if (ResolveHandle == null)
{
throw new InvalidOperationException($"Unexpected type: {type}");
}
else
{
Args[i] = ResolveHandle(type, m); break;
}
}
}
}
}
}
}

127
Core/Packets/FilePackets.cs Normal file
View File

@ -0,0 +1,127 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal enum FileResponse : byte
{
NeedToDownload = 0,
AlreadyExists = 1,
Completed = 2,
Loaded = 3,
LoadFailed = 4,
}
internal partial class Packets
{
internal class FileTransferRequest : Packet
{
public override PacketType Type => PacketType.FileTransferRequest;
public int ID { get; set; }
public string Name { get; set; }
public long FileLength { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID from the download
m.Write(ID);
// The name of the file
m.Write(Name);
// The length of the file
m.Write(FileLength);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
Name = m.ReadString();
FileLength = m.ReadInt64();
}
}
internal class FileTransferResponse : Packet
{
public override PacketType Type => PacketType.FileTransferResponse;
public int ID { get; set; }
public FileResponse Response { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID from the download
m.Write(ID);
m.Write((byte)Response);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
Response = (FileResponse)m.ReadByte();
}
}
internal class FileTransferChunk : Packet
{
public override PacketType Type => PacketType.FileTransferChunk;
public int ID { get; set; }
public byte[] FileChunk { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID from the download
m.Write(ID);
m.WriteByteArray(FileChunk);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
FileChunk = m.ReadByteArray();
}
}
internal class FileTransferComplete : Packet
{
public override PacketType Type => PacketType.FileTransferComplete;
public int ID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// The ID for the download
m.Write(ID);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
}
}
internal class AllResourcesSent : Packet
{
public override PacketType Type => PacketType.AllResourcesSent;
}
}
}

67
Core/Packets/HolePunch.cs Normal file
View File

@ -0,0 +1,67 @@
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class HolePunchInit : Packet
{
public override PacketType Type => PacketType.HolePunchInit;
public int TargetID { get; set; }
public string TargetInternal { get; set; }
public string TargetExternal { get; set; }
public bool Connect { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(TargetID);
m.Write(TargetInternal);
m.Write(TargetExternal);
m.Write(Connect);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
TargetID = m.ReadInt32();
TargetInternal = m.ReadString();
TargetExternal = m.ReadString();
Connect = m.ReadBoolean();
#endregion
}
}
internal class HolePunch : Packet
{
public override PacketType Type => PacketType.HolePunch;
public int Puncher { get; set; }
/// <summary>
/// 1:initial, 2:acknowledged, 3:confirmed
/// </summary>
public byte Status { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(Puncher);
m.Write(Status);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
Puncher = m.ReadInt32();
Status = m.ReadByte();
#endregion
}
}
}
}

48
Core/Packets/Misc.cs Normal file
View File

@ -0,0 +1,48 @@
using Lidgren.Network;
using System.Collections.Generic;
namespace RageCoop.Core
{
internal partial class Packets
{
/// <summary>
/// Request direct connection to another client
/// </summary>
internal class ConnectionRequest : Packet
{
public int TargetID { get; set; }
public override PacketType Type => PacketType.ConnectionRequest;
protected override void Serialize(NetOutgoingMessage m)
{
var data = new List<byte>(10);
m.Write(TargetID);
}
public override void Deserialize(NetIncomingMessage m)
{
TargetID = m.ReadInt32();
}
}
/// <summary>
/// Sent to the host when a direct connection has been established
/// </summary>
internal class P2PConnect : Packet
{
public int ID { get; set; }
public override PacketType Type => PacketType.P2PConnect;
protected override void Serialize(NetOutgoingMessage m)
{
var data = new List<byte>(10);
m.Write(ID);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
}
}
}
}

158
Core/Packets/Packets.cs Normal file
View File

@ -0,0 +1,158 @@
using Lidgren.Network;
using System;
namespace RageCoop.Core
{
internal enum PacketType : byte
{
Handshake = 0,
PlayerConnect = 1,
PlayerDisconnect = 2,
PlayerInfoUpdate = 3,
PublicKeyRequest = 4,
PublicKeyResponse = 5,
Request = 6,
Response = 7,
PingPong = 8,
HandshakeSuccess = 9,
ChatMessage = 10,
FileTransferChunk = 11,
FileTransferRequest = 12,
FileTransferResponse = 13,
FileTransferComplete = 14,
AllResourcesSent = 15,
CustomEvent = 16,
ConnectionRequest = 18,
P2PConnect = 19,
HolePunchInit = 20,
HolePunch = 21,
Voice = 22,
#region Sync
PedSync = 23,
VehicleSync = 24,
ProjectileSync = 25,
#endregion
#region EVENT
PedKilled = 30,
BulletShot = 31,
VehicleBulletShot = 32,
OwnerChanged = 35,
NozzleTransform = 37,
#endregion
Unknown = 255
}
internal enum ConnectionChannel
{
Default = 0,
Chat = 1,
Voice = 2,
Native = 3,
Mod = 4,
File = 5,
Event = 6,
RequestResponse = 7,
PingPong = 8,
VehicleSync = 9,
PedSync = 10,
ProjectileSync = 11,
SyncEvents = 12,
}
[Flags]
internal enum PedDataFlags : ushort
{
None = 0,
IsAiming = 1 << 0,
IsInStealthMode = 1 << 1,
IsReloading = 1 << 2,
IsJumping = 1 << 3,
IsRagdoll = 1 << 4,
IsOnFire = 1 << 5,
IsInParachuteFreeFall = 1 << 6,
IsParachuteOpen = 1 << 7,
IsOnLadder = 1 << 8,
IsVaulting = 1 << 9,
IsInCover = 1 << 10,
IsInLowCover = 1 << 11,
IsInCoverFacingLeft = 1 << 12,
IsBlindFiring = 1 << 13,
IsInvincible = 1 << 14,
IsFullSync = 1 << 15,
}
internal enum ProjectileDataFlags : byte
{
None = 0,
Exploded = 1 << 0,
IsAttached = 1 << 1,
IsOrgin = 1 << 2,
IsShotByVehicle = 1 << 3,
}
#region ===== VEHICLE DATA =====
internal enum VehicleDataFlags : ushort
{
None = 0,
IsEngineRunning = 1 << 0,
AreLightsOn = 1 << 1,
AreBrakeLightsOn = 1 << 2,
AreHighBeamsOn = 1 << 3,
IsSirenActive = 1 << 4,
IsDead = 1 << 5,
IsHornActive = 1 << 6,
IsTransformed = 1 << 7,
IsParachuteActive = 1 << 8,
IsRocketBoostActive = 1 << 9,
IsAircraft = 1 << 10,
IsDeluxoHovering = 1 << 11,
HasRoof = 1 << 12,
IsFullSync = 1 << 13,
IsOnFire = 1 << 14,
Repaired = 1 << 15,
}
internal enum PlayerConfigFlags : byte
{
None = 0,
ShowBlip = 1 << 0,
ShowNameTag = 1 << 1
}
internal struct VehicleDamageModel
{
public byte BrokenDoors { get; set; }
public byte OpenedDoors { get; set; }
public byte BrokenWindows { get; set; }
public short BurstedTires { get; set; }
public byte LeftHeadLightBroken { get; set; }
public byte RightHeadLightBroken { get; set; }
}
#endregion
internal interface IPacket
{
PacketType Type { get; }
void Deserialize(NetIncomingMessage m);
}
internal abstract class Packet : IPacket
{
public abstract PacketType Type { get; }
public void Pack(NetOutgoingMessage m)
{
m.Write((byte)Type);
Serialize(m);
}
protected virtual void Serialize(NetOutgoingMessage m) { }
public virtual void Deserialize(NetIncomingMessage m) { }
}
}

213
Core/Packets/PedSync.cs Normal file
View File

@ -0,0 +1,213 @@
using GTA;
using GTA.Math;
using Lidgren.Network;
using System.Collections.Generic;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class PedSync : Packet
{
public override PacketType Type => PacketType.PedSync;
public int ID { get; set; }
public int OwnerID { get; set; }
public int VehicleID { get; set; }
public VehicleSeat Seat { get; set; }
public PedDataFlags Flags { get; set; }
public int Health { get; set; }
public Vector3 Position { get; set; }
public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
#region RAGDOLL
public Vector3 HeadPosition { get; set; }
public Vector3 RightFootPosition { get; set; }
public Vector3 LeftFootPosition { get; set; }
#endregion
public byte Speed { get; set; }
public Vector3 AimCoords { get; set; }
public float Heading { get; set; }
#region FULL
public int ModelHash { get; set; }
public uint CurrentWeaponHash { get; set; }
public byte[] Clothes { get; set; }
public Dictionary<uint, bool> WeaponComponents { get; set; }
public byte WeaponTint { get; set; }
public BlipColor BlipColor { get; set; } = (BlipColor)255;
public BlipSprite BlipSprite { get; set; } = 0;
public float BlipScale { get; set; } = 1;
#endregion
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(OwnerID);
m.Write((ushort)Flags);
m.Write(Health);
m.Write(Speed);
if (Flags.HasPedFlag(PedDataFlags.IsRagdoll))
{
m.Write(HeadPosition);
m.Write(RightFootPosition);
m.Write(LeftFootPosition);
}
else
{
if (Speed >= 4)
{
m.Write(VehicleID);
m.Write((byte)(Seat + 3));
}
m.Write(Position);
}
m.Write(Rotation);
m.Write(Velocity);
if (Flags.HasPedFlag(PedDataFlags.IsAiming))
{
m.Write(AimCoords);
}
m.Write(Heading);
if (Flags.HasPedFlag(PedDataFlags.IsFullSync))
{
m.Write(ModelHash);
m.Write(CurrentWeaponHash);
m.Write(Clothes);
if (WeaponComponents != null)
{
m.Write(true);
m.Write((ushort)WeaponComponents.Count);
foreach (KeyValuePair<uint, bool> component in WeaponComponents)
{
m.Write(component.Key);
m.Write(component.Value);
}
}
else
{
// Player weapon doesn't have any components
m.Write(false);
}
m.Write(WeaponTint);
m.Write((byte)BlipColor);
if ((byte)BlipColor != 255)
{
m.Write((ushort)BlipSprite);
m.Write(BlipScale);
}
}
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
ID = m.ReadInt32();
OwnerID = m.ReadInt32();
Flags = (PedDataFlags)m.ReadUInt16();
Health = m.ReadInt32();
Speed = m.ReadByte();
if (Flags.HasPedFlag(PedDataFlags.IsRagdoll))
{
HeadPosition = m.ReadVector3();
RightFootPosition = m.ReadVector3();
LeftFootPosition = m.ReadVector3();
Position = HeadPosition;
}
else
{
// Vehicle related
if (Speed >= 4)
{
VehicleID = m.ReadInt32();
Seat = (VehicleSeat)(m.ReadByte() - 3);
}
// Read player position
Position = m.ReadVector3();
}
Rotation = m.ReadVector3();
Velocity = m.ReadVector3();
if (Flags.HasPedFlag(PedDataFlags.IsAiming))
{
// Read player aim coords
AimCoords = m.ReadVector3();
}
Heading = m.ReadFloat();
if (Flags.HasPedFlag(PedDataFlags.IsFullSync))
{
// Read player model hash
ModelHash = m.ReadInt32();
// Read player weapon hash
CurrentWeaponHash = m.ReadUInt32();
// Read player clothes
Clothes = m.ReadBytes(36);
// Read player weapon components
if (m.ReadBoolean())
{
WeaponComponents = new Dictionary<uint, bool>();
ushort comCount = m.ReadUInt16();
for (ushort i = 0; i < comCount; i++)
{
WeaponComponents.Add(m.ReadUInt32(), m.ReadBoolean());
}
}
WeaponTint = m.ReadByte();
BlipColor = (BlipColor)m.ReadByte();
if ((byte)BlipColor != 255)
{
BlipSprite = (BlipSprite)m.ReadUInt16();
BlipScale = m.ReadFloat();
}
}
#endregion
}
}
}
}

View File

@ -0,0 +1,249 @@
using GTA.Math;
using Lidgren.Network;
using System.Net;
namespace RageCoop.Core
{
internal partial class Packets
{
internal struct PlayerData
{
public int ID;
public string Username;
}
public class Handshake : Packet
{
public override PacketType Type => PacketType.Handshake;
public int PedID { get; set; }
public string Username { get; set; }
public string ModVersion { get; set; }
/// <summary>
/// The asymetrically crypted Aes key
/// </summary>
public byte[] AesKeyCrypted;
/// <summary>
/// The asymetrically crypted Aes IV
/// </summary>
public byte[] AesIVCrypted;
/// <summary>
/// The password hash with client Aes
/// </summary>
public byte[] PasswordEncrypted { get; set; }
public IPEndPoint InternalEndPoint { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write Player Ped ID
m.Write(PedID);
// Write Username
m.Write(Username);
// Write ModVersion
m.Write(ModVersion);
m.Write(InternalEndPoint.ToString());
// Write AesKeyCrypted
m.WriteByteArray(AesKeyCrypted);
// Write AesIVCrypted
m.WriteByteArray(AesIVCrypted);
// Write PassHash
m.WriteByteArray(PasswordEncrypted);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read player netHandle
PedID = m.ReadInt32();
// Read Username
Username = m.ReadString();
// Read ModVersion
ModVersion = m.ReadString();
InternalEndPoint = CoreUtils.StringToEndPoint(m.ReadString());
AesKeyCrypted = m.ReadByteArray();
AesIVCrypted = m.ReadByteArray();
PasswordEncrypted = m.ReadByteArray();
#endregion
}
}
public class HandshakeSuccess : Packet
{
public PlayerData[] Players { get; set; }
public override PacketType Type => PacketType.HandshakeSuccess;
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(Players.Length);
foreach (var p in Players)
{
m.Write(p.ID);
m.Write(p.Username);
}
}
public override void Deserialize(NetIncomingMessage m)
{
Players = new PlayerData[m.ReadInt32()];
for (int i = 0; i < Players.Length; i++)
{
Players[i] = new PlayerData()
{
ID = m.ReadInt32(),
Username = m.ReadString(),
};
}
}
}
public class PlayerConnect : Packet
{
public override PacketType Type => PacketType.PlayerConnect;
public int PedID { get; set; }
public string Username { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write NetHandle
m.Write(PedID);
m.Write(Username);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read player netHandle
PedID = m.ReadInt32();
// Read Username
Username = m.ReadString();
#endregion
}
}
public class PlayerDisconnect : Packet
{
public override PacketType Type => PacketType.PlayerDisconnect;
public int PedID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(PedID);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
PedID = m.ReadInt32();
#endregion
}
}
public class PlayerInfoUpdate : Packet
{
public override PacketType Type => PacketType.PlayerInfoUpdate;
/// <summary>
/// Ped ID for this Player
/// </summary>
public int PedID { get; set; }
public string Username { get; set; }
public float Latency { get; set; }
public Vector3 Position { get; set; }
public bool IsHost;
protected override void Serialize(NetOutgoingMessage m)
{
// Write ID
m.Write(PedID);
// Write Username
m.Write(Username);
// Write Latency
m.Write(Latency);
m.Write(Position);
m.Write(IsHost);
}
public override void Deserialize(NetIncomingMessage m)
{
// Read player ID
PedID = m.ReadInt32();
// Read Username
Username = m.ReadString();
Latency = m.ReadFloat();
Position = m.ReadVector3();
IsHost = m.ReadBoolean();
}
}
public class PublicKeyResponse : Packet
{
public override PacketType Type => PacketType.PublicKeyResponse;
public byte[] Modulus;
public byte[] Exponent;
protected override void Serialize(NetOutgoingMessage m)
{
m.WriteByteArray(Modulus);
m.WriteByteArray(Exponent);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
Modulus = m.ReadByteArray();
Exponent = m.ReadByteArray();
#endregion
}
}
public class PublicKeyRequest : Packet
{
public override PacketType Type => PacketType.PublicKeyRequest;
}
}
}

View File

@ -0,0 +1,82 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class ProjectileSync : Packet
{
public override PacketType Type => PacketType.ProjectileSync;
public int ID { get; set; }
public int ShooterID { get; set; }
public uint WeaponHash { get; set; }
public Vector3 Position { get; set; }
public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public ProjectileDataFlags Flags { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write id
m.Write(ID);
// Write ShooterID
m.Write(ShooterID);
m.Write(WeaponHash);
// Write position
m.Write(Position);
// Write rotation
m.Write(Rotation);
// Write velocity
m.Write(Velocity);
m.Write((byte)Flags);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read id
ID = m.ReadInt32();
// Read ShooterID
ShooterID = m.ReadInt32();
WeaponHash = m.ReadUInt32();
// Read position
Position = m.ReadVector3();
// Read rotation
Rotation = m.ReadVector3();
// Read velocity
Velocity = m.ReadVector3();
Flags = (ProjectileDataFlags)m.ReadByte();
#endregion
}
}
}
}

View File

@ -0,0 +1,65 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class BulletShot : Packet
{
public override PacketType Type => PacketType.BulletShot;
public int OwnerID { get; set; }
public uint WeaponHash { get; set; }
public Vector3 StartPosition { get; set; }
public Vector3 EndPosition { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
// Write OwnerID
m.Write(OwnerID);
// Write weapon hash
m.Write(WeaponHash);
// Write StartPosition
m.Write(StartPosition);
// Write EndPosition
m.Write(EndPosition);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
// Read OwnerID
OwnerID = m.ReadInt32();
// Read WeponHash
WeaponHash = m.ReadUInt32();
// Read StartPosition
StartPosition = m.ReadVector3();
// Read EndPosition
EndPosition = m.ReadVector3();
#endregion
}
}
}
}

View File

@ -0,0 +1,42 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class NozzleTransform : Packet
{
public override PacketType Type => PacketType.NozzleTransform;
public int VehicleID { get; set; }
public bool Hover { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(VehicleID);
m.Write(Hover);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
VehicleID = m.ReadInt32();
Hover = m.ReadBoolean();
#endregion
}
}
}
}

View File

@ -0,0 +1,38 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class OwnerChanged : Packet
{
public override PacketType Type => PacketType.OwnerChanged;
public int ID { get; set; }
public int NewOwnerID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(NewOwnerID);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
ID = m.ReadInt32();
NewOwnerID = m.ReadInt32();
#endregion
}
}
}
}

View File

@ -0,0 +1,39 @@

using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class PedKilled : Packet
{
public override PacketType Type => PacketType.PedKilled;
public int VictimID { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(VictimID);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
VictimID = m.ReadInt32();
#endregion
}
}
}
}

View File

@ -0,0 +1,52 @@
using GTA.Math;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class VehicleBulletShot : Packet
{
public override PacketType Type => PacketType.VehicleBulletShot;
public int OwnerID { get; set; }
public ushort Bone { get; set; }
public uint WeaponHash { get; set; }
public Vector3 StartPosition { get; set; }
public Vector3 EndPosition { get; set; }
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(OwnerID);
m.Write(Bone);
m.Write(WeaponHash);
m.Write(StartPosition);
m.Write(EndPosition);
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
OwnerID = m.ReadInt32();
Bone = m.ReadUInt16();
WeaponHash = m.ReadUInt32();
StartPosition = m.ReadVector3();
EndPosition = m.ReadVector3();
#endregion
}
}
}
}

227
Core/Packets/VehicleSync.cs Normal file
View File

@ -0,0 +1,227 @@
using GTA;
using GTA.Math;
using Lidgren.Network;
using System.Collections.Generic;
namespace RageCoop.Core
{
internal partial class Packets
{
public class VehicleSync : Packet
{
public override PacketType Type => PacketType.VehicleSync;
public int ID { get; set; }
public int OwnerID { get; set; }
public VehicleDataFlags Flags { get; set; }
public Vector3 Position { get; set; }
public Quaternion Quaternion { get; set; }
// public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public Vector3 RotationVelocity { get; set; }
public float ThrottlePower { get; set; }
public float BrakePower { get; set; }
public float SteeringAngle { get; set; }
public float DeluxoWingRatio { get; set; } = -1;
#region FULL-SYNC
public int ModelHash { get; set; }
public float EngineHealth { get; set; }
public byte[] Colors { get; set; }
public Dictionary<int, int> Mods { get; set; }
public VehicleDamageModel DamageModel { get; set; }
public byte LandingGear { get; set; }
public byte RoofState { get; set; }
public VehicleLockStatus LockStatus { get; set; }
public int Livery { get; set; } = -1;
public byte RadioStation { get; set; } = 255;
public string LicensePlate { get; set; }
#endregion
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(OwnerID);
m.Write((ushort)Flags);
m.Write(Position);
m.Write(Quaternion);
m.Write(Velocity);
m.Write(RotationVelocity);
m.Write(ThrottlePower);
m.Write(BrakePower);
m.Write(SteeringAngle);
if (Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
{
m.Write(DeluxoWingRatio);
}
if (Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
m.Write(ModelHash);
m.Write(EngineHealth);
// Check
if (Flags.HasVehFlag(VehicleDataFlags.IsAircraft))
{
// Write the vehicle landing gear
m.Write(LandingGear);
}
if (Flags.HasVehFlag(VehicleDataFlags.HasRoof))
{
m.Write(RoofState);
}
// Write vehicle colors
m.Write(Colors[0]);
m.Write(Colors[1]);
// Write vehicle mods
// Write the count of mods
m.Write((short)Mods.Count);
// Loop the dictionary and add the values
foreach (KeyValuePair<int, int> mod in Mods)
{
// Write the mod value
m.Write(mod.Key);
m.Write(mod.Value);
}
if (!DamageModel.Equals(default(VehicleDamageModel)))
{
// Write boolean = true
m.Write(true);
// Write vehicle damage model
m.Write(DamageModel.BrokenDoors);
m.Write(DamageModel.OpenedDoors);
m.Write(DamageModel.BrokenWindows);
m.Write(DamageModel.BurstedTires);
m.Write(DamageModel.LeftHeadLightBroken);
m.Write(DamageModel.RightHeadLightBroken);
}
else
{
// Write boolean = false
m.Write(false);
}
// Write LockStatus
m.Write((byte)LockStatus);
// Write RadioStation
m.Write(RadioStation);
// Write LicensePlate
m.Write(LicensePlate);
m.Write((byte)(Livery + 1));
}
}
public override void Deserialize(NetIncomingMessage m)
{
#region NetIncomingMessageToPacket
ID = m.ReadInt32();
OwnerID = m.ReadInt32();
Flags = (VehicleDataFlags)m.ReadUInt16();
Position = m.ReadVector3();
Quaternion = m.ReadQuaternion();
Velocity = m.ReadVector3();
RotationVelocity = m.ReadVector3();
ThrottlePower = m.ReadFloat();
BrakePower = m.ReadFloat();
SteeringAngle = m.ReadFloat();
if (Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering))
{
DeluxoWingRatio = m.ReadFloat();
}
if (Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
// Read vehicle model hash
ModelHash = m.ReadInt32();
// Read vehicle engine health
EngineHealth = m.ReadFloat();
// Check
if (Flags.HasVehFlag(VehicleDataFlags.IsAircraft))
{
// Read vehicle landing gear
LandingGear = m.ReadByte();
}
if (Flags.HasVehFlag(VehicleDataFlags.HasRoof))
{
RoofState = m.ReadByte();
}
// Read vehicle colors
byte vehColor1 = m.ReadByte();
byte vehColor2 = m.ReadByte();
Colors = new byte[] { vehColor1, vehColor2 };
// Read vehicle mods
// Create new Dictionary
Mods = new Dictionary<int, int>();
// Read count of mods
short vehModCount = m.ReadInt16();
// Loop
for (int i = 0; i < vehModCount; i++)
{
// Read the mod value
Mods.Add(m.ReadInt32(), m.ReadInt32());
}
if (m.ReadBoolean())
{
// Read vehicle damage model
DamageModel = new VehicleDamageModel()
{
BrokenDoors = m.ReadByte(),
OpenedDoors = m.ReadByte(),
BrokenWindows = m.ReadByte(),
BurstedTires = m.ReadInt16(),
LeftHeadLightBroken = m.ReadByte(),
RightHeadLightBroken = m.ReadByte()
};
}
// Read LockStatus
LockStatus = (VehicleLockStatus)m.ReadByte();
// Read RadioStation
RadioStation = m.ReadByte();
LicensePlate = m.ReadString();
Livery = m.ReadByte() - 1;
}
#endregion
}
}
}
}

29
Core/Packets/Voice.cs Normal file
View File

@ -0,0 +1,29 @@
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class Voice : Packet
{
public int ID { get; set; }
public byte[] Buffer { get; set; }
public int Recorded { get; set; }
public override PacketType Type => PacketType.Voice;
protected override void Serialize(NetOutgoingMessage m)
{
m.Write(ID);
m.Write(Buffer);
m.Write(Recorded);
}
public override void Deserialize(NetIncomingMessage m)
{
ID = m.ReadInt32();
Buffer = m.ReadByteArray();
Recorded = m.ReadInt32();
}
}
}
}

View File

@ -24,14 +24,26 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Lidgren.Network\**" />
<EmbeddedResource Remove="Lidgren.Network\**" />
<None Remove="Lidgren.Network\**" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="Costura.Fody" Version="5.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Fody" Version="6.6.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="SharpZipLib" Version="1.4.0" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
</ItemGroup>
@ -39,9 +51,6 @@
<Reference Include="Lidgren.Network">
<HintPath>..\libs\Lidgren.Network.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="ScriptHookVDotNet3">
<HintPath>..\libs\ScriptHookVDotNet3.dll</HintPath>
</Reference>

View File

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
namespace RageCoop.Core.Scripting
{
/// <summary>
/// Describes how the event should be sent or processed
/// </summary>
public enum CustomEventFlags : byte
{
None = 0,
/// <summary>
/// Data will be encrypted and decrypted on target client
/// </summary>
Encrypted = 1,
/// <summary>
/// Event will be queued and fired in script thread, specify this flag if your handler will call native functions.
/// </summary>
Queued = 2,
}
/// <summary>
/// Struct to identify different event using hash
/// </summary>
public struct CustomEventHash
{
private static readonly MD5 Hasher = MD5.Create();
private static readonly Dictionary<int, string> Hashed = new Dictionary<int, string>();
/// <summary>
/// Hash value
/// </summary>
public int Hash;
/// <summary>
/// Create from hash
/// </summary>
/// <param name="hash"></param>
public static implicit operator CustomEventHash(int hash)
{
return new CustomEventHash() { Hash = hash };
}
/// <summary>
/// Create from string
/// </summary>
/// <param name="name"></param>
public static implicit operator CustomEventHash(string name)
{
return new CustomEventHash() { Hash = FromString(name) };
}
/// <summary>
/// Get a Int32 hash of a string.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">The exception is thrown when the name did not match a previously computed one and the hash was the same.</exception>
public static int FromString(string s)
{
var hash = BitConverter.ToInt32(Hasher.ComputeHash(Encoding.UTF8.GetBytes(s)), 0);
lock (Hashed)
{
if (Hashed.TryGetValue(hash, out string name))
{
if (name != s)
{
throw new ArgumentException($"Hashed value has collision with another name:{name}, hashed value:{hash}");
}
return hash;
}
Hashed.Add(hash, s);
return hash;
}
}
/// <summary>
/// To int
/// </summary>
/// <param name="h"></param>
public static implicit operator int(CustomEventHash h)
{
return h.Hash;
}
}
/// <summary>
///
/// </summary>
public static class CustomEvents
{
internal static readonly CustomEventHash OnPlayerDied = "RageCoop.OnPlayerDied";
internal static readonly CustomEventHash SetWeather = "RageCoop.SetWeather";
internal static readonly CustomEventHash OnPedDeleted = "RageCoop.OnPedDeleted";
internal static readonly CustomEventHash OnVehicleDeleted = "RageCoop.OnVehicleDeleted";
internal static readonly CustomEventHash SetAutoRespawn = "RageCoop.SetAutoRespawn";
internal static readonly CustomEventHash SetDisplayNameTag = "RageCoop.SetDisplayNameTag";
internal static readonly CustomEventHash NativeCall = "RageCoop.NativeCall";
internal static readonly CustomEventHash NativeResponse = "RageCoop.NativeResponse";
internal static readonly CustomEventHash AllResourcesSent = "RageCoop.AllResourcesSent";
internal static readonly CustomEventHash ServerPropSync = "RageCoop.ServerPropSync";
internal static readonly CustomEventHash ServerBlipSync = "RageCoop.ServerBlipSync";
internal static readonly CustomEventHash SetEntity = "RageCoop.SetEntity";
internal static readonly CustomEventHash DeleteServerProp = "RageCoop.DeleteServerProp";
internal static readonly CustomEventHash UpdatePedBlip = "RageCoop.UpdatePedBlip";
internal static readonly CustomEventHash DeleteEntity = "RageCoop.DeleteEntity";
internal static readonly CustomEventHash DeleteServerBlip = "RageCoop.DeleteServerBlip";
internal static readonly CustomEventHash CreateVehicle = "RageCoop.CreateVehicle";
internal static readonly CustomEventHash WeatherTimeSync = "RageCoop.WeatherTimeSync";
internal static readonly CustomEventHash IsHost = "RageCoop.IsHost";
/// <summary>
/// Get event hash from string.
/// </summary>
/// <returns></returns>
/// <remarks>This method is obsoete, you should use implicit operator or <see cref="CustomEventHash.FromString(string)"/></remarks>
[Obsolete]
public static int Hash(string s)
{
return CustomEventHash.FromString(s);
}
}
}

View File

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

86
Core/Worker.cs Normal file
View File

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

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