424 Commits

Author SHA1 Message Date
85706518fe Update to SHVDN nightly (#70) 2024-06-30 16:17:35 +08:00
49208371d1 Allow keyboard layout change in chat 2023-12-12 11:52:20 -03:00
761156a574 Avoid vehicle door break and repair loop 2023-11-27 10:24:35 -03:00
8bd6ea15a3 Remove global limits
world limits will be applied to all players instead
Fix explosions when remote and local vehicles exist at same position
Fix "floating corpses" issue when remote NPCs are killed
2023-11-20 11:32:18 -03:00
3c7f16f7a4 Add ped limit menu setting
Fix vehicle limit menu setting
Fix player blip disappearing after switching character
2023-11-12 10:50:37 -03:00
393a401860 Always sync vehicles with synced peds 2023-11-07 20:27:14 -03:00
4a54851c51 Update AssemblyInfo.cs 2023-10-23 10:36:57 -03:00
f0eefa575c Always sync player ped 2023-10-22 18:59:19 -03:00
3fc813b2d8 Fix bug disconnecting from server 2023-10-22 17:14:50 -03:00
fbff72ff14 Update LemonUI 2023-10-18 16:57:19 -03:00
4e52407591 Update nightly-build.yaml 2023-10-18 16:56:44 -03:00
4e6fde129d Add global limits 2023-10-18 10:41:34 -03:00
92e1a970a8 Show kill notification 2023-10-06 23:46:08 -03:00
d0eb0b7818 Bump version
Add kill message
2023-10-06 16:36:49 -03:00
14151d7b2c Add option to disable blip and nametag display 2023-08-14 14:08:30 -03:00
1f8d70a520 Fix player dying when switching characters in missions 2023-08-13 19:29:48 -03:00
2cb5baf5f7 Fix player dying when switching characters 2023-08-09 23:50:47 -03:00
9287aba0f9 Update README.md 2023-07-26 15:42:08 -03:00
99642fd40c Update SHVDN 2023-07-26 15:12:58 -03:00
2fbf06b504 Use Lidgren.Network release build 2023-07-24 10:40:33 -03:00
13b771ec9f Fix exception entering vehicle as passenger 2023-07-24 10:39:37 -03:00
3b987f59e0 Remove update menu 2023-07-14 09:43:00 -03:00
de96f29097 Don't delete peds in vehicle 2023-07-14 09:39:06 -03:00
6136cbfc14 Allow multiple servers on same address
with different ports
2023-07-14 09:38:02 -03:00
ed145aedd6 Update master server 2023-07-14 09:36:49 -03:00
3f3b5fd2d0 Closes #40 2022-09-30 23:40:14 +08:00
9173e9a99e Fix non-ambient peds get deleted 2022-09-30 23:27:37 +08:00
6e64c458df Merge branch 'dev-nightly' of https://github.com/RAGECOOP/RAGECOOP-V into dev-nightly 2022-09-09 11:24:02 +08:00
76f959abe9 Stuff 2022-09-09 11:23:46 +08:00
f2e85d66ab Update build-test.yaml 2022-09-09 11:15:27 +08:00
e30ef1f4bd Update actions 2022-09-08 20:09:52 -07:00
6e8f6e78f6 Add build test action 2022-09-08 20:05:26 -07:00
f28c83ccbd Merge pull request #38 from RAGECOOP/dev-nightly
Bump to 1.5.4
2022-09-07 22:44:48 -07:00
a83821b3d2 Version bump 2022-09-08 13:19:09 -07:00
3b5436064e Don't announce if already present in master server 2022-09-08 13:15:34 -07:00
c4b321324e Filter out white space for reload key parsing 2022-09-08 12:59:14 -07:00
ba8d525ddf Merge pull request #37 from RAGECOOP/dev-nightly
Rewrite packet system
2022-09-07 21:45:14 -07:00
884e2f39f0 Yet another code cleanup 2022-09-08 12:41:56 -07:00
76c529f1d1 Rewrote packet system to reduce heap allocation 2022-09-08 12:37:06 -07:00
df0064bd38 Update nightly-build.yaml 2022-09-07 21:01:22 -07:00
f1fc96bbd7 Fix weird projectile shooting 2022-09-07 11:08:25 +08:00
23e9326f5f Fix stuff, add listening address display and projectile shoot prediction 2022-09-07 09:52:40 +08:00
4621fb4987 Some code cleanup and formatting 2022-09-06 21:46:35 +08:00
6c82895fa7 code cleaned up 2022-09-05 13:02:09 +02:00
84b040766f Simplify GetBytesFromObject 2022-08-27 14:23:28 +08:00
f44558cd3b Add some client API 2022-08-27 14:17:10 +08:00
accdbbcbc6 blah 2022-08-27 13:53:03 +08:00
2d4107f35e Fix traffic 2022-08-27 12:40:47 +08:00
bac53fd769 Load scripts from main assembly only 2022-08-26 22:58:03 +08:00
8f63fee5b5 Delete temp directory 2022-08-25 22:34:54 +08:00
8d0ad0b600 Show runtime information 2022-08-25 22:32:01 +08:00
dc08c0c1f6 Added support for loading runtime-specific libraries 2022-08-25 21:51:09 +08:00
faaa856aa5 Instructional buttons for popup 2022-08-25 00:58:21 +08:00
eab64f9254 Check fixed data every 100 frames 2022-08-24 22:38:50 +08:00
6c936cb8f9 Show help in popup 2022-08-24 19:26:40 +08:00
83a37f8556 blah 2022-08-24 18:48:21 +08:00
bb4eacce26 Don't disable traffic if not on server 2022-08-24 18:47:59 +08:00
d5b71db5d4 💩 2022-08-23 23:21:35 +08:00
89ebd0305c Fix ped running not displayed 2022-08-23 18:10:52 +08:00
b209202292 Add option to show entity owner 2022-08-23 17:43:24 +08:00
f199241ed8 Time for a release 2022-08-23 16:29:43 +08:00
50229116a7 blah 2022-08-23 14:04:18 +08:00
f192caeae1 Add sdk 2022-08-23 14:03:33 +08:00
6005b2ba11 Add slipstreaming 2022-08-23 13:51:38 +08:00
6e213611cc Fix deluxo wing 2022-08-23 13:45:45 +08:00
347d65af62 Expose chat meesage API to client script 2022-08-23 13:34:12 +08:00
636ee3a33f Fix memory stream close on send 2022-08-23 13:22:14 +08:00
8ff08e0804 Add resource package support 2022-08-23 12:21:17 +08:00
6d7fe58719 Ped sync tweaks 2022-08-23 10:55:54 +08:00
c7e14ef191 Fix disable traffic not always work 2022-08-22 21:59:01 +08:00
39eb5ef80a Hmm... 2022-08-22 19:38:04 +08:00
d25c2539e0 Tweaks 2022-08-22 19:27:25 +08:00
eb31a33104 Fix vibration on stopped vehicle 2022-08-22 19:25:03 +08:00
8b3f8ffd9b Check ped weapon on full sync only 2022-08-22 19:13:52 +08:00
cca09c4178 Fixed ped weapon display after exiting vehicle 2022-08-22 19:08:09 +08:00
871bb7e64e ver 2022-08-22 17:57:43 +08:00
02da3ce8af Fix voice on P2P mode 2022-08-22 17:55:58 +08:00
711ba62fc2 Update installer 2022-08-22 17:30:15 +08:00
8f7d3135db Revert "Fix output path overlap"
This reverts commit 2af9482da9.
2022-08-22 17:27:35 +08:00
cb563a1bf8 Revert "Update nightly-build.yaml"
This reverts commit 6720636d48.
2022-08-22 17:27:29 +08:00
a84ab42a42 blah 2022-08-22 17:01:08 +08:00
b3e285f92f Wait ten minutes before updating 2022-08-22 16:59:00 +08:00
01433f0de6 Restore damage model for aircraft 2022-08-22 11:36:52 +08:00
c034cd8aa9 Fix traffic 2022-08-22 10:01:09 +08:00
363c2ccb00 Don't disable vehicle enter/exit key 2022-08-22 09:40:02 +08:00
7380c162be Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-22 09:34:52 +08:00
f1c7192029 Change kicking options 2022-08-22 09:33:54 +08:00
6720636d48 Update nightly-build.yaml 2022-08-22 00:46:46 +08:00
2af9482da9 Fix output path overlap 2022-08-22 00:36:43 +08:00
bf22c17bba Add AntiAsshole and host display 2022-08-22 00:23:46 +08:00
01492a0c55 Auto server update should work now 2022-08-21 19:56:59 +08:00
c17b234057 Update AssemblyInfo.cs 2022-08-21 19:41:25 +08:00
739577bbf1 Hmm... 2022-08-21 19:30:27 +08:00
2d2da624e4 Add automatic server update (need testing) 2022-08-21 19:13:12 +08:00
9e66762061 Add ZeroTier check to installer 2022-08-21 15:13:36 +08:00
84d578366a Fix installer 2022-08-21 14:28:37 +08:00
03bc3a7742 blah 2022-08-21 14:22:41 +08:00
5bfc7f836e Fix vehicle seat sync 2022-08-21 14:15:01 +08:00
54977c79f1 Fix unintended carjacking 2022-08-21 14:12:44 +08:00
0352bfa328 Add menyoo and directory check for installer 2022-08-21 13:42:28 +08:00
6fbc386b45 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-21 12:37:47 +08:00
203e3b2528 Add Client.EntitiesCount 2022-08-21 12:37:37 +08:00
c9aa995224 Update README.md 2022-08-21 12:17:20 +08:00
c201b94897 Better vehicle rotation display 2022-08-20 22:36:48 +08:00
87f873d1a5 blah 2022-08-20 22:22:06 +08:00
6c355b109d Fix player blip (again) 2022-08-20 22:15:01 +08:00
2ef3012672 Fix installer not overwriting files 2022-08-20 17:27:05 +08:00
7229574ff4 Hmm 2022-08-20 17:17:49 +08:00
eff9fd50b1 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-20 15:31:03 +08:00
e2cbf26360 Fix installer output 2022-08-20 15:30:54 +08:00
b0f448fd5f Fix insyaller output 2022-08-20 15:29:44 +08:00
721c4d3dee Add client installer 2022-08-20 15:20:25 +08:00
87dfc18d91 Smoother ped heading calibration 2022-08-20 12:33:55 +08:00
fa4fe2c10f Fix player blip 2022-08-20 11:32:35 +08:00
3a088ddbfc Fix blip duplication and name issue 2022-08-20 11:08:51 +08:00
52cc9b647c Vehicle duplication prevention 2022-08-19 22:31:13 +08:00
1aa23901d5 Update lidgren again 2022-08-19 22:10:01 +08:00
52584d5774 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-19 21:43:34 +08:00
62bf15dede default LogLevel to 0 2022-08-19 21:43:23 +08:00
722ca16f57 Update README.md 2022-08-19 21:39:40 +08:00
3e61498366 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-19 21:35:08 +08:00
1ff2fa5691 Update lidgren 2022-08-19 21:35:05 +08:00
5088973474 Update README.md 2022-08-19 21:29:24 +08:00
b63378f4d0 Update README.md 2022-08-19 21:28:44 +08:00
f234830c54 Fix stuff 2022-08-18 22:08:06 +08:00
5075289657 ? 2022-08-18 21:48:42 +08:00
c20118aac1 Vehicle cleanup 2022-08-18 20:44:39 +08:00
c4312faf4a blah 2022-08-18 18:59:38 +08:00
7566423889 Security tweaks 2022-08-18 17:45:08 +08:00
be13e0d102 Small projectile fix 2022-08-18 08:43:10 +08:00
490802862a Huh? 2022-08-18 08:26:41 +08:00
e369a50b89 Add vehicle parachute sync 2022-08-18 08:25:15 +08:00
13a6431248 Add rocket boost sync 2022-08-17 23:18:06 +08:00
cb73db7146 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-17 22:56:27 +08:00
9dcd6bb363 Apply prediction on server entity update 2022-08-17 22:50:17 +08:00
842e1435f2 aargh..... 2022-08-17 01:54:14 +08:00
4d03f8e371 blahblahblah 2022-08-16 23:31:17 +08:00
e040d53cc2 blah 2022-08-16 23:18:34 +08:00
22bfa43ec5 Update version 2022-08-16 23:17:04 +08:00
7123bea8e5 Small fix 2022-08-16 22:23:32 +08:00
973f6873e4 Fix some control not being ignored on chat 2022-08-16 22:16:18 +08:00
efb759b9f9 blah 2022-08-16 20:07:25 +08:00
ad3ff96f61 This is it 2022-08-16 19:51:56 +08:00
e156b2195f Display ping (round-trip time) 2022-08-16 13:44:56 +08:00
73a99e033d Update DevTool 2022-08-15 22:43:32 +08:00
b44d722b5f Closes #24 2022-08-15 22:41:17 +08:00
307b6365f7 Closes #25 2022-08-15 22:38:31 +08:00
a49ee19d3d Closes #17 2022-08-15 22:36:43 +08:00
9c5c1da4c9 Add champion weapon, (closes #21) 2022-08-15 22:35:43 +08:00
0348296dc1 Add a bunch of vehicle weapons (closes #23) 2022-08-15 22:34:35 +08:00
adb9439226 Add BRUISER3, BRUTUS3, MONSTER5 weapon sync (closes #26) 2022-08-15 22:21:47 +08:00
ae10da874b Hmm... 2022-08-15 21:25:42 +08:00
cb91486848 Update SHVDN 2022-08-15 20:50:26 +08:00
3289c03fd2 Vehicle seat fix 2022-08-15 20:04:08 +08:00
d4ffb0c929 blah 2022-08-15 19:40:24 +08:00
3c089e2c47 Fix looking coordinate in first person 2022-08-15 19:39:36 +08:00
7a4e4182b5 Add drive-by weapon display
This still have some issues:
ped not aiming consistently
sometimes not working at all
doesn't work for throwable weapons
2022-08-15 18:25:43 +08:00
aa6c45b20d Voice tweaks 2022-08-15 16:13:53 +08:00
0943a7992f blah 2022-08-15 00:19:00 +02:00
d30e1f6bb3 Added mouth animation when speaking 2022-08-14 23:35:36 +02:00
0c71d4dbaa Assign Client.Player.Owner immediately 2022-08-15 00:12:07 +08:00
c78fbd89bc Update README.md 2022-08-14 19:10:45 +08:00
dc83cd5be9 Ped sync improvements 2022-08-14 19:06:51 +08:00
b5f6fc578d Projectile refactor 2022-08-14 17:08:43 +08:00
4acf964f0a Small ragdoll tweak 2022-08-14 13:56:10 +08:00
560f092304 Don't set heading when aiming 2022-08-14 13:21:13 +08:00
8305d9997a Better vehicle weapon handling and muzzle flash 2022-08-14 13:04:39 +08:00
d68941b3ac blah 2022-08-14 10:46:42 +08:00
67c4646198 Fix update not deleting old assemblies 2022-08-14 10:45:39 +08:00
a7dab1e0e1 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-14 10:36:08 +08:00
2b8a3db6bf Retore traffic on disconnect 2022-08-14 10:35:59 +08:00
f9609afd8a Small update for voice chat 2022-08-14 02:26:59 +02:00
609fd90561 Small tweaks 2022-08-13 16:59:09 +08:00
a7d64d241d New vehicle seat sync and projectile fix 2022-08-13 16:14:18 +08:00
b436abf131 Some hint 2022-08-13 11:42:29 +08:00
c14221f1a3 Remove json attributes 2022-08-13 11:42:15 +08:00
abbc871d1a Small fix 2022-08-13 11:28:35 +08:00
edb8838455 Update libs 2022-08-13 11:05:03 +08:00
4cca75ee35 Merge pull request #32 from RAGECOOP/voice-chat
Add voice chat
2022-08-13 10:55:16 +08:00
669b457275 Don't send voice back 2022-08-13 10:54:39 +08:00
2ef0060037 Added ClearAll() for voice 2022-08-13 04:03:01 +02:00
55649f26ae Use ConcurrentDictionary for ServerEntities 2022-08-13 09:47:13 +08:00
6bb7d5d0fd Merge branch 'voice-chat' of https://github.com/RAGECOOP/RAGECOOP-V into voice-chat 2022-08-13 03:39:16 +02:00
8f5deaa33b More voice changes 2022-08-13 03:39:11 +02:00
91b0afb6ef Remove Acceleration 2022-08-13 09:31:09 +08:00
4dc3cc5c28 Fix voice and add Server.Forward() and Server.SendToAll() 2022-08-13 08:56:05 +08:00
f9a411833a small fix 2022-08-13 02:39:18 +02:00
d24d786fd3 Better packet reuse 2022-08-13 08:22:14 +08:00
edae7a075a blub 2022-08-13 02:19:40 +02:00
85b028e3e7 Added NAudio and first voice test 2022-08-13 00:52:34 +02:00
36de46a2ce Fixed warnings 2022-08-12 20:48:31 +02:00
6e5f72ee70 blah 2022-08-12 20:58:17 +08:00
8d450813e1 ZeroTier intergration 2022-08-12 20:40:50 +08:00
495e5dc6b0 Better server info handling 2022-08-12 18:10:56 +08:00
0bd1d2c2a7 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-12 18:07:06 +08:00
339f817c7e ZeroTier helper 2022-08-12 18:02:43 +08:00
b583618e6c Merge pull request #31 from xEntenKoeniqx/main
Cleaned up (Client)
2022-08-11 21:27:18 +08:00
dc0fafbe9e Fixed failed build of github-actions 2022-08-11 15:22:36 +02:00
b9e22a880f Cleaned up (Client) 2022-08-11 14:59:09 +02:00
cb7f0290ec "Entities" cleaned up 2022-08-11 14:48:19 +02:00
d70c739208 Cleaned up 2022-08-11 14:29:18 +02:00
29202906e1 Fix stuff 2022-08-11 19:42:30 +08:00
1d3e53f734 Packet recycling 2022-08-11 18:25:01 +08:00
a26a799a9f Variables renaming 2022-08-11 16:29:29 +08:00
033d0a3f2e Move some stuff to core 2022-08-11 16:25:38 +08:00
95e302a698 Fix player disconnect 2022-08-11 15:38:19 +08:00
ff9e16bd5e Restore 2022-08-10 20:42:47 +08:00
01c668b159 blah 2022-08-08 18:07:22 +08:00
74e51c8070 Display remote blip 2022-08-08 17:47:09 +08:00
01524b1796 Move CurrentWeaponHash to full sync 2022-08-08 17:29:15 +08:00
83d79b0a17 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-08 17:03:53 +08:00
c3d7c2b335 P2P connectivity 2022-08-08 17:03:41 +08:00
6b6125eb09 Update DownloadManager.cs 2022-08-08 10:19:48 +08:00
09bb121ffe blah 2022-08-06 12:33:05 +08:00
a50be5b869 Some stuff for the upcoming change 2022-08-06 12:32:04 +08:00
ca5ef8cf4c read packet with BinaryReader 2022-08-06 11:40:38 +08:00
4e33956acd Faster way to get client by username 2022-08-06 10:58:24 +08:00
9aae315e11 Simplify 2022-08-06 10:49:55 +08:00
742e7ea998 Packet refactor 2022-08-06 10:43:24 +08:00
69bd4c837c Delete dll when installing update 2022-08-05 17:24:51 +08:00
8c10d24842 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-05 16:13:56 +08:00
6b681afdee Fix wheels 2022-08-05 16:13:40 +08:00
6186497999 blah 2022-08-04 19:18:22 +08:00
2571da594e Remove ResourceDomain 2022-08-04 19:13:39 +08:00
7c52cad569 Remove mscoree reference 2022-08-04 19:07:08 +08:00
303bc940f2 Move network info option to debug menu 2022-08-04 17:47:57 +08:00
eda903abde Sync every 30 ms 2022-08-04 17:38:28 +08:00
5d5bc668eb Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-08-04 17:14:11 +08:00
6e5653a168 Add latency simulation 2022-08-04 17:14:07 +08:00
87343b8255 Update README.md 2022-08-04 15:11:57 +08:00
cb21291983 Update README.md 2022-08-04 15:02:54 +08:00
8dd2241590 Update SyncedVehicle.cs 2022-08-04 08:33:18 +08:00
858fa81ea2 blah 2022-08-02 17:43:48 +08:00
8d6c70b75e Ok, this might look junky... 2022-08-02 17:43:18 +08:00
ffd6ce23c3 Add vehicle velocity prediction/acceleration 2022-08-02 16:42:40 +08:00
5a882d0bc8 Fix announce thread exception 2022-08-02 15:55:26 +08:00
cb1b4f4970 Update Program.cs 2022-08-01 23:20:22 +08:00
0de040548d Update FindScript() 2022-08-01 17:07:08 +08:00
40f4cf88c9 Back to velocity 2022-08-01 16:56:46 +08:00
275b39fb1e Add digits 2022-08-01 16:56:32 +08:00
fe5da3cf90 Check 2022-08-01 07:14:24 +08:00
4ed4e37604 Revert "Set client to null on disconnect"
This reverts commit 10552d119f.
2022-08-01 07:13:05 +08:00
10552d119f Set client to null on disconnect 2022-07-31 21:03:47 +08:00
d4e0e6f0ab Stricter username check 2022-07-31 20:56:51 +08:00
e2fce3cf9b Add some client API back 2022-07-31 20:47:04 +08:00
ad5ffc22ac blah 2022-07-30 21:55:57 +08:00
f980790bd7 blah 2022-07-30 21:54:45 +08:00
df8519961a blah 2022-07-30 21:53:42 +08:00
171a6be736 Revert "Squared"
This reverts commit 91035d9a3f.
2022-07-30 21:51:56 +08:00
91035d9a3f Squared 2022-07-30 21:37:15 +08:00
60e7d91409 Read ped speed 2022-07-30 15:45:27 +08:00
d05d1dac85 predict projectile positioin 2022-07-30 14:09:07 +08:00
8184b4d155 Predict projectile position 2022-07-30 13:43:54 +08:00
a0d5f9d2df stricter flip check 2022-07-30 12:50:04 +08:00
49f7f9e3e1 Calibrate position with force 2022-07-30 12:39:09 +08:00
911d8d5d64 Fix player PlayerInfoUpdate 2022-07-30 12:03:57 +08:00
3015ffe3ba Revert "flush"
This reverts commit 459cfccff5.
2022-07-30 11:44:14 +08:00
eb74bcf987 Update vehicle sync 2022-07-29 22:35:20 +08:00
b63898d8b9 Update vehicle sync 2022-07-29 22:26:19 +08:00
459cfccff5 flush 2022-07-29 21:53:26 +08:00
fdb66cba6d Fix chat message 2022-07-29 21:27:04 +08:00
93d705e402 Latency fix 2022-07-29 21:15:23 +08:00
c8bbdc69d0 Update latency detection method
Plus some server code refactoring
2022-07-29 20:35:39 +08:00
9b1587f334 Ignore API assemblies 2022-07-29 19:11:31 +08:00
3e67cc519e Ignore ready 2022-07-29 18:44:17 +08:00
eee6f614f1 Encrypt chat message 2022-07-29 18:17:45 +08:00
62e5bad6ac Resource interoperability 2022-07-29 17:44:02 +08:00
163f342d7a Change Sender to Client in EventArgs 2022-07-29 16:17:33 +08:00
370247eef2 Allow raising event when sending chat message wth API 2022-07-29 15:07:52 +08:00
f22ecb03f2 Fix welcome message 2022-07-29 14:51:52 +08:00
577857ab5a Hash password at server side 2022-07-29 14:51:17 +08:00
095d16a57e rix 2022-07-29 01:31:34 +08:00
862c2c0a71 Update Server.cs 2022-07-29 01:13:32 +08:00
d7e9ce8ff1 Don't invoke chat message if it's a command 2022-07-29 01:08:34 +08:00
e657a02f7a Update RageCoop.Client.csproj 2022-07-29 00:52:57 +08:00
182ff877f0 Update RageCoop.Client.csproj 2022-07-29 00:44:31 +08:00
521358abb5 Update README.md 2022-07-29 00:36:36 +08:00
b62aed5c5a Update RageCoop.Client.csproj 2022-07-29 00:30:33 +08:00
5c4e31806d cd /d 2022-07-28 18:00:13 +08:00
9f8d695d45 cd 2022-07-28 17:55:40 +08:00
e0e5f52d36 Send message back 2022-07-28 17:50:41 +08:00
9fda563438 Invoke constructor later 2022-07-28 17:46:34 +08:00
6d5bb285c9 Fix client copy 2022-07-28 17:03:29 +08:00
7ac405b8f9 Embed referenced assemblies 2022-07-28 15:46:15 +08:00
45d53bd38c Memory patch for weapon/radio slowdown/vignetting 2022-07-25 22:22:57 +08:00
cc52f66f5d Read quaternion and rotation directly from memory 2022-07-25 19:07:58 +08:00
e072c2aee8 Read entity position directly from memory 2022-07-23 23:45:20 +08:00
e52135c343 Allow sending chat message and command in console 2022-07-22 19:43:48 +08:00
cb53e8a664 Delay vehicle repair, fix vehicle if BodyHealth increased 2022-07-21 23:10:56 +08:00
44a8445b54 Delay vehicle repair 2022-07-21 22:42:29 +08:00
b0a197cd18 Back to old latency compensation 2022-07-21 09:53:07 +08:00
9ba52b332c Revert some vehicle sync change 2022-07-21 09:43:09 +08:00
ecfc77e57a Fix resource loading, add player died notification 2022-07-21 08:41:05 +08:00
2ba3e33d39 Don't follow position if velocity is greater than 3 2022-07-20 18:02:17 +08:00
71ecdbd5b9 code cleanup 2022-07-20 17:50:01 +08:00
bbe0b21aa4 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-07-20 09:32:34 +08:00
51dd445a31 Fix resource duplication 2022-07-20 09:32:26 +08:00
264158957c Send response in same sequence channel 2022-07-20 09:11:07 +08:00
9d8963222c Update nightly-build.yaml 2022-07-20 07:50:03 +08:00
c1a35dbab3 Update nightly-build.yaml 2022-07-20 07:44:52 +08:00
d3931cc57c Update nightly-build.yaml 2022-07-20 07:37:26 +08:00
3795ced530 Update nightly-build.yaml 2022-07-20 07:35:02 +08:00
1372a5c33f Update release configuration 2022-07-20 07:34:53 +08:00
9527da9785 Add ability to share server file 2022-07-19 17:15:53 +08:00
3072db0e21 Only load top-level assembly 2022-07-18 17:27:06 +08:00
444e6d7ffa Unify resource file path 2022-07-18 17:24:31 +08:00
809c935d37 Fix entity fire 2022-07-17 20:12:25 +08:00
10bd54a785 remove testing code 2022-07-17 19:19:58 +08:00
ece899f2a1 Better vehicle movement sync 2022-07-17 19:19:23 +08:00
5d1a182e47 Better ragdoll sync (maybe) 2022-07-17 18:44:16 +08:00
228ab9e727 Ped movement change 2022-07-17 14:23:19 +08:00
2605aafc4f Vehicle flag fix 2022-07-17 12:22:11 +08:00
582bb2a7d7 Ahhh... 2022-07-17 12:00:26 +08:00
0a3dbb1f59 Merge vehicle sync packet, comment cleanup 2022-07-17 11:59:37 +08:00
5d1832fcef Merge ped packet
Back to old project family (for debugging)
Fix blip name
Delete newly spawned traffic if limit exceeded
2022-07-17 11:15:02 +08:00
30ed509c55 Prevent entity spawning from interfering Tick event 2022-07-15 13:45:43 +08:00
2ad91011c3 blah 2022-07-14 18:52:04 +08:00
e40d4695ea Add QueueAction overload 2022-07-14 18:50:15 +08:00
e4c03f8ccc Add in-game update feature 2022-07-14 17:59:41 +08:00
e10a0f5415 Add Logger property for resource script 2022-07-14 16:44:35 +08:00
2a29011015 Better projectile check 2022-07-14 15:28:03 +08:00
a37e8869ac Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-07-14 10:33:34 +08:00
038860f158 Update nametag drawing method 2022-07-14 10:33:24 +08:00
51e678d760 Update README.md 2022-07-13 20:13:29 +08:00
1e2a9992d8 Merge branch 'main' of https://github.com/RAGECOOP/RAGECOOP-V 2022-07-13 11:08:21 +08:00
5b7bb917ef add tls 2022-07-13 11:08:11 +08:00
72aff78718 Move logger 2022-07-13 11:08:04 +08:00
cdeef7279c coding style 2022-07-13 10:36:38 +08:00
9cb5c557c5 fix vehicle doors sync 2022-07-13 00:20:06 +08:00
1e1259d0a5 Fix 2022-07-12 17:16:28 +08:00
20b326d396 Check before breaking a door 2022-07-12 17:15:54 +08:00
b7cad3b8f7 Move GetWeaponComponents to WeaponUtil 2022-07-12 17:14:06 +08:00
cf8b54a3b5 Fix WeatherTimeSync option 2022-07-12 17:10:16 +08:00
edca1e2b98 Don't sync invisible vehicles 2022-07-11 14:53:22 +08:00
89410b8b46 Fix master server 2022-07-11 13:26:28 +08:00
a9397690e2 Fix 2022-07-11 12:02:46 +08:00
ea678ece94 Update master server 2022-07-11 12:01:12 +08:00
f28f23f560 Change master server 2022-07-11 12:00:25 +08:00
b2911017d0 Cleanup 2022-07-11 11:59:32 +08:00
cda4f702a1 Small API cleanup 2022-07-11 11:30:00 +08:00
b6a0ae7f4a Remove MapLoader 2022-07-11 11:24:17 +08:00
b6f3508680 Fix 2022-07-11 11:11:48 +08:00
ad0368b4c0 Merge resource loading 2022-07-11 11:10:43 +08:00
b1d67bd1c4 Add OnKeyDown and OnKeyUp event for API 2022-07-11 11:06:57 +08:00
cda3d37f37 Connect fix 2022-07-11 11:06:31 +08:00
abed0f146f Don't load resources previously downloaded 2022-07-11 10:08:21 +08:00
bdeaeee293 Update Resources.cs 2022-07-10 23:30:29 +08:00
b37ce4d0c1 Update 2022-07-10 18:02:53 +08:00
e3ff62bdc7 Remove docs 2022-07-10 16:49:41 +08:00
eab6c79e15 Update docs 2022-07-10 16:39:28 +08:00
260678c1bc Update index.md 2022-07-10 16:34:42 +08:00
b2d300784f Update docs 2022-07-10 16:33:59 +08:00
8d1d4f5eb7 Update nightly-build.yaml 2022-07-10 16:25:44 +08:00
25a03c7e27 Update nightly-build.yaml 2022-07-10 16:22:24 +08:00
27ab63ae7a Add weather and time sync 2022-07-10 16:13:08 +08:00
9567e97b31 Add vehicle livery sync 2022-07-09 22:18:00 +08:00
b10a8b47d5 Small vehicle exit fix 2022-07-09 19:59:17 +08:00
1bc274e2d0 Some tweaks 2022-07-09 19:32:11 +08:00
dca3420071 Use Array.Copy() instead of Linq 2022-07-09 14:04:39 +08:00
bc3fc70a95 Some tweaks 2022-07-09 13:55:06 +08:00
eee983dfd2 Change output path 2022-07-09 13:27:07 +08:00
fbec69cd71 Reload SHVDN on disconnect if there're client resources 2022-07-09 13:16:41 +08:00
615c49e395 Add country 2022-07-09 11:39:37 +08:00
462d68df6b Add addtional server info 2022-07-09 11:36:04 +08:00
0224abc9bd Request model before creating vehicle 2022-07-09 11:21:42 +08:00
16fe2fe1ab Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-09 10:51:26 +08:00
61c4486551 Disable code trimming 2022-07-09 10:44:54 +08:00
17ef088492 Don't show info if no script loaded 2022-07-09 09:59:17 +08:00
1111879c4d Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-09 09:55:22 +08:00
e9d0b73296 Add managed and loaded assembly check 2022-07-09 09:55:12 +08:00
8eadbf6fee Update README.md 2022-07-08 19:51:21 +08:00
8ae491d0d5 Move ServerPbject and ServerEntities to RageCoop.Server.Scripting 2022-07-07 16:57:43 +08:00
3dd125f861 Reference custom SHVDN build 2022-07-07 16:24:03 +08:00
01b4753dc6 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-06 01:03:35 +08:00
5a5d78ac27 Networking statistics, disable resource unload 2022-07-06 01:03:18 +08:00
a05eedd68e Fix NativeResponse 2022-07-05 21:58:37 +08:00
05304f9461 Add SendCustomEventQueued() 2022-07-05 11:18:26 +08:00
fff5ec4534 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-05 10:52:33 +08:00
cf7641cee0 Change object list to array for CustomEvent 2022-07-05 10:52:22 +08:00
a77e0c1fc9 Fix ServerBlip 2022-07-05 00:18:12 +08:00
f374de2064 Update BaseScript.cs 2022-07-05 00:16:15 +08:00
8e0620bedb Fix CreateVehicle() not setting owner properly 2022-07-05 00:02:22 +08:00
8bb39b1bfa Add PedBlip 2022-07-04 23:59:51 +08:00
2450956fda Add ped blip sync 2022-07-04 22:50:47 +08:00
56cd17401b Change parameter order for consistency 2022-07-04 21:35:01 +08:00
a53b514fec Add ServerEntities.CreateVehicle() 2022-07-04 21:29:13 +08:00
de93af73f2 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-04 09:57:13 +08:00
44f106fd0c Add ServerBlip.Handle handle 2022-07-04 09:54:43 +08:00
8ac5cc40c1 Change parameter order for SendCustomEvent and SendNativecall
Use NativeCall to update entity
2022-07-04 09:41:00 +08:00
8d7eebd390 Fix DeleteEntity not being dispatched to main thread 2022-07-03 23:42:00 +08:00
0a5bb67c54 Add Name property for blip, fix blip scale 2022-07-03 21:00:05 +08:00
f866151cc5 Update docs 2022-07-03 15:37:51 +08:00
08e17b1714 Server side blip and Vector2 argument 2022-07-03 15:28:28 +08:00
fa96f4c073 Add Freeze() method for ServerObject 2022-07-03 10:55:49 +08:00
dc5cf2b965 ServerObject Inheritance, getter and setter for Position and Roatation, Fix connect error. 2022-07-03 10:46:24 +08:00
f47f0570f9 Update docs 2022-07-02 18:32:49 +08:00
e0c96cc200 Minor documentation fix 2022-07-02 18:30:16 +08:00
17e1ae9ea9 Update documentation 2022-07-02 18:04:07 +08:00
8401d616e0 Convert object to handle at client side 2022-07-02 17:53:35 +08:00
d8ac486984 Server side prop control 2022-07-02 17:14:56 +08:00
335ea2ca38 Fix data folder 2022-07-02 13:53:14 +08:00
c73ff96690 Add JB7002 weapon (not tested), closes #15 2022-07-02 13:37:44 +08:00
15b23e3a4e Add DOMINATOR5, IMPALER3, IMPERATOR2, SLAMVAN5 (not tested), closes #16 2022-07-02 13:32:26 +08:00
47f86c098d Add Runier2 weapon, closes #18 2022-07-02 13:21:55 +08:00
6c2c7e881f Add TAMPA3 weapon, closes #19 2022-07-02 13:12:59 +08:00
74a2265200 Add DUNE3 weapon sync 2022-07-02 12:53:18 +08:00
0b2ec1d626 Fix player position not correctly set 2022-07-02 12:39:50 +08:00
8ee188cf19 update docs 2022-07-02 11:23:12 +08:00
eb5b23ae17 Server side entities and custom resource location 2022-07-01 19:02:38 +08:00
77999fe8f3 Add docfx documentation 2022-07-01 17:00:42 +08:00
64fda51917 Restore 2022-07-01 14:39:43 +08:00
4165b757a5 XML comments 2022-07-01 13:54:18 +08:00
d31799ab7b Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-07-01 12:25:37 +08:00
8a46bd6b68 Resource loading 2022-07-01 12:22:31 +08:00
58362c2613 Preparing for PluginLoader 2022-06-30 09:28:13 +08:00
7d6c5fe3bd Remove "nightly" suffix 2022-06-27 16:00:19 +08:00
f9b9d78b79 Merge branch 'main' of https://github.com/RAGECOOP/RageCoop-V 2022-06-27 15:35:32 +08:00
7670af88a2 Thread termination fix 2022-06-27 15:35:23 +08:00
0800361b40 Minor fixes 2022-06-27 14:42:57 +08:00
cdf3f41127 Update README.md 2022-06-27 14:13:55 +08:00
552bf8d61c Update README.md 2022-06-27 14:12:53 +08:00
2f58e48f42 Nightly 2022-06-27 14:07:13 +08:00
67035f5d2f test 2022-06-27 13:56:42 +08:00
b48206d942 nightly test 2022-06-27 13:49:16 +08:00
300 changed files with 15359 additions and 32932 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 RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
- name: Build server win-x64
run: dotnet build RageCoop.Server/RageCoop.Server.csproj -o bin/Release/Server
- name: Upload server
uses: actions/upload-artifact@v3
with:
name: RageCoop.Server
path: bin/Release/Server
- name: Upload Client
uses: actions/upload-artifact@v3
with:
name: RageCoop.Client
path: bin/Release/Client
- uses: actions/checkout@v2

View File

@ -2,9 +2,7 @@ name: Nightly-build
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
branches: [ "dev-nightly" ]
jobs:
build:
@ -22,29 +20,58 @@ jobs:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Restore dependencies
run: dotnet restore
- name: Build client
run: dotnet build RageCoop.Client/RageCoop.Client.csproj --no-restore --configuration Release -o RageCoop.Client/bin/RageCoop
- name: Restore nuget packages
run: nuget restore
- name: Build client and installer
run: dotnet build RageCoop.Client.Installer/RageCoop.Client.Installer.csproj --configuration Release -o bin/Release/Client/RageCoop
- name: Build server win-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r win-x64 -o RageCoop.Server/bin/win-x64 -c Release
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r win-x64 -o bin/Release/Server/win-x64 -c Release
- name: Build server linux-x64
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-x64 -o RageCoop.Server/bin/linux-x64 -c Release
- name: Build server linux-arm
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true -r linux-arm -o RageCoop.Server/bin/linux-arm -c Release
- uses: actions/upload-artifact@v3
run: dotnet publish RageCoop.Server/RageCoop.Server.csproj --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=false -r linux-x64 -o bin/Release/Server/linux-x64 -c Release
- uses: vimtor/action-zip@v1
with:
name: RageCoop.Client
path: RageCoop.Client/bin
- uses: actions/upload-artifact@v3
files: bin/Release/Client
dest: RageCoop.Client.zip
- uses: vimtor/action-zip@v1
with:
name: RageCoop.Server-win-x64
path: RageCoop.Server/bin/win-x64
- uses: actions/upload-artifact@v3
files: bin/Release/Server/win-x64
dest: RageCoop.Server-win-x64.zip
- uses: vimtor/action-zip@v1
with:
name: RageCoop.Server-linux-x64
path: RageCoop.Server/bin/linux-x64
- uses: actions/upload-artifact@v3
files: bin/Release/Server/linux-x64
dest: RageCoop.Server-linux-x64.zip
- uses: WebFreak001/deploy-nightly@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
name: RageCoop.Server-linux-arm
path: RageCoop.Server/bin/linux-arm
- uses: actions/checkout@v2
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
release_id: 70603992
asset_path: RageCoop.Client.zip
asset_name: RageCoop.Client.zip
asset_content_type: application/zip
max_releases: 7
- uses: WebFreak001/deploy-nightly@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
release_id: 70603992
asset_path: RageCoop.Server-win-x64.zip
asset_name: RageCoop.Server-win-x64.zip
asset_content_type: application/zip
max_releases: 7
- uses: WebFreak001/deploy-nightly@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/RAGECOOP/RAGECOOP-V/releases/70603992/assets{?name,label}
release_id: 70603992
asset_path: RageCoop.Server-linux-x64.zip
asset_name: RageCoop.Server-linux-x64.zip
asset_content_type: application/zip
max_releases: 7

3
.gitignore vendored
View File

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

View File

@ -1,76 +1,76 @@
# 🌐 RAGECOOP
[![Downloads][downloads-shield]][downloads-url]
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
# ⚠ Notice
The original author of this project is [EntenKoeniq](https://github.com/EntenKoeniq).
The project has been reworked and is currently maintained by [Sardelka9515](https://github.com/Sardelka9515).
To download the legacy versions, go to [this repository](https://github.com/RAGECOOP/RAGECOOP-V.OLD)
# 🧠 That's it
RAGECOOP is a multiplayer mod to play story mode or some mods made for RAGECOOP or just drive around with your buddy.
_Old name: GTACOOP:R_
RAGECOOP brings multiplayer experience to the story mode, you can complete missions together with your friends, use mods without any restriction/getting banned, or just mess around with your fella!
# 📋 Requirements
- Visual Studio 2022
- .NET 6.0
- .NET Framework 4.8
# 👁 Requirements
- ScriptHookV
- ScriptHookVDotNet 3.6.0 or later
- .NET Framework 4.8 Runtime or SDK
# 📚 Libraries
- [ScriptHookVDotNet3](https://github.com/crosire/scripthookvdotnet/releases/tag/v3.4.0)
- [LemonUI.SHVDN3](https://github.com/justalemon/LemonUI/releases/tag/v1.6)
- Lidgren Network Custom (***PRIVATE***)
# 📋 Building the project
You'll need:
- .NET 6.0 SDK
- .NET Framework 4.8 SDK
Recommended IDE:
- Visual Studio Code
- Visul Studio 2022
Then run `dotnet build` in the solution directory, built binaries are in the `bin` folder
# 📚 Third-party libraries
- [ScriptHookVDotNet3](https://github.com/crosire/scripthookvdotnet)
- [LemonUI.SHVDN3](https://github.com/justalemon/LemonUI)
- Lidgren Network Custom
- - No new features (only improvements)
- [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/13.0.1)
- [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
- [ClearScript](https://github.com/microsoft/ClearScript)
- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
- [DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins)
# Features
# 👋 Features
1. Synchronized bullets
2. Synchronized vehicle/player/NPC
3. Synchronized projectiles
4. Simple ragdoll sync
5. Smoother vehicle/ped movement.
6. Ownership based sync logic, carjacking is now working (sort of).
7. Introduced SyncEvents.
8. Code refactoring and namespace cleanup
9. Synchronized vehicle doors, brake and throttle.
10. Weaponized vehicle sync(WIP).
11. Other improvements
5. Decent compatibility with other mods, set up a private modded server to have some fun!
6. Weaponized vehicle sync(WIP).
7. Optimization for high-Ping condition, play with friends around the world!
8. Powerful scripting API and resource system, easily [add multiplayer functionality to your mod](HTTPS://docs.ragecoop.com).
# Known issues
# Known issues
1. Weapon sounds are missing.
2. Cover sync is still buggy.
3. Framerate drop with high number of synchronized entities.
5. Scripting API is screwed.(will be rewritten in the future)
See [Bugs](https://github.com/RAGECOOP/RAGECOOP-V/issues/33)
## Installation
# 🔫 Installation
Refer to the [wiki](https://github.com/RAGECOOP/RAGECOOP-V/wiki)
# Downloads
# 🧨 Downloads
Download latest release [here](https://github.com/RAGECOOP/RAGECOOP-V/releases/latest)
You can also download nightly builds [here](https://github.com/RAGECOOP/RAGECOOP-V/actions), which includes latest features and bug-fixes, but has not been thoroughly tested.
Select latest workflow run, scroll down to bottom then download the artifacts.
You can also download nightly builds [here](https://github.com/RAGECOOP/RAGECOOP-V/releases/nightly), which includes latest features and bug-fixes, but has not been thoroughly tested.
Please note that this is incompatible with all previous versions of ragecoop, remove old files before installing.
# Support us
<a href="https://patreon.com/Sardelka"><img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3DSardelka%26type%3Dpatrons&style=for-the-badge" /></a>
# 🦆 Special thanks to
- [Makinolo](https://github.com/Makinolo), [oldnapalm](https://github.com/oldnapalm)
- - For testing, ideas, contributions and the first modification with the API
- [crosire](https://github.com/crosire)
@ -79,8 +79,11 @@ Please note that this is incompatible with all previous versions of ragecoop, re
- - For the extensive work in LemonUI
# 📝 License
This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOOP-V/blob/main/LICENSE)
[downloads-shield]: https://img.shields.io/github/downloads/RAGECOOP/RAGECOOP-V/total?style=for-the-badge
[downloads-url]: https://github.com/RAGECOOP/RAGECOOP-V/releases
[contributors-shield]: https://img.shields.io/github/contributors/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
[contributors-url]: https://github.com/RAGECOOP/RAGECOOP-V/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
@ -90,3 +93,4 @@ This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOO
[issues-shield]: https://img.shields.io/github/issues/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
[issues-url]: https://github.com/RAGECOOP/RAGECOOP-V/issues

View File

@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Core", "RageCoop.C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RageCoop.Client", "RageCoop.Client\RageCoop.Client.csproj", "{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RageCoop.Client.Installer", "RageCoop.Client.Installer\RageCoop.Client.Installer.csproj", "{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -41,6 +43,14 @@ Global
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|Any CPU.Build.0 = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.ActiveCfg = Release|Any CPU
{EF56D109-1F22-43E0-9DFF-CFCFB94E0681}.Release|x64.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|x64.ActiveCfg = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Debug|x64.Build.0 = Debug|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|Any CPU.Build.0 = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.ActiveCfg = Release|Any CPU
{576D8610-0C28-4B60-BE2B-8657EA7CEE1B}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

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

View File

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<None Remove="bg.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RageCoop.Client\RageCoop.Client.csproj" />
<ProjectReference Include="..\RageCoop.Core\RageCoop.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Resource Include="bg.png" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

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>

View File

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

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

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.PostTicker(TimeStamps.Dump(), false).Handle;
}
}

View File

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

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 ExcludeAssemblies="RageCoop.Core"/>
</Weavers>

View File

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

View File

@ -1,41 +0,0 @@
// NO MERGE
// Taken from the .NET Runtime Repository
// https://github.com/dotnet/runtime
// Copyright (c) .NET Foundation and Contributors
// Under the MIT License
#if FIVEM
using System;
namespace LemonUI // Previously System.ComponentModel
{
/// <summary>
/// Represents the method that will handle the event raised when canceling an event.
/// </summary>
public delegate void CancelEventHandler(object sender, CancelEventArgs e);
/// <summary>
/// EventArgs used to describe a cancel event.
/// </summary>
public class CancelEventArgs : EventArgs
{
/// <summary>
/// Gets or sets a value indicating whether we should cancel the operation or not
/// </summary>
public bool Cancel { get; set; }
/// <summary>
/// Default constructor
/// </summary>
public CancelEventArgs()
{
}
/// <summary>
/// Helper constructor
/// </summary>
/// <param name="cancel"></param>
public CancelEventArgs(bool cancel) => Cancel = cancel;
}
}
#endif

View File

@ -1,138 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage.Native;
using Control = Rage.GameControl;
#elif SHVDN3
using GTA;
using GTA.Native;
#endif
using System.Collections.Generic;
namespace LemonUI
{
/// <summary>
/// Tools for dealing with controls.
/// </summary>
internal static class Controls
{
/// <summary>
/// Gets if the player used a controller for the last input.
/// </summary>
public static bool IsUsingController
{
get
{
#if FIVEM
return !API.IsInputDisabled(2);
#elif RAGEMP
return !Invoker.Invoke<bool>(Natives.IsInputDisabled, 2);
#elif RPH
return !NativeFunction.CallByHash<bool>(0xA571D46727E2B718, 2);
#elif SHVDN3
return !Function.Call<bool>(Hash._IS_USING_KEYBOARD, 2);
#endif
}
}
/// <summary>
/// Checks if a control was pressed during the last frame.
/// </summary>
/// <param name="control">The control to check.</param>
/// <returns>true if the control was pressed, false otherwise.</returns>
public static bool IsJustPressed(Control control)
{
#if FIVEM
return API.IsDisabledControlJustPressed(0, (int)control);
#elif RAGEMP
return Invoker.Invoke<bool>(Natives.IsDisabledControlJustPressed, 0, (int)control);
#elif RPH
return NativeFunction.CallByHash<bool>(0x91AEF906BCA88877, 0, (int)control);
#elif SHVDN3
return Function.Call<bool>(Hash.IS_DISABLED_CONTROL_JUST_PRESSED, 0, (int)control);
#endif
}
/// <summary>
/// Checks if a control is currently pressed.
/// </summary>
/// <param name="control">The control to check.</param>
/// <returns>true if the control is pressed, false otherwise.</returns>
public static bool IsPressed(Control control)
{
#if FIVEM
return API.IsDisabledControlPressed(0, (int)control);
#elif RAGEMP
return Invoker.Invoke<bool>(Natives.IsDisabledControlJustPressed, 0, (int)control);
#elif RPH
return NativeFunction.CallByHash<bool>(0xE2587F8CBBD87B1D, 0, (int)control);
#elif SHVDN3
return Function.Call<bool>(Hash.IS_DISABLED_CONTROL_PRESSED, 0, (int)control);
#endif
}
/// <summary>
/// Disables all of the controls during the next frame.
/// </summary>
public static void DisableAll(int inputGroup = 0)
{
#if FIVEM
API.DisableAllControlActions(inputGroup);
#elif RAGEMP
Invoker.Invoke(Natives.DisableAllControlActions, inputGroup);
#elif RPH
NativeFunction.CallByHash<int>(0x5F4B6931816E599B, inputGroup);
#elif SHVDN3
Function.Call(Hash.DISABLE_ALL_CONTROL_ACTIONS, inputGroup);
#endif
}
/// <summary>
/// Enables a control during the next frame.
/// </summary>
/// <param name="control">The control to enable.</param>
public static void EnableThisFrame(Control control)
{
#if FIVEM
API.EnableControlAction(0, (int)control, true);
#elif RAGEMP
Invoker.Invoke(Natives.EnableControlAction, 0, (int)control, true);
#elif RPH
NativeFunction.CallByHash<int>(0x351220255D64C155, 0, (int)control);
#elif SHVDN3
Function.Call(Hash.ENABLE_CONTROL_ACTION, 0, (int)control);
#endif
}
/// <summary>
/// Enables a specific set of controls during the next frame.
/// </summary>
/// <param name="controls">The controls to enable.</param>
public static void EnableThisFrame(IEnumerable<Control> controls)
{
foreach (Control control in controls)
{
EnableThisFrame(control);
}
}
/// <summary>
/// Disables a control during the next frame.
/// </summary>
/// <param name="control">The control to disable.</param>
public static void DisableThisFrame(Control control)
{
#if FIVEM
API.DisableControlAction(0, (int)control, true);
#elif RAGEMP
Invoker.Invoke(Natives.DisableControlAction, 0, (int)control, true);
#elif RPH
NativeFunction.CallByHash<int>(0xFE99B66D079CF6BC, 0, (int)control, true);
#elif SHVDN3
Function.Call(Hash.DISABLE_CONTROL_ACTION, 0, (int)control, true);
#endif
}
}
}

View File

@ -1,23 +0,0 @@
#if RPH
namespace LemonUI
{
/// <summary>
/// The alignment of the element to draw.
/// </summary>
public enum Alignment
{
/// <summary>
/// Aligns the element to the Center.
/// </summary>
Center = 0,
/// <summary>
/// Aligns the element to the Left.
/// </summary>
Left = 1,
/// <summary>
/// Aligns the element to the RIght.
/// </summary>
Right = 2,
}
}
#endif

View File

@ -1,113 +0,0 @@
using LemonUI.Extensions;
using System.Drawing;
namespace LemonUI.Elements
{
/// <summary>
/// Base class for all of the 2D elements.
/// </summary>
public abstract class BaseElement : I2Dimensional
{
#region Private Fields
/// <summary>
/// The 1080 scaled position.
/// </summary>
protected internal PointF literalPosition = PointF.Empty;
/// <summary>
/// The relative position between 0 and 1.
/// </summary>
protected internal PointF relativePosition = PointF.Empty;
/// <summary>
/// The 1080 scaled size.
/// </summary>
protected internal SizeF literalSize = SizeF.Empty;
/// <summary>
/// The relative size between 0 and 1.
/// </summary>
protected internal SizeF relativeSize = SizeF.Empty;
#endregion
#region Public Properties
/// <summary>
/// The Position of the drawable.
/// </summary>
public PointF Position
{
get
{
return literalPosition;
}
set
{
literalPosition = value;
Recalculate();
}
}
/// <summary>
/// The Size of the drawable.
/// </summary>
public SizeF Size
{
get
{
return literalSize;
}
set
{
literalSize = value;
Recalculate();
}
}
/// <summary>
/// The Color of the drawable.
/// </summary>
public Color Color { get; set; } = Color.FromArgb(255, 255, 255, 255);
/// <summary>
/// The rotation of the drawable.
/// </summary>
public float Heading { get; set; } = 0;
#endregion
#region Constructors
/// <summary>
/// Creates a new <see cref="BaseElement"/> with the specified Position and Size.
/// </summary>
/// <param name="pos">The position of the Element.</param>
/// <param name="size">The size of the Element.</param>
public BaseElement(PointF pos, SizeF size)
{
literalPosition = pos;
literalSize = size;
Recalculate();
}
#endregion
#region Private Functions
/// <summary>
/// Recalculates the size and position of this item.
/// </summary>
public virtual void Recalculate()
{
relativePosition = literalPosition.ToRelative();
relativeSize = literalSize.ToRelative();
}
#endregion
#region Public Functions
/// <summary>
/// Draws the item on the screen.
/// </summary>
public abstract void Draw();
#endregion
}
}

View File

@ -1,33 +0,0 @@
// NO MERGE
#if RPH
namespace LemonUI.Elements
{
/// <summary>
/// An enum representing the fonts available in game.
/// </summary>
public enum Font
{
/// <summary>
/// Chalet London Nineteen Sixty.
/// </summary>
ChaletLondon = 0,
/// <summary>
/// SignPainter HouseScript Regular.
/// </summary>
HouseScript = 1,
/// <summary>
/// Unknown Monospaced Font.
/// </summary>
Monospace = 2,
/// <summary>
/// Chalet Comprime Cologne.
/// </summary>
ChaletComprimeCologne = 4,
/// <summary>
/// Pricedown.
/// </summary>
Pricedown = 7
}
}
#endif

View File

@ -1,23 +0,0 @@
using System.Drawing;
namespace LemonUI.Elements
{
/// <summary>
/// A 2D item that can be drawn on the screen.
/// </summary>
public interface I2Dimensional : IRecalculable, IDrawable
{
/// <summary>
/// The Position of the drawable.
/// </summary>
PointF Position { get; set; }
/// <summary>
/// The Size of the drawable.
/// </summary>
SizeF Size { get; set; }
/// <summary>
/// The Color of the drawable.
/// </summary>
Color Color { get; set; }
}
}

View File

@ -1,68 +0,0 @@
#if FIVEM
using Alignment = CitizenFX.Core.UI.Alignment;
using Font = CitizenFX.Core.UI.Font;
#elif RAGEMP
using RAGE.Game;
#elif SHVDN3
using Alignment = GTA.UI.Alignment;
using Font = GTA.UI.Font;
#endif
using System.Drawing;
namespace LemonUI.Elements
{
/// <summary>
/// A Drawable screen text.
/// </summary>
public interface IText : IRecalculable, IDrawable
{
/// <summary>
/// The position of the text.
/// </summary>
PointF Position { get; set; }
/// <summary>
/// The text to draw.
/// </summary>
string Text { get; set; }
/// <summary>
/// The color of the text.
/// </summary>
Color Color { get; set; }
/// <summary>
/// The game font to use.
/// </summary>
Font Font { get; set; }
/// <summary>
/// The scale of the text.
/// </summary>
float Scale { get; set; }
/// <summary>
/// If the text should have a drop down shadow.
/// </summary>
bool Shadow { get; set; }
/// <summary>
/// If the text should have an outline.
/// </summary>
bool Outline { get; set; }
/// <summary>
/// The alignment of the text.
/// </summary>
Alignment Alignment { get; set; }
/// <summary>
/// The maximum distance from X where the text would wrap into a new line.
/// </summary>
float WordWrap { get; set; }
/// <summary>
/// The width that the text takes from the screen.
/// </summary>
float Width { get; }
/// <summary>
/// The number of lines used by this text.
/// </summary>
int LineCount { get; }
/// <summary>
/// The height of each line of text.
/// </summary>
float LineHeight { get; }
}
}

View File

@ -1,65 +0,0 @@
#if FIVEM
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage.Native;
#elif SHVDN3
using GTA.Native;
#endif
using System.Drawing;
namespace LemonUI.Elements
{
/// <summary>
/// A 2D rectangle.
/// </summary>
public class ScaledRectangle : BaseElement
{
#region Constructor
/// <summary>
/// Creates a new <see cref="ScaledRectangle"/> with the specified Position and Size.
/// </summary>
/// <param name="pos">The position of the Rectangle.</param>
/// <param name="size">The size of the Rectangle.</param>
public ScaledRectangle(PointF pos, SizeF size) : base(pos, size)
{
}
#endregion
#region Public Functions
/// <summary>
/// Draws the rectangle on the screen.
/// </summary>
public override void Draw()
{
if (Size == SizeF.Empty)
{
return;
}
#if FIVEM
API.DrawRect(relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
#elif RAGEMP
Invoker.Invoke(Natives.DrawRect, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
#elif RPH
NativeFunction.CallByHash<int>(0x3A618A217E5154F0, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
#elif SHVDN3
Function.Call(Hash.DRAW_RECT, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Color.R, Color.G, Color.B, Color.A);
#endif
}
/// <summary>
/// Recalculates the position based on the size.
/// </summary>
public override void Recalculate()
{
base.Recalculate();
relativePosition.X += relativeSize.Width * 0.5f;
relativePosition.Y += relativeSize.Height * 0.5f;
}
#endregion
}
}

View File

@ -1,506 +0,0 @@
#if FIVEM
using CitizenFX.Core.Native;
using CitizenFX.Core.UI;
using Font = CitizenFX.Core.UI.Font;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage.Native;
#elif SHVDN3
using GTA.UI;
using GTA.Native;
using Font = GTA.UI.Font;
#endif
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using LemonUI.Extensions;
namespace LemonUI.Elements
{
/// <summary>
/// A text string.
/// </summary>
public class ScaledText : IText
{
#region Consistent Values
/// <summary>
/// The size of every chunk of text.
/// </summary>
private const int chunkSize = 90;
#endregion
#region Private Fields
/// <summary>
/// The absolute 1080p based screen position.
/// </summary>
private PointF absolutePosition = PointF.Empty;
/// <summary>
/// The relative 0-1 relative position.
/// </summary>
private PointF relativePosition = PointF.Empty;
/// <summary>
/// The raw string of text.
/// </summary>
private string text = string.Empty;
/// <summary>
/// The raw string split into equally sized strings.
/// </summary>
private List<string> chunks = new List<string>();
/// <summary>
/// The alignment of the item.
/// </summary>
private Alignment alignment = Alignment.Left;
/// <summary>
/// The word wrap value passed by the user.
/// </summary>
private float internalWrap = 0f;
/// <summary>
/// The real word wrap value based on the position of the text.
/// </summary>
private float realWrap = 0f;
#endregion
#region Public Properties
/// <summary>
/// The position of the text.
/// </summary>
public PointF Position
{
get => absolutePosition;
set
{
absolutePosition = value;
relativePosition = value.ToRelative();
}
}
/// <summary>
/// The text to draw.
/// </summary>
public string Text
{
get => text;
set
{
text = value;
Slice();
}
}
/// <summary>
/// The color of the text.
/// </summary>
public Color Color { get; set; } = Color.FromArgb(255, 255, 255, 255);
/// <summary>
/// The game font to use.
/// </summary>
public Font Font { get; set; } = Font.ChaletLondon;
/// <summary>
/// The scale of the text.
/// </summary>
public float Scale { get; set; } = 1f;
/// <summary>
/// If the text should have a drop down shadow.
/// </summary>
public bool Shadow { get; set; } = false;
/// <summary>
/// If the test should have an outline.
/// </summary>
public bool Outline { get; set; } = false;
/// <summary>
/// The alignment of the text.
/// </summary>
public Alignment Alignment
{
get => alignment;
set
{
alignment = value;
Recalculate();
}
}
/// <summary>
/// The distance from the start position where the text will be wrapped into new lines.
/// </summary>
public float WordWrap
{
get
{
return internalWrap;
}
set
{
internalWrap = value;
Recalculate();
}
}
/// <summary>
/// The width that the text takes from the screen.
/// </summary>
public float Width
{
get
{
#if FIVEM
API.BeginTextCommandWidth("CELL_EMAIL_BCON");
Add();
return API.EndTextCommandGetWidth(true) * 1f.ToXAbsolute();
#elif RAGEMP
Invoker.Invoke(Natives.BeginTextCommandWidth, "CELL_EMAIL_BCON");
Add();
return Invoker.Invoke<float>(Natives.EndTextCommandGetWidth) * 1f.ToXAbsolute();
#elif RPH
NativeFunction.CallByHash<int>(0x54CE8AC98E120CAB, "CELL_EMAIL_BCON");
Add();
return NativeFunction.CallByHash<float>(0x85F061DA64ED2F67, true) * 1f.ToXAbsolute();
#elif SHVDN3
Function.Call(Hash._BEGIN_TEXT_COMMAND_GET_WIDTH, "CELL_EMAIL_BCON");
Add();
return Function.Call<float>(Hash._END_TEXT_COMMAND_GET_WIDTH, true) * 1f.ToXAbsolute();
#endif
}
}
/// <summary>
/// The number of lines used by this text.
/// </summary>
public int LineCount
{
get
{
#if FIVEM
API.BeginTextCommandLineCount("CELL_EMAIL_BCON");
#elif RAGEMP
Invoker.Invoke(Natives.BeginTextCommandLineCount, "CELL_EMAIL_BCON");
#elif RPH
NativeFunction.CallByHash<int>(0x521FB041D93DD0E4, "CELL_EMAIL_BCON");
#elif SHVDN3
Function.Call(Hash._BEGIN_TEXT_COMMAND_LINE_COUNT, "CELL_EMAIL_BCON");
#endif
Add();
#if FIVEM
return API.EndTextCommandGetLineCount(relativePosition.X, relativePosition.Y);
#elif RAGEMP
return Invoker.Invoke<int>(Natives.EndTextCommandGetLineCount, relativePosition.X, relativePosition.Y);
#elif RPH
return NativeFunction.CallByHash<int>(0x9040DFB09BE75706, relativePosition.X, relativePosition.Y);
#elif SHVDN3
return Function.Call<int>(Hash._END_TEXT_COMMAND_LINE_COUNT, relativePosition.X, relativePosition.Y);
#endif
}
}
/// <summary>
/// The relative height of each line in the text.
/// </summary>
public float LineHeight
{
get
{
// Height will always be 1080
#if FIVEM
return 1080 * API.GetTextScaleHeight(Scale, (int)Font);
#elif RAGEMP
return 1080 * Invoker.Invoke<float>(Natives.GetTextScaleHeight, Scale, (int)Font);
#elif RPH
return 1080 * NativeFunction.CallByHash<float>(0xDB88A37483346780, Scale, (int)Font);
#elif SHVDN3
return 1080 * Function.Call<float>(Hash.GET_RENDERED_CHARACTER_HEIGHT, Scale, (int)Font);
#endif
}
}
#endregion
#region Constructors
/// <summary>
/// Creates a text with the specified options.
/// </summary>
/// <param name="pos">The position where the text should be located.</param>
/// <param name="text">The text to show.</param>
public ScaledText(PointF pos, string text) : this(pos, text, 1f, Font.ChaletLondon)
{
}
/// <summary>
/// Creates a text with the specified options.
/// </summary>
/// <param name="pos">The position where the text should be located.</param>
/// <param name="text">The text to show.</param>
/// <param name="scale">The scale of the text.</param>
public ScaledText(PointF pos, string text, float scale) : this(pos, text, scale, Font.ChaletLondon)
{
}
/// <summary>
/// Creates a text with the specified options
/// </summary>
/// <param name="pos">The position where the text should be located.</param>
/// <param name="text">The text to show.</param>
/// <param name="scale">The scale of the text.</param>
/// <param name="font">The font to use.</param>
public ScaledText(PointF pos, string text, float scale, Font font)
{
Position = pos;
Text = text;
Scale = scale;
Font = font;
}
#endregion
#region Tools
/// <summary>
/// Adds the text information for measurement.
/// </summary>
private void Add()
{
if (Scale == 0)
{
return;
}
#if FIVEM
foreach (string chunk in chunks)
{
API.AddTextComponentString(chunk);
}
API.SetTextFont((int)Font);
API.SetTextScale(1f, Scale);
API.SetTextColour(Color.R, Color.G, Color.B, Color.A);
API.SetTextJustification((int)Alignment);
if (Shadow)
{
API.SetTextDropShadow();
}
if (Outline)
{
API.SetTextOutline();
}
if (WordWrap > 0)
{
switch (Alignment)
{
case Alignment.Center:
API.SetTextWrap(relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
break;
case Alignment.Left:
API.SetTextWrap(relativePosition.X, relativePosition.X + realWrap);
break;
case Alignment.Right:
API.SetTextWrap(relativePosition.X - realWrap, relativePosition.X);
break;
}
}
else if (Alignment == Alignment.Right)
{
API.SetTextWrap(0f, relativePosition.X);
}
#elif RAGEMP
foreach (string chunk in chunks)
{
Invoker.Invoke(Natives.AddTextComponentSubstringPlayerName, chunk);
}
Invoker.Invoke(Natives.SetTextFont, (int)Font);
Invoker.Invoke(Natives.SetTextScale, 1f, Scale);
Invoker.Invoke(Natives.SetTextColour, Color.R, Color.G, Color.B, Color.A);
Invoker.Invoke(Natives.SetTextJustification, (int)Alignment);
if (Shadow)
{
Invoker.Invoke(Natives.SetTextDropShadow);
}
if (Outline)
{
Invoker.Invoke(Natives.SetTextOutline);
}
if (WordWrap > 0)
{
switch (Alignment)
{
case Alignment.Center:
Invoker.Invoke(Natives.SetTextWrap, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
break;
case Alignment.Left:
Invoker.Invoke(Natives.SetTextWrap, relativePosition.X, relativePosition.X + realWrap);
break;
case Alignment.Right:
Invoker.Invoke(Natives.SetTextWrap, relativePosition.X - realWrap, relativePosition.X);
break;
}
}
else if (Alignment == Alignment.Right)
{
Invoker.Invoke(0x63145D9C883A1A70, 0f, relativePosition.X);
}
#elif RPH
foreach (string chunk in chunks)
{
NativeFunction.CallByHash<int>(0x6C188BE134E074AA, chunk);
}
NativeFunction.CallByHash<int>(0x66E0276CC5F6B9DA, (int)Font);
NativeFunction.CallByHash<int>(0x07C837F9A01C34C9, 1f, Scale);
NativeFunction.CallByHash<int>(0xBE6B23FFA53FB442, Color.R, Color.G, Color.B, Color.A);
NativeFunction.CallByHash<int>(0x4E096588B13FFECA, (int)Alignment);
if (Shadow)
{
NativeFunction.CallByHash<int>(0x1CA3E9EAC9D93E5E);
}
if (Outline)
{
NativeFunction.CallByHash<int>(0x2513DFB0FB8400FE);
}
if (WordWrap > 0)
{
switch (Alignment)
{
case Alignment.Center:
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
break;
case Alignment.Left:
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X, relativePosition.X + realWrap);
break;
case Alignment.Right:
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, relativePosition.X - realWrap, relativePosition.X);
break;
}
}
else if (Alignment == Alignment.Right)
{
NativeFunction.CallByHash<int>(0x63145D9C883A1A70, 0f, relativePosition.X);
}
#elif SHVDN3
foreach (string chunk in chunks)
{
Function.Call((Hash)0x6C188BE134E074AA, chunk); // _ADD_TEXT_COMPONENT_STRING on v2, ADD_TEXT_COMPONENT_SUBSTRING_PLAYER_NAME on v3
}
Function.Call(Hash.SET_TEXT_FONT, (int)Font);
Function.Call(Hash.SET_TEXT_SCALE, 1f, Scale);
Function.Call(Hash.SET_TEXT_COLOUR, Color.R, Color.G, Color.B, Color.A);
Function.Call(Hash.SET_TEXT_JUSTIFICATION, (int)Alignment);
if (Shadow)
{
Function.Call(Hash.SET_TEXT_DROP_SHADOW);
}
if (Outline)
{
Function.Call(Hash.SET_TEXT_OUTLINE);
}
if (WordWrap > 0)
{
switch (Alignment)
{
case Alignment.Center:
Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X - (realWrap * 0.5f), relativePosition.X + (realWrap * 0.5f));
break;
case Alignment.Left:
Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X, relativePosition.X + realWrap);
break;
case Alignment.Right:
Function.Call(Hash.SET_TEXT_WRAP, relativePosition.X - realWrap, relativePosition.X);
break;
}
}
else if (Alignment == Alignment.Right)
{
Function.Call(Hash.SET_TEXT_WRAP, 0f, relativePosition.X);
}
#endif
}
/// <summary>
/// Slices the string of text into appropiately saved chunks.
/// </summary>
private void Slice()
{
// If the entire text is under 90 bytes, save it as is and return
if (Encoding.UTF8.GetByteCount(text) <= chunkSize)
{
chunks.Clear();
chunks.Add(text);
return;
}
// Create a new list of chunks and a temporary string
List<string> newChunks = new List<string>();
string temp = string.Empty;
// Iterate over the characters in the string
foreach (char character in text)
{
// Create a temporary string with the character
string with = string.Concat(temp, character);
// If this string is higher than 90 bytes, add the existing string onto the list
if (Encoding.UTF8.GetByteCount(with) > chunkSize)
{
newChunks.Add(temp);
temp = character.ToString();
continue;
}
// And save the new string generated
temp = with;
}
// If after finishing we still have a piece, save it
if (temp != string.Empty)
{
newChunks.Add(temp);
}
// Once we have finished, replace the old chunks
chunks = newChunks;
}
/// <summary>
/// Recalculates the size, position and word wrap of this item.
/// </summary>
public void Recalculate()
{
// Do the normal Size and Position recalculation
relativePosition = absolutePosition.ToRelative();
// And recalculate the word wrap if necessary
if (internalWrap <= 0)
{
realWrap = 0;
}
else
{
realWrap = internalWrap.ToXRelative();
}
}
#endregion
#region Public Functions
/// <summary>
/// Draws the text on the screen.
/// </summary>
public void Draw()
{
#if FIVEM
API.SetTextEntry("CELL_EMAIL_BCON");
#elif RAGEMP
Invoker.Invoke(Natives.BeginTextCommandDisplayText, "CELL_EMAIL_BCON");
#elif RPH
NativeFunction.CallByHash<int>(0x25FBB336DF1804CB, "CELL_EMAIL_BCON");
#elif SHVDN3
Function.Call((Hash)0x25FBB336DF1804CB, "CELL_EMAIL_BCON"); // _SET_TEXT_ENTRY on v2, BEGIN_TEXT_COMMAND_DISPLAY_TEXT on v3
#endif
Add();
#if FIVEM
API.DrawText(relativePosition.X, relativePosition.Y);
#elif RAGEMP
Invoker.Invoke(Natives.DrawDebugText, relativePosition.X, relativePosition.Y);
#elif RPH
NativeFunction.CallByHash<int>(0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y);
#elif SHVDN3
Function.Call((Hash)0xCD015E5BB0D96A57, relativePosition.X, relativePosition.Y); // _DRAW_TEXT on v2, END_TEXT_COMMAND_DISPLAY_TEXT on v3
#endif
}
#endregion
}
}

View File

@ -1,144 +0,0 @@
#if FIVEM
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage.Native;
#elif SHVDN3
using GTA.Native;
#endif
using System.Drawing;
namespace LemonUI.Elements
{
/// <summary>
/// A 2D game texture.
/// </summary>
public class ScaledTexture : BaseElement
{
#region Public Properties
/// <summary>
/// The dictionary where the texture is loaded.
/// </summary>
public string Dictionary { get; set; }
/// <summary>
/// The texture to draw from the dictionary.
/// </summary>
public string Texture { get; set; }
#endregion
#region Constructors
/// <summary>
/// Creates a new <see cref="ScaledTexture"/> with a Position and Size of Zero.
/// </summary>
/// <param name="dictionary">The dictionary where the texture is located.</param>
/// <param name="texture">The texture to draw.</param>
public ScaledTexture(string dictionary, string texture) : this(PointF.Empty, SizeF.Empty, dictionary, texture)
{
}
/// <summary>
/// Creates a new <see cref="ScaledTexture"/> with a Position and Size of zero.
/// </summary>
/// <param name="pos">The position of the Texture.</param>
/// <param name="size">The size of the Texture.</param>
/// <param name="dictionary">The dictionary where the texture is located.</param>
/// <param name="texture">The texture to draw.</param>
public ScaledTexture(PointF pos, SizeF size, string dictionary, string texture) : base(pos, size)
{
Dictionary = dictionary;
Texture = texture;
Request();
}
#endregion
#region Private Functions
/// <summary>
/// Requests the texture dictionary for this class.
/// </summary>
private void Request()
{
#if FIVEM
if (!API.HasStreamedTextureDictLoaded(Dictionary))
{
API.RequestStreamedTextureDict(Dictionary, true);
}
#elif RAGEMP
if (!Invoker.Invoke<bool>(Natives.HasStreamedTextureDictLoaded, Dictionary))
{
Invoker.Invoke(Natives.RequestStreamedTextureDict, Dictionary, true);
}
#elif RPH
if (!NativeFunction.CallByHash<bool>(0x0145F696AAAAD2E4, Dictionary))
{
NativeFunction.CallByHash<int>(0xDFA2EF8E04127DD5, Dictionary, true);
}
#elif SHVDN3
if (!Function.Call<bool>(Hash.HAS_STREAMED_TEXTURE_DICT_LOADED, Dictionary))
{
Function.Call(Hash.REQUEST_STREAMED_TEXTURE_DICT, Dictionary, true);
}
#endif
}
#endregion
#region Public Functions
/// <summary>
/// Draws the texture on the screen.
/// </summary>
public override void Draw()
{
if (Size == SizeF.Empty)
{
return;
}
Request();
#if FIVEM
API.DrawSprite(Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
#elif RAGEMP
Invoker.Invoke(Natives.DrawSprite, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
#elif RPH
NativeFunction.CallByHash<int>(0xE7FFAE5EBF23D890, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
#elif SHVDN3
Function.Call(Hash.DRAW_SPRITE, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, Heading, Color.R, Color.G, Color.B, Color.A);
#endif
}
/// <summary>
/// Draws a specific part of the texture on the screen.
/// </summary>
public void DrawSpecific(PointF topLeft, PointF bottomRight)
{
if (Size == SizeF.Empty)
{
return;
}
Request();
#if FIVEM
API.DrawSpriteUv(Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
#elif RAGEMP
Invoker.Invoke(0x95812F9B26074726, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
#elif RPH
NativeFunction.CallByHash<int>(0x95812F9B26074726, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
#elif SHVDN3
Function.Call((Hash)0x95812F9B26074726, Dictionary, Texture, relativePosition.X, relativePosition.Y, relativeSize.Width, relativeSize.Height, topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y, Heading, Color.R, Color.G, Color.B, Color.A);
#endif
}
/// <summary>
/// Recalculates the position based on the size.
/// </summary>
public override void Recalculate()
{
base.Recalculate();
relativePosition.X += relativeSize.Width * 0.5f;
relativePosition.Y += relativeSize.Height * 0.5f;
}
#endregion
}
}

View File

@ -1,49 +0,0 @@
namespace LemonUI.Extensions
{
/// <summary>
/// Extensions for the float class.
/// </summary>
public static class FloatExtensions
{
/// <summary>
/// Converts an absolute X or Width float to a relative one.
/// </summary>
/// <param name="fin">The float to convert.</param>
/// <returns>A relative float between 0 and 1.</returns>
public static float ToXRelative(this float fin)
{
Screen.ToRelative(fin, 0, out float fout, out _);
return fout;
}
/// <summary>
/// Converts an absolute Y or Height float to a relative one.
/// </summary>
/// <param name="fin">The float to convert.</param>
/// <returns>A relative float between 0 and 1.</returns>
public static float ToYRelative(this float fin)
{
Screen.ToRelative(0, fin, out _, out float fout);
return fout;
}
/// <summary>
/// Converts an relative X or Width float to an absolute one.
/// </summary>
/// <param name="fin">The float to convert.</param>
/// <returns>An absolute float.</returns>
public static float ToXAbsolute(this float fin)
{
Screen.ToAbsolute(fin, 0, out float fout, out _);
return fout;
}
/// <summary>
/// Converts an relative Y or Height float to an absolute one.
/// </summary>
/// <param name="fin">The float to convert.</param>
/// <returns>An absolute float.</returns>
public static float ToYAbsolute(this float fin)
{
Screen.ToAbsolute(0, fin, out _, out float fout);
return fout;
}
}
}

View File

@ -1,31 +0,0 @@
using System.Drawing;
namespace LemonUI.Extensions
{
/// <summary>
/// Extensions for the Point and PointF classes.
/// </summary>
public static class PointExtensions
{
/// <summary>
/// Converts an absolute 1080-based position into a relative one.
/// </summary>
/// <param name="point">The absolute PointF.</param>
/// <returns>A new PointF with relative values.</returns>
public static PointF ToRelative(this PointF point)
{
Screen.ToRelative(point.X, point.Y, out float x, out float y);
return new PointF(x, y);
}
/// <summary>
/// Converts a normalized 0-1 position into an absolute one.
/// </summary>
/// <param name="point">The relative PointF.</param>
/// <returns>A new PointF with absolute values.</returns>
public static PointF ToAbsolute(this PointF point)
{
Screen.ToAbsolute(point.X, point.Y, out float x, out float y);
return new PointF(x, y);
}
}
}

View File

@ -1,31 +0,0 @@
using System.Drawing;
namespace LemonUI.Extensions
{
/// <summary>
/// Extensions for the Size and SizeF classes.
/// </summary>
public static class SizeExtensions
{
/// <summary>
/// Converts an absolute 1080-based size into a relative one.
/// </summary>
/// <param name="size">The absolute SizeF.</param>
/// <returns>A new SizeF with relative values.</returns>
public static SizeF ToRelative(this SizeF size)
{
Screen.ToRelative(size.Width, size.Height, out float width, out float height);
return new SizeF(width, height);
}
/// <summary>
/// Converts a normalized 0-1 size into an absolute one.
/// </summary>
/// <param name="size">The relative SizeF.</param>
/// <returns>A new SizeF with absolute values.</returns>
public static SizeF ToAbsolute(this SizeF size)
{
Screen.ToAbsolute(size.Width, size.Height, out float width, out float height);
return new SizeF(width, height);
}
}
}

View File

@ -1,36 +0,0 @@
using System;
namespace LemonUI
{
/// <summary>
/// Represents a container that can hold other UI Elements.
/// </summary>
public interface IContainer<T> : IRecalculable, IProcessable
{
/// <summary>
/// Adds the specified item into the Container.
/// </summary>
/// <param name="item">The item to add.</param>
void Add(T item);
/// <summary>
/// Removes the item from the container.
/// </summary>
/// <param name="item">The item to remove.</param>
void Remove(T item);
/// <summary>
/// Removes all of the items that match the function.
/// </summary>
/// <param name="func">The function to check items.</param>
void Remove(Func<T, bool> func);
/// <summary>
/// Clears all of the items in the container.
/// </summary>
void Clear();
/// <summary>
/// Checks if the item is part of the container.
/// </summary>
/// <param name="item">The item to check.</param>
/// <returns><see langword="true"/> if the item is in this container, <see langword="false"/> otherwise.</returns>
bool Contains(T item);
}
}

View File

@ -1,13 +0,0 @@
namespace LemonUI
{
/// <summary>
/// Represents an item that can be drawn.
/// </summary>
public interface IDrawable
{
/// <summary>
/// Draws the item on the screen.
/// </summary>
void Draw();
}
}

View File

@ -1,17 +0,0 @@
namespace LemonUI
{
/// <summary>
/// Interface for items that can be processed in an Object Pool.
/// </summary>
public interface IProcessable
{
/// <summary>
/// If this processable item is visible on the screen.
/// </summary>
bool Visible { get; set; }
/// <summary>
/// Processes the object.
/// </summary>
void Process();
}
}

View File

@ -1,13 +0,0 @@
namespace LemonUI
{
/// <summary>
/// Interface for classes that have values that need to be recalculated on resolution changes.
/// </summary>
public interface IRecalculable
{
/// <summary>
/// Recalculates the values.
/// </summary>
void Recalculate();
}
}

View File

@ -1,67 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents a badge that can be applied to a <see cref="NativeItem"/>.
/// </summary>
public class BadgeSet
{
#region Properties
/// <summary>
/// The texture dictionary where the normal texture is located.
/// </summary>
public string NormalDictionary { get; set; } = string.Empty;
/// <summary>
/// The texture to use when the item is not hovered.
/// </summary>
public string NormalTexture { get; set; } = string.Empty;
/// <summary>
/// The texture dictionary where the normal texture is located.
/// </summary>
public string HoveredDictionary { get; set; } = string.Empty;
/// <summary>
/// The texture to use when the item is hovered.
/// </summary>
public string HoveredTexture { get; set; } = string.Empty;
#endregion
#region Constructor
/// <summary>
/// Creates a new empty <see cref="BadgeSet"/>.
/// </summary>
public BadgeSet()
{
}
/// <summary>
/// Creates a new <see cref="BadgeSet"/> where both textures are in the same dictionary.
/// </summary>
/// <param name="dict">The dictionary where the textures are located.</param>
/// <param name="normal">The normal texture name.</param>
/// <param name="hovered">The hovered texture name.</param>
public BadgeSet(string dict, string normal, string hovered)
{
NormalDictionary = dict;
NormalTexture = normal;
HoveredDictionary = dict;
HoveredTexture = hovered;
}
/// <summary>
/// Creates a new <see cref="BadgeSet"/> where both textures are in different dictionaries.
/// </summary>
/// <param name="normalDict">The dictionary where the normal texture is located.</param>
/// <param name="normalTexture">The normal texture name.</param>
/// <param name="hoveredDict">The dictionary where the hovered texture is located.</param>
/// <param name="hoveredTexture">The hovered texture name.</param>
public BadgeSet(string normalDict, string normalTexture, string hoveredDict, string hoveredTexture)
{
NormalDictionary = normalDict;
NormalTexture = normalTexture;
HoveredDictionary = hoveredDict;
HoveredTexture = hoveredTexture;
}
#endregion
}
}

View File

@ -1,101 +0,0 @@
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Stores the different colors required to make the colors of a <see cref="NativeItem"/> dynamic.
/// </summary>
public class ColorSet
{
#region Fields
private static readonly Color colorWhite = Color.FromArgb(255, 255, 255, 255);
private static readonly Color colorWhiteSmoke = Color.FromArgb(255, 245, 245, 245);
private static readonly Color colorBlack = Color.FromArgb(255, 0, 0, 0);
private static readonly Color colorDisabled = Color.FromArgb(255, 163, 159, 148);
#endregion
#region Properties
/// <summary>
/// The color of the <see cref="NativeItem.Title"/> when the <see cref="NativeItem"/> is not hovered and enabled.
/// </summary>
public Color TitleNormal { get; set; } = colorWhiteSmoke;
/// <summary>
/// The color of the <see cref="NativeItem.Title"/> when the <see cref="NativeItem"/> is hovered.
/// </summary>
public Color TitleHovered { get; set; } = colorBlack;
/// <summary>
/// The color of the <see cref="NativeItem.Title"/> when the <see cref="NativeItem"/> is disabled.
/// </summary>
public Color TitleDisabled { get; set; } = colorDisabled;
/// <summary>
/// The color of the <see cref="NativeItem.AltTitle"/> when the <see cref="NativeItem"/> is not hovered and enabled.
/// </summary>
public Color AltTitleNormal { get; set; } = colorWhiteSmoke;
/// <summary>
/// The color of the <see cref="NativeItem.AltTitle"/> when the <see cref="NativeItem"/> is hovered.
/// </summary>
public Color AltTitleHovered { get; set; } = colorBlack;
/// <summary>
/// The color of the <see cref="NativeItem.AltTitle"/> when the <see cref="NativeItem"/> is disabled.
/// </summary>
public Color AltTitleDisabled { get; set; } = colorDisabled;
/// <summary>
/// The color of the <see cref="NativeSlidableItem"/> arrows when the item is not hovered and enabled.
/// </summary>
public Color ArrowsNormal { get; set; } = colorWhiteSmoke;
/// <summary>
/// The color of the <see cref="NativeSlidableItem"/> arrows when the item is hovered.
/// </summary>
public Color ArrowsHovered { get; set; } = colorBlack;
/// <summary>
/// The color of the <see cref="NativeSlidableItem"/> arrows when the item is disabled.
/// </summary>
public Color ArrowsDisabled { get; set; } = colorDisabled;
/// <summary>
/// The color of the <see cref="NativeItem.LeftBadge"/> when the <see cref="NativeItem"/> is not hovered and enabled.
/// </summary>
public Color BadgeLeftNormal { get; set; } = colorWhite;
/// <summary>
/// The color of the <see cref="NativeItem.LeftBadge"/> when the <see cref="NativeItem"/> is hovered.
/// </summary>
public Color BadgeLeftHovered { get; set; } = colorWhite;
/// <summary>
/// The color of the <see cref="NativeItem.LeftBadge"/> when the <see cref="NativeItem"/> is disabled.
/// </summary>
public Color BadgeLeftDisabled { get; set; } = colorWhite;
/// <summary>
/// The color of the <see cref="NativeItem.RightBadge"/> or <see cref="NativeCheckboxItem"/> checkbox when the <see cref="NativeItem"/> is not hovered and enabled.
/// </summary>
public Color BadgeRightNormal { get; set; } = colorWhite;
/// <summary>
/// The color of the <see cref="NativeItem.RightBadge"/> or <see cref="NativeCheckboxItem"/> checkbox when the <see cref="NativeItem"/> is hovered.
/// </summary>
public Color BadgeRightHovered { get; set; } = colorWhite;
/// <summary>
/// The color of the <see cref="NativeItem.RightBadge"/> or <see cref="NativeCheckboxItem"/> checkbox when the <see cref="NativeItem"/> is disabled.
/// </summary>
public Color BadgeRightDisabled { get; set; } = colorWhite;
/// <summary>
/// The normal color of the custom background if <see cref="NativeItem.UseCustomBackground"/> is set to <see langword="true"/>.
/// </summary>
public Color BackgroundNormal { get; set; } = colorWhite;
/// <summary>
/// The hovered color of the custom background if <see cref="NativeItem.UseCustomBackground"/> is set to <see langword="true"/>.
/// </summary>
public Color BackgroundHovered { get; set; } = colorWhite;
/// <summary>
/// The disabled color of the custom background if <see cref="NativeItem.UseCustomBackground"/> is set to <see langword="true"/>.
/// </summary>
public Color BackgroundDisabled { get; set; } = colorWhite;
#endregion
}
}

View File

@ -1,22 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// The Style of title for the Color Panel.
/// </summary>
public enum ColorTitleStyle
{
/// <summary>
/// Does not shows any Title.
/// The count will still be shown if <see cref="NativeColorPanel.ShowCount"/> is set to <see langword="true"/>.
/// </summary>
None = -1,
/// <summary>
/// Shows a Simple Title for all of the Colors.
/// </summary>
Simple = 0,
/// <summary>
/// Shows the Color Name as the Title.
/// </summary>
ColorName = 1
}
}

View File

@ -1,21 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// The visibility setting for the Item Count of the Menu.
/// </summary>
public enum CountVisibility
{
/// <summary>
/// The Item Count is never shown.
/// </summary>
Never = -1,
/// <summary>
/// The Item Count is shown when is not possible to show all of the items in the screen.
/// </summary>
Auto = 0,
/// <summary>
/// The Item Count is always shown.
/// </summary>
Always = 1
}
}

View File

@ -1,21 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// The movement direction of the item change.
/// </summary>
public enum Direction
{
/// <summary>
/// The Direction is Unknown.
/// </summary>
Unknown = 0,
/// <summary>
/// The item was moved to the Left.
/// </summary>
Left = 1,
/// <summary>
/// The item was moved to the Right.
/// </summary>
Right = 2,
}
}

View File

@ -1,21 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// The style of the Grid Panel.
/// </summary>
public enum GridStyle
{
/// <summary>
/// The full grid with X and Y values.
/// </summary>
Full = 0,
/// <summary>
/// A single row on the center with the X value only.
/// </summary>
Row = 1,
/// <summary>
/// A single column on the center with the Y value only.
/// </summary>
Column = 2,
}
}

View File

@ -1,25 +0,0 @@
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Represents the Previous and Current X and Y values when changing the position on a grid.
/// </summary>
public class GridValueChangedArgs
{
/// <summary>
/// The values present before they were changed.
/// </summary>
public PointF Before { get; }
/// <summary>
/// The values present after they were changed.
/// </summary>
public PointF After { get; }
internal GridValueChangedArgs(PointF before, PointF after)
{
Before = before;
After = after;
}
}
}

View File

@ -1,9 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the method that is called when the value on a grid is changed.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="ItemActivatedArgs"/> with the item information.</param>
public delegate void GridValueChangedEventHandler(object sender, GridValueChangedArgs e);
}

View File

@ -1,18 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the arguments of an item activation.
/// </summary>
public class ItemActivatedArgs
{
/// <summary>
/// The item that was just activated.
/// </summary>
public NativeItem Item { get; }
internal ItemActivatedArgs(NativeItem item)
{
Item = item;
}
}
}

View File

@ -1,9 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the method that is called when an item is activated on a menu.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="ItemActivatedArgs"/> with the item information.</param>
public delegate void ItemActivatedEventHandler(object sender, ItemActivatedArgs e);
}

View File

@ -1,29 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the change of the selection of an item.
/// </summary>
/// <typeparam name="T">The type of object that got changed.</typeparam>
public class ItemChangedEventArgs<T>
{
/// <summary>
/// The new object.
/// </summary>
public T Object { get; set; }
/// <summary>
/// The index of the object.
/// </summary>
public int Index { get; }
/// <summary>
/// The direction of the Item Changed event.
/// </summary>
public Direction Direction { get; }
internal ItemChangedEventArgs(T obj, int index, Direction direction)
{
Object = obj;
Index = index;
Direction = direction;
}
}
}

View File

@ -1,10 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the method that is called when the selected item is changed on a List Item.
/// </summary>
/// <typeparam name="T">The type of item that was changed.</typeparam>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="ItemChangedEventArgs{T}"/> with the information of the selected item.</param>
public delegate void ItemChangedEventHandler<T>(object sender, ItemChangedEventArgs<T> e);
}

View File

@ -1,17 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// The operation performed when the menu items are modified.
/// </summary>
public enum ItemOperation
{
/// <summary>
/// The item has been removed.
/// </summary>
Removed = -1,
/// <summary>
/// The item has been added.
/// </summary>
Added = 0,
}
}

View File

@ -1,36 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the different
/// </summary>
public class MenuModifiedEventArgs
{
#region Properties
/// <summary>
/// The item that was modified.
/// </summary>
public NativeItem Item { get; }
/// <summary>
/// The operation that was performed in the item.
/// </summary>
public ItemOperation Operation { get; }
#endregion
#region Constructors
/// <summary>
/// Creates a new <see cref="MenuModifiedEventArgs"/>.
/// </summary>
/// <param name="item">The item that was modified.</param>
/// <param name="operation">The operation that was performed in the item.</param>
public MenuModifiedEventArgs(NativeItem item, ItemOperation operation)
{
Item = item;
Operation = operation;
}
#endregion
}
}

View File

@ -1,9 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the method that is called when the items on a menu are changed (added or removed).
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="MenuModifiedEventArgs"/> with the menu operation.</param>
public delegate void MenuModifiedEventHandler(object sender, MenuModifiedEventArgs e);
}

View File

@ -1,174 +0,0 @@
using LemonUI.Elements;
using System;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Rockstar-like checkbox item.
/// </summary>
public class NativeCheckboxItem : NativeItem
{
#region Fields
/// <summary>
/// The image shown on the checkbox.
/// </summary>
internal protected ScaledTexture check = new ScaledTexture(PointF.Empty, SizeF.Empty, "commonmenu", string.Empty);
/// <summary>
/// If this item is checked or not.
/// </summary>
private bool checked_ = false;
#endregion
#region Properties
/// <summary>
/// If this item is checked or not.
/// </summary>
public bool Checked
{
get => checked_;
set
{
if (checked_ == value)
{
return;
}
checked_ = value;
UpdateTexture(lastSelected);
CheckboxChanged?.Invoke(this, EventArgs.Empty);
}
}
#endregion
#region Events
/// <summary>
/// Event triggered when the checkbox changes.
/// </summary>
public event EventHandler CheckboxChanged;
#endregion
#region Constructor
/// <summary>
/// Creates a new <see cref="NativeCheckboxItem"/>.
/// </summary>
/// <param name="title">The title used for the Item.</param>
public NativeCheckboxItem(string title) : this(title, string.Empty, false)
{
}
/// <summary>
/// Creates a new <see cref="NativeCheckboxItem"/>.
/// </summary>
/// <param name="title">The title used for the Item.</param>
/// <param name="check">If the checkbox should be enabled or not.</param>
public NativeCheckboxItem(string title, bool check) : this(title, string.Empty, check)
{
}
/// <summary>
/// Creates a new <see cref="NativeCheckboxItem"/>.
/// </summary>
/// <param name="title">The title used for the Item.</param>
/// <param name="description">The description of the Item.</param>
public NativeCheckboxItem(string title, string description) : this(title, description, false)
{
}
/// <summary>
/// Creates a new <see cref="NativeCheckboxItem"/>.
/// </summary>
/// <param name="title">The title used for the Item.</param>
/// <param name="description">The description of the Item.</param>
/// <param name="check">If the checkbox should be enabled or not.</param>
public NativeCheckboxItem(string title, string description, bool check) : base(title, description)
{
Checked = check;
Activated += Toggle;
EnabledChanged += NativeCheckboxItem_EnabledChanged;
}
#endregion
#region Local Events
private void NativeCheckboxItem_EnabledChanged(object sender, EventArgs e) => UpdateTexture(lastSelected);
#endregion
#region Internal Functions
/// <summary>
/// Inverts the checkbox activation.
/// </summary>
private void Toggle(object sender, EventArgs e) => Checked = !Checked;
/// <summary>
/// Updates the texture of the sprite.
/// </summary>
internal protected void UpdateTexture(bool selected)
{
// If the item is not selected or is not enabled, use the white pictures
if (!selected || !Enabled)
{
check.Texture = Checked ? "shop_box_tick" : "shop_box_blank";
}
// Otherwise, use the black ones
else
{
check.Texture = Checked ? "shop_box_tickb" : "shop_box_blankb";
}
}
#endregion
#region Public Functions
/// <summary>
/// Recalculates the item positions and sizes with the specified values.
/// </summary>
/// <param name="pos">The position of the item.</param>
/// <param name="size">The size of the item.</param>
/// <param name="selected">If this item has been selected.</param>
public override void Recalculate(PointF pos, SizeF size, bool selected)
{
base.Recalculate(pos, size, selected);
// Set the correct texture
UpdateTexture(selected);
// And set the checkbox positions
check.Position = new PointF(pos.X + size.Width - 50, pos.Y - 6);
check.Size = new SizeF(50, 50);
}
/// <summary>
/// Draws the Checkbox on the screen.
/// </summary>
public override void Draw()
{
title.Draw();
badgeLeft?.Draw();
check.Draw();
}
/// <inheritdoc/>
public override void UpdateColors()
{
base.UpdateColors();
if (!Enabled)
{
check.Color = Colors.BadgeRightDisabled;
}
else if (lastSelected)
{
check.Color = Colors.BadgeRightHovered;
}
else
{
check.Color = Colors.BadgeRightNormal;
}
}
#endregion
}
}

View File

@ -1,49 +0,0 @@
using System.Drawing;
using LemonUI.Elements;
namespace LemonUI.Menus
{
/// <summary>
/// Represents the Color Information shown on the Panel.
/// </summary>
public class NativeColorData
{
#region Internal Fields
internal readonly ScaledRectangle rectangle = new ScaledRectangle(PointF.Empty, SizeF.Empty);
#endregion
#region Public Properties
/// <summary>
/// The name of the color.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The RGBA values of the color.
/// </summary>
public Color Color
{
get => rectangle.Color;
set => rectangle.Color = value;
}
#endregion
#region Constructor
/// <summary>
/// Creates a new Color Panel information.
/// </summary>
/// <param name="name">The name of the color.</param>
/// <param name="color">The RGBA values of the color.</param>
public NativeColorData(string name, Color color)
{
Name = name;
rectangle.Color = color;
}
#endregion
}
}

View File

@ -1,678 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.UI;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Control = Rage.GameControl;
#elif SHVDN3
using GTA;
using GTA.UI;
#endif
using LemonUI.Elements;
using System;
using System.Collections.Generic;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// A Panel that allows you to select a Color.
/// </summary>
public class NativeColorPanel : NativePanel
{
#region Constants
/// <summary>
/// The space difference for the colors and opacity bar on the left.
/// </summary>
private const float leftDifference = 16;
/// <summary>
/// The space difference for the colors and opacity bar on the left.
/// </summary>
private const float rightDifference = 12;
#endregion
#region Private Fields
/// <summary>
/// The position reported after the last Recalculation.
/// </summary>
private PointF lastPosition = PointF.Empty;
/// <summary>
/// The Width reported after the last Recalculation.
/// </summary>
private float lastWidth = 0;
/// <summary>
/// The title of the Color Panel.
/// </summary>
private ScaledText title = new ScaledText(PointF.Empty, string.Empty, 0.325f)
{
Alignment = Alignment.Center
};
/// <summary>
/// The rectangle used for marking the item selection on the screen.
/// </summary>
private ScaledRectangle selectionRectangle = new ScaledRectangle(PointF.Empty, SizeF.Empty);
/// <summary>
/// The "Opacity" text when the opacity bar is enabled
/// </summary>
private ScaledText opacityText = new ScaledText(PointF.Empty, "Opacity", 0.325f)
{
Alignment = Alignment.Center
};
/// <summary>
/// The zero percent text when the opacity bar is enabled.
/// </summary>
private ScaledText percentMin = new ScaledText(PointF.Empty, "0%", 0.325f);
/// <summary>
/// The 100 percent text when the opacity bar is enabled.
/// </summary>
private ScaledText percentMax = new ScaledText(PointF.Empty, "100%", 0.325f);
/// <summary>
/// The top section of the opacity bar.
/// </summary>
private ScaledRectangle opacityForeground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(255, 240, 240, 240)
};
/// <summary>
/// The background of the opacity bar.
/// </summary>
private ScaledRectangle opacityBackground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(150, 88, 88, 88)
};
/// <summary>
/// If the opacity bar is available to the user.
/// </summary>
private bool showOpacity = false;
/// <summary>
/// The current value of the opacity slider.
/// </summary>
private int opacity = 0;
/// <summary>
/// The current index of the Colors.
/// </summary>
private int index = 0;
/// <summary>
/// The position of the first item.
/// </summary>
private int firstItem = 0;
/// <summary>
/// The maximum number of items shown at once.
/// </summary>
private int maxItems = 9;
/// <summary>
/// The generic title for this color.
/// </summary>
private string simpleTitle = "Color";
/// <summary>
/// The style of the title.
/// </summary>
private ColorTitleStyle titleStyle = ColorTitleStyle.Simple;
/// <summary>
/// If the number of colors should be shown.
/// </summary>
private bool showCount = true;
/// <summary>
/// The items that are currently visible on the screen.
/// </summary>
private List<NativeColorData> visibleItems = new List<NativeColorData>();
#endregion
#region Public Fields
/// <summary>
/// The default sound used for the Color Navigation.
/// </summary>
public static readonly Sound DefaultSound = new Sound("HUD_FRONTEND_DEFAULT_SOUNDSET", "NAV_LEFT_RIGHT");
#endregion
#region Public Properties
/// <inheritdoc/>
public override bool Clickable => true;
/// <summary>
/// If the Opacity selector should be shown.
/// </summary>
public bool ShowOpacity
{
get => showOpacity;
set
{
showOpacity = value;
Recalculate();
}
}
/// <summary>
/// The opacity value of the color.
/// </summary>
/// <remarks>
/// The value needs to be set between 100 and 0.
/// It will return -1 if <see cref="ShowOpacity"/> is set to <see langword="false"/>.
/// </remarks>
public int Opacity
{
get
{
if (!ShowOpacity)
{
return -1;
}
return opacity;
}
set
{
if (!ShowOpacity)
{
return;
}
if (value > 100 || value < 0)
{
throw new IndexOutOfRangeException("The value needs to be over 0 and under 100.");
}
opacity = value;
UpdateOpacityBar();
}
}
/// <summary>
/// The currently selected color.
/// </summary>
/// <remarks>
/// If <see cref="ShowOpacity"/> is set to <see langword="true"/>.
/// </remarks>
public Color SelectedColor
{
get
{
// If there is no selected color information, return
NativeColorData data = SelectedItem;
if (data == null)
{
return default;
}
// Otherwise, return the color
return Color.FromArgb(ShowOpacity ? (int)(255 * (Opacity * 0.01f)) : 255, data.Color.R, data.Color.G, data.Color.B);
}
}
/// <summary>
/// Returns the currently selected <see cref="NativeColorData"/>.
/// </summary>
public NativeColorData SelectedItem
{
get
{
if (Colors.Count == 0 || index >= Colors.Count)
{
return null;
}
return Colors[SelectedIndex];
}
}
/// <summary>
/// The index of the currently selected Color.
/// </summary>
public int SelectedIndex
{
get
{
if (Colors.Count == 0 || index >= Colors.Count)
{
return -1;
}
return index;
}
set
{
// If the list of items is empty, don't allow the user to set the index
if (Colors == null || Colors.Count == 0)
{
throw new InvalidOperationException("There are no items in this menu.");
}
// If the value is over or equal than the number of items, raise an exception
else if (value >= Colors.Count)
{
throw new InvalidOperationException($"The index is over {Colors.Count - 1}");
}
// Calculate the bounds of the menu
int lower = firstItem;
int upper = firstItem + maxItems;
// Time to set the first item based on the total number of items
// If the item is between the allowed values, do nothing because we are on the correct first item
if (value >= lower && value < upper - 1)
{
}
// If the upper bound + 1 equals the new index, increase it by one
else if (upper == value)
{
firstItem += 1;
}
// If the first item minus one equals the value, decrease it by one
else if (lower - 1 == value)
{
firstItem -= 1;
}
// Otherwise, set it somewhere
else
{
// If the value is under the max items, set it to zero
if (value < maxItems)
{
firstItem = 0;
}
// Otherwise, set it at the bottom
else
{
firstItem = value - maxItems + 1;
}
}
// Save the index and update the items
index = value;
UpdateItems();
// Finally, play the switch change sound
Sound?.PlayFrontend();
}
}
/// <summary>
/// The Title used for the Panel when <see cref="TitleStyle"/> is set to <see cref="ColorTitleStyle.Simple"/>.
/// </summary>
public string Title
{
get => simpleTitle;
set
{
simpleTitle = value;
UpdateTitle();
}
}
/// <summary>
/// The style of the Panel Title.
/// </summary>
public ColorTitleStyle TitleStyle
{
get => titleStyle;
set
{
titleStyle = value;
UpdateTitle();
}
}
/// <summary>
/// If the count of items should be shown as part of the title.
/// </summary>
public bool ShowCount
{
get => showCount;
set
{
showCount = value;
UpdateTitle();
}
}
/// <summary>
/// THe maximum number of items shown on the screen.
/// </summary>
public int MaxItems
{
get => maxItems;
set
{
if (value == maxItems)
{
return;
}
maxItems = value;
UpdateItems();
UpdateTitle();
}
}
/// <summary>
/// The colors shown on this Panel.
/// </summary>
public List<NativeColorData> Colors { get; } = new List<NativeColorData>();
/// <summary>
/// The sound played when the item is changed.
/// </summary>
public Sound Sound { get; set; } = DefaultSound;
#endregion
#region Constructors
/// <summary>
/// Creates a color panel with no Items or Title.
/// </summary>
public NativeColorPanel() : this(string.Empty)
{
}
/// <summary>
/// Creates a Panel with a specific Title and set of Colors.
/// </summary>
/// <param name="title">The title of the panel.</param>
/// <param name="colors">The colors of the panel.</param>
public NativeColorPanel(string title, params NativeColorData[] colors)
{
// Set the title of the Panel
Title = title;
// Add the colors that we got
Colors.AddRange(colors);
}
#endregion
#region Private Functions
/// <summary>
/// Updates the Text of the Title.
/// </summary>
private void UpdateTitle()
{
string newTitle = string.Empty;
// Add the title based on the correct style
switch (titleStyle)
{
case ColorTitleStyle.Simple:
newTitle = Title;
break;
case ColorTitleStyle.ColorName:
newTitle = SelectedItem == null ? string.Empty : SelectedItem.Name;
break;
}
// If we need to add the count of colors
if (ShowCount)
{
// Add a space at the end if required
if (!newTitle.EndsWith(" "))
{
newTitle += " ";
}
// And add the item count
newTitle += $"({SelectedIndex + 1} of {Colors.Count})";
}
// And finally set the new title
title.Text = newTitle;
}
/// <summary>
/// Updates the position of the Items.
/// </summary>
private void UpdateItems()
{
// See UpdateItemList() on LemonUI.Menus.NativeMenu to understand this section
List<NativeColorData> list = new List<NativeColorData>();
for (int i = 0; i < MaxItems; i++)
{
int start = firstItem + i;
if (start >= Colors.Count)
{
break;
}
list.Add(Colors[start]);
}
visibleItems = list;
// Get the width based on the maximum number of items
float width = (lastWidth - leftDifference - rightDifference) / maxItems;
// And store the number of items already completed
int count = 0;
// Select the correct extra distance based on the prescence of the Opacity toggle
float extra = ShowOpacity ? 78 : 0;
// Then, start iterating over the colors visible on the screen
foreach (NativeColorData color in visibleItems)
{
// Set the position based on the number of items completed
color.rectangle.Position = new PointF(lastPosition.X + leftDifference + (width * count), lastPosition.Y + extra + 54);
// And set the size of it based on the number of items
color.rectangle.Size = new SizeF(width, 45);
// Finally, increase the count by one
count++;
}
// If there is a selected color item
if (SelectedItem != null)
{
// Set the position and size of the selection rectangle based on the currently selected color
ScaledRectangle colorRect = SelectedItem.rectangle;
const float height = 8;
selectionRectangle.Position = new PointF(colorRect.Position.X, colorRect.Position.Y - height);
selectionRectangle.Size = new SizeF(colorRect.Size.Width, height);
}
// Finally, update the text of the title
UpdateTitle();
}
/// <summary>
/// Updates the size of the opacity bar.
/// </summary>
private void UpdateOpacityBar()
{
// If the opacity bar is disabled, return
if (!ShowOpacity)
{
return;
}
// Otherwise, set the size based in the last known position
float x = lastPosition.X + 13;
float y = lastPosition.Y + 48;
float width = lastWidth - leftDifference - rightDifference;
const float height = 9;
opacityBackground.Position = new PointF(x, y);
opacityBackground.Size = new SizeF(width, height);
opacityForeground.Position = new PointF(x, y);
opacityForeground.Size = new SizeF(width * (Opacity * 0.01f), height);
}
/// <summary>
/// Recalculates the Color panel with the last known Position and Width.
/// </summary>
private void Recalculate() => Recalculate(lastPosition, lastWidth);
#endregion
#region Public Functions
/// <summary>
/// Moves to the Previous Color.
/// </summary>
public void Previous()
{
// If there are no items, return
if (Colors.Count == 0)
{
return;
}
// If we are on the first item, go back to the last one
if (SelectedIndex == 0)
{
SelectedIndex = Colors.Count - 1;
}
// Otherwise, reduce it by one
else
{
SelectedIndex -= 1;
}
}
/// <summary>
/// Moves to the Next Color.
/// </summary>
public void Next()
{
// If there are no items, return
if (Colors.Count == 0)
{
return;
}
// If we are on the last item, go back to the first one
if (Colors.Count - 1 == SelectedIndex)
{
SelectedIndex = 0;
}
// Otherwise, increase it by one
else
{
SelectedIndex += 1;
}
}
/// <summary>
/// Adds a color to the Panel.
/// </summary>
/// <param name="color">The color to add.</param>
public void Add(NativeColorData color)
{
if (Colors.Contains(color))
{
throw new ArgumentException("Color is already part of the Panel.", nameof(color));
}
Colors.Add(color);
Recalculate();
}
/// <summary>
/// Removes a color from the panel.
/// </summary>
/// <param name="color">The color to remove.</param>
public void Remove(NativeColorData color)
{
// Remove it if there
// If not, ignore it
Colors.Remove(color);
// If the index is higher or equal than the max number of items
// Set the max - 1
if (SelectedIndex >= Colors.Count)
{
SelectedIndex = Colors.Count - 1;
}
else
{
UpdateItems();
}
}
/// <summary>
/// Removes all of the
/// </summary>
/// <param name="func"></param>
public void Remove(Func<NativeColorData, bool> func)
{
foreach (NativeColorData color in new List<NativeColorData>(Colors))
{
if (func(color))
{
Colors.Remove(color);
}
}
Recalculate();
}
/// <summary>
/// Removes all of the colors from the Panel.
/// </summary>
public void Clear()
{
Colors.Clear();
Recalculate();
}
/// <summary>
/// Checks if the Color Data is present on this Panel.
/// </summary>
/// <param name="color">The Color Data to check.</param>
public void Contains(NativeColorData color) => Colors.Contains(color);
/// <summary>
/// Recalculates the position of the Color Panel.
/// </summary>
/// <param name="position">The position of the panel.</param>
/// <param name="width">The width of the menu.</param>
public override void Recalculate(PointF position, float width)
{
// Save the last position and width
lastPosition = position;
lastWidth = width;
// Select the correct extra distance based on the prescence of the Opacity toggle
float extra = ShowOpacity ? 78 : 0;
// Set the position and size of the Background
Background.Position = position;
Background.Size = new SizeF(width, ShowOpacity ? 188 : 111);
// And then set the position of the text
title.Position = new PointF(position.X + (width * 0.5f), position.Y + extra + 10f);
// Then, set the position of the opacity bar and texts
UpdateOpacityBar();
opacityText.Position = new PointF(position.X + (width * 0.5f), position.Y + 10f);
percentMin.Position = new PointF(position.X + 9, position.Y + 11);
percentMax.Position = new PointF(position.X + width - 60, position.Y + 11);
// Finally, update the list of items
UpdateItems();
}
/// <summary>
/// Draws the Color Panel.
/// </summary>
public override void Process()
{
// If the user pressed one of the keys, move to the left or right
if (Controls.IsJustPressed(Control.FrontendLt))
{
Previous();
}
else if (Controls.IsJustPressed(Control.FrontendRt))
{
Next();
}
// If the user pressed one of the bumpers with the Opacity bar enabled, increase or decrease it
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendLb))
{
if (Opacity > 0)
{
Opacity--;
Sound?.PlayFrontend();
}
}
else if (ShowOpacity && Controls.IsJustPressed(Control.FrontendRb))
{
if (Opacity < 100)
{
Opacity++;
Sound?.PlayFrontend();
}
}
// Draw the items
base.Process();
title.Draw();
foreach (NativeColorData color in visibleItems)
{
color.rectangle.Draw();
}
if (Colors.Count != 0)
{
selectionRectangle.Draw();
}
if (ShowOpacity)
{
opacityText.Draw();
percentMin.Draw();
percentMax.Draw();
opacityBackground.Draw();
opacityForeground.Draw();
}
}
#endregion
}
}

View File

@ -1,163 +0,0 @@
using LemonUI.Elements;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Dynamic Items allow you to dynamically change the item shown to the user.
/// </summary>
/// <typeparam name="T">The type of item.</typeparam>
public class NativeDynamicItem<T> : NativeSlidableItem
{
#region Fields
private readonly ScaledText text = new ScaledText(PointF.Empty, string.Empty, 0.35f);
private T item = default;
#endregion
#region Properties
/// <summary>
/// The currently selected item.
/// </summary>
public T SelectedItem
{
get => item;
set
{
item = value;
}
}
#endregion
#region Events
/// <summary>
/// Event triggered when the user has changed the item.
/// </summary>
public event ItemChangedEventHandler<T> ItemChanged;
#endregion
#region Constructor
/// <summary>
/// Creates a new Dynamic List Item.
/// </summary>
/// <param name="title">The Title of the item.</param>
public NativeDynamicItem(string title) : this(title, string.Empty, default)
{
}
/// <summary>
/// Creates a new Dynamic List Item.
/// </summary>
/// <param name="title">The Title of the item.</param>
/// <param name="item">The Item to set.</param>
public NativeDynamicItem(string title, T item) : this(title, string.Empty, item)
{
}
/// <summary>
/// Creates a new Dynamic List Item.
/// </summary>
/// <param name="title">The Title of the item.</param>
/// <param name="description">The Description of the item.</param>
public NativeDynamicItem(string title, string description) : this(title, description, default)
{
}
/// <summary>
/// Creates a new Dynamic List Item.
/// </summary>
/// <param name="title">The Title of the item.</param>
/// <param name="description">The Description of the item.</param>
/// <param name="item">The Item to set.</param>
public NativeDynamicItem(string title, string description, T item) : base(title, description)
{
this.item = item;
}
#endregion
#region Functions
/// <summary>
/// Updates the currently selected item based on the index.
/// </summary>
private void UpdateItemName()
{
// This is the SAME as the normal NativeListItem
text.Text = !SelectedItem.Equals(default) ? SelectedItem.ToString() : string.Empty;
text.Position = new PointF(RightArrow.Position.X - text.Width + 3, text.Position.Y);
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, LeftArrow.Position.Y);
}
/// <summary>
/// Gets the previous item.
/// </summary>
public override void GoLeft()
{
ItemChangedEventArgs<T> arguments = new ItemChangedEventArgs<T>(item, -1, Direction.Left);
ItemChanged?.Invoke(this, arguments);
SelectedItem = arguments.Object;
UpdateItemName();
}
/// <summary>
/// Gets the next item.
/// </summary>
public override void GoRight()
{
ItemChangedEventArgs<T> arguments = new ItemChangedEventArgs<T>(item, -1, Direction.Right);
ItemChanged?.Invoke(this, arguments);
SelectedItem = arguments.Object;
UpdateItemName();
}
/// <summary>
/// Recalculates the position of the current List Item.
/// </summary>
/// <param name="pos">The new position of the item.</param>
/// <param name="size">The Size of the item.</param>
/// <param name="selected">If the item is selected or not.</param>
public override void Recalculate(PointF pos, SizeF size, bool selected)
{
// This is the SAME as the normal NativeListItem
base.Recalculate(pos, size, selected);
float textWidth = RightArrow.Size.Width;
text.Position = new PointF(pos.X + size.Width - textWidth - 1 - text.Width, pos.Y + 3);
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, pos.Y + 4);
UpdateItemName();
}
/// <summary>
/// Draws the List on the screen.
/// </summary>
public override void Draw()
{
base.Draw(); // Arrows, Title and Left Badge
text.Draw();
}
/// <inheritdoc/>
public override void UpdateColors()
{
base.UpdateColors();
if (!Enabled)
{
text.Color = Colors.TitleDisabled;
}
else if (lastSelected)
{
text.Color = Colors.TitleHovered;
}
else
{
text.Color = Colors.TitleNormal;
}
}
#endregion
}
}

View File

@ -1,371 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.Native;
using CitizenFX.Core.UI;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage;
using Rage.Native;
using Control = Rage.GameControl;
#elif SHVDN3
using GTA;
using GTA.Native;
using GTA.UI;
#endif
using LemonUI.Elements;
using LemonUI.Extensions;
using System;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Represents a grid where you can select X and Y values.
/// </summary>
public class NativeGridPanel : NativePanel
{
#region Fields
private PointF position = PointF.Empty;
private float width = 0;
private readonly ScaledText labelTop = new ScaledText(PointF.Empty, "Y+", 0.33f)
{
Alignment = Alignment.Center
};
private readonly ScaledText labelBottom = new ScaledText(PointF.Empty, "Y-", 0.33f)
{
Alignment = Alignment.Center
};
private readonly ScaledText labelLeft = new ScaledText(PointF.Empty, "X-", 0.33f)
{
Alignment = Alignment.Right
};
private readonly ScaledText labelRight = new ScaledText(PointF.Empty, "X+", 0.33f);
private readonly ScaledTexture grid = new ScaledTexture("pause_menu_pages_char_mom_dad", "nose_grid")
{
Color = Color.FromArgb(205, 105, 105, 102)
};
private readonly ScaledTexture dot = new ScaledTexture("commonmenu", "common_medal")
{
Color = Color.FromArgb(255, 255, 255, 255)
};
private PointF innerPosition = PointF.Empty;
private SizeF innerSize = SizeF.Empty;
private GridStyle style = GridStyle.Full;
private float x;
private float y;
#endregion
#region Properties
/// <inheritdoc/>
public override bool Clickable => true;
/// <summary>
/// The X value between 0 and 1.
/// </summary>
public float X
{
get
{
switch (style)
{
case GridStyle.Full:
case GridStyle.Row:
return x;
default:
return 0.5f;
}
}
set
{
if (value > 1 || value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
if (style == GridStyle.Column)
{
return;
}
PointF before = new PointF(X, Y);
x = value;
UpdateDot(before);
}
}
/// <summary>
/// The X value between 0 and 1.
/// </summary>
public float Y
{
get
{
switch (style)
{
case GridStyle.Full:
case GridStyle.Column:
return y;
default:
return 0.5f;
}
}
set
{
if (value > 1 || value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
if (style == GridStyle.Row)
{
return;
}
PointF before = new PointF(X, Y);
y = value;
UpdateDot(before);
}
}
/// <summary>
/// The text label shown on the top.
/// </summary>
public string LabelTop
{
get => labelTop.Text;
set => labelTop.Text = value;
}
/// <summary>
/// The text label shown on the bottom.
/// </summary>
public string LabelBottom
{
get => labelBottom.Text;
set => labelBottom.Text = value;
}
/// <summary>
/// The text label shown on the left.
/// </summary>
public string LabelLeft
{
get => labelLeft.Text;
set => labelLeft.Text = value;
}
/// <summary>
/// The text label shown on the right.
/// </summary>
public string LabelRight
{
get => labelRight.Text;
set => labelRight.Text = value;
}
/// <summary>
/// The style of this grid.
/// </summary>
public GridStyle Style
{
get => style;
set
{
if (!Enum.IsDefined(typeof(GridStyle), value))
{
throw new ArgumentOutOfRangeException(nameof(value), "The Grid style is not valid! Expected Full, Row or Column.");
}
style = value;
Recalculate();
}
}
#endregion
#region Events
/// <summary>
/// Event triggered when X and/or Y values are changed.
/// </summary>
public event GridValueChangedEventHandler ValuesChanged;
#endregion
#region Constructor
/// <summary>
/// Creates a new <see cref="NativeGridPanel"/>.
/// </summary>
public NativeGridPanel() : base()
{
}
#endregion
#region Functions
private void Recalculate() => Recalculate(position, width);
private void UpdateDot(PointF before, bool trigger = true)
{
float posX = innerSize.Width * (style == GridStyle.Full || style == GridStyle.Row ? x : 0.5f);
float posY = innerSize.Height * (style == GridStyle.Full || style == GridStyle.Column ? y : 0.5f);
dot.Size = new SizeF(45, 45);
dot.Position = new PointF(innerPosition.X + posX - (dot.Size.Width * 0.5f),
innerPosition.Y + posY - (dot.Size.Height * 0.5f));
if (trigger)
{
ValuesChanged?.Invoke(this, new GridValueChangedArgs(before, new PointF(X, Y)));
}
}
/// <inheritdoc/>
public override void Recalculate(PointF position, float width)
{
this.position = position;
this.width = width;
const float height = 270;
const int offsetX = 20;
const int offsetY = 20;
base.Recalculate(position, width);
Background.Size = new SizeF(width, height);
switch (style)
{
case GridStyle.Full:
grid.Position = new PointF(position.X + (width * 0.5f) - 95, position.Y + (height * 0.5f) - 94);
grid.Size = new SizeF(192, 192);
break;
case GridStyle.Row:
grid.Position = new PointF(position.X + (width * 0.5f) - 95, position.Y + (height * 0.5f) - 15);
grid.Size = new SizeF(192, 36);
break;
case GridStyle.Column:
grid.Position = new PointF(position.X + (width * 0.5f) - 17, position.Y + (height * 0.5f) - 94);
grid.Size = new SizeF(36, 192);
break;
}
labelTop.Position = new PointF(position.X + (width * 0.5f), position.Y + 10);
labelBottom.Position = new PointF(position.X + (width * 0.5f), position.Y + height - 34);
labelLeft.Position = new PointF(position.X + (width * 0.5f) - 102, position.Y + (height * 0.5f) - (labelLeft.LineHeight * 0.5f));
labelRight.Position = new PointF(position.X + (width * 0.5f) + 102, position.Y + (height * 0.5f) - (labelLeft.LineHeight * 0.5f));
innerPosition = new PointF(grid.Position.X + offsetX, grid.Position.Y + offsetY);
innerSize = new SizeF(grid.Size.Width - (offsetX * 2), grid.Size.Height - (offsetY * 2));
UpdateDot(PointF.Empty, false);
}
/// <inheritdoc/>
public override void Process()
{
float previousX = X;
float previousY = Y;
Background.Draw();
switch (style)
{
case GridStyle.Full:
labelTop.Draw();
labelBottom.Draw();
labelLeft.Draw();
labelRight.Draw();
grid.Draw();
break;
case GridStyle.Row:
labelLeft.Draw();
labelRight.Draw();
grid.DrawSpecific(new PointF(0, 0.4f), new PointF(1, 0.6f));
break;
case GridStyle.Column:
labelTop.Draw();
labelBottom.Draw();
grid.DrawSpecific(new PointF(0.4f, 0), new PointF(0.6f, 1));
break;
}
dot.Draw();
#if FIVEM
bool usingKeyboard = API.IsInputDisabled(2);
#elif RAGEMP
bool usingKeyboard = Invoker.Invoke<bool>(0xA571D46727E2B718, 2);
#elif RPH
bool usingKeyboard = NativeFunction.CallByHash<bool>(0xA571D46727E2B718, 2);
#elif SHVDN3
bool usingKeyboard = Function.Call<bool>(Hash._IS_USING_KEYBOARD, 2);
#endif
if (usingKeyboard)
{
if (Screen.IsCursorInArea(grid.Position, grid.Size) && Controls.IsPressed(Control.CursorAccept))
{
PointF cursor = Screen.CursorPositionRelative;
PointF pos = innerPosition.ToRelative();
PointF start = new PointF(cursor.X - pos.X, cursor.Y - pos.Y);
SizeF size = innerSize.ToRelative();
x = start.X / size.Width;
y = start.Y / size.Height;
}
else
{
return;
}
}
else
{
Controls.DisableThisFrame(Control.LookUpDown);
Controls.DisableThisFrame(Control.LookLeftRight);
Controls.EnableThisFrame(Control.ScriptRightAxisX);
Controls.EnableThisFrame(Control.ScriptRightAxisY);
#if FIVEM
float rX = Game.GetControlNormal(0, Control.ScriptRightAxisX);
float rY = Game.GetControlNormal(0, Control.ScriptRightAxisY);
float frameTime = Game.LastFrameTime;
#elif RAGEMP
float rX = Invoker.Invoke<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisX);
float rY = Invoker.Invoke<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisY);
float frameTime = Invoker.Invoke<float>(Natives.GetFrameTime);
#elif RPH
float rX = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisX);
float rY = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.ScriptRightAxisY);
float frameTime = Game.FrameTime;
#elif SHVDN3
float rX = Game.GetControlValueNormalized(Control.ScriptRightAxisX);
float rY = Game.GetControlValueNormalized(Control.ScriptRightAxisY);
float frameTime = Game.LastFrameTime;
#endif
x += rX * frameTime;
y += rY * frameTime;
}
// Make sure that the values are not under zero or over one
if (x < 0)
{
x = 0;
}
else if (x > 1)
{
x = 1;
}
if (y < 0)
{
y = 0;
}
else if (y > 1)
{
y = 1;
}
if (previousX != x || previousY != y)
{
UpdateDot(new PointF(previousX, previousX));
}
}
#endregion
}
}

View File

@ -1,398 +0,0 @@
#if FIVEM
using Font = CitizenFX.Core.UI.Font;
#elif RAGEMP
using Font = RAGE.Game.Font;
#elif RPH
using Font = LemonUI.Elements.Font;
#elif SHVDN3
using Font = GTA.UI.Font;
#endif
using LemonUI.Elements;
using System;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Basic Rockstar-like item.
/// </summary>
public class NativeItem : IDrawable
{
#region Protected Internal Fields
/// <summary>
/// The title of the object.
/// </summary>
protected internal ScaledText title = null;
/// <summary>
/// The last known Item Position.
/// </summary>
protected internal PointF lastPosition = PointF.Empty;
/// <summary>
/// The last known Item Size.
/// </summary>
protected internal SizeF lastSize = SizeF.Empty;
/// <summary>
/// The last known Item Selection.
/// </summary>
protected internal bool lastSelected = false;
/// <summary>
/// The left badge of the Item.
/// </summary>
protected internal I2Dimensional badgeLeft = null;
/// <summary>
/// The left badge of the Item.
/// </summary>
protected internal I2Dimensional badgeRight = null;
/// <summary>
/// The alternate title of the menu.
/// </summary>
protected internal ScaledText altTitle = null;
#endregion
#region Private Fields
private bool enabled = true;
private BadgeSet badgeSetLeft = null;
private BadgeSet badgeSetRight = null;
private ColorSet colors = new ColorSet();
private ScaledRectangle background = new ScaledRectangle(PointF.Empty, SizeF.Empty);
#endregion
#region Public Properties
/// <summary>
/// If this item can be used or not.
/// </summary>
public bool Enabled
{
get => enabled;
set
{
if (enabled == value)
{
return;
}
enabled = value;
EnabledChanged?.Invoke(this, EventArgs.Empty);
UpdateColors();
}
}
/// <summary>
/// Object that contains data about this Item.
/// </summary>
public virtual object Tag { get; set; }
/// <summary>
/// The title of the item.
/// </summary>
public string Title
{
get => title.Text;
set => title.Text = value;
}
/// <summary>
/// The alternative title of the item shown on the right.
/// </summary>
public string AltTitle
{
get => altTitle.Text;
set
{
altTitle.Text = value;
Recalculate();
}
}
/// <summary>
/// The font of title item.
/// </summary>
public Font TitleFont
{
get => title.Font;
set => title.Font = value;
}
/// <summary>
/// The font of alternative title item shown on the right.
/// </summary>
public Font AltTitleFont
{
get => altTitle.Font;
set => altTitle.Font = value;
}
/// <summary>
/// The description of the item.
/// </summary>
public string Description { get; set; }
/// <summary>
/// The Left badge of the Item.
/// </summary>
public I2Dimensional LeftBadge
{
get => badgeLeft;
set
{
badgeLeft = value;
Recalculate();
UpdateColors();
}
}
/// <summary>
/// The Left badge set of the Item.
/// </summary>
public BadgeSet LeftBadgeSet
{
get => badgeSetLeft;
set
{
badgeSetLeft = value;
Recalculate();
UpdateColors();
}
}
/// <summary>
/// The Right badge of the Item.
/// </summary>
public I2Dimensional RightBadge
{
get => badgeRight;
set
{
badgeRight = value;
Recalculate();
UpdateColors();
}
}
/// <summary>
/// The Right badge set of the Item.
/// </summary>
public BadgeSet RightBadgeSet
{
get => badgeSetRight;
set
{
badgeSetRight = value;
Recalculate();
UpdateColors();
}
}
/// <summary>
/// The different colors that change dynamically when the item is used.
/// </summary>
public ColorSet Colors
{
get => colors;
set
{
colors = value;
UpdateColors();
}
}
/// <summary>
/// The Panel asociated to this <see cref="NativeItem"/>.
/// </summary>
public NativePanel Panel { get; set; } = null;
/// <summary>
/// If a custom colored background should be used.
/// </summary>
public bool UseCustomBackground { get; set; }
/// <summary>
/// If this item is being hovered.
/// </summary>
public bool IsHovered => Screen.IsCursorInArea(background.Position, background.Size);
#endregion
#region Events
/// <summary>
/// Event triggered when the item is selected.
/// </summary>
public event SelectedEventHandler Selected;
/// <summary>
/// Event triggered when the item is activated.
/// </summary>
public event EventHandler Activated;
/// <summary>
/// Event triggered when the <see cref="Enabled"/> property is changed.
/// </summary>
public event EventHandler EnabledChanged;
#endregion
#region Constructors
/// <summary>
/// Creates a new <see cref="NativeItem"/>.
/// </summary>
/// <param name="title">The title of the item.</param>
public NativeItem(string title) : this(title, string.Empty, string.Empty)
{
}
/// <summary>
/// Creates a new <see cref="NativeItem"/>.
/// </summary>
/// <param name="title">The title of the item.</param>
/// <param name="description">The description of the item.</param>
public NativeItem(string title, string description) : this(title, description, string.Empty)
{
}
/// <summary>
/// Creates a new <see cref="NativeItem"/>.
/// </summary>
/// <param name="title">The title of the item.</param>
/// <param name="description">The description of the item.</param>
/// <param name="altTitle">The alternative title of the item, shown on the right.</param>
public NativeItem(string title, string description, string altTitle)
{
this.title = new ScaledText(PointF.Empty, title, 0.345f);
Description = description;
this.altTitle = new ScaledText(PointF.Empty, altTitle, 0.345f);
}
#endregion
#region Event Triggers
/// <summary>
/// Triggers the Selected event.
/// </summary>
protected internal void OnSelected(object sender, SelectedEventArgs e) => Selected?.Invoke(sender, e);
/// <summary>
/// Triggers the Activated event.
/// </summary>
protected internal void OnActivated(object sender) => Activated?.Invoke(sender, EventArgs.Empty);
#endregion
#region Private Functions
/// <summary>
/// Recalculates the item with the last known values.
/// </summary>
protected void Recalculate() => Recalculate(lastPosition, lastSize, lastSelected);
#endregion
#region Public Functions
/// <summary>
/// Recalculates the item positions and sizes with the specified values.
/// </summary>
/// <param name="pos">The position of the item.</param>
/// <param name="size">The size of the item.</param>
/// <param name="selected">If this item has been selected.</param>
public virtual void Recalculate(PointF pos, SizeF size, bool selected)
{
lastPosition = pos;
lastSize = size;
lastSelected = selected;
background.Position = pos;
background.Size = size;
if (badgeSetLeft != null)
{
if (!(badgeLeft is ScaledTexture))
{
badgeLeft = new ScaledTexture(string.Empty, string.Empty);
}
ScaledTexture left = (ScaledTexture)badgeLeft;
left.Dictionary = selected ? badgeSetLeft.HoveredDictionary : badgeSetLeft.NormalDictionary;
left.Texture = selected ? badgeSetLeft.HoveredTexture : badgeSetLeft.NormalTexture;
}
if (badgeSetRight != null)
{
if (!(badgeRight is ScaledTexture))
{
badgeRight = new ScaledTexture(string.Empty, string.Empty);
}
ScaledTexture right = (ScaledTexture)badgeRight;
right.Dictionary = selected ? badgeSetRight.HoveredDictionary : badgeSetRight.NormalDictionary;
right.Texture = selected ? badgeSetRight.HoveredTexture : badgeSetRight.NormalTexture;
}
if (badgeLeft != null)
{
badgeLeft.Position = new PointF(pos.X + 2, pos.Y - 3);
badgeLeft.Size = new SizeF(45, 45);
}
if (badgeRight != null)
{
badgeRight.Position = new PointF(pos.X + size.Width - 47, pos.Y - 3);
badgeRight.Size = new SizeF(45, 45);
}
title.Position = new PointF(pos.X + (badgeLeft == null ? 0 : 34) + 6, pos.Y + 3);
altTitle.Position = new PointF(pos.X + size.Width - (badgeRight == null ? 0 : 34) - altTitle.Width - 6, pos.Y + 3);
UpdateColors();
}
/// <summary>
/// Draws the item.
/// </summary>
public virtual void Draw()
{
if (UseCustomBackground)
{
background.Draw();
}
title.Draw();
altTitle.Draw();
badgeLeft?.Draw();
badgeRight?.Draw();
}
/// <summary>
/// Updates the colors of the <see cref="Elements"/> from the <see cref="Colors"/> <see cref="ColorSet"/>.
/// </summary>
public virtual void UpdateColors()
{
if (!Enabled)
{
background.Color = Colors.BackgroundDisabled;
title.Color = Colors.TitleDisabled;
altTitle.Color = Colors.AltTitleDisabled;
if (badgeLeft != null)
{
badgeLeft.Color = Colors.BadgeLeftDisabled;
}
if (badgeRight != null)
{
badgeRight.Color = Colors.BadgeRightDisabled;
}
}
else if (lastSelected)
{
background.Color = Colors.BackgroundHovered;
title.Color = Colors.TitleHovered;
altTitle.Color = Colors.AltTitleHovered;
if (badgeLeft != null)
{
badgeLeft.Color = Colors.BadgeLeftHovered;
}
if (badgeRight != null)
{
badgeRight.Color = Colors.BadgeRightHovered;
}
}
else
{
background.Color = Colors.BackgroundNormal;
title.Color = Colors.TitleNormal;
altTitle.Color = Colors.AltTitleNormal;
if (badgeLeft != null)
{
badgeLeft.Color = Colors.BadgeLeftNormal;
}
if (badgeRight != null)
{
badgeRight.Color = Colors.BadgeRightNormal;
}
}
}
#endregion
}
}

View File

@ -1,366 +0,0 @@
using LemonUI.Elements;
using System;
using System.Collections.Generic;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Base class for list items.
/// </summary>
public abstract class NativeListItem : NativeSlidableItem
{
/// <summary>
/// The text of the current item.
/// </summary>
internal protected ScaledText text = null;
/// <summary>
/// Creates a new list item with a title and subtitle.
/// </summary>
/// <param name="title">The title of the Item.</param>
/// <param name="subtitle">The subtitle of the Item.</param>
public NativeListItem(string title, string subtitle) : base(title, subtitle)
{
text = new ScaledText(PointF.Empty, string.Empty, 0.35f);
}
}
/// <summary>
/// An item that allows you to scroll between a set of objects.
/// </summary>
public class NativeListItem<T> : NativeListItem
{
#region Fields
private int index = 0;
private List<T> items = new List<T>();
#endregion
#region Properties
/// <summary>
/// The index of the currently selected index.
/// </summary>
public int SelectedIndex
{
get
{
if (Items.Count == 0)
{
return -1;
}
return index;
}
set
{
if (Items.Count == 0)
{
throw new InvalidOperationException("There are no available items.");
}
if (value < 0)
{
throw new InvalidOperationException("The index is under zero.");
}
if (value >= Items.Count)
{
throw new InvalidOperationException($"The index is over the limit of {Items.Count - 1}");
}
if (index == value)
{
return;
}
index = value;
TriggerEvent(value, Direction.Unknown);
UpdateIndex();
}
}
/// <summary>
/// The currently selected item.
/// </summary>
public T SelectedItem
{
get
{
if (Items.Count == 0)
{
return default;
}
return Items[SelectedIndex];
}
set
{
if (Items.Count == 0)
{
throw new InvalidOperationException("There are no available items.");
}
int newIndex = Items.IndexOf(value);
if (newIndex == -1)
{
throw new InvalidOperationException("The object is not the list of Items.");
}
SelectedIndex = newIndex;
}
}
/// <summary>
/// The objects used by this item.
/// </summary>
public List<T> Items
{
get => items;
set
{
items = value;
UpdateIndex();
}
}
#endregion
#region Events
/// <summary>
/// Event triggered when the selected item is changed.
/// </summary>
public event ItemChangedEventHandler<T> ItemChanged;
#endregion
#region Constructors
/// <summary>
/// Creates a new <see cref="NativeListItem"/>.
/// </summary>
/// <param name="title">The title of the Item.</param>
/// <param name="objs">The objects that are available on the Item.</param>
public NativeListItem(string title, params T[] objs) : this(title, string.Empty, objs)
{
}
/// <summary>
/// Creates a new <see cref="NativeListItem"/>.
/// </summary>
/// <param name="title">The title of the Item.</param>
/// <param name="subtitle">The subtitle of the Item.</param>
/// <param name="objs">The objects that are available on the Item.</param>
public NativeListItem(string title, string subtitle, params T[] objs) : base(title, subtitle)
{
items = new List<T>();
items.AddRange(objs);
UpdateIndex();
}
#endregion
#region Tools
/// <summary>
/// Triggers the <seealso cref="ItemChangedEventHandler{T}"/> event.
/// </summary>
private void TriggerEvent(int index, Direction direction)
{
ItemChanged?.Invoke(this, new ItemChangedEventArgs<T>(items[index], index, direction));
}
private void FixIndexIfRequired()
{
if (index >= items.Count)
{
index = items.Count - 1;
UpdateIndex();
}
}
/// <summary>
/// Updates the currently selected item based on the index.
/// </summary>
private void UpdateIndex()
{
text.Text = SelectedIndex != -1 ? SelectedItem.ToString() : string.Empty;
text.Position = new PointF(RightArrow.Position.X - text.Width + 3, text.Position.Y);
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, LeftArrow.Position.Y);
}
#endregion
#region Functions
/// <summary>
/// Adds a <typeparamref name="T" /> into this item.
/// </summary>
/// <param name="item">The <typeparamref name="T" /> to add.</param>
public void Add(T item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
if (items.Contains(item))
{
throw new InvalidOperationException("Item is already part of this NativeListItem.");
}
items.Add(item);
if (items.Count == 1)
{
UpdateIndex();
}
}
/// <summary>
/// Adds a <typeparamref name="T" /> in a specific location.
/// </summary>
/// <param name="position">The position where the item should be added.</param>
/// <param name="item">The <typeparamref name="T" /> to add.</param>
public void Add(int position, T item)
{
if (item == null)
{
throw new ArgumentNullException(nameof(item));
}
if (position < 0 || position > Items.Count)
{
throw new ArgumentOutOfRangeException(nameof(position), "The position is out of the range of items.");
}
Items.Insert(position, item);
FixIndexIfRequired();
}
/// <summary>
/// Removes a specific <typeparamref name="T" />.
/// </summary>
/// <param name="item">The <typeparamref name="T" /> to remove.</param>
public void Remove(T item)
{
if (items.Remove(item))
{
FixIndexIfRequired();
}
}
/// <summary>
/// Removes a <typeparamref name="T" /> at a specific location.
/// </summary>
/// <param name="position">The position of the <typeparamref name="T" />.</param>
public void RemoveAt(int position)
{
if (position >= items.Count)
{
return;
}
items.RemoveAt(position);
FixIndexIfRequired();
}
/// <summary>
/// Removes all of the items that match the <paramref name="pred"/>.
/// </summary>
/// <param name="pred">The function to use as a check.</param>
public void Remove(Func<T, bool> pred)
{
if (items.RemoveAll(pred.Invoke) > 0)
{
FixIndexIfRequired();
}
}
/// <summary>
/// Removes all of the <typeparamref name="T" /> from this item.
/// </summary>
public void Clear()
{
items.Clear();
UpdateIndex();
}
/// <summary>
/// Recalculates the item positions and sizes with the specified values.
/// </summary>
/// <param name="pos">The position of the item.</param>
/// <param name="size">The size of the item.</param>
/// <param name="selected">If this item has been selected.</param>
public override void Recalculate(PointF pos, SizeF size, bool selected)
{
base.Recalculate(pos, size, selected);
text.Position = new PointF(pos.X + size.Width - RightArrow.Size.Width - 1 - text.Width, pos.Y + 3);
LeftArrow.Position = new PointF(text.Position.X - LeftArrow.Size.Width, pos.Y + 4);
}
/// <summary>
/// Moves to the previous item.
/// </summary>
public override void GoLeft()
{
if (Items.Count == 0)
{
return;
}
if (index == 0)
{
index = Items.Count - 1;
}
else
{
index--;
}
TriggerEvent(index, Direction.Left);
UpdateIndex();
}
/// <summary>
/// Moves to the next item.
/// </summary>
public override void GoRight()
{
if (Items.Count == 0)
{
return;
}
if (index == Items.Count - 1)
{
index = 0;
}
else
{
index++;
}
TriggerEvent(index, Direction.Right);
UpdateIndex();
}
/// <summary>
/// Draws the List on the screen.
/// </summary>
public override void Draw()
{
base.Draw(); // Arrows, Title and Left Badge
text.Draw();
}
/// <inheritdoc/>
public override void UpdateColors()
{
base.UpdateColors();
if (!Enabled)
{
text.Color = Colors.TitleDisabled;
}
else if (lastSelected)
{
text.Color = Colors.TitleHovered;
}
else
{
text.Color = Colors.TitleNormal;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,49 +0,0 @@
using LemonUI.Elements;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Represents a panel shown under the description of the item description.
/// </summary>
public abstract class NativePanel
{
#region Public Properties
/// <summary>
/// If this panel is visible to the user.
/// </summary>
public virtual bool Visible { get; set; } = true;
/// <summary>
/// If the item has controls that can be clicked.
/// </summary>
public virtual bool Clickable { get; } = false;
/// <summary>
/// The Background of the panel itself.
/// </summary>
public ScaledTexture Background { get; } = new ScaledTexture("commonmenu", "gradient_bgd");
#endregion
#region Public Functions
/// <summary>
/// Recalculates the menu contents.
/// </summary>
/// <param name="position">The position of the panel.</param>
/// <param name="width">The width of the menu.</param>
public virtual void Recalculate(PointF position, float width)
{
Background.Position = position;
}
/// <summary>
/// Processes and Draws the panel.
/// </summary>
public virtual void Process()
{
Background.Draw();
}
#endregion
}
}

View File

@ -1,30 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// A Blank Separator Item for creating empty spaces between menu items.
/// </summary>
public class NativeSeparatorItem : NativeItem
{
#region Constructor
/// <summary>
/// Creates a new Menu Separator.
/// </summary>
public NativeSeparatorItem() : base(string.Empty, string.Empty, string.Empty)
{
}
#endregion
#region Public Functions
/// <summary>
/// Draws nothing.
/// </summary>
public override void Draw()
{
}
#endregion
}
}

View File

@ -1,143 +0,0 @@
using LemonUI.Elements;
using System;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Basic elements for a slidable item.
/// </summary>
public abstract class NativeSlidableItem : NativeItem
{
#region Private Fields
private bool alwaysVisible = false;
#endregion
#region Internal Fields
/// <summary>
/// The arrow pointing to the Left.
/// </summary>
[Obsolete("arrowLeft is Obsolete, use LeftArrow instead.")]
internal protected ScaledTexture arrowLeft = null;
/// <summary>
/// The arrow pointing to the Right.
/// </summary>
[Obsolete("arrowRight is Obsolete, use RightArrow instead.")]
internal protected ScaledTexture arrowRight = null;
#endregion
#region Public Properties
/// <summary>
/// The arrow pointing to the Left.
/// </summary>
public ScaledTexture LeftArrow { get; }
/// <summary>
/// The arrow pointing to the Right.
/// </summary>
public ScaledTexture RightArrow { get; }
/// <summary>
/// Whether the arrows should always be shown regardless of the visibility of the Item.
/// </summary>
public bool ArrowsAlwaysVisible
{
get => alwaysVisible;
set
{
alwaysVisible = value;
Recalculate();
}
}
#endregion
#region Constructors
/// <summary>
/// Creates a new item that can be sliden.
/// </summary>
/// <param name="title">The title of the Item.</param>
/// <param name="description">The description of the Item.</param>
public NativeSlidableItem(string title, string description) : base(title, description)
{
LeftArrow = new ScaledTexture(PointF.Empty, SizeF.Empty, "commonmenu", "arrowleft");
RightArrow = new ScaledTexture(PointF.Empty, SizeF.Empty, "commonmenu", "arrowright");
#pragma warning disable CS0618
arrowLeft = LeftArrow;
arrowRight = RightArrow;
#pragma warning restore CS0618
EnabledChanged += NativeSlidableItem_EnabledChanged;
}
#endregion
#region Local Events
private void NativeSlidableItem_EnabledChanged(object sender, EventArgs e) => Recalculate();
#endregion
#region Public Functions
/// <summary>
/// Recalculates the item positions and sizes with the specified values.
/// </summary>
/// <param name="pos">The position of the item.</param>
/// <param name="size">The size of the item.</param>
/// <param name="selected">If this item has been selected.</param>
public override void Recalculate(PointF pos, SizeF size, bool selected)
{
base.Recalculate(pos, size, selected);
LeftArrow.Size = (selected && Enabled) || ArrowsAlwaysVisible ? new SizeF(30, 30) : SizeF.Empty;
RightArrow.Size = (selected && Enabled) || ArrowsAlwaysVisible ? new SizeF(30, 30) : SizeF.Empty;
RightArrow.Position = new PointF(pos.X + size.Width - RightArrow.Size.Width - 5, pos.Y + 4);
}
/// <summary>
/// Moves to the previous item.
/// </summary>
public abstract void GoLeft();
/// <summary>
/// Moves to the next item.
/// </summary>
public abstract void GoRight();
/// <summary>
/// Draws the left and right arrow.
/// </summary>
public override void Draw()
{
title.Draw();
badgeLeft?.Draw();
LeftArrow.Draw();
RightArrow.Draw();
}
/// <inheritdoc/>
public override void UpdateColors()
{
base.UpdateColors();
if (!Enabled)
{
LeftArrow.Color = Colors.ArrowsDisabled;
RightArrow.Color = Colors.ArrowsDisabled;
}
else if (lastSelected)
{
LeftArrow.Color = Colors.ArrowsHovered;
RightArrow.Color = Colors.ArrowsHovered;
}
else
{
LeftArrow.Color = Colors.ArrowsNormal;
RightArrow.Color = Colors.ArrowsNormal;
}
}
#endregion
}
}

View File

@ -1,241 +0,0 @@
using LemonUI.Elements;
using System;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// A slider item for changing integer values.
/// </summary>
public class NativeSliderItem : NativeSlidableItem
{
#region Internal Fields
/// <summary>
/// The background of the slider.
/// </summary>
internal protected ScaledRectangle background = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(255, 4, 32, 57)
};
/// <summary>
/// THe front of the slider.
/// </summary>
internal protected ScaledRectangle slider = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(255, 57, 116, 200)
};
#endregion
#region Private Fields
/// <summary>
/// The maximum value of the slider.
/// </summary>
private int maximum = 0;
/// <summary>
/// The current value of the slider.
/// </summary>
private int _value = 100;
#endregion
#region Public Properties
/// <summary>
/// The color of the Slider.
/// </summary>
public Color SliderColor
{
get => slider.Color;
set => slider.Color = value;
}
/// <summary>
/// The maximum value of the slider.
/// </summary>
public int Maximum
{
get => maximum;
set
{
// If the value was not changed, return
if (maximum == value)
{
return;
}
// Otherwise, save the new value
maximum = value;
// If the current value is higher than the max, set the max
if (_value > maximum)
{
_value = maximum;
ValueChanged?.Invoke(this, EventArgs.Empty);
}
// Finally, update the location of the slider
UpdatePosition();
}
}
/// <summary>
/// The current value of the slider.
/// </summary>
public int Value
{
get => _value;
set
{
// If the value is over the limit, raise an exception
if (value > maximum)
{
throw new ArgumentOutOfRangeException(nameof(value), $"The value is over the maximum of {maximum - 1}");
}
// Otherwise, save it
_value = value;
// Trigger the respective event
ValueChanged?.Invoke(this, EventArgs.Empty);
// And update the location of the slider
UpdatePosition();
}
}
/// <summary>
/// The multiplier for increasing and decreasing the value.
/// </summary>
public int Multiplier { get; set; } = 1;
#endregion
#region Event
/// <summary>
/// Event triggered when the value of the menu changes.
/// </summary>
public event EventHandler ValueChanged;
#endregion
#region Constructors
/// <summary>
/// Creates a <see cref="NativeSliderItem"/> with a maximum of 100.
/// </summary>
/// <param name="title">The title of the Item.</param>
public NativeSliderItem(string title) : this(title, string.Empty, 100, 0)
{
}
/// <summary>
/// Creates a <see cref="NativeSliderItem"/> with a maximum of 100.
/// </summary>
/// <param name="title">The title of the Item.</param>
/// <param name="description">The description of the Item.</param>
public NativeSliderItem(string title, string description) : this(title, description, 100, 0)
{
}
/// <summary>
/// Creates a <see cref="NativeSliderItem"/> with a specific current and maximum value.
/// </summary>
/// <param name="title">The title of the Item.</param>
/// <param name="max">The maximum value of the Slider.</param>
/// <param name="value">The current value of the Slider.</param>
public NativeSliderItem(string title, int max, int value) : this(title, string.Empty, max, value)
{
}
/// <summary>
/// Creates a <see cref="NativeSliderItem"/> with a specific maximum.
/// </summary>
/// <param name="title">The title of the Item.</param>
/// <param name="description">The description of the Item.</param>
/// <param name="max">The maximum value of the Slider.</param>
/// <param name="value">The current value of the Slider.</param>
public NativeSliderItem(string title, string description, int max, int value) : base(title, description)
{
maximum = max;
_value = value;
}
#endregion
#region Internal Functions
/// <summary>
/// Updates the position of the bar based on the value.
/// </summary>
internal protected void UpdatePosition()
{
// Calculate the increment, and then the value of X
float increment = _value / (float)maximum;
float x = (background.Size.Width - slider.Size.Width) * increment;
// Then, add the X to the slider position
slider.Position = new PointF(background.Position.X + x, background.Position.Y);
}
#endregion
#region Public Functions
/// <summary>
/// Recalculates the item positions and sizes with the specified values.
/// </summary>
/// <param name="pos">The position of the item.</param>
/// <param name="size">The size of the item.</param>
/// <param name="selected">If this item has been selected.</param>
public override void Recalculate(PointF pos, SizeF size, bool selected)
{
base.Recalculate(pos, size, selected);
// Set the position and size of the background
background.Size = new SizeF(150, 9);
background.Position = new PointF(pos.X + size.Width - background.Size.Width - 7 - LeftArrow.Size.Width, pos.Y + 14);
// And do the same for the left arrow
LeftArrow.Position = new PointF(background.Position.X - LeftArrow.Size.Width, pos.Y + 4);
// Finally, set the correct position of the slider
slider.Size = new SizeF(75, 9);
UpdatePosition();
}
/// <summary>
/// Reduces the value of the slider.
/// </summary>
public override void GoLeft()
{
// Calculate the new value
int newValue = _value - Multiplier;
// If is under zero, set it to zero
if (newValue < 0)
{
Value = 0;
}
// Otherwise, set it to the new value
else
{
Value = newValue;
}
}
/// <summary>
/// Increases the value of the slider.
/// </summary>
public override void GoRight()
{
// Calculate the new value
int newValue = _value + Multiplier;
// If the value is over the maximum, set the max
if (newValue > maximum)
{
Value = maximum;
}
// Otherwise, set the calculated value
else
{
Value = newValue;
}
}
/// <summary>
/// Draws the slider.
/// </summary>
public override void Draw()
{
base.Draw(); // Arrows, Title and Left Badge
background.Draw();
slider.Draw();
}
#endregion
}
}

View File

@ -1,187 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using LemonUI.Elements;
namespace LemonUI.Menus
{
/// <summary>
/// Represents the Information of a specific field in a <see cref="NativeStatsPanel"/>.
/// </summary>
public class NativeStatsInfo
{
#region Fields
private readonly ScaledText text = new ScaledText(PointF.Empty, string.Empty, 0.35f);
private float value = 100;
private readonly List<ScaledRectangle> backgrounds = new List<ScaledRectangle>();
private readonly List<ScaledRectangle> foregrounds = new List<ScaledRectangle>();
#endregion
#region Properties
/// <summary>
/// The name of the Stats Field.
/// </summary>
public string Name
{
get => text.Text;
set => text.Text = value;
}
/// <summary>
/// The value of the Stats bar.
/// </summary>
public float Value
{
get => value;
set
{
if (value > 100 || value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "The Value of the Stat can't be over 100 or under 0.");
}
this.value = value;
UpdateBars();
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new Stat Info with the specified name and value set to zero.
/// </summary>
/// <param name="name">The name of the Stat.</param>
public NativeStatsInfo(string name) : this(name, 0)
{
}
/// <summary>
/// Creates a new Stat Info with the specified name and value.
/// </summary>
/// <param name="name">The name of the Stat.</param>
/// <param name="value"></param>
public NativeStatsInfo(string name, int value)
{
Name = name;
this.value = value;
for (int i = 0; i < 5; i++)
{
backgrounds.Add(new ScaledRectangle(PointF.Empty, SizeF.Empty));
foregrounds.Add(new ScaledRectangle(PointF.Empty, SizeF.Empty));
}
}
#endregion
#region Functions
internal void SetColor(Color background, Color foreground)
{
foreach (ScaledRectangle rectangle in backgrounds)
{
rectangle.Color = background;
}
foreach (ScaledRectangle rectangle in foregrounds)
{
rectangle.Color = foreground;
}
}
/// <summary>
/// Updates the values of the bars.
/// </summary>
private void UpdateBars()
{
SizeF @default = new SizeF(35, 9);
// FIRST BAR
if (value > 0 && value < 20)
{
foregrounds[0].Size = new SizeF(@default.Width * (value / 20), @default.Height);
}
else
{
foregrounds[0].Size = value > 20 ? @default : SizeF.Empty;
}
// SECOND BAR
if (value > 20 && value < 40)
{
foregrounds[1].Size = new SizeF(@default.Width * ((value - 20) / 20), @default.Height);
}
else
{
foregrounds[1].Size = value > 40 ? @default : SizeF.Empty;
}
// THIRD BAR
if (value > 40 && value < 60)
{
foregrounds[2].Size = new SizeF(@default.Width * ((value - 40) / 20), @default.Height);
}
else
{
foregrounds[2].Size = value > 60 ? @default : SizeF.Empty;
}
// FOURTH BAR
if (value > 60 && value < 80)
{
foregrounds[3].Size = new SizeF(@default.Width * ((value - 60) / 20), @default.Height);
}
else
{
foregrounds[3].Size = value > 80 ? @default : SizeF.Empty;
}
// FIFTH BAR
if (value > 80 && value < 100)
{
foregrounds[4].Size = new SizeF(@default.Width * ((value - 80) / 20), @default.Height);
}
else
{
foregrounds[4].Size = value == 100 ? @default : SizeF.Empty;
}
}
/// <summary>
/// Recalculates the position of the stat Text and Bar.
/// </summary>
/// <param name="position">The new position fot the Stat.</param>
/// <param name="width">The Width of the parent Stats Panel.</param>
public void Recalculate(PointF position, float width)
{
text.Position = new PointF(position.X, position.Y);
for (int i = 0; i < 5; i++)
{
PointF pos = new PointF(position.X + width - 234 + ((35 + 3) * i), position.Y + 10);
backgrounds[i].Position = pos;
backgrounds[i].Size = new SizeF(35, 9);
foregrounds[i].Position = pos;
}
UpdateBars();
}
/// <summary>
/// Draws the stat information.
/// </summary>
public void Draw()
{
foreach (ScaledRectangle background in backgrounds)
{
background.Draw();
}
foreach (ScaledRectangle foreground in foregrounds)
{
foreground.Draw();
}
text.Draw();
}
#endregion
}
}

View File

@ -1,167 +0,0 @@
using LemonUI.Elements;
using System;
using System.Collections.Generic;
using System.Drawing;
namespace LemonUI.Menus
{
/// <summary>
/// Represents a Statistics panel.
/// </summary>
public class NativeStatsPanel : NativePanel, IContainer<NativeStatsInfo>
{
#region Fields
private readonly List<NativeStatsInfo> fields = new List<NativeStatsInfo>();
private PointF lastPosition = PointF.Empty;
private float lastWidth = 0;
private Color backgroundColor = Color.FromArgb(255, 169, 169, 169);
private Color foregroundColor = Color.FromArgb(255, 255, 255, 255);
#endregion
#region Properties
/// <summary>
/// The color of the background of the bars.
/// </summary>
public Color BackgroundColor
{
get => backgroundColor;
set
{
backgroundColor = value;
foreach (NativeStatsInfo field in fields)
{
field.SetColor(value, foregroundColor);
}
}
}
/// <summary>
/// The color of the foreground of the bars.
/// </summary>
public Color ForegroundColor
{
get => foregroundColor;
set
{
foregroundColor = value;
foreach (NativeStatsInfo field in fields)
{
field.SetColor(backgroundColor, value);
}
}
}
#endregion
#region Constructor
/// <summary>
/// Creates a new Stats Panel.
/// </summary>
/// <param name="stats">The Statistics to add.</param>
public NativeStatsPanel(params NativeStatsInfo[] stats)
{
foreach (NativeStatsInfo field in stats)
{
Add(field);
}
}
#endregion
#region Functions
/// <summary>
/// Adds a stat to the player field.
/// </summary>
/// <param name="field">The Field to add.</param>
public void Add(NativeStatsInfo field)
{
if (fields.Contains(field))
{
throw new InvalidOperationException("Stat is already part of the panel.");
}
fields.Add(field);
field.SetColor(backgroundColor, foregroundColor);
}
/// <summary>
/// Removes a field from the panel.
/// </summary>
/// <param name="field">The field to remove.</param>
public void Remove(NativeStatsInfo field)
{
if (!fields.Contains(field))
{
return;
}
fields.Remove(field);
Recalculate();
}
/// <summary>
/// Removes the items that match the function.
/// </summary>
/// <param name="func">The function used to match items.</param>
public void Remove(Func<NativeStatsInfo, bool> func)
{
foreach (NativeStatsInfo item in new List<NativeStatsInfo>(fields))
{
if (func(item))
{
Remove(item);
}
}
}
/// <summary>
/// Removes all of the Stats fields.
/// </summary>
public void Clear()
{
fields.Clear();
Recalculate();
}
/// <summary>
/// Checks if the field is part of the Stats Panel.
/// </summary>
/// <param name="field">The field to check.</param>
/// <returns><see langword="true"/> if the item is part of the Panel, <see langword="false"/> otherwise.</returns>
public bool Contains(NativeStatsInfo field) => fields.Contains(field);
/// <summary>
/// Recalculates the Stats panel with the last known Position and Width.
/// </summary>
public void Recalculate() => Recalculate(lastPosition, lastWidth);
/// <summary>
/// Recalculates the position of the Stats panel.
/// </summary>
/// <param name="position">The new position of the Stats Panel.</param>
/// <param name="width">The width of the menu.</param>
public override void Recalculate(PointF position, float width)
{
base.Recalculate(position, width);
Background.Size = new SizeF(width, (fields.Count * 38) + 9);
for (int i = 0; i < fields.Count; i++)
{
fields[i].Recalculate(new PointF(position.X + 9, position.Y + 9 + (38 * i)), width);
}
}
/// <summary>
/// Processes the Stats Panel.
/// </summary>
public override void Process()
{
base.Process();
foreach (NativeStatsInfo info in fields)
{
info.Draw();
}
}
#endregion
}
}

View File

@ -1,75 +0,0 @@
using System;
namespace LemonUI.Menus
{
/// <summary>
/// Item used for opening submenus.
/// </summary>
public class NativeSubmenuItem : NativeItem
{
#region Public Properties
/// <summary>
/// The menu opened by this item.
/// </summary>
public NativeMenu Menu { get; }
#endregion
#region Constructors
/// <summary>
/// Creates a new Item that opens a Submenu.
/// </summary>
/// <param name="menu">The menu that this item will open.</param>
/// <param name="parent">The parent menu where this item will be located.</param>
public NativeSubmenuItem(NativeMenu menu, NativeMenu parent) : this(menu, parent, ">>>")
{
}
/// <summary>
/// Creates a new Item that opens a Submenu.
/// </summary>
/// <param name="menu">The menu that this item will open.</param>
/// <param name="parent">The parent menu where this item will be located.</param>
/// <param name="endlabel">The alternative title of the item, shown on the right.</param>
public NativeSubmenuItem(NativeMenu menu, NativeMenu parent, string endlabel) : base(menu.Subtitle, menu.Description, endlabel)
{
Menu = menu ?? throw new ArgumentNullException(nameof(menu));
Menu.Parent = parent ?? throw new ArgumentNullException(nameof(parent));
Activated += NativeSubmenuItem_Activated;
}
#endregion
#region Functions
/// <inheritdoc/>
public override void Draw()
{
// There is no Process(), so let's use draw to update the description
if (Description != Menu.Description)
{
Description = Menu.Description;
}
base.Draw();
}
#endregion
#region Local Events
private void NativeSubmenuItem_Activated(object sender, EventArgs e)
{
Menu.Parent.Visible = false;
if (!Menu.Parent.Visible)
{
Menu.Visible = true;
}
}
#endregion
}
}

View File

@ -1,28 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the selection of an item in the screen.
/// </summary>
public class SelectedEventArgs
{
/// <summary>
/// The index of the item in the full list of items.
/// </summary>
public int Index { get; }
/// <summary>
/// The index of the item in the screen.
/// </summary>
public int OnScreen { get; }
/// <summary>
/// Creates a new <see cref="SelectedEventArgs"/>.
/// </summary>
/// <param name="index">The index of the item in the menu.</param>
/// <param name="screen">The index of the item based on the number of items shown on screen,</param>
public SelectedEventArgs(int index, int screen)
{
Index = index;
OnScreen = screen;
}
}
}

View File

@ -1,9 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// Represents the method that is called when a new item is selected in the Menu.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="SelectedEventArgs"/> with the index information.</param>
public delegate void SelectedEventHandler(object sender, SelectedEventArgs e);
}

View File

@ -1,21 +0,0 @@
namespace LemonUI.Menus
{
/// <summary>
/// The behavior of the <see cref="NativeMenu"/>'s subtitle.
/// </summary>
public enum SubtitleBehavior
{
/// <summary>
/// The subtitle will always be shown.
/// </summary>
AlwaysShow = 0,
/// <summary>
/// The subtitle will always be shown, except when is empty.
/// </summary>
ShowIfRequired = 1,
/// <summary>
/// The subtitle will never be shown.
/// </summary>
AlwaysHide = 2
}
}

View File

@ -1,297 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.UI;
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
using RAGE.NUI;
#elif RPH
using Rage;
using Rage.Native;
#elif SHVDN3
using GTA.Native;
using GTA.UI;
#endif
using System;
using System.Collections.Concurrent;
using System.Drawing;
namespace LemonUI
{
/// <summary>
/// Represents the method that reports a Resolution change in the Game Settings.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="ResolutionChangedEventArgs"/> containing the Previous and Current resolution.</param>
public delegate void ResolutionChangedEventHandler(object sender, ResolutionChangedEventArgs e);
/// <summary>
/// Represents the method that reports a Safe Zone change in the Game Settings.
/// </summary>
/// <param name="sender">The source of the event event.</param>
/// <param name="e">A <see cref="ResolutionChangedEventArgs"/> containing the Previous and Current Safe Zone.</param>
public delegate void SafeZoneChangedEventHandler(object sender, SafeZoneChangedEventArgs e);
/// <summary>
/// Represents the information after a Resolution Change in the game.
/// </summary>
public class ResolutionChangedEventArgs
{
/// <summary>
/// The Game Resolution before it was changed.
/// </summary>
public SizeF Before { get; }
/// <summary>
/// The Game Resolution after it was changed.
/// </summary>
public SizeF After { get; }
internal ResolutionChangedEventArgs(SizeF before, SizeF after)
{
Before = before;
After = after;
}
}
/// <summary>
/// Represents the information after a Safe Zone Change in the game.
/// </summary>
public class SafeZoneChangedEventArgs
{
/// <summary>
/// The raw Safezone size before the change.
/// </summary>
public float Before { get; }
/// <summary>
/// The Safezone size after the change.
/// </summary>
public float After { get; }
internal SafeZoneChangedEventArgs(float before, float after)
{
Before = before;
After = after;
}
}
/// <summary>
/// Manager for Menus and Items.
/// </summary>
public class ObjectPool
{
#region Private Fields
/// <summary>
/// The last known resolution by the object pool.
/// </summary>
#if FIVEM
private SizeF lastKnownResolution = CitizenFX.Core.UI.Screen.Resolution;
#elif RAGEMP
private SizeF lastKnownResolution = new SizeF(Game.ScreenResolution.Width, Game.ScreenResolution.Height);
#elif RPH
private SizeF lastKnownResolution = Game.Resolution;
#elif SHVDN3
private SizeF lastKnownResolution = GTA.UI.Screen.Resolution;
#endif
/// <summary>
/// The last know Safezone size.
/// </summary>
#if FIVEM
private float lastKnownSafezone = API.GetSafeZoneSize();
#elif RAGEMP
private float lastKnownSafezone = Invoker.Invoke<float>(Natives.GetSafeZoneSize);
#elif RPH
private float lastKnownSafezone = NativeFunction.CallByHash<float>(0xBAF107B6BB2C97F0);
#elif SHVDN3
private float lastKnownSafezone = Function.Call<float>(Hash.GET_SAFE_ZONE_SIZE);
#endif
/// <summary>
/// The list of processable objects.
/// </summary>
private readonly ConcurrentDictionary<int, IProcessable> objects = new ConcurrentDictionary<int, IProcessable>();
#endregion
#region Public Properties
/// <summary>
/// Checks if there are objects visible on the screen.
/// </summary>
public bool AreAnyVisible
{
get
{
// Iterate over the objects
foreach (IProcessable obj in objects.Values)
{
// If is visible return true
if (obj.Visible)
{
return true;
}
}
// If none were visible return false
return false;
}
}
#endregion
#region Events
/// <summary>
/// Event triggered when the game resolution is changed.
/// </summary>
public event ResolutionChangedEventHandler ResolutionChanged;
/// <summary>
/// Event triggered when the Safezone size option in the Display settings is changed.
/// </summary>
public event SafeZoneChangedEventHandler SafezoneChanged;
#endregion
#region Tools
/// <summary>
/// Detects resolution changes by comparing the last known resolution and the current one.
/// </summary>
private void DetectResolutionChanges()
{
// Get the current resolution
#if FIVEM
SizeF resolution = CitizenFX.Core.UI.Screen.Resolution;
#elif RAGEMP
ScreenResolutionType raw = Game.ScreenResolution;
SizeF resolution = new SizeF(raw.Width, raw.Height);
#elif RPH
SizeF resolution = Game.Resolution;
#elif SHVDN3
SizeF resolution = GTA.UI.Screen.Resolution;
#endif
// If the old res does not matches the current one
if (lastKnownResolution != resolution)
{
// Trigger the event
ResolutionChanged?.Invoke(this, new ResolutionChangedEventArgs(lastKnownResolution, resolution));
// Refresh everything
RefreshAll();
// And save the new resolution
lastKnownResolution = resolution;
}
}
/// <summary>
/// Detects Safezone changes by comparing the last known value to the current one.
/// </summary>
private void DetectSafezoneChanges()
{
// Get the current Safezone size
#if FIVEM
float safezone = API.GetSafeZoneSize();
#elif RAGEMP
float safezone = Invoker.Invoke<float>(Natives.GetSafeZoneSize);
#elif RPH
float safezone = NativeFunction.CallByHash<float>(0xBAF107B6BB2C97F0);
#elif SHVDN3
float safezone = Function.Call<float>(Hash.GET_SAFE_ZONE_SIZE);
#endif
// If is not the same as the last one
if (lastKnownSafezone != safezone)
{
// Trigger the event
SafezoneChanged?.Invoke(this, new SafeZoneChangedEventArgs(lastKnownSafezone, safezone));
// Refresh everything
RefreshAll();
// And save the new safezone
lastKnownSafezone = safezone;
}
}
#endregion
#region Public Function
/// <summary>
/// Adds the object into the pool.
/// </summary>
/// <param name="obj">The object to add.</param>
public void Add(IProcessable obj)
{
// Make sure that the object is not null
if (obj == null)
{
throw new ArgumentNullException(nameof(obj));
}
int key = obj.GetHashCode();
// Otherwise, add it to the general pool
if (objects.ContainsKey(key))
{
throw new InvalidOperationException("The object is already part of this pool.");
}
objects.TryAdd(key, obj);
}
/// <summary>
/// Removes the object from the pool.
/// </summary>
/// <param name="obj">The object to remove.</param>
public void Remove(IProcessable obj)
{
objects.TryRemove(obj.GetHashCode(), out _);
}
/// <summary>
/// Performs the specified action on each element that matches T.
/// </summary>
/// <typeparam name="T">The type to match.</typeparam>
/// <param name="action">The action delegate to perform on each T.</param>
public void ForEach<T>(Action<T> action)
{
foreach (IProcessable obj in objects.Values)
{
if (obj is T conv)
{
action(conv);
}
}
}
/// <summary>
/// Refreshes all of the items.
/// </summary>
public void RefreshAll()
{
// Iterate over the objects and recalculate those possible
foreach (IProcessable obj in objects.Values)
{
if (obj is IRecalculable recal)
{
recal.Recalculate();
}
}
}
/// <summary>
/// Hides all of the objects.
/// </summary>
public void HideAll()
{
foreach (IProcessable obj in objects.Values)
{
obj.Visible = false;
}
}
/// <summary>
/// Processes the objects and features in this pool.
/// This needs to be called every tick.
/// </summary>
public void Process()
{
// See if there are resolution or safezone changes
DetectResolutionChanges();
DetectSafezoneChanges();
// And process the objects in the pool
foreach (IProcessable obj in objects.Values)
{
obj.Process();
}
}
#endregion
}
}

View File

@ -1,342 +0,0 @@
#if FIVEM
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage.Native;
#elif SHVDN3
using GTA.Native;
#endif
using System;
namespace LemonUI.Scaleform
{
/// <summary>
/// Represents a generic Scaleform object.
/// </summary>
public abstract class BaseScaleform : IScaleform
{
#region Private Fields
#if FIVEM || SHVDN3
/// <summary>
/// The ID of the scaleform.
/// </summary>
[Obsolete("Please use the Handle or Name properties and call the methods manually.", true)]
#endif
#if FIVEM
protected CitizenFX.Core.Scaleform scaleform = new CitizenFX.Core.Scaleform(string.Empty);
#elif SHVDN3
protected GTA.Scaleform scaleform = new GTA.Scaleform(string.Empty);
#endif
#endregion
#region Public Properties
/// <summary>
/// The ID or Handle of the Scaleform.
/// </summary>
public int Handle { get; private set; }
/// <summary>
/// The Name of the Scaleform.
/// </summary>
public string Name { get; }
/// <summary>
/// If the Scaleform should be visible or not.
/// </summary>
public bool Visible { get; set; }
/// <summary>
/// If the Scaleform is loaded or not.
/// </summary>
public bool IsLoaded
{
get
{
#if FIVEM
return API.HasScaleformMovieLoaded(Handle);
#elif RAGEMP
return Invoker.Invoke<bool>(Natives.HasScaleformMovieLoaded, Handle);
#elif RPH
return NativeFunction.CallByHash<bool>(0x85F01B8D5B90570E, Handle);
#elif SHVDN3
return Function.Call<bool>(Hash.HAS_SCALEFORM_MOVIE_LOADED, Handle);
#endif
}
}
#endregion
#region Constructors
/// <summary>
/// Creates a new Scaleform class with the specified Scaleform object name.
/// </summary>
/// <param name="sc">The Scalform object.</param>
public BaseScaleform(string sc)
{
Name = sc;
#if FIVEM
Handle = API.RequestScaleformMovie(Name);
#elif RAGEMP
Handle = Invoker.Invoke<int>(Natives.RequestScaleformMovie, Name);
#elif RPH
Handle = NativeFunction.CallByHash<int>(0x11FE353CF9733E6F, Name);
#elif SHVDN3
Handle = Function.Call<int>(Hash.REQUEST_SCALEFORM_MOVIE, Name);
#endif
}
#endregion
#region Private Functions
private void CallFunctionBase(string function, params object[] parameters)
{
#if FIVEM
API.BeginScaleformMovieMethod(Handle, function);
#elif RAGEMP
Invoker.Invoke(0xF6E48914C7A8694E, Handle, function);
#elif RPH
NativeFunction.CallByHash<int>(0xF6E48914C7A8694E, Handle, function);
#elif SHVDN3
Function.Call((Hash)0xF6E48914C7A8694E, Handle, function);
#endif
foreach (object obj in parameters)
{
if (obj is int objInt)
{
#if FIVEM
API.ScaleformMovieMethodAddParamInt(objInt);
#elif RAGEMP
Invoker.Invoke(0xC3D0841A0CC546A6, objInt);
#elif RPH
NativeFunction.CallByHash<int>(0xC3D0841A0CC546A6, objInt);
#elif SHVDN3
Function.Call((Hash)0xC3D0841A0CC546A6, objInt);
#endif
}
else if (obj is string objString)
{
#if FIVEM
API.BeginTextCommandScaleformString("STRING");
API.AddTextComponentSubstringPlayerName(objString);
API.EndTextCommandScaleformString();
#elif RAGEMP
Invoker.Invoke(Natives.BeginTextCommandScaleformString, "STRING");
Invoker.Invoke(Natives.AddTextComponentSubstringPlayerName, objString);
Invoker.Invoke(Natives.EndTextCommandScaleformString);
#elif RPH
NativeFunction.CallByHash<int>(0x80338406F3475E55, "STRING");
NativeFunction.CallByHash<int>(0x6C188BE134E074AA, objString);
NativeFunction.CallByHash<int>(0x362E2D3FE93A9959);
#elif SHVDN3
Function.Call((Hash)0x80338406F3475E55, "STRING");
Function.Call((Hash)0x6C188BE134E074AA, objString);
Function.Call((Hash)0x362E2D3FE93A9959);
#endif
}
else if (obj is float objFloat)
{
#if FIVEM
API.ScaleformMovieMethodAddParamFloat(objFloat);
#elif RAGEMP
Invoker.Invoke(0xD69736AAE04DB51A, objFloat);
#elif RPH
NativeFunction.CallByHash<int>(0xD69736AAE04DB51A, objFloat);
#elif SHVDN3
Function.Call((Hash)0xD69736AAE04DB51A, objFloat);
#endif
}
else if (obj is double objDouble)
{
#if FIVEM
API.ScaleformMovieMethodAddParamFloat((float)objDouble);
#elif RAGEMP
Invoker.Invoke(0xD69736AAE04DB51A, (float)objDouble);
#elif RPH
NativeFunction.CallByHash<int>(0xD69736AAE04DB51A, (float)objDouble);
#elif SHVDN3
Function.Call((Hash)0xD69736AAE04DB51A, (float)objDouble);
#endif
}
else if (obj is bool objBool)
{
#if FIVEM
API.ScaleformMovieMethodAddParamBool(objBool);
#elif RAGEMP
Invoker.Invoke(0xC58424BA936EB458, objBool);
#elif RPH
NativeFunction.CallByHash<int>(0xC58424BA936EB458, objBool);
#elif SHVDN3
Function.Call((Hash)0xC58424BA936EB458, objBool);
#endif
}
else
{
throw new ArgumentException($"Unexpected argument type {obj.GetType().Name}.", nameof(parameters));
}
}
}
#endregion
#region Public Functions
/// <summary>
/// Checks if the specified Scaleform Return Value is ready to be fetched.
/// </summary>
/// <param name="id">The Identifier of the Value.</param>
/// <returns><see langword="true"/> if the value is ready, <see langword="false"/> otherwise.</returns>
public bool IsValueReady(int id)
{
#if FIVEM
return API.IsScaleformMovieMethodReturnValueReady(id);
#elif RAGEMP
return Invoker.Invoke<bool>(Natives._0x768FF8961BA904D6, id);
#elif RPH
return NativeFunction.CallByHash<bool>(0x768FF8961BA904D6, id);
#elif SHVDN3
return Function.Call<bool>(Hash.IS_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_READY, id);
#endif
}
/// <summary>
/// Gets a specific value.
/// </summary>
/// <typeparam name="T">The type of value.</typeparam>
/// <param name="id">The Identifier of the value.</param>
/// <returns>The value returned by the native.</returns>
public T GetValue<T>(int id)
{
if (typeof(T) == typeof(string))
{
#if FIVEM
return (T)(object)API.GetScaleformMovieMethodReturnValueString(id);
#elif RAGEMP
return Invoker.Invoke<T>(0xE1E258829A885245, id);
#elif RPH
return (T)NativeFunction.CallByHash(0xE1E258829A885245, typeof(string), id);
#elif SHVDN3
return (T)(object)Function.Call<string>(Hash.GET_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_STRING, id);
#endif
}
else if (typeof(T) == typeof(int))
{
#if FIVEM
return (T)(object)API.GetScaleformMovieMethodReturnValueInt(id);
#elif RAGEMP
return Invoker.Invoke<T>(0x2DE7EFA66B906036, id);
#elif RPH
return (T)(object)NativeFunction.CallByHash<int>(0x2DE7EFA66B906036, id);
#elif SHVDN3
return (T)(object)Function.Call<int>(Hash.GET_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_INT, id);
#endif
}
else if (typeof(T) == typeof(bool))
{
#if FIVEM
return (T)(object)API.GetScaleformMovieMethodReturnValueBool(id);
#elif RAGEMP
return Invoker.Invoke<T>(0xD80A80346A45D761, id);
#elif RPH
return (T)(object)NativeFunction.CallByHash<bool>(0xD80A80346A45D761, id);
#elif SHVDN3
return (T)(object)Function.Call<bool>(Hash._GET_SCALEFORM_MOVIE_METHOD_RETURN_VALUE_BOOL, id);
#endif
}
else
{
throw new InvalidOperationException($"Expected string, int or bool, got {typeof(T).Name}.");
}
}
/// <summary>
/// Calls a Scaleform function.
/// </summary>
/// <param name="function">The name of the function to call.</param>
/// <param name="parameters">The parameters to pass.</param>
public void CallFunction(string function, params object[] parameters)
{
CallFunctionBase(function, parameters);
#if FIVEM
API.EndScaleformMovieMethod();
#elif RAGEMP
Invoker.Invoke(0xC6796A8FFA375E53);
#elif RPH
NativeFunction.CallByHash<int>(0xC6796A8FFA375E53);
#elif SHVDN3
Function.Call((Hash)0xC6796A8FFA375E53);
#endif
}
/// <summary>
/// Calls a Scaleform function with a return value.
/// </summary>
/// <param name="function">The name of the function to call.</param>
/// <param name="parameters">The parameters to pass.</param>
public int CallFunctionReturn(string function, params object[] parameters)
{
CallFunctionBase(function, parameters);
#if FIVEM
return API.EndScaleformMovieMethodReturnValue();
#elif RAGEMP
return Invoker.Invoke<int>(0xC50AA39A577AF886);
#elif RPH
return NativeFunction.CallByHash<int>(0xC50AA39A577AF886);
#elif SHVDN3
return Function.Call<int>((Hash)0xC50AA39A577AF886);
#endif
}
/// <summary>
/// Updates the parameters of the Scaleform.
/// </summary>
public abstract void Update();
/// <summary>
/// Draws the scaleform full screen.
/// </summary>
public virtual void DrawFullScreen()
{
if (!Visible)
{
return;
}
Update();
#if FIVEM
API.DrawScaleformMovieFullscreen(Handle, 255, 255, 255, 255, 0);
#elif RAGEMP
Invoker.Invoke(Natives.DrawScaleformMovieFullscreen, 255, 255, 255, 255, 0);
#elif RPH
NativeFunction.CallByHash<int>(0x0DF606929C105BE1, Handle, 255, 255, 255, 255, 0);
#elif SHVDN3
Function.Call(Hash.DRAW_SCALEFORM_MOVIE_FULLSCREEN, Handle, 255, 255, 255, 255, 0);
#endif
}
/// <summary>
/// Draws the scaleform full screen.
/// </summary>
public virtual void Draw() => DrawFullScreen();
/// <summary>
/// Draws the scaleform full screen.
/// </summary>
public virtual void Process() => DrawFullScreen();
/// <summary>
/// Marks the scaleform as no longer needed.
/// </summary>
public void Dispose()
{
int id = Handle;
#if FIVEM
API.SetScaleformMovieAsNoLongerNeeded(ref id);
#elif RAGEMP
Invoker.Invoke(Natives.SetScaleformMovieAsNoLongerNeeded, id);
#elif RPH
NativeFunction.CallByHash<int>(0x1D132D614DD86811, new NativeArgument(id));
#elif SHVDN3
Function.Call(Hash.SET_SCALEFORM_MOVIE_AS_NO_LONGER_NEEDED, new OutputArgument(id));
#endif
}
#endregion
}
}

View File

@ -1,357 +0,0 @@
#if FIVEM
using CitizenFX.Core;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage;
#elif SHVDN3
using GTA;
#endif
using System;
namespace LemonUI.Scaleform
{
/// <summary>
/// The type for a big message.
/// </summary>
public enum MessageType
{
/// <summary>
/// A centered message with customizable text an d background colors.
/// Internally called SHOW_SHARD_CENTERED_MP_MESSAGE.
/// </summary>
Customizable = 0,
/// <summary>
/// Used when you rank up on GTA Online.
/// Internally called SHOW_SHARD_CREW_RANKUP_MP_MESSAGE.
/// </summary>
RankUp = 1,
/// <summary>
/// The Mission Passed screen on PS3 and Xbox 360.
/// Internally called SHOW_MISSION_PASSED_MESSAGE.
/// </summary>
MissionPassedOldGen = 2,
/// <summary>
/// The Message Type shown on the Wasted screen.
/// Internally called SHOW_SHARD_WASTED_MP_MESSAGE.
/// </summary>
Wasted = 3,
/// <summary>
/// Used on the GTA Online Freemode event announcements.
/// Internally called SHOW_PLANE_MESSAGE.
/// </summary>
Plane = 4,
/// <summary>
/// Development leftover from when GTA Online was Cops and Crooks.
/// Internally called SHOW_BIG_MP_MESSAGE.
/// </summary>
CopsAndCrooks = 5,
/// <summary>
/// Message shown when the player purchases a weapon.
/// Internally called SHOW_WEAPON_PURCHASED.
/// </summary>
Weapon = 6,
/// <summary>
/// Unknown where this one is used.
/// Internally called SHOW_CENTERED_MP_MESSAGE_LARGE.
/// </summary>
CenteredLarge = 7,
}
/// <summary>
/// A customizable big message.
/// </summary>
public class BigMessage : BaseScaleform
{
#region Constant Fields
private const uint unarmed = 0xA2719263;
#endregion
#region Private Fields
private MessageType type;
private uint weaponHash;
private uint hideAfter;
#endregion
#region Public Properties
/// <summary>
/// The title of the message.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The subtitle or description of the message.
/// </summary>
public string Message { get; set; }
/// <summary>
/// The color of the text.
/// Only used on the Customizable message type.
/// </summary>
public int TextColor { get; set; }
/// <summary>
/// The color of the background in the Customizable message type.
/// </summary>
public int BackgroundColor { get; set; }
/// <summary>
/// The Rank when the mode is set to Cops and Crooks.
/// </summary>
public string Rank { get; set; }
#if !RAGEMP
/// <summary>
/// The hash of the Weapon as an enum.
/// </summary>
public WeaponHash Weapon
{
get => (WeaponHash)weaponHash;
set => weaponHash = (uint)value;
}
#endif
/// <summary>
/// The hash of the Weapon as it's native value.
/// </summary>
public uint WeaponHash
{
get => weaponHash;
set => weaponHash = value;
}
/// <summary>
/// The type of message to show.
/// </summary>
public MessageType Type
{
get => type;
set
{
if (!Enum.IsDefined(typeof(MessageType), value))
{
throw new InvalidOperationException($"{value} is not a valid message type.");
}
type = value;
}
}
#endregion
#region Constructors
/// <summary>
/// Creates a standard customizable message with just a title.
/// </summary>
/// <param name="title">The title to use.</param>
public BigMessage(string title) : this(title, string.Empty, string.Empty, unarmed, 0, 0, MessageType.Customizable)
{
}
/// <summary>
/// Creates a custom message with the specified title.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="type">The type of message.</param>
public BigMessage(string title, MessageType type) : this(title, string.Empty, string.Empty, unarmed, 0, 0, type)
{
}
/// <summary>
/// Creates a standard customizable message with a title and message.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="message">The message to show.</param>
public BigMessage(string title, string message) : this(title, message, string.Empty, unarmed, 0, 0, MessageType.Customizable)
{
}
/// <summary>
/// Creates a Cops and Crooks message type.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="message">The message to show.</param>
/// <param name="rank">Text to show in the Rank space.</param>
public BigMessage(string title, string message, string rank) : this(title, message, rank, unarmed, 0, 0, MessageType.CopsAndCrooks)
{
}
/// <summary>
/// Creates a message with the specified type, title and message.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="message">The message to show.</param>
/// <param name="type">The type of message.</param>
public BigMessage(string title, string message, MessageType type) : this(title, message, string.Empty, unarmed, 0, 0, type)
{
}
/// <summary>
/// Creates a standard customizable message with a title and a custom text color.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="colorText">The color of the text.</param>
public BigMessage(string title, int colorText) : this(title, string.Empty, string.Empty, unarmed, colorText, 0, MessageType.Customizable)
{
}
/// <summary>
/// Creates a standard customizable message with a specific title and custom colors.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="colorText">The color of the text.</param>
/// <param name="colorBackground">The color of the background.</param>
public BigMessage(string title, int colorText, int colorBackground) : this(title, string.Empty, string.Empty, unarmed, colorText, colorBackground, MessageType.Customizable)
{
}
#if !RAGEMP
/// <summary>
/// Creates a Weapon Purchase message with a custom text and weapons.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="weapon">The name of the Weapon.</param>
/// <param name="hash">The hash of the Weapon image.</param>
public BigMessage(string title, string weapon, WeaponHash hash) : this(title, string.Empty, weapon, hash, 0, 0, MessageType.Weapon)
{
}
/// <summary>
/// Creates a Weapon Purchase message with a custom text and weapons.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="message">The message to show.</param>
/// <param name="weapon">The name of the Weapon.</param>
/// <param name="hash">The hash of the Weapon image.</param>
public BigMessage(string title, string message, string weapon, WeaponHash hash) : this(title, message, weapon, hash, 0, 0, MessageType.Weapon)
{
}
/// <summary>
/// Creates a message with all of the selected information.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="message">The message to show.</param>
/// <param name="rank">The Rank on Cops and Crooks.</param>
/// <param name="weapon">The hash of the Weapon image.</param>
/// <param name="colorText">The color of the text.</param>
/// <param name="colorBackground">The color of the background.</param>
/// <param name="type">The type of message.</param>
public BigMessage(string title, string message, string rank, WeaponHash weapon, int colorText, int colorBackground, MessageType type) : this(title, message, rank, (uint)weapon, colorText, colorBackground, type)
{
}
#endif
/// <summary>
/// Creates a message with all of the selected information.
/// </summary>
/// <param name="title">The title to use.</param>
/// <param name="message">The message to show.</param>
/// <param name="rank">The Rank on Cops and Crooks.</param>
/// <param name="weapon">The hash of the Weapon image.</param>
/// <param name="colorText">The color of the text.</param>
/// <param name="colorBackground">The color of the background.</param>
/// <param name="type">The type of message.</param>
public BigMessage(string title, string message, string rank, uint weapon, int colorText, int colorBackground, MessageType type) : base("MP_BIG_MESSAGE_FREEMODE")
{
Title = title;
Message = message;
Rank = rank;
WeaponHash = weapon;
TextColor = colorText;
BackgroundColor = colorBackground;
Type = type;
Update();
}
#endregion
#region Public Functions
/// <summary>
/// Updates the Message information in the Scaleform.
/// </summary>
public override void Update()
{
// Select the correct function to call
string function;
switch (type)
{
case MessageType.Customizable:
function = "SHOW_SHARD_CENTERED_MP_MESSAGE";
break;
case MessageType.RankUp:
function = "SHOW_SHARD_CREW_RANKUP_MP_MESSAGE";
break;
case MessageType.MissionPassedOldGen:
function = "SHOW_MISSION_PASSED_MESSAGE";
break;
case MessageType.Wasted:
function = "SHOW_SHARD_WASTED_MP_MESSAGE";
break;
case MessageType.Plane:
function = "SHOW_PLANE_MESSAGE";
break;
case MessageType.CopsAndCrooks:
function = "SHOW_BIG_MP_MESSAGE";
break;
case MessageType.Weapon:
function = "SHOW_WEAPON_PURCHASED";
break;
case MessageType.CenteredLarge:
function = "SHOW_CENTERED_MP_MESSAGE_LARGE";
break;
default:
throw new InvalidOperationException($"{type} is not a valid message type.");
}
// And add the parameters
switch (type)
{
case MessageType.Customizable:
CallFunction(function, Title, Message, TextColor, BackgroundColor);
break;
case MessageType.CopsAndCrooks:
CallFunction(function, Title, Message, Rank);
break;
case MessageType.Weapon:
CallFunction(function, Title, Message, (int)weaponHash);
break;
default:
CallFunction(function, Title, Message);
break;
}
}
/// <summary>
/// Fades the big message out.
/// </summary>
/// <param name="time">The time it will take to do the fade.</param>
public void FadeOut(int time)
{
if (time < 0)
{
throw new ArgumentOutOfRangeException(nameof(time), "Time can't be under zero.");
}
CallFunction("SHARD_ANIM_OUT", 0, time);
#if RAGEMP
uint currentTime = (uint)Misc.GetGameTimer();
#elif RPH
uint currentTime = Game.GameTime;
#else
uint currentTime = (uint)Game.GameTime;
#endif
hideAfter = currentTime + (uint)time;
}
/// <inheritdoc/>
public override void DrawFullScreen()
{
#if RAGEMP
uint time = (uint)Misc.GetGameTimer();
#elif RPH
uint time = Game.GameTime;
#else
uint time = (uint)Game.GameTime;
#endif
if (hideAfter > 0 && time > hideAfter)
{
Visible = false;
hideAfter = 0;
}
base.DrawFullScreen();
}
#endregion
}
}

View File

@ -1,425 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage;
using Rage.Native;
using Control = Rage.GameControl;
#elif SHVDN3
using GTA;
using GTA.Native;
#endif
using System;
using System.Collections.Generic;
namespace LemonUI.Scaleform
{
/// <summary>
/// The Background of the BruteForce Hack Minigame.
/// </summary>
public enum BruteForceBackground
{
/// <summary>
/// A simple Black background.
/// </summary>
Black = 0,
/// <summary>
/// A simple Purple background.
/// </summary>
Purple = 1,
/// <summary>
/// A simple Gray background.
/// </summary>
Gray = 2,
/// <summary>
/// A simple Light Blue background.
/// </summary>
LightBlue = 3,
/// <summary>
/// A Light Blue Wallpaper.
/// </summary>
Wallpaper1 = 4,
/// <summary>
/// A Fade from Gray in the center to Black in the corners.
/// </summary>
DarkFade = 5,
}
/// <summary>
/// The status of the BruteForce Hack after finishing.
/// </summary>
public enum BruteForceStatus
{
/// <summary>
/// The user completed the hack successfully.
/// </summary>
Completed = 0,
/// <summary>
/// The user ran out of time.
/// </summary>
OutOfTime = 1,
/// <summary>
/// The player ran out of lives.
/// </summary>
OutOfLives = 2,
}
/// <summary>
/// Event information after an the BruteForce hack has finished.
/// </summary>
public class BruteForceFinishedEventArgs
{
/// <summary>
/// The final status of the Hack.
/// </summary>
public BruteForceStatus Status { get; }
internal BruteForceFinishedEventArgs(BruteForceStatus status)
{
Status = status;
}
}
/// <summary>
/// Represents the method that is called when the end user finishes the BruteForce hack.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">An <see cref="BruteForceFinishedEventArgs"/> with the hack status.</param>
public delegate void BruteForceFinishedEventHandler(object sender, BruteForceFinishedEventArgs e);
/// <summary>
/// The BruteForce Hacking Minigame shown in multiple missions.
/// </summary>
public class BruteForce : BaseScaleform
{
#region Fields
private static readonly Random random = new Random();
private static readonly Sound soundRowSwitch = new Sound(string.Empty, "HACKING_MOVE_CURSOR");
private static readonly Sound soundRowCompleted = new Sound(string.Empty, "HACKING_CLICK");
private static readonly Sound soundRowFailed = new Sound(string.Empty, "HACKING_CLICK_BAD");
private static readonly Sound soundSuccess = new Sound(string.Empty, "HACKING_SUCCESS");
private int hideTime = -1;
private int output = 0;
private bool firstRun = true;
private bool inProgress = false;
private BruteForceBackground background = BruteForceBackground.Black;
private string word = "LEMONADE";
private int livesTotal = 5;
private int livesCurrent = 5;
private int closeAfter = -1;
private TimeSpan end = TimeSpan.Zero;
private TimeSpan countdown = TimeSpan.Zero;
private bool showLives = true;
#endregion
#region Properties
/// <summary>
/// The Word shown to select in the menu.
/// </summary>
public string Word
{
get => word;
set
{
if (value.Length != 8)
{
throw new ArgumentOutOfRangeException("The word needs to be exactly 8 characters long.", nameof(value));
}
word = value;
CallFunction("SET_ROULETTE_WORD", value);
}
}
/// <summary>
/// The background of the Hacking minigame.
/// </summary>
public BruteForceBackground Background
{
get => background;
set
{
background = value;
CallFunction("SET_BACKGROUND", (int)value);
}
}
/// <summary>
/// The number of Lives of the minigame.
/// </summary>
public int TotalLives
{
get => livesTotal;
set
{
livesTotal = value;
if (livesCurrent > value)
{
livesCurrent = value;
}
CallFunction("SET_LIVES", livesCurrent, value);
}
}
/// <summary>
/// The current number of lives that the player has.
/// </summary>
public int CurrentLives => livesCurrent;
/// <summary>
/// The messages that might appear on success.
/// </summary>
public List<string> SuccessMessages { get; } = new List<string>();
/// <summary>
/// The messages that will appear when the player fails.
/// </summary>
public List<string> FailMessages { get; } = new List<string>();
/// <summary>
/// The time in milliseconds to wait before closing the Hack window automatically.
/// </summary>
/// <remarks>
/// This can be set to -1 to keep the Hack window open.
/// </remarks>
public int CloseAfter
{
get => closeAfter;
set
{
if (value < -1)
{
throw new ArgumentOutOfRangeException("The Closure time can't be under -1.", nameof(value));
}
closeAfter = value;
}
}
/// <summary>
/// If the player can retry the hack after failing.
/// </summary>
public bool CanRetry { get; set; } = false;
/// <summary>
/// The countdown of the Hack minigame.
/// </summary>
public TimeSpan Countdown
{
get => countdown;
set => countdown = value;
}
/// <summary>
/// If the lives of the player should be shown on the top right.
/// </summary>
public bool ShowLives
{
get => showLives;
set
{
showLives = value;
CallFunction("SHOW_LIVES", value);
}
}
/// <summary>
/// If all of the rows should be restarted after the player fails one.
/// </summary>
public bool ResetOnRowFail { get; set; } = true;
#endregion
#region Events
/// <summary>
/// Event triggered when the player finishes a hack.
/// </summary>
public event BruteForceFinishedEventHandler HackFinished;
#endregion
#region Constructors
/// <summary>
/// Creates a new Hacking Scaleform.
/// </summary>
public BruteForce() : base("HACKING_PC")
{
Visible = false;
for (int i = 0; i < 8; i++)
{
SetColumnSpeed(i, 100);
}
}
#endregion
#region Functions
/// <summary>
/// Resets the entire Hacking minigame.
/// </summary>
public void Reset()
{
inProgress = true;
Background = background;
RunProgram(4);
RunProgram(83);
TotalLives = livesTotal;
Word = word;
ShowLives = showLives;
#if RAGEMP
int time = Misc.GetGameTimer();
#elif RPH
uint time = Game.GameTime;
#else
int time = Game.GameTime;
#endif
end = TimeSpan.FromMilliseconds(time) + countdown;
}
/// <summary>
/// Sets the speed of one of the 8 columns.
/// </summary>
/// <param name="index">The index of the column.</param>
/// <param name="speed">The speed of the column.</param>
public void SetColumnSpeed(int index, float speed)
{
if (index >= 8 || index < 0)
{
throw new ArgumentOutOfRangeException("The index needs to be between 0 and 7.", nameof(index));
}
CallFunction("SET_COLUMN_SPEED", index, speed);
}
/// <summary>
/// Runs the specified Hacking program.
/// </summary>
/// <param name="program">The program to open.</param>
public void RunProgram(int program)
{
CallFunction("RUN_PROGRAM", program);
}
/// <summary>
/// Updates the information of the Hacking window.
/// </summary>
public override void Update()
{
#if RAGEMP
int time = Misc.GetGameTimer();
#elif RPH
uint time = Game.GameTime;
#else
int time = Game.GameTime;
#endif
// If there is a time set to hide the Hack window
if (hideTime != -1)
{
// If that time has already passed, go ahead and hide the window
if (hideTime <= time)
{
Visible = false;
hideTime = -1;
return;
}
}
// If this is the first run and is not in progress, reset it
if (firstRun && !inProgress)
{
firstRun = false;
Reset();
}
// If the hack minigame is not in progress but the player can retry and he pressed enter, reset it
if (!inProgress && CanRetry && Controls.IsJustPressed(Control.FrontendAccept))
{
Reset();
hideTime = -1;
}
// If the Hack minigame is in progress
if (inProgress)
{
// If there is a countdown set
if (countdown > TimeSpan.Zero)
{
// Calculate the time left
TimeSpan span = countdown - (TimeSpan.FromMilliseconds(time) - end);
// If is lower or equal than zero, the player failed
if (span <= TimeSpan.Zero)
{
CallFunction("SET_COUNTDOWN", 0, 0, 0);
string err = FailMessages.Count == 0 ? string.Empty : FailMessages[random.Next(FailMessages.Count)];
CallFunction("SET_ROULETTE_OUTCOME", false, err);
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
inProgress = false;
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.OutOfTime));
return;
}
// Otherwise, update the visible time
else
{
CallFunction("SET_COUNTDOWN", span.Minutes, span.Seconds, span.Milliseconds);
}
}
// If the user pressed left, go to the left
if (Controls.IsJustPressed(Control.MoveLeftOnly) || Controls.IsJustPressed(Control.FrontendLeft))
{
soundRowSwitch.PlayFrontend();
CallFunction("SET_INPUT_EVENT", 10);
}
// If the user pressed right, go to the right
else if (Controls.IsJustPressed(Control.MoveRightOnly) || Controls.IsJustPressed(Control.FrontendRight))
{
soundRowSwitch.PlayFrontend();
CallFunction("SET_INPUT_EVENT", 11);
}
// If the user pressed accept, send the selection event
else if (Controls.IsJustPressed(Control.FrontendAccept))
{
output = CallFunctionReturn("SET_INPUT_EVENT_SELECT");
}
// If there is some output to receive
if (output != 0)
{
// If the value is ready, go ahead and check it
if (IsValueReady(output))
{
switch (GetValue<int>(output))
{
case 86: // Hack Completed
string ok = SuccessMessages.Count == 0 ? string.Empty : SuccessMessages[random.Next(SuccessMessages.Count)];
CallFunction("SET_ROULETTE_OUTCOME", true, ok);
soundSuccess.PlayFrontend();
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.Completed));
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
inProgress = false;
break;
case 87: // Row Failed (or lives failed)
livesCurrent--;
CallFunction("SET_LIVES", livesCurrent, livesTotal);
soundRowFailed.PlayFrontend();
if (livesCurrent <= 0)
{
string err = FailMessages.Count == 0 ? string.Empty : FailMessages[random.Next(FailMessages.Count)];
CallFunction("SET_ROULETTE_OUTCOME", false, err);
hideTime = closeAfter == -1 ? -1 : (int)time + CloseAfter;
inProgress = false;
HackFinished?.Invoke(this, new BruteForceFinishedEventArgs(BruteForceStatus.OutOfLives));
}
break;
case 92: // Row Completed
soundRowCompleted.PlayFrontend();
break;
}
output = 0;
}
}
}
}
#endregion
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace LemonUI.Scaleform
{
/// <summary>
/// Scaleforms are 2D Adobe Flash-like objects.
/// </summary>
public interface IScaleform : IDrawable, IProcessable, IDisposable
{
/// <summary>
/// Draws the Scaleform in full screen.
/// </summary>
void DrawFullScreen();
}
}

View File

@ -1,196 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage.Native;
using Control = Rage.GameControl;
#elif SHVDN3
using GTA;
using GTA.Native;
#endif
using System;
using System.Collections.Generic;
namespace LemonUI.Scaleform
{
/// <summary>
/// An individual instructional button.
/// </summary>
public struct InstructionalButton
{
#region Private Fields
private Control control;
private string raw;
#endregion
#region Public Properties
/// <summary>
/// The description of this button.
/// </summary>
public string Description { get; set; }
/// <summary>
/// The Control used by this button.
/// </summary>
public Control Control
{
get => control;
set
{
control = value;
#if FIVEM
raw = API.GetControlInstructionalButton(2, (int)value, 1);
#elif RAGEMP
raw = Invoker.Invoke<string>(Natives.GetControlInstructionalButton, 2, (int)value, 1);
#elif RPH
raw = (string)NativeFunction.CallByHash(0x0499D7B09FC9B407, typeof(string), 2, (int)control, 1);
#elif SHVDN3
raw = Function.Call<string>(Hash.GET_CONTROL_INSTRUCTIONAL_BUTTON, 2, (int)value, 1);
#endif
}
}
/// <summary>
/// The Raw Control sent to the Scaleform.
/// </summary>
public string Raw
{
get => raw;
set
{
raw = value;
control = (Control)(-1);
}
}
#endregion
#region Constructor
/// <summary>
/// Creates an instructional button for a Control.
/// </summary>
/// <param name="description">The text for the description.</param>
/// <param name="control">The control to use.</param>
public InstructionalButton(string description, Control control)
{
Description = description;
this.control = control;
#if FIVEM
raw = API.GetControlInstructionalButton(2, (int)control, 1);
#elif RAGEMP
raw = Invoker.Invoke<string>(Natives.GetControlInstructionalButton, 2, (int)control, 1);
#elif RPH
raw = (string)NativeFunction.CallByHash(0x0499D7B09FC9B407, typeof(string), 2, (int)control, 1);
#elif SHVDN3
raw = Function.Call<string>(Hash.GET_CONTROL_INSTRUCTIONAL_BUTTON, 2, (int)control, 1);
#endif
}
/// <summary>
/// Creates an instructional button for a raw control.
/// </summary>
/// <param name="description">The text for the description.</param>
/// <param name="raw">The raw value of the control.</param>
public InstructionalButton(string description, string raw)
{
Description = description;
control = (Control)(-1);
this.raw = raw;
}
#endregion
}
/// <summary>
/// Buttons shown on the bottom right of the screen.
/// </summary>
public class InstructionalButtons : BaseScaleform
{
#region Public Fields
/// <summary>
/// The buttons used in this Scaleform.
/// </summary>
private readonly List<InstructionalButton> buttons = new List<InstructionalButton>();
#endregion
#region Constructors
/// <summary>
/// Creates a new set of Instructional Buttons.
/// </summary>
/// <param name="buttons">The buttons to add into this menu.</param>
public InstructionalButtons(params InstructionalButton[] buttons) : base("INSTRUCTIONAL_BUTTONS")
{
this.buttons.AddRange(buttons);
}
#endregion
#region Public Functions
/// <summary>
/// Adds an Instructional Button.
/// </summary>
/// <param name="button">The button to add.</param>
public void Add(InstructionalButton button)
{
// If the item is already in the list, raise an exception
if (buttons.Contains(button))
{
throw new InvalidOperationException("The button is already in the Scaleform.");
}
// Otherwise, add it to the list of items
buttons.Add(button);
}
/// <summary>
/// Removes an Instructional Button.
/// </summary>
/// <param name="button">The button to remove.</param>
public void Remove(InstructionalButton button)
{
// If the button is not in the list, return
if (!buttons.Contains(button))
{
return;
}
// Otherwise, remove it
buttons.Remove(button);
}
/// <summary>
/// Removes all of the instructional buttons.
/// </summary>
public void Clear()
{
buttons.Clear();
}
/// <summary>
/// Refreshes the items shown in the Instructional buttons.
/// </summary>
public override void Update()
{
// Clear all of the existing items
CallFunction("CLEAR_ALL");
CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
CallFunction("CREATE_CONTAINER");
// And add them again
for (int i = 0; i < buttons.Count; i++)
{
InstructionalButton button = buttons[i];
CallFunction("SET_DATA_SLOT", i, button.Raw, button.Description);
}
CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
}
#endregion
}
}

View File

@ -1,92 +0,0 @@
namespace LemonUI.Scaleform
{
/// <summary>
/// Loading screen like the transition between story mode and online.
/// </summary>
public class LoadingScreen : BaseScaleform
{
#region Public Properties
/// <summary>
/// The title of the loading screen.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The subtitle of the loading screen.
/// </summary>
public string Subtitle { get; set; }
/// <summary>
/// The description of the loading screen.
/// </summary>
public string Description { get; set; }
/// <summary>
/// The Texture Dictionary (TXD) where the texture is loaded.
/// </summary>
public string Dictionary { get; private set; }
/// <summary>
/// The texture in the dictionary.
/// </summary>
public string Texture { get; private set; }
#endregion
#region Constructors
/// <summary>
/// Creates a new GTA Online like loading screen with no image.
/// </summary>
/// <param name="title">The title of the screen.</param>
/// <param name="subtitle">The subtitle of the screen.</param>
/// <param name="description">The description of the screen.</param>
public LoadingScreen(string title, string subtitle, string description) : this(title, subtitle, description, string.Empty, string.Empty)
{
}
/// <summary>
/// Creates a new GTA Online like loading screen with a custom texture.
/// </summary>
/// <param name="title">The title of the screen.</param>
/// <param name="subtitle">The subtitle of the screen.</param>
/// <param name="description">The description of the screen.</param>
/// <param name="dictionary">The dictionary where the texture is located.</param>
/// <param name="texture">The texture to use on the right.</param>
public LoadingScreen(string title, string subtitle, string description, string dictionary, string texture) : base("GTAV_ONLINE")
{
// Tell the Scaleform to use the online loading screen
CallFunction("HIDE_ONLINE_LOGO");
CallFunction("SETUP_BIGFEED", false);
// Save the values
Title = title;
Subtitle = subtitle;
Description = description;
Dictionary = dictionary;
Texture = texture;
// And send them back to the scaleform
Update();
}
#endregion
#region Public Functions
/// <summary>
/// Changes the texture shown on the loading screen.
/// </summary>
/// <param name="dictionary">The Texture Dictionary or TXD.</param>
/// <param name="texture">The Texture name.</param>
public void ChangeTexture(string dictionary, string texture)
{
Dictionary = dictionary;
Texture = texture;
Update();
}
/// <summary>
/// Updates the Title, Description and Image of the loading screen.
/// </summary>
public override void Update()
{
CallFunction("SET_BIGFEED_INFO", "footerStr", Description, 0, Dictionary, Texture, Subtitle, "urlDeprecated", Title);
}
#endregion
}
}

View File

@ -1,57 +0,0 @@
namespace LemonUI.Scaleform
{
/// <summary>
/// A warning pop-up.
/// </summary>
public class PopUp : BaseScaleform
{
#region Properties
/// <summary>
/// The title of the Pop-up.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The subtitle of the Pop-up.
/// </summary>
public string Subtitle { get; set; }
/// <summary>
/// The prompt of the Pop-up.
/// </summary>
public string Prompt { get; set; }
/// <summary>
/// If the black background should be shown.
/// </summary>
public bool ShowBackground { get; set; } = true;
/// <summary>
/// The error message to show.
/// </summary>
public string Error { get; set; }
#endregion
#region Constructors
/// <summary>
/// Creates a new Pop-up instance.
/// </summary>
public PopUp() : base("POPUP_WARNING")
{
}
#endregion
#region Functions
/// <summary>
/// Updates the texts of the Pop-up.
/// </summary>
public override void Update()
{
// first parameter "msecs" is unused
CallFunction("SHOW_POPUP_WARNING", 0, Title, Subtitle, Prompt, ShowBackground, 0, Error);
}
#endregion
}
}

View File

@ -1,284 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.Native;
using CitizenFX.Core.UI;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage;
using Rage.Native;
using Control = Rage.GameControl;
#elif SHVDN3
using GTA;
using GTA.Native;
using GTA.UI;
#endif
using LemonUI.Extensions;
using System;
using System.Drawing;
namespace LemonUI
{
/// <summary>
/// Represents the internal alignment of screen elements.
/// </summary>
public enum GFXAlignment
{
/// <summary>
/// Vertical Alignment to the Bottom.
/// </summary>
Bottom = 66,
/// <summary>
/// Vertical Alignment to the Top.
/// </summary>
Top = 84,
/// <summary>
/// Centered Vertically or Horizontally.
/// </summary>
Center = 67,
/// <summary>
/// Horizontal Alignment to the Left.
/// </summary>
Left = 76,
/// <summary>
/// Horizontal Alignment to the Right.
/// </summary>
Right = 82,
}
/// <summary>
/// Contains a set of tools to work with the screen information.
/// </summary>
public static class Screen
{
/// <summary>
/// The Aspect Ratio of the screen resolution.
/// </summary>
public static float AspectRatio
{
get
{
#if FIVEM
return API.GetAspectRatio(false);
#elif RAGEMP
return Invoker.Invoke<float>(Natives.GetAspectRatio);
#elif RPH
return NativeFunction.CallByHash<float>(0xF1307EF624A80D87, false);
#elif SHVDN3
return Function.Call<float>(Hash._GET_ASPECT_RATIO, false);
#endif
}
}
/// <summary>
/// The location of the cursor on screen between 0 and 1.
/// </summary>
public static PointF CursorPositionRelative
{
get
{
#if FIVEM
float cursorX = API.GetControlNormal(0, (int)Control.CursorX);
float cursorY = API.GetControlNormal(0, (int)Control.CursorY);
#elif RAGEMP
float cursorX = Invoker.Invoke<float>(Natives.GetControlNormal, 0, (int)Control.CursorX);
float cursorY = Invoker.Invoke<float>(Natives.GetControlNormal, 0, (int)Control.CursorY);
#elif RPH
float cursorX = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.CursorX);
float cursorY = NativeFunction.CallByHash<float>(0xEC3C9B8D5327B563, 0, (int)Control.CursorY);
#elif SHVDN3
float cursorX = Function.Call<float>(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorX);
float cursorY = Function.Call<float>(Hash.GET_CONTROL_NORMAL, 0, (int)Control.CursorY);
#endif
return new PointF(cursorX, cursorY);
}
}
/// <summary>
/// Converts a relative resolution into one scaled to 1080p.
/// </summary>
/// <param name="relativeX">The relative value of X.</param>
/// <param name="relativeY">The relative value of Y.</param>
/// <param name="absoluteX">The value of X scaled to 1080p.</param>
/// <param name="absoluteY">The value of Y scaled to 1080p.</param>
public static void ToAbsolute(float relativeX, float relativeY, out float absoluteX, out float absoluteY)
{
// Get the real width based on the aspect ratio
float width = 1080f * AspectRatio;
// And save the correct values
absoluteX = width * relativeX;
absoluteY = 1080f * relativeY;
}
/// <summary>
/// Converts a 1080p-based resolution into relative values.
/// </summary>
/// <param name="absoluteX">The 1080p based X coord.</param>
/// <param name="absoluteY">The 1080p based Y coord.</param>
/// <param name="relativeX">The value of X converted to relative.</param>
/// <param name="relativeY">The value of Y converted to relative.</param>
public static void ToRelative(float absoluteX, float absoluteY, out float relativeX, out float relativeY)
{
// Get the real width based on the aspect ratio
float width = 1080f * AspectRatio;
// And save the correct values
relativeX = absoluteX / width;
relativeY = absoluteY / 1080f;
}
/// <summary>
/// Checks if the cursor is inside of the specified area.
/// </summary>
/// <remarks>
/// This function takes values scaled to 1080p and is aware of the alignment set via SET_SCRIPT_GFX_ALIGN.
/// </remarks>
/// <param name="pos">The start of the area.</param>
/// <param name="size">The size of the area to check.</param>
/// <returns><see langword="true"/> if the cursor is in the specified bounds, <see langword="false"/> otherwise.</returns>
public static bool IsCursorInArea(PointF pos, SizeF size) => IsCursorInArea(pos.X, pos.Y, size.Width, size.Height);
/// <summary>
/// Checks if the cursor is inside of the specified area.
/// </summary>
/// <remarks>
/// This function takes values scaled to 1080p and is aware of the alignment set via SET_SCRIPT_GFX_ALIGN.
/// </remarks>
/// <param name="x">The start X position.</param>
/// <param name="y">The start Y position.</param>
/// <param name="width">The height of the search area from X.</param>
/// <param name="height">The height of the search area from Y.</param>
/// <returns><see langword="true"/> if the cursor is in the specified bounds, <see langword="false"/> otherwise.</returns>
public static bool IsCursorInArea(float x, float y, float width, float height)
{
PointF cursor = CursorPositionRelative;
ToRelative(width, height, out float realWidth, out float realHeight);
PointF realPos = GetRealPosition(x, y).ToRelative();
bool isX = cursor.X >= realPos.X && cursor.X <= realPos.X + realWidth;
bool isY = cursor.Y > realPos.Y && cursor.Y < realPos.Y + realHeight;
return isX && isY;
}
/// <summary>
/// Converts the specified position into one that is aware of <see cref="SetElementAlignment(GFXAlignment, GFXAlignment)"/>.
/// </summary>
/// <param name="og">The original 1080p based position.</param>
/// <returns>A new 1080p based position that is aware of the the Alignment.</returns>
public static PointF GetRealPosition(PointF og) => GetRealPosition(og.X, og.Y);
/// <summary>
/// Converts the specified position into one that is aware of <see cref="SetElementAlignment(GFXAlignment, GFXAlignment)"/>.
/// </summary>
/// <param name="x">The 1080p based X position.</param>
/// <param name="y">The 1080p based Y position.</param>
/// <returns>A new 1080p based position that is aware of the the Alignment.</returns>
public static PointF GetRealPosition(float x, float y)
{
// Convert the resolution to relative
ToRelative(x, y, out float relativeX, out float relativeY);
// Request the real location of the position
float realX = 0, realY = 0;
#if FIVEM
API.GetScriptGfxPosition(relativeX, relativeY, ref realX, ref realY);
#elif RAGEMP
FloatReference argX = new FloatReference();
FloatReference argY = new FloatReference();
Invoker.Invoke<int>(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY);
realX = argX.Value;
realY = argY.Value;
#elif RPH
using (NativePointer argX = new NativePointer())
using (NativePointer argY = new NativePointer())
{
NativeFunction.CallByHash<int>(0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY);
realX = argX.GetValue<float>();
realY = argY.GetValue<float>();
}
#elif SHVDN3
OutputArgument argX = new OutputArgument();
OutputArgument argY = new OutputArgument();
Function.Call((Hash)0x6DD8F5AA635EB4B2, relativeX, relativeY, argX, argY); // _GET_SCRIPT_GFX_POSITION
realX = argX.GetResult<float>();
realY = argY.GetResult<float>();
#endif
// And return it converted to absolute
ToAbsolute(realX, realY, out float absoluteX, out float absoluteY);
return new PointF(absoluteX, absoluteY);
}
/// <summary>
/// Shows the cursor during the current game frame.
/// </summary>
public static void ShowCursorThisFrame()
{
#if FIVEM
API.SetMouseCursorActiveThisFrame();
#elif RAGEMP
Invoker.Invoke(0xAAE7CE1D63167423);
#elif RPH
NativeFunction.CallByHash<int>(0xAAE7CE1D63167423);
#elif SHVDN3
Function.Call(Hash._SET_MOUSE_CURSOR_ACTIVE_THIS_FRAME);
#endif
}
/// <summary>
/// Sets the alignment of game elements like <see cref="Elements.ScaledRectangle"/>, <see cref="Elements.ScaledText"/> and <see cref="Elements.ScaledTexture"/>.
/// </summary>
/// <param name="horizontal">The Horizontal alignment of the items.</param>
/// <param name="vertical">The vertical alignment of the items.</param>
public static void SetElementAlignment(Alignment horizontal, GFXAlignment vertical)
{
// If the enum value is not correct, raise an exception
if (!Enum.IsDefined(typeof(Alignment), horizontal))
{
throw new ArgumentException("Alignment is not one of the allowed values (Left, Right, Center).", nameof(horizontal));
}
// Otherwise, just call the correct function
switch (horizontal)
{
case Alignment.Left:
SetElementAlignment(GFXAlignment.Left, vertical);
break;
case Alignment.Right:
SetElementAlignment(GFXAlignment.Right, vertical);
break;
case Alignment.Center:
SetElementAlignment(GFXAlignment.Right, vertical);
break;
}
}
/// <summary>
/// Sets the alignment of game elements like <see cref="Elements.ScaledRectangle"/>, <see cref="Elements.ScaledText"/> and <see cref="Elements.ScaledTexture"/>.
/// </summary>
/// <param name="horizontal">The Horizontal alignment of the items.</param>
/// <param name="vertical">The vertical alignment of the items.</param>
public static void SetElementAlignment(GFXAlignment horizontal, GFXAlignment vertical)
{
#if FIVEM
API.SetScriptGfxAlign((int)horizontal, (int)vertical);
API.SetScriptGfxAlignParams(0, 0, 0, 0);
#elif RAGEMP
Invoker.Invoke(0xB8A850F20A067EB6, (int)horizontal, (int)vertical);
Invoker.Invoke(0xF5A2C681787E579D, 0, 0, 0, 0);
#elif RPH
NativeFunction.CallByHash<int>(0xB8A850F20A067EB6, (int)horizontal, (int)vertical);
NativeFunction.CallByHash<int>(0xF5A2C681787E579D, 0, 0, 0, 0);
#elif SHVDN3
Function.Call(Hash.SET_SCRIPT_GFX_ALIGN, (int)horizontal, (int)vertical);
Function.Call(Hash.SET_SCRIPT_GFX_ALIGN_PARAMS, 0, 0, 0, 0);
#endif
}
/// <summary>
/// Resets the alignment of the game elements.
/// </summary>
public static void ResetElementAlignment()
{
#if FIVEM
API.ResetScriptGfxAlign();
#elif RAGEMP
Invoker.Invoke(0xE3A3DB414A373DAB);
#elif RPH
NativeFunction.CallByHash<int>(0xE3A3DB414A373DAB);
#elif SHVDN3
Function.Call(Hash.RESET_SCRIPT_GFX_ALIGN);
#endif
}
}
}

View File

@ -1,65 +0,0 @@
#if FIVEM
using CitizenFX.Core;
using CitizenFX.Core.Native;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage;
using Rage.Native;
#elif SHVDN3
using GTA;
using GTA.Native;
#endif
namespace LemonUI
{
/// <summary>
/// Contains information for a Game Sound that is played at specific times.
/// </summary>
public class Sound
{
/// <summary>
/// The Set where the sound is located.
/// </summary>
public string Set { get; set; }
/// <summary>
/// The name of the sound file.
/// </summary>
public string File { get; set; }
/// <summary>
/// Creates a new <see cref="Sound"/> class with the specified Sound Set and File.
/// </summary>
/// <param name="set">The Set where the sound is located.</param>
/// <param name="file">The name of the sound file.</param>
public Sound(string set, string file)
{
Set = set;
File = file;
}
/// <summary>
/// Plays the sound for the local <see cref="Player"/>.
/// </summary>
public void PlayFrontend()
{
#if FIVEM
API.PlaySoundFrontend(-1, File, Set, false);
int id = API.GetSoundId();
API.ReleaseSoundId(id);
#elif RAGEMP
Invoker.Invoke(Natives.PlaySoundFrontend, -1, File, Set, false);
int id = Invoker.Invoke<int>(Natives.GetSoundId);
Invoker.Invoke(Natives.ReleaseSoundId, id);
#elif RPH
NativeFunction.CallByHash<int>(0x67C540AA08E4A6F5, -1, File, Set, false);
int id = NativeFunction.CallByHash<int>(0x430386FE9BF80B45);
NativeFunction.CallByHash<int>(0x353FC880830B88FA, id);
#elif SHVDN3
Function.Call(Hash.PLAY_SOUND_FRONTEND, -1, File, Set, false);
int id = Function.Call<int>(Hash.GET_SOUND_ID);
Function.Call(Hash.RELEASE_SOUND_ID, id);
#endif
}
}
}

View File

@ -1,23 +0,0 @@
namespace LemonUI.TimerBars
{
/// <summary>
/// The spacing of the objectives in the timer bar.
/// </summary>
public enum ObjectiveSpacing
{
/// <summary>
/// The objectives will be equally spaced.
/// </summary>
/// <remarks>
/// If you have way too many objectives and not enough width, you might end up with objectives overlapping each other.
/// </remarks>
Equal = 0,
/// <summary>
/// The items will all have the same spacing.
/// </summary>
/// <remarks>
/// If you have way too many objectives and not enough width, the objectives might end up outside of the timer bar.
/// </remarks>
Fixed = 1
}
}

View File

@ -1,151 +0,0 @@
#if FIVEM
using CitizenFX.Core.UI;
#elif RAGEMP
using RAGE.Game;
#elif SHVDN3
using GTA.UI;
#endif
using LemonUI.Elements;
using System.Drawing;
namespace LemonUI.TimerBars
{
/// <summary>
/// Represents a Bar with text information shown in the bottom right.
/// </summary>
public class TimerBar : IDrawable
{
#region Constant Fields
/// <summary>
/// The separation between the different timer bars.
/// </summary>
internal const float separation = 6.25f;
/// <summary>
/// The width of the background.
/// </summary>
internal const float backgroundWidth = 220;
/// <summary>
/// The height of the background.
/// </summary>
internal const float backgroundHeight = 37;
#endregion
#region Private Fields
private string rawTitle = string.Empty;
private string rawInfo = string.Empty;
#endregion
#region Internal Fields
/// <summary>
/// The background of the timer bar.
/// </summary>
internal protected readonly ScaledTexture background = new ScaledTexture("timerbars", "all_black_bg")
{
Color = Color.FromArgb(160, 255, 255, 255)
};
/// <summary>
/// The title of the timer bar.
/// </summary>
internal protected readonly ScaledText title = new ScaledText(PointF.Empty, string.Empty, 0.29f)
{
Alignment = Alignment.Right,
WordWrap = 1000
};
/// <summary>
/// The information of the Timer Bar.
/// </summary>
internal protected readonly ScaledText info = new ScaledText(PointF.Empty, string.Empty, 0.5f)
{
Alignment = Alignment.Right,
WordWrap = 1000
};
#endregion
#region Public Properties
/// <summary>
/// The title of the bar, shown on the left.
/// </summary>
public string Title
{
get => rawTitle;
set
{
rawTitle = value;
title.Text = value.ToUpperInvariant();
}
}
/// <summary>
/// The information shown on the right.
/// </summary>
public string Info
{
get => rawInfo;
set
{
rawInfo = value;
info.Text = value.ToUpperInvariant();
}
}
/// <summary>
/// The Width of the information text.
/// </summary>
public float InfoWidth => info.Width;
/// <summary>
/// The color of the information text.
/// </summary>
public Color Color
{
get => info.Color;
set => info.Color = value;
}
#endregion
#region Constructors
/// <summary>
/// Creates a new <see cref="TimerBar"/> with the specified Title and Value.
/// </summary>
/// <param name="title">The title of the bar.</param>
/// <param name="info">The information shown on the bar.</param>
public TimerBar(string title, string info)
{
Title = title;
Info = info;
}
#endregion
#region Public Functions
/// <summary>
/// Recalculates the position of the timer bar elements based on the location of it on the screen.
/// </summary>
/// <param name="pos">The Top Left position of the Timer Bar.</param>
public virtual void Recalculate(PointF pos)
{
background.Position = pos;
background.Size = new SizeF(backgroundWidth, backgroundHeight);
title.Position = new PointF(pos.X + 91, pos.Y + 8);
info.Position = new PointF(pos.X + 218, pos.Y - 3);
}
/// <summary>
/// Draws the timer bar information.
/// </summary>
public virtual void Draw()
{
background.Draw();
title.Draw();
info.Draw();
}
#endregion
}
}

View File

@ -1,174 +0,0 @@
#if FIVEM
using CitizenFX.Core.UI;
#elif RAGEMP
using RAGE.Game;
#elif RPH
using Rage;
using Rage.Native;
#elif SHVDN3
using GTA.UI;
#endif
using System;
using System.Collections.Generic;
using System.Drawing;
namespace LemonUI.TimerBars
{
/// <summary>
/// A collection or Set of <see cref="TimerBar"/>.
/// </summary>
public class TimerBarCollection : IContainer<TimerBar>
{
#region Public Properties
/// <summary>
/// If this collection of Timer Bars is visible to the user.
/// </summary>
public bool Visible { get; set; } = true;
/// <summary>
/// The <see cref="TimerBar"/>s that are part of this collection.
/// </summary>
public List<TimerBar> TimerBars { get; } = new List<TimerBar>();
#endregion
#region Constructors
/// <summary>
/// Creates a new collection of Timer Bars.
/// </summary>
/// <param name="bars"></param>
public TimerBarCollection(params TimerBar[] bars)
{
TimerBars.AddRange(bars);
Recalculate();
}
#endregion
#region Public Functions
/// <summary>
/// Adds a <see cref="TimerBar"/> onto this collection.
/// </summary>
/// <param name="bar">The <see cref="TimerBar"/> to add.</param>
public void Add(TimerBar bar)
{
// If the item is already on the list, raise an exception
if (TimerBars.Contains(bar))
{
throw new InvalidOperationException("The item is already part of the menu.");
}
// Also raise an exception if is null
if (bar == null)
{
throw new ArgumentNullException(nameof(bar));
}
// If we got here, add it
TimerBars.Add(bar);
// And recalculate the positions of the existing items
Recalculate();
}
/// <summary>
/// Removes a <see cref="TimerBar"/> from the Collection.
/// </summary>
/// <param name="bar">The <see cref="TimerBar"/> to remove.</param>
public void Remove(TimerBar bar)
{
// If the bar is not present, return
if (!TimerBars.Contains(bar))
{
return;
}
// Otherwise, remove it
TimerBars.Remove(bar);
// And recalculate the positions
Recalculate();
}
/// <summary>
/// Removes all of the <see cref="TimerBar"/> that match the function.
/// </summary>
/// <param name="func">The function to check the <see cref="TimerBar"/>.</param>
public void Remove(Func<TimerBar, bool> func)
{
// Iterate over the timer bars
foreach (TimerBar bar in new List<TimerBar>(TimerBars))
{
// If it matches the function, remove it
if (func(bar))
{
TimerBars.Remove(bar);
}
}
// Finally, recalculate the positions
Recalculate();
}
/// <summary>
/// Removes all of the <see cref="TimerBar"/> in this collection.
/// </summary>
public void Clear() => TimerBars.Clear();
/// <summary>
/// Checks if the <see cref="TimerBar"/> is part of this collection.
/// </summary>
/// <param name="bar">The <see cref="TimerBar"/> to check.</param>
public bool Contains(TimerBar bar) => TimerBars.Contains(bar);
/// <summary>
/// Recalculates the positions and sizes of the <see cref="TimerBar"/>.
/// </summary>
public void Recalculate()
{
// Get the position of 0,0 while staying safe zone aware
Screen.SetElementAlignment(GFXAlignment.Right, GFXAlignment.Bottom);
PointF pos = Screen.GetRealPosition(PointF.Empty);
Screen.ResetElementAlignment();
// Iterate over the existing timer bars and save the count
int count = 0;
foreach (TimerBar timerBar in TimerBars)
{
// And send them to the timer bar
timerBar.Recalculate(new PointF(pos.X - TimerBar.backgroundWidth, pos.Y - (TimerBar.backgroundHeight * (TimerBars.Count - count)) - (TimerBar.separation * (TimerBars.Count - count - 1))));
// Finish by increasing the total count of items
count++;
}
}
/// <summary>
/// Draws the known timer bars.
/// </summary>
public void Process()
{
// If there are no timer bars or the collection is disabled, return
if (TimerBars.Count == 0 || !Visible)
{
return;
}
// Hide the texts in the bottom right corner of the screen
#if FIVEM
CitizenFX.Core.UI.Screen.Hud.HideComponentThisFrame(HudComponent.AreaName);
CitizenFX.Core.UI.Screen.Hud.HideComponentThisFrame(HudComponent.StreetName);
CitizenFX.Core.UI.Screen.Hud.HideComponentThisFrame(HudComponent.VehicleName);
#elif RAGEMP
Invoker.Invoke(Natives.HideHudComponentThisFrame, HudComponent.AreaName);
Invoker.Invoke(Natives.HideHudComponentThisFrame, HudComponent.StreetName);
Invoker.Invoke(Natives.HideHudComponentThisFrame, HudComponent.VehicleName);
#elif RPH
NativeFunction.CallByHash<int>(0x6806C51AD12B83B8, 7);
NativeFunction.CallByHash<int>(0x6806C51AD12B83B8, 9);
NativeFunction.CallByHash<int>(0x6806C51AD12B83B8, 6);
#elif SHVDN3
Hud.HideComponentThisFrame(HudComponent.AreaName);
Hud.HideComponentThisFrame(HudComponent.StreetName);
Hud.HideComponentThisFrame(HudComponent.VehicleName);
#endif
// Draw the existing timer bars
foreach (TimerBar timerBar in TimerBars)
{
timerBar.Draw();
}
}
#endregion
}
}

View File

@ -1,220 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using LemonUI.Elements;
namespace LemonUI.TimerBars
{
/// <summary>
/// A timer bar for a specific amount of objectives.
/// </summary>
public class TimerBarObjective : TimerBar
{
#region Fields
private const float width = 20;
private const float height = 20;
private static readonly Color colorWhite = Color.FromArgb(255, 255, 255);
private static readonly Color colorCompleted = Color.FromArgb(101, 180, 212);
private readonly List<ScaledTexture> objectives = new List<ScaledTexture>();
private PointF lastPosition = default;
private int count = 1;
private int completed = 0;
private Color colorSet = colorCompleted;
private ObjectiveSpacing objectiveSpacing = ObjectiveSpacing.Equal;
#endregion
#region Properties
/// <summary>
/// The number of objectives shown in the timer bar.
/// </summary>
public int Count
{
get => count;
set
{
if (count == value)
{
return;
}
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "The number of objectives can't be under or equal to zero.");
}
count = value;
UpdateObjectiveCount();
}
}
/// <summary>
/// The number of completed objectives.
/// </summary>
public int Completed
{
get => completed;
set
{
if (completed == value)
{
return;
}
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "The number of completed objectives can't be under zero.");
}
if (value > Count)
{
throw new ArgumentOutOfRangeException(nameof(value), "The number of completed objectives can't be over the total number of objectives.");
}
completed = value;
UpdateObjectiveColors();
}
}
/// <summary>
/// The color used for completed objectives.
/// </summary>
public Color CompletedColor
{
get => colorSet;
set
{
if (colorSet == value)
{
return;
}
colorSet = value;
UpdateObjectiveColors();
}
}
public ObjectiveSpacing Spacing
{
get => objectiveSpacing;
set
{
if (objectiveSpacing == value)
{
return;
}
objectiveSpacing = value;
Recalculate(lastPosition);
}
}
#endregion
#region Constructors
/// <summary>
/// Creates a new timer bar used to show objectives.
/// </summary>
public TimerBarObjective(string title) : base(title, string.Empty)
{
UpdateObjectiveCount();
}
#endregion
#region Tools
private void UpdateObjectiveCount()
{
// just to make sure
if (completed > count)
{
completed = count;
}
objectives.Clear();
for (int i = 0; i < count; i++)
{
objectives.Add(new ScaledTexture("timerbars", "circle_checkpoints"));
}
UpdateObjectiveColors();
}
private void UpdateObjectiveColors()
{
for (int i = 0; i < objectives.Count; i++)
{
ScaledTexture texture = objectives[i];
texture.Color = i < completed ? colorSet : colorWhite;
}
}
#endregion
#region Functions
/// <summary>
/// Draws the objective timer bar.
/// </summary>
public override void Draw()
{
background.Draw();
title.Draw();
foreach (ScaledTexture texture in objectives)
{
texture.Draw();
}
}
/// <inheritdoc/>
public override void Recalculate(PointF pos)
{
lastPosition = pos;
base.Recalculate(pos);
const float safe = width + 5;
float startY = pos.Y + (backgroundHeight * 0.5f) - (height * 0.5f);
switch (objectiveSpacing)
{
case ObjectiveSpacing.Equal:
{
const float half = backgroundWidth * 0.5f;
float startX = pos.X + half;
float spacingWidth = (half - safe) / (objectives.Count - 1);
for (int i = 0; i < objectives.Count; i++)
{
ScaledTexture texture = objectives[i];
texture.Size = new SizeF(width, height);
texture.Position = new PointF(startX + (spacingWidth * i), startY);
}
break;
}
case ObjectiveSpacing.Fixed:
{
float startX = pos.X + backgroundWidth - safe - (width * (count - 1));
for (int i = 0; i < objectives.Count; i++)
{
ScaledTexture texture = objectives[i];
texture.Size = new SizeF(width, height);
texture.Position = new PointF(startX + (i * width), startY);
}
break;
}
}
}
#endregion
}
}

View File

@ -1,123 +0,0 @@
using LemonUI.Elements;
using System;
using System.Drawing;
namespace LemonUI.TimerBars
{
/// <summary>
/// Represents a Timer Bar that shows the progress of something.
/// </summary>
public class TimerBarProgress : TimerBar
{
#region Constant Fields
private const float barWidth = 108;
private const float barHeight = 15;
#endregion
#region Private Fields
private float progress = 100;
#endregion
#region Internal Fields
/// <summary>
/// The background of the Progress Bar.
/// </summary>
internal protected readonly ScaledRectangle barBackground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(255, 139, 0, 0)
};
/// <summary>
/// The foreground of the Progress Bar.
/// </summary>
internal protected readonly ScaledRectangle barForeground = new ScaledRectangle(PointF.Empty, SizeF.Empty)
{
Color = Color.FromArgb(255, 255, 0, 0)
};
#endregion
#region Public Properties
/// <summary>
/// The progress of the bar.
/// </summary>
public float Progress
{
get => progress;
set
{
if (value < 0 || value > 100)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
progress = value;
barForeground.Size = new SizeF(barWidth * (value * 0.01f), barHeight);
}
}
/// <summary>
/// The Foreground color of the Progress bar.
/// </summary>
public Color ForegroundColor
{
get => barForeground.Color;
set => barForeground.Color = value;
}
/// <summary>
/// The Background color of the Progress bar.
/// </summary>
public Color BackgroundColor
{
get => barBackground.Color;
set => barBackground.Color = value;
}
#endregion
#region Constructors
/// <summary>
/// Creates a new <see cref="TimerBarProgress"/> with the specified title.
/// </summary>
/// <param name="title">The title of the bar.</param>
public TimerBarProgress(string title) : base(title, string.Empty)
{
}
#endregion
#region Public Functions
/// <summary>
/// Recalculates the position of the timer bar elements based on the location of it on the screen.
/// </summary>
/// <param name="pos">The Top Left position of the Timer Bar.</param>
public override void Recalculate(PointF pos)
{
// Recalculate the base elements
base.Recalculate(pos);
// And set the size and position of the progress bar
PointF barPos = new PointF(pos.X + 103, pos.Y + 12);
barBackground.Position = barPos;
barBackground.Size = new SizeF(barWidth, barHeight);
barForeground.Position = barPos;
barForeground.Size = new SizeF(barWidth * (progress * 0.01f), barHeight);
}
/// <summary>
/// Draws the TimerBar.
/// </summary>
public override void Draw()
{
background.Draw();
title.Draw();
barBackground.Draw();
barForeground.Draw();
}
#endregion
}
}

View File

@ -1,16 +1,16 @@
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Diagnostics;
using GTA;
using GTA.Math;
using GTA.Native;
using RageCoop.Client.Menus;
using RageCoop.Core;
using GTA;
using GTA.Native;
using GTA.Math;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace RageCoop.Client
{
@ -19,36 +19,49 @@ namespace RageCoop.Client
/// </summary>
internal class Main : Script
{
private bool _gameLoaded = false;
internal static readonly string CurrentVersion = "V0_5_0";
internal static Version Version = typeof(Main).Assembly.GetName().Version;
internal static int LocalPlayerID=0;
internal static int LocalPlayerID = 0;
internal static RelationshipGroup SyncedPedsGroup;
internal static new Settings Settings = null;
internal static Scripting.BaseScript BaseScript=new Scripting.BaseScript();
internal static Scripting.BaseScript BaseScript = new Scripting.BaseScript();
#if !NON_INTERACTIVE
#endif
internal static Chat MainChat = null;
internal static Stopwatch Counter = new Stopwatch();
internal static Logger Logger = null;
internal static ulong Ticked = 0;
internal static Scripting.Resources Resources=null;
private static List<Func<bool>> QueuedActions = new List<Func<bool>>();
internal static Vector3 PlayerPosition;
internal static Scripting.Resources Resources = null;
private static readonly List<Func<bool>> QueuedActions = new List<Func<bool>>();
public static Worker Worker;
/// <summary>
/// Don't use it!
/// </summary>
public Main()
{
Settings = Util.ReadSettings();
Logger=new Logger()
Worker = new Worker("RageCoop.Client.Main.Worker", Logger);
try
{
LogPath=$"RageCoop\\RageCoop.Client.log",
UseConsole=false,
Settings = Util.ReadSettings();
}
catch
{
GTA.UI.Notification.PostTicker("Malformed configuration, overwriting with default values...", false);
Settings = new Settings();
Util.SaveSettings();
}
Directory.CreateDirectory(Settings.DataDirectory);
Logger = new Logger()
{
LogPath = $"{Settings.DataDirectory}\\RageCoop.Client.log",
UseConsole = false,
#if DEBUG
LogLevel = 0,
#else
@ -64,37 +77,39 @@ namespace RageCoop.Client
{
return;
}
if (!_gameLoaded)
{
GTA.UI.Notification.Show("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
GTA.UI.Notification.PostTicker("~r~Please update your GTA5 to v1.0.1290 or newer!", true);
_gameLoaded = true;
}
};
return;
}
BaseScript.OnStart();
SyncedPedsGroup=World.AddRelationshipGroup("SYNCPED");
SyncedPedsGroup = World.AddRelationshipGroup("SYNCPED");
Game.Player.Character.RelationshipGroup.SetRelationshipBetweenGroups(SyncedPedsGroup, Relationship.Neutral, true);
#if !NON_INTERACTIVE
#endif
MainChat = new Chat();
Tick += OnTick;
Tick += (s,e) => { Scripting.API.Events.InvokeTick(); };
Tick += (s, e) => { Scripting.API.Events.InvokeTick(); };
KeyDown += OnKeyDown;
Aborted += (object sender, EventArgs e) => CleanUp();
KeyDown += (s, e) => { Scripting.API.Events.InvokeKeyDown(s, e); };
KeyUp += (s, e) => { Scripting.API.Events.InvokeKeyUp(s, e); };
Aborted += (object sender, EventArgs e) => Disconnected("Abort");
Util.NativeMemory();
Counter.Restart();
}
#if DEBUG
private ulong _lastDebugData;
private int _debugBytesSend;
private int _debugBytesReceived;
#endif
public static Ped P;
public static float FPS;
private bool _lastDead;
private void OnTick(object sender, EventArgs e)
{
P = Game.Player.Character;
PlayerPosition = P.ReadPosition();
FPS = Game.FPS;
if (Game.IsLoading)
{
return;
@ -102,22 +117,23 @@ namespace RageCoop.Client
else if (!_gameLoaded && (_gameLoaded = true))
{
#if !NON_INTERACTIVE
GTA.UI.Notification.Show(GTA.UI.NotificationIcon.AllPlayersConf, "RAGECOOP","Welcome!", $"Press ~g~{Main.Settings.MenuKey}~s~ to open the menu.");
GTA.UI.Notification.PostMessageText($"Press ~g~{Settings.MenuKey}~s~ to open the menu.", new GTA.Graphics.TextureAsset("CHAR_ALL_PLAYERS_CONF", "CHAR_ALL_PLAYERS_CONF"), false, GTA.UI.FeedTextIcon.Message, "RAGECOOP", "Welcome!");
#endif
}
#if !NON_INTERACTIVE
CoopMenu.MenuPool.Process();
#endif
DoQueuedActions();
if (!Networking.IsOnServer)
{
return;
}
if (Game.TimeScale!=1)
if (Game.TimeScale != 1)
{
Game.TimeScale=1;
Game.TimeScale = 1;
}
try
{
@ -125,32 +141,17 @@ namespace RageCoop.Client
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
MapLoader.LoadAll();
#if DEBUG
Main.Logger.Error(ex);
#endif
}
if (Networking.ShowNetworkInfo)
{
ulong time = Util.GetTickCount64();
if (time - _lastDebugData > 1000)
{
_lastDebugData = time;
_debugBytesReceived = Networking.Client.Statistics.ReceivedBytes;
_debugBytesSend = Networking.Client.Statistics.SentBytes;
Networking.Client.Statistics.Reset();
}
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 0), $"L: {Networking.Latency * 1000:N0}ms", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {Lidgren.Network.NetUtility.ToHumanReadable(_debugBytesReceived)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60), $"S: {Lidgren.Network.NetUtility.ToHumanReadable(_debugBytesSend)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 30), $"R: {Lidgren.Network.NetUtility.ToHumanReadable(Statistics.BytesDownPerSecond)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
new LemonUI.Elements.ScaledText(new PointF(Screen.PrimaryScreen.Bounds.Width / 2, 60), $"S: {Lidgren.Network.NetUtility.ToHumanReadable(Statistics.BytesUpPerSecond)}/s", 0.5f) { Alignment = GTA.UI.Alignment.Center }.Draw();
}
#endif
MainChat.Tick();
PlayerList.Tick();
@ -158,33 +159,34 @@ namespace RageCoop.Client
{
Function.Call(Hash.PAUSE_DEATH_ARREST_RESTART, true);
Function.Call(Hash.IGNORE_NEXT_RESTART, true);
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
Function.Call(Hash.FORCE_GAME_STATE_PLAYING);
Function.Call(Hash.TERMINATE_ALL_SCRIPTS_WITH_THIS_NAME, "respawn_controller");
var P = Game.Player.Character;
if (P.IsDead)
{
Function.Call(Hash.SET_FADE_OUT_AFTER_DEATH, false);
if (P.Health!=1)
if (P.Health != 1)
{
P.Health=1;
Game.Player.WantedLevel=0;
P.Health = 1;
Game.Player.WantedLevel = 0;
Main.Logger.Debug("Player died.");
Scripting.API.Events.InvokePlayerDied(KillMessage());
}
GTA.UI.Screen.StopEffects();
}
else
{
Function.Call(Hash.DISPLAY_HUD, true);
}
}
else if (P.IsDead && !_lastDead)
{
Scripting.API.Events.InvokePlayerDied(KillMessage());
}
_lastDead = P.IsDead;
Ticked++;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (MainChat.Focused)
@ -194,12 +196,26 @@ namespace RageCoop.Client
}
if (Networking.IsOnServer)
{
if (Voice.WasInitialized())
{
if (Game.IsControlPressed(GTA.Control.PushToTalk))
{
Voice.StartRecording();
return;
}
else if (Voice.IsRecording())
{
Voice.StopRecording();
return;
}
}
if (Game.IsControlPressed(GTA.Control.FrontendPause))
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
}
if (Game.IsControlPressed(GTA.Control.FrontendPauseAlternate)&&Settings.DisableAlternatePause)
if (Game.IsControlPressed(GTA.Control.FrontendPauseAlternate) && Settings.DisableAlternatePause)
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
return;
@ -213,8 +229,8 @@ namespace RageCoop.Client
{
if (x.Visible)
{
CoopMenu.LastMenu=x;
x.Visible=false;
CoopMenu.LastMenu = x;
x.Visible = false;
}
});
}
@ -223,14 +239,6 @@ namespace RageCoop.Client
CoopMenu.LastMenu.Visible = true;
}
}
else if (Game.IsControlJustPressed(GTA.Control.MultiplayerInfo))
{
if (Networking.IsOnServer)
{
ulong currentTimestamp = Util.GetTickCount64();
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000 ? (currentTimestamp - 6000) : currentTimestamp;
}
}
else if (Game.IsControlJustPressed(GTA.Control.MpTextChatAll))
{
if (Networking.IsOnServer)
@ -238,10 +246,19 @@ namespace RageCoop.Client
MainChat.Focused = true;
}
}
else if (e.KeyCode==Settings.PassengerKey)
else if (MainChat.Focused) { return; }
else if (Game.IsControlJustPressed(GTA.Control.MultiplayerInfo))
{
if (Networking.IsOnServer)
{
ulong currentTimestamp = Util.GetTickCount64();
PlayerList.Pressed = (currentTimestamp - PlayerList.Pressed) < 5000 ? (currentTimestamp - 6000) : currentTimestamp;
}
}
else if (e.KeyCode == Settings.PassengerKey)
{
var P = Game.Player.Character;
if (!P.IsInVehicle())
{
if (P.IsTaskActive(TaskType.CTaskEnterVehicle))
@ -250,94 +267,74 @@ namespace RageCoop.Client
}
else
{
var V = World.GetClosestVehicle(P.Position, 50);
var V = World.GetClosestVehicle(P.ReadPosition(), 50);
if (V!=null)
if (V != null)
{
var seat = P.GetNearestSeat(V);
P.Task.EnterVehicle(V, seat);
var p = V.GetPedOnSeat(seat);
if (p != null && !p.IsDead)
{
for (int i = -1; i < V.PassengerCapacity; i++)
{
seat = (VehicleSeat)i;
p = V.GetPedOnSeat(seat);
if (p == null || p.IsDead)
{
break;
}
}
}
P.Task.EnterVehicle(V, seat, -1, 5, EnterVehicleFlags.None);
}
}
}
}
}
public static void CleanUp()
internal static void Connected()
{
MainChat.Clear();
EntityPool.Cleanup();
PlayerList.Cleanup();
Main.LocalPlayerID=default;
}
internal static readonly Dictionary<ulong, byte> CheckNativeHash = new Dictionary<ulong, byte>()
{
{ 0xD49F9B0955C367DE, 1 }, // Entities
{ 0xEF29A16337FACADB, 1 }, //
{ 0xB4AC7D0CF06BFE8F, 1 }, //
{ 0x9B62392B474F44A0, 1 }, //
{ 0x7DD959874C1FD534, 1 }, //
{ 0xAF35D0D2583051B0, 2 }, // Vehicles
{ 0x63C6CCA8E68AE8C8, 2 }, //
{ 0x509D5878EB39E842, 3 }, // Props
{ 0x9A294B2138ABB884, 3 }, //
{ 0x46818D79B1F7499A, 4 }, // Blips
{ 0x5CDE92C702A8FCE7, 4 }, //
{ 0xBE339365C863BD36, 4 }, //
{ 0x5A039BB0BCA604B6, 4 }, //
{ 0x0134F0835AB6BFCB, 5 } // Checkpoints
};
internal static Dictionary<int, byte> ServerItems = new Dictionary<int, byte>();
internal static void CleanUpWorld()
{
if (ServerItems.Count == 0)
Memory.ApplyPatches();
if (Settings.Voice && !Voice.WasInitialized())
{
return;
Voice.Init();
}
lock (ServerItems)
QueueAction(() =>
{
foreach (KeyValuePair<int, byte> item in ServerItems)
WorldThread.Traffic(!Settings.DisableTraffic);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, true);
CoopMenu.ConnectedMenuSetting();
MainChat.Init();
GTA.UI.Notification.PostTicker("~g~Connected!", false);
});
Logger.Info(">> Connected <<");
}
public static void Disconnected(string reason)
{
Logger.Info($">> Disconnected << reason: {reason}");
QueueAction(() =>
{
if (MainChat.Focused)
{
try
{
switch (item.Value)
{
case 1:
World.GetAllEntities().FirstOrDefault(x => x.Handle == item.Key)?.Delete();
break;
case 2:
World.GetAllVehicles().FirstOrDefault(x => x.Handle == item.Key)?.Delete();
break;
case 3:
World.GetAllProps().FirstOrDefault(x => x.Handle == item.Key)?.Delete();
break;
case 4:
Blip blip = new Blip(item.Key);
if (blip.Exists())
{
blip.Delete();
}
break;
case 5:
Checkpoint checkpoint = new Checkpoint(item.Key);
if (checkpoint.Exists())
{
checkpoint.Delete();
}
break;
}
}
catch
{
GTA.UI.Notification.Show("~r~~h~CleanUpWorld() Error");
Main.Logger.Error($"CleanUpWorld(): ~r~Item {item.Value} cannot be deleted!");
}
MainChat.Focused = false;
}
ServerItems.Clear();
}
PlayerList.Cleanup();
MainChat.Clear();
EntityPool.Cleanup();
WorldThread.Traffic(true);
Function.Call(Hash.SET_ENABLE_VEHICLE_SLIPSTREAMING, false);
CoopMenu.DisconnectedMenuSetting();
if (reason != "Abort")
GTA.UI.Notification.PostTicker("~r~Disconnected: " + reason, false);
LocalPlayerID = default;
});
Memory.RestorePatches();
DownloadManager.Cleanup();
Voice.ClearAll();
Resources.Unload();
}
private static void DoQueuedActions()
{
lock (QueuedActions)
@ -351,9 +348,11 @@ namespace RageCoop.Client
QueuedActions.Remove(action);
}
}
catch(Exception ex)
catch (Exception ex)
{
#if DEBUG
Logger.Error(ex);
#endif
QueuedActions.Remove(action);
}
}
@ -361,9 +360,9 @@ namespace RageCoop.Client
}
/// <summary>
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// Queue an action to be executed on next tick, allowing you to call scripting API from another thread.
/// </summary>
/// <param name="a"> The action to be executed, must return a bool indicating whether the action cane be removed after execution.</param>
/// <param name="a"> An action to be executed with a return value indicating whether the action can be removed after execution.</param>
internal static void QueueAction(Func<bool> a)
{
lock (QueuedActions)
@ -375,7 +374,7 @@ namespace RageCoop.Client
{
lock (QueuedActions)
{
QueuedActions.Add(() => { a(); return true; }) ;
QueuedActions.Add(() => { a(); return true; });
}
}
/// <summary>
@ -386,6 +385,24 @@ namespace RageCoop.Client
lock (QueuedActions) { QueuedActions.Clear(); }
}
public static void Delay(Action a, int time)
{
Task.Run(() =>
{
Thread.Sleep(time);
QueueAction(a);
});
}
private string KillMessage()
{
if (P.Killer != null)
{
var killer = EntityPool.GetPedByHandle(P.Killer.Handle);
if (killer != null && killer.ID == killer.Owner.ID)
return $"~h~{PlayerList.GetPlayer(LocalPlayerID).Username}~h~ was killed by ~h~{killer.Owner.Username}~h~ ({P.CauseOfDeath})";
}
return $"~h~{PlayerList.GetPlayer(LocalPlayerID).Username}~h~ died";
}
}
}

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 };
private static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
internal static readonly NativeItem _serverConnectItem = new NativeItem("Connect");
private static readonly NativeItem _aboutItem = new NativeItem("About", "~y~SOURCE~s~~n~" +
"https://github.com/RAGECOOP~n~" +
"~y~VERSION~s~~n~" +
Main.CurrentVersion.Replace("_", ".")) { LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
Main.Version)
{ LeftBadge = new LemonUI.Elements.ScaledTexture("commonmenu", "shop_new_star") };
#endregion
@ -50,10 +50,10 @@ namespace RageCoop.Client.Menus
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0);
_usernameItem.Activated += UsernameActivated;
_passwordItem.Activated+=_passwordActivated;
_passwordItem.Activated += _passwordActivated;
ServerIpItem.Activated += ServerIpActivated;
_serverConnectItem.Activated += (sender, item) => { Networking.ToggleConnection(Main.Settings.LastServerAddress); };
@ -67,14 +67,17 @@ namespace RageCoop.Client.Menus
Menu.AddSubMenu(SettingsMenu.Menu);
Menu.AddSubMenu(DevToolMenu.Menu);
#if DEBUG
Menu.AddSubMenu(DebugMenu.Menu);
#endif
MenuPool.Add(Menu);
MenuPool.Add(SettingsMenu.Menu);
MenuPool.Add(DevToolMenu.Menu);
#if DEBUG
MenuPool.Add(DebugMenu.Menu);
MenuPool.Add(DebugMenu.DiagnosticMenu);
#endif
MenuPool.Add(ServersMenu.Menu);
MenuPool.Add(PopUp);
@ -82,21 +85,32 @@ namespace RageCoop.Client.Menus
}
public static bool ShowPopUp(string prompt, string title,string subtitle,string error,bool showbackground)
public static bool ShowPopUp(string prompt, string title, string subtitle, string error, bool showbackground)
{
PopUp.Prompt=prompt;
PopUp.Title=title;
PopUp.Subtitle=subtitle;
PopUp.Error=error;
PopUp.ShowBackground=showbackground;
PopUp.Visible=true;
PopUp.Prompt = prompt;
PopUp.Title = title;
PopUp.Subtitle = subtitle;
PopUp.Error = error;
PopUp.ShowBackground = showbackground;
PopUp.Visible = true;
Script.Yield();
while (true)
{
Game.DisableAllControlsThisFrame();
MenuPool.Process();
var scaleform = Scaleform.RequestMovie("instructional_buttons");
scaleform.CallFunction("CLEAR_ALL");
scaleform.CallFunction("TOGGLE_MOUSE_BUTTONS", 0);
scaleform.CallFunction("CREATE_CONTAINER");
scaleform.CallFunction("SET_DATA_SLOT", 0, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendAccept, 0), "Continue");
scaleform.CallFunction("SET_DATA_SLOT", 1, Function.Call<string>((Hash)0x0499D7B09FC9B407, 2, (int)Control.FrontendCancel, 0), "Cancel");
scaleform.CallFunction("DRAW_INSTRUCTIONAL_BUTTONS", -1);
scaleform.Render2D();
if (Game.IsControlJustPressed(Control.FrontendAccept))
{
PopUp.Visible=false;
PopUp.Visible = false;
return true;
}
else if (Game.IsControlJustPressed(Control.FrontendCancel))
@ -105,6 +119,8 @@ namespace RageCoop.Client.Menus
return false;
}
Script.Yield();
Game.DisableAllControlsThisFrame();
}
}
public static void UsernameActivated(object a, System.EventArgs b)
@ -122,12 +138,9 @@ namespace RageCoop.Client.Menus
private static void _passwordActivated(object sender, System.EventArgs e)
{
string newPass = Game.GetUserInput(WindowTitle.EnterMessage20, "", 20);
if (!string.IsNullOrWhiteSpace(newPass))
{
Main.Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
Main.Settings.Password = newPass;
Util.SaveSettings();
_passwordItem.AltTitle = new string('*', newPass.Length);
}
public static void ServerIpActivated(object a, System.EventArgs b)
{

View File

@ -1,18 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using LemonUI;
using LemonUI.Menus;
#if DEBUG
using GTA;
using LemonUI.Menus;
using System;
using System.Drawing;
namespace RageCoop.Client
{
internal static class DebugMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings") {
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Debug", "Debug settings")
{
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
@ -21,24 +18,17 @@ namespace RageCoop.Client
UseMouse = false,
Alignment = Main.Settings.FlipMenu ? GTA.UI.Alignment.Right : GTA.UI.Alignment.Left
};
private static NativeItem d1=new NativeItem("PositionPrediction");
public static NativeItem SimulatedLatencyItem = new NativeItem("Simulated network latency", "Simulated network latency in ms (one way)", "0");
public static NativeCheckboxItem ShowOwnerItem = new NativeCheckboxItem("Show entity owner", "Show the owner name of the entity you're aiming at", false);
private static readonly NativeCheckboxItem ShowNetworkInfoItem = new NativeCheckboxItem("Show Network Info", Networking.ShowNetworkInfo);
static DebugMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0);
d1.Activated+=(sender,e) =>
{
try{ SyncParameters.PositioinPredictionDefault =float.Parse(Game.GetUserInput(WindowTitle.EnterMessage20, SyncParameters.PositioinPredictionDefault.ToString(), 20));}
catch { }
Update();
};
Menu.Add(d1);
Menu.AddSubMenu(DiagnosticMenu);
Menu.Opening+=(sender, e) =>Update();
DiagnosticMenu.Opening+=(sender, e) =>
DiagnosticMenu.Opening += (sender, e) =>
{
DiagnosticMenu.Clear();
DiagnosticMenu.Add(new NativeItem("EntityPool", EntityPool.DumpDebug()));
@ -47,15 +37,24 @@ namespace RageCoop.Client
DiagnosticMenu.Add(new NativeItem(pair.Key.ToString(), pair.Value.ToString(), pair.Value.ToString()));
}
};
SimulatedLatencyItem.Activated += (s, e) =>
{
try
{
SimulatedLatencyItem.AltTitle = ((Networking.SimulatedLatency = int.Parse(Game.GetUserInput(SimulatedLatencyItem.AltTitle)) * 0.002f) * 500).ToString();
}
catch (Exception ex) { Main.Logger.Error(ex); }
};
ShowNetworkInfoItem.CheckboxChanged += (s, e) => { Networking.ShowNetworkInfo = ShowNetworkInfoItem.Checked; };
ShowOwnerItem.CheckboxChanged += (s, e) => { Main.Settings.ShowEntityOwnerName = ShowOwnerItem.Checked; Util.SaveSettings(); };
Menu.Add(SimulatedLatencyItem);
Menu.Add(ShowNetworkInfoItem);
Menu.Add(ShowOwnerItem);
Menu.AddSubMenu(DiagnosticMenu);
Update();
}
private static void Update()
{
d1.AltTitle = SyncParameters.PositioinPredictionDefault.ToString();
}
}
}
#endif

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");
@ -26,20 +22,20 @@ namespace RageCoop.Client
static DevToolMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0);
enableItem.Activated+=enableItem_Activated;
enableItem.Checked=false;
enableSecondaryItem.CheckboxChanged+=EnableSecondaryItem_Changed;
enableItem.Activated += enableItem_Activated;
enableItem.Checked = false;
enableSecondaryItem.CheckboxChanged += EnableSecondaryItem_Changed;
secondaryBoneIndexItem.Enabled=false;
clipboardItem.Activated+=ClipboardItem_Activated;
dirItem.ItemChanged+=DirItem_ItemChanged;
secondaryBoneIndexItem.Enabled = false;
clipboardItem.Activated += ClipboardItem_Activated;
dirItem.ItemChanged += DirItem_ItemChanged;
foreach (var d in Enum.GetValues(typeof(MuzzleDir)))
{
dirItem.Items.Add((MuzzleDir)d);
}
dirItem.SelectedIndex=0;
dirItem.SelectedIndex = 0;
Menu.Add(enableItem);
Menu.Add(boneIndexItem);
@ -53,19 +49,19 @@ namespace RageCoop.Client
{
if (enableSecondaryItem.Checked)
{
DevTool.UseSecondary=true;
secondaryBoneIndexItem.Enabled=true;
DevTool.UseSecondary = true;
secondaryBoneIndexItem.Enabled = true;
}
else
{
DevTool.UseSecondary=false;
secondaryBoneIndexItem.Enabled=false;
DevTool.UseSecondary = false;
secondaryBoneIndexItem.Enabled = false;
}
}
private static void DirItem_ItemChanged(object sender, ItemChangedEventArgs<MuzzleDir> e)
{
DevTool.Direction=dirItem.SelectedItem;
DevTool.Direction = dirItem.SelectedItem;
}
private static void ClipboardItem_Activated(object sender, EventArgs e)
@ -77,11 +73,11 @@ namespace RageCoop.Client
{
if (enableItem.Checked)
{
DevTool.ToMark=Game.Player.Character.CurrentVehicle;
DevTool.ToMark = Game.Player.Character.CurrentVehicle;
}
else
{
DevTool.ToMark=null;
DevTool.ToMark = null;
}
}
}

View File

@ -1,36 +1,15 @@
using System;
using System.Net;
using System.Drawing;
using System.Collections.Generic;
using Newtonsoft.Json;
using GTA.UI;
using LemonUI.Menus;
using Newtonsoft.Json;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Net;
using System.Threading;
namespace RageCoop.Client.Menus
{
internal class ServerListClass
{
[JsonProperty("address")]
public string Address { get; set; }
[JsonProperty("port")]
public string Port { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
[JsonProperty("players")]
public int Players { get; set; }
[JsonProperty("maxPlayers")]
public int MaxPlayers { get; set; }
[JsonProperty("country")]
public string Country { get; set; }
}
/// <summary>
/// Don't use it!
@ -51,7 +30,7 @@ namespace RageCoop.Client.Menus
static ServersMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
Menu.BannerText.Color = Color.FromArgb(255, 165, 0);
Menu.Opening += (object sender, System.ComponentModel.CancelEventArgs e) =>
{
@ -59,7 +38,7 @@ namespace RageCoop.Client.Menus
Menu.Add(ResultItem = new NativeItem("Loading..."));
// Prevent freezing
GetServersThread=new Thread(()=> GetAllServers());
GetServersThread = new Thread(() => GetAllServers());
GetServersThread.Start();
};
Menu.Closing += (object sender, System.ComponentModel.CancelEventArgs e) =>
@ -76,10 +55,10 @@ namespace RageCoop.Client.Menus
private static void GetAllServers()
{
List<ServerListClass> serverList = null;
var realUrl = Main.Settings.MasterServer=="[AUTO]" ? DownloadString("https://ragecoop.online/stuff/masterserver") : Main.Settings.MasterServer;
serverList = JsonConvert.DeserializeObject<List<ServerListClass>>(DownloadString(realUrl));
List<ServerInfo> serverList = null;
var realUrl = Main.Settings.MasterServer;
serverList = JsonConvert.DeserializeObject<List<ServerInfo>>(DownloadString(realUrl));
// Need to be processed in main thread
Main.QueueAction(() =>
{
@ -94,17 +73,25 @@ namespace RageCoop.Client.Menus
return;
}
CleanUpList();
foreach (ServerListClass server in serverList)
foreach (ServerInfo server in serverList)
{
string address = $"{server.Address}:{server.Port}";
NativeItem tmpItem = new NativeItem($"[{server.Country}] {server.Name}", $"~b~{address}~s~~n~~g~Version {server.Version}.x~s~") { AltTitle = $"[{server.Players}/{server.MaxPlayers}]" };
string address = $"{server.address}:{server.port}";
NativeItem tmpItem = new NativeItem($"[{server.country}] {server.name}", $"~b~{address}~s~~n~~g~Version {server.version}~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.PostTicker($"~y~Joining ZeroTier network... {server.ztID}", false);
if (ZeroTierHelper.Join(server.ztID) == null)
{
throw new Exception("Failed to obtain ZeroTier network IP");
}
}
Networking.ToggleConnection(address, null, null, PublicKey.FromServerInfo(server));
#if !NON_INTERACTIVE
CoopMenu.ServerIpItem.AltTitle = address;
@ -115,7 +102,11 @@ namespace RageCoop.Client.Menus
}
catch (Exception ex)
{
GTA.UI.Notification.Show($"~r~{ex.Message}");
Notification.PostTicker($"~r~{ex.Message}", false);
if (server.useZT)
{
Notification.PostTicker($"Make sure ZeroTier is correctly installed, download it from https://www.zerotier.com/", false);
}
}
};
Menu.Add(tmpItem);

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,95 @@ 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 _showBlip = new NativeCheckboxItem("Show player blip", "Show other player's blip on map", Main.Settings.ShowPlayerBlip);
private static readonly NativeCheckboxItem _showNametag = new NativeCheckboxItem("Show player nametag", "Show other player's nametag on your screen", Main.Settings.ShowPlayerNameTag);
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());
private static readonly NativeItem _pedSoftLimit = new NativeItem("Ped limit (soft)", "The game won't spawn more NPCs if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldPedSoftLimit.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);
Menu.BannerText.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;
_pedSoftLimit.Activated += PedSoftLimitActivated;
_showBlip.Activated += (s, e) =>
{
Main.Settings.ShowPlayerBlip = _showBlip.Checked;
Util.SaveSettings();
};
_showNametag.Activated += (s, e) =>
{
Main.Settings.ShowPlayerNameTag = _showNametag.Checked;
Util.SaveSettings();
};
Menu.Add(_disableTrafficItem);
Menu.Add(_disablePauseAlt);
Menu.Add(_flipMenuItem);
Menu.Add(_showNetworkInfoItem);
Menu.Add(_disableVoice);
Menu.Add(_menuKey);
Menu.Add(_passengerKey);
Menu.Add(_vehicleSoftLimit);
Menu.Add(_pedSoftLimit);
Menu.Add(_showBlip);
Menu.Add(_showNametag);
}
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();
_vehicleSoftLimit.AltTitle = Main.Settings.WorldVehicleSoftLimit.ToString();
Util.SaveSettings();
}
catch { }
}
private static void PedSoftLimitActivated(object sender, EventArgs e)
{
try
{
Main.Settings.WorldPedSoftLimit = int.Parse(
Game.GetUserInput(WindowTitle.EnterMessage20,
Main.Settings.WorldPedSoftLimit.ToString(), 20));
_pedSoftLimit.AltTitle = Main.Settings.WorldPedSoftLimit.ToString();
Util.SaveSettings();
}
catch { }
@ -75,11 +113,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 +127,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 +139,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 +152,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

@ -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)
@ -32,11 +31,12 @@ namespace RageCoop.Client
}
private ulong LastMessageTime { get; set; }
private Keys LastKey { get; set; }
private bool CurrentHidden { get; set; }
private bool Hidden
{
get { return CurrentHidden; }
get => CurrentHidden;
set
{
if (value)
@ -57,7 +57,7 @@ namespace RageCoop.Client
public Chat()
{
MainScaleForm = new Scaleform("multiplayer_chat");
MainScaleForm = Scaleform.RequestMovie("multiplayer_chat");
}
public void Init()
@ -106,7 +106,7 @@ namespace RageCoop.Client
CurrentInput = "";
return;
}
if (key == Keys.PageUp)
{
MainScaleForm.CallFunction("PAGE_UP");
@ -116,6 +116,10 @@ namespace RageCoop.Client
MainScaleForm.CallFunction("PAGE_DOWN");
}
if (key == Keys.Menu && LastKey == Keys.ShiftKey)
ActivateKeyboardLayout(1, 0);
LastKey = key;
string keyChar = GetCharFromKey(key, Game.IsKeyPressed(Keys.ShiftKey), false);
if (keyChar.Length == 0)
@ -175,5 +179,8 @@ namespace RageCoop.Client
ToUnicodeEx((uint)key, 0, keyboardState, buf, 256, 0, InputLanguage.CurrentInputLanguage.Handle);
return buf.ToString();
}
[DllImport("user32.dll")]
public static extern int ActivateKeyboardLayout(int hkl, uint flags);
}
}

View File

@ -1,42 +1,85 @@
using System.IO;
using System.Linq;
using RageCoop.Core;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace RageCoop.Client
{
internal static class DownloadManager
{
static string downloadFolder = $"RageCoop\\Resources\\{Main.Settings.LastServerAddress.Replace(":", ".")}";
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
public static void AddFile(int id, string name, long length)
public static event EventHandler<string> DownloadCompleted;
static DownloadManager()
{
Main.Logger.Debug($"Downloading file to {downloadFolder}\\{name} , id:{id}");
if (!Directory.Exists(downloadFolder))
Networking.RequestHandlers.Add(PacketType.FileTransferRequest, (data) =>
{
Directory.CreateDirectory(downloadFolder);
var fr = new Packets.FileTransferRequest();
fr.Deserialize(data);
if (fr.Name.EndsWith(".res"))
{
_resources.Add(fr.Name);
}
return new Packets.FileTransferResponse()
{
ID = fr.ID,
Response = AddFile(fr.ID, fr.Name, fr.FileLength) ? FileResponse.NeedToDownload : FileResponse.AlreadyExists
};
});
Networking.RequestHandlers.Add(PacketType.FileTransferComplete, (data) =>
{
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
packet.Deserialize(data);
Main.Logger.Debug($"Finalizing download:{packet.ID}");
Complete(packet.ID);
// Inform the server that the download is completed
return new Packets.FileTransferResponse()
{
ID = packet.ID,
Response = FileResponse.Completed
};
});
Networking.RequestHandlers.Add(PacketType.AllResourcesSent, (data) =>
{
try
{
Main.Resources.Load(ResourceFolder, _resources.ToArray());
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.Loaded };
}
catch (Exception ex)
{
Main.Logger.Error("Error occurred when loading server resource:");
Main.Logger.Error(ex);
return new Packets.FileTransferResponse() { ID = 0, Response = FileResponse.LoadFailed };
}
});
}
public static string ResourceFolder => Path.GetFullPath(Path.Combine(Main.Settings.DataDirectory, "Resources", Main.Settings.LastServerAddress.Replace(":", ".")));
private static readonly Dictionary<int, DownloadFile> InProgressDownloads = new Dictionary<int, DownloadFile>();
private static readonly HashSet<string> _resources = new HashSet<string>();
public static bool AddFile(int id, string name, long length)
{
var path = $"{ResourceFolder}\\{name}";
Main.Logger.Debug($"Downloading file to {path} , id:{id}");
if (!Directory.Exists(Directory.GetParent(path).FullName))
{
Directory.CreateDirectory(Directory.GetParent(path).FullName);
}
if (FileAlreadyExists(downloadFolder, name, length))
if (FileAlreadyExists(ResourceFolder, name, length))
{
Main.Logger.Debug($"File already exists! canceling download:{name}");
Cancel(id);
if (name=="Resources.zip")
{
Main.Logger.Debug("Loading resources...");
Main.Resources.Load(Path.Combine(downloadFolder));
}
return;
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, name));
return false;
}
/*
if (!name.EndsWith(".zip"))
{
Cancel(id);
GTA.UI.Notification.Show($"The download of a file from the server was blocked! [{name}]", true);
Main.Logger.Error($"The download of a file from the server was blocked! [{name}]");
return;
Main.Logger.Error($"File download blocked! [{name}]");
return false;
}
*/
lock (InProgressDownloads)
{
InProgressDownloads.Add(id, new DownloadFile()
@ -44,9 +87,10 @@ namespace RageCoop.Client
FileID = id,
FileName = name,
FileLength = length,
Stream = new FileStream($"{downloadFolder}\\{name}", FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
Stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.ReadWrite)
});
}
return true;
}
/// <summary>
@ -78,8 +122,7 @@ namespace RageCoop.Client
{
lock (InProgressDownloads)
{
DownloadFile file;
if (InProgressDownloads.TryGetValue(id, out file))
if (InProgressDownloads.TryGetValue(id, out DownloadFile file))
{
file.Stream.Write(chunk, 0, chunk.Length);
@ -87,47 +130,19 @@ namespace RageCoop.Client
else
{
Main.Logger.Trace($"Received unhandled file chunk:{id}");
return;
}
}
}
public static void Cancel(int id)
{
Main.Logger.Debug($"Canceling download:{id}");
// Tell the server to stop sending chunks
Networking.SendDownloadFinish(id);
DownloadFile file;
lock (InProgressDownloads)
{
if (InProgressDownloads.TryGetValue(id, out file))
{
InProgressDownloads.Remove(id);
file.Dispose();
}
}
}
public static void Complete(int id)
{
DownloadFile f;
if (InProgressDownloads.TryGetValue(id, out f))
if (InProgressDownloads.TryGetValue(id, out DownloadFile f))
{
lock (InProgressDownloads)
{
InProgressDownloads.Remove(id);
f.Dispose();
Main.Logger.Info($"Download finished:{f.FileName}");
if (f.FileName=="Resources.zip")
{
Main.Logger.Debug("Loading resources...");
Main.Resources.Load(Path.Combine(downloadFolder));
}
Networking.SendDownloadFinish(id);
}
InProgressDownloads.Remove(id);
f.Dispose();
Main.Logger.Info($"Download finished:{f.FileName}");
DownloadCompleted?.Invoke(null, Path.Combine(ResourceFolder, f.FileName));
}
else
{
@ -145,11 +160,12 @@ namespace RageCoop.Client
}
InProgressDownloads.Clear();
}
_resources.Clear();
}
}
public class DownloadFile: System.IDisposable
internal class DownloadFile : IDisposable
{
public int FileID { get; set; } = 0;
public string FileName { get; set; } = string.Empty;
@ -158,7 +174,7 @@ namespace RageCoop.Client
public FileStream Stream { get; set; }
public void Dispose()
{
if(Stream!= null)
if (Stream != null)
{
Stream.Flush();
Stream.Close();

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

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

View File

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

View File

@ -1,382 +1,421 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using GTA;
using Lidgren.Network;
using RageCoop.Core;
using GTA;
using RageCoop.Client.Menus;
using RageCoop.Core;
using System;
using System.Linq;
using System.Threading;
using GTA.Math;
using GTA.Native;
using System.Net;
namespace RageCoop.Client
{
internal static partial class Networking
{
private static AutoResetEvent PublicKeyReceived=new AutoResetEvent(false);
/// <summary>
/// Reduce GC pressure by reusing frequently used packets
/// </summary>
private static class ReceivedPackets
{
public static Packets.PedSync PedPacket = new Packets.PedSync();
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
}
/// <summary>
/// Used to reslove entity handle in a <see cref="Packets.CustomEvent"/>
/// </summary>
private static readonly Func<byte, NetIncomingMessage, object> _resolveHandle = (t, reader) =>
{
switch (t)
{
case 50:
return EntityPool.ServerProps[reader.ReadInt32()].MainProp?.Handle;
case 51:
return EntityPool.GetPedByID(reader.ReadInt32())?.MainPed?.Handle;
case 52:
return EntityPool.GetVehicleByID(reader.ReadInt32())?.MainVehicle?.Handle;
case 60:
return EntityPool.ServerBlips[reader.ReadInt32()].Handle;
default:
throw new ArgumentException("Cannot resolve server side argument: " + t);
}
};
private static readonly AutoResetEvent _publicKeyReceived = new AutoResetEvent(false);
private static bool _recycle;
public static void ProcessMessage(NetIncomingMessage message)
{
if(message == null) { return; }
if (message == null) { return; }
_recycle = true;
switch (message.MessageType)
{
case NetIncomingMessageType.StatusChanged:
NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
string reason = message.ReadString();
switch (status)
{
case NetConnectionStatus.InitiatedConnect:
#if !NON_INTERACTIVE
CoopMenu.InitiateConnectionMenuSetting();
#endif
if (message.SenderConnection == ServerConnection)
{
CoopMenu.InitiateConnectionMenuSetting();
}
break;
case NetConnectionStatus.Connected:
Main.QueueAction(() => {
CoopMenu.ConnectedMenuSetting();
Main.MainChat.Init();
PlayerList.Cleanup();
GTA.UI.Notification.Show("~g~Connected!");
});
if (message.SenderConnection == ServerConnection)
{
var response = message.SenderConnection.RemoteHailMessage;
if ((PacketType)response.ReadByte() != PacketType.HandshakeSuccess)
{
throw new Exception("Invalid handshake response!");
}
var p = new Packets.HandshakeSuccess();
p.Deserialize(response);
foreach (var player in p.Players)
{
PlayerList.SetPlayer(player.ID, player.Username);
}
Main.Connected();
}
else
{
// Self-initiated connection
if (message.SenderConnection.RemoteHailMessage == null) { return; }
Main.Logger.Info(">> Connected <<");
var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>();
if (PlayerList.Players.TryGetValue(p.ID, out var player))
{
player.Connection = message.SenderConnection;
Main.Logger.Debug($"Direct connection to {player.Username} established");
}
else
{
Main.Logger.Info($"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
message.SenderConnection.Disconnect("eat poop");
}
}
break;
case NetConnectionStatus.Disconnected:
DownloadManager.Cleanup();
// Reset all values
Latency = 0;
Main.QueueAction(() => Main.CleanUpWorld());
if (Main.MainChat.Focused)
if (message.SenderConnection == ServerConnection)
{
Main.MainChat.Focused = false;
Main.Disconnected(reason);
}
Main.QueueAction(() => Main.CleanUp());
#if !NON_INTERACTIVE
CoopMenu.DisconnectedMenuSetting();
#endif
Main.QueueAction(() =>
GTA.UI.Notification.Show("~r~Disconnected: " + reason));
MapLoader.DeleteAll();
Main.Resources.Unload();
Main.Logger.Info($">> Disconnected << reason: {reason}");
break;
}
break;
case NetIncomingMessageType.Data:
{
if (message.LengthBytes==0) { break; }
var packetType = PacketTypes.Unknown;
if (message.LengthBytes == 0) { break; }
var packetType = PacketType.Unknown;
try
{
packetType = (PacketTypes)message.ReadByte();
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
// Get packet type
packetType = (PacketType)message.ReadByte();
switch (packetType)
{
case PacketTypes.PlayerConnect:
case PacketType.Response:
{
Packets.PlayerConnect packet = new Packets.PlayerConnect();
packet.Unpack(data);
Main.QueueAction(() => PlayerConnect(packet));
}
break;
case PacketTypes.PlayerDisconnect:
{
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect();
packet.Unpack(data);
Main.QueueAction(() => PlayerDisconnect(packet));
}
break;
case PacketTypes.PlayerInfoUpdate:
{
var packet = new Packets.PlayerInfoUpdate();
packet.Unpack(data);
PlayerList.UpdatePlayer(packet);
int id = message.ReadInt32();
if (PendingResponses.TryGetValue(id, out var callback))
{
callback((PacketType)message.ReadByte(), message);
PendingResponses.Remove(id);
}
break;
}
#region ENTITY SYNC
case PacketTypes.VehicleSync:
case PacketType.Request:
{
Packets.VehicleSync packet = new Packets.VehicleSync();
packet.Unpack(data);
VehicleSync(packet);
}
break;
case PacketTypes.PedSync:
{
Packets.PedSync packet = new Packets.PedSync();
packet.Unpack(data);
PedSync(packet);
}
break;
case PacketTypes.VehicleStateSync:
{
Packets.VehicleStateSync packet = new Packets.VehicleStateSync();
packet.Unpack(data);
VehicleStateSync(packet);
}
break;
case PacketTypes.PedStateSync:
{
Packets.PedStateSync packet = new Packets.PedStateSync();
packet.Unpack(data);
PedStateSync(packet);
}
break;
case PacketTypes.ProjectileSync:
{
Packets.ProjectileSync packet = new Packets.ProjectileSync();
packet.Unpack(data);
ProjectileSync(packet);
int id = message.ReadInt32();
var realType = (PacketType)message.ReadByte();
if (RequestHandlers.TryGetValue(realType, out var handler))
{
var response = Peer.CreateMessage();
response.Write((byte)PacketType.Response);
response.Write(id);
handler(message).Pack(response);
Peer.SendMessage(response, ServerConnection, NetDeliveryMethod.ReliableOrdered, message.SequenceChannel);
Peer.FlushSendQueue();
}
else
{
Main.Logger.Debug("Did not find a request handler of type: " + realType);
}
break;
}
#endregion
case PacketTypes.ChatMessage:
{
Packets.ChatMessage packet = new Packets.ChatMessage();
packet.Unpack(data);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketTypes.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent();
packet.Unpack(data);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
break;
case PacketTypes.FileTransferChunk:
{
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
packet.Unpack(data);
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
case PacketTypes.FileTransferRequest:
{
Packets.FileTransferRequest packet = new Packets.FileTransferRequest();
packet.Unpack(data);
DownloadManager.AddFile(packet.ID, packet.Name, packet.FileLength);
}
break;
case PacketTypes.FileTransferComplete:
{
Packets.FileTransferComplete packet = new Packets.FileTransferComplete();
packet.Unpack(data);
Main.Logger.Debug($"Finalizing download:{packet.ID}");
DownloadManager.Complete(packet.ID);
}
break;
default:
if (packetType.IsSyncEvent())
{
// Dispatch to main thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
HandlePacket(packetType, message, message.SenderConnection, ref _recycle);
break;
}
break;
}
}
catch (Exception ex)
{
Main.QueueAction(() => {
GTA.UI.Notification.Show("~r~~h~Packet Error");
#if DEBUG
Main.QueueAction(() =>
{
GTA.UI.Notification.Show($"~r~~h~Packet Error {ex.Message}");
return true;
});
Main.Logger.Error($"[{packetType}] {ex.Message}");
Main.Logger.Error(ex);
Client.Disconnect($"Packet Error [{packetType}]");
Peer.Shutdown($"Packet Error [{packetType}]");
#endif
_recycle = false;
}
break;
}
case NetIncomingMessageType.ConnectionLatencyUpdated:
Latency = message.ReadFloat();
break;
case NetIncomingMessageType.UnconnectedData:
{
var packetType = (PacketTypes)message.ReadByte();
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
if (packetType==PacketTypes.PublicKeyResponse)
var packetType = (PacketType)message.ReadByte();
switch (packetType)
{
var packet=new Packets.PublicKeyResponse();
packet.Unpack(data);
Security.SetServerPublicKey(packet.Modulus,packet.Exponent);
PublicKeyReceived.Set();
case PacketType.HolePunch:
{
HolePunch.Punched(message.GetPacket<Packets.HolePunch>(), message.SenderEndPoint);
break;
}
case PacketType.PublicKeyResponse:
{
if (message.SenderEndPoint.ToString() != _targetServerEP.ToString() || !IsConnecting) { break; }
var packet = message.GetPacket<Packets.PublicKeyResponse>();
Security.SetServerPublicKey(packet.Modulus, packet.Exponent);
_publicKeyReceived.Set();
break;
}
}
break;
}
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.ErrorMessage:
case NetIncomingMessageType.WarningMessage:
case NetIncomingMessageType.VerboseDebugMessage:
Main.Logger.Trace(message.ReadString());
break;
default:
break;
}
Client.Recycle(message);
if (_recycle)
{
Peer.Recycle(message);
}
}
private static void HandlePacket(PacketType packetType, NetIncomingMessage msg, NetConnection senderConnection, ref bool recycle)
{
switch (packetType)
{
case PacketType.HolePunchInit:
HolePunch.Add(msg.GetPacket<Packets.HolePunchInit>());
break;
case PacketType.PlayerConnect:
PlayerConnect(msg.GetPacket<Packets.PlayerConnect>());
break;
case PacketType.PlayerDisconnect:
PlayerDisconnect(msg.GetPacket<Packets.PlayerDisconnect>());
break;
case PacketType.PlayerInfoUpdate:
PlayerList.UpdatePlayer(msg.GetPacket<Packets.PlayerInfoUpdate>());
break;
case PacketType.VehicleSync:
ReceivedPackets.VehicelPacket.Deserialize(msg);
VehicleSync(ReceivedPackets.VehicelPacket);
break;
case PacketType.PedSync:
ReceivedPackets.PedPacket.Deserialize(msg);
PedSync(ReceivedPackets.PedPacket);
break;
case PacketType.ProjectileSync:
ReceivedPackets.ProjectilePacket.Deserialize(msg);
ProjectileSync(ReceivedPackets.ProjectilePacket);
break;
case PacketType.ChatMessage:
{
Packets.ChatMessage packet = new Packets.ChatMessage((b) => Security.Decrypt(b));
packet.Deserialize(msg);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketType.Voice:
{
if (Main.Settings.Voice)
{
Packets.Voice packet = new Packets.Voice();
packet.Deserialize(msg);
SyncedPed player = EntityPool.GetPedByID(packet.ID);
player.IsSpeaking = true;
player.LastSpeakingTime = Main.Ticked;
Voice.AddVoiceData(packet.Buffer, packet.Recorded);
}
}
break;
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
break;
case PacketType.CustomEventQueued:
{
recycle = false;
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
Main.QueueAction(() =>
{
packet.Deserialize(msg);
Scripting.API.Events.InvokeCustomEventReceived(packet);
Peer.Recycle(msg);
});
}
break;
case PacketType.FileTransferChunk:
{
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
packet.Deserialize(msg);
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
default:
if (packetType.IsSyncEvent())
{
recycle = false;
// Dispatch to script thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, msg); return true; });
}
break;
}
}
private static void PedSync(Packets.PedSync packet)
{
SyncedPed c = EntityPool.GetPedByID(packet.ID);
if (c==null)
if (c == null)
{
Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c=new SyncedPed(packet.ID));
if (EntityPool.PedsByID.Count(x => x.Value.OwnerID == packet.OwnerID) < Main.Settings.WorldPedSoftLimit / PlayerList.Players.Count ||
/*EntityPool.VehiclesByID.Any(x => x.Value.Position.DistanceTo(packet.Position) < 2) ||*/ // allows players to exceed the peds limit
packet.ID == packet.OwnerID)
{
// Main.Logger.Debug($"Creating character for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(c = new SyncedPed(packet.ID));
}
else return;
}
PedDataFlags flags = packet.Flag;
c.ID=packet.ID;
//c.OwnerID=packet.OwnerID;
c.ID = packet.ID;
c.OwnerID = packet.OwnerID;
c.Health = packet.Health;
c.Position = packet.Position;
c.Rotation = packet.Rotation;
c.Velocity = packet.Velocity;
c.Speed = packet.Speed;
c.CurrentWeaponHash = packet.CurrentWeaponHash;
c.IsAiming = flags.HasPedFlag(PedDataFlags.IsAiming);
c.IsReloading = flags.HasPedFlag(PedDataFlags.IsReloading);
c.IsJumping = flags.HasPedFlag(PedDataFlags.IsJumping);
c.IsRagdoll = flags.HasPedFlag(PedDataFlags.IsRagdoll);
c.IsOnFire = flags.HasPedFlag(PedDataFlags.IsOnFire);
c.IsInParachuteFreeFall = flags.HasPedFlag(PedDataFlags.IsInParachuteFreeFall);
c.IsParachuteOpen = flags.HasPedFlag(PedDataFlags.IsParachuteOpen);
c.IsOnLadder = flags.HasPedFlag(PedDataFlags.IsOnLadder);
c.IsVaulting = flags.HasPedFlag(PedDataFlags.IsVaulting);
c.IsInCover = flags.HasPedFlag(PedDataFlags.IsInCover);
c.IsInStealthMode = flags.HasPedFlag(PedDataFlags.IsInStealthMode);
c.Heading=packet.Heading;
c.LastSynced = Main.Ticked;
c.Flags = packet.Flags;
c.Heading = packet.Heading;
c.Position = packet.Position;
c.LastSyncedStopWatch.Restart();
if (c.IsRagdoll)
{
c.HeadPosition = packet.HeadPosition;
c.RightFootPosition = packet.RightFootPosition;
c.LeftFootPosition = packet.LeftFootPosition;
}
else if (c.Speed >= 4)
{
c.VehicleID = packet.VehicleID;
c.Seat = packet.Seat;
}
c.LastSynced = Main.Ticked;
if (c.IsAiming)
{
c.AimCoords = packet.AimCoords;
}
if (c.IsRagdoll)
if (packet.Flags.HasPedFlag(PedDataFlags.IsFullSync))
{
c.RotationVelocity=packet.RotationVelocity;
c.CurrentWeaponHash = packet.CurrentWeaponHash;
c.Clothes = packet.Clothes;
c.WeaponComponents = packet.WeaponComponents;
c.WeaponTint = packet.WeaponTint;
c.Model = packet.ModelHash;
c.BlipColor = packet.BlipColor;
c.BlipSprite = packet.BlipSprite;
c.BlipScale = packet.BlipScale;
c.LastFullSynced = Main.Ticked;
}
}
private static void PedStateSync(Packets.PedStateSync packet)
{
SyncedPed c = EntityPool.GetPedByID(packet.ID);
if (c==null) { return; }
c.ID=packet.ID;
c.OwnerID=packet.OwnerID;
c.Clothes=packet.Clothes;
c.WeaponComponents=packet.WeaponComponents;
c.WeaponTint=packet.WeaponTint;
c.ModelHash=packet.ModelHash;
c.LastStateSynced = Main.Ticked;
}
private static void VehicleSync(Packets.VehicleSync packet)
{
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
if (v==null)
{
EntityPool.ThreadSafe.Add(v=new SyncedVehicle(packet.ID));
}
if (v.IsMine) { return; }
v.ID= packet.ID;
v.Position=packet.Position;
v.Quaternion=packet.Quaternion;
v.SteeringAngle=packet.SteeringAngle;
v.ThrottlePower=packet.ThrottlePower;
v.BrakePower=packet.BrakePower;
v.Velocity=packet.Velocity;
v.RotationVelocity=packet.RotationVelocity;
v.DeluxoWingRatio=packet.DeluxoWingRatio;
v.LastSynced=Main.Ticked;
}
private static void VehicleStateSync(Packets.VehicleStateSync packet)
{
SyncedVehicle v = EntityPool.GetVehicleByID(packet.ID);
if (v==null||v.IsMine) { return; }
v.ID= packet.ID;
v.OwnerID= packet.OwnerID;
v.DamageModel=packet.DamageModel;
v.EngineHealth=packet.EngineHealth;
v.OwnerID=packet.OwnerID;
v.Mods=packet.Mods;
v.ModelHash=packet.ModelHash;
v.Colors=packet.Colors;
v.LandingGear=packet.LandingGear;
v.RoofState=(VehicleRoofState)packet.RoofState;
v.EngineRunning = packet.Flag.HasVehFlag(VehicleDataFlags.IsEngineRunning);
v.LightsOn = packet.Flag.HasVehFlag(VehicleDataFlags.AreLightsOn);
v.BrakeLightsOn = packet.Flag.HasVehFlag(VehicleDataFlags.AreBrakeLightsOn);
v.HighBeamsOn = packet.Flag.HasVehFlag(VehicleDataFlags.AreHighBeamsOn);
v.SireneActive = packet.Flag.HasVehFlag(VehicleDataFlags.IsSirenActive);
v.IsDead = packet.Flag.HasVehFlag(VehicleDataFlags.IsDead);
v.HornActive = packet.Flag.HasVehFlag(VehicleDataFlags.IsHornActive);
v.Transformed = packet.Flag.HasVehFlag(VehicleDataFlags.IsTransformed);
v.Passengers=new Dictionary<VehicleSeat, SyncedPed>();
v.LockStatus=packet.LockStatus;
v.RadioStation=packet.RadioStation;
v.LicensePlate=packet.LicensePlate;
v.Flags=packet.Flag;
foreach (KeyValuePair<int, int> pair in packet.Passengers)
if (v == null)
{
if (EntityPool.PedExists(pair.Value))
if (EntityPool.VehiclesByID.Count(x => x.Value.OwnerID == packet.OwnerID) < Main.Settings.WorldVehicleSoftLimit / PlayerList.Players.Count ||
EntityPool.PedsByID.Any(x => x.Value.VehicleID == packet.ID || x.Value.Position.DistanceTo(packet.Position) < 2))
{
v.Passengers.Add((VehicleSeat)pair.Key, EntityPool.GetPedByID(pair.Value));
// Main.Logger.Debug($"Creating vehicle for incoming sync:{packet.ID}");
EntityPool.ThreadSafe.Add(v = new SyncedVehicle(packet.ID));
}
else return;
}
if (v.IsLocal) { return; }
v.ID = packet.ID;
v.OwnerID = packet.OwnerID;
v.Flags = packet.Flags;
v.Position = packet.Position;
v.Quaternion = packet.Quaternion;
v.SteeringAngle = packet.SteeringAngle;
v.ThrottlePower = packet.ThrottlePower;
v.BrakePower = packet.BrakePower;
v.Velocity = packet.Velocity;
v.RotationVelocity = packet.RotationVelocity;
v.DeluxoWingRatio = packet.DeluxoWingRatio;
v.LastSynced = Main.Ticked;
v.LastSyncedStopWatch.Restart();
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsFullSync))
{
v.DamageModel = packet.DamageModel;
v.EngineHealth = packet.EngineHealth;
v.Mods = packet.Mods;
v.Model = packet.ModelHash;
v.Colors = packet.Colors;
v.LandingGear = packet.LandingGear;
v.RoofState = (VehicleRoofState)packet.RoofState;
v.LockStatus = packet.LockStatus;
v.RadioStation = packet.RadioStation;
v.LicensePlate = packet.LicensePlate;
v.Livery = packet.Livery;
v.LastFullSynced = Main.Ticked;
}
v.LastStateSynced= Main.Ticked;
}
private static void ProjectileSync(Packets.ProjectileSync packet)
{
var p = EntityPool.GetProjectileByID(packet.ID);
if (p==null)
if (p == null)
{
if (packet.Exploded) { return; }
Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p=new SyncedProjectile(packet.ID));
if (packet.Flags.HasProjDataFlag(ProjectileDataFlags.Exploded)) { return; }
// Main.Logger.Debug($"Creating new projectile: {(WeaponHash)packet.WeaponHash}");
EntityPool.ThreadSafe.Add(p = new SyncedProjectile(packet.ID));
}
p.Position=packet.Position;
p.Rotation=packet.Rotation;
p.Velocity=packet.Velocity;
p.Hash=(WeaponHash)packet.WeaponHash;
p.ShooterID=packet.ShooterID;
p.Exploded=packet.Exploded;
p.LastSynced=Main.Ticked;
p.Flags = packet.Flags;
p.Position = packet.Position;
p.Rotation = packet.Rotation;
p.Velocity = packet.Velocity;
p.WeaponHash = (WeaponHash)packet.WeaponHash;
p.Shooter = packet.Flags.HasProjDataFlag(ProjectileDataFlags.IsShotByVehicle) ?
(SyncedEntity)EntityPool.GetVehicleByID(packet.ShooterID) : EntityPool.GetPedByID(packet.ShooterID);
p.LastSynced = Main.Ticked;
p.LastSyncedStopWatch.Restart();
}
}
}

View File

@ -1,178 +1,207 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using GTA;
using GTA.Math;
using GTA.Native;
using Lidgren.Network;
using RageCoop.Core;
using GTA;
using GTA.Native;
using GTA.Math;
using System;
using System.Collections.Generic;
namespace RageCoop.Client
{
internal static partial class Networking
{
#region -- SEND --
/// <summary>
/// Pack the packet then send to server.
/// Reduce GC pressure by reusing frequently used packets
/// </summary>
/// <param name="p"></param>
/// <param name="channel"></param>
/// <param name="method"></param>
public static void Send(Packet p, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
private static class SendPackets
{
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
p.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, method, (int)channel);
public static Packets.PedSync PedPacket = new Packets.PedSync();
public static Packets.VehicleSync VehicelPacket = new Packets.VehicleSync();
public static Packets.ProjectileSync ProjectilePacket = new Packets.ProjectileSync();
}
public static int SyncInterval = 30;
public static List<NetConnection> Targets = new List<NetConnection>();
public static void SendSync(Packet p, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
Peer.SendTo(p, Targets, channel, method);
}
public static void SendPed(SyncedPed c)
public static void SendPed(SyncedPed sp, bool full)
{
Ped p = c.MainPed;
var packet=new Packets.PedSync()
if (sp.LastSentStopWatch.ElapsedMilliseconds < SyncInterval)
{
ID =c.ID,
Health = p.Health,
Position = p.Position,
Rotation = p.Rotation,
Velocity = p.Velocity,
Speed = p.GetPedSpeed(),
CurrentWeaponHash = (uint)p.Weapons.Current.Hash,
Flag = p.GetPedFlags(),
Heading=p.Heading,
};
if (packet.Flag.HasPedFlag(PedDataFlags.IsAiming))
{
packet.AimCoords = p.GetAimCoord();
return;
}
if (packet.Flag.HasPedFlag(PedDataFlags.IsRagdoll))
Ped ped = sp.MainPed;
var p = SendPackets.PedPacket;
p.ID = sp.ID;
p.OwnerID = sp.OwnerID;
p.Health = ped.Health;
p.Rotation = ped.ReadRotation();
p.Velocity = ped.ReadVelocity();
p.Speed = ped.GetPedSpeed();
p.Flags = ped.GetPedFlags();
p.Heading = ped.Heading;
if (p.Flags.HasPedFlag(PedDataFlags.IsAiming))
{
packet.RotationVelocity=p.RotationVelocity;
p.AimCoords = ped.GetAimCoord();
}
Send(packet, ConnectionChannel.PedSync);
}
public static void SendPedState(SyncedPed c)
{
Ped p = c.MainPed;
var packet=new Packets.PedStateSync()
if (p.Flags.HasPedFlag(PedDataFlags.IsRagdoll))
{
ID = c.ID,
OwnerID=c.OwnerID,
Clothes=p.GetPedClothes(),
ModelHash=p.Model.Hash,
WeaponComponents=p.Weapons.Current.GetWeaponComponents(),
WeaponTint=(byte)Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, p, p.Weapons.Current.Hash)
};
p.HeadPosition = ped.Bones[Bone.SkelHead].Position;
p.RightFootPosition = ped.Bones[Bone.SkelRightFoot].Position;
p.LeftFootPosition = ped.Bones[Bone.SkelLeftFoot].Position;
}
else
{
// Seat sync
if (p.Speed >= 4)
{
var veh = ped.CurrentVehicle?.GetSyncEntity() ?? ped.VehicleTryingToEnter?.GetSyncEntity() ?? ped.LastVehicle?.GetSyncEntity();
p.VehicleID = veh?.ID ?? 0;
if (p.VehicleID == 0) { Main.Logger.Error("Invalid vehicle"); }
if (p.Speed == 5)
{
p.Seat = ped.GetSeatTryingToEnter();
}
else
{
p.Seat = ped.SeatIndex;
}
if (!veh.IsLocal && p.Speed == 4 && p.Seat == VehicleSeat.Driver)
{
veh.OwnerID = Main.LocalPlayerID;
SyncEvents.TriggerChangeOwner(veh.ID, Main.LocalPlayerID);
}
}
p.Position = ped.ReadPosition();
}
sp.LastSentStopWatch.Restart();
if (full)
{
var w = ped.VehicleWeapon;
p.CurrentWeaponHash = (w != VehicleWeaponHash.Invalid) ? (uint)w : (uint)ped.Weapons.Current.Hash;
p.Flags |= PedDataFlags.IsFullSync;
p.Clothes = ped.GetPedClothes();
p.ModelHash = ped.Model.Hash;
p.WeaponComponents = ped.Weapons.Current.GetWeaponComponents();
p.WeaponTint = (byte)Function.Call<int>(Hash.GET_PED_WEAPON_TINT_INDEX, ped, ped.Weapons.Current.Hash);
Send(packet, ConnectionChannel.PedSync);
Blip b;
if (sp.IsPlayer)
{
p.BlipColor = Scripting.API.Config.BlipColor;
p.BlipSprite = Scripting.API.Config.BlipSprite;
p.BlipScale = Scripting.API.Config.BlipScale;
}
else if ((b = ped.AttachedBlip) != null)
{
p.BlipColor = b.Color;
p.BlipSprite = b.Sprite;
if (p.BlipSprite == BlipSprite.PoliceOfficer || p.BlipSprite == BlipSprite.PoliceOfficer2)
{
p.BlipScale = 0.5f;
}
}
else
{
p.BlipColor = (BlipColor)255;
}
}
SendSync(p, ConnectionChannel.PedSync);
}
public static void SendVehicle(SyncedVehicle v)
public static void SendVehicle(SyncedVehicle v, bool full)
{
if (v.LastSentStopWatch.ElapsedMilliseconds < SyncInterval)
{
return;
}
Vehicle veh = v.MainVehicle;
var packet = new Packets.VehicleSync()
var packet = SendPackets.VehicelPacket;
packet.ID = v.ID;
packet.OwnerID = v.OwnerID;
packet.Flags = v.GetVehicleFlags();
packet.SteeringAngle = veh.SteeringAngle;
packet.Position = veh.ReadPosition();
packet.Velocity = veh.Velocity;
packet.Quaternion = veh.ReadQuaternion();
packet.RotationVelocity = veh.LocalRotationVelocity;
packet.ThrottlePower = veh.ThrottlePower;
packet.BrakePower = veh.BrakePower;
v.LastSentStopWatch.Restart();
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering)) { packet.DeluxoWingRatio = v.MainVehicle.GetDeluxoWingRatio(); }
if (full)
{
ID =v.ID,
SteeringAngle = veh.SteeringAngle,
Position = veh.PredictPosition(),
Quaternion=veh.Quaternion,
// Rotation = veh.Rotation,
Velocity = veh.Velocity,
RotationVelocity=veh.RotationVelocity,
ThrottlePower = veh.ThrottlePower,
BrakePower = veh.BrakePower,
};
if (v.MainVehicle.Model.Hash==1483171323) { packet.DeluxoWingRatio=v.MainVehicle.GetDeluxoWingRatio(); }
Send(packet,ConnectionChannel.VehicleSync);
}
public static void SendVehicleState(SyncedVehicle v)
{
Vehicle veh = v.MainVehicle;
byte primaryColor = 0;
byte secondaryColor = 0;
unsafe
{
Function.Call<byte>(Hash.GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
byte primaryColor = 0;
byte secondaryColor = 0;
unsafe
{
Function.Call<byte>(Hash.GET_VEHICLE_COLOURS, veh, &primaryColor, &secondaryColor);
}
packet.Flags |= VehicleDataFlags.IsFullSync;
packet.Colors = new byte[] { primaryColor, secondaryColor };
packet.DamageModel = veh.GetVehicleDamageModel();
packet.LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0;
packet.RoofState = (byte)veh.RoofState;
packet.Mods = veh.Mods.GetVehicleMods();
packet.ModelHash = veh.Model.Hash;
packet.EngineHealth = veh.EngineHealth;
packet.LockStatus = veh.LockStatus;
packet.LicensePlate = Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, veh);
packet.Livery = Function.Call<int>(Hash.GET_VEHICLE_LIVERY, veh);
if (v.MainVehicle == Game.Player.LastVehicle)
{
packet.RadioStation = Util.GetPlayerRadioIndex();
}
if (packet.EngineHealth > v.LastEngineHealth)
{
packet.Flags |= VehicleDataFlags.Repaired;
}
v.LastEngineHealth = packet.EngineHealth;
}
var packet=new Packets.VehicleStateSync()
{
ID =v.ID,
OwnerID = v.OwnerID,
Flag = veh.GetVehicleFlags(),
Colors=new byte[] { primaryColor, secondaryColor },
DamageModel=veh.GetVehicleDamageModel(),
LandingGear = veh.IsAircraft ? (byte)veh.LandingGearState : (byte)0,
RoofState=(byte)veh.RoofState,
Mods = veh.Mods.GetVehicleMods(),
ModelHash=veh.Model.Hash,
EngineHealth=veh.EngineHealth,
Passengers=veh.GetPassengers(),
LockStatus=veh.LockStatus,
LicensePlate=Function.Call<string>(Hash.GET_VEHICLE_NUMBER_PLATE_TEXT, veh)
};
if (v.MainVehicle==Game.Player.LastVehicle)
{
packet.RadioStation=Util.GetPlayerRadioIndex();
}
Send(packet, ConnectionChannel.VehicleSync);
SendSync(packet, ConnectionChannel.VehicleSync);
}
public static void SendProjectile(SyncedProjectile sp)
{
var p = sp.MainProjectile;
var packet = new Packets.ProjectileSync()
{
ID =sp.ID,
ShooterID=sp.ShooterID,
Position=p.Position,
Rotation=p.Rotation,
Velocity=p.Velocity,
WeaponHash=(uint)p.WeaponHash,
Exploded=p.IsDead
};
if (p.IsDead) { EntityPool.RemoveProjectile(sp.ID,"Dead"); }
Send(packet, ConnectionChannel.ProjectileSync);
sp.ExtractData(ref SendPackets.ProjectilePacket);
if (sp.MainProjectile.IsDead) { EntityPool.RemoveProjectile(sp.ID, "Dead"); }
SendSync(SendPackets.ProjectilePacket, ConnectionChannel.ProjectileSync);
}
#region SYNC EVENTS
public static void SendBulletShot(Vector3 start,Vector3 end,uint weapon,int ownerID)
public static void SendBullet(Vector3 start, Vector3 end, uint weapon, int ownerID)
{
Send(new Packets.BulletShot()
SendSync(new Packets.BulletShot()
{
StartPosition = start,
EndPosition = end,
OwnerID = ownerID,
WeaponHash=weapon,
WeaponHash = weapon,
}, ConnectionChannel.SyncEvents);
}
public static void SendVehicleBullet(uint hash, SyncedPed owner, EntityBone b)
{
SendSync(new Packets.VehicleBulletShot
{
StartPosition = b.Position,
EndPosition = b.Position + b.ForwardVector,
OwnerID = owner.ID,
Bone = (ushort)b.Index,
WeaponHash = hash
});
}
#endregion
public static void SendChatMessage(string message)
{
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.ChatMessage() { Username = Main.Settings.Username, Message = message }.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.Chat);
Client.FlushSendQueue();
#if DEBUG
#endif
Peer.SendTo(new Packets.ChatMessage(new Func<string, byte[]>((s) => Security.Encrypt(s.GetBytes())))
{ Username = Main.Settings.Username, Message = message }, ServerConnection, ConnectionChannel.Chat, NetDeliveryMethod.ReliableOrdered);
Peer.FlushSendQueue();
}
public static void SendDownloadFinish(int id)
public static void SendVoiceMessage(byte[] buffer, int recorded)
{
NetOutgoingMessage outgoingMessage = Client.CreateMessage();
new Packets.FileTransferComplete() { ID = id }.Pack(outgoingMessage);
Client.SendMessage(outgoingMessage, NetDeliveryMethod.ReliableOrdered, (byte)ConnectionChannel.File);
Client.FlushSendQueue();
#if DEBUG
#endif
SendSync(new Packets.Voice() { ID = Main.LocalPlayerID, Buffer = buffer, Recorded = recorded }, ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
}
#endregion
}
}

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;
}
});
}
}
}

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