mirror of
https://github.com/jimathy/jim_bridge.git
synced 2026-11-15 02:43:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40eed38dc2 | ||
|
|
8c947aae73 | ||
|
|
a96602d351 | ||
|
|
b176f9d5ad | ||
|
|
41ac8d8d7f | ||
|
|
08be58d14f | ||
|
|
b891701359 | ||
|
|
71c0abb32c | ||
|
|
949363c76a | ||
|
|
d21f4b22b5 | ||
|
|
e84a9f1c60 | ||
|
|
12c6648767 | ||
|
|
9a57f852b1 | ||
|
|
61aae4fa92 | ||
|
|
606ad1d124 | ||
|
|
d2bf1c01bf | ||
|
|
8f31c740bc | ||
|
|
f9b9de0b57 | ||
|
|
6f0949da21 | ||
|
|
27a0e147fb | ||
|
|
960f73ad6d | ||
|
|
18ecc1429e | ||
|
|
a9403e5513 | ||
|
|
9ec7bddfe6 | ||
|
|
d46aa8c2cb | ||
|
|
0793554855 | ||
|
|
9efc3c94cd | ||
|
|
7e320bcb99 | ||
|
|
9c66499fab | ||
|
|
54f96f7e77 | ||
|
|
dffed24fb3 | ||
|
|
aae2bd5021 | ||
|
|
258a510452 | ||
|
|
2ac3843fb2 | ||
|
|
732b07c957 | ||
|
|
8e8010f837 | ||
|
|
696b469309 | ||
|
|
e05815cfbd | ||
|
|
8594ba12ec | ||
|
|
9f914bd497 | ||
|
|
ad47836232 | ||
|
|
ae4637f9c4 | ||
|
|
a13284d0e0 | ||
|
|
98f8944a79 | ||
|
|
13b0f796ff | ||
|
|
b282d3393f | ||
|
|
180cc7b2a2 | ||
|
|
beb67b79a5 | ||
|
|
8d48b0d692 | ||
|
|
c359bc4342 | ||
|
|
40ca0eb04d | ||
|
|
2404fff002 | ||
|
|
03ca409bf3 | ||
|
|
9a78b057dd | ||
|
|
45f390c177 | ||
|
|
803c612cbb | ||
|
|
63bcb86ac1 | ||
|
|
0bc3c0f9b4 | ||
|
|
672dd7684b | ||
|
|
f53f29a4b2 | ||
|
|
c040390d1f | ||
|
|
26b1f36029 | ||
|
|
331dc94e2f | ||
|
|
d1dab48acc | ||
|
|
7e595df143 | ||
|
|
d791a2e09f | ||
|
|
7738bb4fca | ||
|
|
3ad65810c3 | ||
|
|
45eeaf8f0d | ||
|
|
37f7eb1116 | ||
|
|
145e3aa156 | ||
|
|
bb176addbf | ||
|
|
178040348e | ||
|
|
2cf80881e4 | ||
|
|
ee15f3b076 | ||
|
|
6919f038ee | ||
|
|
47029da6ed | ||
|
|
e1d292d1bf | ||
|
|
f08638812c | ||
|
|
de5424c506 | ||
|
|
b46d734442 | ||
|
|
92ad96e435 | ||
|
|
6166314cbf | ||
|
|
9fa8f58cfb | ||
|
|
ed997ad8a2 | ||
|
|
20cea95783 | ||
|
|
0dc5a45b5d | ||
|
|
54b1280bef | ||
|
|
3ac65d4630 | ||
|
|
ed49876c69 | ||
|
|
69d0f07bb6 | ||
|
|
b975f318a0 | ||
|
|
ce1df2c205 | ||
|
|
75d8146e24 | ||
|
|
268c95f29a | ||
|
|
91dd069e5a | ||
|
|
f96d96f26e | ||
|
|
ae231b6101 | ||
|
|
c709159ea4 | ||
|
|
c8089109c6 | ||
|
|
807cfe1a2a | ||
|
|
420e035e2b | ||
|
|
a0377df713 | ||
|
|
301b069312 | ||
|
|
6c09e55fd4 | ||
|
|
d8467f0b02 | ||
|
|
a9832da7d7 | ||
|
|
2942907963 | ||
|
|
02ca13bcbd | ||
|
|
833bc675ce | ||
|
|
ac9a7df57b | ||
|
|
657bc3935c | ||
|
|
10085a3c23 | ||
|
|
0d3888565c | ||
|
|
8533bbff90 | ||
|
|
42630cc3c4 | ||
|
|
188fb81bdc | ||
|
|
cbc3a52643 | ||
|
|
bda18260b3 | ||
|
|
32dd938eef | ||
|
|
2826bc95c7 | ||
|
|
b35817740b | ||
|
|
fffef0c228 | ||
|
|
0caeca4824 | ||
|
|
b5536dbb6b | ||
|
|
26f8140ee8 | ||
|
|
6e4d9bc1d7 | ||
|
|
610f492762 | ||
|
|
f88cdb4f9d | ||
|
|
463358b53a | ||
|
|
3b038fb700 | ||
|
|
8efec21408 | ||
|
|
39691a2254 | ||
|
|
2924c611f7 | ||
|
|
be5449b100 | ||
|
|
1e5d4800fc | ||
|
|
cb49fa6949 | ||
|
|
315634c173 | ||
|
|
eafa881cf8 | ||
|
|
a851f8823c | ||
|
|
354002312e | ||
|
|
392e0601a8 | ||
|
|
035ef26486 | ||
|
|
db9e7fea0a | ||
|
|
7077b7d831 | ||
|
|
bd94b14dad | ||
|
|
8b0e4d803f | ||
|
|
5f81bfa899 | ||
|
|
85626d3356 | ||
|
|
ceeb9021d9 | ||
|
|
09887f3a9d | ||
|
|
26c341c6ec | ||
|
|
bfca698457 | ||
|
|
c13d39ca05 | ||
|
|
0bcf1b7ed4 | ||
|
|
d7622f5143 | ||
|
|
d1d992f807 | ||
|
|
16f77f442c | ||
|
|
3b959b8567 | ||
|
|
345a81aaf7 | ||
|
|
06ef5cb53b | ||
|
|
748e5e2201 | ||
|
|
981563df6a | ||
|
|
b4df8deac6 | ||
|
|
e3da31970e | ||
|
|
6fb06da0e4 | ||
|
|
945cd52787 | ||
|
|
a880c2dadb | ||
|
|
76855d3246 | ||
|
|
d6c4619717 | ||
|
|
f799e6b2d6 | ||
|
|
2acd506504 | ||
|
|
6cfb776eb4 | ||
|
|
f09e34b1b5 | ||
|
|
1146639d6f | ||
|
|
a9ac2e8897 | ||
|
|
1a1960f5a8 | ||
|
|
996b135973 | ||
|
|
9337238832 | ||
|
|
ee049a953c | ||
|
|
adfe3e8e08 | ||
|
|
71c9c99db7 | ||
|
|
dd66083f45 | ||
|
|
9ddd6422bd | ||
|
|
7d24c274cd | ||
|
|
3911824fc6 | ||
|
|
9729d2fef1 | ||
|
|
3cb4005889 | ||
|
|
bf759ebaf8 | ||
|
|
3d0eb3df65 | ||
|
|
fe09d0f773 | ||
|
|
880d2b0233 | ||
|
|
5865d2eeb2 | ||
|
|
528735abd1 | ||
|
|
d4df995c71 | ||
|
|
3b13fa663e | ||
|
|
e973c64ffd | ||
|
|
6ecf10c084 | ||
|
|
79e765fbb0 | ||
|
|
d4a08d5e2b | ||
|
|
eeb3f40018 | ||
|
|
e6b4c1d13c | ||
|
|
6ecbc3392a | ||
|
|
7f10c394de | ||
|
|
fae099fd93 | ||
|
|
64d43b54e9 | ||
|
|
d4abdbc93f | ||
|
|
b595453efe | ||
|
|
d255034c02 | ||
|
|
d6fd2cc28c | ||
|
|
7c47de5dc0 | ||
|
|
c1ab2b3505 | ||
|
|
1665ab41de | ||
|
|
6d397b59bf | ||
|
|
9dfbcbe878 | ||
|
|
ca0970b650 | ||
|
|
02218a4902 | ||
|
|
a900044306 | ||
|
|
7382a534a0 | ||
|
|
3b76b15bb4 | ||
|
|
f2eda0fef6 | ||
|
|
61c1cce814 | ||
|
|
0f1660e3ed | ||
|
|
e5b55e49b7 | ||
|
|
1ff01526bc | ||
|
|
1ed227080b | ||
|
|
b07c089946 | ||
|
|
d603067c77 | ||
|
|
5a97b8f83a | ||
|
|
d0a09871c7 | ||
|
|
e87aa8d74d | ||
|
|
e204e6153d | ||
|
|
5c69f8dd28 | ||
|
|
f3fa309dbf | ||
|
|
9d74e7cb46 | ||
|
|
fea72bba2e | ||
|
|
b364f14260 | ||
|
|
8077ec4f46 | ||
|
|
e5277574df | ||
|
|
e0563dd4ce | ||
|
|
1987cf3df3 | ||
|
|
9f5145c3b2 | ||
|
|
5921cf8adc | ||
|
|
f154b45076 | ||
|
|
2c41272c36 | ||
|
|
57d4beafed | ||
|
|
bdb526bd1e | ||
|
|
bb9dbe248f | ||
|
|
caf2e7fbed | ||
|
|
d0b8fb16d6 | ||
|
|
2e57e66095 | ||
|
|
05fc856e86 | ||
|
|
74e6a679e2 | ||
|
|
17db5aa31a | ||
|
|
387c97de71 | ||
|
|
de45491c8c | ||
|
|
4da4cbfc15 | ||
|
|
9fbf2f2211 | ||
|
|
41b2965743 | ||
|
|
a02c11d3cc | ||
|
|
c888aa5ecf | ||
|
|
30de9a2d9c | ||
|
|
804327f3aa | ||
|
|
ba59418b6d | ||
|
|
f39678002f | ||
|
|
3e402cae3d | ||
|
|
b4aa1b3eda | ||
|
|
0f9bab00ef | ||
|
|
9d8706e8c6 | ||
|
|
6ba1e14888 | ||
|
|
0454f8c0a9 | ||
|
|
e17d8f2b93 | ||
|
|
b44dfa91e5 | ||
|
|
51d4c88694 | ||
|
|
f5f502875e | ||
|
|
9c769f4e8e | ||
|
|
87cdd5502e | ||
|
|
68d713a8dc | ||
|
|
dd34f8aab1 | ||
|
|
4a3fb8db4d | ||
|
|
59733de08e | ||
|
|
2b7a1c2e6d | ||
|
|
5db9e88bb9 | ||
|
|
a5b1f750dd | ||
|
|
4c514b459e | ||
|
|
32b99d14e1 | ||
|
|
de8f7c050c | ||
|
|
9c4c92e696 | ||
|
|
0cedd85afb | ||
|
|
1bb125ed31 | ||
|
|
72778235b8 | ||
|
|
76c34b8c03 | ||
|
|
3ca2da2990 | ||
|
|
d3035efd5c | ||
|
|
64a74a8928 | ||
|
|
e0fcf23dba | ||
|
|
ad1ce5f9ea | ||
|
|
c3ce0ccc54 | ||
|
|
0b7c96f66a | ||
|
|
3daa6a9772 | ||
|
|
c86cdf9227 | ||
|
|
ca78c1b100 | ||
|
|
398701c4d7 | ||
|
|
00e420fd45 | ||
|
|
a07cbc467d | ||
|
|
ccd5144e7b | ||
|
|
195182f850 | ||
|
|
c2b48c444c | ||
|
|
156b44a49b | ||
|
|
a48b83155c | ||
|
|
e455d1b43d | ||
|
|
14c2a79673 | ||
|
|
97a1d97678 | ||
|
|
5001642504 | ||
|
|
5f413a2823 | ||
|
|
4d04682b33 | ||
|
|
cb38d275d1 | ||
|
|
84cfe1c97d | ||
|
|
47b01df621 | ||
|
|
7e073b16ec | ||
|
|
ab4298fa2d | ||
|
|
7997ea45b7 | ||
|
|
7aa9d90d0d | ||
|
|
451e4cbbdb | ||
|
|
520758b355 | ||
|
|
e4e9ef0fb2 | ||
|
|
ada724069b | ||
|
|
9198068a19 | ||
|
|
263d5160aa | ||
|
|
65137207ae | ||
|
|
3bd9935346 | ||
|
|
23e2488cd1 | ||
|
|
e9bbdd4ce0 | ||
|
|
f5a7ec9291 | ||
|
|
d80ea38a39 | ||
|
|
e64912622b | ||
|
|
04b25e2c06 | ||
|
|
51473bf0b8 | ||
|
|
921b973f81 | ||
|
|
42d08ccf4c | ||
|
|
68412d8006 | ||
|
|
4c89ec3190 | ||
|
|
b2fa1ef247 | ||
|
|
9872c77591 | ||
|
|
7ecb2f18e9 | ||
|
|
d0c09f3e00 | ||
|
|
c1e38a93bd | ||
|
|
6a3041524a | ||
|
|
f069db5c61 | ||
|
|
3b768d5c7d | ||
|
|
6b8698353b | ||
|
|
5868312388 | ||
|
|
b0b139b179 | ||
|
|
4bf4806b9e | ||
|
|
826e745fc3 | ||
|
|
bc2867984c | ||
|
|
31633bc61d | ||
|
|
1623feacf1 | ||
|
|
a203657e2a | ||
|
|
1f4a2ee3d9 | ||
|
|
e9c54cbdd4 | ||
|
|
4e8a15590f | ||
|
|
c745d51832 | ||
|
|
f9b97b7505 | ||
|
|
489da64c5e | ||
|
|
07c35cd917 | ||
|
|
3f72a21d75 | ||
|
|
4ad6170a49 | ||
|
|
18df0817b1 | ||
|
|
0eca7ae29e | ||
|
|
6a5913230f | ||
|
|
b7a284e051 | ||
|
|
0accdda2dd | ||
|
|
37b855d7a7 | ||
|
|
7432dd5118 | ||
|
|
59ff34c9cc | ||
|
|
b81ada265b | ||
|
|
65c9c6c586 | ||
|
|
278cbb4d34 | ||
|
|
381c19e701 | ||
|
|
fc2ebefc9e | ||
|
|
423b75e7d9 | ||
|
|
e984a437df | ||
|
|
0818dae0df | ||
|
|
771a2a3217 | ||
|
|
04b3f87b32 | ||
|
|
6c3d0ffc5e | ||
|
|
3f093ce934 | ||
|
|
9a47ec59b0 | ||
|
|
69ce8d9225 | ||
|
|
93b884de01 | ||
|
|
a308f82be9 | ||
|
|
b3100a336c | ||
|
|
8f7f9bb009 | ||
|
|
25dadae446 | ||
|
|
1d588c4f41 | ||
|
|
ac58fe4bb7 | ||
|
|
45c5af8e81 | ||
|
|
673d9e3a12 | ||
|
|
738e11c8e3 | ||
|
|
842379e8f9 | ||
|
|
d1bb796d5f | ||
|
|
b7db98ffdc | ||
|
|
6ce6770df4 | ||
|
|
56ef3b75b2 | ||
|
|
bd1be5e953 | ||
|
|
8f72968e76 | ||
|
|
b27f91ac87 | ||
|
|
35357c07a3 | ||
|
|
943436467e | ||
|
|
747fd272d4 | ||
|
|
3ec00956df | ||
|
|
1248102635 | ||
|
|
2be706a860 | ||
|
|
2da2c56705 | ||
|
|
4d8305c844 | ||
|
|
8611bc6d3c | ||
|
|
2572f86030 | ||
|
|
83ba74bc12 | ||
|
|
6bfb549fd0 | ||
|
|
31a7ac2951 | ||
|
|
1cb2bdb525 | ||
|
|
daa9dca142 | ||
|
|
c947993e9e | ||
|
|
8dd2b7ae9a | ||
|
|
8eb98bff29 | ||
|
|
f9812a689e | ||
|
|
e72fad0bfa | ||
|
|
61a582a330 | ||
|
|
80c357986d | ||
|
|
6c2b0b487f | ||
|
|
ad3d2b534f | ||
|
|
a4e9333ab8 | ||
|
|
e08b349e31 | ||
|
|
2bf7a28508 | ||
|
|
e6bee2ae2e | ||
|
|
fc832d73f9 | ||
|
|
e8e9b53f53 | ||
|
|
7304376e90 | ||
|
|
7e9914afda | ||
|
|
89f6f6deb0 | ||
|
|
faa4a123fd | ||
|
|
f1ae24d0fe | ||
|
|
4670d87c96 | ||
|
|
4848b51ecc | ||
|
|
2aa0cc9d5d | ||
|
|
5e3ace804d | ||
|
|
42b109a5f1 | ||
|
|
5a8afb516b | ||
|
|
ef2771cc83 | ||
|
|
942c11af1d | ||
|
|
b291e3f4b6 | ||
|
|
46e3f18edc | ||
|
|
0886d8a4b6 | ||
|
|
6a23e4ac91 | ||
|
|
f7b7718679 | ||
|
|
9ae034da77 | ||
|
|
4d1b2abd3d | ||
|
|
f8e3f64907 | ||
|
|
3702835958 | ||
|
|
23c2e1472e | ||
|
|
6367c9ed3b | ||
|
|
2d299a6a4a | ||
|
|
ced629d4dc | ||
|
|
714121d272 | ||
|
|
14c140cf85 | ||
|
|
881aec7c04 | ||
|
|
85b31e7e1d | ||
|
|
66c9287202 | ||
|
|
f0f59b8568 | ||
|
|
e03efa6687 | ||
|
|
5a22651961 | ||
|
|
236689452e | ||
|
|
b686f267e1 | ||
|
|
096e323409 | ||
|
|
b71ec14615 | ||
|
|
5a2304b785 | ||
|
|
6f413c555d | ||
|
|
fbc92aea41 | ||
|
|
7b31343991 | ||
|
|
872cb39715 | ||
|
|
dd35674307 | ||
|
|
1d102e94b4 | ||
|
|
c18932443a | ||
|
|
7563b4abdf | ||
|
|
20509e5cc2 | ||
|
|
43e30e95bc | ||
|
|
9efff1060b | ||
|
|
3b608e2b4f | ||
|
|
bfed10c019 | ||
|
|
4bb29b8f14 | ||
|
|
6eca0dd1d1 | ||
|
|
cc96bcde34 | ||
|
|
ab61e40567 | ||
|
|
e17e2fb865 | ||
|
|
a6bd82a41c | ||
|
|
fac495c0ef | ||
|
|
15bea85bfb | ||
|
|
f889080bc0 | ||
|
|
7d62358558 | ||
|
|
f7a7e89c56 | ||
|
|
5f6720d37b | ||
|
|
e541201a08 | ||
|
|
2bf04f69cf | ||
|
|
8479aea1b0 | ||
|
|
d17a1da5b2 | ||
|
|
f1ff06c098 | ||
|
|
df785043e8 | ||
|
|
5ae4799a5f | ||
|
|
8cf4a6bd23 | ||
|
|
3e067c8492 | ||
|
|
6847639c65 | ||
|
|
0592c3ebc8 | ||
|
|
2ca1cbd110 | ||
|
|
8642733b78 | ||
|
|
5a4293182c | ||
|
|
6075878038 | ||
|
|
7eaeac886f | ||
|
|
fe27a1ca1c | ||
|
|
0caea0b473 | ||
|
|
370c77433b | ||
|
|
ac85d35097 | ||
|
|
85c3e06910 | ||
|
|
7d0736bfc2 | ||
|
|
b0b011ac56 | ||
|
|
23091c2e73 | ||
|
|
ef2ba3ea7a | ||
|
|
df60f3b2b1 | ||
|
|
4cc001d2f0 | ||
|
|
e2bb32d9de | ||
|
|
725125a274 | ||
|
|
dcf2018ec6 | ||
|
|
e5316f45aa | ||
|
|
02c202d075 | ||
|
|
9e9fb14f55 | ||
|
|
61e0d777a8 | ||
|
|
78c3e33d38 | ||
|
|
3be5b5e1ee | ||
|
|
f550479298 | ||
|
|
5fe0ab8815 | ||
|
|
56ccb43c2b | ||
|
|
93d0754935 | ||
|
|
d82366b59f | ||
|
|
35202f7000 | ||
|
|
bc5aadc394 | ||
|
|
181c480ba6 | ||
|
|
b973c71638 | ||
|
|
ede0a44251 | ||
|
|
e4b0f8601a | ||
|
|
d12c615f0b | ||
|
|
e58163d631 | ||
|
|
e801910519 | ||
|
|
dbca627bc3 | ||
|
|
b09534aad5 | ||
|
|
63c2d5d83d | ||
|
|
52dc9ff8ae | ||
|
|
10e184afb0 | ||
|
|
4e645001fb | ||
|
|
e33b68bd55 | ||
|
|
7094bda2fb | ||
|
|
409c181cb2 | ||
|
|
6c38f90b9a | ||
|
|
d693a99852 | ||
|
|
508c4a734a | ||
|
|
cd93ea3a38 | ||
|
|
bf88dbd4b3 |
No files matched your search
@@ -0,0 +1,16 @@
|
|||||||
|
# VCS / CI noise
|
||||||
|
/.gitattributes export-ignore
|
||||||
|
/.gitignore export-ignore
|
||||||
|
/.gitmodules export-ignore
|
||||||
|
/.github export-ignore
|
||||||
|
|
||||||
|
# Dev tooling/config
|
||||||
|
/.vscode export-ignore
|
||||||
|
*.code-workspace export-ignore
|
||||||
|
/.editorconfig export-ignore
|
||||||
|
/.eslintrc* export-ignore
|
||||||
|
/.prettier* export-ignore
|
||||||
|
|
||||||
|
# Omit release-only helpers
|
||||||
|
/server/restaurantchecker.lua export-ignore
|
||||||
|
/server/installitems.lua export-ignore
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # 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: jixelpatterns
|
||||||
|
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
|
||||||
|
custom: ['https://jimathy666.tebex.io/']
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: Discord Commit Notifier
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['*']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
notify:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Validate event JSON (sanity check)
|
||||||
|
run: jq -e . "$GITHUB_EVENT_PATH" >/dev/null
|
||||||
|
|
||||||
|
- name: Send commit payload to Discord Bot
|
||||||
|
env:
|
||||||
|
URL: http://${{ secrets.DISCORDBOT }}:3000/github-commits
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
curl -sS --fail-with-body -X POST "$URL" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data-binary "@$GITHUB_EVENT_PATH"
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
name: Discord Release Notifier
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Manual or UI-published releases
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
# Releases created by your tag-driven workflow
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Package & Release"] # must match the 'name:' in release.yml
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# 1) Handle manual / UI / non-workflow-created releases
|
||||||
|
notify-from-release:
|
||||||
|
if: github.event_name == 'release' && github.event.action == 'published'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Send release payload to Discord Bot
|
||||||
|
env:
|
||||||
|
URL: http://${{ secrets.DISCORDBOT }}:3000/github-releases
|
||||||
|
run: |
|
||||||
|
curl -sS -X POST "$URL" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data-binary "@$GITHUB_EVENT_PATH"
|
||||||
|
|
||||||
|
# 2) Handle releases created by your 'Package & Release' workflow
|
||||||
|
# (which won’t trigger `on: release` when using GITHUB_TOKEN)
|
||||||
|
notify-from-workflow-run:
|
||||||
|
if: >
|
||||||
|
github.event_name == 'workflow_run' &&
|
||||||
|
github.event.workflow_run.conclusion == 'success'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Install jq
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Fetch release JSON by tag
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
OWNER="${GITHUB_REPOSITORY%/*}"
|
||||||
|
REPO="${GITHUB_REPOSITORY#*/}"
|
||||||
|
TAG="${{ github.event.workflow_run.head_branch }}"
|
||||||
|
# head_branch is the tag name for a tag push workflow_run
|
||||||
|
curl -fsSL -H "Authorization: Bearer $GH_TOKEN" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"https://api.github.com/repos/$OWNER/$REPO/releases/tags/$TAG" \
|
||||||
|
> release.json
|
||||||
|
|
||||||
|
- name: Wrap as 'release' event payload & notify bot
|
||||||
|
env:
|
||||||
|
URL: http://${{ secrets.DISCORDBOT }}:3000/github-releases
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
jq -n --slurpfile rel release.json \
|
||||||
|
--arg repo "$GITHUB_REPOSITORY" \
|
||||||
|
--arg repo_url "https://github.com/$GITHUB_REPOSITORY" \
|
||||||
|
--arg action "published" '
|
||||||
|
{ action: $action,
|
||||||
|
repository: { full_name: $repo, html_url: $repo_url },
|
||||||
|
release: $rel[0],
|
||||||
|
sender: { login: "github-actions[bot]" } }' > payload.json
|
||||||
|
|
||||||
|
curl -sS -X POST "$URL" \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data-binary "@payload.json"
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
name: Package & Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
- "*.*.*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout (full history)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Derive vars
|
||||||
|
id: vars
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT # e.g. jim_bridge
|
||||||
|
echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT # strip leading v
|
||||||
|
|
||||||
|
- name: Build zip with folder prefix
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
NAME='${{ steps.vars.outputs.name }}'
|
||||||
|
VER='${{ steps.vars.outputs.version }}'
|
||||||
|
git archive --format=zip --prefix="${NAME}/" -o "${NAME}-${VER}.zip" "$GITHUB_REF_NAME"
|
||||||
|
ls -lh "${NAME}-${VER}.zip"
|
||||||
|
|
||||||
|
# --- Patch notes generation (commit titles + links) -------------------
|
||||||
|
- name: Find previous tag (SemVer aware)
|
||||||
|
id: prev
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
CUR="${GITHUB_REF_NAME}"
|
||||||
|
# Sort tags by version desc, drop the current, take the next newest
|
||||||
|
PREV=$(git tag --sort=-v:refname | grep -v -x "$CUR" | head -n 1 || true)
|
||||||
|
echo "prev=$PREV" >> $GITHUB_OUTPUT
|
||||||
|
echo "Previous tag: ${PREV:-<none>}"
|
||||||
|
|
||||||
|
- name: Generate RELEASE_NOTES.md from commits
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
REPO="${{ github.repository }}"
|
||||||
|
CUR="${GITHUB_REF_NAME}"
|
||||||
|
PREV='${{ steps.prev.outputs.prev }}'
|
||||||
|
|
||||||
|
if [ -n "$PREV" ]; then
|
||||||
|
RANGE="$PREV..$CUR"
|
||||||
|
HEADER="## Changes in $CUR"
|
||||||
|
else
|
||||||
|
# First release: include all commits
|
||||||
|
ROOT=$(git rev-list --max-parents=0 HEAD | tail -n 1)
|
||||||
|
RANGE="$ROOT..$CUR"
|
||||||
|
HEADER="## Changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "$HEADER"
|
||||||
|
echo
|
||||||
|
# Oldest → newest, subject only, skip merge commits
|
||||||
|
# Escape '[' and ']' so markdown doesn't break on rare titles
|
||||||
|
git log --reverse --no-merges --pretty=format:'- ['%h'] - %s' "$RANGE" \
|
||||||
|
| perl -pe 's/([\[\]])/\\$1/g'
|
||||||
|
} > RELEASE_NOTES.md
|
||||||
|
|
||||||
|
echo "--- RELEASE_NOTES.md ---"
|
||||||
|
cat RELEASE_NOTES.md
|
||||||
|
echo "------------------------"
|
||||||
|
|
||||||
|
- name: Create / Update GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.ref_name }}
|
||||||
|
name: ${{ github.ref_name }}
|
||||||
|
files: |
|
||||||
|
*.zip
|
||||||
|
body_path: RELEASE_NOTES.md # attach our generated notes
|
||||||
|
# If you wanted GitHub's auto notes instead, set:
|
||||||
|
# generate_release_notes: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1,26 +1,88 @@
|
|||||||
# Jim_Bridge
|
# Jim_Bridge
|
||||||
|
|
||||||
This script is intended to be used with my all my scripts (soon)
|
This script is intended to be used with my all my scripts (soon)
|
||||||
|
|
||||||
It was started due to wanting to bring the same features from some scripts into others with minimal work and multiple updates
|
It was started due to wanting to bring the same features from some scripts into others with minimal work and without multiple updates fer script
|
||||||
- Having certain functions in one place(this script) makes it easier to update, enchance and fix things
|
|
||||||
- This brings the possibility of branching to mutliple frameworks as I've added some already:
|
It BRIDGES frameworks and cores together through this script and does it best to detect what is being used to automate the process
|
||||||
- `"qb-core"`
|
- Having certain functions in one place makes it easier to update, enchance and fix things already in place
|
||||||
- `"qbx-core"`
|
- This brings the possibility of branching to mutliple frameworks as I've added some already:
|
||||||
- `"ox_core"`
|
- `"qb-core"`
|
||||||
- `"es_extended"` (requires ox_lib and ox_inventory)
|
- `"qbx-core"`
|
||||||
|
- `"ox_core"`
|
||||||
All the next updates of my scripts will use this script and be added as a dependancy
|
- `"es_extended"` - requires ox_lib and ox_inventory
|
||||||
|
- `"rsg-core"` - basic support for RedM's RSG Core
|
||||||
------
|
|
||||||
|
All of my scripts will use this script and be added as a dependancy
|
||||||
It was a tough decision to put it up on github instead of tebex and encrypted
|
|
||||||
|
This was originally designed to be used for my scripts but has grown into a whole framework of unified functions that anyone can use for their own, I encourage it
|
||||||
But I want this script to grow with help of others who know more about other cores
|
|
||||||
|
------
|
||||||
---
|
|
||||||
|
## I want this script to grow with help of others who know more about other cores, I'm not a book of framework knowledge
|
||||||
The installation of this script is simple
|
This script was designed by me through over a year of research and testing.
|
||||||
- it just needs to start before any script that requires it
|
Some of it of it hasn't been personally tested but the information has been gathered through documentation on other scripts
|
||||||
- it can start before core scripts if you want
|
I hope it works as well I intend, but feel free to do pull requests if you know how to fix an issue
|
||||||
- for `qb-core` I personally place this script in `resources > [standalone]`
|
|
||||||
|
(Please also keep it to a similar format to prevent breakages in other scripts)
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
The installation of this script is simple
|
||||||
|
- REMOVE `-main` from the folder name, like any other github hosted script
|
||||||
|
- It just needs to start before any script that requires it
|
||||||
|
- It can start before core scripts if you want
|
||||||
|
- For example with `qb-core` I personally place this script in `resources > [standalone]`
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
I've added the ability to add override server convars to your server.cfg
|
||||||
|
- This can be used to ensure you don't have silly mistakes like forgetting to change what inventory system you use
|
||||||
|
- Also it can force debug mode off, to ensure your live server doesn't accidently get polyzones and debug information showing
|
||||||
|
- This isn't required but helpful if you are like me
|
||||||
|
|
||||||
|
```
|
||||||
|
# Jim Config Settings
|
||||||
|
# These optional but forces settings for all jim scripts
|
||||||
|
#-------
|
||||||
|
# Add this to your live server.cfg and set to true to force debug mode off
|
||||||
|
# Set to false if to dev server to allow debug mode
|
||||||
|
setr jim_DisableDebug true
|
||||||
|
setr jim_DisableEventDebug true
|
||||||
|
|
||||||
|
# Force the default setting for what framework scripts should be used
|
||||||
|
setr jim_menuScript qb # qb, ox, gta, lation
|
||||||
|
setr jim_notifyScript gta # qb, ox, gta, esx, okok, red, lation
|
||||||
|
setr jim_drawTextScript qb # qb, ox, gta, esx, lation
|
||||||
|
setr jim_progressBarScript qb # qb, ox, gta, esx, lation
|
||||||
|
setr jim_skillCheckScript qb # qb, ox, gta, lation
|
||||||
|
setr jim_dontUseTarget false # Set to true to disable target systems and use draw text 3d
|
||||||
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
## Support for different frameworks and scripts
|
||||||
|
|
||||||
|
In `starter.lua` is the list of script folder names, this is already setup but this is for people who have customised/renamed their cores or scripts
|
||||||
|
|
||||||
|
# WIP
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For organised documentation please see the new `documentation` folder within this resource
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
In your own resource, simply call the desired function exported from `jim_bridge`. Each function is built to work across multiple frameworks, offering compatibility and consistency.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Example usage:
|
||||||
|
createCallback("myCallback", function(data)
|
||||||
|
print("Callback received:", data)
|
||||||
|
end)
|
||||||
|
|
||||||
|
triggerCallback("myCallback", "Hello World")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
local function readBoolMeta(key, default)
|
||||||
|
local val = GetResourceMetadata(GetCurrentResourceName(), key, 0)
|
||||||
|
if not val then return default end
|
||||||
|
val = tostring(val):lower()
|
||||||
|
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
|
||||||
|
end
|
||||||
|
|
||||||
|
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
|
||||||
|
|
||||||
|
if not SUPPRESS_UPDATES then
|
||||||
|
local function parseVersion(version)
|
||||||
|
local parts = {}
|
||||||
|
for num in version:gmatch("%d+") do
|
||||||
|
table.insert(parts, tonumber(num))
|
||||||
|
end
|
||||||
|
return parts
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compareVersions(current, newest)
|
||||||
|
local currentParts = parseVersion(current)
|
||||||
|
local newestParts = parseVersion(newest)
|
||||||
|
for i = 1, math.max(#currentParts, #newestParts) do
|
||||||
|
local c = currentParts[i] or 0
|
||||||
|
local n = newestParts[i] or 0
|
||||||
|
if c < n then return -1
|
||||||
|
elseif c > n then return 1 end
|
||||||
|
end
|
||||||
|
return 0 -- equal
|
||||||
|
end
|
||||||
|
|
||||||
|
function CheckBridgeVersion()
|
||||||
|
if IsDuplicityVersion() then
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(4000)
|
||||||
|
local currentVersionRaw = GetResourceMetadata("jim_bridge", 'version')
|
||||||
|
--PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/test.txt', function(err, body, headers)
|
||||||
|
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/jim_bridge/master/version.txt', function(err, body, headers)
|
||||||
|
if not body then
|
||||||
|
print("^1Unable to run version check for ^7'^3jim_bridge^7' (^3"..currentVersionRaw.."^7)")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
for line in body:gmatch("[^\r\n]+") do
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
|
||||||
|
local newestVersionRaw = lines[1] or "0.0.0"
|
||||||
|
local changelog = {}
|
||||||
|
for i = 2, #lines do
|
||||||
|
table.insert(changelog, lines[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
local compareResult = compareVersions(currentVersionRaw, newestVersionRaw)
|
||||||
|
|
||||||
|
if compareResult == 0 then
|
||||||
|
print("^7'^3jim_bridge^7' - ^2You are running the latest version^7. ^7(^3"..currentVersionRaw.."^7)")
|
||||||
|
elseif compareResult < 0 then
|
||||||
|
print("^1----------------------------------------------------------------------^7")
|
||||||
|
print("^7'^3jim_bridge^7' - ^1You are running an outdated version^7! ^7(^3"..currentVersionRaw.."^7 → ^3"..newestVersionRaw.."^7)")
|
||||||
|
for _, line in ipairs(changelog) do
|
||||||
|
print((line:find("http") and "^7" or "^5")..line)
|
||||||
|
end
|
||||||
|
print("^1----------------------------------------------------------------------^7")
|
||||||
|
SetTimeout(3600000, function()
|
||||||
|
CheckBridgeVersion()
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
print("^7'^3jim_bridge^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersionRaw.."^7) (^1Expect Errors^7)")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
CheckBridgeVersion()
|
||||||
|
end
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
if _G.__bridge_cache_loaded then return end
|
||||||
|
_G.__bridge_cache_loaded = true
|
||||||
|
|
||||||
|
_G.BridgeCache = {
|
||||||
|
hasCache = false,
|
||||||
|
data = nil,
|
||||||
|
queue = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterNetEvent("jim_bridge:receiveCache", function(data)
|
||||||
|
if _G.BridgeCache.hasCache then return end
|
||||||
|
_G.BridgeCache.data = data
|
||||||
|
_G.BridgeCache.hasCache = true
|
||||||
|
|
||||||
|
for _, cb in ipairs(_G.BridgeCache.queue) do
|
||||||
|
cb(data)
|
||||||
|
end
|
||||||
|
_G.BridgeCache.queue = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Only send the request ONCE per client session
|
||||||
|
TriggerServerEvent("jim_bridge:requestCache")
|
||||||
|
|
||||||
|
-- Exported async function
|
||||||
|
function GetBridgeCache(callback)
|
||||||
|
if _G.BridgeCache.hasCache then
|
||||||
|
callback(_G.BridgeCache.data)
|
||||||
|
else
|
||||||
|
table.insert(_G.BridgeCache.queue, callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
exports("GetBridgeCache", GetBridgeCache)
|
||||||
-501
@@ -1,501 +0,0 @@
|
|||||||
if GetResourceState(OXLibExport):find("start") then
|
|
||||||
createCallback(GetCurrentResourceName()..':server:GetStashItems', function(source, stashName) local stash = getStash(stashName) return stash end)
|
|
||||||
else
|
|
||||||
createCallback(GetCurrentResourceName()..':server:GetStashItems', function(source, cb, stashName) local stash = getStash(stashName) cb(stash) end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local timeout = 0
|
|
||||||
local timing = false
|
|
||||||
local stashItems = {}
|
|
||||||
function GetStashTimeout(stashName, stop)
|
|
||||||
if stop then stashItems = {} timing = false timeout = 0 return end
|
|
||||||
if stashItems[1] then return true else
|
|
||||||
if timeout <= 0 then
|
|
||||||
stashItems = triggerCallback(GetCurrentResourceName()..':server:GetStashItems', stashName)
|
|
||||||
timeout = 15000
|
|
||||||
if not timing then
|
|
||||||
CreateThread(function()
|
|
||||||
timing = true
|
|
||||||
while timeout > 0 do timeout -= 1000 Wait(1000) end
|
|
||||||
timing = false stashItems = {} timeout = 0
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local CraftLock = false
|
|
||||||
function craftingMenu(data)
|
|
||||||
if CraftLock then return end
|
|
||||||
if data.stashName and not GetStashTimeout(data.stashName) then
|
|
||||||
--triggerNotify(nil, "Chacking", "success")
|
|
||||||
end
|
|
||||||
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
|
|
||||||
local Menu = {}
|
|
||||||
local Recipes = data.craftable.Recipes
|
|
||||||
for i = 1, #Recipes do
|
|
||||||
for k, v in pairs(Recipes[i]) do
|
|
||||||
if k ~= "amount" and k ~= "job" and k ~= "gang" then
|
|
||||||
if Recipes[i].job then hasjob = false
|
|
||||||
for l, b in pairs(data.craftable[i]["job"]) do
|
|
||||||
hasjob = hasJob(l)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local setheader, settext = "", ""
|
|
||||||
local text = ""
|
|
||||||
local disable = false
|
|
||||||
local checktable = {}
|
|
||||||
if Recipes[i].job and hasjob == false then else
|
|
||||||
for l, b in pairs(Recipes[i][tostring(k)]) do
|
|
||||||
if not Items[l] then print("^3Error^7: ^2Script can't find ingredient item in Shared Items - ^1"..l.."^7") return end
|
|
||||||
settext = settext..(settext ~= "" and br or "")..Items[l].label..(b > 1 and " x"..b or "")
|
|
||||||
if data.stashName then checktable[l] = stashhasItem(stashItems, l, b)
|
|
||||||
else checktable[l] = hasItem(l, b) end
|
|
||||||
Wait(0)
|
|
||||||
end
|
|
||||||
for _, v in pairs(checktable) do
|
|
||||||
if not v then
|
|
||||||
disable = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
setheader = Items[tostring(k)].label..(Recipes[i]["amount"] > 1 and " x"..Recipes[i]["amount"] or "")..(not disable and " ✔️" or "")
|
|
||||||
Menu[#Menu + 1] = {
|
|
||||||
isMenuHeader = disable,
|
|
||||||
icon = invImg(tostring(k)),
|
|
||||||
header = setheader, txt = settext,
|
|
||||||
onSelect = function()
|
|
||||||
local transdata = { item = k, craft = data.craftable.Recipes[i], craftable = data.craftable, coords = data.coords, stashName = data.stashName, onBack = data.onBack }
|
|
||||||
if Config.Crafting.MultiCraft then
|
|
||||||
multiCraft(transdata)
|
|
||||||
else
|
|
||||||
makeItem(transdata)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
openMenu(Menu, { header = data.craftable.Header, onBack = data.onBack or nil, canClose = true, onExit = function() end, })
|
|
||||||
lookEnt(data.coords)
|
|
||||||
end
|
|
||||||
|
|
||||||
function multiCraft(data) local Menu = {}
|
|
||||||
local success = Config.Crafting.MultiCraftAmounts
|
|
||||||
if data.stashName and not GetStashTimeout(data.stashName) then
|
|
||||||
--triggerNotify(nil, "Refreshing stashinfo", "success")
|
|
||||||
end
|
|
||||||
Menu[#Menu+1] = {
|
|
||||||
isMenuHeader = true,
|
|
||||||
icon = invImg(data.item),
|
|
||||||
header = Items[data.item].label,
|
|
||||||
}
|
|
||||||
for k in pairsByKeys(success) do
|
|
||||||
local settext = ""
|
|
||||||
for l, b in pairs(data.craft[data.item]) do
|
|
||||||
if data.stashName then
|
|
||||||
success[k] = stashhasItem(stashItems, l, (b * k))
|
|
||||||
else
|
|
||||||
success[k] = hasItem(l, (b * k))
|
|
||||||
end
|
|
||||||
settext = settext..(settext ~= "" and br or "")..Items[l].label..(b*k > 1 and "- x"..b*k or "")
|
|
||||||
Wait(0)
|
|
||||||
end
|
|
||||||
Menu[#Menu + 1] = {
|
|
||||||
isMenuHeader = not success[k],
|
|
||||||
header = "Craft - x"..k * data.craft.amount,
|
|
||||||
txt = settext,
|
|
||||||
onSelect = function ()
|
|
||||||
makeItem({item = data.item, craft = data.craft, craftable = data.craftable, amount = k, coords = data.coords, stashName = data.stashName, onBack = data.onBack })
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
openMenu(Menu, { header = data.craftable.Header, onBack = function() craftingMenu(data) end, })
|
|
||||||
end
|
|
||||||
|
|
||||||
function makeItem(data)
|
|
||||||
if not CraftLock then CraftLock = true else return end
|
|
||||||
local bartime, bartext, animDict, anim = nil, nil, nil, nil
|
|
||||||
if data.craftable.progressBar then
|
|
||||||
bartime = data.craftable.progressBar.time
|
|
||||||
bartext = data.craftable.progressBar.label
|
|
||||||
else
|
|
||||||
bartime = 5000
|
|
||||||
bartext = Loc[Config.Lan].progressbar["progress_make"]
|
|
||||||
end
|
|
||||||
animDict = data.craftable.Anims.animDict or "amb@prop_human_parking_meter@male@idle_a"
|
|
||||||
anim = data.craftable.Anims.anim or "idle_a"
|
|
||||||
|
|
||||||
local amount = (data.amount and data.amount ~= 1) and data.amount or 1
|
|
||||||
|
|
||||||
local crafted = true
|
|
||||||
local crafting = true
|
|
||||||
|
|
||||||
local cam = createTempCam(PlayerPedId(), data.coords)
|
|
||||||
startTempCam(cam)
|
|
||||||
|
|
||||||
for i = 1, amount do
|
|
||||||
for _, v in pairs(data.craft) do
|
|
||||||
if type(v) == "table" then
|
|
||||||
for l, b in pairs(v) do
|
|
||||||
if crafting and progressBar({
|
|
||||||
label = "Using "..b.." "..Items[l].label,
|
|
||||||
time = 1000,
|
|
||||||
cancel = true,
|
|
||||||
dict = 'pickup_object',
|
|
||||||
anim = "putdown_low",
|
|
||||||
flag = 48,
|
|
||||||
icon = l,
|
|
||||||
}) then
|
|
||||||
--TriggerEvent('inventory:client:ItemBox', Items[l], "use", b) -- Show item box for each item
|
|
||||||
else
|
|
||||||
crafted = false
|
|
||||||
crafting = false
|
|
||||||
break
|
|
||||||
end
|
|
||||||
Wait(200)
|
|
||||||
end
|
|
||||||
if crafted then
|
|
||||||
if crafting and progressBar({
|
|
||||||
label = bartext..Items[data.item].label,
|
|
||||||
time = bartime,
|
|
||||||
cancel = true,
|
|
||||||
dict = animDict,
|
|
||||||
anim = anim,
|
|
||||||
flag = 8,
|
|
||||||
icon = data.item,
|
|
||||||
}) then
|
|
||||||
TriggerServerEvent(GetCurrentResourceName()..":Crafting:GetItem", data.item, data.craft, data.stashName)
|
|
||||||
else
|
|
||||||
crafting = false
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Wait(500)
|
|
||||||
end
|
|
||||||
stopTempCam()
|
|
||||||
CraftLock = false
|
|
||||||
lockInv(false)
|
|
||||||
craftingMenu(data)
|
|
||||||
ClearPedTasks(PlayerPedId())
|
|
||||||
end
|
|
||||||
|
|
||||||
RegisterNetEvent(GetCurrentResourceName()..":Crafting:GetItem", function(ItemMake, craftable, stashName)
|
|
||||||
local src = source
|
|
||||||
local amount = 1
|
|
||||||
if stashName then
|
|
||||||
local stashItems = getStash(stashName)
|
|
||||||
if craftable["amount"] then amount = craftable["amount"] end
|
|
||||||
local itemRemove = {}
|
|
||||||
for k, v in pairs(craftable[ItemMake]) do
|
|
||||||
for l, b in pairs(stashItems) do
|
|
||||||
if k == b.name then
|
|
||||||
itemRemove[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
stashRemoveItem(stashItems, stashName, itemRemove)
|
|
||||||
else
|
|
||||||
if craftable then
|
|
||||||
if craftable["amount"] then amount = craftable["amount"] end
|
|
||||||
for k, v in pairs(craftable[ItemMake]) do
|
|
||||||
TriggerEvent(GetCurrentResourceName()..":server:toggleItem", false, tostring(k), v, src)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
TriggerEvent(GetCurrentResourceName()..":server:toggleItem", true, ItemMake, amount, src)
|
|
||||||
end)
|
|
||||||
|
|
||||||
--[[SHOPS]]--
|
|
||||||
function openShop(data)
|
|
||||||
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
exports[OXInv]:openInventory('shop', { type = data.shop })
|
|
||||||
else
|
|
||||||
TriggerServerEvent(Config.JimShops and "jim-shops:ShopOpen" or "inventory:server:OpenInventory", "shop", data.shop, data.items)
|
|
||||||
end
|
|
||||||
lookEnt(data.coords)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Client & Server side
|
|
||||||
function hasItem(items, amount, src) local amount, count = amount and amount or 1, 0
|
|
||||||
if src then
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
local item = exports[OXInv]:GetItem(src, items)
|
|
||||||
local amount = (amount or 1)
|
|
||||||
if item.count >= amount then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..item.count.."^7/^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
else if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif GetResourceState(QSInv):find("start") then
|
|
||||||
for _, itemData in pairs(exports[QSInv]:GetInventory(src)) do
|
|
||||||
if itemData and (itemData.name == items) then
|
|
||||||
--if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
|
|
||||||
count += (itemData.amount or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count >= amount then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif GetResourceState(CoreInv):find("start") then
|
|
||||||
local item = exports[CoreInv]:getItems('primary-'..src, items)
|
|
||||||
|
|
||||||
if item.amount >= amount then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..item.amount.."^7/^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
else if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
local success = exports["codem-inventory"]:CheckItemValid(src, items, amount)
|
|
||||||
if success then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
for _, itemData in pairs(exports[CodeMInv]:GetUserInventory(src)) do
|
|
||||||
if itemData and (itemData.name == items) then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
|
|
||||||
count += (itemData.amount or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count >= amount then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
else
|
|
||||||
for _, itemData in pairs(Core.Functions.GetPlayer(src).PlayerData.items) do
|
|
||||||
if itemData and (itemData.name == items) then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
|
|
||||||
count += (itemData.amount or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count >= amount then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
local count = tonumber(exports[OXInv]:Search('count', items))
|
|
||||||
local amount = (amount or 1)
|
|
||||||
if count >= amount then if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end return true
|
|
||||||
else if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end return false end
|
|
||||||
|
|
||||||
elseif GetResourceState(QSInv):find("start") then
|
|
||||||
for _, itemData in pairs(exports[QSInv]:GetInventory()) do
|
|
||||||
if itemData and (itemData.name == items) then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
|
|
||||||
count += (itemData.amount or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count >= amount then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif GetResourceState(CoreInv):find("start") then
|
|
||||||
for _, itemData in pairs(exports[CoreInv]:getInventory('primary-'..GetPlayerServerId(PlayerPedId()))) do
|
|
||||||
if itemData and (itemData.name == items) then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
|
|
||||||
count += (itemData.amount or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count >= amount then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7") end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
local success = exports[CodeMInv]:CheckItemValid(items, amount)
|
|
||||||
if success then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..amount.." "..tostring(items)) end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif GetResourceState(QBInv):find("start") then
|
|
||||||
--if tonumber(GetResourceMetadata(QBInv, 'version')) <= 1.2 then
|
|
||||||
-- return exports[QBInv]:HasItem(items, amount)
|
|
||||||
--else
|
|
||||||
for _, itemData in pairs(Core.Functions.GetPlayerData().items) do
|
|
||||||
if itemData and (itemData.name == items) then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3HasItem^7: ^2Item^7: '^3"..tostring(items).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)") end
|
|
||||||
count += (itemData.amount or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count >= amount then
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3HasItem^7: ^5FOUND^7 ^3"..count.."^7/^3"..amount.." "..tostring(items))
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3HasItem^7: ^2"..tostring(items).." ^1NOT FOUND^7")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
-- end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Stash Items
|
|
||||||
function openStash(data)
|
|
||||||
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
exports[OXInv]:openInventory('stash', data.stash)
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
exports[CodeMInv]:OpenStash(data.stash, 400000, 100)
|
|
||||||
else
|
|
||||||
TriggerEvent("inventory:client:SetCurrentStash", data.stash)
|
|
||||||
TriggerServerEvent("inventory:server:OpenInventory", "stash", data.stash)
|
|
||||||
end
|
|
||||||
lookEnt(data.coords)
|
|
||||||
end
|
|
||||||
|
|
||||||
function getStash(stashName)
|
|
||||||
local stashItems, items = {}, {}
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7ox_inventory") end
|
|
||||||
stashItems = exports[OXInv]:Inventory(stashName).items
|
|
||||||
elseif GetResourceState(QSInv):find("start") then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7qs-inventory") end
|
|
||||||
stashItems = exports[QSInv]:GetStashItems(stashName)
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7qs-inventory") end
|
|
||||||
stashItems = exports[CodeMInv]:GetInventoryItems('Stash', stashName)
|
|
||||||
elseif GetResourceState(QBInv):find("start") then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Retrieving ^3Stash^2 with ^7qb-inventory") end
|
|
||||||
local result = MySQL.scalar.await('SELECT items FROM stashitems WHERE stash = ?', { stashName })
|
|
||||||
if result then stashItems = json.decode(result) end
|
|
||||||
end
|
|
||||||
if stashItems then
|
|
||||||
for _, item in pairs(stashItems) do
|
|
||||||
local itemInfo = Items[item.name:lower()]
|
|
||||||
if itemInfo then
|
|
||||||
items[item.slot] = {
|
|
||||||
name = itemInfo["name"],
|
|
||||||
amount = tonumber(item.amount) or tonumber(item.count),
|
|
||||||
info = item.info ~= nil and item.info or "",
|
|
||||||
label = itemInfo["label"],
|
|
||||||
description = itemInfo["description"] ~= nil and itemInfo["description"] or "",
|
|
||||||
weight = itemInfo["weight"],
|
|
||||||
type = itemInfo["type"],
|
|
||||||
unique = itemInfo["unique"],
|
|
||||||
useable = itemInfo["useable"],
|
|
||||||
image = itemInfo["image"],
|
|
||||||
slot = item.slot,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3GetStashItems^7: ^2Stash information for ^7'^6"..stashName.."^7' ^2retrieved^7") end
|
|
||||||
end
|
|
||||||
return items
|
|
||||||
end
|
|
||||||
|
|
||||||
function stashRemoveItem(stashItems, stashName, items) local amount = amount and amount or 1
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
for k, v in pairs(items) do
|
|
||||||
exports[OXInv]:RemoveItem(stashName, k, v)
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..OXInv, k, v) end
|
|
||||||
end
|
|
||||||
elseif GetResourceState(QSInv):find("start") then
|
|
||||||
for k, v in pairs(items) do
|
|
||||||
exports[QSInv]:RemoveItemIntoStash(stashName, k, v)
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QSInv, k, v) end
|
|
||||||
end
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
for k, v in pairs(items) do
|
|
||||||
for l in pairs(stashItems) do
|
|
||||||
if stashItems[l].name == k then
|
|
||||||
if (stashItems[l].amount - v) <= 0 then
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
|
|
||||||
end
|
|
||||||
stashItems[l] = nil
|
|
||||||
else
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..CodeMInv, k, v)
|
|
||||||
end
|
|
||||||
stashItems[l].amount -= v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for k, v in pairs(items) do
|
|
||||||
for l in pairs(stashItems) do
|
|
||||||
if stashItems[l].name == k then
|
|
||||||
if (stashItems[l].amount - v) <= 0 then
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^2None of this item left in stash ^3Stash^7", k, v)
|
|
||||||
end
|
|
||||||
stashItems[l] = nil
|
|
||||||
else
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^2Removing item from ^3Stash^2 with ^7"..QBInv, k, v)
|
|
||||||
end
|
|
||||||
stashItems[l].amount -= v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3saveStash^7: ^2Saving ^3QB^2 stash ^7'^6"..stashName.."^7'")
|
|
||||||
end
|
|
||||||
MySQL.Async.insert('INSERT INTO stashitems (stash, items) VALUES (:stash, :items) ON DUPLICATE KEY UPDATE items = :items', { ['stash'] = stashName, ['items'] = json.encode(stashItems) })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
RegisterNetEvent(GetCurrentResourceName()..":server:stashRemoveItem", stashRemoveItem)
|
|
||||||
|
|
||||||
function stashhasItem(stashItems, item, amount) local amount, count = amount and amount or 1, 0
|
|
||||||
for k, itemData in pairs(stashItems) do
|
|
||||||
if itemData and (itemData.name == item) then
|
|
||||||
--if Config.System.Debug then
|
|
||||||
-- print("^6Bridge^7: ^3stashHasItem^7: ^2Item^7: '^3"..tostring(item).."^7' ^2Slot^7: ^3"..itemData.slot.." ^7x(^3"..tostring(itemData.amount).."^7)")
|
|
||||||
--end
|
|
||||||
count += (itemData.amount or 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count >= amount then
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3stashHasItem^7: ^2Items ^3"..item.." ^5FOUND^7 x^3"..count.."^7/^3"..amount.."^7") end
|
|
||||||
return
|
|
||||||
true
|
|
||||||
else
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3stashHasItem^7: ^2Items ^1NOT FOUND^7 "..json.encode(item)) end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
## Animal Ped Support
|
||||||
|
### isAnimal.lua
|
||||||
|
This module provides logic to detect if a Ped or model is an animal and classify it into specific categories (cat, dog, coyote, etc.). Useful for wildlife, animal roles, or pet systems.
|
||||||
|
|
||||||
|
❔At player load in, it attempts to get what kind of ped/animal you are but there are functions to double check in scripts
|
||||||
|
|
||||||
|
❔Alot of this is used to determine what models/animations are available
|
||||||
|
|
||||||
|
Global flags:
|
||||||
|
- `isCat`, `isDog`, `isBigDog`, `isSmallDog`, `isCoyote`, `isAnimal` — used to store classification of the player's current ped.
|
||||||
|
|
||||||
|
- **isPedAnimal(ped?)**
|
||||||
|
|
||||||
|
- Checks if a ped is an animal based on predefined animal models.
|
||||||
|
- Sets global `isAnimal` to true if matched.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local isPlayerAnimal = isPedAnimal()
|
||||||
|
local isOtherPedAnimal = isPedAnimal(GetPedInVehicleSeat(vehicle, -1))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **isCat(ped)**
|
||||||
|
|
||||||
|
- Returns true if the ped model matches a cat.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if isCat() then print("You're a cat!") end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **isDog(ped)**
|
||||||
|
|
||||||
|
- Returns two values:
|
||||||
|
- `true`, `true` — if ped is a big dog
|
||||||
|
- `true`, `false` — if ped is a small dog
|
||||||
|
- `false`, `nil` — if not a dog
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local isDog, isBig = isDog()
|
||||||
|
if isDog then print(isBig and "Big Dog" or "Small Dog") end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getAnimalModels()**
|
||||||
|
|
||||||
|
- Returns a flat list of all registered animal model hashes.
|
||||||
|
- Can be used to check ped models against
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
for _, model in pairs(getAnimalModels()) do print(model) end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getAnimalAnims(ped)**
|
||||||
|
|
||||||
|
- Returns the animation set defined for the given animal model.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local anims = getAnimalAnims(PlayerPedId())
|
||||||
|
if anims then playAnim(anims.sitDict, anims.sitAnim) end
|
||||||
|
```
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
### helpers.lua
|
||||||
|
This utility module provides functions for resource checks, debugging, formatting, coordinate math, vector calculations, progress bars, and drawing tools.
|
||||||
|
|
||||||
|
- **isStarted(script)**
|
||||||
|
- Returns `true` if a resource is started.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if isStarted("myResource") then print("Resource is running") end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getScript()**
|
||||||
|
- Caches and returns the name of the current resource.
|
||||||
|
- Easier than typing `GetCurrentResourceName()` over and over
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
print("Current script:", getScript())
|
||||||
|
```
|
||||||
|
|
||||||
|
- **isServer()**
|
||||||
|
- Returns true if running on the server.
|
||||||
|
- This was mainly made becuase `IsDuplicityVersion()` kept confusing me
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if isServer() then print("Server-side!") end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **debugPrint(...)** / **eventPrint(...)**
|
||||||
|
- Prints messages with debug context if debugMode is enabled.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
debugPrint("Loaded object:", objName)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **jsonPrint(table)**
|
||||||
|
- Pretty-prints a table with colorized JSON if debugMode is enabled.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
jsonPrint(myData)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **keyGen()**
|
||||||
|
- Generates a 3-character unique ID.
|
||||||
|
- Good grabbing randomly generated strings
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
print("Generated Key:", keyGen())
|
||||||
|
```
|
||||||
|
|
||||||
|
- **cv(amount)**
|
||||||
|
- Comma-separates a number (e.g., `1000000` to `1,000,000`).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
print(cv(1000000)) -- "1,000,000"
|
||||||
|
```
|
||||||
|
|
||||||
|
- **formatCoord(vec)**
|
||||||
|
- Outputs a formatted string from vector types.
|
||||||
|
- Compacts and adds console colours to the vector to be printed
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
print(formatCoord(vector3(123.45, 678.9, 10.0)))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getCenterOfZones(coords)**
|
||||||
|
- Returns average center position of a vector3 list.
|
||||||
|
- A few use cases, but I used it to see how well spaced blips were together
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local center = getCenterOfZones({vector3(0,0,0), vector3(10,10,0)})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **countTable(tbl)**
|
||||||
|
- Returns the number of entries in a table.
|
||||||
|
- Simple function to print how many "entires" are in a table
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
print(countTable({a=1,b=2,c=3})) -- 3
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pairsByKeys(tbl)**
|
||||||
|
- Iterator for sorted keys.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
for k, v in pairsByKeys(myTable) do print(k, v) end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **concatenateText(tbl)**
|
||||||
|
- Joins string table entries with newlines.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
print(concatenateText({"Line 1", "Line 2"}))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **RotationToDirection(rot)**
|
||||||
|
- Converts a heading vector to a directional vector.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local dir = RotationToDirection(rotation)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **basicBar(percent)**
|
||||||
|
- Returns a bar like `████░░░░░` at 50%.
|
||||||
|
- Basically a progress bar but as a string, I use this in drawTexts when progressbars aren't able to be used
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
print(basicBar(50))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **normalizeVector(vec)**
|
||||||
|
- Returns a normalized version of a vector3.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local norm = normalizeVector(vector3(3,4,0))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **drawLine(start, end, color)** / **drawSphere(pos, color)**
|
||||||
|
- Debug drawing helpers.
|
||||||
|
- Stays visible for more than one frame
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
drawLine(vector3(0,0,0), vector3(10,10,10), vector4(255,0,0,255))
|
||||||
|
drawSphere(vector3(5,5,5), vector4(0,255,0,255))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **PerformRaycast(start, end, entity?, flags?)**
|
||||||
|
- Raycast with material detection. Returns ray hit data.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local hit, hitPos, material = PerformRaycast(startVec, endVec, playerPed, 1)
|
||||||
|
if hit == 1 then
|
||||||
|
print("Hit at position:", hitPos)
|
||||||
|
print("Material:", material)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **adjustForGround(coords)**
|
||||||
|
- Adjusts a z-coordinate to ground height.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
coords = adjustForGround(vector3(100, 200, 300))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **ensureNetToVeh(id)** / **ensureNetToEnt(id)**
|
||||||
|
- Resolves net ID to entity safely.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local veh = ensureNetToVeh(netId)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **sendLog(text)** / **sendServerLog(data)**
|
||||||
|
- Logging helpers, includes player name, coords, script source.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
sendLog("Suspicious activity detected")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **GetGroundMaterialAtPosition(coords)**
|
||||||
|
- Returns the material hash + readable name from the surface below coords.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local hash, name = GetGroundMaterialAtPosition(vector3(0,0,0))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **GetPropDimensions(model)**
|
||||||
|
- Loads a model and returns width, depth, height.
|
||||||
|
- I use this to parse a model to create a box target instead of entity target when creating distProps/distPeds
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local w,d,h = GetPropDimensions("prop_barrel_01a")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **GetEntityForwardVector(entity)**
|
||||||
|
- Returns the forward direction vector based on entity heading.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local fwd = GetEntityForwardVector(PlayerPedId())
|
||||||
|
```
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
### loaders.lua
|
||||||
|
This module provides loading utilities for common asset types such as models, animations, texture dictionaries, and audio banks. It also provides animation and sound helpers.
|
||||||
|
|
||||||
|
- **loadModel(model)**
|
||||||
|
- Loads a model into memory if valid and not already loaded.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
loadModel('prop_chair_01a')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **unloadModel(model)**
|
||||||
|
- Unloads a model from memory.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
unloadModel('prop_chair_01a')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **loadAnimDict(animDict)**
|
||||||
|
- Loads an animation dictionary into memory.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
loadAnimDict('amb@world_human_hang_out_street@male_c@base')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **unloadAnimDict(animDict)**
|
||||||
|
- Removes an animation dictionary from memory.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
unloadAnimDict('amb@world_human_hang_out_street@male_c@base')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **loadPtfxDict(ptFxName)**
|
||||||
|
- Loads a particle effect (ptfx) dictionary.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
loadPtfxDict('core')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **unloadPtfxDict(dict)**
|
||||||
|
- Unloads a particle effect dictionary from memory.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
unloadPtfxDict('core')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **loadTextureDict(dict)**
|
||||||
|
- Loads a streamed texture dictionary.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
loadTextureDict('commonmenu')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **loadScriptBank(bank)**
|
||||||
|
- Loads a script audio bank.
|
||||||
|
- Returns true on success.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **loadAmbientBank(bank)**
|
||||||
|
- Loads an ambient audio bank.
|
||||||
|
- Returns true on success.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local success = loadAmbientBank('AMB_REVERB_GENERIC')
|
||||||
|
```
|
||||||
|
|
||||||
|
- **playAnim(animDict, animName, duration?, flag?, ped?, speed?)**
|
||||||
|
- Plays an animation on a ped.
|
||||||
|
- Loads the dictionary if not already loaded.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **stopAnim(animDict, animName, ped?)**
|
||||||
|
- Stops an animation and unloads the dictionary.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
|
||||||
|
```
|
||||||
|
|
||||||
|
- **playGameSound(audioBank, soundSet, soundRef, coords, synced, range?)**
|
||||||
|
- Plays a game sound from a coordinate or entity.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
|
||||||
|
```
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
### callback.lua
|
||||||
|
|
||||||
|
These functions wrap the native callback handling of the selected framework (e.g., OX, QBCore, ESX) instead of implementing a standalone callback system, ensuring full compatibility.
|
||||||
|
|
||||||
|
- **createCallback(callbackName, funct)**
|
||||||
|
|
||||||
|
- Registers a callback function with the appropriate framework.
|
||||||
|
- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
|
||||||
|
- It adapts the callback function to match the expected signature for the framework.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local table = { ["info"] = "HI" }
|
||||||
|
createCallback('myCallback', function(source, ...)
|
||||||
|
return table
|
||||||
|
end)
|
||||||
|
|
||||||
|
createCallback("callback:checkVehicleOwned", function(source, plate)
|
||||||
|
local result = isVehicleOwned(plate)
|
||||||
|
if result then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **triggerCallback(callbackName, ...)**
|
||||||
|
|
||||||
|
- Triggers a server callback and returns the result.
|
||||||
|
- This function uses the appropriate framework's method to call the server-side callback and awaits the result.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local result = triggerCallback('myCallback')
|
||||||
|
jsonPrint(result)
|
||||||
|
|
||||||
|
local result = triggerCallback("callback:checkVehicleOwned", plate)
|
||||||
|
print(result)
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
### cameras.lua
|
||||||
|
This module provides utilities for managing temporary in-game cameras, useful for cutscenes, cinematic views, or scripted perspectives.
|
||||||
|
|
||||||
|
- **createTempCam(ent, coords)**
|
||||||
|
|
||||||
|
- Creates a temporary camera at the specified coordinates or relative to an entity.
|
||||||
|
- If `ent` is an entity, the camera position is calculated as an offset from the entity's position using `GetOffsetFromEntityInWorldCoords`.
|
||||||
|
- If `ent` is a `vector3`, it is used directly as the camera's position.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local cam = createTempCam(PlayerPedId(), GetEntityCoords(PlayerPedId()) + vector3(0, 2.0, 1.0))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **startTempCam(cam)**
|
||||||
|
|
||||||
|
- Activates and renders the temporary camera.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
startTempCam(cam)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **stopTempCam()**
|
||||||
|
|
||||||
|
- Deactivates and deletes all currently running custom camera, restoring normal view.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
stopTempCam()
|
||||||
|
```
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
### contextmenus.lua
|
||||||
|
|
||||||
|
These functions provide a unified way to interact with different context menu systems, such as OX and WarMenu, depending on what's available on the server.
|
||||||
|
|
||||||
|
- **openMenu(Menu, data)**
|
||||||
|
|
||||||
|
- Opens a context menu using the preferred menu system.
|
||||||
|
- Automatically selects between supported systems like OX or WarMenu based on availability.
|
||||||
|
- The `Menu` parameter should be a list of menu entries, and the `data` parameter can be used to set headers, subtexts, and actions like `onBack`, `onExit`, and `canClose`.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
openMenu({
|
||||||
|
{ header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
|
||||||
|
{ header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
|
||||||
|
}, {
|
||||||
|
header = "Main Menu",
|
||||||
|
headertxt = "Select an option",
|
||||||
|
onBack = function() print("Return selected") end,
|
||||||
|
onExit = function() print("Menu closed") end,
|
||||||
|
canClose = true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **isOx()**
|
||||||
|
|
||||||
|
- Checks whether the OX context menu system is available on the server.
|
||||||
|
- Allows to do specific things if ox_lib menu is in use
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if isOx() then
|
||||||
|
print("OX Context Menu is available")
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **isWarMenuOpen()**
|
||||||
|
|
||||||
|
- Returns whether WarMenu is currently open.
|
||||||
|
- Useful to prevent opening a new menu if one is already active.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if not isWarMenuOpen() then
|
||||||
|
openMenu("main_menu", menuData)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
- Returns whether the WarMenu is currently open.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
### crafting.lua
|
||||||
|
|
||||||
|
These functions support crafting logic, such as opening crafting menus, handling multi-craft operations, and creating item data from recipes.
|
||||||
|
|
||||||
|
- **craftingMenu(data)**
|
||||||
|
|
||||||
|
- Opens a menu for selecting the quantity to craft.
|
||||||
|
- Presents the player with multiple crafting quantities based on `Config.Crafting.MultiCraftAmounts`.
|
||||||
|
- **Parameters:**
|
||||||
|
- `item` (`string`): The item to craft.
|
||||||
|
- `craft` (`table`): The crafting recipe.
|
||||||
|
- `craftable` (`table`): Crafting options.
|
||||||
|
- `coords` (`vector3`): Where crafting occurs.
|
||||||
|
- `stashName` (`string`): The stash name(s) for item availability.
|
||||||
|
- `onBack` (`function`): Callback when returning.
|
||||||
|
- `metadata` (`table`, optional): Metadata for the crafted item.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
craftingMenu({
|
||||||
|
craftable = {
|
||||||
|
Header = "Weapon Crafting",
|
||||||
|
Recipes = {
|
||||||
|
[1] = {
|
||||||
|
["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 },
|
||||||
|
amount = 1,
|
||||||
|
},
|
||||||
|
-- More recipes...
|
||||||
|
},
|
||||||
|
Anims = {
|
||||||
|
animDict = "amb@prop_human_parking_meter@male@idle_a",
|
||||||
|
anim = "idle_a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
coords = vector3(100.0, 200.0, 300.0),
|
||||||
|
stashTable = "crafting_stash",
|
||||||
|
job = "mechanic",
|
||||||
|
onBack = function() print("Returning to previous menu") end,
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
### drawText.lua
|
||||||
|
|
||||||
|
These functions handle drawing and hiding styled on-screen prompts or UI overlays, compatible with different styling frameworks such as OX Lib.
|
||||||
|
|
||||||
|
- **drawText(image, input, style, oxStyleTable)**
|
||||||
|
|
||||||
|
- Displays styled text or prompts on screen.
|
||||||
|
- Designed to adapt styling depending on which UI system (e.g., OX) is in use.
|
||||||
|
- **Parameters:**
|
||||||
|
- `image` (`string`): Optional icon or image path.
|
||||||
|
- `input` (`string`): The main text to display.
|
||||||
|
- `style` (`string|table`): Preset or custom styling.
|
||||||
|
- `oxStyleTable` (`table`, optional): Extended style options if using OX UI.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **hideText()**
|
||||||
|
|
||||||
|
- Hides any active text or UI element previously drawn with `drawText()`.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
hideText()
|
||||||
|
```
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
### duifunctions.lua
|
||||||
|
|
||||||
|
These functions support dynamic UI image rendering in 3D environments using runtime texture dictionaries. Ideal for integrating `nui://` or external `http://` image URLs into MLOs or world props.
|
||||||
|
|
||||||
|
- **createDui(name, http, size, txd))**
|
||||||
|
|
||||||
|
- Sets up a runtime texture dictionary and links an image from a `nui://` or `http://` URL.
|
||||||
|
- This function should be used once to create the texture dictionary needed for rendering Dui images.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createDui("logo", "https://example.com/logo.png", { x = 512, y = 256 }, scriptTxd)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **DuiSelect(data)**
|
||||||
|
|
||||||
|
- Updates the URL on an existing texture dictionary to change the rendered image.
|
||||||
|
- Can be used at runtime to swap out visuals in MLOs or props.
|
||||||
|
- **Parameters:**
|
||||||
|
- `textureDict` (`string`): The texture dictionary to target.
|
||||||
|
- `texture` (`string`): The specific texture name to override.
|
||||||
|
- `url` (`string`): The new image URL.
|
||||||
|
- `width`, `height` (`number`): The texture resolution.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
DuiSelect({
|
||||||
|
name = "logo",
|
||||||
|
texn = "logoTex",
|
||||||
|
texd = "someTxd",
|
||||||
|
size = { x = 512, y = 256 }
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
### input.lua
|
||||||
|
|
||||||
|
This function displays a customizable input dialog for user text or number entry. It supports both simple and complex input structures.
|
||||||
|
|
||||||
|
- **createInput(title, opts)**
|
||||||
|
- Opens a styled input dialog with configurable fields, labels, input types, and validation.
|
||||||
|
- Supports multiple field types including `text`, `number`, `password`, `checkbox`, `color`, `slider`, and more.
|
||||||
|
- **Parameters:**
|
||||||
|
- `title` (`string`): Title displayed at the top of the input box.
|
||||||
|
- `opts` (`table`): A table of fields with attributes like `label`, `name`, `type`, `value`, and more.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local userInput = createInput("Enter Details", {
|
||||||
|
{ type = "text", text = "Name", name = "playerName", isRequired = true },
|
||||||
|
{ type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
|
||||||
|
{ type = "radio", label = "Gender", name = "playerGender", options = {
|
||||||
|
{ text = "Male", value = "male" },
|
||||||
|
{ text = "Female", value = "female" },
|
||||||
|
{ text = "Other", value = "other" },
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
if userInput then
|
||||||
|
print(json.encode(userInput))
|
||||||
|
end
|
||||||
|
```
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
### inventories.lua
|
||||||
|
|
||||||
|
These functions manage inventory locking, item checking, and player inventory retrieval.
|
||||||
|
|
||||||
|
- **lockInv(toggle)**
|
||||||
|
|
||||||
|
- Locks or unlocks the player's inventory.
|
||||||
|
- Freezes/unfreezes movement and toggles hotbar state.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
lockInv(true) -- Lock inventory
|
||||||
|
lockInv(false) -- Unlock inventory
|
||||||
|
```
|
||||||
|
|
||||||
|
- **hasItem(items, amount, src)**
|
||||||
|
|
||||||
|
- Checks if a player has the specified item(s) in sufficient quantity.
|
||||||
|
- **Returns:**
|
||||||
|
- `boolean`: Whether the player has the item(s).
|
||||||
|
- `table`: Details of available item counts.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local hasAll, details = hasItem({"health_potion", "mana_potion"}, 2, playerId)
|
||||||
|
if hasAll then
|
||||||
|
-- Proceed with action
|
||||||
|
else
|
||||||
|
-- Inform the player about missing items
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getPlayerInv(src)**
|
||||||
|
|
||||||
|
- Retrieves the player’s current inventory.
|
||||||
|
- May be used for inventory UI, logging, or crafting systems.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local inventory = getPlayerInv(playerId)
|
||||||
|
for k, item in pairs(inventory) do
|
||||||
|
print(item.name, item.amount)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### itemcontrol.lua
|
||||||
|
|
||||||
|
This module includes a variety of utility functions for managing items in player inventories, including giving, removing, durability, and use logic.
|
||||||
|
|
||||||
|
- **createUseableItem(item, funct)**
|
||||||
|
|
||||||
|
⚠️ This doesn't work for `ox_inv`, you will need to add the event info to it's `items.lua`
|
||||||
|
- Registers a useable item and binds it to a callback function.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createUseableItem("bandage", function(source)
|
||||||
|
-- Heal logic here
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **invImg(item)**
|
||||||
|
|
||||||
|
- Returns the inventory image path for an item.
|
||||||
|
- Automatically grabs the inventory image link based on inventory script
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local imagePath = invImg("water_bottle")
|
||||||
|
print(imagePath)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **addItem(item, amount, info, src)**
|
||||||
|
|
||||||
|
⚠️ Requires Auth callback if called from client side
|
||||||
|
- Adds a specific amount of an item to a player's inventory.
|
||||||
|
- Automatically attempts to add items using detected inventory script
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
-- Client Side
|
||||||
|
addItem("lockpick", 3, {})
|
||||||
|
|
||||||
|
-- Server Side
|
||||||
|
addItem("lockpick", 3, {}, source)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **removeItem(item, amount, src, slot)**
|
||||||
|
|
||||||
|
- Removes a specific amount of an item from a player’s inventory.
|
||||||
|
- Automatically attempts to remove item using detected inventory script
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
-- Client Side
|
||||||
|
removeItem(
|
||||||
|
"ammo_pistol",
|
||||||
|
30,
|
||||||
|
nil,
|
||||||
|
slot --[[optional]]--
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Server Side
|
||||||
|
removeItem(
|
||||||
|
"ammo_pistol",
|
||||||
|
30,
|
||||||
|
source,
|
||||||
|
slot --[[optional]]--
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **dupeWarn(src, item, amount)**
|
||||||
|
|
||||||
|
- Logs or handles a potential duplication exploit involving an item.
|
||||||
|
- This is called automatically if a player has been triggered exploit detection
|
||||||
|
- Disabled if debugMode is enabled
|
||||||
|
|
||||||
|
- **breakTool(data)**
|
||||||
|
|
||||||
|
- Breaks a tool or item, often due to exceeding durability.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
breakTool({ item = "drill", damage = 10 })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getDurability(item)**
|
||||||
|
|
||||||
|
- Returns the current durability value of a given item.
|
||||||
|
- Searched for the lowest slot number (eg. slot 1) and retreives that items durability
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local durability, slot = getDurability("drill")
|
||||||
|
if durability then
|
||||||
|
print("Durability:", durability)
|
||||||
|
print("Slot:", slot)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **canCarry(itemTable, src)**
|
||||||
|
|
||||||
|
⚠️ Currently server side only
|
||||||
|
- Checks if a player can carry the item(s) specified in `itemTable`.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local carryCheck = canCarry({ ["health_potion"] = 2, ["mana_potion"] = 3 }, playerId)
|
||||||
|
if carryCheck["health_potion"] and carryCheck["mana_potion"] then
|
||||||
|
-- Player can carry items.
|
||||||
|
else
|
||||||
|
-- Notify player.
|
||||||
|
end
|
||||||
|
```
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
### jobfunctions.lua
|
||||||
|
|
||||||
|
This module provides functions for managing job-based logic, such as checking player roles, toggling duty status, and simulating job-related interactions.
|
||||||
|
|
||||||
|
- **makeBossRoles(role)**
|
||||||
|
|
||||||
|
- Sets up boss-level permissions or access for the specified job role.
|
||||||
|
- Often used to determine if a player can access job menus or perform administrative actions.
|
||||||
|
- Used mainly for creating boss locked target tables
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
makeBossRoles("police")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **jobCheck(job)**
|
||||||
|
|
||||||
|
- Simple check if the player has the specified job.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if jobCheck("mechanic") then
|
||||||
|
-- Allow mechanic features.
|
||||||
|
else
|
||||||
|
-- Deny access.
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **toggleDuty()**
|
||||||
|
|
||||||
|
- Toggles the player’s on/off duty status, typically used for jobs like police, EMS, etc.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
toggleDuty()
|
||||||
|
```
|
||||||
|
|
||||||
|
- **washHands(data)**
|
||||||
|
|
||||||
|
- Simulates the action of washing hands, used in job scripts or medical job logic.
|
||||||
|
- Currently only animation and a notfication
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
washHands({ coords = vector3(200.0, 300.0, 40.0) })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **useToilet(data)**
|
||||||
|
|
||||||
|
- Triggers toilet-use interaction, likely includes animation or sound.
|
||||||
|
- Currently only animation and a notfication
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
useToilet({ urinal = true })
|
||||||
|
-- Player uses a urinal with corresponding animations and notifications
|
||||||
|
|
||||||
|
useToilet({ urinal = false, sitcoords = vector4(215.76, -810.12, 29.73, 90.0) })
|
||||||
|
-- Player sits down to use a toilet with corresponding animations and notifications
|
||||||
|
```
|
||||||
|
|
||||||
|
- **useDoor(data)**
|
||||||
|
|
||||||
|
- Handles interactions with doors that aren't openable and teleports the player
|
||||||
|
- eg. Recycle Center, it's used to teleport the player from outside a building to the IPL
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
useDoor({ telecoords = vector4(215.76, -810.12, 29.73, 90.0) })
|
||||||
|
```
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
### makeBlip.lua
|
||||||
|
|
||||||
|
This module provides simple utilities for adding static or entity-based blips to the minimap.
|
||||||
|
|
||||||
|
❔ This has basic support for RedM too
|
||||||
|
- **makeBlip(data)**
|
||||||
|
|
||||||
|
- This function adds a map blip at the provided coordinates and sets various display properties such as sprite, color, scale, and more.
|
||||||
|
- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local blipData = {
|
||||||
|
coords = vector3(123.4, 567.8, 90.1),
|
||||||
|
sprite = 1,
|
||||||
|
col = 2,
|
||||||
|
scale = 0.8,
|
||||||
|
disp = 4,
|
||||||
|
category = 7,
|
||||||
|
name = "My Blip",
|
||||||
|
preview = "http://example.com/preview.png"
|
||||||
|
}
|
||||||
|
local blip = makeBlip(blipData)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **makeEntityBlip(data)**
|
||||||
|
|
||||||
|
- This function adds a map blip attached to the provided entity and sets various display properties such as sprite, color, scale, and more.
|
||||||
|
- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local blipData = {
|
||||||
|
entity = myEntity,
|
||||||
|
sprite = 1,
|
||||||
|
col = 2,
|
||||||
|
scale = 0.8,
|
||||||
|
disp = 4,
|
||||||
|
category = 7,
|
||||||
|
name = "Entity Blip",
|
||||||
|
preview = "http://example.com/preview.png"
|
||||||
|
}
|
||||||
|
local blip = makeEntityBlip(blipData)
|
||||||
|
```
|
||||||
|
|
||||||
|
### makePed.lua
|
||||||
|
This module provides tools to create persistent or distance-based NPCs with optional animations, scenarios, and config randomization.
|
||||||
|
|
||||||
|
- **makeDistPed(data, coords, freeze, collision, scenario, anim, synced)**
|
||||||
|
|
||||||
|
- Creates a ped that only spawns when nearby (performance optimization).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
makeDistPed({model = "a_m_y_business_03" }, -- model data
|
||||||
|
vector4(450.0, -980.0, 30.0, 100.0), -- coords
|
||||||
|
true, -- freeze entity
|
||||||
|
false, -- collision
|
||||||
|
'WORLD_HUMAN_STAND_IMPATIENT', -- Scenario Animation
|
||||||
|
{ dict = "amb@world_human_clipboard@male@idle_a", anim = "idle_c" }, -- Anim Table
|
||||||
|
true -- Network Synced
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **makePed(data, coords, freeze, collision, scenario, anim, synced)**
|
||||||
|
|
||||||
|
- Spawns a ped with more persistent behavior at the given location.
|
||||||
|
- Supports animation playback and freezing.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
makePed({model = "a_m_y_business_03" }, -- model data
|
||||||
|
vector4(450.0, -980.0, 30.0, 100.0), -- coords
|
||||||
|
true, -- freeze entity
|
||||||
|
false, -- collision
|
||||||
|
'WORLD_HUMAN_STAND_IMPATIENT', -- Scenario Animation
|
||||||
|
{ dict = "amb@world_human_clipboard@male@idle_a", anim = "idle_c" }, -- Anim Table
|
||||||
|
true -- Network Synced
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **GenerateRandomPedData(data)**
|
||||||
|
|
||||||
|
- Returns randomized ped model/configuration based on input table.
|
||||||
|
- Useful for dynamic NPC generation.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} })
|
||||||
|
```
|
||||||
|
|
||||||
|
### makeProp.lua
|
||||||
|
This module allows you to spawn static or distance-loaded props in the world.
|
||||||
|
|
||||||
|
- **makeProp(data, freeze, synced)**
|
||||||
|
|
||||||
|
- This function loads the model, creates the object, sets its heading, and freezes it if specified.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local propData = {
|
||||||
|
prop = 'prop_chair_01a',
|
||||||
|
coords = vector4(123.4, 567.8, 90.1, 180.0)
|
||||||
|
}
|
||||||
|
local prop = makeProp(propData, true, false)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **makeDistProp(data, freeze, synced, range)**
|
||||||
|
- Same as `makeProp`, but only spawns if the player is within the specified range.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local propData = {
|
||||||
|
prop = 'prop_chair_01a',
|
||||||
|
coords = vector4(123.4, 567.8, 90.1, 180.0)
|
||||||
|
}
|
||||||
|
makeDistProp(propData, true, false)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **destroyProp(entity)**
|
||||||
|
- Removes a previously created or targeted prop from the world.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
destroyProp(barrel)
|
||||||
|
```
|
||||||
|
|
||||||
|
### makeVeh.lua
|
||||||
|
This module provides functionality for spawning vehicles, including distance-based optimization and entity management.
|
||||||
|
|
||||||
|
- **makeVeh(model, coords)**
|
||||||
|
|
||||||
|
- This function loads the vehicle model, creates the vehicle in the world at the given coordinates, sets initial properties, and returns the vehicle handle.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local vehicle = makeVeh("adder", vector3(250.0, -1000.0, 30.0))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **makeDistVehicle(data, radius, onEnter, onExit)**
|
||||||
|
|
||||||
|
- Creates a vehicle that spawns when the player enters a designated polyzone area.
|
||||||
|
- This is used for `jim-parking` to create a static vehicle that can't move
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
makeDistVehicle({
|
||||||
|
model = "blista",
|
||||||
|
coords = vector3(400.0, -800.0, 30.0),
|
||||||
|
heading = 180.0
|
||||||
|
}, 50.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **removeDistVehicleZone(zoneId)**
|
||||||
|
- Removes a previously created distance-based vehicle zone.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
removeDistVehicleZone("garage_zone_1")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **deleteVehicle(vehicle)**
|
||||||
|
- Deletes a specified vehicle entity.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
deleteVehicle(vehicle)
|
||||||
|
```
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
### metaHandlers.lua
|
||||||
|
|
||||||
|
This module provides access and control over player metadata, which is useful for storing temporary or persistent player-specific values like stats, states, or tags.
|
||||||
|
|
||||||
|
- **GetPlayer(source)**
|
||||||
|
|
||||||
|
⚠️ Server side only
|
||||||
|
- Retrieves the player object (as defined by the framework in use) from the given `source` ID.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local player = GetPlayer(playerId)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **GetMetadata(player, key)**
|
||||||
|
|
||||||
|
- Retrieves the value of a metadata field from the given player.
|
||||||
|
- If called client-side (player is nil), it triggers a server callback to retrieve metadata.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local stress = GetMetadata(player, "stress")
|
||||||
|
print("Player stress level:", stress)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **setPlayerMetadata(player, key, value)**
|
||||||
|
|
||||||
|
⚠️ Server side only
|
||||||
|
- Updates or assigns a value to a specific metadata key for a player.
|
||||||
|
- The function updates the player's metadata using the active core export.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
setPlayerMetadata(player, "stress", 0)
|
||||||
|
```
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
### notify.lua
|
||||||
|
|
||||||
|
This module provides a unified interface to trigger styled notifications across supported frameworks.
|
||||||
|
|
||||||
|
- **triggerNotify(title, message, type, src)**
|
||||||
|
|
||||||
|
- Sends a notification to a player (or to the entire client if `src` is `nil`).
|
||||||
|
- The notification style is determined by `type` (e.g., `success`, `error`, `info`).
|
||||||
|
- **Parameters:**
|
||||||
|
- `title` (`string`): The title or header of the notification.
|
||||||
|
- `message` (`string`): The body or detail text.
|
||||||
|
- `type` (`string`): Type of message (e.g., "success", "error", "info").
|
||||||
|
- `src` (`number`, optional): Server ID of the player to notify (omit to notify locally).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
-- Client-side usage without specifying a player (shows to the current player)
|
||||||
|
triggerNotify("Success", "You have completed the task!", "success")
|
||||||
|
|
||||||
|
-- Server-side usage specifying a player by their server ID
|
||||||
|
triggerNotify("Alert", "You have been warned for misconduct.", "error", source)
|
||||||
|
```
|
||||||
@@ -0,0 +1,372 @@
|
|||||||
|
### pedTasks.lua
|
||||||
|
|
||||||
|
This module wraps the GTA V `Task*` natives so scripted peds can be given orders without having to remember each native's argument list.
|
||||||
|
|
||||||
|
Every task is registered against its ped, so it can be queried and cancelled later. Finite tasks accept an `onComplete` callback and an optional `timeout`, and every function returns `false, "reason"` when it cannot run.
|
||||||
|
|
||||||
|
Coordinates can be passed as a `vector3`, a `vector4`, a plain `{x=,y=,z=}` table, or an entity handle, and are normalised automatically.
|
||||||
|
|
||||||
|
> ⚠️ **The ped must not be frozen.** GTA ignores movement and animation tasks on a frozen
|
||||||
|
> ped, so create peds with `makePed(..., freeze = false, ...)` or call
|
||||||
|
> `FreezeEntityPosition(ped, false)` before giving them tasks.
|
||||||
|
|
||||||
|
> ⚠️ **Peds need a weapon and `canShoot`** before `pedTaskShootAt` will make them fire.
|
||||||
|
> Use `GiveWeaponToPed(ped, `WEAPON_PISTOL`, 50, false, true)` and
|
||||||
|
> `pedCombatAttributes(ped, { canShoot = true, canAim = true })`.
|
||||||
|
> GTA also refuses to let a ped fire unless it is already aiming, so pass `aim = true`
|
||||||
|
> or call `pedTaskAimAt` first.
|
||||||
|
|
||||||
|
#### Movement
|
||||||
|
|
||||||
|
- **pedTaskGoTo(ped, coords, opts)**
|
||||||
|
|
||||||
|
- Sends a ped to walk or run to a set of coordinates. The task completes once the ped is within `opts.radius` of the destination.
|
||||||
|
- **Options:** `run`, `speed`, `radius`, `heading`, `slide`, `nativeTimeout`, `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskGoTo(ped, vec3(455.0, -975.0, 30.0), {
|
||||||
|
run = true,
|
||||||
|
onComplete = function(p) print("arrived") end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskGoToEntity(ped, target, opts)**
|
||||||
|
|
||||||
|
- Sends a ped towards an entity for a set time or until it is within range.
|
||||||
|
- **Options:** `duration`, `distance`, `speed`, `run`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskGoToEntity(ped, vehicle, { run = true })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskFollow(ped, target, opts)**
|
||||||
|
|
||||||
|
- Makes a ped follow another entity at a set offset. Completes when the ped catches up.
|
||||||
|
- **Options:** `offset`, `run`, `speed`, `radius`, `nativeTimeout`, `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskFollow(ped, targetPed, { offset = vec3(2.0, 0.0, 0.0), run = true })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskWander(ped, coords, opts)**
|
||||||
|
|
||||||
|
- Sends a ped wandering randomly inside a radius. Continuous task, no completion callback.
|
||||||
|
- **Options:** `radius`, `length`, `betweenWalks`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskWander(ped, vec3(455.0, -975.0, 30.0), { radius = 10.0 })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskFlee(ped, threat, opts)**
|
||||||
|
|
||||||
|
- Makes a ped run away from a threat, which can be an entity or a set of coordinates.
|
||||||
|
- **Options:** `distance`, `time`, `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskFlee(ped, threatPed, { distance = 60.0 })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskFace(ped, target, opts)**
|
||||||
|
|
||||||
|
- Makes a ped turn to face an entity, or to reach a heading in degrees.
|
||||||
|
- **Options:** `duration`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskFace(ped, targetPed)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Vehicles
|
||||||
|
|
||||||
|
- **pedTaskEnterVehicle(ped, vehicle, opts)**
|
||||||
|
|
||||||
|
- Tells a ped to get into a vehicle. Completes once the ped is seated.
|
||||||
|
- **Options:** `seat` ("any", "driver", "passenger", "rearLeft", "rearRight"), `speed`, `flag` ("normal", "teleportToVehicle", "carjackFromSeat", "teleportDirectly"), `nativeTimeout`, `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskEnterVehicle(ped, vehicle, { seat = "driver" })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskLeaveVehicle(ped, opts)**
|
||||||
|
|
||||||
|
- Tells a ped to get out of a vehicle. Completes once the ped is out.
|
||||||
|
- **Options:** `vehicle`, `flag` ("normal", "normalSlow", "keepDoorOpen", "teleportOutside", "throwOutOfVehicle", "moveToPassengerFirst"), `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskLeaveVehicle(ped)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskOpenDoor(ped, vehicle, door, opts)**
|
||||||
|
|
||||||
|
- Tells a ped to open a vehicle door. GTA opens the door nearest to the given seat, so use seat names rather than door names. Asking for "driver" may still open the passenger door if that one is closer.
|
||||||
|
- **Options:** `speed`, `nativeTimeout`, `timeout`, `onComplete`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskOpenDoor(ped, vehicle, "passenger")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedCloseDoor(vehicle, door, opts)**
|
||||||
|
|
||||||
|
- Closes a vehicle door. GTA has no "close door" task, so this uses the door native directly.
|
||||||
|
- **Options:** `instant` (snap it shut), `ped` (a ped whose tasks should be cancelled first, so they stop fighting the close)
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedCloseDoor(vehicle, "passenger", { ped = ped })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskDriveTo(ped, vehicle, coords, opts)**
|
||||||
|
|
||||||
|
- Makes a ped drive a vehicle to a set of coordinates.
|
||||||
|
- **Options:** `speed`, `drivingMode`, `model`, `stopRange`, `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskDriveTo(ped, vehicle, vec3(100.0, 200.0, 30.0))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskPark(ped, vehicle, coords, opts)**
|
||||||
|
|
||||||
|
- Makes a ped park a vehicle at a set of coordinates.
|
||||||
|
- **Options:** `heading`, `mode` ("ignoreHeading", "forward", "reverse"), `radius`, `keepEngineOn`, `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskPark(ped, vehicle, vec3(100.0, 200.0, 30.0), { heading = 90.0 })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskChase(ped, target)**
|
||||||
|
|
||||||
|
- Makes a ped in a vehicle chase another entity. Continuous task.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskChase(ped, targetVehicle)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Combat
|
||||||
|
|
||||||
|
- **pedTaskCombat(ped, target, opts)**
|
||||||
|
|
||||||
|
- Sends a ped to attack a target. Continuous task.
|
||||||
|
- **Options:** `style` ("melee" or "ranged")
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskCombat(ped, targetPed, { style = "melee" })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskShootAt(ped, target, opts)**
|
||||||
|
|
||||||
|
- Makes a ped shoot at an entity or a set of coordinates. Continuous task.
|
||||||
|
- **Options:** `duration`, `pattern` (e.g. "FIRING_PATTERN_FULL_AUTO"), `aim` (aim at the target first, required for the ped to actually fire)
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskShootAt(ped, targetPed, { pattern = "FIRING_PATTERN_BURST_FIRE", aim = true })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskAimAt(ped, target, opts)**
|
||||||
|
|
||||||
|
- Makes a ped aim a weapon at an entity or a set of coordinates. Continuous task.
|
||||||
|
- **Options:** `duration`, `instant`, `intro`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskAimAt(ped, vec3(100.0, 200.0, 30.0))
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskAccuracy(ped, level)**
|
||||||
|
|
||||||
|
- Sets how accurate a ped is when shooting, `0` is poor and `100` is perfect.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskAccuracy(ped, 80)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskFiringPattern(ped, pattern)**
|
||||||
|
|
||||||
|
- Sets the firing pattern a ped uses, by name or hash.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskFiringPattern(ped, "FIRING_PATTERN_BURST_FIRE")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Animation
|
||||||
|
|
||||||
|
- **pedTaskPlayAnim(ped, dict, anim, opts)**
|
||||||
|
|
||||||
|
- Plays an animation on a ped. Completes once the animation has finished, unless `opts.loop` is set.
|
||||||
|
- **Options:** `blendIn`, `blendOut`, `duration`, `flag` ("looping", "holdLastFrame", "repositionWhenFinished", "notInterruptable", "upperBody"), `playbackRate`, `lock`, `loop`, `timeout`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskPlayAnim(ped, "amb@world_human_clipboard@male@idle_a", "idle_c")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskScenario(ped, scenario, opts)**
|
||||||
|
|
||||||
|
- Starts a scenario on a ped, such as a sitting or leaning animation. Continuous task.
|
||||||
|
- **Options:** `duration`, `intro`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskScenario(ped, "WORLD_HUMAN_STAND_IMPATIENT")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskLookAt(ped, target, opts)**
|
||||||
|
|
||||||
|
- Makes a ped look at an entity or a set of coordinates.
|
||||||
|
- **Options:** `duration`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskLookAt(ped, targetPed, { duration = 3000 })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskRagdoll(ped, duration, opts)**
|
||||||
|
|
||||||
|
- Makes a ped ragdoll for a set duration.
|
||||||
|
- **Options:** `type`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskRagdoll(ped, 3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskStandStill(ped, duration)**
|
||||||
|
|
||||||
|
- Makes a ped stand still for a set duration.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskStandStill(ped, 3000)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Behaviour
|
||||||
|
|
||||||
|
- **pedCombatAttributes(ped, opts)**
|
||||||
|
|
||||||
|
- Sets what a ped is allowed to do in combat. The native takes one attribute at a time, so every option provided is applied individually and anything left out is not changed.
|
||||||
|
- **Options:** any of `alwaysFight`, `canShoot`, `canAim`, `canUseCover`, `aggressive`, `perfectAccuracy`, `disableFleeFromCombat`, `canFlank`, `canCharge` and the rest of the game's combat attributes.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedCombatAttributes(ped, { alwaysFight = true, canShoot = true, canUseCover = true })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedFleeAttributes(ped, opts)**
|
||||||
|
|
||||||
|
- Sets what a ped is allowed to do when it is fleeing. The native takes a bitmask, so each attribute is applied as `1 << bit`.
|
||||||
|
- FiveM only documents bit `15` as "force cower", the rest follow the game's flee attribute ordering. Use `pedSetFleeAttribute` if you need a specific bit.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedFleeAttributes(ped, { [15] = true })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedSetFleeAttribute(ped, bit, enabled)**
|
||||||
|
|
||||||
|
- Sets a single flee attribute on a ped by its bit position.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedSetFleeAttribute(ped, 15, true)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedCombatRange(ped, range)**
|
||||||
|
|
||||||
|
- Sets how far a ped will engage in combat. Accepts "melee", "preferred", "far" or a raw value 0-2.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedCombatRange(ped, "melee")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedCombatAbility(ped, level)**
|
||||||
|
|
||||||
|
- Sets how well a ped fights, from `0` (poor) to `4` (very good).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedCombatAbility(ped, 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedCombatMovement(ped, mode)**
|
||||||
|
|
||||||
|
- Sets how a ped moves while fighting. Accepts "hold", "strafe", "advance" or a raw value 0-2.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedCombatMovement(ped, "strafe")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedSetConfigFlag(ped, flag, value)** / **pedGetConfigFlag(ped, flag)**
|
||||||
|
|
||||||
|
- Reads and writes raw ped config flags, for anything the helpers above do not cover.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedSetConfigFlag(ped, 17, true) -- block non temporary events
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedState(ped, opts)**
|
||||||
|
|
||||||
|
- Sets a group of general states on a ped. Only the options provided are applied.
|
||||||
|
- **Options:** `invincible`, `canRagdoll`, `dropsWeapons`, `criticalHits`, `canSwitchWeapon`, `drunk`, `armour`, `health`, `canBeDamaged`, `blocksEvents`, `moveRate`, `density`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedState(ped, { invincible = true, canRagdoll = false, armour = 100 })
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Tracking
|
||||||
|
|
||||||
|
- **pedTaskStatus(ped)**
|
||||||
|
|
||||||
|
- Returns the name of the task currently assigned to a ped, or nil.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if pedTaskStatus(ped) == "goTo" then print("on the move") end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskIsRunning(ped, name)**
|
||||||
|
|
||||||
|
- Checks if a named task is currently assigned to a ped.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if pedTaskIsRunning(ped, "combat") then print("fighting") end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskActive(ped, index)**
|
||||||
|
|
||||||
|
- Checks if a native task is active on a ped by its task index.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if pedTaskActive(ped, pedTaskWanderIndex()) then print("wandering") end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskWanderIndex()**
|
||||||
|
|
||||||
|
- Returns the native task index used by the wander task, for use with `pedTaskActive`.
|
||||||
|
|
||||||
|
- **pedTaskCancel(ped, name)**
|
||||||
|
|
||||||
|
- Cancels a task on a ped. GTA has no per task cancellation, so the ped's whole task queue is cleared. The name confirms the task is still the one running first.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskCancel(ped, "goTo")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskClear(ped)**
|
||||||
|
|
||||||
|
- Clears every task on a ped and resets its tracking.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskClear(ped)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pedTaskWait(ped, condition, opts)**
|
||||||
|
|
||||||
|
- Waits for a condition to become true for a ped. Useful for chaining tasks together without a separate script thread.
|
||||||
|
- **Options:** `timeout`, `interval`, `onComplete`, `onTimeout`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pedTaskWait(ped, function(p) return #(GetEntityCoords(p) - target) < 3.0 end, {
|
||||||
|
onComplete = function(p) pedTaskPlayAnim(p, "dict", "anim") end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Chaining tasks together
|
||||||
|
|
||||||
|
```lua
|
||||||
|
pedTaskGoTo(ped, coords, {
|
||||||
|
run = true,
|
||||||
|
onComplete = function(p)
|
||||||
|
pedTaskPlayAnim(p, "amb@world_human_clipboard@male@idle_a", "idle_c", {
|
||||||
|
onComplete = function(p2)
|
||||||
|
pedTaskGoTo(p2, coords2)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
### phones.lua
|
||||||
|
|
||||||
|
This module allows you to send in-game mail/messages to player phones, depending on the phone system integrated (e.g., QB, NPWD).
|
||||||
|
|
||||||
|
- **sendPhoneMail(data)**
|
||||||
|
|
||||||
|
- Sends a mail message to a player's in-game phone app.
|
||||||
|
- Typically used to notify players about deliveries, missions, or reminders.
|
||||||
|
- **Parameters:**
|
||||||
|
- `data` (`table`): Must contain phone/mail system-compatible fields like `sender`, `subject`, `message`, and `receiver`.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
sendPhoneMail({
|
||||||
|
subject = "Welcome!",
|
||||||
|
sender = "Admin",
|
||||||
|
message = "Thank you for joining our server.",
|
||||||
|
actions = {
|
||||||
|
{ label = "Reply", action = replyFunction }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
### playerfunctions.lua
|
||||||
|
|
||||||
|
This module contains helper functions for manipulating player states, interactions, and utility checks.
|
||||||
|
|
||||||
|
- **instantLookEnt(ent, ent2)**
|
||||||
|
|
||||||
|
- Instantly turns an entity to face a target (entity or coordinates) without animation.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
instantLookEnt(nil, vector3(200.0, 300.0, 40.0))
|
||||||
|
instantLookEnt(ped1, ped2)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **lookEnt(entity)**
|
||||||
|
|
||||||
|
- Makes the current player look toward the given entity.
|
||||||
|
- Usually called after when opening a menu or something similar to make the player visually face the location
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
lookEnt(vector3(200.0, 300.0, 40.0))
|
||||||
|
lookEnt(pedEntity)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **setThirst(src, thirst)**
|
||||||
|
|
||||||
|
⚠️ Server Side Only
|
||||||
|
- Sets the thirst level of a player.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
setThirst(source, 75)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **setHunger(src, hunger)**
|
||||||
|
|
||||||
|
⚠️ Server Side Only
|
||||||
|
- Sets the hunger level of a player.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
setHunger(source, 50)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **chargePlayer(cost, moneyType, newsrc)**
|
||||||
|
|
||||||
|
⚠️ Server Side Only
|
||||||
|
- Deducts money from a player of the specified type (`cash`, `bank`, etc).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
chargePlayer(100, "cash", source)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **fundPlayer(fund, moneyType, newsrc)**
|
||||||
|
|
||||||
|
⚠️ Server Side Only
|
||||||
|
- Adds money to a player's balance of a given type.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
fundPlayer(250, "bank", source)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **ConsumeSuccess(itemName, type, data)**
|
||||||
|
|
||||||
|
- Handles logic when an item is successfully consumed (e.g., food, drink, etc).
|
||||||
|
- Supports hunger and thirst info directly from table eg. `{ hunger = 10, thirst = 20 }`
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
ConsumeSuccess("health_pack", "food", { hunger = 10 })
|
||||||
|
|
||||||
|
ConsumeSuccess("beer", "alcohol", { thirst = 20 })
|
||||||
|
```
|
||||||
|
|
||||||
|
- **hasJob(job, source, grade)**
|
||||||
|
|
||||||
|
- Checks if a player has a certain job and optionally checks for a specific grade.
|
||||||
|
- Similar to `jobCheck()` but also retrieves as much player job info as possible
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
-- Check if the player has the 'police' job and is on duty
|
||||||
|
local hasPoliceJob, isOnDuty = hasJob("police")
|
||||||
|
if hasPoliceJob and isOnDuty then
|
||||||
|
-- Grant access to police-specific features
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if a specific player has the 'gang_leader' job with at least grade 2
|
||||||
|
local hasGangLeaderJob, _ = hasJob("gang_leader", playerId, 2)
|
||||||
|
if hasGangLeaderJob then
|
||||||
|
-- Allow gang leader actions
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getPlayer(source)**
|
||||||
|
|
||||||
|
- Retrieves basic player information (name, cash, bank, job, etc.) based on the active core/inventory system.
|
||||||
|
- Can be called server-side (passing a player source) or client-side (for current player).
|
||||||
|
- Called often in my scripts as its makes use of frameworks "GetPlayerData" etc.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
-- Get information for a specific player
|
||||||
|
local playerInfo = getPlayer(playerId)
|
||||||
|
print(playerInfo.name, playerInfo.cash, playerInfo.bank)
|
||||||
|
|
||||||
|
-- Get information for the current player (client-side)
|
||||||
|
local myInfo = getPlayer()
|
||||||
|
print(myInfo.name, myInfo.cash, myInfo.bank)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **GetPlayersFromCoords(coords, radius)**
|
||||||
|
|
||||||
|
- Returns a list of players within a specified radius of a set of coordinates.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local nearby = GetPlayersFromCoords(GetEntityCoords(PlayerPedId()), 10.0)
|
||||||
|
for _, playerId in pairs(nearby) do
|
||||||
|
print("Nearby player ID:", playerId)
|
||||||
|
end
|
||||||
|
```
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
### polyZone.lua
|
||||||
|
|
||||||
|
This module provides helpers for creating and removing polygon or circular zones using PolyZone-compatible data structures.
|
||||||
|
|
||||||
|
- **createPoly(data)**
|
||||||
|
|
||||||
|
- Creates a polygonal zone using the detected polyzone library (ox_lib or PolyZone).
|
||||||
|
- Automatically checks which polyzone script is active. When using ox_lib, it converts the provided 2D points to 3D (setting a constant z value) and sets a thickness value.
|
||||||
|
- For PolyZone, it creates the zone and attaches onEnter and onExit callbacks for ease of use.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createPoly({
|
||||||
|
name = 'testZone',
|
||||||
|
debug = true,
|
||||||
|
points = { vec2(100.0, 100.0), vec2(200.0, 100.0), vec2(200.0, 200.0), vec2(100.0, 200.0) },
|
||||||
|
onEnter = function() print("Entered Test Zone") end,
|
||||||
|
onExit = function() print("Exited Test Zone") end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **createCirclePoly(data)**
|
||||||
|
|
||||||
|
- When using ox_lib, it creates a sphere zone. For PolyZone, it creates a CircleZone and attaches onEnter and onExit callbacks.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createCirclePoly({
|
||||||
|
name = 'circleZone',
|
||||||
|
coords = vector3(150.0, 150.0, 20.0),
|
||||||
|
radius = 50.0,
|
||||||
|
onEnter = function() print("Entered Circle Zone") end,
|
||||||
|
onExit = function() print("Exited Circle Zone") end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **removePolyZone(Location)**
|
||||||
|
|
||||||
|
- Removes a previously created polygon or circle zone by name.
|
||||||
|
- Detects the active polyzone library and calls the appropriate removal method.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local zone = createPoly({...})
|
||||||
|
---
|
||||||
|
removePolyZone(zone)
|
||||||
|
```
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
### progressBars.lua
|
||||||
|
This module provides a unified progress bar system compatible with various UI frameworks.
|
||||||
|
|
||||||
|
❔It also contains an experimental "Shared Progressbar" system which was created for things like "giving an item to another player"
|
||||||
|
|
||||||
|
- **progressBar(data)**
|
||||||
|
- Displays a progress bar using the current configured system (e.g., ox_lib, qb, etc).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local success = progressBar({
|
||||||
|
label = "Processing...",
|
||||||
|
time = 5000,
|
||||||
|
dict = "amb@world_human_hang_out_street@female_hold_arm@base",
|
||||||
|
anim = "base",
|
||||||
|
flag = 49,
|
||||||
|
cancel = true,
|
||||||
|
})
|
||||||
|
if success then
|
||||||
|
print("Success!")
|
||||||
|
else
|
||||||
|
print("Cancelled")
|
||||||
|
end
|
||||||
|
```
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
### scaleEntity.lua
|
||||||
|
|
||||||
|
This utility provides a simple interface to scale an entity (ped, object, vehicle) in the world.
|
||||||
|
|
||||||
|
⚠️ **Important:** Unless the model's collision is removed or modified, the scale will reset when interacted with by other entities (e.g., walking into it, driving over it).
|
||||||
|
|
||||||
|
- **scaleEntity(entity, scale)**
|
||||||
|
|
||||||
|
- Scales the specified entity by the given factor.
|
||||||
|
- **Parameters:**
|
||||||
|
- `entity` (`number`): Entity handle (ped, object, vehicle).
|
||||||
|
- `scale` (`number`): Scale factor (e.g., `1.0` is normal size, `0.5` is half size).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
scaleEntity(PlayerPedId(), 0.8) -- Shrinks player slightly
|
||||||
|
```
|
||||||
|
|
||||||
|
- **resetScale(entity)**
|
||||||
|
|
||||||
|
- Resets the entity's scale back to its original default.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
resetScale(PlayerPedId()) -- Return to default size
|
||||||
|
```
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
### shops.lua
|
||||||
|
|
||||||
|
This module provides functions to open, sell to, and register in-game shops and markets.
|
||||||
|
|
||||||
|
- **sellMenu(data)**
|
||||||
|
|
||||||
|
- Opens a UI menu for selling items to a vendor or market system.
|
||||||
|
- A simple system for the ability to sell all of an item in a players inventory
|
||||||
|
- Good for money making eg. pawn shops
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
sellMenu({
|
||||||
|
sellTable = {
|
||||||
|
Header = "Sell Items",
|
||||||
|
Items = {
|
||||||
|
["gold_ring"] = 100,
|
||||||
|
["diamond"] = 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ped = pedEntity,
|
||||||
|
onBack = function() print("Returning to previous menu") end,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **sellAnim(data)**
|
||||||
|
|
||||||
|
- Plays an animation during the selling process for immersion.
|
||||||
|
- Used in sellMenu, but can be called externally if needed
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
sellAnim({
|
||||||
|
item = "gold_ring",
|
||||||
|
price = 100,
|
||||||
|
ped = pedEntity,
|
||||||
|
onBack = function() print("Sold Items") end,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **openShop(data)**
|
||||||
|
|
||||||
|
- Opens a shop interface for purchasing items.
|
||||||
|
- Checks job/gang restrictions, then uses the active inventory system to open the shop.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
openShop({
|
||||||
|
shop = "weapon_shop",
|
||||||
|
items = weaponShopItems,
|
||||||
|
coords = vector3(100.0, 200.0, 300.0),
|
||||||
|
job = "police",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **registerShop(name, label, items, society)**
|
||||||
|
|
||||||
|
- Registers a named shop with associated items and an optional society for fund handling.
|
||||||
|
- Supports either OXInv or QBInv (with QBInvNew flag).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
registerShop("fishing_shop", "Bait & Tackle", {
|
||||||
|
{ item = "fishing_rod", price = 50 },
|
||||||
|
{ item = "bait", price = 5 }
|
||||||
|
}, "fishing_society")
|
||||||
|
```
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
### skillcheck.lua
|
||||||
|
|
||||||
|
This module provides a UI-based skill check system, useful for crafting, hacking, or other interactive gameplay scenarios.
|
||||||
|
|
||||||
|
- **skillCheck()**
|
||||||
|
|
||||||
|
- Starts a skill check minigame sequence using the provided configuration.
|
||||||
|
- Configuration may define speed, difficulty, bar size, or success zones.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if skillCheck() then
|
||||||
|
print("Success!")
|
||||||
|
else
|
||||||
|
print("Failed :(")
|
||||||
|
end
|
||||||
|
```
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
### societybank.lua
|
||||||
|
|
||||||
|
This module handles financial operations for society accounts, typically used for jobs or organizations.
|
||||||
|
|
||||||
|
- **getSocietyAccount(society)**
|
||||||
|
|
||||||
|
- Retrieves the current balance for the specified society.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local balance = getSocietyAccount("police")
|
||||||
|
print("Police account balance: $"..balance)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **chargeSociety(society, amount)**
|
||||||
|
|
||||||
|
- Deducts a specified amount from the society's account.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
chargeSociety("police", 500)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **fundSociety(society, amount)**
|
||||||
|
|
||||||
|
- Adds funds to a society’s account.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
fundSociety("ambulance", 1200)
|
||||||
|
```
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
### stashcontrol.lua
|
||||||
|
|
||||||
|
This module handles logic for interacting with stashes—shared inventories for crafting, jobs, or storage systems.
|
||||||
|
|
||||||
|
- **checkStashItem(stashes, itemTable)**
|
||||||
|
|
||||||
|
- Retrieves (or updates) a local stash cache entry with a timeout.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local found, foundinStash = checkStashItem({"crafting_stash"}, { item = "steel", amount = 2 })
|
||||||
|
if found then print("Found item in "..foundInStash) end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **openStash(data)**
|
||||||
|
|
||||||
|
- Opens a stash using the active inventory system.
|
||||||
|
- Checks for job or gang restrictions before opening the stash.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
openStash({
|
||||||
|
stash = "playerStash",
|
||||||
|
label = "Player Stash",
|
||||||
|
coords = vector3(100, 200, 30)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getStash(stashName)**
|
||||||
|
|
||||||
|
- Retrieves the stash data by name (usually used for querying contents).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local items = getStash("playerStash")
|
||||||
|
for k, v in pairs(items) do
|
||||||
|
print(k)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **stashRemoveItem(stashItems, stashName, items)**
|
||||||
|
|
||||||
|
- Removes specified items from a stash. Used during crafting or transfers.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
stashRemoveItem(currentItems, "playerStash", { iron = 2, wood = 5 })
|
||||||
|
```
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
### targets.lua
|
||||||
|
|
||||||
|
⚠️ This module provides utility functions for adding and removing interaction targets with entities, models, zones, and coordinates. Supports common targeting frameworks like `ox_target`, `qb-target`, and more.
|
||||||
|
|
||||||
|
- **createEntityTarget(entity, opts, dist)**
|
||||||
|
|
||||||
|
- Adds interaction targets to a specific in-world entity.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createEntityTarget(entityId, {
|
||||||
|
{
|
||||||
|
action = function()
|
||||||
|
openStorage()
|
||||||
|
end,
|
||||||
|
icon = "fas fa-box",
|
||||||
|
job = "police",
|
||||||
|
label = "Open Storage",
|
||||||
|
},
|
||||||
|
}, 2.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **createBoxTarget(data, opts, dist)**
|
||||||
|
|
||||||
|
- Creates an interactable box zone with configurable options.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createBoxTarget(
|
||||||
|
{
|
||||||
|
'storageBox',
|
||||||
|
vector3(100.0, 200.0, 30.0),
|
||||||
|
2.0,
|
||||||
|
2.0,
|
||||||
|
{
|
||||||
|
name = 'storageBox',
|
||||||
|
heading = 100.0,
|
||||||
|
debugPoly = true,
|
||||||
|
minZ = 27.0,
|
||||||
|
maxZ = 32.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
action = function()
|
||||||
|
openStorage()
|
||||||
|
end,
|
||||||
|
icon = "fas fa-box",
|
||||||
|
job = "police",
|
||||||
|
label = "Open Storage",
|
||||||
|
},
|
||||||
|
}, 2.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **createCircleTarget(data, opts, dist)**
|
||||||
|
|
||||||
|
- Creates an interactable circular zone.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createCircleTarget({
|
||||||
|
name = 'centralPark',
|
||||||
|
coords = vector3(200.0, 300.0, 40.0),
|
||||||
|
radius = 50.0,
|
||||||
|
options = { debugPoly = false }
|
||||||
|
}, {
|
||||||
|
{ icon = "fas fa-tree", label = "Relax", action = relaxAction }
|
||||||
|
}, 2.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **createModelTarget(models, opts, dist)**
|
||||||
|
|
||||||
|
- Adds interactions to all matching models globally.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
createModelTarget({ model1, model2 },
|
||||||
|
{
|
||||||
|
{
|
||||||
|
action = function()
|
||||||
|
openStorage()
|
||||||
|
end,
|
||||||
|
icon = "fas fa-box",
|
||||||
|
job = "police",
|
||||||
|
label = "Open Storage",
|
||||||
|
},
|
||||||
|
}, 2.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **removeEntityTarget(entity)**
|
||||||
|
|
||||||
|
- Removes all targets linked to the specified entity.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
removeEntityTarget(vehicle)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **removeZoneTarget(target)**
|
||||||
|
|
||||||
|
- Removes a named zone-based target.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
removeZoneTarget("shop_box")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **removeModelTarget(model)**
|
||||||
|
|
||||||
|
- Removes interactions tied to a model globally.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
removeModelTarget("prop_vend_soda")
|
||||||
|
```
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
### vehicles.lua
|
||||||
|
|
||||||
|
This module provides utilities for reading, modifying, and interacting with vehicle properties and positioning.
|
||||||
|
|
||||||
|
- **searchCar(vehicle)**
|
||||||
|
|
||||||
|
- Searches the 'Vehicles' table for a specific vehicle's details.
|
||||||
|
- If the vehicle differs from the last searched, it retrieves its model and updates the carInfo table.
|
||||||
|
- The table includes the vehicle's name, price, and class information.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local info = searchCar(vehicleEntity)
|
||||||
|
print(info.name, info.price, info.class.name, info.class.index)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getVehicleProperties(vehicle)**
|
||||||
|
|
||||||
|
- Retrieves the properties of a given vehicle using the active framework.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local props = getVehicleProperties(vehicle)
|
||||||
|
if props then
|
||||||
|
print(json.encode(props))
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **setVehicleProperties(vehicle, props)**
|
||||||
|
|
||||||
|
- Sets the properties of a given vehicle if changes are detected.
|
||||||
|
- It compares the current properties with the new ones and applies the update using the active framework.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
setVehicleProperties(vehicle, props)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **checkDifferences(vehicle, newProps)**
|
||||||
|
|
||||||
|
- Checks for differences between the current and new vehicle properties.
|
||||||
|
- Compares properties using JSON encoding for deep comparison and logs differences.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
if checkDifferences(vehicleEntity, newProperties) then
|
||||||
|
setVehicleProperties(vehicleEntity, newProperties)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
- **pushVehicle(entity)**
|
||||||
|
|
||||||
|
- This function ensures that the vehicle is controlled by the current player and is set as a mission entity.
|
||||||
|
- It requests network control and sets the vehicle accordingly to synchronize changes across clients.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
pushVehicle(vehicle)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **getClosestVehicle(coords, src)**
|
||||||
|
|
||||||
|
- Finds the closest vehicle to the specified coordinates.
|
||||||
|
- The function uses different APIs based on whether a source is provided.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
local closestVeh, distance = getClosestVehicle({ x = 100, y = 200, z = 30 }, source)
|
||||||
|
```
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
### wrapperfunctions.lua
|
||||||
|
|
||||||
|
Provides wrapper compatibility functions for command and inventory stash systems across different frameworks (OX, QB, ESX, etc).
|
||||||
|
|
||||||
|
- **registerCommand(command, options)**
|
||||||
|
|
||||||
|
⚠️ Server Side Only
|
||||||
|
- Registers a command using the appropriate framework's API.
|
||||||
|
- **Parameters:**
|
||||||
|
- `command`: Command name (string)
|
||||||
|
- `options`: Table including help, params, callback, autocomplete, restrictedGroup
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
registerCommand("greet", {
|
||||||
|
"Greets the player",
|
||||||
|
{ name = "name", help = "Name of the player to greet" },
|
||||||
|
function(source, args) print("Hello, "..args[1].."!") end,
|
||||||
|
nil,
|
||||||
|
"admin"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- **registerStash(name, label, slots?, weight?, owner?, coords?)**
|
||||||
|
|
||||||
|
⚠️ Server Side Only
|
||||||
|
- Registers a stash using OX, or Origen inventory systems.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
registerStash(
|
||||||
|
"playerStash",
|
||||||
|
"Player Stash",
|
||||||
|
100,
|
||||||
|
8000000,
|
||||||
|
"player123",
|
||||||
|
{ x = 100.0, y = 200.0, z = 30.0 }
|
||||||
|
)
|
||||||
|
```
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
## Scaleforms
|
||||||
|
### debugScaleform.lua
|
||||||
|
This module renders debug text in-game when `debugMode` is enabled. Useful for live diagnostics or UI placement feedback.
|
||||||
|
|
||||||
|
- **debugScaleForm(textTable, loc)**
|
||||||
|
- Displays an overlay of lines of text at the specified screen location.
|
||||||
|
- **Note:** This only works if `debugMode` is set to `true`.
|
||||||
|
- **Parameters:**
|
||||||
|
- `textTable` (`table`): A list of strings to display.
|
||||||
|
- `loc` (`vector2`, optional): Top-left anchor point on screen (default: `vec2(0.05, 0.65)`).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
debugScaleForm({
|
||||||
|
"Line 1: Debug info",
|
||||||
|
"Line 2: More info"
|
||||||
|
})
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
### instructionalButtons.lua
|
||||||
|
This module renders instructional button prompts using native scaleforms in GTA V and RedM.
|
||||||
|
|
||||||
|
⚠️ **Note:** Because it uses native scaleforms, it must be run inside a `while` loop to remain visible.
|
||||||
|
|
||||||
|
- **makeInstructionalButtons(info)**
|
||||||
|
- Draws instructional buttons using the GTA scaleform `instructional_buttons`.
|
||||||
|
- **Parameters:**
|
||||||
|
- `info` (`table`): An array of tables, where each entry contains:
|
||||||
|
- `keys` (`table`): Control key codes (e.g., `{38, 29}`).
|
||||||
|
- `text` (`string`): The label for the button.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
makeInstructionalButtons({
|
||||||
|
{ keys = {38, 29}, text = "Open Menu" },
|
||||||
|
{ keys = {45}, text = "Close Menu" }
|
||||||
|
})
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **makeRedInstructionalButtons(info, title)**
|
||||||
|
- Experimental: Displays prompts using RedM-style `PromptSetGroup` API.
|
||||||
|
- **Note:** Still must be run in a loop for visibility.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
makeRedInstructionalButtons({
|
||||||
|
{ keys = {0x760A9C6F}, text = "Mount Horse" },
|
||||||
|
{ keys = {0x4CC0E2FE}, text = "Dismount" }
|
||||||
|
}, "Horse Controls")
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
### scaleform_basic.lua
|
||||||
|
This module includes helper functions for 3D text rendering and UI overlays using basic native scaleform techniques.
|
||||||
|
|
||||||
|
- **DrawText3D(coord, text, highlight)**
|
||||||
|
|
||||||
|
- Draws 3D text in the world at the given coordinates, with optional highlight.
|
||||||
|
- Includes a semi-transparent black background box for visibility.
|
||||||
|
- **Parameters:**
|
||||||
|
- `coord` (`vector3`): World position to draw text.
|
||||||
|
- `text` (`string`): Text content.
|
||||||
|
- `highlight` (`boolean`, optional): Highlights `~w~` sections with yellow.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
DrawText3D(vector3(100, 200, 300), "Hello World", true)
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **DisplayHelpMsg(text)**
|
||||||
|
- Shows a help message in the top-left corner of the screen.
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
DisplayHelpMsg("Press E to interact")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **displaySpinner(text)**
|
||||||
|
- Shows a busy spinner with a message (e.g., "Saving...").
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
displaySpinner("Saving data...")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **stopSpinner()**
|
||||||
|
- Hides any active busy spinner (client-only).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
stopSpinner()
|
||||||
|
```
|
||||||
|
|
||||||
|
### timerBars.lua
|
||||||
|
This module provides a native scaleform-based timer bar HUD. Ideal for displaying tasks, loading indicators, or time-sensitive objectives.
|
||||||
|
|
||||||
|
- **createTimerHud(title, data, alpha)**
|
||||||
|
|
||||||
|
⚠️ This was an attempt to recreate the gta native shooting ranges )fairly specific use case)
|
||||||
|
- Draws a timer bar with custom label and right-aligned values using the native GTA HUD system.
|
||||||
|
- **Parameters:**
|
||||||
|
- `title` (`string`): Title/header displayed on the bar.
|
||||||
|
- `data` (`table`): List of `label = value` entries to display (max 4 rows).
|
||||||
|
- `alpha` (`number`, optional): Opacity of the bar (0-255).
|
||||||
|
- **Example:**
|
||||||
|
```lua
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
createTimerHud("Timer Bar", {
|
||||||
|
{ stat = "Health", value = "85%" },
|
||||||
|
{ stat = "Armor", value = "50%", multi = 2 },
|
||||||
|
{ stat = "Stamina", value = "100%" },
|
||||||
|
}, 180)
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
```
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
Exports = {
|
|
||||||
QBExport = "qb-core",
|
|
||||||
QBXExport = "qbx_core",
|
|
||||||
ESXExport = "es_extended",
|
|
||||||
OXCoreExport = "ox_core",
|
|
||||||
|
|
||||||
OXInv = "ox_inventory",
|
|
||||||
QBInv = "qb-inventory",
|
|
||||||
QSInv = "qs-inventory",
|
|
||||||
CoreInv = "core-inventory",
|
|
||||||
CodeMInv = "codem-inventory",
|
|
||||||
|
|
||||||
OXLibExport = "ox_lib",
|
|
||||||
|
|
||||||
QBMenuExport = "qb-menu",
|
|
||||||
|
|
||||||
QBTargetExport = "qb-target",
|
|
||||||
OXTargetExport = "ox_target"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,556 @@
|
|||||||
|
--[[
|
||||||
|
Cached Resource Initialization Module
|
||||||
|
--------------------------------------
|
||||||
|
Initializes shared resource data (Items, Vehicles, Jobs, Gangs) once and caches it
|
||||||
|
to improve performance and reduce redundant data fetching across scripts.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Define resource names used in different frameworks
|
||||||
|
local Exports = {
|
||||||
|
QBExport = "qb-core",
|
||||||
|
QBXExport = "qbx_core",
|
||||||
|
ESXExport = "es_extended",
|
||||||
|
OXCoreExport = "ox_core",
|
||||||
|
|
||||||
|
OXInv = "ox_inventory",
|
||||||
|
QBInv = "qb-inventory",
|
||||||
|
PSInv = "ps-inventory",
|
||||||
|
CoreInv = "core_inventory",
|
||||||
|
CodeMInv = "codem-inventory",
|
||||||
|
OrigenInv = "origen_inventory",
|
||||||
|
TgiannInv = "tgiann-inventory",
|
||||||
|
JPRInv = "jpr-inventory",
|
||||||
|
|
||||||
|
OXLibExport = "ox_lib",
|
||||||
|
|
||||||
|
QBMenuExport = "qb-menu",
|
||||||
|
|
||||||
|
QBTargetExport = "qb-target",
|
||||||
|
OXTargetExport = "ox_target",
|
||||||
|
|
||||||
|
-- REDM
|
||||||
|
RSGExport = "rsg-core",
|
||||||
|
RSGInv = "rsg-inventory",
|
||||||
|
|
||||||
|
VorpExport = "vorp_core",
|
||||||
|
VorpInv = "vorp_inventory",
|
||||||
|
VorpMenu = "vorp_menu",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Prevent reloading if cache is already initialized
|
||||||
|
local cache = { Items = {}, Vehicles = {}, Jobs = {}, Gangs = {}, }
|
||||||
|
local cacheReady = false
|
||||||
|
|
||||||
|
-- Timer info, for debugging more then anything
|
||||||
|
local timers = {}
|
||||||
|
local function startTimer(label)
|
||||||
|
timers[label] = GetGameTimer()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function endTimer(label)
|
||||||
|
timers[label] = GetGameTimer() - (timers[label] or GetGameTimer())
|
||||||
|
timers[label] = "("..(timers[label] / 1000).."s)"
|
||||||
|
end
|
||||||
|
startTimer("Cache") startTimer("Items") startTimer("Vehicles") startTimer("Jobs") startTimer("InvWeight") startTimer("InvSlots")
|
||||||
|
|
||||||
|
-- Helper functions --
|
||||||
|
local function checkExists(resourceName)
|
||||||
|
local state = GetResourceState(resourceName)
|
||||||
|
return state and (state:find("start") or state:find("stopped"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function waitStarted(resourceName)
|
||||||
|
while GetResourceState(resourceName) ~= "started" do Wait(100) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function waitStartedOrStopped(resourceName)
|
||||||
|
local state = GetResourceState(resourceName)
|
||||||
|
while state ~= "started" and state ~= "stopped" do
|
||||||
|
Wait(100)
|
||||||
|
state = GetResourceState(resourceName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dupLowercaseWeapons(items)
|
||||||
|
for k, v in pairs(items or {}) do
|
||||||
|
if type(k) == "string" then
|
||||||
|
if k:find("WEAPON") then
|
||||||
|
items[k:lower()] = v
|
||||||
|
end
|
||||||
|
else
|
||||||
|
print("^1ERROR^7: ^1Possible table inside a table, check your items.lua^7?")
|
||||||
|
print("^1Possible Issue found^7:")
|
||||||
|
print(json.encode(items[k], { indent = true }))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Forceload libs --
|
||||||
|
|
||||||
|
-- Ensure oxmysql resource is loaded for jim_bridge internally
|
||||||
|
-- For some core's it needs to get data from the database
|
||||||
|
if checkExists(Exports.OXCoreExport) or checkExists(Exports.ESXExport) or checkExists(Exports.VorpExport) then
|
||||||
|
local fileLoader = assert(load(LoadResourceFile("oxmysql", ('lib/MySQL.lua')), ('@@oxmysql/lib/MySQL.lua')))
|
||||||
|
fileLoader()
|
||||||
|
end
|
||||||
|
|
||||||
|
if checkExists(Exports.OXCoreExport) then
|
||||||
|
-- Detected OX_Core in server, wait for it to be started if needed
|
||||||
|
waitStartedOrStopped(Exports.OXCoreExport)
|
||||||
|
local fileLoader = assert(load(LoadResourceFile(Exports.OXCoreExport, ('lib/init.lua')), ('@@'..Exports.OXCoreExport..'/lib/init.lua')))
|
||||||
|
fileLoader()
|
||||||
|
end
|
||||||
|
if checkExists(Exports.ESXExport) then
|
||||||
|
-- Detected ESX in server, wait for it to be started if needed
|
||||||
|
waitStarted(Exports.ESXExport)
|
||||||
|
local fileLoader = assert(load(LoadResourceFile(Exports.ESXExport, ('/imports.lua')), ('@@'..Exports.ESXExport..'/imports.lua')))
|
||||||
|
fileLoader()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initialize variables for caching
|
||||||
|
local itemResource, jobResource, vehResource = "N/A", "N/A", "N/A"
|
||||||
|
|
||||||
|
-- Print just to announce it knows the exports/scripts exist in the server
|
||||||
|
for _, v in pairs(Exports) do
|
||||||
|
if checkExists(v) then
|
||||||
|
print("^6Bridge^7: '^3"..v.."^7' detected")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
---- Load Items -----
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
local itemFunc = {
|
||||||
|
{ script = Exports.OXInv,
|
||||||
|
cacheItem = function()
|
||||||
|
local success, result = pcall(function()
|
||||||
|
return exports[Exports.OXInv]:Items()
|
||||||
|
end)
|
||||||
|
if success and result then
|
||||||
|
cache.Items = result
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.TgiannInv,
|
||||||
|
cacheItem = function()
|
||||||
|
local success, result = pcall(function()
|
||||||
|
return exports[Exports.TgiannInv]:Items()
|
||||||
|
end)
|
||||||
|
if success and result then
|
||||||
|
cache.Items = result
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.QBXExport,
|
||||||
|
cacheItem = function()
|
||||||
|
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
|
||||||
|
-- If this is nil, they need to update to qbx_core 1.23.0+
|
||||||
|
if not cache.Items then
|
||||||
|
-- if their inventory doesn't allow that (they refuse to update their butchered core replacement):
|
||||||
|
|
||||||
|
if GetResourceState(Exports.OrigenInv):find("start") then
|
||||||
|
itemResource = Exports.OrigenInv
|
||||||
|
cache.Items = exports[Exports.OrigenInv]:Items()
|
||||||
|
|
||||||
|
elseif GetResourceState(Exports.CodeMInv):find("start") then
|
||||||
|
itemResource = Exports.CodeMInv
|
||||||
|
cache.Items = exports[Exports.CodeMInv]:GetItemList()
|
||||||
|
|
||||||
|
elseif GetResourceState(Exports.CoreInv):find("start") then
|
||||||
|
itemResource = Exports.CoreInv
|
||||||
|
cache.Items = exports[Exports.CoreInv]:getItemsList()
|
||||||
|
|
||||||
|
elseif GetResourceState(Exports.TgiannInv):find("start") then
|
||||||
|
itemResource = Exports.TgiannInv
|
||||||
|
cache.Items = exports[Exports.TgiannInv]:Items()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.QBExport,
|
||||||
|
cacheItem = function()
|
||||||
|
cache.Items = exports[Exports.QBExport]:GetCoreObject().Shared.Items
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.ESXExport,
|
||||||
|
cacheItem = function()
|
||||||
|
cache.Items = ESX.GetItems()
|
||||||
|
while not next(cache.Items) do
|
||||||
|
cache.Items = ESX.GetItems()
|
||||||
|
Wait(1000)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.RSGExport,
|
||||||
|
cacheItem = function()
|
||||||
|
cache.Items = exports[Exports.RSGExport]:GetCoreObject().Shared.Items
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.VorpInv,
|
||||||
|
cacheItem = function()
|
||||||
|
local dbItems = MySQL.query.await('SELECT * FROM `items`')
|
||||||
|
local tempItems = {}
|
||||||
|
for i = 1, #dbItems do
|
||||||
|
local v = dbItems[i]
|
||||||
|
tempItems[v.item] = {
|
||||||
|
name = v.item,
|
||||||
|
label = v.label,
|
||||||
|
weight = v.weight,
|
||||||
|
info = v.metadata,
|
||||||
|
usable = v.usable,
|
||||||
|
type = v.type,
|
||||||
|
description = v.desc,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
cache.Items = tempItems
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, #itemFunc do
|
||||||
|
local data = itemFunc[i]
|
||||||
|
if checkExists(data.script) then
|
||||||
|
waitStarted(data.script) -- Wait for detected script to start fully
|
||||||
|
data.cacheItem() -- run tablized function for core/inv
|
||||||
|
dupLowercaseWeapons(cache.Items) -- make usable weapon item names for jim_bridge
|
||||||
|
itemResource = data.script -- Grab script name to announce later
|
||||||
|
endTimer("Items") -- end timer
|
||||||
|
break -- break loop so it doesn't keep checking
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
--- Load Vehicles ---
|
||||||
|
---------------------
|
||||||
|
---
|
||||||
|
local vehicleFunc = {
|
||||||
|
|
||||||
|
{ script = Exports.QBXExport,
|
||||||
|
cacheVehicle = function()
|
||||||
|
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.QBExport,
|
||||||
|
cacheVehicle = function()
|
||||||
|
cache.Vehicles = exports[Exports.QBExport]:GetCoreObject().Shared.Vehicles
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.OXCoreExport,
|
||||||
|
cacheVehicle = function()
|
||||||
|
cache.Vehicles = {}
|
||||||
|
for k, v in pairs(Ox.GetVehicleData()) do
|
||||||
|
cache.Vehicles[k] = {
|
||||||
|
model = k, hash = joaat(k),
|
||||||
|
price = v.price,
|
||||||
|
name = v.name,
|
||||||
|
brand = v.make
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.ESXExport,
|
||||||
|
cacheVehicle = function()
|
||||||
|
while not MySQL do Wait(100) end
|
||||||
|
for _, v in pairs(MySQL.query.await('SELECT model, price, name FROM vehicles')) do
|
||||||
|
cache.Vehicles[v.model] = {
|
||||||
|
model = v.model,
|
||||||
|
hash = joaat(v.model),
|
||||||
|
price = v.price,
|
||||||
|
name = v.name,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.RSGExport,
|
||||||
|
cacheVehicle = function()
|
||||||
|
cache.Vehicles = exports[Exports.RSGExport]:GetCoreObject().Shared.Vehicles
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.VorpExport,
|
||||||
|
cacheVehicle = function()
|
||||||
|
cache.Vehicles = { ["unkown"] = {} }
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, #vehicleFunc do
|
||||||
|
local data = vehicleFunc[i]
|
||||||
|
if checkExists(data.script) then
|
||||||
|
waitStarted(data.script) -- Wait for detected script to start fully
|
||||||
|
data.cacheVehicle() -- run tablized function for core
|
||||||
|
vehResource = data.script -- Grab script name to announce later
|
||||||
|
endTimer("Vehicles") -- end timer
|
||||||
|
break -- break loop so it doesn't keep checking
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
----- Load Jobs -----
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
local jobFunc = {
|
||||||
|
|
||||||
|
{ script = Exports.QBXExport,
|
||||||
|
cacheJob = function()
|
||||||
|
cache.Jobs, cache.Gangs = exports[Exports.QBXExport]:GetJobs(), exports[Exports.QBXExport]:GetGangs()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.QBExport,
|
||||||
|
cacheJob = function()
|
||||||
|
Core = exports[Exports.QBExport]:GetCoreObject()
|
||||||
|
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.OXCoreExport,
|
||||||
|
cacheJob = function()
|
||||||
|
while not MySQL do Wait(100) end
|
||||||
|
local tempJobs = MySQL.query.await('SELECT * FROM `ox_groups`')
|
||||||
|
local tempGrades = MySQL.query.await('SELECT * FROM `ox_group_grades`')
|
||||||
|
local gradeMap = {}
|
||||||
|
for _, grade in pairs(tempGrades) do
|
||||||
|
gradeMap[grade.group] = gradeMap[grade.group] or {}
|
||||||
|
gradeMap[grade.group][grade.grade] = { name = grade.label }
|
||||||
|
end
|
||||||
|
for _, job in pairs(tempJobs) do
|
||||||
|
cache.Jobs[job.name] = {
|
||||||
|
label = job.label,
|
||||||
|
grades = gradeMap[job.name] or {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
cache.Gangs = cache.Jobs
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.ESXExport,
|
||||||
|
cacheJob = function()
|
||||||
|
ESX = ESX or exports[Exports.ESXExport]:getSharedObject()
|
||||||
|
cache.Jobs = ESX.GetJobs()
|
||||||
|
while not next(cache.Jobs) do
|
||||||
|
Wait(100)
|
||||||
|
cache.Jobs = ESX.GetJobs()
|
||||||
|
end
|
||||||
|
for Role, Grades in pairs(cache.Jobs) do
|
||||||
|
-- Check for if user has added grades
|
||||||
|
if Grades.grades == nil or not next(Grades.grades) then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
for grade, info in pairs(Grades.grades) do
|
||||||
|
if info.label and info.label:find("[Bb]oss") then
|
||||||
|
cache.Jobs[Role].grades[grade].isBoss = true
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local highestGrade = nil
|
||||||
|
for k in pairs(Grades.grades) do
|
||||||
|
local num = tonumber(k)
|
||||||
|
if num and (not highestGrade or num > highestGrade) then
|
||||||
|
highestGrade = num
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if highestGrade then
|
||||||
|
cache.Jobs[Role].grades[tostring(highestGrade)].isBoss = true
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
cache.Gangs = cache.Jobs
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.RSGExport,
|
||||||
|
cacheJob = function()
|
||||||
|
Core = exports[Exports.RSGExport]:GetCoreObject()
|
||||||
|
cache.Jobs, cache.Gangs = Core.Shared.Jobs, Core.Shared.Gangs
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ script = Exports.VorpExport,
|
||||||
|
cacheJob = function()
|
||||||
|
cache.Jobs = { ["unkown"] = {} }
|
||||||
|
cache.Gangs = cache.Jobs
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, #jobFunc do
|
||||||
|
local data = jobFunc[i]
|
||||||
|
if checkExists(data.script) then
|
||||||
|
waitStarted(data.script) -- Wait for detected script to start fully
|
||||||
|
data.cacheJob() -- run tablized function for core
|
||||||
|
jobResource = data.script -- Grab script name to announce later
|
||||||
|
endTimer("Jobs") -- end timer
|
||||||
|
break -- break loop so it doesn't keep checking
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Fallback if nil or empty
|
||||||
|
if cache.Items == nil or not next(cache.Items) then
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
print("^1ERROR^7: ^1Can NOT find "..itemResource.." ^7Items ^1list^7, ^1possible error in that file or is it empty^7?")
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
cache.Items = {} -- Fallback to an empty table
|
||||||
|
end
|
||||||
|
if cache.Vehicles == nil or not next(cache.Vehicles) then
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
print("^1ERROR^7: ^1Can NOT find "..vehResource.." ^7Vehicles ^1list^7, ^1possible error in that file or is it empty^7?")
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
cache.Vehicles = {} -- Fallback to an empty table
|
||||||
|
end
|
||||||
|
if cache.Jobs == nil or not next(cache.Jobs) then
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
print("^1ERROR^7: ^1Can NOT find "..jobResource.." ^7job ^1list^7, ^1possible error in that file or is it empty^7?")
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
cache.Jobs = {} -- Fallback to an empty table
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Auto Detection of Inventory Weight -- **EXPERIMENTAL**
|
||||||
|
-- Forcefully load the the specified config file from inventory scripts
|
||||||
|
-- This allows to get information required for certain functions that need to detect how much space is left in a players inventory
|
||||||
|
-- This is born from too many tickets of me needing to explain that they need to change "InventoryWeight" to match their inv setting
|
||||||
|
local function getInventoryConfig(resource, data)
|
||||||
|
if data.convars then
|
||||||
|
return function(path)
|
||||||
|
local key = path[1]
|
||||||
|
local convar = key == "MaxWeight" and data.convars.weight or key == "MaxSlots" and data.convars.slots
|
||||||
|
return convar and GetConvarInt(convar.key, convar.default) or nil, "Unsupported convar path: " .. table.concat(path, ".")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local content = LoadResourceFile(resource, data.file)
|
||||||
|
if not content then return nil, "Failed to load file" end
|
||||||
|
|
||||||
|
local env = {
|
||||||
|
GetConvar = GetConvar,
|
||||||
|
vector3 = vector3,
|
||||||
|
vector4 = vector4,
|
||||||
|
Citizen = Citizen,
|
||||||
|
GetResourceState = GetResourceState,
|
||||||
|
exports = exports,
|
||||||
|
DependencyCheck = function() return nil end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local fn, err = load(content, '@'..data.file, 't', env)
|
||||||
|
|
||||||
|
if not fn then return nil, "Failed to compile config: " .. err end
|
||||||
|
if not pcall(fn) then return nil, "Error executing config file" end
|
||||||
|
|
||||||
|
local cfg = env.Config or env.config
|
||||||
|
if not cfg then return nil, "Config table not found" end
|
||||||
|
|
||||||
|
return function(path)
|
||||||
|
local ref = cfg
|
||||||
|
for _, k in ipairs(path) do
|
||||||
|
if type(ref) ~= "table" then return nil, "Path invalid at: " .. tostring(k) end
|
||||||
|
ref = ref[k]
|
||||||
|
end
|
||||||
|
return ref
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Inventory table
|
||||||
|
local invWeightTable = {
|
||||||
|
[Exports.OXInv] = { convars = {
|
||||||
|
weight = { key = "inventory:weight", default = 30000 },
|
||||||
|
slots = { key = "inventory:slots", default = 40 }
|
||||||
|
}},
|
||||||
|
[Exports.QBInv] = {
|
||||||
|
fallback = {
|
||||||
|
{ file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } }, -- old version
|
||||||
|
{ file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } }, -- new version
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[Exports.JPRInv] = { file = "configs/main_config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
|
||||||
|
[Exports.PSInv] = { file = "config.lua", path = { "MaxInventoryWeight" }, slotPath = { "MaxInventorySlots" } },
|
||||||
|
[Exports.TgiannInv] = { file = "configs/config.lua", path = { "slotsMaxWeights", "player", "maxWeight" }, slotPath = { "slotsMaxWeights", "player", "slots" } },
|
||||||
|
[Exports.CodeMInv] = { file = "config/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
|
||||||
|
[Exports.RSGInv] = { file = "shared/config.lua", path = { "MaxWeight" }, slotPath = { "MaxSlots" } },
|
||||||
|
--[Exports.OrigenInv] = { file = "config.lua", path = { "MaxWeight" } },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Run config detection
|
||||||
|
local invResource = ""
|
||||||
|
for script, data in pairs(invWeightTable) do
|
||||||
|
if checkExists(script) then
|
||||||
|
if script == Exports.QBInv and GetResourceState(Exports.JPRInv):find("start") then goto skip end
|
||||||
|
|
||||||
|
waitStartedOrStopped(script)
|
||||||
|
|
||||||
|
local attempts = data.fallback or { data }
|
||||||
|
local lookup, used, err
|
||||||
|
|
||||||
|
for _, option in ipairs(attempts) do
|
||||||
|
local try, e = getInventoryConfig(script, option)
|
||||||
|
if try then
|
||||||
|
lookup = try
|
||||||
|
used = option
|
||||||
|
break
|
||||||
|
end
|
||||||
|
err = e
|
||||||
|
end
|
||||||
|
|
||||||
|
if not lookup then
|
||||||
|
print(("^1ERROR^7: ^1Config loader failed from ^5%s^7: ^1%s^7"):format(script, err or "unknown"))
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local function resolve(label, path)
|
||||||
|
local val, perr = lookup(path)
|
||||||
|
if val then
|
||||||
|
cache["Inventory"..label] = val
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local warnType = label == "Weight" and "^1ERROR" or "^3WARNING"
|
||||||
|
print(("%s^7: ^1Failed to get ^7Inventory%s ^1from ^5%s^7: ^1%s^7"):format(warnType, label, script, perr or "unknown"))
|
||||||
|
end
|
||||||
|
|
||||||
|
resolve("Weight", used.path or { "MaxWeight" })
|
||||||
|
resolve("Slots", used.slotPath or { "MaxSlots" })
|
||||||
|
|
||||||
|
invResource = script:gsub("-", "^7-^4"):gsub("_", "^7_^4")
|
||||||
|
break
|
||||||
|
end
|
||||||
|
::skip::
|
||||||
|
end
|
||||||
|
endTimer("InvWeight")
|
||||||
|
endTimer("InvSlots")
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
local counts = {
|
||||||
|
Items = 0, Vehicles = 0, Jobs = 0, Gangs = 0,
|
||||||
|
}
|
||||||
|
for k, v in pairs(cache) do
|
||||||
|
if type(v) ~= "number" then for count in pairs(v) do counts[k] += 1 end end
|
||||||
|
end
|
||||||
|
if cache.InventoryWeight then
|
||||||
|
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventoryWeight^7: ^3"..cache.InventoryWeight.."^7 (^3"..(cache.InventoryWeight / 1000).."kg^7) ^7"..timers["InvWeight"])
|
||||||
|
end
|
||||||
|
if cache.InventorySlots then
|
||||||
|
print("^6FrameWorkCache^7: ^4"..invResource.."^2 InventorySlots^7: ^3"..cache.InventorySlots.." ^7"..timers["InvSlots"])
|
||||||
|
end
|
||||||
|
print("^6FrameworkCache^7: ^4"..itemResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Items).."^2 Items ^7"..timers["Items"])
|
||||||
|
print("^6FrameworkCache^7: ^4"..vehResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Vehicles).."^2 Vehicles ^7"..timers["Vehicles"])
|
||||||
|
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Jobs).."^2 Jobs ^7"..timers["Jobs"])
|
||||||
|
print("^6FrameworkCache^7: ^4"..jobResource:gsub("-", "^7-^4"):gsub("_", "^7_^4").."^2 Loaded ^3"..tostring(counts.Gangs).."^2 Gangs ^7"..timers["Jobs"])
|
||||||
|
endTimer("Cache")
|
||||||
|
print("^6FrameworkCache^7: ^2Cache Ready ^7"..timers["Cache"])
|
||||||
|
cacheReady = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent("jim_bridge:requestCache", function()
|
||||||
|
local src = source
|
||||||
|
TriggerClientEvent("jim_bridge:receiveCache", src, cache)
|
||||||
|
end)
|
||||||
|
|
||||||
|
exports("GetSharedData", function()
|
||||||
|
-- Wait indefinitely (with periodic checks) until cacheReady is true
|
||||||
|
local timeout = GetGameTimer() + 20000 -- 20 second failsafe timeout
|
||||||
|
while not cacheReady and GetGameTimer() < timeout do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not cacheReady then
|
||||||
|
print("^1ERROR^7: jim_bridge cache timed out waiting for data.")
|
||||||
|
return nil -- Signal clearly if cache never became ready
|
||||||
|
end
|
||||||
|
|
||||||
|
return cache
|
||||||
|
end)
|
||||||
-784
@@ -1,784 +0,0 @@
|
|||||||
onDuty = false
|
|
||||||
function jobCheck(job)
|
|
||||||
canDo = true
|
|
||||||
if not hasJob(job) or not onDuty then
|
|
||||||
triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"])
|
|
||||||
canDo = false
|
|
||||||
end
|
|
||||||
return canDo
|
|
||||||
end
|
|
||||||
|
|
||||||
local time = 100
|
|
||||||
function loadModel(model)
|
|
||||||
if not HasModelLoaded(model) then if Config.System.Debug then print("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'") end
|
|
||||||
while not HasModelLoaded(model) do if time > 0 then time = time - 1 RequestModel(model)
|
|
||||||
else time = 1000 print("^6Bridge^7: ^3LoadModel^7: ^2Timed out loading model ^7'^6"..model.."^7'") break end
|
|
||||||
Wait(10) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function unloadModel(model) if Config.System.Debug then print("^6Bridge^7: ^2Removing Model^7: '^6"..model.."^7'") end SetModelAsNoLongerNeeded(model) end
|
|
||||||
function loadAnimDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..dict.."^7'") end while not HasAnimDictLoaded(dict) do RequestAnimDict(dict) Wait(5) end end
|
|
||||||
function unloadAnimDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Removing Anim Dictionary^7: '^6"..dict.."^7'") end RemoveAnimDict(dict) end
|
|
||||||
function loadPtfxDict(dict) if Config.System.Debug then if not HasNamedPtfxAssetLoaded(dict) then print("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..dict.."^7'") end end while not HasNamedPtfxAssetLoaded(dict) do RequestNamedPtfxAsset(dict) Wait(5) end end
|
|
||||||
function unloadPtfxDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'") end RemoveNamedPtfxAsset(dict) end
|
|
||||||
function loadTextureDict(dict) if Config.System.Debug then print("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'") end while not HasStreamedTextureDictLoaded(dict) do RequestNamedPtfxAsset(dict) Wait(5) end end
|
|
||||||
|
|
||||||
function countTable(table) local i = 0 for keys in pairs(table) do i += 1 end return i end
|
|
||||||
|
|
||||||
function pairsByKeys(t) local a = {} for n in pairs(t) do a[#a+1] = n end table.sort(a) local i = 0 local iter = function() i += 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter end
|
|
||||||
|
|
||||||
function playAnim(animDict, animName, duration, flag, ped)
|
|
||||||
loadAnimDict(animDict)
|
|
||||||
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, 1.0, -1.0, duration or 30000, flag or 50, 1, false, false, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
function stopAnim(animDict, animName)
|
|
||||||
StopAnimTask(PlayerPedId(), animName, animDict)
|
|
||||||
unloadAnimDict(animDict)
|
|
||||||
end
|
|
||||||
|
|
||||||
function makeVeh(model, coords)
|
|
||||||
loadModel(model)
|
|
||||||
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, true, false)
|
|
||||||
SetVehicleHasBeenOwnedByPlayer(veh, true)
|
|
||||||
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
|
|
||||||
Wait(100)
|
|
||||||
SetVehicleNeedsToBeHotwired(veh, false)
|
|
||||||
SetVehRadioStation(veh, 'OFF')
|
|
||||||
SetVehicleFuelLevel(veh, 100.0)
|
|
||||||
SetVehicleModKit(veh, 0)
|
|
||||||
SetVehicleOnGroundProperly(veh)
|
|
||||||
if Config.System.Debug then
|
|
||||||
local coords = { string.format("%.2f", coords.x), string.format("%.2f", coords.y), string.format("%.2f", coords.z), (string.format("%.2f", coords.w or 0.0)) }
|
|
||||||
print("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..(model).."^7' | ^2Coord^7: ^5vec4^7(^6"..(coords[1]).."^7, ^6"..(coords[2]).."^7, ^6"..(coords[3]).."^7, ^6"..(coords[4]).."^7)")
|
|
||||||
end
|
|
||||||
unloadModel(model)
|
|
||||||
return veh
|
|
||||||
end
|
|
||||||
|
|
||||||
local Peds = {}
|
|
||||||
local Props = {}
|
|
||||||
function makePed(model, coords, freeze, collision, scenario, anim, synced)
|
|
||||||
loadModel(model)
|
|
||||||
local ped = CreatePed(0, model, coords.x, coords.y, coords.z-1.03, coords.w, synced and synced or true, false)
|
|
||||||
SetEntityInvincible(ped, true)
|
|
||||||
SetBlockingOfNonTemporaryEvents(ped, true)
|
|
||||||
FreezeEntityPosition(ped, freeze and freeze or true)
|
|
||||||
|
|
||||||
if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end
|
|
||||||
if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end
|
|
||||||
if anim then
|
|
||||||
loadAnimDict(anim[1])
|
|
||||||
TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
|
|
||||||
end
|
|
||||||
if Config.System.Debug then
|
|
||||||
local coords = { string.format("%.2f", coords.x), string.format("%.2f", coords.y), string.format("%.2f", coords.z), (string.format("%.2f", coords.w or 0.0)) }
|
|
||||||
print("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^6"..(model).."^7' | ^2Coord^7: ^5vec4^7(^6"..(coords[1]).."^7, ^6"..(coords[2]).."^7, ^6"..(coords[3]).."^7, ^6"..(coords[4]).."^7)")
|
|
||||||
end
|
|
||||||
unloadModel(model)
|
|
||||||
Peds[#Peds+1] = ped
|
|
||||||
return ped
|
|
||||||
end
|
|
||||||
|
|
||||||
function makeProp(data, freeze, synced)
|
|
||||||
loadModel(data.prop)
|
|
||||||
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced or false, synced or false, false)
|
|
||||||
SetEntityHeading(prop, data.coords.w + 180.0)
|
|
||||||
FreezeEntityPosition(prop, freeze and freeze or 0)
|
|
||||||
if Config.System.Debug then
|
|
||||||
local coords = { string.format("%.2f", data.coords.x), string.format("%.2f", data.coords.y), string.format("%.2f", data.coords.z), (string.format("%.2f", data.coords.w or 0.0)) }
|
|
||||||
print("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..(data.prop).."^7' | ^2Coord^7: ^5vec4^7(^6"..(coords[1]).."^7, ^6"..(coords[2]).."^7, ^6"..(coords[3]).."^7, ^6"..(coords[4]).."^7)")
|
|
||||||
end
|
|
||||||
unloadModel(data.prop)
|
|
||||||
Props[#Props+1] = prop
|
|
||||||
return prop
|
|
||||||
end
|
|
||||||
|
|
||||||
AddEventHandler('onResourceStop', function(r)
|
|
||||||
if r ~= GetCurrentResourceName() then return end
|
|
||||||
stopSpinner()
|
|
||||||
for i = 1, #Peds do DeletePed(Peds[i]) end
|
|
||||||
for i = 1, #Props do destroyProp(Props[i]) end
|
|
||||||
end)
|
|
||||||
|
|
||||||
function instantLookEnt(ent, ent2)
|
|
||||||
local p1 = GetEntityCoords(ent, true)
|
|
||||||
local p2 = GetEntityCoords(ent2, true)
|
|
||||||
|
|
||||||
local dx = p2.x - p1.x
|
|
||||||
local dy = p2.y - p1.y
|
|
||||||
|
|
||||||
local heading = GetHeadingFromVector_2d(dx, dy)
|
|
||||||
SetEntityHeading( ent, heading )
|
|
||||||
end
|
|
||||||
|
|
||||||
function lookEnt(entity) local ped = PlayerPedId()
|
|
||||||
if entity then
|
|
||||||
if type(entity) == "vector3" or type(entity) == "vector4" then
|
|
||||||
if not IsPedHeadingTowardsPosition(ped, entity.xyz, 30.0) then
|
|
||||||
TaskTurnPedToFaceCoord(ped, entity.xyz, 1500)
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Turning Player to^7: '^6"..json.encode(entity).."^7'") end
|
|
||||||
Wait(1500)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if DoesEntityExist(entity) then
|
|
||||||
if not IsPedHeadingTowardsPosition(ped, GetEntityCoords(entity), 30.0) then
|
|
||||||
TaskTurnPedToFaceCoord(ped, GetEntityCoords(entity), 1500)
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Turning Player to^7: '^6"..entity.."^7'") end
|
|
||||||
Wait(1500)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function destroyProp(entity)
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'") end
|
|
||||||
if IsEntityAttachedToEntity(entity, PlayerPedId()) then
|
|
||||||
SetEntityAsMissionEntity(entity)
|
|
||||||
DetachEntity(entity, true, true)
|
|
||||||
end
|
|
||||||
DeleteObject(entity)
|
|
||||||
end
|
|
||||||
|
|
||||||
function pushVehicle(entity)
|
|
||||||
SetVehicleModKit(entity, 0)
|
|
||||||
if entity ~= 0 and DoesEntityExist(entity) then
|
|
||||||
if not NetworkHasControlOfEntity(entity) then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Requesting network control of vehicle^7.") end
|
|
||||||
NetworkRequestControlOfEntity(entity)
|
|
||||||
local timeout = 2000
|
|
||||||
while timeout > 0 and not NetworkHasControlOfEntity(entity) do
|
|
||||||
Wait(100)
|
|
||||||
timeout = timeout - 100
|
|
||||||
end
|
|
||||||
if NetworkHasControlOfEntity(entity) and Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Network has control of entity^7.") end
|
|
||||||
end
|
|
||||||
if not IsEntityAMissionEntity(entity) then
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Setting vehicle as a ^7'^2mission^7' &2entity^7.") end
|
|
||||||
SetEntityAsMissionEntity(entity, true, true)
|
|
||||||
local timeout = 2000
|
|
||||||
while timeout > 0 and not IsEntityAMissionEntity(entity) do
|
|
||||||
Wait(100)
|
|
||||||
timeout = timeout - 100
|
|
||||||
end
|
|
||||||
if IsEntityAMissionEntity(entity) and Config.System.Debug then print("^6Bridge^7: ^3pushVehicle^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.") end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function makeBlip(data)
|
|
||||||
local blip = AddBlipForCoord(data.coords)
|
|
||||||
SetBlipAsShortRange(blip, true)
|
|
||||||
SetBlipSprite(blip, data.sprite or 106)
|
|
||||||
SetBlipColour(blip, data.col or 5)
|
|
||||||
SetBlipScale(blip, data.scale or 0.7)
|
|
||||||
SetBlipDisplay(blip, (data.disp or 6))
|
|
||||||
if data.category then SetBlipCategory(blip, data.category) end
|
|
||||||
BeginTextCommandSetBlipName('STRING')
|
|
||||||
AddTextComponentString(tostring(data.name))
|
|
||||||
EndTextCommandSetBlipName(blip)
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^6Blip ^2created for location^7: '^6"..data.name.."^7'") end
|
|
||||||
return blip
|
|
||||||
end
|
|
||||||
|
|
||||||
function makeEntityBlip(data)
|
|
||||||
AddBlipForEntity(data.entity)
|
|
||||||
local blip = GetBlipFromEntity(data.entity)
|
|
||||||
SetBlipAsShortRange(blip, true)
|
|
||||||
SetBlipSprite(blip, data.sprite or 106)
|
|
||||||
SetBlipColour(blip, data.col or 5)
|
|
||||||
SetBlipScale(blip, data.scale or 0.7)
|
|
||||||
SetBlipDisplay(blip, (data.disp or 6))
|
|
||||||
if data.category then SetBlipCategory(blip, data.category) end
|
|
||||||
BeginTextCommandSetBlipName('STRING')
|
|
||||||
AddTextComponentString(tostring(data.name))
|
|
||||||
EndTextCommandSetBlipName(blip)
|
|
||||||
if Config.System.Debug then print("^6Bridge^7: ^6Blip ^2created for Entity^7: '^6"..data.name.."^7'") end
|
|
||||||
return blip
|
|
||||||
end
|
|
||||||
|
|
||||||
function lockInv(toggle)
|
|
||||||
FreezeEntityPosition(PlayerPedId(), toggle)
|
|
||||||
LocalPlayer.state:set("inv_busy", toggle, true)
|
|
||||||
TriggerEvent('inventory:client:busy:status', toggle)
|
|
||||||
TriggerEvent('canUseInventoryAndHotbar:toggle', not toggle)
|
|
||||||
end
|
|
||||||
|
|
||||||
function createTempCam(ent, coords)
|
|
||||||
local cam = nil
|
|
||||||
if Config.Crafting.craftCam then
|
|
||||||
if Config.System.Debug then
|
|
||||||
triggerNotify(nil, "ModCam Created", "success")
|
|
||||||
end
|
|
||||||
local camCoords = GetOffsetFromEntityInWorldCoords(ent, 1.2, -0.3, 0.8)
|
|
||||||
--local pedCoords = GetEntityCoords(PlayerPedId())
|
|
||||||
cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z+0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
|
|
||||||
PointCamAtCoord(cam, coords)
|
|
||||||
end
|
|
||||||
return cam
|
|
||||||
end
|
|
||||||
|
|
||||||
function keyGen()
|
|
||||||
local charset = {
|
|
||||||
"q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k","l","z","x","c","v","b","n","m",
|
|
||||||
"Q","W","E","R","T","Y","U","I","O","P","A","S","D","F","G","H","J","K","L","Z","X","C","V","B","N","M",
|
|
||||||
"1","2","3","4","5","6","7","8","9","0"
|
|
||||||
}
|
|
||||||
local GeneratedID = ""
|
|
||||||
for i = 1, 3 do GeneratedID = GeneratedID..charset[math.random(1, #charset)] end
|
|
||||||
return GeneratedID
|
|
||||||
end
|
|
||||||
|
|
||||||
function startTempCam(cam)
|
|
||||||
if Config.Crafting.craftCam then
|
|
||||||
SetCamActive(cam, true)
|
|
||||||
RenderScriptCams(true, true, 1000, true, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function stopTempCam()
|
|
||||||
if Config.Crafting.craftCam then
|
|
||||||
CreateThread(function()
|
|
||||||
Wait(1000)
|
|
||||||
RenderScriptCams(false, true, 500, true, true)
|
|
||||||
DestroyAllCams()
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local inProgress = false
|
|
||||||
function progressBar(data)
|
|
||||||
local result = nil
|
|
||||||
if data.cam then
|
|
||||||
startTempCam(data.cam)
|
|
||||||
end
|
|
||||||
if Config.System.ProgressBar == "ox" then
|
|
||||||
if exports[OXLibExport]:progressBar({
|
|
||||||
duration = Config.System.Debug and 1000 or data.time,
|
|
||||||
label = data.label,
|
|
||||||
useWhileDead = data.dead and data.dead or false,
|
|
||||||
canCancel = data.cancel and data.cancel or true,
|
|
||||||
anim = {
|
|
||||||
dict = data.dict,
|
|
||||||
clip = data.anim,
|
|
||||||
flag = (data.flag == 8 and 32 or data.flag) or nil,
|
|
||||||
scenario = data.task
|
|
||||||
},
|
|
||||||
disable = {
|
|
||||||
combat = true
|
|
||||||
},
|
|
||||||
}) then
|
|
||||||
result = true
|
|
||||||
lockInv(false)
|
|
||||||
if data.cam then stopTempCam(data.cam) end
|
|
||||||
else
|
|
||||||
result = false
|
|
||||||
lockInv(false)
|
|
||||||
if data.cam then stopTempCam(data.cam) end
|
|
||||||
end
|
|
||||||
elseif Config.System.ProgressBar == "qb" then
|
|
||||||
Core.Functions.Progressbar("mechbar",
|
|
||||||
data.label,
|
|
||||||
Config.System.Debug and 1000 or data.time,
|
|
||||||
data.dead and data.dead or false,
|
|
||||||
data.cancel or true,
|
|
||||||
{ disableMovement = true, disableCarMovement = true, disableMouse = false, disableCombat = true, },
|
|
||||||
{ animDict = data.dict, anim = data.anim, flags = data.flag and data.flag or 32, task = data.task }, {}, {},
|
|
||||||
function()
|
|
||||||
result = true
|
|
||||||
lockInv(false)
|
|
||||||
if data.cam then
|
|
||||||
stopTempCam(data.cam)
|
|
||||||
end
|
|
||||||
end, function()
|
|
||||||
result = false
|
|
||||||
lockInv(false)
|
|
||||||
if data.cam then
|
|
||||||
stopTempCam(data.cam)
|
|
||||||
end
|
|
||||||
end, data.icon)
|
|
||||||
elseif Config.System.ProgressBar == "gta" then
|
|
||||||
|
|
||||||
--local wait, inProgress = (Config.System.Debug and 1000 or data.time), true
|
|
||||||
local wait = data.time
|
|
||||||
inProgress = true
|
|
||||||
if not (data.dead and data.dead or false) then
|
|
||||||
lockInv(true)
|
|
||||||
displaySpinner(data.label)
|
|
||||||
local ped = PlayerPedId()
|
|
||||||
if data.dict then
|
|
||||||
playAnim(data.dict, data.anim, -1, (data.flag == 8 and 32 or data.flag) or nil)
|
|
||||||
end
|
|
||||||
if data.task then
|
|
||||||
TaskStartScenarioInPlace(ped, data.task, -1, true)
|
|
||||||
end
|
|
||||||
while inProgress and wait > 0 do wait -= 15
|
|
||||||
local waitTimer = 0
|
|
||||||
DisablePlayerFiring(ped, true)
|
|
||||||
DisableControlAction(0, 25, true)
|
|
||||||
DisableControlAction(0, 21, true)
|
|
||||||
DisableControlAction(0, 30, true)
|
|
||||||
DisableControlAction(0, 31, true)
|
|
||||||
DisableControlAction(0, 36, true)
|
|
||||||
if data.cam ~= nil then
|
|
||||||
DisableControlAction(0, 1, true)
|
|
||||||
DisableControlAction(0, 2, true)
|
|
||||||
DisableControlAction(0, 106, true)
|
|
||||||
end
|
|
||||||
if data.cancel then
|
|
||||||
if IsControlJustReleased(0, 202) or IsControlJustReleased(0, 77) then
|
|
||||||
inProgress = false
|
|
||||||
waitTimer = 1500
|
|
||||||
displaySpinner(Loc[Config.Lan].error["cancel"])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Wait(waitTimer)
|
|
||||||
end
|
|
||||||
inProgress = false
|
|
||||||
if data.cam then stopTempCam(data.cam) end
|
|
||||||
if data.dict then stopAnim(data.dict, data.anim, ped) end
|
|
||||||
ClearPedTasks(ped)
|
|
||||||
end
|
|
||||||
lockInv(false)
|
|
||||||
stopSpinner()
|
|
||||||
result = (wait <= 0)
|
|
||||||
end
|
|
||||||
while result == nil do Wait(10) end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
function displaySpinner(text)
|
|
||||||
BeginTextCommandBusyspinnerOn('STRING')
|
|
||||||
AddTextComponentSubstringPlayerName(text)
|
|
||||||
EndTextCommandBusyspinnerOn(4)
|
|
||||||
end
|
|
||||||
|
|
||||||
function stopSpinner() if not IsDuplicityVersion() then BusyspinnerOff() end end
|
|
||||||
|
|
||||||
function stopPropgressBar()
|
|
||||||
if Config.System.ProgressBar == "ox" then
|
|
||||||
exports[OXLibExport]:cancelProgress()
|
|
||||||
elseif Config.System.ProgressBar == "qb" then
|
|
||||||
TriggerEvent("progressbar:client:cancel")
|
|
||||||
elseif Config.System.ProgressBar == "gta" then
|
|
||||||
inProgress = false
|
|
||||||
BusyspinnerOff()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- [[ OTHER ]] --
|
|
||||||
function washHands(data)
|
|
||||||
lookEnt(data.coords)
|
|
||||||
local cam = createTempCam(PlayerPedId(), data.coords)
|
|
||||||
if progressBar({
|
|
||||||
label = Loc[Config.Lan].progressbar["progress_washing"],
|
|
||||||
time = 5000,
|
|
||||||
cancel = true,
|
|
||||||
dict = "mp_arresting",
|
|
||||||
anim = "a_uncuff",
|
|
||||||
flag = 32,
|
|
||||||
icon = "fas fa-hand-holding-droplet",
|
|
||||||
cam = cam
|
|
||||||
}) then
|
|
||||||
triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
|
|
||||||
else
|
|
||||||
triggerNotify(nil, Loc[Config.Lan].error["cancel"], 'error')
|
|
||||||
end
|
|
||||||
ClearPedTasks(PlayerPedId())
|
|
||||||
end
|
|
||||||
|
|
||||||
function useToilet(data)
|
|
||||||
if data.urinal then
|
|
||||||
if progressBar({
|
|
||||||
label = "Using Urinal",
|
|
||||||
time = 5000,
|
|
||||||
cancel = true,
|
|
||||||
dict = "misscarsteal2peeing",
|
|
||||||
anim = "peeing_loop",
|
|
||||||
flag = 32
|
|
||||||
}) then
|
|
||||||
TriggerServerEvent(GetCurrentResourceName().."server:Urinal")else
|
|
||||||
lockInv(false)
|
|
||||||
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error')
|
|
||||||
end
|
|
||||||
else
|
|
||||||
TaskStartScenarioAtPosition(PlayerPedId(), "PROP_HUMAN_SEAT_CHAIR_MP_PLAYER", data.sitcoords.x, data.sitcoords.y, data.sitcoords.z, data.sitcoords[4], 0, 1, true)
|
|
||||||
if progressBar({
|
|
||||||
label = "Using Toilet",
|
|
||||||
time = 10000,
|
|
||||||
cancel = true
|
|
||||||
}) then
|
|
||||||
TriggerServerEvent(GetCurrentResourceName().."server:Urinal")
|
|
||||||
ClearPedTasks(PlayerPedId())
|
|
||||||
else
|
|
||||||
lockInv(false)
|
|
||||||
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], 'error')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
RegisterNetEvent(GetCurrentResourceName()..":server:Urinal", function()
|
|
||||||
local src = source
|
|
||||||
local Player = getPlayer(src)
|
|
||||||
local thirstamt = math.random(10,30)
|
|
||||||
local thirst = Player.thirst - thirstamt
|
|
||||||
setThirst(src, getPlayer(src).thirst - thirst)
|
|
||||||
end)
|
|
||||||
|
|
||||||
RegisterNetEvent(GetCurrentResourceName()..":server:setNeed", function(type, amount) local src = source
|
|
||||||
if type == "thirst" then
|
|
||||||
setThirst(src, amount)
|
|
||||||
elseif type == "thirst" then
|
|
||||||
setHunger(src, amount)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
function setThirst(src, thirst)
|
|
||||||
if GetResourceState(ESXExport):find("start") then
|
|
||||||
TriggerClientEvent('esx_status:add', src, 'thirst', thirst)
|
|
||||||
elseif GetResourceState(QBExport):find("start") or GetResourceState(QBXExport):find("start") then
|
|
||||||
local Player = QBCore.Functions.GetPlayer(src)
|
|
||||||
Player.Functions.SetMetaData('thirst', thirst)
|
|
||||||
TriggerClientEvent("hud:client:UpdateNeeds", src, thirst, Player.PlayerData.metadata.thirst)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function setHunger(src, hunger)
|
|
||||||
if GetResourceState(ESXExport):find("start") then
|
|
||||||
TriggerClientEvent('esx_status:add', src, 'hunger', hunger)
|
|
||||||
elseif GetResourceState(QBExport):find("start") or GetResourceState(QBXExport):find("start") then
|
|
||||||
local Player = QBCore.Functions.GetPlayer(src)
|
|
||||||
Player.Functions.SetMetaData('hunger', hunger)
|
|
||||||
TriggerClientEvent("hud:client:UpdateNeeds", src, hunger, Player.PlayerData.metadata.hunger)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function useDoor(data)
|
|
||||||
DoScreenFadeOut(500)
|
|
||||||
while not IsScreenFadedOut() do Wait(10) end
|
|
||||||
SetEntityCoords(PlayerPedId(), data.telecoords.xyz, 0, 0, 0, false)
|
|
||||||
SetEntityHeading(PlayerPedId(), data.telecoords.w)
|
|
||||||
DoScreenFadeIn(1000)
|
|
||||||
Wait(100)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- [[CONSUME]] --
|
|
||||||
function ConsumeSuccess(itemName, type)
|
|
||||||
ExecuteCommand("e c")
|
|
||||||
toggleItem(false, itemName, 1)
|
|
||||||
if GetResourceState(ESXEport):find("start") then
|
|
||||||
if Items[itemName].hunger then
|
|
||||||
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "hunger", Items[itemName].hunger * 10000)
|
|
||||||
end
|
|
||||||
if Items[itemName].thirst then
|
|
||||||
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "thirst", Items[itemName].thirst * 10000)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if Items[itemName].hunger then
|
|
||||||
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "hunger", Core.Functions.GetPlayerData().metadata["hunger"] + Items[itemName].hunger)
|
|
||||||
end
|
|
||||||
if Items[itemName].thirst then
|
|
||||||
TriggerServerEvent(GetCurrentResourceName()..":server:setNeed", "thirst", Core.Functions.GetPlayerData().metadata["thirst"] + Items[itemName].thirst)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if type == "alcohol" then alcoholCount += 1
|
|
||||||
if alcoholCount > 1 and alcoholCount < 4 then TriggerEvent("evidence:client:SetStatus", "alcohol", 200) elseif alcoholCount >= 4 then TriggerEvent("evidence:client:SetStatus", "heavyalcohol", 200) AlienEffect() end
|
|
||||||
end
|
|
||||||
if Config.RewardItem == itemName then toggleItem(true, Config.RewardPool[math.random(1, #Config.RewardPool)], 1) end
|
|
||||||
end
|
|
||||||
|
|
||||||
function addItem(item, amount, info)
|
|
||||||
TriggerServerEvent(GetCurrentResourceName()..":server:toggleItem", true, item, amount)
|
|
||||||
end
|
|
||||||
|
|
||||||
function removeItem(item, amount)
|
|
||||||
TriggerServerEvent(GetCurrentResourceName()..":server:toggleItem", false, item, amount)
|
|
||||||
end
|
|
||||||
|
|
||||||
RegisterNetEvent(GetCurrentResourceName()..":server:toggleItem", function(give, item, amount, newsrc)
|
|
||||||
local src = newsrc or source
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^3toggleItem ^2triggered^7: ^6"..(tostring(give) == "true" and "addItem" or "removeItem").."^7 - ['^8"..tostring(item).."^7'] x"..(tostring(amount) or "1"))
|
|
||||||
end
|
|
||||||
local remamount = (amount and amount or 1)
|
|
||||||
if item == nil then return end
|
|
||||||
if give == 0 or give == false then
|
|
||||||
if hasItem(item, amount and amount or 1, src) then -- check if you still have the item
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
local success = exports[OXInv]:RemoveItem(src, item, (amount and amount or 1), nil)
|
|
||||||
elseif GetResourceState(QSInv):find("start") then
|
|
||||||
local success = exports[QSInv]:RemoveItem(src, item, amount)
|
|
||||||
elseif GetResourceState(CoreInv):find("start") then
|
|
||||||
local success = exports[CoreInv]:removeItemExact('primary-'..src, item, amount)
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
local success = exports[CodeMInv]:RemoveItem(src, item, amount)
|
|
||||||
elseif GetResourceState(QBInv):find("start") then
|
|
||||||
while remamount > 0 do
|
|
||||||
if Core.Functions.GetPlayer(src).Functions.RemoveItem(item, 1) then end
|
|
||||||
remamount -= 1
|
|
||||||
end
|
|
||||||
if Config.Crafting.showItemBox then
|
|
||||||
TriggerClientEvent('inventory:client:ItemBox', src, Items[item], "remove", (amount and amount or 1))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^1Removing ^2from Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7)'")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
dupeWarn(src, item) -- if not boot the player
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local amount = amount and amount or 1
|
|
||||||
if GetResourceState(OXInv):find("start") then
|
|
||||||
local success = exports[OXInv]:AddItem(src, item, amount or 1, nil)
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^1OX^7'")
|
|
||||||
end
|
|
||||||
elseif GetResourceState(QSInv):find("start") then
|
|
||||||
local success = exports[QSInv]:AddItem(src, item, amount)
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^1QS^7'")
|
|
||||||
end
|
|
||||||
elseif GetResourceState(CoreInv):find("start") then
|
|
||||||
local success = exports[CoreInv]:addItem('primary-'..src, item, amount)
|
|
||||||
if Config.System.Debug then
|
|
||||||
print("^6Bridge^7: ^4Giving ^2Player^7(^2"..src.."^7) '^6"..Items[item].label.."^7(^2x^6"..((amount and amount or "1")).."^7) ^1Core^7'")
|
|
||||||
end
|
|
||||||
elseif GetResourceState(CodeMInv):find("start") then
|
|
||||||
local success = exports[CodeMInv]:AddItem(src, item, amount)
|
|
||||||
elseif GetResourceState(QBInv):find("start") then
|
|
||||||
if Core.Functions.GetPlayer(src).Functions.AddItem(item, amount or 1) then
|
|
||||||
--if Config.Crafting.showItemBox then
|
|
||||||
TriggerClientEvent("inventory:client:ItemBox", src, Items[item], "add", amount and amount or 1)
|
|
||||||
--end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
--Item Exploit protection
|
|
||||||
function dupeWarn(src, item)
|
|
||||||
print("^5DupeWarn^7: (^1"..tostring(src).."^7) ^2Tried to remove item ^7('^3"..item.."^7')^2 but it wasn't there^7")
|
|
||||||
if Config.System.Debug == false then
|
|
||||||
DropPlayer(src, src.." ^1Kicked for attempting to duplicate items")
|
|
||||||
end
|
|
||||||
print("^5DupeWarn:^7: (^1"..tostring(src).."^7) ^2Dropped from server for item duplicating^7")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function toggleDuty()
|
|
||||||
if GetResourceState(QBExport):find("start") then
|
|
||||||
TriggerServerEvent("QBCore:ToggleDuty")
|
|
||||||
else
|
|
||||||
onDuty = not onDuty
|
|
||||||
if onDuty then
|
|
||||||
triggerNotify(nil, "Now on duty", "success")
|
|
||||||
else
|
|
||||||
triggerNotify(nil, "Now off duty", "success")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function CheckVersion()
|
|
||||||
if IsDuplicityVersion() then
|
|
||||||
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..GetCurrentResourceName()..'.txt', function(err, newestVersion, headers)
|
|
||||||
if not newestVersion then print("Currently unable to run a version check.") return end
|
|
||||||
local currentVersion = "^3"..GetResourceMetadata(GetCurrentResourceName(), 'version'):gsub("%.", "^7.^3").."^7"
|
|
||||||
newestVersion = "^3"..newestVersion:sub(1, -2):gsub("%.", "^7.^3").."^7"
|
|
||||||
print("^6Version Check^7: ^2Running^7: "..currentVersion.." ^2Latest^7: "..newestVersion)
|
|
||||||
print(newestVersion == currentVersion and '^6You are running the latest version.^7 ('..currentVersion..')' or "^1You are currently running an outdated version^7, ^1please update^7!")
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
CheckVersion()
|
|
||||||
|
|
||||||
--Screen Effects
|
|
||||||
local alienEffect = false
|
|
||||||
function AlienEffect()
|
|
||||||
if alienEffect then return else alienEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3AlienEffect^7() ^2activated") end
|
|
||||||
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
|
|
||||||
Wait(math.random(5000, 8000))
|
|
||||||
local Ped = PlayerPedId()
|
|
||||||
local animDict = "MOVE_M@DRUNK@VERYDRUNK"
|
|
||||||
loadAnimDict(animDict)
|
|
||||||
SetPedCanRagdoll(Ped, true)
|
|
||||||
ShakeGameplayCam('DRUNK_SHAKE', 2.80)
|
|
||||||
SetTimecycleModifier("Drunk")
|
|
||||||
SetPedMovementClipset(Ped, animDict, 1)
|
|
||||||
SetPedMotionBlur(Ped, true)
|
|
||||||
SetPedIsDrunk(Ped, true)
|
|
||||||
Wait(1500)
|
|
||||||
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
|
|
||||||
Wait(13500)
|
|
||||||
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
|
|
||||||
Wait(120500)
|
|
||||||
ClearTimecycleModifier()
|
|
||||||
ResetScenarioTypesEnabled()
|
|
||||||
ResetPedMovementClipset(Ped, 0)
|
|
||||||
SetPedIsDrunk(Ped, false)
|
|
||||||
SetPedMotionBlur(Ped, false)
|
|
||||||
AnimpostfxStopAll()
|
|
||||||
ShakeGameplayCam('DRUNK_SHAKE', 0.0)
|
|
||||||
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
|
|
||||||
Wait(math.random(45000, 60000))
|
|
||||||
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFightIn")
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFight")
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFightOut")
|
|
||||||
alienEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3AlienEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
local weedEffect = false
|
|
||||||
function WeedEffect()
|
|
||||||
if weedEffect then return else weedEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3WeedEffect^7() ^2activated") end
|
|
||||||
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
|
|
||||||
Wait(math.random(3000, 20000))
|
|
||||||
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
|
|
||||||
Wait(math.random(15000, 20000))
|
|
||||||
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFightIn")
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFight")
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFightOut")
|
|
||||||
weedEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3WeedEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
local trevorEffect = false
|
|
||||||
function TrevorEffect()
|
|
||||||
if trevorEffect then return else trevorEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3TrevorEffect^7() ^2activated") end
|
|
||||||
AnimpostfxPlay("DrugsTrevorClownsFightIn", 3.0, 0)
|
|
||||||
Wait(3000)
|
|
||||||
AnimpostfxPlay("DrugsTrevorClownsFight", 3.0, 0)
|
|
||||||
Wait(30000)
|
|
||||||
AnimpostfxPlay("DrugsTrevorClownsFightOut", 3.0, 0)
|
|
||||||
AnimpostfxStop("DrugsTrevorClownsFight")
|
|
||||||
AnimpostfxStop("DrugsTrevorClownsFightIn")
|
|
||||||
AnimpostfxStop("DrugsTrevorClownsFightOut")
|
|
||||||
trevorEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3TrevorEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
local turboEffect = false
|
|
||||||
function TurboEffect()
|
|
||||||
if turboEffect then return else turboEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3TurboEffect^7() ^2activated") end
|
|
||||||
AnimpostfxPlay('RaceTurbo', 0, true)
|
|
||||||
SetTimecycleModifier('rply_motionblur')
|
|
||||||
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
|
|
||||||
Wait(30000)
|
|
||||||
StopGameplayCamShaking(true)
|
|
||||||
SetTransitionTimecycleModifier('default', 0.35)
|
|
||||||
Wait(1000)
|
|
||||||
ClearTimecycleModifier()
|
|
||||||
AnimpostfxStop('RaceTurbo')
|
|
||||||
turboEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3TurboEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
local rampageEffect = false
|
|
||||||
function RampageEffect()
|
|
||||||
if rampageEffect then return else rampageEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3RampageEffect^7() ^2activated") end
|
|
||||||
AnimpostfxPlay('Rampage', 0, true)
|
|
||||||
SetTimecycleModifier('rply_motionblur')
|
|
||||||
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
|
|
||||||
Wait(30000)
|
|
||||||
StopGameplayCamShaking(true)
|
|
||||||
SetTransitionTimecycleModifier('default', 0.35)
|
|
||||||
Wait(1000)
|
|
||||||
ClearTimecycleModifier()
|
|
||||||
AnimpostfxStop('Rampage')
|
|
||||||
rampageEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3RampageEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
local focusEffect = false
|
|
||||||
function FocusEffect()
|
|
||||||
if focusEffect then return else focusEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3FocusEffect^7() ^2activated") end
|
|
||||||
Wait(1000)
|
|
||||||
AnimpostfxPlay('FocusIn', 0, true)
|
|
||||||
Wait(30000)
|
|
||||||
AnimpostfxStop('FocusIn')
|
|
||||||
focusEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3FocusEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
local NightVisionEffect = false
|
|
||||||
function NightVisionEffect()
|
|
||||||
if NightVisionEffect then return else NightVisionEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3NightVisionEffect^7() ^2activated") end
|
|
||||||
SetNightvision(true)
|
|
||||||
Wait(math.random(3000, 4000)) -- FEEL FREE TO CHANGE THIS
|
|
||||||
SetNightvision(false)
|
|
||||||
SetSeethrough(false)
|
|
||||||
NightVisionEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3NightVisionEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
local thermalEffect = false
|
|
||||||
function ThermalEffect()
|
|
||||||
if thermalEffect then return else thermalEffect = true end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3ThermalEffect^7() ^2activated") end
|
|
||||||
SetNightvision(true)
|
|
||||||
SetSeethrough(true)
|
|
||||||
Wait(math.random(2000, 3000)) -- FEEL FREE TO CHANGE THIS
|
|
||||||
SetNightvision(false)
|
|
||||||
SetSeethrough(false)
|
|
||||||
thermalEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3ThermalEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
|
|
||||||
--Built-in Buff effects
|
|
||||||
local healEffect = false
|
|
||||||
function HealEffect(data)
|
|
||||||
if healEffect then return end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3HealEffect^7() ^2activated") end
|
|
||||||
healEffect = true
|
|
||||||
local count = (data[1] / 1000)
|
|
||||||
while count > 0 do
|
|
||||||
Wait(1000)
|
|
||||||
count -= 1
|
|
||||||
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + data[2])
|
|
||||||
end
|
|
||||||
healEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3HealEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
|
|
||||||
local staminaEffect = false
|
|
||||||
function StaminaEffect(data)
|
|
||||||
if staminaEffect then return end
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3StaminaEffect^7() ^2activated") end
|
|
||||||
staminaEffect = true
|
|
||||||
local startStamina = (data[1] / 1000)
|
|
||||||
SetRunSprintMultiplierForPlayer(PlayerId(), 1.49)
|
|
||||||
while startStamina > 0 do
|
|
||||||
Wait(1000)
|
|
||||||
if math.random(5, 100) < 10 then RestorePlayerStamina(PlayerId(), data[2]) end
|
|
||||||
startStamina -= 1
|
|
||||||
if math.random(5, 100) < 51 then end
|
|
||||||
end
|
|
||||||
startStamina = 0
|
|
||||||
SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
|
|
||||||
staminaEffect = false
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^3StaminaEffect^7() ^2stopped") end
|
|
||||||
end
|
|
||||||
|
|
||||||
function StopEffects() -- Used to clear up any effects stuck on screen
|
|
||||||
if Config.System.Debug then print("^5Debug^7: ^2All screen effects stopped") end
|
|
||||||
ShakeGameplayCam('DRUNK_SHAKE', 0.0)
|
|
||||||
SetPedToRagdoll(PlayerPedId(), 5000, 1000, 1, 0, 0, 0)
|
|
||||||
ClearTimecycleModifier()
|
|
||||||
ResetScenarioTypesEnabled()
|
|
||||||
ResetPedMovementClipset(PlayerPedId(), 0)
|
|
||||||
SetPedIsDrunk(PlayerPedId(), false)
|
|
||||||
SetPedMotionBlur(PlayerPedId(), false)
|
|
||||||
SetNightvision(false)
|
|
||||||
SetSeethrough(false)
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFightIn")
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFight")
|
|
||||||
AnimpostfxStop("DrugsMichaelAliensFightOut")
|
|
||||||
AnimpostfxStop("DrugsTrevorClownsFight")
|
|
||||||
AnimpostfxStop("DrugsTrevorClownsFightIn")
|
|
||||||
AnimpostfxStop("DrugsTrevorClownsFightOut")
|
|
||||||
AnimpostfxStop('RaceTurbo')
|
|
||||||
AnimpostfxStop('FocusIn')
|
|
||||||
AnimpostfxStop('Rampage')
|
|
||||||
end
|
|
||||||
+25
-7
@@ -1,14 +1,32 @@
|
|||||||
name "Jim_Bridge"
|
name "Jim_Bridge"
|
||||||
author "Jimathy"
|
author "Jimathy"
|
||||||
version "help"
|
version "2.1.09"
|
||||||
description "Framework Bridge By Jimathy"
|
description "Framework Bridge By Jimathy"
|
||||||
fx_version "cerulean"
|
fx_version "cerulean"
|
||||||
game "gta5"
|
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
|
||||||
|
games { 'gta5', 'rdr3' }
|
||||||
lua54 'yes'
|
lua54 'yes'
|
||||||
|
|
||||||
files {
|
files {
|
||||||
'exports.lua',
|
'starter.lua',
|
||||||
'functions.lua',
|
'shared/*.lua',
|
||||||
'wrapper.lua',
|
'shared/make/*.lua',
|
||||||
'crafting.lua',
|
'shared/auth/*.lua',
|
||||||
}
|
'shared/make/*.lua',
|
||||||
|
'shared/modules/*.lua',
|
||||||
|
'shared/scaleforms/*.lua',
|
||||||
|
'shared/wrappers/*.lua',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Version checker
|
||||||
|
server_scripts {
|
||||||
|
'frameworkCache.lua',
|
||||||
|
'_versioncheck.lua',
|
||||||
|
}
|
||||||
|
|
||||||
|
client_scripts {
|
||||||
|
'clientFrameworkCache.lua',
|
||||||
|
'ui_modules/*.lua',
|
||||||
|
}
|
||||||
|
|
||||||
|
suppress_updates 'false' -- set to 'true' to disable update pings
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
-------------------------------------------------------------
|
||||||
|
-- Exploit Auth System
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
forceDisableExplotProtection = false -- dangerous, this allows exploits
|
||||||
|
AuthEvent = nil
|
||||||
|
currentToken = nil
|
||||||
|
if isServer() then
|
||||||
|
|
||||||
|
local excludeRes = {
|
||||||
|
[Exports.QBExport] = true,
|
||||||
|
[Exports.ESXExport] = true,
|
||||||
|
[Exports.VorpExport] = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
local AuthEvent = getScript()..":"..keyGen()..keyGen()..keyGen()..keyGen()..":"..keyGen()..keyGen()..keyGen()..keyGen()
|
||||||
|
validTokens = validTokens or {}
|
||||||
|
|
||||||
|
createCallback(AuthEvent, function(source)
|
||||||
|
local src = source
|
||||||
|
local token = keyGen()..keyGen()..keyGen()..keyGen() -- Use a secure random generator here
|
||||||
|
local invokingRes = GetInvokingResource()
|
||||||
|
|
||||||
|
debugPrint(invokingRes)
|
||||||
|
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then
|
||||||
|
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
debugPrint("^1Auth^7:^2 Player Source^7: "..src.." ^2requested new token^7:", token)
|
||||||
|
validTokens[src] = token
|
||||||
|
timeOutAuth(validTokens[src], src) -- Give script 10 seconds, then clear token
|
||||||
|
return token
|
||||||
|
end)
|
||||||
|
|
||||||
|
function timeOutAuth(token, src)
|
||||||
|
local token = token
|
||||||
|
SetTimeout(10000, function()
|
||||||
|
if token == validTokens[src] then
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
print("^7Clearing token for player ^1"..src.."^7", token)
|
||||||
|
print("^7This shouldn't happen unless a token has been called by a player or script and it hasn't been used")
|
||||||
|
print("^1--------------------------------------------^7")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterNetEvent(getScript()..":clearAuthToken", function()
|
||||||
|
local src = source
|
||||||
|
debugPrint("^1Auth^7: ^2Manually removing token for Player Source^7:", src, validTokens[src])
|
||||||
|
validTokens[src] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
receivedEvent = {}
|
||||||
|
authCooldown = {}
|
||||||
|
|
||||||
|
createCallback(getScript()..":callback:GetAuthEvent", function(source)
|
||||||
|
local src = source
|
||||||
|
local invokingRes = GetInvokingResource()
|
||||||
|
|
||||||
|
if invokingRes and invokingRes ~= getScript() and not excludeRes[invokingRes] then
|
||||||
|
debugPrint("^1Error^7: ^1Possible exploit^7, ^1vital callback was called from an external resource^7")
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
if authCooldown[src] then
|
||||||
|
debugPrint("^1Auth^7: ^3Cooldown active^7 for Player ^1"..src.."^7, ignoring additional auth request")
|
||||||
|
return AuthEvent
|
||||||
|
end
|
||||||
|
|
||||||
|
debugPrint("^1Auth^7: ^2Player Source^7: "..src.." ^2requested ^3AuthEvent^7", AuthEvent)
|
||||||
|
|
||||||
|
authCooldown[src] = true
|
||||||
|
SetTimeout(60000, function() -- 1 minute cooldown
|
||||||
|
authCooldown[src] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
if not receivedEvent[src] then
|
||||||
|
receivedEvent[src] = true
|
||||||
|
return AuthEvent
|
||||||
|
else
|
||||||
|
print("^1Auth^7: ^1Player ^7"..src.." ^1tried to request auth token more than once^7")
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent(getScript()..":clearAuthEventRequest", function()
|
||||||
|
local src = source
|
||||||
|
debugPrint("^1Auth^7: ^2Manually clearing Auth Event for Player Source^7:", src, AuthEvent)
|
||||||
|
receivedEvent[src] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Multiuse function to check if the generated client token is valid
|
||||||
|
function checkToken(src, token, genType, name)
|
||||||
|
if forceDisableExplotProtection == true then return true end
|
||||||
|
|
||||||
|
if token == nil then
|
||||||
|
debugPrint("^1Auth^7: ^1No token recieved^7")
|
||||||
|
if genType == "stash" then
|
||||||
|
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1without an auth token^7")
|
||||||
|
elseif genType == "item" then
|
||||||
|
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1without an auth token^7")
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
debugPrint("^1Auth^7: ^2Auth token received^7, ^2checking against server cache^7..")
|
||||||
|
if token ~= validTokens[src] then
|
||||||
|
debugPrint("^1Auth^7: ^1Tokens don't match! ^7", token, validTokens[src])
|
||||||
|
if genType == "stash" then
|
||||||
|
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1create a stash ^7"..name.." ^1with an incorrect auth token^7")
|
||||||
|
elseif genType == "item" then
|
||||||
|
dupeWarn(src, name, "^1Auth Error^7: ^3"..src.." ^1attempted to spawn an item ^7"..name.." ^1with an incorrect auth token^7")
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
debugPrint("^1Auth^7: ^2Client and Server Auth tokens match^7!", token, validTokens[src])
|
||||||
|
validTokens[src] = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
onPlayerLoaded(function()
|
||||||
|
debugPrint("^1Auth^7: ^2Requesting ^3Auth Event^7")
|
||||||
|
AuthEvent = triggerCallback(getScript()..":callback:GetAuthEvent")
|
||||||
|
end, true)
|
||||||
|
|
||||||
|
onPlayerUnload(function()
|
||||||
|
debugPrint("^1Auth^7: ^2Clearing Auth Event^7")
|
||||||
|
TriggerServerEvent(getScript()..":clearAuthEventRequest")
|
||||||
|
end, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function distExploitCheck(table, src)
|
||||||
|
if forceDisableExplotProtection == true then return true end
|
||||||
|
if not table then
|
||||||
|
print("^1Error^7: ^1This wasn^7'^1t reigstered correctly or this is an exploit attempt^1")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local maxDist = 10.0
|
||||||
|
local nearestDist = 999999.0
|
||||||
|
local nearestCoords = nil
|
||||||
|
|
||||||
|
local ped = src and GetPlayerPed(src) or PlayerPedId()
|
||||||
|
local srcCoords = GetEntityCoords(ped)
|
||||||
|
|
||||||
|
for i = 1, #table do
|
||||||
|
|
||||||
|
local dist2d = #(table[i].xy - srcCoords.xy)
|
||||||
|
|
||||||
|
-- Track the nearestDist
|
||||||
|
if dist2d < nearestDist then
|
||||||
|
nearestDist = dist2d
|
||||||
|
nearestCoords = table[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
if dist2d <= maxDist then
|
||||||
|
debugPrint("^1Found location^7: ^3"..nearestDist.." ^7away from player - "..formatCoord(nearestCoords))
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print(src and ("^1Src ^3"..src.." ") or "", "^1Tried to open a registered shop/stash from over the distance limit^7")
|
||||||
|
print("^1Nearest Possible Location^7: ^3"..nearestDist.." ^7away from player - "..formatCoord(nearestCoords))
|
||||||
|
return false
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
-- IN NO WAY PERFECT -- ** Experimental debugging
|
||||||
|
function toggleDebug()
|
||||||
|
Config.System.Debug = not Config.System.Debug
|
||||||
|
print("Debug Prints = "..tostring(Config.System.Debug))
|
||||||
|
end
|
||||||
|
exports("toggleDebug", toggleDebug)
|
||||||
|
|
||||||
|
function getDebug() return Config.System.Debug end
|
||||||
|
exports("getDebug", getDebug)
|
||||||
|
|
||||||
|
local origRegisterNetEvent = RegisterNetEvent
|
||||||
|
local origTriggerEvent = TriggerEvent
|
||||||
|
local origTriggerServerEvent = TriggerServerEvent
|
||||||
|
local origTriggerClientEvent = TriggerClientEvent
|
||||||
|
local origExecuteCommand = ExecuteCommand
|
||||||
|
local origRegisterCommand = RegisterCommand
|
||||||
|
local origPairs = pairs
|
||||||
|
local origiPairs = ipairs
|
||||||
|
|
||||||
|
function getDebugInfo(info)
|
||||||
|
local info = info
|
||||||
|
local level = 2
|
||||||
|
if info and info.short_src:match("scheduler.lua") then
|
||||||
|
repeat
|
||||||
|
info = debug.getinfo(level, "nSl")
|
||||||
|
level += 1
|
||||||
|
local found = false
|
||||||
|
for _, v in pairs({
|
||||||
|
"deffered.lua",
|
||||||
|
"scheduler.lua",
|
||||||
|
"_eventDebug.lua",
|
||||||
|
"targets.lua",
|
||||||
|
"init.lua",
|
||||||
|
"MySQL.lua",
|
||||||
|
"helpers.lua",
|
||||||
|
}) do
|
||||||
|
if info and info.short_src:match(v) then
|
||||||
|
found = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
until not info or (info.short_src and found == false)
|
||||||
|
end
|
||||||
|
|
||||||
|
return " ^7[^3"..(info and info.short_src:match("^.+/(.+)$") or "unknown").."^7:^3"..(info and info.currentline or "unknown").."^7]"
|
||||||
|
end
|
||||||
|
|
||||||
|
--This is just a for debugging, not important, just announces which events are being registered triggered when these functions are used
|
||||||
|
function RegisterNetEvent(name, funct)
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
if name:find("__ox_cb_") then
|
||||||
|
print("^6Bridge^7: ^2Registered ^3"..(isServer() and "Server" or "Client").." ^2Callback^7: ^6"..name:gsub("__ox_cb_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
else
|
||||||
|
print("^6Bridge^7: ^2Registering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
origRegisterNetEvent(name, funct)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TriggerEvent(name, ...)
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
local data = {...}
|
||||||
|
if name:find("__cfx_export") then
|
||||||
|
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Export^7: ^6"..name:gsub("__cfx_export_", ""):gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
else
|
||||||
|
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3"..(isServer() and "Server" or "Client").." ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
for i, value in ipairs(data) do
|
||||||
|
if value then
|
||||||
|
local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
|
||||||
|
print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
|
||||||
|
end
|
||||||
|
end end
|
||||||
|
origTriggerEvent(name, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TriggerServerEvent(name, ...) -- Client side, trigger a server event
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
local data = {...}
|
||||||
|
if name:find("__ox_cb") then
|
||||||
|
print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Server ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
else
|
||||||
|
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Server ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
for i, value in ipairs(data) do
|
||||||
|
if value then
|
||||||
|
local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
|
||||||
|
print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
|
||||||
|
end
|
||||||
|
end end
|
||||||
|
origTriggerServerEvent(name, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function TriggerClientEvent(name, ...) -- Server side, trigger a client event
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
local data = {...}
|
||||||
|
if name:find("__ox_cb") then
|
||||||
|
print("^6Bridge^7: "..GetPrintTime().." ^2Triggered ^3Client ^2Callback: ^6"..name:gsub("__ox_cb_", "").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
else
|
||||||
|
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3Client ^2Net event^7: ^6"..name:gsub("%:", "^7:^4").."^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
for i, value in ipairs(data) do
|
||||||
|
if value then
|
||||||
|
local valueStr = (type(value) == "table" and json.encode(value) or tostring(value))
|
||||||
|
print(string.format("^6Bridge^7: ^7[^3%d^7]: ^7(^5%s^7): %s".."^7", i, type(value), valueStr))
|
||||||
|
end
|
||||||
|
end end
|
||||||
|
origTriggerClientEvent(name, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function RegisterCommand(command, funct, restrict)
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
print("^6Bridge^7: ^2Registering ^2Command^7: /"..command.." ^7| ^4Funct^7: "..tostring(funct):gsub("function: ", "").." ^7| ^4Admin^7: "..(restict and "true" or "false")..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
origRegisterCommand(command, funct, restrict)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ExecuteCommand(comm) -- Client side, execute /command
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
print("^6Bridge^7: "..GetPrintTime().." ^2Triggering ^3ExecuteCommand^7: /"..comm..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
origExecuteCommand(comm)
|
||||||
|
end
|
||||||
|
|
||||||
|
function pairs(tbl)
|
||||||
|
if not tbl then
|
||||||
|
print("^1Error^7: ^1nil ^2for ^3pairs^7(), ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
return origPairs({})
|
||||||
|
end
|
||||||
|
return origPairs(tbl)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ipairs(tbl)
|
||||||
|
local tbl = tbl
|
||||||
|
if not tbl then
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
print("^1Error^7: ^3iPairs^7() ^1nil ^2recieved^7, ^2setting to ^7{} ^2to prevent break^7"..getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
tbl = {}
|
||||||
|
end
|
||||||
|
return pairsByKeys(tbl) -- change to pairsByKeys for less errors
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
local origPrint = print
|
||||||
|
function print(...)
|
||||||
|
origPrint(getDebugInfo(debug.getinfo(2, "nSl"))..":")
|
||||||
|
origPrint(...)
|
||||||
|
end
|
||||||
|
]]
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
--[[
|
||||||
|
Player & Resource Event Utility Functions
|
||||||
|
-------------------------------------------
|
||||||
|
This module provides functions to:
|
||||||
|
• Execute code when the player character is loaded or unloaded.
|
||||||
|
• Execute code on resource start and stop.
|
||||||
|
• Wait for the player to be logged in before proceeding.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local onLoadLast = {} -- keyed by tostring(func)
|
||||||
|
|
||||||
|
local function _debouncedRun(func)
|
||||||
|
local key = tostring(func)
|
||||||
|
local now = GetGameTimer()
|
||||||
|
local last = onLoadLast[key] or -1e12
|
||||||
|
|
||||||
|
if (now - last) < 5000 then
|
||||||
|
debugPrint(("^6Bridge^7 ^3onPlayerLoaded^7 skipped — cooldown %dms remaining"):format(5000 - (now - last)))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
onLoadLast[key] = now
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(2000) -- keep your small delay
|
||||||
|
debugPrint("^6Bridge^7: ^2Executing onPlayerLoaded callback")
|
||||||
|
func()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local frameworkLoadFunc = {
|
||||||
|
{ framework = Exports.QBXExport,
|
||||||
|
onPlayerLoaded =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', func)
|
||||||
|
end,
|
||||||
|
onPlayerUnload =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('QBCore:Client:OnPlayerUnload', func)
|
||||||
|
end,
|
||||||
|
waitforLogin =
|
||||||
|
function(timeout)
|
||||||
|
local startTime = GetGameTimer()
|
||||||
|
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
return LocalPlayer.state.isLoggedIn
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = Exports.QBExport,
|
||||||
|
onPlayerLoaded =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', func)
|
||||||
|
end,
|
||||||
|
onPlayerUnload =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('QBCore:Client:OnPlayerUnload', func)
|
||||||
|
end,
|
||||||
|
waitforLogin =
|
||||||
|
function(timeout)
|
||||||
|
local startTime = GetGameTimer()
|
||||||
|
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
return LocalPlayer.state.isLoggedIn
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = Exports.ESXExport,
|
||||||
|
onPlayerLoaded =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent("esx:playerLoaded", function()
|
||||||
|
-- make sure shared has loaded (because sql)
|
||||||
|
if waitForSharedLoad() then func() end
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
onPlayerUnload =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent("esx:onPlayerLogout", func)
|
||||||
|
end,
|
||||||
|
waitforLogin =
|
||||||
|
function(timeout)
|
||||||
|
local startTime = GetGameTimer()
|
||||||
|
while (GetGameTimer() - startTime) < timeout do
|
||||||
|
local playerData = ESX.GetPlayerData()
|
||||||
|
if playerData and playerData.job then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = Exports.OXCoreExport,
|
||||||
|
onPlayerLoaded =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('ox:playerLoaded', func)
|
||||||
|
end,
|
||||||
|
onPlayerUnload =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('ox:playerLogout', func)
|
||||||
|
end,
|
||||||
|
waitforLogin =
|
||||||
|
function(timeout)
|
||||||
|
if OxPlayer["stateId"] then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
while not OxPlayer["stateId"] do
|
||||||
|
Wait(1000)
|
||||||
|
if OxPlayer.get["stateId"] then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = Exports.RSGExport,
|
||||||
|
onPlayerLoaded =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('RSGCore:Client:OnPlayerLoaded', func)
|
||||||
|
end,
|
||||||
|
onPlayerUnload =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('RSGCore:Client:OnPlayerUnload', func)
|
||||||
|
end,
|
||||||
|
waitforLogin =
|
||||||
|
function(timeout)
|
||||||
|
local startTime = GetGameTimer()
|
||||||
|
while not LocalPlayer.state.isLoggedIn and (GetGameTimer() - startTime) < timeout do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
return LocalPlayer.state.isLoggedIn
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = Exports.VorpExport,
|
||||||
|
onPlayerLoaded =
|
||||||
|
function(func)
|
||||||
|
RegisterNetEvent('vorp_core:Client:OnPlayerSpawned', func)
|
||||||
|
end,
|
||||||
|
onPlayerUnload =
|
||||||
|
function(func)
|
||||||
|
--??
|
||||||
|
end,
|
||||||
|
waitforLogin =
|
||||||
|
function(timeout)
|
||||||
|
local startTime = GetGameTimer()
|
||||||
|
while not LocalPlayer.state.IsInSession and (GetGameTimer() - startTime) < timeout do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
return LocalPlayer.state.IsInSession
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Player Loaded and Unloaded Events
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Executes a function when the player character is loaded.
|
||||||
|
--- If onStart is true, the function will also run on resource start (after ensuring the player is logged in).
|
||||||
|
---
|
||||||
|
--- @param func function The function to execute when the player is loaded.
|
||||||
|
--- @param onStart boolean (optional) If true, also execute on resource start. Default is false.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- onPlayerLoaded(function()
|
||||||
|
--- print("Player logged in")
|
||||||
|
--- -- Your initialization code here.
|
||||||
|
--- end, true)
|
||||||
|
--- ```
|
||||||
|
function onPlayerLoaded(func, onStart)
|
||||||
|
if onStart then
|
||||||
|
onResourceStart(function()
|
||||||
|
if not waitForLogin() then return end
|
||||||
|
debugPrint("^6Bridge^7: ^3onResourceStart^7()^2 routed through ^3onPlayerLoaded^7()")
|
||||||
|
_debouncedRun(func)
|
||||||
|
end, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local handler = function()
|
||||||
|
_debouncedRun(func)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #frameworkLoadFunc do
|
||||||
|
local data = frameworkLoadFunc[i]
|
||||||
|
jsonPrint(data)
|
||||||
|
if isStarted(data.framework) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^3"..data.framework.." ^5onPlayerLoaded^7()")
|
||||||
|
data.onPlayerLoaded(handler)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("^1ERROR^7: ^1No supported core detected for onPlayerLoaded - Check starter.lua^7")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Executes a function when the player character is unloaded.
|
||||||
|
--- @param func function The function to execute when the player unloads.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- onPlayerUnload(function()
|
||||||
|
--- print("Player has logged out of their character")
|
||||||
|
--- -- Your cleanup code here.
|
||||||
|
--- end)
|
||||||
|
--- ```
|
||||||
|
function onPlayerUnload(func)
|
||||||
|
for i = 1, #frameworkLoadFunc do
|
||||||
|
local data = frameworkLoadFunc[i]
|
||||||
|
if isStarted(data.framework) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^3"..data.framework.." ^5onPlayerUnload^7()")
|
||||||
|
data.onPlayerUnload(func)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("^1ERROR^7: ^1No supported core detected for onPlayerUnload - Check starter.lua^7")
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Resource Start and Stop Events
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Executes a function when the resource starts.
|
||||||
|
--- @param func function The function to execute.
|
||||||
|
--- @param thisScript boolean (optional) If true, only runs when this resource starts (default true).
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- onResourceStart(function()
|
||||||
|
--- print("Script ensured")
|
||||||
|
--- -- Initialization code on resource start.
|
||||||
|
--- end, true)
|
||||||
|
--- ```
|
||||||
|
local hasPrinted = false
|
||||||
|
function onResourceStart(func, thisScript)
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStart^7()")
|
||||||
|
AddEventHandler('onResourceStart', function(resourceName)
|
||||||
|
if getScript() == resourceName and (thisScript or true) then
|
||||||
|
if waitForSharedLoad() then
|
||||||
|
if not hasPrinted then
|
||||||
|
debugPrint("^6Bridge^7: ^2Shared Load Detected^7.")
|
||||||
|
hasPrinted = true
|
||||||
|
end
|
||||||
|
func(resourceName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Executes a function when the resource stops.
|
||||||
|
--- @param func function The function to execute.
|
||||||
|
--- @param thisScript boolean (optional) If true, only runs when this resource stops (default true).
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- onResourceStop(function()
|
||||||
|
--- -- Cleanup code here.
|
||||||
|
--- end, true)
|
||||||
|
--- ```
|
||||||
|
function onResourceStop(func, thisScript)
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^3onResourceStop^7()")
|
||||||
|
AddEventHandler('onResourceStop', function(resourceName)
|
||||||
|
if getScript() == resourceName and (thisScript or true) then
|
||||||
|
func(resourceName)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Wait for Login
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Blocks execution until the player is logged in.
|
||||||
|
--- @usage
|
||||||
|
--- waitForLogin()
|
||||||
|
function waitForLogin()
|
||||||
|
for i = 1, #frameworkLoadFunc do
|
||||||
|
local data = frameworkLoadFunc[i]
|
||||||
|
if isStarted(data.framework) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Waiting for ^3"..data.framework.."^2 player login^7.")
|
||||||
|
local result = data.waitforLogin(10000)
|
||||||
|
if result == true then
|
||||||
|
debugPrint("^6Bridge^7: ^3"..data.framework.."^2 Player Login Detected^7.")
|
||||||
|
else
|
||||||
|
print("^4Error^7: ^2Timeout reached while waiting for player login^7.")
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local messageShown = false
|
||||||
|
function waitForSharedLoad()
|
||||||
|
local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
|
||||||
|
local loop = 0
|
||||||
|
|
||||||
|
while ((not Jobs or not next(Jobs)) or
|
||||||
|
(not Items or not next(Items)) or
|
||||||
|
(not Vehicles or not next(Vehicles))
|
||||||
|
) and (timeout and GetGameTimer() < timeout) do
|
||||||
|
if loop >= 3 and not messageShown then
|
||||||
|
if (not Jobs or not next(Jobs)) then
|
||||||
|
print("^4Debug^7: ^2Waiting for ^7Jobs^2 to be loaded")
|
||||||
|
end
|
||||||
|
if (not Items or not next(Items)) then
|
||||||
|
print("^4Debug^7: ^2Waiting for ^7Items^2 to be loaded")
|
||||||
|
end
|
||||||
|
if (not Vehicles or not next(Vehicles)) then
|
||||||
|
print("^4Debug^7: ^2Waiting for ^7Vehicles^2 to be loaded")
|
||||||
|
end
|
||||||
|
messageShown = true
|
||||||
|
end
|
||||||
|
Wait(1000)
|
||||||
|
|
||||||
|
loop += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if Jobs and Items and Vehicles then
|
||||||
|
debugPrint("^6Bridge^7: ^2Jobs, Items, and Vehicles Loaded^7.")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
print("^4Error^7: ^1Timeout reached while waiting for shared load^7.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
local function readBoolMeta(key, default)
|
||||||
|
local val = GetResourceMetadata("jim_bridge", key, 0)
|
||||||
|
if not val then return default end
|
||||||
|
val = tostring(val):lower()
|
||||||
|
return (val == 'true' or val == '1' or val == 'yes' or val == 'on')
|
||||||
|
end
|
||||||
|
|
||||||
|
local SUPPRESS_UPDATES = readBoolMeta('suppress_updates', false)
|
||||||
|
|
||||||
|
if not SUPPRESS_UPDATES then
|
||||||
|
function parseVersion(version)
|
||||||
|
local parts = {}
|
||||||
|
for num in version:gmatch("%d+") do
|
||||||
|
table.insert(parts, tonumber(num))
|
||||||
|
end
|
||||||
|
return parts
|
||||||
|
end
|
||||||
|
|
||||||
|
function compareVersions(current, newest)
|
||||||
|
local currentParts = parseVersion(current)
|
||||||
|
local newestParts = parseVersion(newest)
|
||||||
|
for i = 1, math.max(#currentParts, #newestParts) do
|
||||||
|
local c = currentParts[i] or 0
|
||||||
|
local n = newestParts[i] or 0
|
||||||
|
if c < n then return -1
|
||||||
|
elseif c > n then return 1 end
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function capitalize(str)
|
||||||
|
return (str:gsub("^%l", string.upper):gsub("%-%l", function(letter) return "-" .. string.upper(letter:sub(2)) end))
|
||||||
|
end
|
||||||
|
|
||||||
|
local scriptName = ("^2"..capitalize(getScript()):gsub("%-", "^7-^2"):gsub("%_", "^7_^2")) or ""
|
||||||
|
local rawVersion = GetResourceMetadata(getScript(), 'version', nil) or "?"
|
||||||
|
local scriptVersion = ("^5"..rawVersion:gsub("%.", "^7.^5")) or ""
|
||||||
|
local scriptDescription = ("^2"..(GetResourceMetadata(getScript(), 'description', nil) or "")) or ""
|
||||||
|
local scriptAuthor = ("^2by ^4"..GetResourceMetadata(getScript(), 'author', nil):gsub("%and", "^7and^4")) or ""
|
||||||
|
|
||||||
|
print(scriptName.." ^7v"..scriptVersion.."^7 - "..scriptDescription.." "..scriptAuthor.."^7")
|
||||||
|
|
||||||
|
function CheckVersion()
|
||||||
|
if isServer() and GetResourceMetadata(getScript(), 'author', nil) == "Jimathy" then
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(4000)
|
||||||
|
local script = getScript()
|
||||||
|
local currentVersionRaw = GetResourceMetadata(script, 'version')
|
||||||
|
|
||||||
|
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/UpdateVersions/master/'..script..'.txt', function(err, newestVersionRaw, headers)
|
||||||
|
if not newestVersionRaw then
|
||||||
|
-- fallback
|
||||||
|
PerformHttpRequest('https://raw.githubusercontent.com/jimathy/'..script..'/master/version.txt', function(err, fallbackVersionRaw, headers)
|
||||||
|
if not fallbackVersionRaw then
|
||||||
|
print("^1Currently unable to run a version check for ^7'^3"..script.."^7' (^3"..currentVersionRaw.."^7)")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local lines = {}
|
||||||
|
for line in fallbackVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
|
||||||
|
local fallbackVersion = (lines[1] or "0.0.0"):gsub("v", "")
|
||||||
|
local changelog = {}
|
||||||
|
for i = 2, #lines do table.insert(changelog, lines[i]) end
|
||||||
|
|
||||||
|
local compareResult = compareVersions(currentVersionRaw, fallbackVersion)
|
||||||
|
if compareResult == 0 then
|
||||||
|
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
|
||||||
|
elseif compareResult < 0 then
|
||||||
|
print("^1----------------------------------------------------------------------^7")
|
||||||
|
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..fallbackVersion.."^7)")
|
||||||
|
if #changelog > 0 then
|
||||||
|
for _, line in ipairs(changelog) do
|
||||||
|
print((line:find("http") and "^7" or "^5")..line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("^1----------------------------------------------------------------------^7")
|
||||||
|
SetTimeout(1200000, function() CheckVersion() end)
|
||||||
|
else
|
||||||
|
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..fallbackVersion.."^7)")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
local lines = {}
|
||||||
|
for line in newestVersionRaw:gmatch("[^\r\n]+") do table.insert(lines, line) end
|
||||||
|
local newestVersion = (lines[1] or "0.0.0"):gsub("v", "")
|
||||||
|
local changelog = {}
|
||||||
|
for i = 2, #lines do table.insert(changelog, lines[i]) end
|
||||||
|
|
||||||
|
local compareResult = compareVersions(currentVersionRaw, newestVersion)
|
||||||
|
if compareResult == 0 then
|
||||||
|
print("^7'^3"..script.."^7' - ^2You are running the latest version^7. (^3"..currentVersionRaw.."^7)")
|
||||||
|
elseif compareResult < 0 then
|
||||||
|
print("^1----------------------------------------------------------------------^7")
|
||||||
|
print("^7'^3"..script.."^7' - ^1You are currently running an outdated version^7! (^3"..currentVersionRaw.."^7 → ^3"..newestVersion.."^7)")
|
||||||
|
if #changelog > 0 then
|
||||||
|
for _, line in ipairs(changelog) do
|
||||||
|
print((line:find("http") and "^7" or "^5")..line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("^1----------------------------------------------------------------------^7")
|
||||||
|
SetTimeout(3600000, function() CheckVersion() end)
|
||||||
|
else
|
||||||
|
print("^7'^3"..script.."^7' - ^5You are running a newer version ^7(^3"..currentVersionRaw.."^7 ← ^3"..newestVersion.."^7)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CheckVersion()
|
||||||
|
end
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
local CALLBACK_RETRIES = 0
|
||||||
|
|
||||||
|
-- Internal: await a callback with a timeout
|
||||||
|
local function awaitWithTimeout(registerFn, timeoutMs)
|
||||||
|
local p = promise.new()
|
||||||
|
local finished = false
|
||||||
|
|
||||||
|
registerFn(function(result)
|
||||||
|
if finished then return end
|
||||||
|
finished = true
|
||||||
|
p:resolve(result)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- timeout watchdog
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(5000)
|
||||||
|
if not finished then
|
||||||
|
finished = true
|
||||||
|
p:reject('timeout')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
local ok, res = pcall(function() return Citizen.Await(p) end)
|
||||||
|
if ok then return res, nil end
|
||||||
|
return nil, res
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Registers a callback function with the appropriate framework.
|
||||||
|
---
|
||||||
|
--- This function checks which framework is started (e.g., OX, QB, ESX) and registers the callback accordingly.
|
||||||
|
--- It adapts the callback function to match the expected signature for the framework.
|
||||||
|
---
|
||||||
|
---@param callbackName string The name of the callback to register.
|
||||||
|
---@param funct function The function to be called when the callback is triggered.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local table = { ["info"] = "HI" }
|
||||||
|
--- createCallback('myCallback', function(source, ...)
|
||||||
|
--- return table
|
||||||
|
--- end)
|
||||||
|
---
|
||||||
|
--- createCallback("callback:checkVehicleOwned", function(source, plate)
|
||||||
|
--- local result = isVehicleOwned(plate)
|
||||||
|
--- if result then
|
||||||
|
--- return true
|
||||||
|
--- else
|
||||||
|
--- return false
|
||||||
|
--- end
|
||||||
|
--- end)
|
||||||
|
--- ```
|
||||||
|
function createCallback(callbackName, funct)
|
||||||
|
if isServer() then
|
||||||
|
if isStarted(OXLibExport) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^4"..OXLibExport.." ^3Callback^7:", callbackName)
|
||||||
|
lib.callback.register(callbackName, funct)
|
||||||
|
else
|
||||||
|
local adaptedFunction = function(source, cb, ...)
|
||||||
|
local result = funct(source, ...)
|
||||||
|
cb(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
if isStarted(QBExport) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^4"..QBExport.." ^3Callback^7:", callbackName)
|
||||||
|
Core = Core or exports[QBExport]:GetCoreObject()
|
||||||
|
Core.Functions.CreateCallback(callbackName, adaptedFunction)
|
||||||
|
elseif isStarted(VorpExport) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^4"..VorpExport.." ^3Callback^7:", callbackName)
|
||||||
|
Core = Core or exports.vorp_core:GetCore()
|
||||||
|
Core.Callback.Register(callbackName, adaptedFunction)
|
||||||
|
elseif isStarted(ESXExport) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^4"..ESXExport.." ^3Callback^7:", callbackName)
|
||||||
|
ESX.RegisterServerCallback(callbackName, adaptedFunction)
|
||||||
|
else
|
||||||
|
print("^1ERROR^7: ^1Can't find any supported framework to register callback with^7: "..callbackName)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Triggers a server callback and returns the result.
|
||||||
|
---
|
||||||
|
--- This function triggers a server callback using the appropriate framework's method and awaits the result.
|
||||||
|
---
|
||||||
|
---@param callbackName string The name of the callback to trigger.
|
||||||
|
---@param ... any Additional arguments to pass to the callback.
|
||||||
|
---
|
||||||
|
---@return any any The result returned by the callback function.
|
||||||
|
---@return string string The error/success message returned by the callback function.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local result = triggerCallback('myCallback')
|
||||||
|
--- jsonPrint(result)
|
||||||
|
---
|
||||||
|
--- local result = triggerCallback("callback:checkVehicleOwned", plate)
|
||||||
|
--- print(result)
|
||||||
|
--- ```
|
||||||
|
function triggerCallback(callbackName, ...)
|
||||||
|
debugPrint("^6Bridge^7: ^2Triggering ^3Callback^7:", callbackName)
|
||||||
|
local args = {...}
|
||||||
|
|
||||||
|
if isStarted(OXLibExport) then
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return lib.callback.await(callbackName, false, table.unpack(args))
|
||||||
|
end)
|
||||||
|
if ok then return res, "nil" end
|
||||||
|
return nil, tostring(res)
|
||||||
|
end
|
||||||
|
|
||||||
|
local attempts = 0
|
||||||
|
local lastErr
|
||||||
|
|
||||||
|
repeat
|
||||||
|
attempts = attempts + 1
|
||||||
|
|
||||||
|
if isStarted(QBExport) then
|
||||||
|
local res, err = awaitWithTimeout(function(cb)
|
||||||
|
Core.Functions.TriggerCallback(callbackName, cb, table.unpack(args))
|
||||||
|
end, 5000)
|
||||||
|
if res ~= nil then return res, "nil" end
|
||||||
|
lastErr = err
|
||||||
|
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (QB) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
|
||||||
|
|
||||||
|
elseif isStarted(VorpExport) then
|
||||||
|
local res, err = awaitWithTimeout(function(cb)
|
||||||
|
Core.Callback.TriggerAwait(callbackName, cb, table.unpack(args))
|
||||||
|
end, 5000)
|
||||||
|
if res ~= nil then return res, "nil" end
|
||||||
|
lastErr = err
|
||||||
|
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (Vorp) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
|
||||||
|
|
||||||
|
elseif isStarted(ESXExport) then
|
||||||
|
local res, err = awaitWithTimeout(function(cb)
|
||||||
|
ESX.TriggerServerCallback(callbackName, cb, table.unpack(args))
|
||||||
|
end, 5000)
|
||||||
|
if res ~= nil then return res, "nil" end
|
||||||
|
lastErr = err
|
||||||
|
debugPrint(("^6Bridge^7: ^3Callback^7 %s ^1failed^7 (ESX) attempt %d: %s"):format(callbackName, attempts, tostring(err)))
|
||||||
|
|
||||||
|
else
|
||||||
|
print("^6Bridge^7: ^1ERROR^7: ^3Can't find any script to trigger callback with^7:", callbackName)
|
||||||
|
return nil, "no_framework"
|
||||||
|
end
|
||||||
|
|
||||||
|
Wait(10)
|
||||||
|
until attempts > (1 + CALLBACK_RETRIES)
|
||||||
|
|
||||||
|
return nil, lastErr or "timeout"
|
||||||
|
end
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
-- Client Item Registry
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Dynamically derived list of items a client is allowed to request through the
|
||||||
|
-- ":server:toggleItem" net event.
|
||||||
|
--
|
||||||
|
-- There is no hardcoded item list. The registry is built once, on first use,
|
||||||
|
-- from the host resource's own tables:
|
||||||
|
--
|
||||||
|
-- * Crafting - recipe results and ingredients
|
||||||
|
-- * Stores - shop item definitions ({ name = "item" })
|
||||||
|
-- * MaterialRecieve - material returns
|
||||||
|
-- * createUseableItem() registrations
|
||||||
|
-- * allowClientItem() calls
|
||||||
|
--
|
||||||
|
-- Because it reads the script's existing definitions, adding a new recipe or
|
||||||
|
-- shop item automatically makes it grantable. Servers do not need to maintain
|
||||||
|
-- a second list.
|
||||||
|
--
|
||||||
|
-- Items that are not found in any of the above are refused.
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
ClientItemAllowlist = ClientItemAllowlist or {}
|
||||||
|
ClientItemUseable = ClientItemUseable or {}
|
||||||
|
|
||||||
|
local built = false
|
||||||
|
|
||||||
|
--- Default maximum a client may request of a dynamically discovered item.
|
||||||
|
ClientItemDefaultMax = ClientItemDefaultMax or 10
|
||||||
|
|
||||||
|
local function add(item, max)
|
||||||
|
if type(item) ~= "string" or item == "" then return end
|
||||||
|
if ClientItemAllowlist[item] == nil then
|
||||||
|
ClientItemAllowlist[item] = tonumber(max) or ClientItemDefaultMax
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Walks a table and treats every string key and string value as a candidate
|
||||||
|
-- item name. Non-item strings are harmless because the caller still runs
|
||||||
|
-- doesItemExist() before granting anything.
|
||||||
|
local function scan(tbl, seen, depth)
|
||||||
|
if type(tbl) ~= "table" or seen[tbl] then return end
|
||||||
|
if depth and depth > 12 then return end
|
||||||
|
seen[tbl] = true
|
||||||
|
|
||||||
|
for k, v in pairs(tbl) do
|
||||||
|
if type(k) == "string" then add(k) end
|
||||||
|
if type(v) == "table" then
|
||||||
|
scan(v, seen, (depth or 0) + 1)
|
||||||
|
elseif type(v) == "string" then
|
||||||
|
add(v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Builds the registry from whatever item tables the host resource exposes.
|
||||||
|
local function build()
|
||||||
|
if built then return end
|
||||||
|
built = true
|
||||||
|
|
||||||
|
local seen = {}
|
||||||
|
if type(Crafting) == "table" then scan(Crafting, seen, 0) end
|
||||||
|
if type(Stores) == "table" then scan(Stores, seen, 0) end
|
||||||
|
if type(MaterialRecieve) == "table" then scan(MaterialRecieve, seen, 0) end
|
||||||
|
-- Scripts keep item names in Config too (shops, sell tables, processing).
|
||||||
|
-- This is what makes the registry follow each server's own configuration.
|
||||||
|
if type(Config) == "table" then scan(Config, seen, 0) end
|
||||||
|
|
||||||
|
for item in pairs(ClientItemUseable) do add(item) end
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(ClientItemAllowlist) do count = count + 1 end
|
||||||
|
debugPrint("^6Bridge^7: ^2Client item registry built for ^3"..tostring(getScript()).."^7 - ^6"..count.."^7 entries")
|
||||||
|
|
||||||
|
if count == 0 then
|
||||||
|
print("^6Bridge^7: ^3"..tostring(getScript()).." ^1has no item definitions to build a client registry from^7.")
|
||||||
|
print("^6Bridge^7: ^1Client-side addItem/removeItem grants are disabled for this resource^7.")
|
||||||
|
print("^6Bridge^7: ^2Call ^3allowClientItem(\"item_name\", maxAmount)^2 if this resource needs them^7.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Registers an item as client-grantable. Use this for items that are not
|
||||||
|
--- covered by the host resource's Crafting/Stores/MaterialRecieve tables.
|
||||||
|
--- @param item string
|
||||||
|
--- @param maxAmount number|nil
|
||||||
|
function allowClientItem(item, maxAmount)
|
||||||
|
if type(item) ~= "string" or item == "" then return end
|
||||||
|
ClientItemAllowlist[item] = tonumber(maxAmount) or ClientItemDefaultMax
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Records an item registered through createUseableItem().
|
||||||
|
--- @param item string
|
||||||
|
function registerClientUseableItem(item)
|
||||||
|
if type(item) == "string" and item ~= "" then
|
||||||
|
ClientItemUseable[item] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns the maximum amount a client may request, or nil if not allowed.
|
||||||
|
--- Triggers the one-time build on first call.
|
||||||
|
--- @param item string
|
||||||
|
--- @return number|nil
|
||||||
|
function isClientItemAllowed(item)
|
||||||
|
if not built then build() end
|
||||||
|
return ClientItemAllowlist[item]
|
||||||
|
end
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
--[[
|
||||||
|
Menu Opening Module
|
||||||
|
---------------------
|
||||||
|
This module provides a unified function to open menus using the configured menu system.
|
||||||
|
Supported systems include:
|
||||||
|
• jim_bridge (built-in nui menu that works on any framework)
|
||||||
|
• ox (or ox_context)
|
||||||
|
• qb (using QBMenuExport)
|
||||||
|
• gta (using WarMenu)
|
||||||
|
• esx (using ESX.UI.Menu)
|
||||||
|
]]
|
||||||
|
|
||||||
|
local contextFunc = {
|
||||||
|
["ox"] =
|
||||||
|
function(Menu, data)
|
||||||
|
local index = nil
|
||||||
|
if data.onBack and not data.onSelected then
|
||||||
|
table.insert(Menu, 1, {
|
||||||
|
icon = "fas fa-circle-arrow-left",
|
||||||
|
title = "Return",
|
||||||
|
onSelect = data.onBack,
|
||||||
|
label = "Return",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
for k in pairs(Menu) do
|
||||||
|
if data.onSelected and Menu[k].arrow then
|
||||||
|
Menu[k].icon = "fas fa-angle-right"
|
||||||
|
end
|
||||||
|
-- If no title, use header or txt as title/label.
|
||||||
|
if not Menu[k].title then
|
||||||
|
if Menu[k].header ~= nil and Menu[k].header ~= "" then
|
||||||
|
Menu[k].title = Menu[k].header
|
||||||
|
Menu[k].label = Menu[k].header
|
||||||
|
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
|
||||||
|
else
|
||||||
|
Menu[k].title = Menu[k].txt
|
||||||
|
Menu[k].label = Menu[k].txt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Copy parameters from 'params' if available.
|
||||||
|
if Menu[k].params then
|
||||||
|
Menu[k].event = Menu[k].params.event
|
||||||
|
Menu[k].args = Menu[k].params.args or {}
|
||||||
|
end
|
||||||
|
if Menu[k].isMenuHeader then
|
||||||
|
Menu[k].readOnly = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local menuID = 'Menu'
|
||||||
|
(data.onSelected and lib.registerMenu or lib.registerContext)({
|
||||||
|
id = menuID,
|
||||||
|
title = data.header..br..br..(data.headertxt and data.headertxt or ""),
|
||||||
|
position = 'top-right',
|
||||||
|
options = Menu,
|
||||||
|
canClose = data.canClose and data.canClose or nil,
|
||||||
|
onClose = (data.onBack and data.onBack) or (data.onExit and data.onExit) or nil,
|
||||||
|
onExit = data.onExit and data.onExit or nil,
|
||||||
|
onSelected = data.onSelected and (function(selected) index = selected end) or nil,
|
||||||
|
}, data.onSelected and (function(x, y, args)
|
||||||
|
if Menu[x].refresh then
|
||||||
|
if Menu[x].onSelect then
|
||||||
|
Menu[x].onSelect()
|
||||||
|
end
|
||||||
|
lib.showMenu(menuID, index)
|
||||||
|
else
|
||||||
|
if Menu[x].onSelect then
|
||||||
|
Menu[x].onSelect()
|
||||||
|
else
|
||||||
|
lib.showMenu(menuID, index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end) or nil)
|
||||||
|
if data.onSelected then
|
||||||
|
lib.showMenu(menuID, 1)
|
||||||
|
else
|
||||||
|
lib.showContext(menuID)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
["qb"] =
|
||||||
|
function(Menu, data)
|
||||||
|
if data.onBack then
|
||||||
|
table.insert(Menu, 1, {
|
||||||
|
icon = "fas fa-circle-arrow-left",
|
||||||
|
header = " ",
|
||||||
|
txt = "Return",
|
||||||
|
params = {
|
||||||
|
isAction = true,
|
||||||
|
event = data.onBack,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
elseif data.canClose then
|
||||||
|
table.insert(Menu, 1, {
|
||||||
|
icon = "fas fa-circle-xmark",
|
||||||
|
header = " ",
|
||||||
|
txt = "Close",
|
||||||
|
params = {
|
||||||
|
isAction = true,
|
||||||
|
event = data.onExit and data.onExit or (function() exports[QBMenuExport]:closeMenu() end),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if data.header ~= nil then
|
||||||
|
local tempMenu = {}
|
||||||
|
for k, v in pairs(Menu) do tempMenu[k + 1] = v end
|
||||||
|
tempMenu[1] = { header = data.header, txt = data.headertxt or "", isMenuHeader = true }
|
||||||
|
Menu = tempMenu
|
||||||
|
end
|
||||||
|
for k in pairs(Menu) do
|
||||||
|
if not Menu[k].params or not Menu[k].params.event then
|
||||||
|
Menu[k].params = {
|
||||||
|
isAction = true,
|
||||||
|
event = Menu[k].onSelect or function() end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if not Menu[k].header then Menu[k].header = " " end
|
||||||
|
if Menu[k].arrow then Menu[k].icon = "fas fa-angle-right" end
|
||||||
|
Menu[k].isMenuHeader = Menu[k].isMenuHeader or Menu[k].disable
|
||||||
|
end
|
||||||
|
exports[QBMenuExport]:openMenu(Menu)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["gta"] =
|
||||||
|
function(Menu, data)
|
||||||
|
WarMenu.CreateMenu(tostring(Menu), data.header, " ", {
|
||||||
|
titleColor = { 222, 255, 255 },
|
||||||
|
maxOptionCountOnScreen = 15,
|
||||||
|
width = 0.25,
|
||||||
|
x = 0.7,
|
||||||
|
})
|
||||||
|
if WarMenu.IsAnyMenuOpened() then return end
|
||||||
|
WarMenu.OpenMenu(tostring(Menu))
|
||||||
|
CreateThread(function()
|
||||||
|
local close = true
|
||||||
|
while true do
|
||||||
|
if WarMenu.Begin(tostring(Menu)) then
|
||||||
|
if data.onBack then
|
||||||
|
if WarMenu.SpriteButton("Return", 'commonmenu', "arrowleft", 127, 127, 127) then
|
||||||
|
WarMenu.CloseMenu()
|
||||||
|
Wait(10)
|
||||||
|
data.onBack()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k in pairs(Menu) do
|
||||||
|
local pressed = WarMenu.Button(Menu[k].header)
|
||||||
|
if not Menu[k].header then
|
||||||
|
Menu[k].header = Menu[k].txt
|
||||||
|
Menu[k].txt = nil
|
||||||
|
end
|
||||||
|
if Menu[k].txt and Menu[k].txt ~= "" and WarMenu.IsItemHovered() then
|
||||||
|
if Menu[k].disabled or Menu[k].isMenuHeader then
|
||||||
|
WarMenu.ToolTip("~r~"..Menu[k].txt, 0.18, true)
|
||||||
|
else
|
||||||
|
WarMenu.ToolTip(
|
||||||
|
(Menu[k].blip and "~BLIP_".."8".."~ " or "")..
|
||||||
|
Menu[k].txt:gsub("%:", ":~g~"):gsub("%\n", "\n~s~"), 0.18,
|
||||||
|
true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if pressed and not Menu[k].isMenuHeader then
|
||||||
|
WarMenu.CloseMenu()
|
||||||
|
close = false
|
||||||
|
Wait(10)
|
||||||
|
Menu[k].onSelect()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
WarMenu.End()
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not WarMenu.IsAnyMenuOpened() and close then
|
||||||
|
stopTempCam(cam)
|
||||||
|
if data.onExit then data.onExit() end
|
||||||
|
end
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["esx"] =
|
||||||
|
function(Menu, data)
|
||||||
|
for k in pairs(Menu) do
|
||||||
|
Menu[k].label = Menu[k].header
|
||||||
|
Menu[k].name = "button"..k
|
||||||
|
end
|
||||||
|
if data.canClose then
|
||||||
|
table.insert(Menu, 1, {
|
||||||
|
icon = "fas fa-circle-xmark",
|
||||||
|
label = "Close",
|
||||||
|
name = "close",
|
||||||
|
onSelect = data.onExit,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
if data.onBack then
|
||||||
|
table.insert(Menu, 1, {
|
||||||
|
icon = "fas fa-circle-arrow-left",
|
||||||
|
label = "Return",
|
||||||
|
name = "return",
|
||||||
|
onSelect = data.onBack,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
ESX.UI.Menu.Open("default", getScript(), "Example_Menu", {
|
||||||
|
title = data.header,
|
||||||
|
align = 'top-right',
|
||||||
|
elements = Menu,
|
||||||
|
},
|
||||||
|
function(menuData, menu)
|
||||||
|
for k in pairs(Menu) do
|
||||||
|
if menuData.current.name == Menu[k].name then
|
||||||
|
menu.close()
|
||||||
|
Wait(10)
|
||||||
|
Menu[k].onSelect()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
function(data, menu)
|
||||||
|
menu.close()
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["lation"] =
|
||||||
|
function(Menu, data)
|
||||||
|
if data.onBack then
|
||||||
|
table.insert(Menu, 1, {
|
||||||
|
icon = "fas fa-circle-arrow-left",
|
||||||
|
onSelect = data.onBack,
|
||||||
|
header = "Return",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
for k in pairs(Menu) do
|
||||||
|
if data.onSelected and Menu[k].arrow then
|
||||||
|
Menu[k].icon = "fas fa-angle-right"
|
||||||
|
end
|
||||||
|
-- If no title, use header or txt as title/label.
|
||||||
|
if not Menu[k].title then
|
||||||
|
if Menu[k].header ~= nil and Menu[k].header ~= "" then
|
||||||
|
Menu[k].title = Menu[k].header
|
||||||
|
Menu[k].label = Menu[k].header
|
||||||
|
if Menu[k].txt then Menu[k].description = Menu[k].txt else Menu[k].description = "" end
|
||||||
|
else
|
||||||
|
Menu[k].title = Menu[k].txt
|
||||||
|
Menu[k].label = Menu[k].txt
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Copy parameters from 'params' if available.
|
||||||
|
if Menu[k].params then
|
||||||
|
Menu[k].event = Menu[k].params.event
|
||||||
|
Menu[k].args = Menu[k].params.args or {}
|
||||||
|
end
|
||||||
|
if Menu[k].isMenuHeader then
|
||||||
|
Menu[k].readOnly = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
exports.lation_ui:registerMenu({
|
||||||
|
id = 'menu',
|
||||||
|
title = data.header,
|
||||||
|
onExit = data.onExit and data.onExit or nil,
|
||||||
|
subtitle = (data.headertxt and data.headertxt or ""),
|
||||||
|
options = Menu,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Show menu
|
||||||
|
exports.lation_ui:showMenu('menu')
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
--- Opens a menu using the configured menu system.
|
||||||
|
---
|
||||||
|
--- This function translates code and creates menus in several different menu scripts, depending on the configured menu system specified in `Config.System.Menu`.
|
||||||
|
---
|
||||||
|
---@param Menu table A table containing the menu options to display.
|
||||||
|
--- Each menu item can include:
|
||||||
|
--- - header (`string`): The text to display for the menu item.
|
||||||
|
--- - txt (`string`, optional): Additional text or description.
|
||||||
|
--- - icon (`string`, optional): Icon to display with the menu item.
|
||||||
|
--- - onSelect (`function`, optional): Function to execute when the menu item is selected.
|
||||||
|
--- - arrow (`boolean`, optional): Whether to display an arrow next to the item (for certain menus).
|
||||||
|
--- - params (`table`, optional): Additional parameters, such as events and arguments.
|
||||||
|
--- - isMenuHeader (`boolean`, optional): Marks the item as a header.
|
||||||
|
--- - disabled (`boolean`, optional): Disables the menu item if `true`.
|
||||||
|
---
|
||||||
|
---@param data table A table containing configuration data for the menu.
|
||||||
|
--- - header (`string`): The header/title of the menu.
|
||||||
|
--- - headertxt (`string`, optional): Additional header text.
|
||||||
|
--- - onBack (`function`, optional): Function to call when the "Return" option is selected.
|
||||||
|
--- - onExit (`function`, optional): Function to call when the menu is exited.
|
||||||
|
--- - onSelected (`function`, optional): Function to call when a menu item is selected (for certain menu systems).
|
||||||
|
--- - canClose (`boolean`, optional): Whether the menu can be closed by the user.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- openMenu({
|
||||||
|
--- { header = "Option 1", txt = "Description 1", onSelect = function() print("Option 1 selected") end },
|
||||||
|
--- { header = "Option 2", txt = "Description 2", onSelect = function() print("Option 2 selected") end },
|
||||||
|
--- }, {
|
||||||
|
--- header = "Main Menu",
|
||||||
|
--- headertxt = "Select an option",
|
||||||
|
--- onBack = function() print("Return selected") end,
|
||||||
|
--- onExit = function() print("Menu closed") end,
|
||||||
|
--- canClose = true,
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
function openMenu(Menu, data)
|
||||||
|
contextFunc[Config.System.Menu](Menu, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Line break conversion table, basically, to automate the linebreak in functions `br`
|
||||||
|
-- This is declared and automatically sets it for what menu you are using (depends if one is uding MD formatting or HTML formatting)
|
||||||
|
local lineBreakConversion = {
|
||||||
|
["ox"] = "\n",
|
||||||
|
["gta"] = "\n",
|
||||||
|
["lation"] = "\n",
|
||||||
|
|
||||||
|
["qb"] = "<br>",
|
||||||
|
["esx"] = "<br>",
|
||||||
|
}
|
||||||
|
|
||||||
|
--- A line break constant used for menu header formatting.
|
||||||
|
br = lineBreakConversion[Config.System.Menu]
|
||||||
|
|
||||||
|
--- Checks if the current menu system is 'ox' or 'gta' for formatting purposes.
|
||||||
|
--- @return boolean boolean True if using ox or gta menus, otherwise false.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- if isOx() then
|
||||||
|
--- -- Use specific formatting
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function isOx() return (Config.System.Menu == "ox" or Config.System.Menu == "gta") end
|
||||||
|
|
||||||
|
|
||||||
|
--- Checks if any WarMenu menu is currently open.
|
||||||
|
---
|
||||||
|
--- @return boolean boolean Returns `true` if a WarMenu menu is open; otherwise, `false`.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- if isWarMenuOpen() then
|
||||||
|
--- -- Do something
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function isWarMenuOpen() if Config.System.Menu == "gta" then return WarMenu.IsAnyMenuOpened() else return false end end
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
Items, Vehicles, Jobs, Gangs = nil, nil, nil, nil
|
||||||
|
|
||||||
|
-- Shared Exports Initialization
|
||||||
|
Exports.PSInv = isStarted("lj-inventory") and "lj-inventory" or Exports.PSInv
|
||||||
|
|
||||||
|
OXLibExport, QBXExport, QBExport, ESXExport, OXCoreExport =
|
||||||
|
Exports.OXLibExport or "",
|
||||||
|
Exports.QBXExport or "",
|
||||||
|
Exports.QBExport or "",
|
||||||
|
Exports.ESXExport or "",
|
||||||
|
Exports.OXCoreExport or ""
|
||||||
|
|
||||||
|
OXInv, QBInv, PSInv, CoreInv, CodeMInv, OrigenInv, TgiannInv, JPRInv =
|
||||||
|
Exports.OXInv or "",
|
||||||
|
Exports.QBInv or "",
|
||||||
|
Exports.PSInv or "",
|
||||||
|
Exports.CoreInv or "",
|
||||||
|
Exports.CodeMInv or "",
|
||||||
|
Exports.OrigenInv or "",
|
||||||
|
Exports.TgiannInv or "",
|
||||||
|
Exports.JPRInv or ""
|
||||||
|
|
||||||
|
RSGExport, RSGInv = Exports.RSGExport or "", Exports.RSGInv or ""
|
||||||
|
VorpExport, VorpInv = Exports.VorpExport or "", Exports.VorpInv or ""
|
||||||
|
|
||||||
|
|
||||||
|
QBMenuExport = Exports.QBMenuExport or ""
|
||||||
|
QBTargetExport, OXTargetExport = Exports.QBTargetExport or "", Exports.OXTargetExport or ""
|
||||||
|
|
||||||
|
if isStarted(QBXExport) or isStarted(QBExport) then
|
||||||
|
Core = Core or exports[QBExport]:GetCoreObject()
|
||||||
|
elseif isStarted(RSGExport) then
|
||||||
|
Core = Core or exports[RSGExport]:GetCoreObject()
|
||||||
|
elseif isStarted(VorpExport) then
|
||||||
|
Core = Core or exports[VorpExport]:GetCore()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if IsDuplicityVersion() then
|
||||||
|
local cache = nil
|
||||||
|
local timeout = GetGameTimer() + 900000 -- 15 minutes max wait (had to up this from 2 minutes because of slow servers)
|
||||||
|
|
||||||
|
-- Wait until jim_bridge is started and export is available
|
||||||
|
while not cache and (timeout and GetGameTimer() < timeout) do
|
||||||
|
if GetResourceState("jim_bridge"):find("start") then
|
||||||
|
local success, result = pcall(function()
|
||||||
|
return exports["jim_bridge"]:GetSharedData()
|
||||||
|
end)
|
||||||
|
if success and result then
|
||||||
|
cache = result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
|
||||||
|
if timeout and not cache then
|
||||||
|
print("^1ERROR^7: ^2jim_bridge export not available after timeout^7.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Items = cache.Items
|
||||||
|
Vehicles = cache.Vehicles
|
||||||
|
Jobs = cache.Jobs
|
||||||
|
Gangs = cache.Gangs
|
||||||
|
InventoryWeight = cache.InventoryWeight or InventoryWeight
|
||||||
|
InventorySlots = cache.InventorySlots or 40
|
||||||
|
|
||||||
|
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
|
||||||
|
--print(countTable(Items), countTable(Vehicles), countTable(Jobs))
|
||||||
|
else
|
||||||
|
-- Shared bridge cache - prevent duplicate listeners
|
||||||
|
exports["jim_bridge"]:GetBridgeCache(function(cache)
|
||||||
|
Items = cache.Items or {}
|
||||||
|
Vehicles = cache.Vehicles or {}
|
||||||
|
Jobs = cache.Jobs or {}
|
||||||
|
Gangs = cache.Gangs or {}
|
||||||
|
InventoryWeight = cache.InventoryWeight or InventoryWeight
|
||||||
|
InventorySlots = cache.InventorySlots or 50
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
if isStarted(ESXExport) then
|
||||||
|
for _, v in pairs(Vehicles) do
|
||||||
|
Vehicles[v.model] = {
|
||||||
|
model = v.model,
|
||||||
|
hash = v.hash,
|
||||||
|
price = v.price,
|
||||||
|
name = v.name,
|
||||||
|
brand = GetMakeNameFromVehicleModel(v.model):lower():gsub("^%l", string.upper)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if isStarted(OXInv) then
|
||||||
|
for k, v in pairs(Items) do
|
||||||
|
local tempInfo = exports[OXInv]:Items(k)
|
||||||
|
Items[k].image = k..".png" -- Set default image
|
||||||
|
if tempInfo and tempInfo.client then -- if client info, check for hard set image
|
||||||
|
Items[k].image = (tempInfo.client.image) and tempInfo.client.image:gsub("nui://"..OXInv.."/web/images/", "") or Items[k].image
|
||||||
|
Items[k].hunger = tempInfo.client.hunger
|
||||||
|
Items[k].thirst = tempInfo.client.thirst
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
debugPrint("^6Bridge^7: ^2Shared cache successfully loaded from export^7.")
|
||||||
|
end)
|
||||||
|
end
|
||||||
@@ -0,0 +1,600 @@
|
|||||||
|
--[[
|
||||||
|
Crafting, Selling, and Shop Module
|
||||||
|
-----------------------------------
|
||||||
|
This module provides functions for opening crafting menus, handling multi-crafting,
|
||||||
|
performing the crafting process (with animations and progress bars), selling items,
|
||||||
|
and opening shop interfaces. It integrates with various inventory and menu systems,
|
||||||
|
and uses server callbacks to check item carry capacity.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Global Variables
|
||||||
|
-------------------------------------------------------------
|
||||||
|
CraftLock = false
|
||||||
|
|
||||||
|
-- helper filter table for crafting menus
|
||||||
|
local excludeKeys = {
|
||||||
|
amount = true, metadata = true, description = true, info = true,
|
||||||
|
job = true, gang = true, oneUse = true, slot = true,
|
||||||
|
blueprintRef = true, craftingLevel = true, craftedItems = true,
|
||||||
|
hasCrafted = true, exp = true, anim = true, time = true, id = true,
|
||||||
|
ingredients = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Crafting Menu
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Opens the crafting menu based on provided data.
|
||||||
|
--- Checks job restrictions, builds the recipe menu, and opens the menu.
|
||||||
|
---
|
||||||
|
--- @param data table Crafting menu configuration containing:
|
||||||
|
--- - craftable (`table`) Table with Header, Recipes, Anims, and (optionally) craftedItems.
|
||||||
|
--- - coords (`vector3`) The coordinates where the crafting menu is being opened.
|
||||||
|
--- - stashTable|stashName (`string\table`) Name(s) of the stash for checking item availability.
|
||||||
|
--- - job|gang (`string`) Job or gang requirements.
|
||||||
|
--- - onBack (optional): Function to call when returning.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- craftingMenu({
|
||||||
|
--- craftable = {
|
||||||
|
--- Header = "Weapon Crafting",
|
||||||
|
--- Recipes = {
|
||||||
|
--- weapon_pistol = {
|
||||||
|
--- id = 1,
|
||||||
|
--- ingredients = {
|
||||||
|
--- steel = 5, plastic = 5,
|
||||||
|
--- },
|
||||||
|
--- info = {
|
||||||
|
--- amount = 1,
|
||||||
|
--- },
|
||||||
|
--- },
|
||||||
|
--- -- More recipes...
|
||||||
|
--- },
|
||||||
|
--- Anims = {
|
||||||
|
--- animDict = "amb@prop_human_parking_meter@male@idle_a",
|
||||||
|
--- anim = "idle_a",
|
||||||
|
--- },
|
||||||
|
--- },
|
||||||
|
--- coords = vector3(100.0, 200.0, 300.0),
|
||||||
|
--- stashTable = "crafting_stash",
|
||||||
|
--- job = "mechanic",
|
||||||
|
--- onBack = function() print("Returning to previous menu") end,
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
function craftingMenu(data)
|
||||||
|
if CraftLock then return end
|
||||||
|
local data = cloneTable(data)
|
||||||
|
-- Job or gang check; exit if not authorized.
|
||||||
|
if (data.job or data.gang) and not jobCheck(data.job or data.gang) then return end
|
||||||
|
|
||||||
|
-- Display a temporary "thinking" notification.
|
||||||
|
if Config.System.Menu == "jim" then
|
||||||
|
triggerNotify(nil, "Thinking", "info")
|
||||||
|
else
|
||||||
|
openMenu({ { header = "Thinking...", icon = "fas fa-hourglass-end", isMenuHeader = true } }, { header = "Crafting Menu" } )
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Normalize stash name.
|
||||||
|
data.stashName = data.stashTable or data.stashName
|
||||||
|
|
||||||
|
-- Wrapper to convert old style crafting recipes to be handled by the menu properly
|
||||||
|
--if data.craftable.Recipes[1] then -- assume old style crafting table
|
||||||
|
-- local compatTable = {}
|
||||||
|
-- local id = 0
|
||||||
|
-- for k, v in ipairs(data.craftable.Recipes) do
|
||||||
|
-- local Recipe = v
|
||||||
|
-- for l, b in pairs(Recipe) do
|
||||||
|
-- if doesItemExist(l) then
|
||||||
|
-- id += 1
|
||||||
|
-- compatTable[l] = {
|
||||||
|
-- ingredients = b,
|
||||||
|
-- id = id,
|
||||||
|
-- info = {
|
||||||
|
-- amount = Recipe.amount or 1,
|
||||||
|
-- metadata = Recipe.metadata or nil,
|
||||||
|
-- job = Recipe.job or nil,
|
||||||
|
-- gang = Recipe.gang or nil,
|
||||||
|
-- hasCrafted = Recipe.hasCrafted or nil,
|
||||||
|
-- },
|
||||||
|
-- }
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- end
|
||||||
|
-- data.craftable.Recipes = compatTable
|
||||||
|
--end
|
||||||
|
|
||||||
|
-- Convert to array
|
||||||
|
--local RecipesArray = {}
|
||||||
|
--for k, v in pairs(data.craftable.Recipes) do
|
||||||
|
-- RecipesArray[v.id] = { [k] = v }
|
||||||
|
--end
|
||||||
|
|
||||||
|
local Menu = {}
|
||||||
|
local Recipes = cloneTable(data.craftable.Recipes)
|
||||||
|
local craftedItems = {}
|
||||||
|
|
||||||
|
local tempCarryTable = {}
|
||||||
|
-- Build a temporary table of all required ingredients (default quantity is 1).
|
||||||
|
for i = 1, #Recipes do
|
||||||
|
for k, v in pairs(Recipes[i]) do
|
||||||
|
if not excludeKeys[k] then
|
||||||
|
if not Recipes[i].amount then Recipes[i].amount = 1 end
|
||||||
|
tempCarryTable[k] = math.max(tempCarryTable[k] or 0, Recipes[i].amount)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if the player can carry the required items (server callback).
|
||||||
|
local canCarryTable = triggerCallback(getScript()..':server:canCarry', tempCarryTable)
|
||||||
|
|
||||||
|
local usingStash = data.stashName ~= nil and data.stashName ~= ""
|
||||||
|
|
||||||
|
Menu[#Menu+1] = {
|
||||||
|
icon = usingStash and "fas fa-boxes-stacked" or "fas fa-person",
|
||||||
|
header = "Material Source: "..(usingStash and "Job Stash" or "Player Inventory"),
|
||||||
|
disabled = true,
|
||||||
|
}
|
||||||
|
for i = 1, #Recipes do
|
||||||
|
local menuId = #Menu+1
|
||||||
|
local item = ""
|
||||||
|
local Recipe = {}
|
||||||
|
for k, v in pairs(Recipes[i]) do
|
||||||
|
if not excludeKeys[k] then
|
||||||
|
item = k
|
||||||
|
Recipe = Recipes[i]
|
||||||
|
Recipe.amount = Recipe.amount or 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Job Check
|
||||||
|
local hasGroup = true
|
||||||
|
if Recipe.job then
|
||||||
|
for l, b in pairs(Recipe.job) do
|
||||||
|
hasGroup = hasJob(l, nil, b)
|
||||||
|
if hasGroup then goto skipcheck end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if Recipe.gang then
|
||||||
|
for l, b in pairs(Recipe.gang) do
|
||||||
|
hasGroup = hasJob(l, nil, b)
|
||||||
|
if hasGroup then goto skipcheck end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::skipcheck::
|
||||||
|
|
||||||
|
-- if has group requirement, continue
|
||||||
|
if hasGroup then
|
||||||
|
local setheader, settext, disable, metadata = "", "", false, (Recipe.metadata or Recipe.info or nil)
|
||||||
|
local itemTable = {}
|
||||||
|
local metaTable = {}
|
||||||
|
-- Build ingredient details.
|
||||||
|
for l, b in pairs(Recipe[item]) do
|
||||||
|
local label = getItemLabel(l)
|
||||||
|
local hasItem = checkStashItem(data.stashName, { [l] = b })
|
||||||
|
local missingMark = not hasItem and " ❌" or " "
|
||||||
|
settext = settext..(settext ~= "" and br or "").."[ x"..b.." ] - "..label..missingMark
|
||||||
|
|
||||||
|
metaTable[label] = b
|
||||||
|
itemTable[l] = b
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make sure "canCarryTable" exists
|
||||||
|
while not canCarryTable do Wait(10) end
|
||||||
|
disable = not checkStashItem(data.stashName, itemTable)
|
||||||
|
setheader = ((metadata and metadata.label) or getItemLabel(item))
|
||||||
|
..(Recipe.amount > 1 and " x"..Recipe.amount or "")
|
||||||
|
|
||||||
|
local statusEmoji = disable and " " or not canCarryTable[item] and " 📦" or " ✔️"
|
||||||
|
local isNew = (Recipe.hasCrafted ~= nil and craftedItems[item] == nil) and "✨ " or ""
|
||||||
|
setheader = isNew .. setheader .. statusEmoji
|
||||||
|
|
||||||
|
-- Build menu option using info
|
||||||
|
Menu[menuId] = {
|
||||||
|
arrow = isOx() and (not disable and canCarryTable[item]),
|
||||||
|
isMenuHeader = disable or not canCarryTable[item],
|
||||||
|
icon = invImg((metadata and metadata.image) or item),
|
||||||
|
image = invImg((metadata and metadata.image) or item),
|
||||||
|
header = setheader,
|
||||||
|
txt = settext or nil,
|
||||||
|
metadata = metaTable,
|
||||||
|
onSelect = (not disable and canCarryTable[item]) and function()
|
||||||
|
local transdata = {
|
||||||
|
item = item,
|
||||||
|
craft = Recipe,
|
||||||
|
craftable = data.craftable,
|
||||||
|
coords = data.coords,
|
||||||
|
amount = Recipe.amount,
|
||||||
|
stashName = data.stashName,
|
||||||
|
onBack = data.onBack,
|
||||||
|
metadata = metadata,
|
||||||
|
}
|
||||||
|
if Config.Crafting.MultiCraft then
|
||||||
|
multiCraft(transdata)
|
||||||
|
else
|
||||||
|
makeItem(transdata)
|
||||||
|
end
|
||||||
|
end or nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- open context menu
|
||||||
|
openMenu(Menu, {
|
||||||
|
header = data.craftable.Header,
|
||||||
|
headertxt = data.craftable.Headertxt,
|
||||||
|
onBack = data.onBack or nil,
|
||||||
|
canClose = true,
|
||||||
|
onExit = data.onExit or (function() end),
|
||||||
|
})
|
||||||
|
lookEnt(data.coords)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Multi-Craft Menu
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Opens a menu for selecting the quantity to craft.
|
||||||
|
---
|
||||||
|
--- Presents the player with multiple crafting quantities based on Config.Crafting.MultiCraftAmounts.
|
||||||
|
---
|
||||||
|
--- @param data table Crafting configuration containing:
|
||||||
|
--- - item `string`) The item to craft.
|
||||||
|
--- - craft (`table`) The crafting recipe.
|
||||||
|
--- - craftable (`table`) Crafting options.
|
||||||
|
--- - coords (`vector3`) where crafting occurs.
|
||||||
|
--- - stashName (`string`) The stash name(s) for item availability.
|
||||||
|
--- - onBack (`function`) Callback when returning.
|
||||||
|
--- - metadata (`table`) (optional): Metadata for the crafted item.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- multiCraft({
|
||||||
|
--- item = "weapon_pistol",
|
||||||
|
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
|
||||||
|
--- craftable = craftingOptions,
|
||||||
|
--- coords = vector3(100,200,300),
|
||||||
|
--- stashName = "crafting_stash",
|
||||||
|
--- onBack = function() craftingMenu(data) end,
|
||||||
|
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
function multiCraft(data)
|
||||||
|
local max = 0
|
||||||
|
local stashName = nil
|
||||||
|
local maxCreation = 100
|
||||||
|
|
||||||
|
-- Generate item table to check against stash or inventory
|
||||||
|
-- takes into account the ingredients needed for multiple items
|
||||||
|
local multiItemTable = {}
|
||||||
|
|
||||||
|
for i = 1, maxCreation do
|
||||||
|
multiItemTable[i] = {}
|
||||||
|
for l, b in pairs(data.craft[data.item]) do
|
||||||
|
multiItemTable[i][l] = (b * i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, maxCreation do
|
||||||
|
-- if its received a stash name, check if the items are in the stash
|
||||||
|
if data.stashName then
|
||||||
|
local hasItems, stashname = checkStashItem(data.stashName, multiItemTable[i])
|
||||||
|
if hasItems == true then
|
||||||
|
max += 1
|
||||||
|
stashName = stashname
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- if not check the players inventory for the items
|
||||||
|
local has, _ = hasItem(multiItemTable[i], nil, nil)
|
||||||
|
if has then
|
||||||
|
max += 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local carryMax = triggerCallback(getScript()..":server:getMaxCarryCraft", {
|
||||||
|
item = data.item,
|
||||||
|
max = max
|
||||||
|
})
|
||||||
|
|
||||||
|
local dialog = createInput(data.craftable.Header..(Config.System.Menu == "qb" and ": "..br.."How many to craft? "..br.."Max: "..carryMax or ""), {
|
||||||
|
((Config.System.Menu == "ox" or Config.System.Menu == "lation") and {
|
||||||
|
type = "slider",
|
||||||
|
label = "How many to craft? "..br.."Max: "..carryMax,
|
||||||
|
required = true,
|
||||||
|
default = 1,
|
||||||
|
min = 1,
|
||||||
|
max = carryMax
|
||||||
|
}) or nil,
|
||||||
|
((Config.System.Menu == "qb") and {
|
||||||
|
type = "number",
|
||||||
|
name = "amount",
|
||||||
|
isRequired = true,
|
||||||
|
default = 1,
|
||||||
|
}) or nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
if dialog then
|
||||||
|
if Config.System.Menu == "ox" then
|
||||||
|
|
||||||
|
end
|
||||||
|
if Config.System.Menu == "qb" then
|
||||||
|
dialog["amount"] = tonumber(dialog["amount"])
|
||||||
|
if dialog["amount"] > carryMax or dialog["amount"] < 1 or dialog["amount"] == nil or dialog["amount"] == "" then
|
||||||
|
triggerNotify(nil, "Invalid Amount", "error")
|
||||||
|
craftingMenu(data)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
makeItem({
|
||||||
|
item = data.item,
|
||||||
|
craft = data.craft,
|
||||||
|
craftable = data.craftable,
|
||||||
|
amount = dialog["amount"] or dialog[1],
|
||||||
|
coords = data.coords,
|
||||||
|
stashName = stashName or nil,
|
||||||
|
onBack = data.onBack,
|
||||||
|
metadata = data.metadata,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Crafting Process
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Initiates the crafting process for a specified item.
|
||||||
|
---
|
||||||
|
--- Plays crafting animations, shows progress bars, removes ingredients, and triggers item creation.
|
||||||
|
---
|
||||||
|
--- @param data table Crafting configuration containing:
|
||||||
|
--- - item `string`) The item to craft.
|
||||||
|
--- - craft (`table`) The crafting recipe.
|
||||||
|
--- - craftable (`table`) Crafting options.
|
||||||
|
--- - amount (`number`) (optional): Quantity to craft (default 1).
|
||||||
|
--- - coords (`vector3`) where crafting occurs.
|
||||||
|
--- - stashName (`string`) The stash name(s) for item availability.
|
||||||
|
--- - onBack (`function`) Callback when returning.
|
||||||
|
--- - metadata (`table`) (optional): Metadata for the crafted item.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- makeItem({
|
||||||
|
--- item = "weapon_pistol",
|
||||||
|
--- craft = { ["weapon_pistol"] = { ["steel"] = 5, ["plastic"] = 2 }, amount = 1 },
|
||||||
|
--- craftable = craftingOptions,
|
||||||
|
--- amount = 2,
|
||||||
|
--- coords = vector3(100,200,300),
|
||||||
|
--- stashName = "crafting_stash",
|
||||||
|
--- onBack = function() craftingMenu(data) end,
|
||||||
|
--- metadata = { label = "Custom Pistol", image = "custom_pistol.png" },
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
|
||||||
|
function makeItem(origData)
|
||||||
|
|
||||||
|
local data = cloneTable(origData)
|
||||||
|
|
||||||
|
local Ped = PlayerPedId()
|
||||||
|
if CraftLock then return end
|
||||||
|
CraftLock = true
|
||||||
|
data.stashName = data.stashTable or data.stashName
|
||||||
|
|
||||||
|
local bartime = (data.craftable.progressBar and data.craftable.progressBar.time) or 5000
|
||||||
|
local bartext = (data.craftable.progressBar and data.craftable.progressBar.label)
|
||||||
|
or (Loc[Config.Lan].progressbar and Loc[Config.Lan].progressbar["progress_make"])
|
||||||
|
or "Making "
|
||||||
|
local animDict = (data.craftable.Anims and data.craftable.Anims.animDict) or "amb@prop_human_parking_meter@male@idle_a"
|
||||||
|
local anim = (data.craftable.Anims and data.craftable.Anims.anim) or "idle_a"
|
||||||
|
local craftAmount = (data.amount and data.amount ~= 1) and data.amount or 1
|
||||||
|
local metadata = data.metadata or nil
|
||||||
|
local prop = data.craftable.Anims and data.craftable.Anims.prop or nil
|
||||||
|
|
||||||
|
local canReturn = true
|
||||||
|
|
||||||
|
local crafted, crafting = true, true
|
||||||
|
local cam = createCam(Ped, data.coords.xyz)
|
||||||
|
startCam(cam, 5000)
|
||||||
|
|
||||||
|
-- Calculate total bartime if SingleProgress is enabled
|
||||||
|
local totalBartime = (bartime * craftAmount)
|
||||||
|
|
||||||
|
local craftProp = nil
|
||||||
|
if prop then
|
||||||
|
craftProp = makeProp({ prop = prop.model, coords = GetEntityCoords(PlayerPedId()), true, true })
|
||||||
|
AttachEntityToEntity(craftProp, Ped, GetPedBoneIndex(Ped, prop.bone), prop.pos.x, prop.pos.y, prop.pos.z, prop.rot.x, prop.rot.y, prop.rot.z, true, true, false, true, 1, true)
|
||||||
|
end
|
||||||
|
if data.sound then
|
||||||
|
local s = data.sound
|
||||||
|
PlaySoundFromEntity(s.soundId, s.audioName, Ped, s.audioRef, true, 0)
|
||||||
|
end
|
||||||
|
if not Config.Crafting.SingleProgress then -- if SingleProgress is disabled, dont do ingredient progressbars
|
||||||
|
-- Run ingredient check and usage separately first
|
||||||
|
for i = 1, craftAmount do
|
||||||
|
for k, v in pairs(data.craft[data.item]) do
|
||||||
|
if isInventoryOpen() then
|
||||||
|
print("^1Error^7: ^2Inventory is open, you tried to break things")
|
||||||
|
stopCam(100)
|
||||||
|
ClearPedTasks(Ped)
|
||||||
|
if canReturn then craftingMenu(origData) end
|
||||||
|
CraftLock = false
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if crafting and progressBar({
|
||||||
|
label = "Using "..v.." "..getItemLabel(k),
|
||||||
|
time = 800,
|
||||||
|
cancel = true,
|
||||||
|
dict = 'pickup_object',
|
||||||
|
anim = "putdown_low",
|
||||||
|
flag = 49,
|
||||||
|
icon = k,
|
||||||
|
disableMovement = true,
|
||||||
|
}) then
|
||||||
|
TriggerEvent((isStarted(QBInv) and QBInvNew and "qb-" or "")..'inventory:client:ItemBox', Items[k], "use", v)
|
||||||
|
else
|
||||||
|
crafted, crafting = false, false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
Wait(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not crafted then
|
||||||
|
goto finishEarly
|
||||||
|
end
|
||||||
|
|
||||||
|
if crafting and progressBar({
|
||||||
|
label = bartext..((metadata and metadata.label) or getItemLabel(data.item)),
|
||||||
|
time = bartime,
|
||||||
|
cancel = true,
|
||||||
|
dict = animDict,
|
||||||
|
anim = anim,
|
||||||
|
flag = 49,
|
||||||
|
icon = data.item,
|
||||||
|
disableMovement = true,
|
||||||
|
request = true,
|
||||||
|
}) then
|
||||||
|
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
|
||||||
|
currentToken = nil
|
||||||
|
if data.craft.hasCrafted ~= nil then
|
||||||
|
data.craftable.craftedItems[data.item] = true
|
||||||
|
triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
|
||||||
|
end
|
||||||
|
if data.craft.exp ~= nil then
|
||||||
|
craftingLevel += data.craft.exp.give
|
||||||
|
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
|
||||||
|
end
|
||||||
|
if data.craftable.Recipes[1].oneUse == true then
|
||||||
|
removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
|
||||||
|
local breakId = GetSoundId()
|
||||||
|
PlaySoundFromEntity(breakId, "Drill_Pin_Break", Ped, "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
|
||||||
|
canReturn = false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if crafting and progressBar({
|
||||||
|
label = bartext..((metadata and metadata.label) or getItemLabel(data.item)).." x"..craftAmount,
|
||||||
|
time = totalBartime,
|
||||||
|
cancel = true,
|
||||||
|
dict = animDict,
|
||||||
|
anim = anim,
|
||||||
|
flag = 49,
|
||||||
|
icon = data.item,
|
||||||
|
disableMovement = true,
|
||||||
|
request = true,
|
||||||
|
}) then
|
||||||
|
data.craft.amount *= craftAmount
|
||||||
|
for k, v in pairs(data.craft[data.item]) do
|
||||||
|
-- multiply igredient requirement in sent crafting table for removal
|
||||||
|
data.craft[data.item][k] = (v * craftAmount)
|
||||||
|
end
|
||||||
|
TriggerServerEvent(getScript()..":Crafting:GetItem", data.item, data.craft, data.stashName, metadata, currentToken)
|
||||||
|
currentToken = nil -- clear client cached token
|
||||||
|
-- handle metadata and experience in a single go
|
||||||
|
if data.craft.hasCrafted ~= nil then
|
||||||
|
data.craftable.craftedItems[data.item] = true
|
||||||
|
triggerCallback(getScript()..":server:setPlayerMetadata", "craftedItems", data.craftable.craftedItems)
|
||||||
|
end
|
||||||
|
if data.craft.exp ~= nil then
|
||||||
|
craftingLevel += data.craft["exp"].give * craftAmount
|
||||||
|
triggerCallback(getScript()..":server:setPlayerMetadata", "craftingLevel", craftingLevel)
|
||||||
|
end
|
||||||
|
--if data.craft.Recipes[1].oneUse == true then
|
||||||
|
-- removeItem("craftrecipe", 1, nil, data.craftable.Recipes[1].slot)
|
||||||
|
-- local breakId = GetSoundId()
|
||||||
|
-- PlaySoundFromEntity(breakId, "Drill_Pin_Break", Ped, "DLC_HEIST_FLEECA_SOUNDSET", 1, 0)
|
||||||
|
-- canReturn = false
|
||||||
|
--end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::finishEarly::
|
||||||
|
if craftProp then destroyProp(craftProp) end
|
||||||
|
|
||||||
|
if data.sound then
|
||||||
|
StopSound(data.sound.soundId)
|
||||||
|
end
|
||||||
|
if data.requiredItemfunc then
|
||||||
|
data.requiredItemfunc()
|
||||||
|
end
|
||||||
|
|
||||||
|
--Wait(500)
|
||||||
|
stopCam(100)
|
||||||
|
CraftLock = false
|
||||||
|
if canReturn then craftingMenu(origData) end
|
||||||
|
ClearPedTasks(Ped)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Server Event Handler: Crafted Item
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Server event handler for giving the crafted item to the player.
|
||||||
|
---
|
||||||
|
--- Removes required ingredients from the player's inventory or stash,
|
||||||
|
--- then adds the crafted item to their inventory.
|
||||||
|
---
|
||||||
|
--- @param ItemMake string The item being crafted.
|
||||||
|
--- @param craftable table The crafting recipe and details.
|
||||||
|
--- @param stashName string|table The stash name(s) to remove ingredients from.
|
||||||
|
--- @param metadata table (optional) Metadata for the crafted item.
|
||||||
|
--- @usage
|
||||||
|
RegisterNetEvent(getScript()..":Crafting:GetItem", function(ItemMake, craftable, stashName, metadata, token)
|
||||||
|
local src = source
|
||||||
|
-- debugPrint("^6Bridge^7: ^4"..getScript().."^7:^4Crafting^7:^4GetItem ^2invoked by^7: "..GetInvokingResource())
|
||||||
|
if GetInvokingResource() and GetInvokingResource() ~= getScript() and GetInvokingResource() ~= "qb-core" then
|
||||||
|
debugPrint("^1ERROR^7: ^1Possible exploit^7, ^1vital function was called from an external resource^7")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not checkToken(src, token, "item", ItemMake) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local hasItems, hasTable = hasItem(ItemMake, 1, src)
|
||||||
|
if stashName then
|
||||||
|
local itemRemove = {}
|
||||||
|
if type(stashName) == "table" then
|
||||||
|
for _, name in pairs(stashName) do
|
||||||
|
stashItems = getStash(name)
|
||||||
|
for k, v in pairs(craftable[ItemMake]) do
|
||||||
|
for _, b in pairs(stashItems or {}) do
|
||||||
|
if k == b.name then
|
||||||
|
itemRemove[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
stashItems = getStash(stashName)
|
||||||
|
for k, v in pairs(craftable[ItemMake]) do
|
||||||
|
for _, b in pairs(stashItems or {}) do
|
||||||
|
if k == b.name then
|
||||||
|
itemRemove[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stashRemoveItem(stashItems, stashName, itemRemove)
|
||||||
|
else
|
||||||
|
if craftable[ItemMake] then
|
||||||
|
for k, v in pairs(craftable[ItemMake]) do
|
||||||
|
removeItem(tostring(k), v, src)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
addItem(ItemMake, craftable.amount or 1, metadata, src)
|
||||||
|
-- Optionally, add experience here:
|
||||||
|
-- for example:
|
||||||
|
-- if isStarted("core_skills") then exports["core_skills"]:AddExperience(src, 2) end
|
||||||
|
|
||||||
|
end)
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
--[[
|
||||||
|
Text Drawing Module
|
||||||
|
---------------------
|
||||||
|
This module provides functions to display and hide text on screen using
|
||||||
|
various frameworks: QB, OX, GTA, and ESX.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Text system handlers
|
||||||
|
local textHandlers = {
|
||||||
|
qb = {
|
||||||
|
show =
|
||||||
|
function(text, image)
|
||||||
|
if image then
|
||||||
|
text = '<img src="'..(radarTable[image] or "")..'" style="width:12px;height:12px">'..text
|
||||||
|
end
|
||||||
|
exports[QBExport]:DrawText(text, 'left')
|
||||||
|
end,
|
||||||
|
hide =
|
||||||
|
function()
|
||||||
|
exports[QBExport]:HideText()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
ox = {
|
||||||
|
show =
|
||||||
|
function(input, image, oxStyleTable)
|
||||||
|
local inputCount = #input
|
||||||
|
for i = 1, inputCount do
|
||||||
|
input[i] = input[i] .. (i ~= inputCount and " \n" or "")
|
||||||
|
end
|
||||||
|
lib.showTextUI(table.concat(input), {
|
||||||
|
icon = (image and radarTable[image] or image) or nil,
|
||||||
|
position = 'left-center',
|
||||||
|
style = oxStyleTable
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
hide =
|
||||||
|
function()
|
||||||
|
lib.hideTextUI()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
lation = {
|
||||||
|
show =
|
||||||
|
function(input, image)
|
||||||
|
local inputCount = #input
|
||||||
|
for i = 1, inputCount do
|
||||||
|
input[i] = input[i] .. (i ~= inputCount and " \n" or "")
|
||||||
|
end
|
||||||
|
exports.lation_ui:showText({
|
||||||
|
description = table.concat(input),
|
||||||
|
keybind = nil,
|
||||||
|
icon = (image and radarTable[image] or image) or nil,
|
||||||
|
iconColor = '#3B82F6',
|
||||||
|
position = 'left-center'
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
hide =
|
||||||
|
function()
|
||||||
|
exports.lation_ui:hideText()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
esx = {
|
||||||
|
show =
|
||||||
|
function(text, image)
|
||||||
|
if image then
|
||||||
|
text = '<img src="'..(radarTable[image] or "")..'" style="width:12px;height:12px">'..text
|
||||||
|
end
|
||||||
|
ESX.TextUI(text, nil)
|
||||||
|
end,
|
||||||
|
hide =
|
||||||
|
function()
|
||||||
|
ESX.HideUI()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
gta = {
|
||||||
|
show =
|
||||||
|
function(input, image, style)
|
||||||
|
local text = ""
|
||||||
|
for i = 1, #input do
|
||||||
|
if input[i] ~= "" then
|
||||||
|
text = text .. input[i] .. "\n~s~"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if image then
|
||||||
|
text = "~BLIP_" .. image .. "~ " .. text
|
||||||
|
end
|
||||||
|
DisplayHelpMsg(text:gsub("%:", ":~" .. (style or "g") .. "~"))
|
||||||
|
end,
|
||||||
|
hide =
|
||||||
|
function()
|
||||||
|
ClearAllHelpMessages()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
red = {
|
||||||
|
show =
|
||||||
|
function(input)
|
||||||
|
local text = ""
|
||||||
|
for i = 1, #input do
|
||||||
|
if input[i] ~= "" then
|
||||||
|
text = text .. input[i] .. "\n~q~"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
TriggerEvent("jim-redui:DrawText", text)
|
||||||
|
end,
|
||||||
|
hide =
|
||||||
|
function()
|
||||||
|
TriggerEvent("jim-redui:HideText")
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Displays text on the screen using the configured draw text system.
|
||||||
|
---
|
||||||
|
--- Depending on Config.System.drawText, this function will use different methods to
|
||||||
|
--- display text along with optional images/icons.
|
||||||
|
---
|
||||||
|
--- @param image string|nil Optional image/icon identifier to display with the text.
|
||||||
|
--- @param input table An array of strings; each string is a line of text to display.
|
||||||
|
--- @param style string|nil Optional style code for default GTA popups (e.g., "~g~" for green).
|
||||||
|
--- @param oxStyleTable table|nil Optional table specifying style parameters for the OX text UI.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- drawText("img_link", { "Test line 1", "Test Line 2" }, "~g~")
|
||||||
|
--- ```
|
||||||
|
function drawText(image, input, style, oxStyleTable)
|
||||||
|
if not radarTable then radarTable = {} end
|
||||||
|
|
||||||
|
local systemType = Config.System.drawText
|
||||||
|
local handler = textHandlers[systemType]
|
||||||
|
|
||||||
|
if not handler then return end
|
||||||
|
|
||||||
|
if systemType == "qb" or systemType == "esx" then
|
||||||
|
-- Concatenate lines for QB/ESX system with HTML line breaks.
|
||||||
|
local text = ""
|
||||||
|
for i = 1, #input do
|
||||||
|
text = text .. input[i] .. "</span>" .. (input[i + 1] and "<br>" or "")
|
||||||
|
end
|
||||||
|
text = text:gsub("%:", ":<span style='color:yellow'>")
|
||||||
|
handler.show(text, image)
|
||||||
|
|
||||||
|
elseif systemType == "ox" then
|
||||||
|
handler.show(input, image, oxStyleTable)
|
||||||
|
|
||||||
|
elseif systemType == "lation" then
|
||||||
|
handler.show(input, image)
|
||||||
|
|
||||||
|
elseif systemType == "gta" then
|
||||||
|
handler.show(input, image, style)
|
||||||
|
|
||||||
|
elseif systemType == "red" then
|
||||||
|
handler.show(input)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Hides any text currently displayed on the screen.
|
||||||
|
---
|
||||||
|
--- Clears the text using the appropriate method for the configured draw text system.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- hideText()
|
||||||
|
--- ```
|
||||||
|
function hideText()
|
||||||
|
local handler = textHandlers[Config.System.drawText]
|
||||||
|
if handler and handler.hide then
|
||||||
|
handler.hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
if gameName ~= "rdr3" then
|
||||||
|
--[[
|
||||||
|
DUI Module (Experimental)
|
||||||
|
--------------------------
|
||||||
|
This module handles the creation, modification, and removal of custom DUI (Display UI)
|
||||||
|
elements using runtime textures. It supports both client and server functionality to update DUI
|
||||||
|
images dynamically.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Create a runtime texture dictionary on the client if not running on the server.
|
||||||
|
scriptTxd = not isServer() and CreateRuntimeTxd(getScript()..'scriptTxd') or nil
|
||||||
|
customDUIList = {}
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- DUI Client Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Creates or updates a DUI element.
|
||||||
|
---
|
||||||
|
--- @param name string The unique name for the DUI element.
|
||||||
|
--- @param http string The URL to load into the DUI.
|
||||||
|
--- @param size table A table with .x and .y fields specifying the DUI dimensions.
|
||||||
|
--- @param txd table The runtime texture dictionary where the DUI texture will be created.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- createDui("logo", "https://example.com/logo.png", { x = 512, y = 256 }, scriptTxd)
|
||||||
|
--- ```
|
||||||
|
function createDui(name, http, size, txd)
|
||||||
|
if not customDUIList[name] then
|
||||||
|
local newDui = CreateDui(http, math.floor(size.x), math.floor(size.y))
|
||||||
|
while not GetDuiHandle(newDui) do Wait(0) end
|
||||||
|
CreateRuntimeTextureFromDuiHandle(txd, name, GetDuiHandle(newDui))
|
||||||
|
customDUIList[name] = newDui
|
||||||
|
SetDuiUrl(customDUIList[name], http)
|
||||||
|
else
|
||||||
|
SetDuiUrl(customDUIList[name], http)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Opens a DUI selection input allowing the user to change the DUI image URL.
|
||||||
|
---
|
||||||
|
--- @param data table A table containing DUI data:
|
||||||
|
--- - name: The key name in the DUI list.
|
||||||
|
--- - texn: The texture name.
|
||||||
|
--- - texd: The texture dictionary.
|
||||||
|
--- - size: A table with .x and .y dimensions.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- DuiSelect({ name = "logo", texn = "logoTex", texd = "someTxd", size = { x = 512, y = 256 } })
|
||||||
|
--- ```
|
||||||
|
function DuiSelect(data)
|
||||||
|
local imagePreview = ""
|
||||||
|
--local imagePreview = "<center>- Current Image -<br>" ..
|
||||||
|
-- "<img src="..data.url.." width=150px><br>" ..
|
||||||
|
-- "Size: ["..math.floor(data.size.x)..", "..math.floor(data.size.y).."]<br><br>"
|
||||||
|
|
||||||
|
local dialog = createInput(imagePreview, {
|
||||||
|
{ type = "text", text = "dui_url", name = "url", isRequired = true },
|
||||||
|
})
|
||||||
|
|
||||||
|
if dialog then
|
||||||
|
data.url = dialog.url or dialog[1]
|
||||||
|
-- Scan URL for valid image extension and banned words.
|
||||||
|
local searchList = { "png", "jpg", "jpeg", "gif", "webp", "bmp" }
|
||||||
|
local banList = { "porn" }
|
||||||
|
local searchFound = false
|
||||||
|
for _, ext in pairs(searchList) do
|
||||||
|
if string.find(tostring(data.url), ext) then
|
||||||
|
searchFound = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, banned in pairs(banList) do
|
||||||
|
if string.find(tostring(data.url), banned) then
|
||||||
|
searchFound = false
|
||||||
|
print("BANNED WORD: "..banned)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if searchFound then
|
||||||
|
TriggerServerEvent(getScript()..":Server:ChangeDUI", data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Client event handler to update DUI elements.
|
||||||
|
RegisterNetEvent(getScript()..":Client:ChangeDUI", function(data)
|
||||||
|
debugPrint("^6Bridge^7: ^2Receiving new DUI ^7- ^6"..data.url.."^7")
|
||||||
|
if tostring(data.url) ~= "-" then
|
||||||
|
createDui(data.texn, tostring(data.url), data.size, scriptTxd)
|
||||||
|
AddReplaceTexture(tostring(data.texd), tostring(data.texn), getScript().."scriptTxd", tostring(data.texn))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Client event handler to clear DUI elements.
|
||||||
|
RegisterNetEvent(getScript()..":Client:ClearDUI", function(data)
|
||||||
|
if customDUIList[tostring(data.texn)] then
|
||||||
|
RemoveReplaceTexture(tostring(data.texd), tostring(data.texn))
|
||||||
|
if IsDuiAvailable(customDUIList[tostring(data.texn)]) then
|
||||||
|
SetDuiUrl(customDUIList[data.name], nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- DUI Server Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Server event handler to change DUI settings.
|
||||||
|
--- If no URL is provided, resets to the preset value.
|
||||||
|
RegisterNetEvent(getScript()..":Server:ChangeDUI", function(data)
|
||||||
|
if not data.url then
|
||||||
|
debugPrint("^6Bridge^7: ^2Preset: ^6"..tostring(data.preset).."^7")
|
||||||
|
data.url = data.preset
|
||||||
|
else
|
||||||
|
for k, v in pairs(Locations[data.name].duiList) do
|
||||||
|
if v.tex.texn == data.texn then
|
||||||
|
Locations[data.name].duiList[k].url = data.url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
debugPrint("^6Bridge^7: ^3DUI^2 Sending new DUI to all players^7 - ^6"..data.url.."^7")
|
||||||
|
TriggerClientEvent(getScript()..":Client:ChangeDUI", -1, data)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Server event handler to clear DUI settings.
|
||||||
|
RegisterNetEvent(getScript()..":Server:ClearDUI", function(data)
|
||||||
|
if data.url == "-" then
|
||||||
|
for k, v in pairs(Locations[data.name].duiList) do
|
||||||
|
if v.tex.texn == data.texn then
|
||||||
|
Locations[data.name].duiList[k].url = "-"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
TriggerClientEvent(getScript()..":Client:ClearDUI", -1, data)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Resource Cleanup
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
onResourceStop(function()
|
||||||
|
for k, v in pairs(duiList or {}) do
|
||||||
|
for i = 1, #v do
|
||||||
|
RemoveReplaceTexture(tostring(v[i].tex.texd), tostring(v[i].tex.texn))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, true)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- DUI List Callback (Server)
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
if isServer() then
|
||||||
|
createCallback(getScript()..":Server:duiList", function(source)
|
||||||
|
return duiList
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
--Screen Effects
|
||||||
|
local alienEffect = false
|
||||||
|
function AlienEffect()
|
||||||
|
if alienEffect then return else alienEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3AlienEffect^7() ^2activated")
|
||||||
|
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
|
||||||
|
Wait(math.random(5000, 8000))
|
||||||
|
local Ped = PlayerPedId()
|
||||||
|
local animDict = "MOVE_M@DRUNK@VERYDRUNK"
|
||||||
|
RequestAnimSet(animDict)
|
||||||
|
while not HasAnimSetLoaded(animDict) do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
SetPedCanRagdoll(Ped, true)
|
||||||
|
ShakeGameplayCam('DRUNK_SHAKE', 2.80)
|
||||||
|
SetTimecycleModifier("Drunk")
|
||||||
|
SetPedMovementClipset(Ped, animDict, 1)
|
||||||
|
SetPedMotionBlur(Ped, true)
|
||||||
|
SetPedIsDrunk(Ped, true)
|
||||||
|
Wait(1500)
|
||||||
|
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
|
||||||
|
Wait(13500)
|
||||||
|
SetPedToRagdoll(Ped, 5000, 1000, 1, 0, 0, 0)
|
||||||
|
Wait(120500)
|
||||||
|
ClearTimecycleModifier()
|
||||||
|
ResetScenarioTypesEnabled()
|
||||||
|
ResetPedMovementClipset(Ped, 0)
|
||||||
|
SetPedIsDrunk(Ped, false)
|
||||||
|
SetPedMotionBlur(Ped, false)
|
||||||
|
AnimpostfxStopAll()
|
||||||
|
ShakeGameplayCam('DRUNK_SHAKE', 0.0)
|
||||||
|
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
|
||||||
|
Wait(math.random(45000, 60000))
|
||||||
|
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFightIn")
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFight")
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFightOut")
|
||||||
|
alienEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3AlienEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
local weedEffect = false
|
||||||
|
function WeedEffect()
|
||||||
|
if weedEffect then return else weedEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3WeedEffect^7() ^2activated")
|
||||||
|
AnimpostfxPlay("DrugsMichaelAliensFightIn", 3.0, 0)
|
||||||
|
Wait(math.random(3000, 20000))
|
||||||
|
AnimpostfxPlay("DrugsMichaelAliensFight", 3.0, 0)
|
||||||
|
Wait(math.random(15000, 20000))
|
||||||
|
AnimpostfxPlay("DrugsMichaelAliensFightOut", 3.0, 0)
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFightIn")
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFight")
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFightOut")
|
||||||
|
weedEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3WeedEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
local trevorEffect = false
|
||||||
|
function TrevorEffect()
|
||||||
|
if trevorEffect then return else trevorEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2activated")
|
||||||
|
AnimpostfxPlay("DrugsTrevorClownsFightIn", 3.0, 0)
|
||||||
|
Wait(3000)
|
||||||
|
AnimpostfxPlay("DrugsTrevorClownsFight", 3.0, 0)
|
||||||
|
Wait(30000)
|
||||||
|
AnimpostfxPlay("DrugsTrevorClownsFightOut", 3.0, 0)
|
||||||
|
AnimpostfxStop("DrugsTrevorClownsFight")
|
||||||
|
AnimpostfxStop("DrugsTrevorClownsFightIn")
|
||||||
|
AnimpostfxStop("DrugsTrevorClownsFightOut")
|
||||||
|
trevorEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3TrevorEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
local turboEffect = false
|
||||||
|
function TurboEffect()
|
||||||
|
if turboEffect then return else turboEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3TurboEffect^7() ^2activated")
|
||||||
|
AnimpostfxPlay('RaceTurbo', 0, true)
|
||||||
|
SetTimecycleModifier('rply_motionblur')
|
||||||
|
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
|
||||||
|
Wait(30000)
|
||||||
|
StopGameplayCamShaking(true)
|
||||||
|
SetTransitionTimecycleModifier('default', 0.35)
|
||||||
|
Wait(1000)
|
||||||
|
ClearTimecycleModifier()
|
||||||
|
AnimpostfxStop('RaceTurbo')
|
||||||
|
turboEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3TurboEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
local rampageEffect = false
|
||||||
|
function RampageEffect()
|
||||||
|
if rampageEffect then return else rampageEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3RampageEffect^7() ^2activated")
|
||||||
|
AnimpostfxPlay('Rampage', 0, true)
|
||||||
|
SetTimecycleModifier('rply_motionblur')
|
||||||
|
ShakeGameplayCam('SKY_DIVING_SHAKE', 0.25)
|
||||||
|
Wait(30000)
|
||||||
|
StopGameplayCamShaking(true)
|
||||||
|
SetTransitionTimecycleModifier('default', 0.35)
|
||||||
|
Wait(1000)
|
||||||
|
ClearTimecycleModifier()
|
||||||
|
AnimpostfxStop('Rampage')
|
||||||
|
rampageEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3RampageEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
local focusEffect = false
|
||||||
|
function FocusEffect()
|
||||||
|
if focusEffect then return else focusEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3FocusEffect^7() ^2activated")
|
||||||
|
Wait(1000)
|
||||||
|
AnimpostfxPlay('FocusIn', 0, true)
|
||||||
|
Wait(30000)
|
||||||
|
AnimpostfxStop('FocusIn')
|
||||||
|
focusEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3FocusEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
local nightVisionEffect = false
|
||||||
|
function NightVisionEffect()
|
||||||
|
if nightVisionEffect then return else nightVisionEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2activated")
|
||||||
|
SetNightvision(true)
|
||||||
|
Wait(math.random(3000, 4000)) -- FEEL FREE TO CHANGE THIS
|
||||||
|
SetNightvision(false)
|
||||||
|
SetSeethrough(false)
|
||||||
|
nightVisionEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3NightVisionEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
local thermalEffect = false
|
||||||
|
function ThermalEffect()
|
||||||
|
if thermalEffect then return else thermalEffect = true end
|
||||||
|
debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2activated")
|
||||||
|
SetNightvision(true)
|
||||||
|
SetSeethrough(true)
|
||||||
|
Wait(math.random(2000, 3000)) -- FEEL FREE TO CHANGE THIS
|
||||||
|
SetNightvision(false)
|
||||||
|
SetSeethrough(false)
|
||||||
|
thermalEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3ThermalEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
|
||||||
|
--Built-in Buff effects
|
||||||
|
local healEffect = false
|
||||||
|
function HealEffect(data)
|
||||||
|
if healEffect then return end
|
||||||
|
debugPrint("^5Debug^7: ^3HealEffect^7() ^2activated")
|
||||||
|
healEffect = true
|
||||||
|
local count = (data[1] / 1000)
|
||||||
|
while count > 0 do
|
||||||
|
Wait(1000)
|
||||||
|
count -= 1
|
||||||
|
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) + data[2])
|
||||||
|
end
|
||||||
|
healEffect = false
|
||||||
|
debugPrint("^5Debug^7: ^3HealEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
|
||||||
|
local staminaEffect = false
|
||||||
|
function StaminaEffect(data)
|
||||||
|
if staminaEffect then return end
|
||||||
|
debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2activated")
|
||||||
|
staminaEffect = true
|
||||||
|
local startStamina = (data[1] / 1000)
|
||||||
|
SetRunSprintMultiplierForPlayer(PlayerId(), 1.49)
|
||||||
|
while startStamina > 0 do
|
||||||
|
Wait(1000)
|
||||||
|
if math.random(5, 100) < 10 then RestorePlayerStamina(PlayerId(), data[2]) end
|
||||||
|
startStamina -= 1
|
||||||
|
if math.random(5, 100) < 51 then end
|
||||||
|
end
|
||||||
|
startStamina = 0
|
||||||
|
SetRunSprintMultiplierForPlayer(PlayerId(), 1.0)
|
||||||
|
staminaEffect = false
|
||||||
|
debugPrint("^5Bridge^7: ^3StaminaEffect^7() ^2stopped")
|
||||||
|
end
|
||||||
|
|
||||||
|
function StopEffects() -- Used to clear up any effects stuck on screen
|
||||||
|
debugPrint("^5Bridge^7: ^2All screen effects stopped")
|
||||||
|
ShakeGameplayCam('DRUNK_SHAKE', 0.0)
|
||||||
|
SetPedToRagdoll(PlayerPedId(), 5000, 1000, 1, 0, 0, 0)
|
||||||
|
ClearTimecycleModifier()
|
||||||
|
ResetScenarioTypesEnabled()
|
||||||
|
ResetPedMovementClipset(PlayerPedId(), 0)
|
||||||
|
SetPedIsDrunk(PlayerPedId(), false)
|
||||||
|
SetPedMotionBlur(PlayerPedId(), false)
|
||||||
|
SetNightvision(false)
|
||||||
|
SetSeethrough(false)
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFightIn")
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFight")
|
||||||
|
AnimpostfxStop("DrugsMichaelAliensFightOut")
|
||||||
|
AnimpostfxStop("DrugsTrevorClownsFight")
|
||||||
|
AnimpostfxStop("DrugsTrevorClownsFightIn")
|
||||||
|
AnimpostfxStop("DrugsTrevorClownsFightOut")
|
||||||
|
AnimpostfxStop('RaceTurbo')
|
||||||
|
AnimpostfxStop('FocusIn')
|
||||||
|
AnimpostfxStop('Rampage')
|
||||||
|
end
|
||||||
@@ -0,0 +1,978 @@
|
|||||||
|
--[[
|
||||||
|
Utility Functions for Resource Management and Debugging
|
||||||
|
----------------------------------------------------------
|
||||||
|
This script provides a set of utility functions for managing resources,
|
||||||
|
debugging, and handling common tasks in the game environment.
|
||||||
|
It includes functions to check resource states, generate unique keys,
|
||||||
|
format numbers and coordinates, handle JSON data, perform raycasts, and more.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Resource and Environment Checks
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Checks if a specific resource is started.
|
||||||
|
--- @param script string The name of the resource.
|
||||||
|
--- @return boolean boolean True if the resource state contains "start", false otherwise.
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- if isStarted("myResource") then
|
||||||
|
--- print("Resource is running")
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function isStarted(script)
|
||||||
|
if not script then
|
||||||
|
print("^1Error^7: ^1Tried to check if ^3nil^2 was started^7, ^1returning ^4false^7")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return GetResourceState(script):find("start") ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local scriptName = nil
|
||||||
|
|
||||||
|
--- Retrieves the current resource name, caching it for efficiency.
|
||||||
|
--- @return string string The current resource name.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local currentScript = getScript()
|
||||||
|
--- print("Current script:", currentScript)
|
||||||
|
--- ```
|
||||||
|
function getScript()
|
||||||
|
if not scriptName then scriptName = GetCurrentResourceName() end
|
||||||
|
return scriptName
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Determines if the current context is the server.
|
||||||
|
--- @return boolean boolean True if running on the server, false otherwise.
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- if isServer() then
|
||||||
|
--- -- Server-specific code
|
||||||
|
--- else
|
||||||
|
--- -- Client-specific code
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function isServer()
|
||||||
|
return IsDuplicityVersion()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Allows the user to check if an export exists before trying to call it
|
||||||
|
--- @return boolean boolean True if it exists
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- if checkExportExists("qb-inventory", "OpenInventory") then
|
||||||
|
--- exports["qb-inventory"]:OpenInventory()
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function checkExportExists(resource, export)
|
||||||
|
if not resource or not export then return false end
|
||||||
|
|
||||||
|
local exists = false
|
||||||
|
local ok, result = pcall(function()
|
||||||
|
exists = type(exports[resource][export]) == "function"
|
||||||
|
end)
|
||||||
|
|
||||||
|
if ok and exists then
|
||||||
|
debugPrint(("^3Export Check^7: ^5exports^7['^5%s^7']:^5%s^7() ^2Exists^7"):format(resource, export))
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
debugPrint(("^3Export Check^7: ^5exports^7['^5%s^7']:^5%s^7() ^1Doesn^7'^1t Exist^7"):format(resource, export))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Debugging and JSON Utilities
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Prints debug messages if debugMode is enabled.
|
||||||
|
--- Concatenates all arguments and prints them with debug info.
|
||||||
|
--- @param ... any One or more values to print.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- debugPrint("Player has joined:", playerName)
|
||||||
|
--- ```
|
||||||
|
function debugPrint(...)
|
||||||
|
if debugMode then
|
||||||
|
local args = {...}
|
||||||
|
local output = table.concat(args, " ")
|
||||||
|
print(output, getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Prints event-related debug messages if event debugging is enabled.
|
||||||
|
--- @param ... any One or more values to print.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- eventPrint("Event triggered:", eventName)
|
||||||
|
--- ```
|
||||||
|
function eventPrint(...)
|
||||||
|
if Config.System.EventDebug then
|
||||||
|
print(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns the keys of a table in sorted order.
|
||||||
|
--- @param tbl table The table to sort keys for.
|
||||||
|
--- @return table table A sorted array of keys.
|
||||||
|
function getSortedKeys(tbl)
|
||||||
|
local keys = {}
|
||||||
|
for k in pairs(tbl) do keys[#keys + 1] = k end
|
||||||
|
table.sort(keys, function(a, b)
|
||||||
|
local numA, numB = tonumber(a), tonumber(b)
|
||||||
|
if numA and numB then return numA < numB else return tostring(a) < tostring(b) end
|
||||||
|
end)
|
||||||
|
return keys
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Recursively colorizes a table for debug printing.
|
||||||
|
--- @param tbl table The table to colorize.
|
||||||
|
--- @return table table A new table with colorized keys and values.
|
||||||
|
--- ```lua
|
||||||
|
--- local colorizedData = colorizeTable(myTable)
|
||||||
|
--- jsonPrint(colorizedData)
|
||||||
|
--- ```
|
||||||
|
function colorizeTable(tbl)
|
||||||
|
local newData, sortedKeys = {}, getSortedKeys(tbl)
|
||||||
|
for _, k in ipairs(sortedKeys) do
|
||||||
|
local v = tbl[k]
|
||||||
|
newData["^6"..tostring(k).."^7"] =
|
||||||
|
(type(v) == "table" and colorizeTable(v))
|
||||||
|
or (tostring(type(v)):find("vector") and formatCoord(v))
|
||||||
|
or "^2"..tostring(v).."^7"
|
||||||
|
end
|
||||||
|
return newData
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Encodes a table into an ordered JSON string with indentation.
|
||||||
|
--- @param data table The table to encode.
|
||||||
|
--- @param indent string The indentation string (e.g., " ").
|
||||||
|
--- @param level number The current level of indentation.
|
||||||
|
--- @return string The formatted JSON string.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local jsonString = encodeOrderedJSON(myTable, " ", 0)
|
||||||
|
--- print(jsonString)
|
||||||
|
--- ```
|
||||||
|
function encodeOrderedJSON(data, indent, level)
|
||||||
|
local jsonParts, prefix, sortedKeys = {"{"}, string.rep(indent, level), getSortedKeys(data)
|
||||||
|
for i, k in ipairs(sortedKeys) do
|
||||||
|
jsonParts[#jsonParts + 1] = (i > 1 and ",\n" or "\n")..prefix..indent..json.encode(k)..": "
|
||||||
|
jsonParts[#jsonParts + 1] = (type(data[k]) == "table") and encodeOrderedJSON(data[k], indent, level + 1) or json.encode(data[k])
|
||||||
|
end
|
||||||
|
jsonParts[#jsonParts + 1] = "\n"..prefix.."}"
|
||||||
|
return table.concat(jsonParts)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Prints a table as a colorized and ordered JSON string if debugMode is enabled.
|
||||||
|
--- @param data table The table to print.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- jsonPrint(myTable)
|
||||||
|
--- ```
|
||||||
|
function jsonPrint(data)
|
||||||
|
if debugMode then
|
||||||
|
print(encodeOrderedJSON(colorizeTable(data), " ", 0), getDebugInfo(debug.getinfo(2, "nSl")))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Retrieves the current time formatted for debug prints.
|
||||||
|
--- @return string string The formatted time string, e.g., "^7(14:23:45)".
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local currentTime = GetPrintTime()
|
||||||
|
--- debugPrint("Current Time:", currentTime)
|
||||||
|
--- ```
|
||||||
|
function GetPrintTime()
|
||||||
|
if isServer() then
|
||||||
|
local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
|
||||||
|
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
|
||||||
|
else
|
||||||
|
if gameName == "rdr3" then return "" end
|
||||||
|
local _, _, _, hour, min, sec = GetLocalTime()
|
||||||
|
return "^7("..string.format("%02d", hour)..":"..string.format("%02d", min)..":"..string.format("%02d", sec)..")"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Generates a unique 3-character alphanumeric key.
|
||||||
|
--- @return string string The generated key.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local uniqueKey = keyGen()
|
||||||
|
--- print("Generated Key:", uniqueKey)
|
||||||
|
--- ```
|
||||||
|
function keyGen()
|
||||||
|
local charset = {
|
||||||
|
"q","w","e","r","t","y","u","i","o","p",
|
||||||
|
"a","s","d","f","g","h","j","k","l",
|
||||||
|
"z","x","c","v","b","n","m",
|
||||||
|
"Q","W","E","R","T","Y","U","I","O","P",
|
||||||
|
"A","S","D","F","G","H","J","K","L",
|
||||||
|
"Z","X","C","V","B","N","M",
|
||||||
|
"1","2","3","4","5","6","7","8","9","0"
|
||||||
|
}
|
||||||
|
local GeneratedID = ""
|
||||||
|
for i = 1, 3 do
|
||||||
|
GeneratedID = GeneratedID..charset[math.random(1, #charset)]
|
||||||
|
end
|
||||||
|
return GeneratedID
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Formatting and Vector Math Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Formats a number with commas as thousand separators.
|
||||||
|
--- @param amount number The number to format.
|
||||||
|
--- @return string string The formatted number.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local formattedNumber = cv(1000000) -- "1,000,000"
|
||||||
|
--- print(formattedNumber)
|
||||||
|
--- ``
|
||||||
|
function cv(amount)
|
||||||
|
local formatted = tostring(amount or "0")
|
||||||
|
while true do
|
||||||
|
local newFormatted, count = formatted:gsub("^(-?%d+)(%d%d%d)", '%1,%2')
|
||||||
|
if count == 0 then break end
|
||||||
|
formatted = newFormatted
|
||||||
|
end
|
||||||
|
return formatted
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Formats a coordinate vector for debug printing.
|
||||||
|
--- @param coord table A vector3 or vector4 with x, y, z (and optional w).
|
||||||
|
--- @return string string The formatted coordinate string.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local formattedCoord = formatCoord(vector3(100.0, 200.0, 300.0))
|
||||||
|
--- debugPrint("Player Position:", formattedCoord)
|
||||||
|
--- ```
|
||||||
|
function formatCoord(coord)
|
||||||
|
local vecType = type(coord):gsub("tor", "")
|
||||||
|
local parts = {}
|
||||||
|
|
||||||
|
if coord.x then parts[#parts + 1] = string.format("^6%.1f", coord.x) end
|
||||||
|
if coord.y then parts[#parts + 1] = string.format("^6%.1f", coord.y) end
|
||||||
|
if coord.z then parts[#parts + 1] = string.format("^6%.1f", coord.z) end
|
||||||
|
if coord.w then parts[#parts + 1] = string.format("^6%.1f", coord.w) end
|
||||||
|
|
||||||
|
return string.format("^5%s^7(%s^7)", vecType, table.concat(parts, "^7, "))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculates the center point of a list of coordinates.
|
||||||
|
--- @param tbl table An array of vector3 coordinates.
|
||||||
|
--- @return vector3 vector3 The center coordinate.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local center = getCenterOfZones({vector3(100, 200, 300), vector3(110, 210, 310)})
|
||||||
|
--- print("Center of Zones:", center)
|
||||||
|
--- ```
|
||||||
|
function getCenterOfZones(tbl)
|
||||||
|
local count = #tbl
|
||||||
|
if count == 0 then return vector3(0, 0, 0) end
|
||||||
|
|
||||||
|
local totalX, totalY, totalZ = 0, 0, 0
|
||||||
|
for i = 1, count do
|
||||||
|
local coord = tbl[i]
|
||||||
|
totalX = totalX + coord.x
|
||||||
|
totalY = totalY + coord.y
|
||||||
|
totalZ = totalZ + coord.z
|
||||||
|
end
|
||||||
|
|
||||||
|
return vector3(totalX / count, totalY / count, totalZ / count)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Counts the number of keys in a table.
|
||||||
|
--- @param tbl table The table to count.
|
||||||
|
--- @return number number The key count.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local count = countTable(myTable)
|
||||||
|
--- print("Number of keys:", count)
|
||||||
|
--- ```
|
||||||
|
function countTable(tbl)
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(tbl) do count = count + 1 end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns an iterator over a table's keys in sorted order.
|
||||||
|
--- @param t table The table to iterate over.
|
||||||
|
--- @return function An iterator function for sorted keys.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- for k, v in pairsByKeys(myTable) do
|
||||||
|
--- print(k, v)
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function pairsByKeys(t)
|
||||||
|
if not t then
|
||||||
|
print("^1Error^7: ^3Nil ^2table recieved for ^3pairsByKeys^7(), ^2setting to ^7{} ^2to prevent break^7")
|
||||||
|
t = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local keys = {}
|
||||||
|
for key in pairs(t) do
|
||||||
|
keys[#keys + 1] = key
|
||||||
|
end
|
||||||
|
table.sort(keys)
|
||||||
|
|
||||||
|
local index = 0
|
||||||
|
return function()
|
||||||
|
index = index + 1
|
||||||
|
local key = keys[index]
|
||||||
|
return key, t[key]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a new table with consecutive numerical indices sorted by the 'id' field.
|
||||||
|
--- @param originalTable table The table containing entries with an 'id' field.
|
||||||
|
--- @return table table A sorted table with consecutive indices.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local sortedTable = createConsecutiveTable(originalTable)
|
||||||
|
--- for i, entry in ipairs(sortedTable) do
|
||||||
|
--- print(i, entry)
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function createConsecutiveTable(originalTable)
|
||||||
|
local entries = {}
|
||||||
|
for _, entry in pairs(originalTable) do
|
||||||
|
entries[#entries + 1] = entry
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(entries, function(a, b) return a.id < b.id end)
|
||||||
|
|
||||||
|
for index, entry in ipairs(entries) do
|
||||||
|
entry.id = index
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Concatenates a table of strings into a single string separated by newlines.
|
||||||
|
--- @param tbl table The table containing strings.
|
||||||
|
--- @return string string The concatenated string.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local combinedText = concatenateText({"Line 1", "Line 2", "Line 3"})
|
||||||
|
--- print(combinedText)
|
||||||
|
--- ```
|
||||||
|
function concatenateText(tbl)
|
||||||
|
return table.concat(tbl, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Converts a rotation (degrees) to a direction vector.
|
||||||
|
--- @param rot vector3 A vector3 with rotation values.
|
||||||
|
--- @return vector3 vector3 The forward direction vector.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local direction = RotationToDirection({ z = 90 })
|
||||||
|
--- print(direction)
|
||||||
|
--- ```
|
||||||
|
function RotationToDirection(rot)
|
||||||
|
local radianConvert = math.pi / 180
|
||||||
|
local rotX, rotZ = radianConvert * rot.x, radianConvert * rot.z
|
||||||
|
local cosX = math.abs(math.cos(rotX))
|
||||||
|
|
||||||
|
return vec3(
|
||||||
|
-math.sin(rotZ) * cosX,
|
||||||
|
math.cos(rotZ) * cosX,
|
||||||
|
math.sin(rotX)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a basic progress bar string.
|
||||||
|
--- @param percentage number Completion percentage (0-100).
|
||||||
|
--- @return string string The progress bar (e.g., "█████░░░░░").
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local bar = basicBar(50) -- "█████░░░░░"
|
||||||
|
--- print(bar)
|
||||||
|
--- ```
|
||||||
|
function basicBar(percentage)
|
||||||
|
local total = 10
|
||||||
|
local filled = math.floor(math.min(math.max(percentage, 0), 100) / 100 * total)
|
||||||
|
return string.rep("█", filled) .. string.rep("░", total - filled)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Normalizes a 3D vector.
|
||||||
|
--- @param vec vector3 A vector3 table.
|
||||||
|
--- @return vector3 vector3 A normalized vector.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local normalizedVec = normalizeVector(vector3(1, 2, 3))
|
||||||
|
--- print(normalizedVec)
|
||||||
|
--- ```
|
||||||
|
function normalizeVector(vec)
|
||||||
|
local length = math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z)
|
||||||
|
return length > 0 and vec3(vec.x / length, vec.y / length, vec.z / length) or vec3(0, 0, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Drawing and Raycasting Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Draws a line between two coordinates (for debugging).
|
||||||
|
--- @param startCoords vector3 The starting coordinate.
|
||||||
|
--- @param endCoords vector3 The ending coordinate.
|
||||||
|
--- @param col vector4 A vector4 specifying color and opacity.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- drawLine(vector3(100, 200, 300), vector3(150, 250, 350), vector4(255, 0, 0, 255))
|
||||||
|
--- ```
|
||||||
|
function drawLine(startCoords, endCoords, col)
|
||||||
|
if not debugMode then return end
|
||||||
|
|
||||||
|
local col = col or vec4(255, 255, 255, 150)
|
||||||
|
CreateThread(function()
|
||||||
|
for i = 100, 0, -1 do
|
||||||
|
DrawLine(startCoords.x, startCoords.y, startCoords.z, endCoords.x, endCoords.y, endCoords.z, col.x, col.y, col.z, col.w)
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Draws a sphere at the specified coordinates (for debugging).
|
||||||
|
--- @param coords vector3 The center of the sphere.
|
||||||
|
--- @param col vector4 A vector4 specifying color and opacity.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- drawSphere(vector3(100, 200, 300), vector4(0, 255, 0, 255))
|
||||||
|
--- ```
|
||||||
|
function drawSphere(coords, col)
|
||||||
|
if not debugMode then return end
|
||||||
|
|
||||||
|
local col = col or vec4(255, 255, 255, 150)
|
||||||
|
CreateThread(function()
|
||||||
|
for i = 100, 0, -1 do
|
||||||
|
DrawSphere(coords.x, coords.y, coords.z, 0.5, col.x, col.y, col.z, col.w)
|
||||||
|
Wait(10)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Performs a raycast between two coordinates and returns the results.
|
||||||
|
--- @param startCoords vector3 The starting coordinate.
|
||||||
|
--- @param endCoords vector3 The ending coordinate.
|
||||||
|
--- @param entity number|nil An entity to ignore.
|
||||||
|
--- @param flags number|nil Optional raycast flags (default: 4294967295).
|
||||||
|
--- @return multiple Multiple values returned by GetShapeTestResultIncludingMaterial.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local hit, hitPos, material = PerformRaycast(startVec, endVec, playerPed, 1)
|
||||||
|
--- if hit == 1 then
|
||||||
|
--- print("Hit at position:", hitPos)
|
||||||
|
--- print("Material:", material)
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function PerformRaycast(startCoords, endCoords, entity, flags)
|
||||||
|
drawLine(startCoords, endCoords, vec4(0, 0, 255, 255))
|
||||||
|
|
||||||
|
local shapeTest = StartExpensiveSynchronousShapeTestLosProbe(
|
||||||
|
startCoords.x, startCoords.y, startCoords.z,
|
||||||
|
endCoords.x, endCoords.y, endCoords.z,
|
||||||
|
flags or 4294967295, entity, 4
|
||||||
|
)
|
||||||
|
|
||||||
|
return GetShapeTestResult(shapeTest)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Adjusts the Z-coordinate of a position to the ground level.
|
||||||
|
--- @param coords vector4 A vector3 or vector4 with x, y, z (and optional w).
|
||||||
|
--- @return vector3|vector4 vector The coordinates adjusted for ground level.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local groundCoords = adjustForGround(playerCoords)
|
||||||
|
--- print("Ground Position:", groundCoords)
|
||||||
|
--- ```
|
||||||
|
function adjustForGround(coords)
|
||||||
|
local foundGround, groundZ = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 1.0)
|
||||||
|
|
||||||
|
if foundGround then
|
||||||
|
return coords.w and vec4(coords.x, coords.y, groundZ, coords.w) or vec3(coords.x, coords.y, groundZ)
|
||||||
|
end
|
||||||
|
|
||||||
|
return coords
|
||||||
|
end
|
||||||
|
|
||||||
|
local function waitForNetworkEntity(netId, converter, timeout)
|
||||||
|
timeout = timeout or 100
|
||||||
|
|
||||||
|
while not NetworkDoesNetworkIdExist(netId) and timeout > 0 do
|
||||||
|
timeout = timeout - 1
|
||||||
|
Wait(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not NetworkDoesNetworkIdExist(netId) then return 0 end
|
||||||
|
|
||||||
|
local entity = converter(netId)
|
||||||
|
timeout = 100
|
||||||
|
|
||||||
|
while not DoesEntityExist(entity) and entity ~= 0 and timeout > 0 do
|
||||||
|
timeout = timeout - 1
|
||||||
|
Wait(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
return DoesEntityExist(entity) and entity or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Ensures a network vehicle exists from its network ID.
|
||||||
|
--- @param vehNetID number The network ID.
|
||||||
|
--- @return number number The vehicle entity, or 0 if not found.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local vehicle = ensureNetToVeh(netID)
|
||||||
|
--- if vehicle ~= 0 then
|
||||||
|
--- print("Vehicle exists:", vehicle)
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function ensureNetToVeh(vehNetID)
|
||||||
|
return waitForNetworkEntity(vehNetID, NetToVeh)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Ensures a network entity exists from its network ID.
|
||||||
|
--- @param entNetID number The network ID.
|
||||||
|
--- @return number number The entity, or 0 if not found.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local entity = ensureNetToEnt(netID)
|
||||||
|
--- if entity ~= 0 then
|
||||||
|
--- print("Entity exists:", entity)
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function ensureNetToEnt(entNetID)
|
||||||
|
return waitForNetworkEntity(entNetID, NetworkGetEntityFromNetworkId)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sendLog(text)
|
||||||
|
local Player = getPlayer()
|
||||||
|
local coords = GetEntityCoords(PlayerPedId())
|
||||||
|
local _, _, _, hour, min, sec = GetLocalTime()
|
||||||
|
local data = {
|
||||||
|
script = debug.getinfo(2, "nSl"),
|
||||||
|
coords = coords,
|
||||||
|
localTime = { hour = hour, min = min, sec = sec },
|
||||||
|
firstname = Player.firstname,
|
||||||
|
lastname = Player.lastname,
|
||||||
|
source = Player.source,
|
||||||
|
id = Player.citizenId,
|
||||||
|
text = text,
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint("^5Log Message^7: "..getScript().." - "..Player.firstname.." "..Player.lastname.."("..Player.source..") ["..Player.citizenId.."]", text)
|
||||||
|
TriggerServerEvent(getScript()..":server:sendlog", data)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sendServerLog(data)
|
||||||
|
local hour, min, sec = os.date('%H'), os.date('%M'), os.date('%S')
|
||||||
|
data.serverTime = { house = hour, min = min, sec = sec }
|
||||||
|
--jsonPrint(data)
|
||||||
|
debugPrint("^5Log Message^7: "..getScript().." - "..data.firstname.." "..data.lastname.."("..data.source..") ["..data.id.."]", data.text)
|
||||||
|
|
||||||
|
-- Add your logger here
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterNetEvent(getScript()..":server:sendlog", sendServerLog)
|
||||||
|
|
||||||
|
|
||||||
|
-- This function was created to help create random numbers
|
||||||
|
-- When you create a table of items with a random hunger value for example
|
||||||
|
-- `hunger = math.random(10, 20)` this will be set at script start and never change
|
||||||
|
-- using `hunger = {10, 20}` and then calling this function will make it generate a random number every time
|
||||||
|
-- It also fallsback if it simply recieved a number instead of a table
|
||||||
|
-- For example:
|
||||||
|
-- local hunger = {10, 20}
|
||||||
|
-- local hungerAmount = GetRandomTiming(hunger)
|
||||||
|
-- print(hungerAmount) -- number between 10, 20
|
||||||
|
function GetRandomTiming(tbl)
|
||||||
|
return type(tbl) == "table" and math.random(tbl[1], tbl[2]) or tbl
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Player Movement
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Instantly turns an entity to face a target (entity or coordinates) without animation.
|
||||||
|
---
|
||||||
|
--- @param ent number|nil The Ped to turn (defaults to player's Ped if nil).
|
||||||
|
--- @param ent2 number|vector3|nil The target entity or coordinates to face.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- instantLookEnt(nil, vector3(200.0, 300.0, 40.0))
|
||||||
|
--- instantLookEnt(ped1, ped2)
|
||||||
|
--- ```
|
||||||
|
function instantLookEnt(ent, ent2)
|
||||||
|
local ped = ent or PlayerPedId()
|
||||||
|
local p1 = GetEntityCoords(ped, true)
|
||||||
|
local p2 = type(ent2) == "vector3" and ent2 or GetEntityCoords(ent2, true)
|
||||||
|
|
||||||
|
local dx = p2.x - p1.x
|
||||||
|
local dy = p2.y - p1.y
|
||||||
|
local heading = GetHeadingFromVector_2d(dx, dy)
|
||||||
|
|
||||||
|
debugPrint("^6Bridge^7: ^1Forced ^2Turning Player to^7: '^6"..formatCoord(p2).."^7'")
|
||||||
|
SetEntityHeading(ped, heading)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Makes the player look towards a specific target with an animated turn.
|
||||||
|
---
|
||||||
|
--- If the player is not already facing the target (entity or coordinates), a turning animation is triggered.
|
||||||
|
---
|
||||||
|
--- @param entity number|vector3|vector4|nil The target to look at.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- lookEnt(vector3(200.0, 300.0, 40.0))
|
||||||
|
--- lookEnt(pedEntity)
|
||||||
|
--- ```
|
||||||
|
function lookEnt(entity)
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
if entity then
|
||||||
|
if type(entity) == "vector3" or type(entity) == "vector4" then
|
||||||
|
if not IsPedHeadingTowardsPosition(ped, entity.xyz, 30.0) then
|
||||||
|
TaskTurnPedToFaceCoord(ped, entity.xyz, 1500)
|
||||||
|
debugPrint("^6Bridge^7: ^2Turning Player to^7: '^6"..formatCoord(entity).."^7'")
|
||||||
|
Wait(1500)
|
||||||
|
end
|
||||||
|
-- Make players head face the coord for 5 seconds
|
||||||
|
TaskLookAtCoord(ped, entity.x, entity.y, entity.z, 5000, 1, 1)
|
||||||
|
else
|
||||||
|
if DoesEntityExist(entity) then
|
||||||
|
local entCoords = GetEntityCoords(entity)
|
||||||
|
if not IsPedHeadingTowardsPosition(ped, entCoords, 30.0) then
|
||||||
|
TaskTurnPedToFaceCoord(ped, entCoords, 1500)
|
||||||
|
debugPrint("^6Bridge^7: ^2Turning Player to^7: '^6"..entity.."^7' - '"..formatCoord(entCoords).."^7'")
|
||||||
|
Wait(1500)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make players head face the coord for 5 seconds
|
||||||
|
TaskLookAtEntity(ped, entity, 5000, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Function to clone tables, to use when referencing tables that need to be hard set
|
||||||
|
function cloneTable(obj, opts, seen)
|
||||||
|
if type(obj) ~= "table" then return obj end
|
||||||
|
seen = seen or {}
|
||||||
|
if seen[obj] then return seen[obj] end
|
||||||
|
|
||||||
|
local copy = {}
|
||||||
|
seen[obj] = copy
|
||||||
|
|
||||||
|
-- Copy entries
|
||||||
|
for k, v in pairs(obj) do
|
||||||
|
local k2 = (opts and opts.copy_keys) and cloneTable(k, opts, seen) or k
|
||||||
|
copy[k2] = cloneTable(v, opts, seen)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Preserve metatable unless told not to
|
||||||
|
if not (opts and opts.strip_meta) then
|
||||||
|
local mt = getmetatable(obj)
|
||||||
|
if mt ~= nil then setmetatable(copy, mt) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return copy
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Material and Prop Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- A table mapping material names to their corresponding hash values.
|
||||||
|
---
|
||||||
|
--- Used for identifying materials based on hash codes.
|
||||||
|
local materials = {
|
||||||
|
none = -1,
|
||||||
|
Unknown = -1775485061,
|
||||||
|
concrete = 1187676648,
|
||||||
|
concrete_pothole = 359120722,
|
||||||
|
concrete_dusty = -1084640111,
|
||||||
|
tarmac = 282940568,
|
||||||
|
tarmac_painted = -1301352528,
|
||||||
|
tarmac_pothole = 1886546517,
|
||||||
|
rumble_strip = -250168275,
|
||||||
|
breeze_block = -954112554,
|
||||||
|
rock = -840216541,
|
||||||
|
rock_mossy = -124769592,
|
||||||
|
stone = 765206029,
|
||||||
|
cobblestone = 576169331,
|
||||||
|
brick = 1639053622,
|
||||||
|
marble = 1945073303,
|
||||||
|
paving_slab = 1907048430,
|
||||||
|
sandstone_solid = 592446772,
|
||||||
|
sandstone_brittle = 1913209870,
|
||||||
|
sand_loose = -1595148316,
|
||||||
|
sand_compact = 510490462,
|
||||||
|
sand_wet = 909950165,
|
||||||
|
sand_track = -1907520769,
|
||||||
|
sand_underwater = -1136057692,
|
||||||
|
sand_dry_deep = 509508168,
|
||||||
|
sand_wet_deep = 1288448767,
|
||||||
|
ice = -786060715,
|
||||||
|
ice_tarmac = -1931024423,
|
||||||
|
snow_loose = -1937569590,
|
||||||
|
snow_compact = -878560889,
|
||||||
|
snow_deep = 1619704960,
|
||||||
|
snow_tarmac = 1550304810,
|
||||||
|
gravel_small = 951832588,
|
||||||
|
gravel_large = 2128369009,
|
||||||
|
gravel_deep = -356706482,
|
||||||
|
gravel_train_track = 1925605558,
|
||||||
|
dirt_track = -1885547121,
|
||||||
|
mud_hard = -1942898710,
|
||||||
|
mud_pothole = 312396330,
|
||||||
|
mud_soft = 1635937914,
|
||||||
|
mud_underwater = -273490167,
|
||||||
|
mud_deep = 1109728704,
|
||||||
|
marsh = 223086562,
|
||||||
|
marsh_deep = 1584636462,
|
||||||
|
soil = -700658213,
|
||||||
|
clay_hard = 1144315879,
|
||||||
|
clay_soft = 560985072,
|
||||||
|
grass_long = -461750719,
|
||||||
|
grass = 1333033863,
|
||||||
|
grass_short = -1286696947,
|
||||||
|
hay = -1833527165,
|
||||||
|
bushes = 581794674,
|
||||||
|
twigs = -913351839,
|
||||||
|
leaves = -2041329971,
|
||||||
|
woodchips = -309121453,
|
||||||
|
tree_bark = -1915425863,
|
||||||
|
metal_solid_small = -1447280105,
|
||||||
|
metal_solid_medium = -365631240,
|
||||||
|
metal_solid_large = 752131025,
|
||||||
|
metal_hollow_small = 15972667,
|
||||||
|
metal_hollow_medium = 1849540536,
|
||||||
|
metal_hollow_large = -583213831,
|
||||||
|
metal_chainlink_small = 762193613,
|
||||||
|
metal_chainlink_large = 125958708,
|
||||||
|
metal_corrugated_iron = 834144982,
|
||||||
|
metal_grille = -426118011,
|
||||||
|
metal_railing = 2100727187,
|
||||||
|
metal_duct = 1761524221,
|
||||||
|
metal_garage_door = -231260695,
|
||||||
|
metal_manhole = -754997699,
|
||||||
|
wood_solid_small = -399872228,
|
||||||
|
wood_solid_medium = 555004797,
|
||||||
|
wood_solid_large = 815762359,
|
||||||
|
wood_solid_polished = 126470059,
|
||||||
|
wood_floor_dusty = -749452322,
|
||||||
|
wood_hollow_small = 1993976879,
|
||||||
|
wood_hollow_medium = -365476163,
|
||||||
|
wood_hollow_large = -925419289,
|
||||||
|
wood_chipboard = 1176309403,
|
||||||
|
wood_old_creaky = 722686013,
|
||||||
|
wood_high_density = -1742843392,
|
||||||
|
wood_lattice = 2011204130,
|
||||||
|
ceramic = -1186320715,
|
||||||
|
roof_tile = 1755188853,
|
||||||
|
roof_felt = -1417164731,
|
||||||
|
fibreglass = 1354180827,
|
||||||
|
tarpaulin = -642658848,
|
||||||
|
plastic = -2073312001,
|
||||||
|
plastic_hollow = 627123000,
|
||||||
|
plastic_high_density = -1625995479,
|
||||||
|
plastic_clear = -1859721013,
|
||||||
|
plastic_hollow_clear = 772722531,
|
||||||
|
plastic_high_density_clear = -1338473170,
|
||||||
|
fibreglass_hollow = -766055098,
|
||||||
|
rubber = -145735917,
|
||||||
|
rubber_hollow = -783934672,
|
||||||
|
linoleum = 289630530,
|
||||||
|
laminate = 1845676458,
|
||||||
|
carpet_solid = 669292054,
|
||||||
|
carpet_solid_dusty = 158576196,
|
||||||
|
carpet_floorboard = -1396484943,
|
||||||
|
cloth = 122789469,
|
||||||
|
plaster_solid = -574122433,
|
||||||
|
plaster_brittle = -251888898,
|
||||||
|
cardboard_sheet = 236511221,
|
||||||
|
cardboard_box = -1409054440,
|
||||||
|
paper = 474149820,
|
||||||
|
foam = 808719444,
|
||||||
|
feather_pillow = 1341866303,
|
||||||
|
polystyrene = -1756927331,
|
||||||
|
leather = -570470900,
|
||||||
|
tvscreen = 1429989756,
|
||||||
|
slatted_blinds = 673696729,
|
||||||
|
glass_shoot_through = 937503243,
|
||||||
|
glass_bulletproof = 244521486,
|
||||||
|
glass_opaque = 1500272081,
|
||||||
|
perspex = -1619794068,
|
||||||
|
car_metal = -93061983,
|
||||||
|
car_plastic = 2137197282,
|
||||||
|
car_softtop = -979647862,
|
||||||
|
car_softtop_clear = 2130571536,
|
||||||
|
car_glass_weak = 1247281098,
|
||||||
|
car_glass_medium = 602884284,
|
||||||
|
car_glass_strong = 1070994698,
|
||||||
|
car_glass_bulletproof = -1721915930,
|
||||||
|
car_glass_opaque = 513061559,
|
||||||
|
water = 435688960,
|
||||||
|
blood = 5236042,
|
||||||
|
oil = -634481305,
|
||||||
|
petrol = -1634184340,
|
||||||
|
fresh_meat = 868733839,
|
||||||
|
dried_meat = -1445160429,
|
||||||
|
emissive_glass = 1501078253,
|
||||||
|
emissive_plastic = 1059629996,
|
||||||
|
vfx_metal_electrified = -309134265,
|
||||||
|
vfx_metal_water_tower = 611561919,
|
||||||
|
vfx_metal_steam = -691277294,
|
||||||
|
vfx_metal_flame = 332778253,
|
||||||
|
phys_no_friction = 1666473731,
|
||||||
|
phys_golf_ball = -1693813558,
|
||||||
|
phys_tennis_ball = -256704763,
|
||||||
|
phys_caster = -235302683,
|
||||||
|
phys_caster_rusty = 2016463089,
|
||||||
|
phys_car_void = 1345867677,
|
||||||
|
phys_ped_capsule = -291631035,
|
||||||
|
phys_electric_fence = -1170043733,
|
||||||
|
phys_electric_metal = -2013761145,
|
||||||
|
phys_barbed_wire = -1543323456,
|
||||||
|
phys_pooltable_surface = 605776921,
|
||||||
|
phys_pooltable_cushion = 972939963,
|
||||||
|
phys_pooltable_ball = -748341562,
|
||||||
|
buttocks = 483400232,
|
||||||
|
thigh_left = -460535871,
|
||||||
|
shin_left = 652772852,
|
||||||
|
foot_left = 1926285543,
|
||||||
|
thigh_right = -236981255,
|
||||||
|
shin_right = -446036155,
|
||||||
|
foot_right = -1369136684,
|
||||||
|
spine0 = -1922286884,
|
||||||
|
spine1 = -1140112869,
|
||||||
|
spine2 = 1457572381,
|
||||||
|
spine3 = 32752644,
|
||||||
|
clavicle_left = -1469616465,
|
||||||
|
upper_arm_left = -510342358,
|
||||||
|
lower_arm_left = 1045062756,
|
||||||
|
hand_left = 113101985,
|
||||||
|
clavicle_right = -1557288998,
|
||||||
|
upper_arm_right = 1501153539,
|
||||||
|
lower_arm_right = 1777921590,
|
||||||
|
hand_right = 2000961972,
|
||||||
|
neck = 1718294164,
|
||||||
|
head = -735392753,
|
||||||
|
animal_default = 286224918,
|
||||||
|
car_engine = -1916939624,
|
||||||
|
puddle = 999829011,
|
||||||
|
concrete_pavement = 2015599386,
|
||||||
|
brick_pavement = -1147361576,
|
||||||
|
phys_dynamic_cover_bound = -2047468855,
|
||||||
|
vfx_wood_beer_barrel = 998201806,
|
||||||
|
wood_high_friction = -2140087047,
|
||||||
|
rock_noinst = 127813971,
|
||||||
|
bushes_noinst = 1441114862,
|
||||||
|
metal_solid_road_surface = -729112334,
|
||||||
|
stunt_ramp_surface = -2088174996,
|
||||||
|
temp_01 = 746881105,
|
||||||
|
temp_02 = -1977970111,
|
||||||
|
temp_03 = 1911121241,
|
||||||
|
temp_04 = 1923995104,
|
||||||
|
temp_05 = -1393662448,
|
||||||
|
temp_06 = 1061250033,
|
||||||
|
temp_07 = -1765523682,
|
||||||
|
temp_08 = 1343679702,
|
||||||
|
temp_09 = 1026054937,
|
||||||
|
temp_10 = 63305994,
|
||||||
|
temp_11 = 47470226,
|
||||||
|
temp_12 = 702596674,
|
||||||
|
temp_13 = -1637485913,
|
||||||
|
temp_14 = -645955574,
|
||||||
|
temp_15 = -1583997931,
|
||||||
|
temp_16 = -1512735273,
|
||||||
|
temp_17 = 1011960114,
|
||||||
|
temp_18 = 1354993138,
|
||||||
|
temp_19 = -801804446,
|
||||||
|
temp_20 = -2052880405,
|
||||||
|
temp_21 = -1037756060,
|
||||||
|
temp_22 = -620388353,
|
||||||
|
temp_23 = 465002639,
|
||||||
|
temp_24 = 1963820161,
|
||||||
|
temp_25 = 1952288305,
|
||||||
|
temp_26 = -1116253098,
|
||||||
|
temp_27 = 889255498,
|
||||||
|
temp_28 = -1179674098,
|
||||||
|
temp_29 = 1078418101,
|
||||||
|
temp_30 = 13626292
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Retrieves the ground material at a given position.
|
||||||
|
--- @param coords vector3 The coordinate to test.
|
||||||
|
--- @return number|nil number The material hash if hit, nil otherwise.
|
||||||
|
--- @return string string The material name.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local matHash, matName = GetGroundMaterialAtPosition(vector3(100,200,300))
|
||||||
|
--- print("Material:", matName)
|
||||||
|
--- ```
|
||||||
|
function GetGroundMaterialAtPosition(coords, Ped)
|
||||||
|
local endCoords = vec3(coords.x, coords.y, coords.z - 1.1)
|
||||||
|
local rayHandle = StartShapeTestCapsule(coords.x, coords.y, coords.z, endCoords.x, endCoords.y, endCoords.z, 1.0, 1, Ped or PlayerPedId(), 7)
|
||||||
|
local _, hit, _, _, materialHash, _ = GetShapeTestResultIncludingMaterial(rayHandle)
|
||||||
|
local materialName = "Unknown"
|
||||||
|
for k, v in pairs(materials) do
|
||||||
|
if v == materialHash then
|
||||||
|
materialName = k
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if hit then return materialHash, materialName else return nil, materialName end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Retrieves the dimensions (width, depth, height) of a prop/model.
|
||||||
|
--- @param model string The name or hash of the model.
|
||||||
|
--- @return number number The width of the prop.
|
||||||
|
--- @return number number The depth of the prop.
|
||||||
|
--- @return number number The height of the prop.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local width, depth, height = GetPropDimensions("prop_barrel_01a")
|
||||||
|
--- print("Dimensions:", width, depth, height)
|
||||||
|
--- ```
|
||||||
|
function GetPropDimensions(model)
|
||||||
|
loadModel(model)
|
||||||
|
local minDim, maxDim = GetModelDimensions(model)
|
||||||
|
local width, depth, height = maxDim.x - minDim.x, maxDim.y - minDim.y, maxDim.z - minDim.z
|
||||||
|
|
||||||
|
return width, depth, height
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Retrieves the forward direction vector of an entity based on its heading.
|
||||||
|
---
|
||||||
|
--- This function calculates the forward direction vector using the entity's heading angle.
|
||||||
|
---
|
||||||
|
--- @param entity number The entity whose forward vector is to be calculated.
|
||||||
|
--- @return vector3 vector3 The forward direction vector.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local forwardVec = GetEntityForwardVector(playerPed)
|
||||||
|
--- print("Forward Vector:", forwardVec)
|
||||||
|
--- ```
|
||||||
|
function GetEntityForwardVector(entity)
|
||||||
|
local heading = math.rad(GetEntityHeading(entity) + 90)
|
||||||
|
return vec3(math.cos(heading), math.sin(heading), 0.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function adjustMinMaxZ(coord, table)
|
||||||
|
local table = table
|
||||||
|
local adMinZ, adMaxZ = false, false
|
||||||
|
if table.minZ > (coord.z + 0.1) then
|
||||||
|
adMinZ = true
|
||||||
|
table.minZ = (coord.z - 1.05)
|
||||||
|
end
|
||||||
|
if table.maxZ < (coord.z - 0.1) then
|
||||||
|
adMinZ = true
|
||||||
|
table.maxZ = (coord.z + 0.80)
|
||||||
|
end
|
||||||
|
return table.minZ, table.maxZ, adMinZ, adMaxZ
|
||||||
|
end
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
--[[
|
||||||
|
Input Dialog Module
|
||||||
|
---------------------
|
||||||
|
This module provides a function to create a simple input dialog compatible with
|
||||||
|
multiple menu systems (OX, QB, GTA/WarMenu, and ESX). It supports various input
|
||||||
|
types such as radio buttons, numbers, text, and select dropdowns.
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
--- Creates a simple input dialog using the configured menu system.
|
||||||
|
---
|
||||||
|
--- @param title string The title or header of the input dialog.
|
||||||
|
--- @param opts table A table of input option definitions. Each option should include:
|
||||||
|
--- - type (string): The input type ("radio", "number", "text", "select").
|
||||||
|
--- - label (string, optional): A label for the input (used in radio/select for OX).
|
||||||
|
--- - text (string, optional): The text prompt for the input.
|
||||||
|
--- - name (string): The identifier for the input.
|
||||||
|
--- - isRequired (boolean, optional): Whether input is mandatory.
|
||||||
|
--- - default (any, optional): The default value.
|
||||||
|
--- - options (table, optional): A table of choices for "radio" and "select" types.
|
||||||
|
--- - min (number, optional): Minimum value (for "number" and "select").
|
||||||
|
--- - max (number, optional): Maximum value.
|
||||||
|
--- - txt (string, optional): Additional description.
|
||||||
|
---
|
||||||
|
--- @return table|nil table Returns the user's input as a table if submitted, otherwise nil.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local userInput = createInput("Enter Details", {
|
||||||
|
--- { type = "text", text = "Name", name = "playerName", isRequired = true },
|
||||||
|
--- { type = "number", text = "Age", name = "playerAge", min = 18, max = 99 },
|
||||||
|
--- { type = "radio", label = "Gender", name = "playerGender", options = {
|
||||||
|
--- { text = "Male", value = "male" },
|
||||||
|
--- { text = "Female", value = "female" },
|
||||||
|
--- { text = "Other", value = "other" },
|
||||||
|
--- }},
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
function createInput(title, opts)
|
||||||
|
local dialog = nil
|
||||||
|
local options = {}
|
||||||
|
local currentNum = 0
|
||||||
|
if Config.System.Menu == "ox" then
|
||||||
|
for i = 1, #opts do
|
||||||
|
currentNum += 1
|
||||||
|
if opts[i] == nil then currentNum -= 1 goto skip end
|
||||||
|
if opts[i].type == "radio" then
|
||||||
|
-- Convert radio options to select type for OX
|
||||||
|
for k in pairs(opts[i].options) do
|
||||||
|
opts[i].options[k].label = opts[i].options[k].text
|
||||||
|
end
|
||||||
|
options[currentNum] = {
|
||||||
|
type = "select",
|
||||||
|
isRequired = opts[i].isRequired,
|
||||||
|
label = opts[i].label or opts[i].text,
|
||||||
|
name = opts[i].name,
|
||||||
|
default = opts[i].default or opts[i].options[1].value,
|
||||||
|
options = opts[i].options,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if opts[i].type == "number" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = (opts[i].label or opts[i].text)..(opts[i].txt and " - "..opts[i].txt or ""),
|
||||||
|
isRequired = opts[i].isRequired,
|
||||||
|
name = opts[i].name,
|
||||||
|
default = opts[i].default,
|
||||||
|
options = opts[i].options,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if opts[i].type == "text" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = "input",
|
||||||
|
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
|
||||||
|
default = opts[i].default,
|
||||||
|
isRequired = opts[i].isRequired,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if opts[i].type == "select" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].text..(opts[i].txt and " - "..opts[i].txt or ""),
|
||||||
|
isRequired = opts[i].isRequired,
|
||||||
|
name = opts[i].name,
|
||||||
|
options = opts[i].options,
|
||||||
|
min = opts[i].min,
|
||||||
|
max = opts[i].max,
|
||||||
|
default = opts[i].default,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[i].type == "checkbox" then
|
||||||
|
jsonPrint(opts[i])
|
||||||
|
for k in pairs(opts[i].options) do
|
||||||
|
if options[currentNum] then currentNum += 1 end
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].options[k].text..(opts[i].txt and " - "..opts[i].txt or ""),
|
||||||
|
name = opts[i].options[k].value,
|
||||||
|
}
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[i].type == "color" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].label,
|
||||||
|
isRequired = opts[i].isRequired,
|
||||||
|
format = opts[i].format,
|
||||||
|
default = opts[i].default,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if opts[i].type == "slider" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].label,
|
||||||
|
isRequired = opts[i].required,
|
||||||
|
min = opts[i].min,
|
||||||
|
max = opts[i].max,
|
||||||
|
default = opts[i].default,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
::skip::
|
||||||
|
end
|
||||||
|
dialog = exports[OXLibExport]:inputDialog(title, options)
|
||||||
|
return dialog
|
||||||
|
elseif Config.System.Menu == "qb" then
|
||||||
|
for k, v in pairs(opts) do
|
||||||
|
currentNum += 1
|
||||||
|
if opts[k] == nil then
|
||||||
|
currentNum -= 1
|
||||||
|
else
|
||||||
|
options[currentNum] = opts[k]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
dialog = exports['qb-input']:ShowInput(
|
||||||
|
{ header = title, submitText = "Accept", inputs = options }
|
||||||
|
)
|
||||||
|
return dialog
|
||||||
|
|
||||||
|
elseif Config.System.Menu == "lation" then
|
||||||
|
|
||||||
|
for i in pairs(opts) do
|
||||||
|
currentNum += 1
|
||||||
|
if opts[i] == nil then currentNum -= 1 goto skip end
|
||||||
|
if opts[i].type == "radio" then
|
||||||
|
for k in pairs(opts[i].options) do
|
||||||
|
opts[i].options[k].label = opts[i].options[k].text
|
||||||
|
end
|
||||||
|
options[currentNum] = {
|
||||||
|
type = "select",
|
||||||
|
required = opts[i].isRequired,
|
||||||
|
label = opts[i].label or opts[i].text,
|
||||||
|
name = opts[i].name,
|
||||||
|
default = opts[i].default or opts[i].options[1].value,
|
||||||
|
options = opts[i].options,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if opts[i].type == "number" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].label or opts[i].text,
|
||||||
|
description = opts[i].txt and " - "..opts[i].txt or "",
|
||||||
|
required = opts[i].isRequired,
|
||||||
|
name = opts[i].name,
|
||||||
|
options = opts[i].options,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if opts[i].type == "text" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = "input",
|
||||||
|
label = opts[i].label or opts[i].text,
|
||||||
|
description = (opts[i].txt and " - "..opts[i].txt or ""),
|
||||||
|
placeholder = opts[i].default,
|
||||||
|
required = opts[i].isRequired,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if opts[i].type == "select" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].label or opts[i].text,
|
||||||
|
description = opts[i].txt and " - "..opts[i].txt or "",
|
||||||
|
required = opts[i].isRequired,
|
||||||
|
name = opts[i].name,
|
||||||
|
options = opts[i].options,
|
||||||
|
min = opts[i].min,
|
||||||
|
max = opts[i].max,
|
||||||
|
default = opts[i].default,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[i].type == "checkbox" then
|
||||||
|
for k in pairs(opts[i].options) do
|
||||||
|
if options[currentNum] then currentNum += 1 end
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].options[k].text..(opts[i].txt and " - "..opts[i].txt or ""),
|
||||||
|
name = opts[i].options[k].value,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[i].type == "color" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].label,
|
||||||
|
required = opts[i].isRequired,
|
||||||
|
format = opts[i].format,
|
||||||
|
default = opts[i].default,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[i].type == "slider" then
|
||||||
|
options[currentNum] = {
|
||||||
|
type = opts[i].type,
|
||||||
|
label = opts[i].label,
|
||||||
|
required = opts[i].required,
|
||||||
|
min = opts[i].min,
|
||||||
|
max = opts[i].max,
|
||||||
|
default = opts[i].default,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
::skip::
|
||||||
|
end
|
||||||
|
|
||||||
|
local dialog = exports.lation_ui:input({
|
||||||
|
title = title,
|
||||||
|
submitText = "Accept",
|
||||||
|
options = options
|
||||||
|
})
|
||||||
|
return dialog
|
||||||
|
|
||||||
|
elseif Config.System.Menu == "gta" then
|
||||||
|
WarMenu.CreateMenu(tostring(opts),
|
||||||
|
title,
|
||||||
|
" ",
|
||||||
|
{
|
||||||
|
titleColor = { 222, 255, 255 },
|
||||||
|
maxOptionCountOnScreen = 15,
|
||||||
|
width = 0.25,
|
||||||
|
x = 0.7,
|
||||||
|
})
|
||||||
|
if WarMenu.IsAnyMenuOpened() then return end
|
||||||
|
WarMenu.OpenMenu(tostring(opts))
|
||||||
|
|
||||||
|
local close = true
|
||||||
|
local _comboBoxItems = {}
|
||||||
|
local _comboBoxIndex = { 1, 1 }
|
||||||
|
|
||||||
|
while true do
|
||||||
|
if WarMenu.Begin(tostring(opts)) then
|
||||||
|
for i = 1, #opts do
|
||||||
|
if opts[i].type == "radio" then
|
||||||
|
for k in pairs(opts[i].options) do
|
||||||
|
if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
|
||||||
|
_comboBoxItems[i][k] = opts[i].options[k].text
|
||||||
|
end
|
||||||
|
local _, comboBoxIndex = WarMenu.ComboBox(opts[i].label, _comboBoxItems[i], _comboBoxIndex[i])
|
||||||
|
if _comboBoxIndex[i] ~= comboBoxIndex then
|
||||||
|
_comboBoxIndex[i] = comboBoxIndex
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if opts[i].type == "number" then
|
||||||
|
for b = 1, opts[i].max do
|
||||||
|
if not _comboBoxItems[i] then _comboBoxItems[i] = {} end
|
||||||
|
_comboBoxItems[i][b] = b
|
||||||
|
end
|
||||||
|
local _, comboBoxIndex = WarMenu.ComboBox(opts[i].text, _comboBoxItems[i], _comboBoxIndex[i])
|
||||||
|
if _comboBoxIndex[i] ~= comboBoxIndex then
|
||||||
|
_comboBoxIndex[i] = comboBoxIndex
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local pressed = WarMenu.Button("Pay")
|
||||||
|
if pressed then
|
||||||
|
WarMenu.CloseMenu()
|
||||||
|
close = false
|
||||||
|
local result = {}
|
||||||
|
for i = 1, #_comboBoxIndex do
|
||||||
|
result[i] = _comboBoxItems[i][_comboBoxIndex[i]]
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
WarMenu.End()
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not WarMenu.IsAnyMenuOpened() and close then
|
||||||
|
if data.onExit then data.onExit() end
|
||||||
|
end
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
elseif Config.System.Menu == "esx" then -- horrible input dialog, not even worth using, get OX
|
||||||
|
|
||||||
|
local results = {}
|
||||||
|
for i, opt in ipairs(opts) do
|
||||||
|
local prompt = opt.text or opt.label or "Enter value"
|
||||||
|
-- For radio/select types, append available options in the prompt.
|
||||||
|
if (opt.type == "radio" or opt.type == "select") and opt.options then
|
||||||
|
local choices = ""
|
||||||
|
for j, choice in ipairs(opt.options) do
|
||||||
|
choices = choices .. choice.text .. " (" .. tostring(choice.value) .. ")"
|
||||||
|
if j < #opt.options then choices = choices .. ", " end
|
||||||
|
end
|
||||||
|
prompt = prompt .. " [" .. choices .. "]"
|
||||||
|
elseif opt.type == "number" then
|
||||||
|
prompt = prompt .. " (number between " .. (opt.min or 0) .. " and " .. (opt.max or 100) .. ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
local value = nil
|
||||||
|
ESX.UI.Menu.Open('dialog', getScript(), 'input_' .. i, {
|
||||||
|
title = prompt
|
||||||
|
}, function(data, menu)
|
||||||
|
value = data.value
|
||||||
|
menu.close()
|
||||||
|
end, function(data, menu)
|
||||||
|
menu.close()
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Wait until the player submits a value.
|
||||||
|
while value == nil do
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert to a number if needed.
|
||||||
|
if opt.type == "number" then
|
||||||
|
value = tonumber(value)
|
||||||
|
end
|
||||||
|
results[opt.name or i] = value
|
||||||
|
end
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
--[[
|
||||||
|
Animal Detection Module
|
||||||
|
-------------------------
|
||||||
|
This module determines whether a Ped is an animal and categorizes it as a cat, dog,
|
||||||
|
or other type (e.g., coyote). It uses predefined model hashes stored in the AnimalPeds table.
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
- isCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal: Booleans to track the player's
|
||||||
|
current animal classification.
|
||||||
|
|
||||||
|
When running client-side (not on the server), the module checks the player's Ped after they load.
|
||||||
|
|
||||||
|
Usage Examples:
|
||||||
|
-- Check if the player's Ped is an animal:
|
||||||
|
local animalStatus = isPedAnimal()
|
||||||
|
|
||||||
|
-- Check if a given Ped is a cat:
|
||||||
|
if isCat(somePed) then print("This is a cat!") end
|
||||||
|
|
||||||
|
-- Determine if a Ped is a dog and whether it's big or small:
|
||||||
|
local isDogFlag, isBig = isDog(somePed)
|
||||||
|
|
||||||
|
-- Retrieve a flat list of all animal model hashes:
|
||||||
|
local allAnimalModels = getAnimalModels()
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Global animal classification flags.
|
||||||
|
isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote, isAnimal = false, false, false, false, false, false
|
||||||
|
|
||||||
|
if not isServer() then
|
||||||
|
onPlayerLoaded(function()
|
||||||
|
Wait(1000)
|
||||||
|
-- Reset classification flags
|
||||||
|
isPedCat, isPedDog, isBigDog, isSmallDog, isCoyote = false, false, false, false, false
|
||||||
|
-- Check if the player's Ped is an animal.
|
||||||
|
isPedAnimal()
|
||||||
|
if isAnimal then
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
local pedModel = GetEntityModel(ped)
|
||||||
|
|
||||||
|
-- Determine if the Ped is a cat:
|
||||||
|
-- Also treat 'ft-raccoon' as a cat unless it is 'ft-sphynx'
|
||||||
|
isPedCat = (isCat(ped) == true or pedModel == `ft-raccoon`) and (pedModel ~= `ft-sphynx`)
|
||||||
|
|
||||||
|
-- Determine if the Ped is a dog and whether it's big:
|
||||||
|
isPedDog, isBigDog = isDog(ped)
|
||||||
|
isSmallDog = not isBigDog
|
||||||
|
if isPedDog and pedModel == `a_c_coyote` then isPedDog = false end
|
||||||
|
|
||||||
|
-- Determine if the Ped is a coyote (special case):
|
||||||
|
isCoyote = (pedModel == `ft-sphynx` or pedModel == `a_c_coyote`)
|
||||||
|
|
||||||
|
-- Special override: if model is 'ft-capmonkey2', treat as a dog.
|
||||||
|
if pedModel == `ft-capmonkey2` then isPedDog = true end
|
||||||
|
end
|
||||||
|
end, true)
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Animal Classification Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Determines whether a given Ped is classified as an animal.
|
||||||
|
---
|
||||||
|
--- Checks if the Ped's model hash appears in any of the animal categories defined in AnimalPeds.
|
||||||
|
---
|
||||||
|
--- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped.
|
||||||
|
--- @return boolean boolean True if the Ped is an animal, otherwise false.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local isPlayerAnimal = isAnimal()
|
||||||
|
--- local isSpecificPedAnimal = isAnimal(somePedEntity)
|
||||||
|
--- ```
|
||||||
|
function isPedAnimal(Ped)
|
||||||
|
local PedModel = GetEntityModel(Ped or PlayerPedId())
|
||||||
|
for _, animalCategory in pairs(AnimalPeds) do
|
||||||
|
for animalModelHash, _ in pairs(animalCategory) do
|
||||||
|
if PedModel == animalModelHash then
|
||||||
|
isAnimal = true
|
||||||
|
debugPrint("^4isAnimal^7: ^2Ped is ^4Animal")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks if a given Ped is classified as a cat.
|
||||||
|
---
|
||||||
|
--- Iterates through the CatPeds table and returns true if the Ped's model matches.
|
||||||
|
---
|
||||||
|
--- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped.
|
||||||
|
--- @return boolean True if the Ped is a cat, otherwise false.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- if isCat() then
|
||||||
|
--- print("Player is a cat!")
|
||||||
|
--- end
|
||||||
|
---
|
||||||
|
--- local anotherPed = GetPedInVehicleSeat(vehicle, -1)
|
||||||
|
--- if isCat(anotherPed) then
|
||||||
|
--- print("Driver is a cat!")
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function isCat(Ped)
|
||||||
|
local PedModel = GetEntityModel(Ped or PlayerPedId())
|
||||||
|
for modelHash, _ in pairs(AnimalPeds.CatPeds) do
|
||||||
|
if PedModel == modelHash then
|
||||||
|
debugPrint("^4isAnimal^7: ^2Ped is ^4Cat")
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Determines if a given Ped is a dog and identifies its size category.
|
||||||
|
---
|
||||||
|
--- Checks the BigDogs and SmallDogs tables to see if the Ped's model matches any dog model.
|
||||||
|
---
|
||||||
|
--- @param ped number|nil Optional. The Ped entity to check. Defaults to the player's Ped.
|
||||||
|
---@return boolean, boolean|nil boolean Returns `true` and `true` if the Ped is a big dog,
|
||||||
|
--- `true` and `false` if it's a small dog,
|
||||||
|
--- or `false` and `nil` if it's not a dog.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local isPedDog, isBigDog = isDog()
|
||||||
|
--- if isPedDog then
|
||||||
|
--- if isBigDog then
|
||||||
|
--- print("Player is a big dog!")
|
||||||
|
--- else
|
||||||
|
--- print("Player is a small dog!")
|
||||||
|
--- end
|
||||||
|
--- else
|
||||||
|
--- print("Player is not a dog.")
|
||||||
|
--- end
|
||||||
|
---
|
||||||
|
--- local somePed = GetPedInVehicleSeat(vehicle, 0)
|
||||||
|
--- local isPetDog, isLargeDog = isDog(somePed)
|
||||||
|
--- if isPetDog then
|
||||||
|
--- if isLargeDog then
|
||||||
|
--- print("Passenger is a big dog!")
|
||||||
|
--- else
|
||||||
|
--- print("Passenger is a small dog!")
|
||||||
|
--- end
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function isDog(Ped)
|
||||||
|
local PedModel = GetEntityModel(Ped or PlayerPedId())
|
||||||
|
for modelHash, _ in pairs(AnimalPeds.BigDogs) do
|
||||||
|
if PedModel == modelHash then
|
||||||
|
debugPrint("^4isAnimal^7: ^2Ped is ^4Dog")
|
||||||
|
return true, true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for modelHash, _ in pairs(AnimalPeds.SmallDogs) do
|
||||||
|
if PedModel == modelHash then
|
||||||
|
debugPrint("^4isAnimal^7: ^2Ped is ^4Small Dog")
|
||||||
|
return true, false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Compiles and returns a flat table of all animal model hashes.
|
||||||
|
---
|
||||||
|
--- Iterates through every category in AnimalPeds and collects all model hashes.
|
||||||
|
---
|
||||||
|
--- @return table table A table containing all animal model hashes.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local allAnimalModels = getAnimalModels()
|
||||||
|
--- for _, modelHash in ipairs(allAnimalModels) do
|
||||||
|
--- print("Animal Model Hash:", modelHash)
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function getAnimalModels()
|
||||||
|
local animalModels = {}
|
||||||
|
for _, animalCategory in pairs(AnimalPeds) do
|
||||||
|
for modelHash, _ in pairs(animalCategory) do
|
||||||
|
table.insert(animalModels, modelHash)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return animalModels
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Compiles and returns a table of animal animations for the ped model.
|
||||||
|
---
|
||||||
|
--- Iterates through every category in AnimalPeds and collects all anims.
|
||||||
|
---
|
||||||
|
--- @return table table A table containing all current model anims.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local getAnim = getAnimalAnims(ped)
|
||||||
|
--- playAnim(getAnim.sitDict, getAnim.sitAnim, -1, 1)
|
||||||
|
--- ```
|
||||||
|
function getAnimalAnims(Ped)
|
||||||
|
local model = GetEntityModel(Ped)
|
||||||
|
local animalTable = {}
|
||||||
|
for _, animalCategory in pairs(AnimalPeds) do
|
||||||
|
for k, v in pairs(animalCategory) do
|
||||||
|
if k == model then
|
||||||
|
animalTable = v
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return animalTable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Animal Models Data
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Define the animal models and their associated animations.
|
||||||
|
AnimalPeds = {
|
||||||
|
BigDogs = {
|
||||||
|
[`a_c_chop`] = { deathAnim = "dead_right", deathDict = "creatures@chop@move", exitAnim = "getup_r", exitDict = "creatures@chop@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`a_c_k9`] = { deathAnim = "dead_right", deathDict = "creatures@chop@move", exitAnim = "getup_r", exitDict = "creatures@chop@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`a_c_husky`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`a_c_retriever`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`a_c_shepherd`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`a_c_rottweiler`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-aushep`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`golden_r`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-dobermanv2`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`doberman`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-gs`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`k9_husky`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-bloodhound`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`bernard`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-pterrier`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-labrador`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`dane`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft_malinois`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`abdog`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`dalmatian`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`a_c_dalmatian`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-boxer`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`ft-bs`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`chowchow`] = { deathAnim = "dead_right", deathDict = "creatures@rottweiler@move", exitAnim = "getup_r", exitDict = "creatures@rottweiler@getup", sitAnim = "sit", sitDict = "creatures@rottweiler@in_vehicle@std_car" },
|
||||||
|
[`a_c_coyote`] = { deathAnim = "dead_right", deathDict = "creatures@coyote@move", exitAnim = "getup_r", exitDict = "creatures@coyote@getup", sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
|
||||||
|
[`a_c_coyote_02`] = { deathAnim = "dead_right", deathDict = "creatures@coyote@move", exitAnim = "getup_r", exitDict = "creatures@coyote@getup", sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
|
||||||
|
},
|
||||||
|
SmallDogs = {
|
||||||
|
[`a_c_poodle`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
[`ft-chihuahua`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
[`a_c_pug`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
[`a_c_pug_02`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
[`a_c_westy`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
[`ft-pretriever`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
[`ft-shepk9`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
},
|
||||||
|
CatPeds = {
|
||||||
|
[`bshorthair`] = { deathAnim = "dead_right", deathDict = "creatures@cat@move", exitAnim = "getup_r", exitDict = "creatures@cat@getup", sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base" },
|
||||||
|
[`a_c_cat_01`] = { deathAnim = "dead_right", deathDict = "creatures@cat@move", exitAnim = "getup_r", exitDict = "creatures@cat@getup", sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base" },
|
||||||
|
[`ft-sphynx`] = { deathAnim = "dead_right", deathDict = "creatures@coyote@move", exitAnim = "getup_r", exitDict = "creatures@coyote@getup", sitAnim = "base", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
|
||||||
|
},
|
||||||
|
OtherPeds = {
|
||||||
|
[`ft-raccoon`] = { deathAnim = "dead_right", deathDict = "creatures@cat@move", exitAnim = "getup_r", exitDict = "creatures@cat@getup", sitAnim = "base", sitDict = "creatures@cat@amb@world_cat_sleeping_ledge@base" },
|
||||||
|
[`a_c_hen`] = { deathAnim = "dead_right", deathDict = "creatures@hen@move", exitAnim = "getup_r", exitDict = "creatures@hen@getup" },
|
||||||
|
[`a_c_rabbit_01`] = { deathAnim = "dead_right", deathDict = "creatures@rabbit@move", exitAnim = "getup_r", exitDict = "creatures@rabbit@getup", sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
|
||||||
|
[`a_c_rabbit_02`] = { deathAnim = "dead_right", deathDict = "creatures@rabbit@move", exitAnim = "getup_r", exitDict = "creatures@rabbit@getup", sitAnim = "idle_c", sitDict = "creatures@coyote@amb@world_coyote_howl@base" },
|
||||||
|
[`a_c_rat`] = { deathAnim = "dead_right", deathDict = "creatures@rat@move", exitAnim = "getup_r", exitDict = "creatures@rat@getup" },
|
||||||
|
[`a_c_deer`] = { deathAnim = "dead_right", deathDict = "creatures@deer@move", exitAnim = "getup_r", exitDict = "creatures@deer@getup" },
|
||||||
|
[`a_c_boar`] = { deathAnim = "dead_right", deathDict = "creatures@boar@move", exitAnim = "getup_r", exitDict = "creatures@boar@getup" },
|
||||||
|
[`a_c_boar_02`] = { deathAnim = "dead_right", deathDict = "creatures@boar@move", exitAnim = "getup_r", exitDict = "creatures@boar@getup" },
|
||||||
|
[`a_c_chicken`] = { deathAnim = "dead_right", deathDict = "creatures@chicken@move", exitAnim = "getup_r", exitDict = "creatures@chicken@getup" },
|
||||||
|
[`a_c_pig`] = { deathAnim = "dead_right", deathDict = "creatures@pig@move", exitAnim = "getup_r", exitDict = "creatures@pig@getup" },
|
||||||
|
[`a_c_sharkhammer`] = { deathAnim = "dead_right", deathDict = "creatures@sharkhammer@move", exitAnim = "getup_r", exitDict = "creatures@sharkhammer@getup" },
|
||||||
|
[`a_c_sharktiger`] = { deathAnim = "dead_right", deathDict = "creatures@sharktiger@move", exitAnim = "getup_r", exitDict = "creatures@sharktiger@getup" },
|
||||||
|
[`a_c_crow`] = { deathAnim = "dead_down", deathDict = "creatures@crow@move", exitAnim = "nill", exitDict = "creatures@pug@move" },
|
||||||
|
[`a_c_pigeon`] = { deathAnim = "dead_down", deathDict = "creatures@pigeon@move", exitAnim = "nill", exitDict = "creatures@pug@move" },
|
||||||
|
},
|
||||||
|
Monekys = {
|
||||||
|
[`ft-chimpanzee`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
|
||||||
|
[`a_c_chimp`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
|
||||||
|
[`a_c_chimp_02`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
|
||||||
|
[`a_c_rhesus`] = { deathAnim = "dead", deathDict = "dead_a", exitAnim = "get_up@sat_on_floor@to_stand", exitDict = "getup_0" },
|
||||||
|
[`ft-capmonkey2`] = { deathAnim = "dead_right", deathDict = "creatures@pug@move", exitAnim = "getup_r", exitDict = "creatures@pug@getup", sitAnim = "base", sitDict = "creatures@pug@amb@world_dog_sitting@base" },
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,337 @@
|
|||||||
|
--[[
|
||||||
|
Duty & Interaction Utilities Module
|
||||||
|
--------------------------------------
|
||||||
|
This module provides functions related to:
|
||||||
|
• Determining boss roles from Jobs and Gangs tables.
|
||||||
|
• Checking a player's job and duty status.
|
||||||
|
• Toggling duty state.
|
||||||
|
• Simulating player interactions such as hand washing, using toilets/urinals,
|
||||||
|
and teleporting via doors.
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
local bossMenuFunc = {
|
||||||
|
{ name = "qb-management",
|
||||||
|
openBossMenu = function(isGang, group)
|
||||||
|
if isGang then
|
||||||
|
TriggerEvent("qb-gangmenu:client:OpenMenu")
|
||||||
|
else
|
||||||
|
TriggerEvent("qb-bossmenu:client:OpenMenu")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ name = "qbx_management",
|
||||||
|
openBossMenu = function(isGang, group)
|
||||||
|
if isGang then
|
||||||
|
exports["qbx_management"]:OpenBossMenu("gang")
|
||||||
|
else
|
||||||
|
exports["qbx_management"]:OpenBossMenu("job")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ name = "esx_society",
|
||||||
|
openBossMenu = function(isGang, group)
|
||||||
|
TriggerServerEvent(getScript()..":registerESXSociety", isGang, group)
|
||||||
|
TriggerEvent('esx_society:openBossMenu', group, function() end, { wash = false })
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ name = "tss-bossmenu",
|
||||||
|
openBossMenu = function(isGang, group)
|
||||||
|
TriggerEvent("tss-bossmenu:client:OpenMenu")
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ name = "okokBossMenu",
|
||||||
|
openBossMenu = function(isGang, group)
|
||||||
|
ExecuteCommand('openbossmenu')
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBossMenu(isGang, group)
|
||||||
|
for i = 1, #bossMenuFunc do
|
||||||
|
local bossmenu = bossMenuFunc[i]
|
||||||
|
if isStarted(bossmenu.name) then
|
||||||
|
bossmenu.openBossMenu(isGang, group)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Register ESX Society account in the server
|
||||||
|
RegisterNetEvent(getScript()..":registerESXSociety", function(group)
|
||||||
|
local checkExist = exports.esx_society:GetSociety(group)
|
||||||
|
if checkExist == nil then
|
||||||
|
exports.esx_society:registerSociety(
|
||||||
|
group,
|
||||||
|
Gangs[group] and Gangs[group].label or Jobs[group] and Jobs[group].label,
|
||||||
|
group,
|
||||||
|
group,
|
||||||
|
group,
|
||||||
|
{type = "public"}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Global Duty Status
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- This attempts to sync up with the players framework onDuty variable
|
||||||
|
-- This stops the script getting confused when one is false and one is true
|
||||||
|
onDuty = false
|
||||||
|
|
||||||
|
if not isServer() then
|
||||||
|
onPlayerLoaded(function()
|
||||||
|
onDuty = getPlayer().onDuty
|
||||||
|
end, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Boss Role Detection
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Scans the 'Jobs' and 'Gangs' tables to identify roles classified as bosses.
|
||||||
|
---
|
||||||
|
--- Iterates through the specified role's grades in the Jobs or Gangs table and returns
|
||||||
|
--- a table mapping the role to the lowest grade number that qualifies as a boss (isboss or bankAuth).
|
||||||
|
---
|
||||||
|
--- @param role string The job or gang role to check.
|
||||||
|
--- @return table table A table with the role mapped to its boss grade number.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local bosses = makeBossRoles("police")
|
||||||
|
--- if bosses["police"] then
|
||||||
|
--- print("Police role has a boss grade.")
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function makeBossRoles(role)
|
||||||
|
local boss = {}
|
||||||
|
local data = (Jobs and Jobs[role]) or (Gangs and Gangs[role])
|
||||||
|
if data then
|
||||||
|
for grade, info in pairs(data.grades) do
|
||||||
|
if info.isboss or info.bankAuth or info.isBoss then
|
||||||
|
boss[role] = boss[role] and math.min(boss[role], tonumber(grade)) or tonumber(grade)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return boss
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Job & Duty Checks
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Checks if the player has a specific job (or gang) and is on duty.
|
||||||
|
---
|
||||||
|
--- Verifies whether the player possesses the specified role. If the role is defined in the Jobs table,
|
||||||
|
--- it also checks that the player is clocked in (onDuty). If the check fails, a notification is sent.
|
||||||
|
---
|
||||||
|
--- @param job string The job or gang to check.
|
||||||
|
--- @return boolean Returns true if the player meets the criteria; false otherwise.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- if jobCheck("mechanic") then
|
||||||
|
--- -- Allow mechanic features.
|
||||||
|
--- else
|
||||||
|
--- -- Deny access.
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function jobCheck(job)
|
||||||
|
local canDo = true
|
||||||
|
if Jobs[job] then
|
||||||
|
if not hasJob(job) or not getPlayer().onDuty then
|
||||||
|
triggerNotify(nil, Loc[Config.Lan].error["not_clockedin"])
|
||||||
|
canDo = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if Gangs[job] then
|
||||||
|
if not hasJob(job) then
|
||||||
|
canDo = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return canDo
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Toggles the player's duty status.
|
||||||
|
---
|
||||||
|
--- Switches the player's duty state between on-duty and off-duty. If using QBcore,
|
||||||
|
--- it triggers the appropriate server event. Otherwise, it manually toggles the onDuty variable and notifies the player.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- toggleDuty() -- Player receives a notification of their new duty status.
|
||||||
|
--- ```
|
||||||
|
function toggleDuty()
|
||||||
|
local dutyFunc = {
|
||||||
|
{ framework = QBExport,
|
||||||
|
func = function()
|
||||||
|
TriggerServerEvent("QBCore:ToggleDuty")
|
||||||
|
Wait(100)
|
||||||
|
onDuty = getPlayer().onDuty
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{ framework = QBXExport,
|
||||||
|
func = function()
|
||||||
|
TriggerServerEvent("QBCore:ToggleDuty")
|
||||||
|
Wait(100)
|
||||||
|
onDuty = getPlayer().onDuty
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{ framework = RSGExport,
|
||||||
|
func = function()
|
||||||
|
TriggerServerEvent("RSGCore:ToggleDuty")
|
||||||
|
Wait(100)
|
||||||
|
onDuty = getPlayer().onDuty
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{ framework = ESXExport,
|
||||||
|
func = function()
|
||||||
|
local tempJob = ESX.GetPlayerData().job
|
||||||
|
tempJob.onDuty = not onDuty
|
||||||
|
ESX.SetPlayerData("job", tempJob)
|
||||||
|
onDuty = getPlayer().onDuty
|
||||||
|
if onDuty then
|
||||||
|
triggerNotify(nil, "Now on duty", "success")
|
||||||
|
else
|
||||||
|
triggerNotify(nil, "Now off duty", "success")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, #dutyFunc do
|
||||||
|
local framework = dutyFunc[i]
|
||||||
|
if isStarted(framework.framework) then
|
||||||
|
framework.func()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fallback
|
||||||
|
onDuty = not onDuty
|
||||||
|
if onDuty then
|
||||||
|
triggerNotify(nil, "Now on duty", "success")
|
||||||
|
else
|
||||||
|
triggerNotify(nil, "Now off duty", "success")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Framework specific functions to keep duty status synced
|
||||||
|
RegisterNetEvent("QBCore:Client:OnJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
|
||||||
|
RegisterNetEvent("QBCore:Client:SetDuty", function(duty) onDuty = duty end)
|
||||||
|
|
||||||
|
RegisterNetEvent("qbx_core:client:onJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
|
||||||
|
|
||||||
|
RegisterNetEvent("RSGCore:Client:OnJobUpdate", function(JobInfo) onDuty = JobInfo.onduty end)
|
||||||
|
RegisterNetEvent("RSGCore:Client:SetDuty", function(duty) onDuty = duty end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Interaction Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Initiates the hand-washing action for the player.
|
||||||
|
---
|
||||||
|
--- Triggers an animation and a progress bar to simulate hand washing at the specified coordinates.
|
||||||
|
--- On success, it notifies the player; if cancelled, it sends an error notification.
|
||||||
|
---
|
||||||
|
--- @param data table A table containing:
|
||||||
|
--- - coords (vector3): The location where the hand-washing action occurs.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- washHands({ coords = vector3(200.0, 300.0, 40.0) })
|
||||||
|
--- ```
|
||||||
|
function washHands(data)
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
lookEnt(data.coords)
|
||||||
|
local cam = createTempCam(ped, data.coords)
|
||||||
|
if progressBar({
|
||||||
|
label = Loc[Config.Lan].progressbar["progress_washing"],
|
||||||
|
time = 5000,
|
||||||
|
cancel = true,
|
||||||
|
dict = "mp_arresting",
|
||||||
|
anim = "a_uncuff",
|
||||||
|
flag = 32,
|
||||||
|
icon = "fas fa-hand-holding-droplet",
|
||||||
|
cam = cam
|
||||||
|
}) then
|
||||||
|
triggerNotify(nil, Loc[Config.Lan].success["washed_hands"], "success")
|
||||||
|
else
|
||||||
|
triggerNotify(nil, Loc[Config.Lan].error["cancel"], "error")
|
||||||
|
end
|
||||||
|
ClearPedTasks(ped)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Handles the player's interaction with a toilet or urinal.
|
||||||
|
---
|
||||||
|
--- Manages animations and progress bars for using a urinal or a toilet. If the action is successful,
|
||||||
|
--- it triggers the appropriate server event (urinal usage) or notifies the player if cancelled.
|
||||||
|
---
|
||||||
|
--- @param data table A table containing:
|
||||||
|
--- - urinal (boolean): `true if using a urinal; false for a toilet.`
|
||||||
|
--- - sitcoords (vector4): `Coordinates and heading for seating when using a toilet.`
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- useToilet({ urinal = true })
|
||||||
|
--- -- Player uses a urinal with corresponding animations and notifications
|
||||||
|
---
|
||||||
|
--- useToilet({ urinal = false, sitcoords = vector4(215.76, -810.12, 29.73, 90.0) })
|
||||||
|
--- -- Player sits down to use a toilet with corresponding animations and notifications
|
||||||
|
--- ```
|
||||||
|
function useToilet(data)
|
||||||
|
if data.urinal then
|
||||||
|
if progressBar({
|
||||||
|
label = "Using Urinal",
|
||||||
|
time = 5000,
|
||||||
|
cancel = true,
|
||||||
|
dict = "misscarsteal2peeing",
|
||||||
|
anim = "peeing_loop",
|
||||||
|
flag = 32
|
||||||
|
}) then
|
||||||
|
TriggerServerEvent(getScript().."server:Urinal")
|
||||||
|
else
|
||||||
|
lockInv(false)
|
||||||
|
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], "error")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
TaskStartScenarioAtPosition(PlayerPedId(), "PROP_HUMAN_SEAT_CHAIR_MP_PLAYER", data.sitcoords.x, data.sitcoords.y, data.sitcoords.z, data.sitcoords[4], 0, 1, true)
|
||||||
|
if progressBar({
|
||||||
|
label = "Using Toilet",
|
||||||
|
time = 10000,
|
||||||
|
cancel = true
|
||||||
|
}) then
|
||||||
|
TriggerServerEvent(getScript().."server:Urinal")
|
||||||
|
ClearPedTasks(PlayerPedId())
|
||||||
|
else
|
||||||
|
lockInv(false)
|
||||||
|
triggerNotify(nil, Loc[Config.Lan].error["cancelled"], "error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Teleports the player to specified coordinates with a fade effect.
|
||||||
|
---
|
||||||
|
--- Fades the screen out, moves the player to the target coordinates, sets the player's heading,
|
||||||
|
--- then fades the screen back in. Commonly used for door interactions or teleportation points.
|
||||||
|
---
|
||||||
|
--- @param data table A table containing:
|
||||||
|
--- - telecoords (vector4): The target coordinates and heading.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- useDoor({ telecoords = vector4(215.76, -810.12, 29.73, 90.0) })
|
||||||
|
--- ```
|
||||||
|
function useDoor(data)
|
||||||
|
DoScreenFadeOut(500)
|
||||||
|
while not IsScreenFadedOut() do Wait(10) end
|
||||||
|
SetEntityCoords(PlayerPedId(), data.telecoords.xyz, 0, 0, 0, false)
|
||||||
|
SetEntityHeading(PlayerPedId(), data.telecoords.w)
|
||||||
|
DoScreenFadeIn(1000)
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
local camCache = {}
|
||||||
|
|
||||||
|
--- Creates a temporary camera at a specified position, pointing towards given coordinates.
|
||||||
|
--
|
||||||
|
-- This function creates a camera at a position relative to an entity or at a specified position and orients it to look at the target coordinates.
|
||||||
|
-- The camera is only created if `Config.Crafting.craftCam` is enabled in the configuration.
|
||||||
|
--
|
||||||
|
---@param ent entityId|coords The base position for the camera. Can be an entity handle or a `vector3` position.
|
||||||
|
-- If `ent` is an entity, the camera position is calculated as an offset from the entity's position using `GetOffsetFromEntityInWorldCoords`.
|
||||||
|
-- If `ent` is a `vector3`, it is used directly as the camera's position.
|
||||||
|
--
|
||||||
|
---@param coords vector3|entityId The target `vector3` coordinates that the camera will point at.
|
||||||
|
--
|
||||||
|
---@return camID number The handle of the created camera, or `nil` if the camera was not created (e.g., if `Config.Crafting.craftCam` is `false`).
|
||||||
|
--
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- local cam = createTempCam(entity, targetCoords)
|
||||||
|
-- ```
|
||||||
|
function createTempCam(ent, coords)
|
||||||
|
local camID = nil
|
||||||
|
if (Config.Crafting and Config.Crafting.craftCam) or Config.System.enableCam then
|
||||||
|
|
||||||
|
-- if not ent or coords are provided, make a basic camera to control later
|
||||||
|
if not ent and not coords then
|
||||||
|
camID = CreateCam("DEFAULT_SCRIPTED_CAMERA", true)
|
||||||
|
camCache[#camCache+1] = camID
|
||||||
|
return camID
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if received a vector3 or vector4 use those coords for origin point, otherwrise get offset from entity
|
||||||
|
local camCoords = type(ent) ~= "number" and ent or GetOffsetFromEntityInWorldCoords(ent, 1.0, -0.3, 0.8)
|
||||||
|
|
||||||
|
-- Create the camera
|
||||||
|
camID = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", camCoords.x, camCoords.y, camCoords.z + 0.5, 1.0, 0.0, 0.0, 60.00, false, 0)
|
||||||
|
camCache[#camCache+1] = camID
|
||||||
|
|
||||||
|
debugPrint("^6Bridge^7: ^2Custom Camera Created", camID)
|
||||||
|
|
||||||
|
--if type(coords) == "number" then
|
||||||
|
-- SetCamCoord(camID, GetCamCoord(camID) + vec3(0, 0, 1.0))
|
||||||
|
--end
|
||||||
|
if coords then
|
||||||
|
camLookAt(camID, coords)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
return camID
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local cacheCameraEffect = {}
|
||||||
|
local cachePrevCam = nil
|
||||||
|
--- Activates and starts rendering the temporary camera.
|
||||||
|
--
|
||||||
|
-- This function sets the specified camera as active and begins rendering it with a smooth transition.
|
||||||
|
-- The camera is only activated if `Config.Crafting.craftCam` is enabled in the configuration.
|
||||||
|
--
|
||||||
|
---@param cam number The handle of the camera to activate and render.
|
||||||
|
---@param renderTime number The handle of the camera to activate and render.
|
||||||
|
---@param loadScene boolean The handle of the camera to activate and render.
|
||||||
|
---@param filter table The filter or postFx to render when starting the camera.
|
||||||
|
--
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- startTempCam(camID, 1000, true, { postFx = "HeistCelebEnd" })
|
||||||
|
-- ```
|
||||||
|
function startTempCam(cam, renderTime, loadScene, filter, switchCam)
|
||||||
|
if cam and DoesCamExist(cam) then
|
||||||
|
debugPrint("Starting camera")
|
||||||
|
-- if moving from a cached previous cam, or you've sent a camre id for it to switch from, interpolate to it
|
||||||
|
if switchCam or cachePrevCam then
|
||||||
|
SetCamActiveWithInterp(cam, cachePrevCam, renderTime or 1000, 0, 0)
|
||||||
|
SetCamActive(cachePrevCam, false) -- Set previous cam inactive (shouldn't be needed but just in case)
|
||||||
|
else
|
||||||
|
SetCamActive(cam, true)
|
||||||
|
end
|
||||||
|
cachePrevCam = cam
|
||||||
|
|
||||||
|
-- Clear previous filters
|
||||||
|
if cacheCameraEffect.postFx then
|
||||||
|
AnimpostfxStop(cacheCameraEffect.postFx)
|
||||||
|
cacheCameraEffect.postFx = nil
|
||||||
|
end
|
||||||
|
if cacheCameraEffect.timecycle then
|
||||||
|
ClearTimecycleModifier()
|
||||||
|
cacheCameraEffect.timecycle = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Apply filters (timecycle) here as requested
|
||||||
|
if filter and filter.modifier then
|
||||||
|
SetTimecycleModifier(filter.modifier)
|
||||||
|
SetTimecycleModifierStrength((filter.strength or 1.0) + 0.0)
|
||||||
|
cacheCameraEffect.timecycle = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if filter and filter.postFx then
|
||||||
|
AnimpostfxPlay(filter.postFx, 0, filter.loop and true or false)
|
||||||
|
cacheCameraEffect.postFx = filter.postFx
|
||||||
|
end
|
||||||
|
|
||||||
|
if loadScene then
|
||||||
|
loadLocation(cam, nil, 100.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
RenderScriptCams(true, true, renderTime or 1000, true, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Follow cam or coords
|
||||||
|
function camLookAt(cam, entCoords)
|
||||||
|
if cam and DoesCamExist(cam) and entCoords then
|
||||||
|
if type(entCoords) ~= "number" then
|
||||||
|
PointCamAtCoord(cam, entCoords.xyz)
|
||||||
|
else
|
||||||
|
PointCamAtEntity(cam, entCoords)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function loadLocation(cam, pos, radius)
|
||||||
|
local pos = pos or GetCamCoord(cam)
|
||||||
|
|
||||||
|
SetFocusPosAndVel(pos.x, pos.y, pos.z, 0.0, 0.0, 0.0)
|
||||||
|
RequestCollisionAtCoord(pos.x, pos.y, pos.z)
|
||||||
|
NewLoadSceneStart(pos.x, pos.y, pos.z, 0.0, 0.0, 0.0, radius or 100.0, 0)
|
||||||
|
|
||||||
|
local t0 = GetGameTimer()
|
||||||
|
while not IsNewLoadSceneLoaded() and (GetGameTimer() - t0) < 2000 do
|
||||||
|
RequestCollisionAtCoord(pos.x, pos.y, pos.z)
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
NewLoadSceneStop()
|
||||||
|
end
|
||||||
|
|
||||||
|
function clearLoadLocation()
|
||||||
|
ClearFocus()
|
||||||
|
NewLoadSceneStop()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--- Deactivates the temporary camera and stops rendering.
|
||||||
|
--
|
||||||
|
-- This function waits for one second, then stops rendering script cameras and destroys all cameras.
|
||||||
|
-- The delay allows for any transitions or animations to complete.
|
||||||
|
--
|
||||||
|
-- The camera is only deactivated if `Config.Crafting.craftCam` is enabled in the configuration.
|
||||||
|
--
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- stopTempCam()
|
||||||
|
-- ```
|
||||||
|
function stopTempCam(renderTime)
|
||||||
|
CreateThread(function()
|
||||||
|
Wait(1000)
|
||||||
|
|
||||||
|
cachePrevCam = nil
|
||||||
|
|
||||||
|
-- Clear previous filters
|
||||||
|
if cacheCameraEffect.postFx then
|
||||||
|
AnimpostfxStop(cacheCameraEffect.postFx)
|
||||||
|
cacheCameraEffect.postFx = nil
|
||||||
|
end
|
||||||
|
if cacheCameraEffect.timecycle then
|
||||||
|
ClearTimecycleModifier()
|
||||||
|
cacheCameraEffect.timecycle = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
RenderScriptCams(false, true, renderTime or 500, true, true)
|
||||||
|
|
||||||
|
clearLoadLocation()
|
||||||
|
|
||||||
|
Wait(renderTime or 500)
|
||||||
|
for i = 1, #camCache do
|
||||||
|
DestroyCam(camCache[i], true)
|
||||||
|
end
|
||||||
|
camCache = {}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function createCam(...) return createTempCam(...) end
|
||||||
|
function startCam(...) return startTempCam(...) end
|
||||||
|
function stopCam(...) return stopTempCam(...) end
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
|
||||||
|
--- Loads a specified model into memory.
|
||||||
|
---
|
||||||
|
--- This function checks if the model is valid and not already loaded.
|
||||||
|
--- If not loaded, it requests the model and waits until it is loaded or times out.
|
||||||
|
---
|
||||||
|
---@param model string|number The name or hash of the model to load.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- loadModel('prop_chair_01a')
|
||||||
|
--- ```
|
||||||
|
function loadModel(model, timeout)
|
||||||
|
if not IsModelValid(model) then print("^6Bridge^7: ^1ERROR^7: ^2Model^7 - '^6"..model.."^7' ^2does not exist in server") return false end
|
||||||
|
if HasModelLoaded(model) then return true end
|
||||||
|
|
||||||
|
debugPrint("^6Bridge^7: ^2Loading Model^7: '^6"..model.."^7'")
|
||||||
|
local deadline = GetGameTimer() + (timeout or 10000)
|
||||||
|
while not HasModelLoaded(model) and GetGameTimer() < deadline do
|
||||||
|
RequestModel(model)
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not HasModelLoaded(model) then
|
||||||
|
print("^6Bridge^7: ^3LoadModel^7: ^2Timed out loading model ^7'^6"..model.."^7'")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unloads a model from memory.
|
||||||
|
---
|
||||||
|
--- This function marks a model as no longer needed, allowing the game to free up memory.
|
||||||
|
---
|
||||||
|
---@param model string|number The name or hash of the model to unload.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- unloadModel('prop_chair_01a')
|
||||||
|
--- ```
|
||||||
|
function unloadModel(model)
|
||||||
|
debugPrint("^6Bridge^7: ^2Removing Model from memory cache^7: '^6"..model.."^7'")
|
||||||
|
SetModelAsNoLongerNeeded(model)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Loads an animation dictionary into memory.
|
||||||
|
---
|
||||||
|
--- This function checks if the animation dictionary exists and requests it.
|
||||||
|
--- It waits until the animation dictionary is loaded before proceeding.
|
||||||
|
---
|
||||||
|
---@param animDict string The name of the animation dictionary to load.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- loadAnimDict('amb@world_human_hang_out_street@male_c@base')
|
||||||
|
--- ```
|
||||||
|
function loadAnimDict(animDict)
|
||||||
|
if not DoesAnimDictExist(animDict) then
|
||||||
|
print("^6Bridge^7: ^1ERROR^7: ^2Anim Dictionary^7 - '^6"..animDict.."^7' ^2does not exist in server") return
|
||||||
|
else
|
||||||
|
debugPrint("^6Bridge^7: ^2Loading Anim Dictionary^7: '^6"..animDict.."^7'")
|
||||||
|
while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unloads an animation dictionary from memory.
|
||||||
|
---
|
||||||
|
--- This function removes the animation dictionary from the game's memory cache.
|
||||||
|
---
|
||||||
|
---@param animDict string The name of the animation dictionary to unload.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
---@
|
||||||
|
--- ```lua
|
||||||
|
--- unloadAnimDict('amb@world_human_hang_out_street@male_c@base')
|
||||||
|
--- ```
|
||||||
|
function unloadAnimDict(animDict)
|
||||||
|
debugPrint("^6Bridge^7: ^2Removing Anim Dictionary from memory cache^7: '^6"..animDict.."^7'")
|
||||||
|
RemoveAnimDict(animDict)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Loads a particle effects (ptfx) dictionary into memory.
|
||||||
|
---
|
||||||
|
--- This function requests the named particle effects asset and waits until it's loaded.
|
||||||
|
---
|
||||||
|
---@param ptFxName string The name of the particle effects dictionary to load.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- loadPtfxDict('core')
|
||||||
|
--- ```
|
||||||
|
function loadPtfxDict(ptFxName)
|
||||||
|
if not HasNamedPtfxAssetLoaded(ptFxName) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Loading Ptfx Dictionary^7: '^6"..ptFxName.."^7'")
|
||||||
|
while not HasNamedPtfxAssetLoaded(ptFxName) do RequestNamedPtfxAsset(ptFxName) Wait(5) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unloads a particle effects (ptfx) dictionary from memory.
|
||||||
|
---
|
||||||
|
--- This function removes the named particle effects asset from the game's memory cache.
|
||||||
|
---
|
||||||
|
---@param dict string The name of the particle effects dictionary to unload.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- unloadPtfxDict('core')
|
||||||
|
--- ```
|
||||||
|
function unloadPtfxDict(dict)
|
||||||
|
debugPrint("^6Bridge^7: ^2Removing Ptfx Dictionary^7: '^6"..dict.."^7'")
|
||||||
|
RemoveNamedPtfxAsset(dict)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Loads a texture dictionary into memory.
|
||||||
|
---
|
||||||
|
--- This function requests the streamed texture dictionary and waits until it's loaded.
|
||||||
|
---
|
||||||
|
---@param dict string The name of the texture dictionary to load.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- loadTextureDict('commonmenu')
|
||||||
|
--- ```
|
||||||
|
function loadTextureDict(dict)
|
||||||
|
if not HasStreamedTextureDictLoaded(dict) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Loading Texture Dictionary^7: '^6"..dict.."^7'")
|
||||||
|
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Loads a script audio bank into memory.
|
||||||
|
---
|
||||||
|
--- This function requests a script audio bank and waits until it's loaded or times out.
|
||||||
|
---
|
||||||
|
---@param bank string The name of the script audio bank to load.
|
||||||
|
---
|
||||||
|
---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local success = loadScriptBank('DLC_HEISTS_GENERAL_FRONTEND_SOUNDS')
|
||||||
|
--- ```
|
||||||
|
function loadScriptBank(bank)
|
||||||
|
local timeout = 2000
|
||||||
|
debugPrint("^6Bridge^7: ^2Loading ^3Script ^2AudioBank^7...")
|
||||||
|
while not RequestScriptAudioBank(bank, false) do Wait(10) timeout -= 10 if timeout <= 0 then break end end
|
||||||
|
|
||||||
|
local success = RequestScriptAudioBank(bank, false)
|
||||||
|
debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Loads an ambient audio bank into memory.
|
||||||
|
---
|
||||||
|
--- This function requests an ambient audio bank and waits until it's loaded or times out.
|
||||||
|
---
|
||||||
|
---@param bank string The name of the ambient audio bank to load.
|
||||||
|
---
|
||||||
|
---@return boolean `true` if the audio bank was successfully loaded; otherwise, `false`.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local success = loadAmbientBank('AMB_REVERB_GENERIC')
|
||||||
|
--- ```
|
||||||
|
function loadAmbientBank(bank)
|
||||||
|
local timeout = 2000
|
||||||
|
debugPrint("^6Bridge^7: ^2Loading ^3Ambient ^2AudioBank^7...")
|
||||||
|
while not RequestAmbientAudioBank(bank, 0) do
|
||||||
|
Wait(10)
|
||||||
|
timeout -= 10
|
||||||
|
if timeout <= 0 then break end
|
||||||
|
end
|
||||||
|
local success = RequestAmbientAudioBank(bank, 0)
|
||||||
|
debugPrint("^6Bridge^7: "..(success and "^3Successfully ^2loaded^7: '^4" or "^1Failed to ^2load^7: '^4")..bank.."^7'")
|
||||||
|
return success
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Plays an animation on a specified ped.
|
||||||
|
---
|
||||||
|
--- This function loads the animation dictionary and instructs the ped to play the animation.
|
||||||
|
---
|
||||||
|
---@param animDict string The name of the animation dictionary.
|
||||||
|
---@param animName string The name of the animation within the dictionary.
|
||||||
|
---@param duration number (optional) The duration to play the animation in milliseconds. Default is `30000`.
|
||||||
|
---@param flag number (optional) The animation flag controlling how the animation is played. Default is `50`.
|
||||||
|
---@param ped number (optional) The ped on which to play the animation. Defaults to the player's ped if not specified.
|
||||||
|
---@param speed number (optional) The speed multiplier for the animation. Default is `8.0`.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- playAnim('amb@world_human_hang_out_street@male_c@base', 'base', 5000, 1, PlayerPedId(), 1.0)
|
||||||
|
--- ```
|
||||||
|
function playAnim(animDict, animName, duration, flag, ped, speed)
|
||||||
|
loadAnimDict(animDict)
|
||||||
|
debugPrint("^6Bridge^7: ^3playAnim^7() ^2Triggered^7: ", animDict, animName, flag)
|
||||||
|
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Stops a specified animation on a ped.
|
||||||
|
---
|
||||||
|
--- This function stops the animation and unloads the animation dictionary from memory.
|
||||||
|
---
|
||||||
|
---@param animDict string The name of the animation dictionary.
|
||||||
|
---@param animName string The name of the animation within the dictionary.
|
||||||
|
---@param ped number (optional) The ped on which to stop the animation. Defaults to the player's ped if not specified.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- stopAnim('amb@world_human_hang_out_street@male_c@base', 'base', PlayerPedId())
|
||||||
|
--- ```
|
||||||
|
function stopAnim(animDict, animName, ped)
|
||||||
|
debugPrint("^6Bridge^7: ^3stopAnim^7() ^2Triggered^7: ", animDict, animName)
|
||||||
|
StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
|
||||||
|
StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
|
||||||
|
unloadAnimDict(animDict)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Plays a game sound from a specified coordinate or entity.
|
||||||
|
---
|
||||||
|
--- This function attempts to play a sound from either a coordinate or an entity, using the specified audio bank and sound name.
|
||||||
|
---
|
||||||
|
---@param bank string The name of the audio bank containing the sound.
|
||||||
|
---@param sound string The name of the sound to play.
|
||||||
|
---@param coords vector3|number A `vector3` coordinate or an entity handle from which to play the sound.
|
||||||
|
---@param synced boolean A boolean indicating whether the sound is synced across clients.
|
||||||
|
---@param range number (optional) The maximum range at which the sound can be heard. Default is `10.0`.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- playGameSound('DLC_HEIST_HACKING_SNAKE_SOUNDS', 'Beep', vector3(0, 0, 0), false, 15.0)
|
||||||
|
--- ```
|
||||||
|
function playGameSound(audioBank, soundSet, soundRef, coords, synced, range)
|
||||||
|
debugPrint("^6Bridge^7: ^2Attempting to play: ^3"..soundRef.." ^7('^4"..audioBank.."^7')")
|
||||||
|
loadScriptBank(audioBank)
|
||||||
|
local range = range or 10.0
|
||||||
|
local soundId = GetSoundId()
|
||||||
|
while not soundId do Wait(10) end
|
||||||
|
if type(coords) == "vector3" or type(coords) == "vector4" then
|
||||||
|
debugPrint("^6Bridge^7: ^2Playing sound from Coord^7: "..formatCoord(coords.xyz))
|
||||||
|
PlaySoundFromCoord(soundId, soundRef, coords.x, coords.y, coords.z, soundSet, synced, range, 0)
|
||||||
|
else
|
||||||
|
debugPrint("^6Bridge^7: ^2Playing sound from Entity^7: ^4"..coords.."^7")
|
||||||
|
PlaySoundFromEntity(soundId, soundRef, coords, soundSet, synced, 1.0)
|
||||||
|
end
|
||||||
|
ReleaseScriptAudioBank(audioBank)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Experimental, add fade in for spawned entities
|
||||||
|
function fadeInEnt(ent, duration)
|
||||||
|
if not DoesEntityExist(ent) then return end
|
||||||
|
duration = duration or 500 -- in ms
|
||||||
|
local fadeSteps = 20
|
||||||
|
local stepTime = duration / fadeSteps
|
||||||
|
|
||||||
|
SetEntityAlpha(ent, 0, false)
|
||||||
|
SetEntityVisible(ent, true, false)
|
||||||
|
SetEntityLocallyInvisible(ent) -- sometimes helps prevent "pop-in" in early frames
|
||||||
|
|
||||||
|
for i = 1, fadeSteps do
|
||||||
|
Wait(stepTime)
|
||||||
|
local newAlpha = math.floor((i / fadeSteps) * 255)
|
||||||
|
SetEntityAlpha(ent, newAlpha, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Restore full visibility
|
||||||
|
ResetEntityAlpha(ent)
|
||||||
|
end
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
local blipTable = {}
|
||||||
|
|
||||||
|
--- Creates a blip at specified coordinates with given properties.
|
||||||
|
--
|
||||||
|
-- This function adds a map blip at the provided coordinates and sets various display properties such as sprite, color, scale, and more.
|
||||||
|
-- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
|
||||||
|
--
|
||||||
|
---@param data A table containing blip data and properties.
|
||||||
|
-- - **coords**: A `vector3` containing x, y, z coordinates where the blip will be placed.
|
||||||
|
-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
|
||||||
|
-- - **col** (optional): The color ID of the blip. Default is `5`.
|
||||||
|
-- - **scale** (optional): The scale of the blip. Default is `0.7`.
|
||||||
|
-- - **disp** (optional): The display option of the blip. Default is `6`.
|
||||||
|
-- - **category** (optional): The category ID for the blip.
|
||||||
|
-- - **name**: The name of the blip, used for display on the map.
|
||||||
|
-- - **preview** (optional): A URL or image path for a preview image to display with the blip.
|
||||||
|
--
|
||||||
|
---@return blip blipID The handle of the created blip.
|
||||||
|
--
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- local blipData = {
|
||||||
|
-- coords = vector3(123.4, 567.8, 90.1),
|
||||||
|
-- sprite = 1,
|
||||||
|
-- col = 2,
|
||||||
|
-- scale = 0.8,
|
||||||
|
-- disp = 4,
|
||||||
|
-- category = 7,
|
||||||
|
-- name = "My Blip",
|
||||||
|
-- preview = "http://example.com/preview.png"
|
||||||
|
-- }
|
||||||
|
-- local blip = makeBlip(blipData)
|
||||||
|
-- ```
|
||||||
|
function makeBlip(data)
|
||||||
|
local blip = nil
|
||||||
|
if gameName == "rdr3" then
|
||||||
|
blip = BlipAddForCoords(1664425300, data.coords.x, data.coords.y, data.coords.z)
|
||||||
|
SetBlipSprite(blip, data.sprite or `blip_shop_market_stall`)
|
||||||
|
SetBlipScale(blip, data.scale or 0.2)
|
||||||
|
SetBlipName(blip, data.name)
|
||||||
|
--BlipSetStyle(blip, data.col or `BLIP_STYLE_CREATOR_DEFAULT`)
|
||||||
|
else
|
||||||
|
blip = AddBlipForCoord(vec3(data.coords.x, data.coords.y, data.coords.z))
|
||||||
|
SetBlipCoords(blip, data.coords.x, data.coords.y, data.coords.z) -- Manually set blip coordinates again as sometimes it just refuses
|
||||||
|
SetBlipAsShortRange(blip, true)
|
||||||
|
SetBlipSprite(blip, data.sprite or 106)
|
||||||
|
SetBlipColour(blip, data.col or 5)
|
||||||
|
SetBlipScale(blip, data.scale or 0.7)
|
||||||
|
SetBlipDisplay(blip, data.disp or 6)
|
||||||
|
if data.category then
|
||||||
|
SetBlipCategory(blip, data.category)
|
||||||
|
end
|
||||||
|
BeginTextCommandSetBlipName('STRING')
|
||||||
|
AddTextComponentString(tostring(data.name))
|
||||||
|
EndTextCommandSetBlipName(blip)
|
||||||
|
-- Handle preview image if certain resources are running
|
||||||
|
if isStarted("jim-blipcontroller") then
|
||||||
|
if data.preview then
|
||||||
|
local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
|
||||||
|
if data.preview:find("http") or data.preview:find("nui") then
|
||||||
|
createDui(txname, data.preview, vec2(512, 256), scriptTxd)
|
||||||
|
else
|
||||||
|
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
|
||||||
|
end
|
||||||
|
exports["jim-blipcontroller"]:ShowBlipInfo(blip, {
|
||||||
|
title = data.name,
|
||||||
|
dict = getScript()..'scriptTxd',
|
||||||
|
tex = txname,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
blipTable[blip] = blip
|
||||||
|
|
||||||
|
if DoesBlipExist(blip) then
|
||||||
|
debugPrint("^6Bridge^7: ^6Blip ^2created^7: '^6"..data.name.."^7' - '"..formatCoord(data.coords).."'")
|
||||||
|
else
|
||||||
|
print("Error making blip")
|
||||||
|
end
|
||||||
|
return blip
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a blip attached to a specified entity with given properties.
|
||||||
|
--
|
||||||
|
-- This function adds a map blip attached to the provided entity and sets various display properties such as sprite, color, scale, and more.
|
||||||
|
-- It also handles attaching a preview image to the blip if certain resources are running and a preview is provided.
|
||||||
|
--
|
||||||
|
---@param data table A table containing blip data and properties.
|
||||||
|
-- - **entity**: The entity to which the blip will be attached.
|
||||||
|
-- - **sprite** (optional): The blip sprite/icon ID. Default is `106`.
|
||||||
|
-- - **col** (optional): The color ID of the blip. Default is `5`.
|
||||||
|
-- - **scale** (optional): The scale of the blip. Default is `0.7`.
|
||||||
|
-- - **disp** (optional): The display option of the blip. Default is `6`.
|
||||||
|
-- - **category** (optional): The category ID for the blip.
|
||||||
|
-- - **name**: The name of the blip, used for display on the map.
|
||||||
|
-- - **preview** (optional): A URL or image path for a preview image to display with the blip.
|
||||||
|
--
|
||||||
|
--
|
||||||
|
---@return number blipID The handle of the created blip.
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- local blipData = {
|
||||||
|
-- entity = myEntity,
|
||||||
|
-- sprite = 1,
|
||||||
|
-- col = 2,
|
||||||
|
-- scale = 0.8,
|
||||||
|
-- disp = 4,
|
||||||
|
-- category = 7,
|
||||||
|
-- name = "Entity Blip",
|
||||||
|
-- preview = "http://example.com/preview.png"
|
||||||
|
-- }
|
||||||
|
-- local blip = makeEntityBlip(blipData)
|
||||||
|
-- ```
|
||||||
|
function makeEntityBlip(data)
|
||||||
|
local blip = nil
|
||||||
|
if gameName == "rdr3" then
|
||||||
|
blip = BlipAddForEntity(1664425300, data.entity)
|
||||||
|
SetBlipSprite(blip, data.sprite or `blip_ambient_coach`)
|
||||||
|
SetBlipScale(blip, data.scale or 0.2)
|
||||||
|
SetBlipName(blip, data.name)
|
||||||
|
|
||||||
|
else
|
||||||
|
AddBlipForEntity(data.entity)
|
||||||
|
blip = GetBlipFromEntity(data.entity)
|
||||||
|
blipTable[blip] = blip
|
||||||
|
SetBlipAsShortRange(blip, true)
|
||||||
|
SetBlipSprite(blip, data.sprite or 106)
|
||||||
|
SetBlipColour(blip, data.col or 5)
|
||||||
|
SetBlipScale(blip, data.scale or 0.7)
|
||||||
|
SetBlipDisplay(blip, data.disp or 6)
|
||||||
|
if data.category then SetBlipCategory(blip, data.category) end
|
||||||
|
BeginTextCommandSetBlipName('STRING')
|
||||||
|
AddTextComponentString(tostring(data.name))
|
||||||
|
EndTextCommandSetBlipName(blip)
|
||||||
|
-- Handle preview image if certain resources are running
|
||||||
|
if isStarted("jim-blipcontroller") then
|
||||||
|
if data.preview then
|
||||||
|
local txname = string.gsub(tostring(data.name..'preview'..string.gsub(data.coords.z, "%.", "")), "[ ()~]", "")
|
||||||
|
if data.preview:find("http") or data.preview:find("nui") then
|
||||||
|
createDui(txname, data.preview, vec2(512, 256), scriptTxd)
|
||||||
|
else
|
||||||
|
CreateRuntimeTextureFromImage(scriptTxd, txname, data.preview)
|
||||||
|
end
|
||||||
|
exports["jim-blipcontroller"]:ShowBlipInfo(blip, {
|
||||||
|
title = data.name,
|
||||||
|
dict = getScript()..'scriptTxd',
|
||||||
|
tex = txname,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
blipTable[blip] = blip
|
||||||
|
|
||||||
|
if DoesBlipExist(blip) then
|
||||||
|
debugPrint("^6Bridge^7: ^6Blip ^2created for Entity^7: '^6"..data.name.."^7'")
|
||||||
|
else
|
||||||
|
print("Error making blip")
|
||||||
|
end
|
||||||
|
return blip
|
||||||
|
end
|
||||||
|
|
||||||
|
if gameName == "rdr3" then
|
||||||
|
onResourceStop(function()
|
||||||
|
for k in pairs(blipTable) do
|
||||||
|
RemoveBlip(k)
|
||||||
|
end
|
||||||
|
end, true)
|
||||||
|
end
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
--- A table to keep track of all created Peds.
|
||||||
|
local Peds = {}
|
||||||
|
local distPeds = {}
|
||||||
|
|
||||||
|
--- Creates a distance-based Ped (pedestrian) that spawns when the player enters a specified area.
|
||||||
|
--
|
||||||
|
-- This function sets up a circular area using `createCirclePoly`. When the player enters this area, a Ped is created using `makePed`.
|
||||||
|
-- When the player exits the area, the Ped is deleted.
|
||||||
|
--
|
||||||
|
---@param data table A table containing Ped data and properties. Should include at least `model` and `coords`.
|
||||||
|
---@param coords vector4 A `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
|
||||||
|
---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
|
||||||
|
---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
|
||||||
|
---@param scenario boolean (optional) String specifying the scenario the Ped should perform.
|
||||||
|
---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
|
||||||
|
---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
|
||||||
|
---@param fade boolean (optional) Boolean indicating whether the Ped fades in. Default is `true`.
|
||||||
|
---@param keepModel boolean (optional) Boolean indicating whether the model stays loaded after the Ped is made. Default is `false`.
|
||||||
|
--
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- makeDistPed(pedData, pedCoords, true, false, 'WORLD_HUMAN_STAND_IMPATIENT', nil, true)
|
||||||
|
-- ```
|
||||||
|
function makeDistPed(data, coords, freeze, collision, scenario, anim, synced, func)
|
||||||
|
local zoneCoords = type(data) == "table" and data.coords or coords
|
||||||
|
local randName = keyGen()..keyGen()
|
||||||
|
distPeds[#distPeds+1] = createCirclePoly({
|
||||||
|
name = randName,
|
||||||
|
coords = vec3(zoneCoords.x, zoneCoords.y, zoneCoords.z - 1.03),
|
||||||
|
radius = 50.0,
|
||||||
|
onEnter = function()
|
||||||
|
Peds[randName] = makePed(data, coords, freeze, collision, scenario, anim, synced)
|
||||||
|
if func then func(Peds[randName]) end
|
||||||
|
end,
|
||||||
|
onExit = function()
|
||||||
|
DeletePed(Peds[randName])
|
||||||
|
Peds[randName] = nil
|
||||||
|
end,
|
||||||
|
debug = debugMode,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a Ped (pedestrian character) with specified properties.
|
||||||
|
--
|
||||||
|
-- This function creates a Ped at the given coordinates and applies appearance and clothing based on the provided data.
|
||||||
|
--
|
||||||
|
-- If `data` is a table with `custom` properties, it customizes the Ped's appearance accordingly.
|
||||||
|
--
|
||||||
|
---@param data modelHash|table Either a string/model hash of the Ped model to use, or a table containing `model` and `custom` data.
|
||||||
|
---@param coords vector4 `vector3` or `vector4` specifying the coordinates where the Ped will be placed.
|
||||||
|
---@param freeze boolean (optional) Boolean indicating whether the Ped should be frozen in place. Default is `true`.
|
||||||
|
---@param collision boolean (optional) Boolean indicating whether collision with the Ped is enabled. Default is `false`.
|
||||||
|
---@param scenario string (optional) String specifying the scenario the Ped should perform.
|
||||||
|
---@param anim table (optional) A table containing animation dictionary and name `{animDict, animName}`.
|
||||||
|
---@param synced boolean (optional) Boolean indicating whether the Ped is synced across clients. Default is `false`.
|
||||||
|
--
|
||||||
|
---@return ped entityID The handle of the created Ped.
|
||||||
|
--
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- local ped = makePed(pedData, pedCoords, true, false, nil, {'animDict', 'animName'}, true)
|
||||||
|
-- ```
|
||||||
|
function makePed(data, coords, freeze, collision, scenario, anim, synced, fade, keepModel)
|
||||||
|
local ped = nil
|
||||||
|
local model = nil
|
||||||
|
if type(data) == "table" then
|
||||||
|
model = data.model
|
||||||
|
loadModel(data.model)
|
||||||
|
ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced and synced or false, false)
|
||||||
|
-- Inheritance
|
||||||
|
SetPedHeadBlendData(ped, data.custom.faceFather, data.custom.faceMother, data.custom.raceShape, data.custom.skinFather, data.custom.skinMother, data.custom.raceSkin, data.custom.faceMix or 0, data.custom.skinMix or 0, data.custom.raceMix or 0, false)
|
||||||
|
|
||||||
|
-- Face Features
|
||||||
|
for k, v in pairs({
|
||||||
|
"noseWidth", "noseHeight", "noseSize", "noseBoneHeight", "nosePeakHeight", "noseBoneTwist",
|
||||||
|
"eyebrowHeight", "eyebrowDepth",
|
||||||
|
"cheekBoneHeight", "cheekBoneWidth", "cheeckWidth",
|
||||||
|
"eyeOpening", "lipThickness",
|
||||||
|
"jawWidth", "jawSize",
|
||||||
|
"chinLowering", "chinLength", "chinSize", "chinHole",
|
||||||
|
"neckThickness"
|
||||||
|
}) do
|
||||||
|
SetPedFaceFeature(ped, k - 1, data.custom[v])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Appearance
|
||||||
|
SetPedComponentVariation(ped, 2, data.custom.Hair, 0, 0)
|
||||||
|
SetPedHairColor(ped, data.custom.HairTexture, data.custom.HairHighlight or 0)
|
||||||
|
SetPedHeadOverlay(ped, 2, data.custom.Eyebrows, data.custom.EyebrowsOpacity)
|
||||||
|
SetPedHeadOverlayColor(ped, 2, 1, data.custom.EyebrowsColor, 0)
|
||||||
|
SetPedEyeColor(ped, data.custom.Eyecolor)
|
||||||
|
SetPedHeadOverlay(ped, 4, data.custom.Makeup, data.custom.MakeupOpacity)
|
||||||
|
SetPedHeadOverlayColor(ped, 4, 1, data.custom.MakeupColor, 0)
|
||||||
|
SetPedHeadOverlay(ped, 8, data.custom.Lipstick, data.custom.LipstickOpacity)
|
||||||
|
SetPedHeadOverlayColor(ped, 8, 1, data.custom.LipstickColor, 0)
|
||||||
|
SetPedHeadOverlay(ped, 1, data.custom.Beard, data.custom.BeardOpacity)
|
||||||
|
SetPedHeadOverlayColor(ped, 1, 1, data.custom.BeardColor, 0)
|
||||||
|
|
||||||
|
-- Clothes
|
||||||
|
SetPedComponentVariation(ped, 1, data.custom.Mask, data.custom.MaskVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 7, data.custom.Scarf, data.custom.ScarfVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 11, data.custom.Jacket, data.custom.JacketVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 8, data.custom.Shirt, data.custom.ShirtVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 9, data.custom.Vest, data.custom.VestVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 5, data.custom.Bags, data.custom.BagsVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 3, data.custom.Arms, data.custom.ArmsVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 4, data.custom.Pants, data.custom.PantsVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 6, data.custom.Shoes, data.custom.ShoesVariant, 0)
|
||||||
|
SetPedComponentVariation(ped, 10, data.custom.Decal, data.custom.DecalVariant, 0)
|
||||||
|
|
||||||
|
-- Accessories
|
||||||
|
SetPedPropIndex(ped, 0, data.custom.Hat, data.custom.HatVariant, true)
|
||||||
|
SetPedPropIndex(ped, 1, data.custom.Glasses, data.custom.GlassesVariant, true)
|
||||||
|
|
||||||
|
SetPedPropIndex(ped, 2, data.custom.Ear, data.custom.EarVariant, true)
|
||||||
|
SetPedPropIndex(ped, 6, data.custom.Watches, data.custom.WatchesVariant, true)
|
||||||
|
SetPedPropIndex(ped, 7, data.custom.Bracelets, data.custom.BraceletsVariant, true)
|
||||||
|
else
|
||||||
|
model = data
|
||||||
|
loadModel(model)
|
||||||
|
if gameName == "rdr3" then
|
||||||
|
ped = CreatePed(model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
|
||||||
|
SetEntityAlpha(ped, 0, false)
|
||||||
|
SetEntityVisible(ped, 1) -- SetEntityVisible
|
||||||
|
SetEntityAlpha(ped, 255, false) -- SetEntityAlpha
|
||||||
|
SetRandomOutfitVariation(ped, true) -- Invisible without
|
||||||
|
else
|
||||||
|
ped = CreatePed(0, model, coords.x, coords.y, coords.z - 1.03, coords.w, synced or false, false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SetEntityInvincible(ped, true)
|
||||||
|
SetBlockingOfNonTemporaryEvents(ped, true)
|
||||||
|
FreezeEntityPosition(ped, not (freeze == false))
|
||||||
|
|
||||||
|
if collision then SetEntityNoCollisionEntity(ped, PlayerPedId(), false) end
|
||||||
|
if scenario then TaskStartScenarioInPlace(ped, scenario, 0, true) end
|
||||||
|
if anim then
|
||||||
|
loadAnimDict(anim[1])
|
||||||
|
TaskPlayAnim(ped, anim[1], anim[2], 0.5, 1.0, -1, 1, 0.2, 0, 0, 0)
|
||||||
|
end
|
||||||
|
if DoesEntityExist(ped) then
|
||||||
|
debugPrint("^6Bridge^7: ^1Ped ^2Created^7: '^6"..ped.."^7' | ^2Hash^7: ^7'^5"..(model).."^7' | ^2Coord^7: "..formatCoord(coords))
|
||||||
|
else
|
||||||
|
print("error ped")
|
||||||
|
end
|
||||||
|
-- if keep model is true, don't unload the model from cache, scripts that spawn alot of the same model end up causing issues.
|
||||||
|
if not keepModel then unloadModel(model) end
|
||||||
|
Peds[ped] = ped
|
||||||
|
if fade ~= false and gameName ~= "rdr3" then
|
||||||
|
SetEntityAlpha(ped, 0, false)
|
||||||
|
CreateThread(function()
|
||||||
|
fadeInEnt(ped)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
return ped
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Generates random Ped data by filling in missing customization options with random values.
|
||||||
|
--
|
||||||
|
-- This function takes in a data table that may have some customization options missing in `data.custom`.
|
||||||
|
--
|
||||||
|
-- It generates random values for any missing options and returns a new data table with complete customization.
|
||||||
|
--
|
||||||
|
---@param data table A table containing at least a `model` field, and possibly a `custom` table with customization options.
|
||||||
|
--
|
||||||
|
---@return generatedTable table A new table containing `model` and `custom` with all customization options filled.
|
||||||
|
--
|
||||||
|
---@usage
|
||||||
|
-- ```lua
|
||||||
|
-- local pedData = GenerateRandomPedData({ model = `MP_M_Freemode_01`, custom = {} })
|
||||||
|
-- ```
|
||||||
|
function GenerateRandomPedData(data)
|
||||||
|
local newTable = {
|
||||||
|
model = data.model,
|
||||||
|
custom = {},
|
||||||
|
}
|
||||||
|
local isMale = data.model == `MP_M_Freemode_01`
|
||||||
|
local randomTable = {
|
||||||
|
-- Inheritance
|
||||||
|
faceFather = math.random(0, 45), faceMother = math.random(0, 45), faceMix = (math.random(0, 9) / 10),
|
||||||
|
skinFather = math.random(0, 45), skinMother = math.random(0, 45), skinMix = (math.random(0, 9) / 10),
|
||||||
|
raceShape = math.random(0, 45), raceSkin = math.random(0, 45), raceMix = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
-- Face Features
|
||||||
|
noseWidth = (math.random(0, 9) / 10),
|
||||||
|
noseHeight = (math.random(0, 9) / 10),
|
||||||
|
noseSize = (math.random(0, 9) / 10),
|
||||||
|
noseBoneHeight = (math.random(0, 9) / 10),
|
||||||
|
nosePeakHeight = (math.random(0, 9) / 10),
|
||||||
|
noseBoneTwist = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
eyebrowHeight = (math.random(0, 9) / 10),
|
||||||
|
eyebrowDepth = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
cheekBoneHeight = (math.random(0, 9) / 10),
|
||||||
|
cheekBoneWidth = (math.random(0, 9) / 10),
|
||||||
|
cheeckWidth = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
eyeOpening = (math.random(0, 9) / 10),
|
||||||
|
lipThickness = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
jawWidth = (math.random(0, 9) / 10),
|
||||||
|
jawSize = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
chinLowering = (math.random(0, 9) / 10),
|
||||||
|
chinLength = (math.random(0, 9) / 10),
|
||||||
|
chinSize = (math.random(0, 9) / 10),
|
||||||
|
chinHole = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
neckThickness = (math.random(0, 9) / 10),
|
||||||
|
|
||||||
|
-- Appearance
|
||||||
|
Hair = math.random(0, isMale and 147 or 261), HairTexture = math.random(0, 63), HairHighlight = math.random(0, 63),
|
||||||
|
Eyebrows = math.random(0, 33),
|
||||||
|
EyebrowsOpacity = 0.9, EyebrowsColor = 0,
|
||||||
|
Eyecolor = math.random(0, 30),
|
||||||
|
Makeup = 0, MakeupOpacity = 0, MakeupColor = 0,
|
||||||
|
Lipstick = 0, LipstickOpacity = 0, LipstickColor = 0,
|
||||||
|
Beard = isMale and math.random(0, 28) or -1,
|
||||||
|
BeardOpacity = isMale and 0.9 or 0.0, BeardColor = 0,
|
||||||
|
|
||||||
|
-- Clothing
|
||||||
|
Mask = math.random(0, 252), MaskVariant = 0,
|
||||||
|
Scarf = math.random(0, isMale and 249 or 198), ScarfVariant = 0,
|
||||||
|
Jacket = math.random(0, isMale and 634 or 713), JacketVariant = 0,
|
||||||
|
Shirt = math.random(0, isMale and 237 or 299), ShirtVariant = 0,
|
||||||
|
Vest = math.random(0, isMale and 81 or 91), VestVariant = 0,
|
||||||
|
Bags = math.random(0, isMale and 138 or 148), BagsVariant = 0,
|
||||||
|
Arms = math.random(0, isMale and 224 or 261), ArmsVariant = 0,
|
||||||
|
Pants = math.random(0, isMale and 255 or 275), PantsVariant = 0,
|
||||||
|
Shoes = math.random(0, isMale and 157 or 199), ShoesVariant = 0,
|
||||||
|
Decal = math.random(0, isMale and 238 or 253), DecalVariant = 0,
|
||||||
|
|
||||||
|
-- Accessories
|
||||||
|
Hat = math.random(0, isMale and 232 or 229), HatVariant = 0,
|
||||||
|
Glasses = math.random(0, isMale and 68 or 71), GlassesVariant = 0,
|
||||||
|
Ear = math.random(0, isMale and 51 or 40), EarVariant = 0,
|
||||||
|
Watches = math.random(0, isMale and 46 or 35), WatchesVariant = 0,
|
||||||
|
Bracelets = math.random(0, isMale and 13 or 20), BraceletsVariant = 0,
|
||||||
|
}
|
||||||
|
for option in pairs(randomTable) do
|
||||||
|
if not data.custom[option] then
|
||||||
|
newTable.custom[option] = randomTable[option]
|
||||||
|
debugPrint("^6Bridge^7: ^2Picking Random Ped option ^7[^5"..option.."^7]: ^6"..newTable.custom[option].."^7")
|
||||||
|
else
|
||||||
|
newTable.custom[option] = data.custom[option]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return newTable
|
||||||
|
end
|
||||||
|
|
||||||
|
onPlayerUnload(function()
|
||||||
|
for k, ped in pairs(Peds) do
|
||||||
|
if DoesEntityExist(ped) then DeletePed(ped) end
|
||||||
|
Peds[k] = nil
|
||||||
|
end
|
||||||
|
for i = 1, #distPeds do
|
||||||
|
removeZoneTarget(distPeds[i])
|
||||||
|
end
|
||||||
|
distPeds = {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Cleans up all created Peds when the resource stops.
|
||||||
|
onResourceStop(function()
|
||||||
|
for k, ped in pairs(Peds) do
|
||||||
|
if DoesEntityExist(ped) then DeletePed(ped) end
|
||||||
|
Peds[k] = nil
|
||||||
|
end
|
||||||
|
end, true)
|
||||||
|
|
||||||
|
-- Drop the peds that are already gone
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(5000)
|
||||||
|
for k, ped in pairs(Peds) do
|
||||||
|
if type(k) == "number" and not DoesEntityExist(ped) then Peds[k] = nil end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
local Props = {}
|
||||||
|
local distProps = {}
|
||||||
|
|
||||||
|
--- Creates a prop (object) in the game world at specified coordinates.
|
||||||
|
---
|
||||||
|
--- This function loads the model, creates the object, sets its heading, and freezes it if specified.
|
||||||
|
---
|
||||||
|
---@param data table A table containing prop data.
|
||||||
|
--- - **prop** `string`: The model name or hash of the prop to create.
|
||||||
|
--- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
|
||||||
|
---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
|
||||||
|
---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
|
||||||
|
---
|
||||||
|
---@return number entityID The handle of the created prop object.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local propData = {
|
||||||
|
--- prop = 'prop_chair_01a',
|
||||||
|
--- coords = vector4(123.4, 567.8, 90.1, 180.0)
|
||||||
|
--- }
|
||||||
|
--- local prop = makeProp(propData, true, false)
|
||||||
|
--- ```
|
||||||
|
function makeProp(data, freeze, synced, fade)
|
||||||
|
loadModel(data.prop)
|
||||||
|
local prop = CreateObject(data.prop, data.coords.x, data.coords.y, data.coords.z-1.03, synced and synced or false, synced and synced or false, false)
|
||||||
|
SetEntityHeading(prop, (data.coords.w or 0) + 180.0)
|
||||||
|
FreezeEntityPosition(prop, freeze or false)
|
||||||
|
|
||||||
|
debugPrint("^6Bridge^7: ^1Prop ^2Created^7: '^6"..prop.."^7' | ^2Hash^7: ^7'^6"..data.prop.."^7' | ^2Coord^7: "..formatCoord(data.coords))
|
||||||
|
SetModelAsNoLongerNeeded(data.prop)
|
||||||
|
Props[prop] = prop
|
||||||
|
if fade ~= false and gameName ~= "rdr3" then
|
||||||
|
SetEntityAlpha(prop, 0, false)
|
||||||
|
CreateThread(function()
|
||||||
|
fadeInEnt(prop)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
return prop
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Creates a prop that appears when the player is within a certain distance.
|
||||||
|
---
|
||||||
|
--- This function sets up a proximity area, and when the player enters it, the prop is created.
|
||||||
|
--- When the player exits the area, the prop is destroyed.
|
||||||
|
---
|
||||||
|
---@param data table A table containing prop data.
|
||||||
|
--- - **prop** `string`: The model name or hash of the prop to create.
|
||||||
|
--- - **coords** `vector4`: The coordinates where the prop will be placed. Should include x, y, z, and w (heading).
|
||||||
|
---@param freeze boolean (optional) Whether to freeze the prop in place. Defaults to `false`.
|
||||||
|
---@param synced boolean (optional) Whether the prop should be synced across clients. Defaults to `false`.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local propData = {
|
||||||
|
--- prop = 'prop_chair_01a',
|
||||||
|
--- coords = vector4(123.4, 567.8, 90.1, 180.0)
|
||||||
|
--- }
|
||||||
|
--- makeDistProp(propData, true, false)
|
||||||
|
--- ```
|
||||||
|
function makeDistProp(data, freeze, synced, range, func)
|
||||||
|
local name = keyGen()..keyGen()
|
||||||
|
distProps[#distProps + 1] = createCirclePoly({
|
||||||
|
name = name,
|
||||||
|
coords = vec3(data.coords.x, data.coords.y, data.coords.z - 1.03),
|
||||||
|
radius = range or 50.0,
|
||||||
|
onEnter = function()
|
||||||
|
Props[name] = makeProp(data, freeze, synced)
|
||||||
|
if func then
|
||||||
|
func(Props[name])
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
onExit = function()
|
||||||
|
destroyProp(Props[name])
|
||||||
|
Props[name] = nil
|
||||||
|
end,
|
||||||
|
debug = debugMode,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Destroys a prop, detaching it if attached to the player beforehand.
|
||||||
|
---
|
||||||
|
---@param entity number The handle of the prop entity to destroy.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- destroyProp(prop)
|
||||||
|
--- ```
|
||||||
|
function destroyProp(entity)
|
||||||
|
if entity then
|
||||||
|
debugPrint("^6Bridge^7: ^2Destroying Prop^7: '^6"..entity.."^7'")
|
||||||
|
if IsEntityAttachedToEntity(entity, PlayerPedId()) then
|
||||||
|
SetEntityAsMissionEntity(entity)
|
||||||
|
DetachEntity(entity, true, true)
|
||||||
|
end
|
||||||
|
DeleteObject(entity)
|
||||||
|
Props[entity] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
onPlayerUnload(function()
|
||||||
|
for k, prop in pairs(Props) do
|
||||||
|
if DoesEntityExist(prop) then DeleteObject(prop) end
|
||||||
|
Props[k] = nil
|
||||||
|
end
|
||||||
|
for i = 1, #distProps do
|
||||||
|
removeZoneTarget(distProps[i])
|
||||||
|
end
|
||||||
|
distProps = {}
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Cleans up all created props when the resource stops.
|
||||||
|
onResourceStop(function()
|
||||||
|
for k, prop in pairs(Props) do
|
||||||
|
destroyProp(prop)
|
||||||
|
end
|
||||||
|
end, true)
|
||||||
|
|
||||||
|
-- Drop the props that are already gone
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(5000)
|
||||||
|
for k, prop in pairs(Props) do
|
||||||
|
if type(k) == "number" and not DoesEntityExist(prop) then Props[k] = nil end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
local Vehicles = {}
|
||||||
|
|
||||||
|
--- Creates a vehicle with the specified model and coordinates.
|
||||||
|
---
|
||||||
|
--- This function loads the vehicle model, creates the vehicle in the world at the given coordinates, sets initial properties, and returns the vehicle handle.
|
||||||
|
---
|
||||||
|
---@param model string|number The model name or hash of the vehicle to create.
|
||||||
|
---@param coords vector4 The coordinates where the vehicle will be placed, including x, y, z, and w (heading).
|
||||||
|
---
|
||||||
|
---@return number entityID The handle of the created vehicle.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local vehicle = makeVeh('adder', vector4(123.4, 567.8, 90.1, 180.0))
|
||||||
|
--- ```
|
||||||
|
function makeVeh(model, coords, synced, fade)
|
||||||
|
loadModel(model)
|
||||||
|
local veh = CreateVehicle(model, coords.x, coords.y, coords.z, coords.w, synced ~= false, false)
|
||||||
|
SetVehicleHasBeenOwnedByPlayer(veh, true)
|
||||||
|
if gameName ~= "rdr3" then
|
||||||
|
SetNetworkIdCanMigrate(NetworkGetNetworkIdFromEntity(veh), true)
|
||||||
|
Wait(100)
|
||||||
|
SetVehicleNeedsToBeHotwired(veh, false)
|
||||||
|
SetVehRadioStation(veh, 'OFF')
|
||||||
|
SetVehicleFuelLevel(veh, 100.0)
|
||||||
|
SetVehicleModKit(veh, 0)
|
||||||
|
|
||||||
|
end
|
||||||
|
SetVehicleOnGroundProperly(veh)
|
||||||
|
debugPrint("^6Bridge^7: ^1Veh ^2Created^7: '^6"..veh.."^7' | ^2Hash^7: ^7'^6"..model.."^7' | ^2Coord^7: "..formatCoord(coords))
|
||||||
|
unloadModel(model)
|
||||||
|
Vehicles[#Vehicles + 1] = veh
|
||||||
|
if fade ~= false and gameName ~= "rdr3" then
|
||||||
|
SetEntityAlpha(veh, 0, false)
|
||||||
|
CreateThread(function()
|
||||||
|
fadeInEnt(veh)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
return veh
|
||||||
|
end
|
||||||
|
|
||||||
|
local distanceVehicles = {}
|
||||||
|
--- Creates a vehicle that spawns when the player enters a designated polyzone area.
|
||||||
|
---
|
||||||
|
--- This function sets up a circular polyzone; when the player enters the zone, the vehicle is spawned,
|
||||||
|
--- and when the player exits, the vehicle is deleted.
|
||||||
|
---
|
||||||
|
---@param data table A table containing vehicle data.
|
||||||
|
--- - **vehicle** `string`: The model name or hash of the vehicle to spawn.
|
||||||
|
--- - **coords** `vector4`: The coordinates where the vehicle will be placed. Should include x, y, z, and w (heading).
|
||||||
|
---@param freeze boolean (optional) Whether to freeze the vehicle in place. Defaults to `false`.
|
||||||
|
---@param synced boolean (optional) Whether the vehicle should be synced across clients. Defaults to `false`.
|
||||||
|
function makeDistVehicle(data, radius, onEnter, onExit)
|
||||||
|
local vehicle = nil
|
||||||
|
local zoneId = keyGen() .. keyGen()
|
||||||
|
local zone = createCirclePoly({
|
||||||
|
name = zoneId,
|
||||||
|
coords = vec3(data.coords.x, data.coords.y, data.coords.z),
|
||||||
|
radius = radius,
|
||||||
|
onEnter = function()
|
||||||
|
vehicle = makeVeh(data.model, data.coords, false)
|
||||||
|
if onEnter then
|
||||||
|
debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onEnter^7() ^2running^7")
|
||||||
|
onEnter(vehicle)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
onExit = function()
|
||||||
|
deleteVehicle(vehicle)
|
||||||
|
if onExit then
|
||||||
|
debugPrint("^6Bridge^7: ^4makeDistVehicle ^3onExit^7() ^2running^7")
|
||||||
|
onExit(vehicle)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
debug = debugMode,
|
||||||
|
})
|
||||||
|
distanceVehicles[zoneId] = { zone = zone, vehicle = vehicle }
|
||||||
|
return zoneId
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Removes a specific distance-based vehicle spawning zone.
|
||||||
|
---
|
||||||
|
---@param zoneId string The unique identifier of the zone to remove.
|
||||||
|
function removeDistVehicleZone(zoneId)
|
||||||
|
if distanceVehicles[zoneId].zone then
|
||||||
|
removePolyZone(distanceVehicles[zoneId].zone) -- Adjust this if your polyzone library uses a different removal method.
|
||||||
|
if distanceVehicles[zoneId].vehicle then
|
||||||
|
deleteVehicle(distanceVehicles[zoneId].vehicle)
|
||||||
|
end
|
||||||
|
distanceVehicles[zoneId] = nil
|
||||||
|
print("Removed polyzone for zoneId: " .. zoneId)
|
||||||
|
else
|
||||||
|
print("No zone found with zoneId: " .. zoneId)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Deletes a spawned vehicle.
|
||||||
|
---
|
||||||
|
---@param vehicle number The handle of the vehicle entity to delete.
|
||||||
|
function deleteVehicle(vehicle)
|
||||||
|
if vehicle then
|
||||||
|
debugPrint("^6Bridge^7: ^2Destroying Vehicle^7: '^6" .. vehicle .. "^7'")
|
||||||
|
if IsEntityAttachedToEntity(vehicle, PlayerPedId()) then
|
||||||
|
SetEntityAsMissionEntity(vehicle)
|
||||||
|
DetachEntity(vehicle, true, true)
|
||||||
|
end
|
||||||
|
DeleteVehicle(vehicle)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Cleans up all created vehicles when the resource stops.
|
||||||
|
onResourceStop(function(r)
|
||||||
|
for i = 1, #Vehicles do DeleteVehicle(Vehicles[i]) end
|
||||||
|
end)
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,383 @@
|
|||||||
|
local inProgress = false
|
||||||
|
local storedPID = nil
|
||||||
|
|
||||||
|
|
||||||
|
local progressFunc = {
|
||||||
|
ox = {
|
||||||
|
start =
|
||||||
|
function(data)
|
||||||
|
local options = {
|
||||||
|
duration = debugMode and 1000 or data.time,
|
||||||
|
label = data.label,
|
||||||
|
position = data.position or "bottom",
|
||||||
|
useWhileDead = data.dead or false,
|
||||||
|
canCancel = data.cancel or true,
|
||||||
|
anim = {
|
||||||
|
dict = data.dict,
|
||||||
|
clip = data.anim,
|
||||||
|
flag = (data.flag == 8 and 32 or data.flag) or nil,
|
||||||
|
scenario = data.task
|
||||||
|
},
|
||||||
|
disable = {
|
||||||
|
combat = data.combat or true,
|
||||||
|
move = data.disableMovement or false,
|
||||||
|
car = data.disableMovement or false,
|
||||||
|
mouse = data.mouse or false
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if data.prop and data.prop.model then
|
||||||
|
options.prop = {
|
||||||
|
model = data.prop.model,
|
||||||
|
pos = data.prop.pos or vec3(0, 0, 0),
|
||||||
|
rot = data.prop.rot or vec3(0, 0, 0),
|
||||||
|
bone = data.prop.bone or 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if data.propTwo and data.propTwo.model then
|
||||||
|
options.propTwo = {
|
||||||
|
model = data.propTwo.model,
|
||||||
|
pos = data.propTwo.pos or vec3(0, 0, 0),
|
||||||
|
rot = data.propTwo.rot or vec3(0, 0, 0),
|
||||||
|
bone = data.propTwo.bone or 0
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if data.progressType == "circle" then
|
||||||
|
if exports[OXLibExport]:progressCircle(options) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not data.progressType or data.progressType == "bar" then
|
||||||
|
if exports[OXLibExport]:progressBar(options) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
stop =
|
||||||
|
function()
|
||||||
|
exports[OXLibExport]:cancelProgress()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
qb = {
|
||||||
|
start =
|
||||||
|
function(data)
|
||||||
|
local p = promise.new()
|
||||||
|
Core.Functions.Progressbar("progbar",
|
||||||
|
data.label,
|
||||||
|
debugMode and 1000 or data.time,
|
||||||
|
data.dead or false,
|
||||||
|
data.cancel or true,
|
||||||
|
{
|
||||||
|
disableMovement = data.disableMovement or false,
|
||||||
|
disableCarMovement = data.disableMovement or false,
|
||||||
|
disableMouse = data.disableMouse or false,
|
||||||
|
disableCombat = data.disableCombat or true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
animDict = data.dict,
|
||||||
|
anim = data.anim,
|
||||||
|
flags = data.flag or 32,
|
||||||
|
task = data.task
|
||||||
|
},
|
||||||
|
{}, {},
|
||||||
|
function() p:resolve(true) end,
|
||||||
|
function() p:resolve(false) end,
|
||||||
|
data.icon)
|
||||||
|
return Citizen.Await(p)
|
||||||
|
end,
|
||||||
|
stop =
|
||||||
|
function()
|
||||||
|
TriggerEvent("progressbar:client:cancel")
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
esx = {
|
||||||
|
start =
|
||||||
|
function(data)
|
||||||
|
local p = promise.new()
|
||||||
|
ESX.Progressbar(data.label, debugMode and 1000 or data.time, {
|
||||||
|
FreezePlayer = true,
|
||||||
|
animation = {
|
||||||
|
type = data.anim,
|
||||||
|
dict = data.dict,
|
||||||
|
scenario = data.task,
|
||||||
|
},
|
||||||
|
onFinish = function()
|
||||||
|
p:resolve(true)
|
||||||
|
end,
|
||||||
|
onCancel = function()
|
||||||
|
p:resolve(false)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
})
|
||||||
|
return Citizen.Await(p)
|
||||||
|
end,
|
||||||
|
stop =
|
||||||
|
function()
|
||||||
|
ESX.CancelProgressbar()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
lation = {
|
||||||
|
start =
|
||||||
|
function(data)
|
||||||
|
if exports.lation_ui:progressBar({
|
||||||
|
label = data.label,
|
||||||
|
description = nil,
|
||||||
|
duration = debugMode and 1000 or data.time,
|
||||||
|
icon = data.icon,
|
||||||
|
useWhileDead = data.dead or false,
|
||||||
|
disable = {
|
||||||
|
combat = data.combat or true,
|
||||||
|
move = data.disableMovement or false,
|
||||||
|
car = data.disableMovement or false,
|
||||||
|
},
|
||||||
|
anim = {
|
||||||
|
dict = data.dict,
|
||||||
|
clip = data.anim,
|
||||||
|
flag = data.flag
|
||||||
|
},
|
||||||
|
prop = {
|
||||||
|
model = data.prop and data.prop.model,
|
||||||
|
pos = data.prop and (data.prop.pos or vec3(0, 0, 0)),
|
||||||
|
rot = data.prop and (data.prop.rot or vec3(0, 0, 0)),
|
||||||
|
bone = data.prop and (data.prop.bone or 0)
|
||||||
|
}
|
||||||
|
}) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
stop =
|
||||||
|
function()
|
||||||
|
exports.lation_ui:cancelProgress()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
red = {
|
||||||
|
start =
|
||||||
|
function(data)
|
||||||
|
if exports.jim_bridge:redProgressBar({
|
||||||
|
label = data.label,
|
||||||
|
time = debugMode and 1000 or data.time,
|
||||||
|
dict = data.dict,
|
||||||
|
anim = data.anim,
|
||||||
|
flag = data.flag or 32,
|
||||||
|
task = data.task,
|
||||||
|
disableMovement = data.disableMovement or false,
|
||||||
|
cancel = data.cancel or true,
|
||||||
|
}) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
stop =
|
||||||
|
function()
|
||||||
|
exports.jim_bridge:stopProgressBar()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
gta = {
|
||||||
|
start =
|
||||||
|
function(data)
|
||||||
|
if exports.jim_bridge:gtaProgressBar({
|
||||||
|
label = data.label,
|
||||||
|
time = debugMode and 1000 or data.time,
|
||||||
|
dict = data.dict,
|
||||||
|
anim = data.anim,
|
||||||
|
flag = data.flag or 32,
|
||||||
|
task = data.task,
|
||||||
|
disableMovement = data.disableMovement or false,
|
||||||
|
cancel = data.cancel or true,
|
||||||
|
}) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
stop =
|
||||||
|
function()
|
||||||
|
exports.jim_bridge:stopProgressBar()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
["17mov"] = {
|
||||||
|
start =
|
||||||
|
function(data)
|
||||||
|
if exports["17mov_Hud"]:StartProgress({
|
||||||
|
duration = debugMode and 1000 or data.time,
|
||||||
|
label = data.label,
|
||||||
|
useWhileDead = data.dead or false,
|
||||||
|
canCancel = data.cancel or true,
|
||||||
|
controlDisables = {
|
||||||
|
disableMovement = data.disableMovement or false,
|
||||||
|
disableCarMovement = data.disableMovement or false,
|
||||||
|
disableMouse = data.mouse or false,
|
||||||
|
disableCombat = data.combat or true,
|
||||||
|
},
|
||||||
|
animation = {
|
||||||
|
animDict = data.dict,
|
||||||
|
anim = data.anim,
|
||||||
|
flags = (data.flag == 8 and 32 or data.flag) or nil,
|
||||||
|
task = data.task,
|
||||||
|
},
|
||||||
|
prop = data.prop and {
|
||||||
|
model = data.prop.model,
|
||||||
|
bone = data.prop.bone or 0,
|
||||||
|
coords = data.prop.pos or vec3(0, 0, 0),
|
||||||
|
rotation = data.prop.rot or vec3(0, 0, 0),
|
||||||
|
|
||||||
|
} or nil,
|
||||||
|
propTwo = data.propTwo and {
|
||||||
|
model = data.propTwo.model,
|
||||||
|
bone = data.propTwo.bone or 0,
|
||||||
|
coords = data.propTwo.pos or vec3(0, 0, 0),
|
||||||
|
rotation = data.propTwo.rot or vec3(0, 0, 0),
|
||||||
|
} or nil,
|
||||||
|
|
||||||
|
}, nil, nil) then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
stop =
|
||||||
|
function()
|
||||||
|
exports["17mov_Hud"]:StopProgress()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Displays a progress bar using the configured progress bar system.
|
||||||
|
---
|
||||||
|
--- This function handles displaying a progress bar to the player using the specified progress bar system (e.g., ox, qb, esx, gta).
|
||||||
|
--- It supports shared progress bars between players, animations, camera effects, and more.
|
||||||
|
---
|
||||||
|
---@param data table A table containing the progress bar configuration.
|
||||||
|
--- - **label** (`string`): The text label to display on the progress bar.
|
||||||
|
--- - **time** (`number`): The duration of the progress bar in milliseconds.
|
||||||
|
--- - **dict** (`string`, optional): The animation dictionary to use.
|
||||||
|
--- - **anim** (`string`, optional): The animation name to play.
|
||||||
|
--- - **task** (`string`, optional): The task scenario to perform.
|
||||||
|
--- - **flag** (`number`, optional): The animation flag.
|
||||||
|
--- - **dead** (`boolean`, optional): Whether to allow the progress bar when the player is dead. Default is `false`.
|
||||||
|
--- - **cancel** (`boolean`, optional): Whether the progress bar can be canceled by the player. Default is `true`.
|
||||||
|
--- - **icon** (`string`, optional): The icon to display (for qb progress bar).
|
||||||
|
--- - **cam** (`number`, optional): The camera handle to use.
|
||||||
|
--- - **shared** (`table`, optional): Data for shared progress bars.
|
||||||
|
--- - **pid** (`number`): The player ID to share the progress bar with.
|
||||||
|
--- - **label** (`string`): The label to display on the shared progress bar.
|
||||||
|
---
|
||||||
|
--- @return boolean `true` if the progress bar completed successfully, or `false` if it was canceled.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- local success = progressBar({
|
||||||
|
--- label = "Processing...",
|
||||||
|
--- time = 5000,
|
||||||
|
--- dict = "amb@world_human_hang_out_street@female_hold_arm@base",
|
||||||
|
--- anim = "base",
|
||||||
|
--- flag = 49,
|
||||||
|
--- cancel = true,
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
|
||||||
|
local progresssBarActive = false
|
||||||
|
function progressBar(data)
|
||||||
|
if progresssBarActive then return else progresssBarActive = true end
|
||||||
|
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
if data.shared then
|
||||||
|
debugPrint("^6Bridge^7: ^6Sharing progressBar to player^7: ^6"..data.shared.pid.."^7")
|
||||||
|
storedPID = data.shared.pid
|
||||||
|
TriggerServerEvent(getScript()..":server:sharedProg:Start", data)
|
||||||
|
end
|
||||||
|
local result = nil
|
||||||
|
if data.cam then startTempCam(data.cam) end
|
||||||
|
|
||||||
|
if progressFunc[Config.System.ProgressBar].start(data) then
|
||||||
|
result = true
|
||||||
|
else
|
||||||
|
result = false
|
||||||
|
end
|
||||||
|
|
||||||
|
while result == nil do
|
||||||
|
Wait(10)
|
||||||
|
end
|
||||||
|
debugPrint("^5Debug^7: ^2ProgressBar result^7: ^3"..tostring(result).."^7")
|
||||||
|
|
||||||
|
-- Cleanup
|
||||||
|
FreezeEntityPosition(ped, false)
|
||||||
|
lockInv(false)
|
||||||
|
if data.cam then
|
||||||
|
stopTempCam(data.cam)
|
||||||
|
end
|
||||||
|
if result == false and data.shared then
|
||||||
|
debugPrint("^6Bridge^7: ^2Sending cancel to ^6"..storedPID.."^7")
|
||||||
|
TriggerServerEvent(getScript().."server:sharedProg:cancel", storedPID)
|
||||||
|
end
|
||||||
|
storedPID = nil
|
||||||
|
if result == false then
|
||||||
|
currentToken = nil
|
||||||
|
TriggerServerEvent(getScript()..":clearAuthToken")
|
||||||
|
end
|
||||||
|
if result == true and data.request then
|
||||||
|
TriggerServerEvent(getScript()..":clearAuthToken")
|
||||||
|
currentToken = triggerCallback(AuthEvent)
|
||||||
|
end
|
||||||
|
progresssBarActive = false
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Stops the current progress bar.
|
||||||
|
---
|
||||||
|
--- This function cancels the progress bar based on the configured progress bar system, handling any necessary cleanup.
|
||||||
|
function stopProgressBar()
|
||||||
|
progressFunc[Config.System.ProgressBar].stop()
|
||||||
|
progresssBarActive = false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- System to handle sending/sharing progress bars between players --
|
||||||
|
-- For example, healing someone --
|
||||||
|
|
||||||
|
|
||||||
|
--- Server event handler for starting a shared progress bar.
|
||||||
|
--- This event is triggered when a player wants to start a progress bar on another player.
|
||||||
|
--- It adjusts the data to prevent loops and sends the data to the target client.
|
||||||
|
RegisterNetEvent(getScript()..":server:sharedProg:Start", function(data)
|
||||||
|
local pid = data.shared.pid -- Get player ID from the client
|
||||||
|
data.label = data.shared.label -- Set progress bar label to the shared label
|
||||||
|
data.cancel = false -- Make it so it can't be canceled
|
||||||
|
data.dead = true -- Allow progress bar even if player is dead
|
||||||
|
data.shared = nil -- Remove shared info to prevent loops
|
||||||
|
data.anim = nil -- Remove animation so players don't share it
|
||||||
|
debugPrint("^6Bridge^7: ^6"..source.." ^2is sending shared progressBar to player^7, ^6"..pid.."^7")
|
||||||
|
TriggerClientEvent(getScript()..":client:sharedProg:Start", pid, data)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Client event handler for starting a shared progress bar.
|
||||||
|
--- This event is triggered when the server wants the client to start a shared progress bar.
|
||||||
|
RegisterNetEvent(getScript()..":client:sharedProg:Start", function(data)
|
||||||
|
debugPrint("^6Bridge^7: ^2You have been sent a progressBar^7")
|
||||||
|
progressBar(data)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Server event handler for canceling a shared progress bar.
|
||||||
|
--- This event is triggered when a progress bar is canceled and the server needs to notify the other player.
|
||||||
|
RegisterNetEvent(getScript()..":server:sharedProg:Cancel", function(pid)
|
||||||
|
debugPrint("^6Bridge^7: ^2Sending cancel progressBar to ^6"..pid.."^7")
|
||||||
|
TriggerClientEvent(getScript()..":client:sharedProg:Cancel", pid)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Client event handler for canceling a shared progress bar.
|
||||||
|
--- This event is triggered when the server wants the client to cancel a shared progress bar.
|
||||||
|
RegisterNetEvent(getScript()..":client:sharedProg:Cancel", function()
|
||||||
|
debugPrint("^6Bridge^7: ^2Receiving cancel progressBar^7")
|
||||||
|
stopProgressBar()
|
||||||
|
end)
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
if not isServer() then
|
||||||
|
local RopeCache = {
|
||||||
|
nextId = 1,
|
||||||
|
byId = {},
|
||||||
|
byHandle = {},
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadRopeTextures()
|
||||||
|
RopeLoadTextures()
|
||||||
|
local tries = 0
|
||||||
|
while not RopeAreTexturesLoaded() and tries < 200 do
|
||||||
|
Wait(0)
|
||||||
|
tries += 1
|
||||||
|
end
|
||||||
|
return RopeAreTexturesLoaded()
|
||||||
|
end
|
||||||
|
|
||||||
|
function maybeUnloadTextures()
|
||||||
|
local anyLeft = false
|
||||||
|
local all = GetAllRopes()
|
||||||
|
if all and type(all) == "table" then
|
||||||
|
for _, _ in ipairs(all) do anyLeft = true break end
|
||||||
|
end
|
||||||
|
if not anyLeft then
|
||||||
|
RopeUnloadTextures()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function registerRope(ropeHandle, startCoords, endCoords, opts)
|
||||||
|
if not ropeHandle or ropeHandle == 0 then return nil end
|
||||||
|
local id = RopeCache.nextId
|
||||||
|
RopeCache.nextId = id + 1
|
||||||
|
|
||||||
|
local rec = {
|
||||||
|
handle = ropeHandle,
|
||||||
|
startCoords = startCoords,
|
||||||
|
endCoords = endCoords,
|
||||||
|
type = opts.ropeType,
|
||||||
|
rate = opts.lengthChangeRate,
|
||||||
|
breakable = opts.breakable,
|
||||||
|
createdAt = GetGameTimer()
|
||||||
|
}
|
||||||
|
RopeCache.byId[id] = rec
|
||||||
|
RopeCache.byHandle[ropeHandle] = id
|
||||||
|
debugPrint("^5Debug^7: 2Registered Rope^7'^3"..id.."^7' - '^3"..ropeHandle.."^7'")
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
function resolve(id)
|
||||||
|
if type(id) ~= "number" then return nil, "rope id must be a number" end
|
||||||
|
local rec = RopeCache.byId[id]
|
||||||
|
if not rec then return nil, "rope id not found" end
|
||||||
|
if not DoesRopeExist(rec.handle) then
|
||||||
|
-- stale; clean mapping
|
||||||
|
RopeCache.byHandle[rec.handle] = nil
|
||||||
|
RopeCache.byId[id] = nil
|
||||||
|
return nil, "rope no longer exists"
|
||||||
|
end
|
||||||
|
return rec
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Create a rope pinned between two coordinates.
|
||||||
|
--- @param startCoords vector3|table start coord
|
||||||
|
--- @param endCoords vector3|table end coord
|
||||||
|
--- @param opts table|nil { ropeType=0..7, breakable=false, collision=true, lockFromFront=false, lengthChangeRate=1.0, timeMultiplier=1.0, slack=0.0, preset=nil }
|
||||||
|
--- @return integer ropeId
|
||||||
|
function ropeCreateLine(startCoords, endCoords, opts)
|
||||||
|
|
||||||
|
startCoords = type(startCoords) == "number" and GetEntityCoords(startCoords) or startCoords
|
||||||
|
endCoords = type(endCoords) == "number" and GetEntityCoords(endCoords) or endCoords
|
||||||
|
|
||||||
|
opts = opts or {}
|
||||||
|
|
||||||
|
local length = #(startCoords - endCoords)
|
||||||
|
local mid = (startCoords + endCoords) * 0.5
|
||||||
|
|
||||||
|
local ropeType = opts.ropeType or 1 -- 0..7 (see ropedata.xml types)
|
||||||
|
local initLength = length + (opts.slack or 0.0)
|
||||||
|
local maxLength = initLength -- allow droop equal to init
|
||||||
|
local minLength = 0.0
|
||||||
|
local lengthChangeRate = opts.lengthChangeRate or 1.0
|
||||||
|
local collisionOn = (opts.collision ~= false)
|
||||||
|
local lockFromFront = (opts.lockFromFront == true)
|
||||||
|
local timeMultiplier = opts.timeMultiplier or 1.0
|
||||||
|
local breakable = (opts.breakable == true)
|
||||||
|
|
||||||
|
local rope = AddRope(mid.x, mid.y, mid.z, 0.0, 0.0, 0.0, maxLength, ropeType, initLength, minLength, lengthChangeRate, false, collisionOn, lockFromFront, timeMultiplier, breakable, 0)
|
||||||
|
|
||||||
|
if not rope or rope == 0 or not DoesRopeExist(rope) then
|
||||||
|
debugPrint("^1AddRope failed")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Optional preset (e.g., "ropeFamily3") if provided.
|
||||||
|
if opts.preset then
|
||||||
|
-- Best-effort; if invalid it just won't change.
|
||||||
|
pcall(function() LoadRopeData(rope, tostring(opts.preset)) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
ActivatePhysics(rope)
|
||||||
|
|
||||||
|
-- Pin both ends to the given coords.
|
||||||
|
local count = GetRopeVertexCount(rope)
|
||||||
|
if count and count >= 2 then
|
||||||
|
PinRopeVertex(rope, 0, startCoords.x, startCoords.y, startCoords.z)
|
||||||
|
PinRopeVertex(rope, count - 1, endCoords.x, endCoords.y, endCoords.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
local id = registerRope(rope, startCoords, endCoords, {
|
||||||
|
ropeType = ropeType,
|
||||||
|
lengthChangeRate = lengthChangeRate,
|
||||||
|
breakable = breakable
|
||||||
|
})
|
||||||
|
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Delete and unregister a rope.
|
||||||
|
--- @param id integer
|
||||||
|
function ropeDelete(id)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
local rope = rec.handle
|
||||||
|
if DoesRopeExist(rope) then
|
||||||
|
DeleteRope(rope)
|
||||||
|
end
|
||||||
|
RopeCache.byHandle[rope] = nil
|
||||||
|
RopeCache.byId[id] = nil
|
||||||
|
debugPrint(("deleted rope id=%s"):format(id))
|
||||||
|
maybeUnloadTextures()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Force rope to a specific length (instant snap).
|
||||||
|
--- @param id integer
|
||||||
|
--- @param newLength number
|
||||||
|
function ropeSetLength(id, newLength)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
RopeForceLength(rec.handle, newLength)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Smoothly wind the rope (shorten over time).
|
||||||
|
--- @param id integer
|
||||||
|
function ropeStartWinding(id)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
StartRopeWinding(rec.handle)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Stop winding (if winding).
|
||||||
|
function ropeStopWinding(id)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
StopRopeWinding(rec.handle)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Smoothly unwind the rope from the front (lengthen over time).
|
||||||
|
function ropeStartUnwinding(id)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
StartRopeUnwindingFront(rec.handle)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Stop unwinding (if unwinding).
|
||||||
|
function ropeStopUnwinding(id)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
StopRopeUnwindingFront(rec.handle)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Change the wind speed (length change rate).
|
||||||
|
--- @param id integer
|
||||||
|
--- @param rate number (units per sec; try 0.5..5.0)
|
||||||
|
function ropeSetRate(id, rate)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
SetRopeLengthChangeRate(rec.handle, rate)
|
||||||
|
rec.rate = rate
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move end endpoint to a new coordinate (re-pins the vertex).
|
||||||
|
--- @param id integer
|
||||||
|
--- @param pos vector3|table
|
||||||
|
function ropeMoveEnd(id, pos)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
pos = type(pos) == "number" and GetEntityCoords(pos) or pos
|
||||||
|
local rope = rec.handle
|
||||||
|
local count = GetRopeVertexCount(rope)
|
||||||
|
if not count or count < 2 then return false, "invalid vertex count" end
|
||||||
|
PinRopeVertex(rope, count - 1, pos.x, pos.y, pos.z)
|
||||||
|
rec.endCoord = pos
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Move start endpoint to a new coordinate (re-pins the vertex).
|
||||||
|
--- @param id integer
|
||||||
|
--- @param pos vector3|table
|
||||||
|
function ropeMoveStart(id, pos)
|
||||||
|
local rec, err = resolve(id)
|
||||||
|
if not rec then return false, err end
|
||||||
|
pos = type(pos) == "number" and GetEntityCoords(pos) or pos
|
||||||
|
local rope = rec.handle
|
||||||
|
local count = GetRopeVertexCount(rope)
|
||||||
|
if not count or count < 2 then return false, "invalid vertex count" end
|
||||||
|
PinRopeVertex(rope, 0, pos.x, pos.y, pos.z)
|
||||||
|
rec.startCoord = pos
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Re-pin both endpoints in one call.
|
||||||
|
function ropeSetEnds(ropeId, startCoord, endCoord)
|
||||||
|
startCoord = type(startCoord) == "number" and GetEntityCoords(startCoord) or startCoord
|
||||||
|
endCoord = type(endCoord) == "number" and GetEntityCoords(endCoord) or endCoord
|
||||||
|
local rec, err = resolve(ropeId)
|
||||||
|
if not rec then return false, err end
|
||||||
|
local rope = rec.handle
|
||||||
|
local count = GetRopeVertexCount(rope)
|
||||||
|
if not count or count < 2 then
|
||||||
|
return false, "invalid vertex count"
|
||||||
|
end
|
||||||
|
PinRopeVertex(rope, 0, startCoord.x, startCoord.y, startCoord.z)
|
||||||
|
PinRopeVertex(rope, count - 1, endCoord.x, endCoord.y, endCoord.z)
|
||||||
|
rec.startCoord = startCoord
|
||||||
|
rec.endCoord = endCoord
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function ropeExists(id)
|
||||||
|
local rec = RopeCache.byId[id]
|
||||||
|
return rec and DoesRopeExist(rec.handle) or false
|
||||||
|
end
|
||||||
|
|
||||||
|
onResourceStart(function()
|
||||||
|
loadRopeTextures()
|
||||||
|
end)
|
||||||
|
|
||||||
|
onResourceStop(function()
|
||||||
|
for id, rec in pairs(RopeCache.byId) do
|
||||||
|
if rec.handle and DoesRopeExist(rec.handle) then
|
||||||
|
DeleteRope(rec.handle)
|
||||||
|
end
|
||||||
|
RopeCache.byHandle[rec.handle] = nil
|
||||||
|
RopeCache.byId[id] = nil
|
||||||
|
end
|
||||||
|
maybeUnloadTextures()
|
||||||
|
end)
|
||||||
|
end
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
--[[
|
||||||
|
Player Metadata Utilities Module
|
||||||
|
----------------------------------
|
||||||
|
This module provides functions for retrieving and setting metadata for players
|
||||||
|
across different frameworks (QB, ESX, OXCore). It also registers server callbacks
|
||||||
|
for getting and setting metadata.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local metaDataFunc = {
|
||||||
|
{ framework = QBXExport,
|
||||||
|
GetPlayer =
|
||||||
|
function(src)
|
||||||
|
if src then
|
||||||
|
return exports[QBXExport]:GetPlayer(src)
|
||||||
|
end
|
||||||
|
return exports[QBXExport]:GetPlayerData()
|
||||||
|
end,
|
||||||
|
GetPlayerMetadata =
|
||||||
|
function(Player, dataToCheck)
|
||||||
|
return Player.PlayerData.metadata[dataToCheck]
|
||||||
|
end,
|
||||||
|
SetPlayerMetadata =
|
||||||
|
function(Player, key, value)
|
||||||
|
return Player.Functions.SetMetaData(key, value)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = QBExport,
|
||||||
|
GetPlayer =
|
||||||
|
function(src)
|
||||||
|
if src then
|
||||||
|
return exports[QBExport]:GetCoreObject().Functions.GetPlayer(src)
|
||||||
|
end
|
||||||
|
local info = nil
|
||||||
|
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
|
||||||
|
return info
|
||||||
|
end,
|
||||||
|
GetPlayerMetadata =
|
||||||
|
function(Player, dataToCheck)
|
||||||
|
return Player.PlayerData.metadata[dataToCheck]
|
||||||
|
end,
|
||||||
|
SetPlayerMetadata =
|
||||||
|
function(Player, key, value)
|
||||||
|
return Player.Functions.SetMetaData(key, value)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = ESXExport,
|
||||||
|
GetPlayer =
|
||||||
|
function(src)
|
||||||
|
if src then
|
||||||
|
return ESX.GetPlayerFromId(src)
|
||||||
|
end
|
||||||
|
return ESX.GetPlayerData()
|
||||||
|
end,
|
||||||
|
GetPlayerMetadata =
|
||||||
|
function(Player, dataToCheck)
|
||||||
|
return Player.getMeta(dataToCheck)
|
||||||
|
end,
|
||||||
|
SetPlayerMetadata =
|
||||||
|
function(Player, key, value)
|
||||||
|
return Player.set(key, value)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = OXCoreExport,
|
||||||
|
GetPlayer =
|
||||||
|
function(src)
|
||||||
|
if src then
|
||||||
|
return exports[OXCoreExport]:GetPlayer(src)
|
||||||
|
end
|
||||||
|
return {}
|
||||||
|
end,
|
||||||
|
GetPlayerMetadata =
|
||||||
|
function(Player, dataToCheck)
|
||||||
|
return Player.get(dataToCheck)
|
||||||
|
end,
|
||||||
|
SetPlayerMetadata =
|
||||||
|
function(Player, key, value)
|
||||||
|
return Player.set(key, value)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = RSGExport,
|
||||||
|
GetPlayer =
|
||||||
|
function(src)
|
||||||
|
if src then
|
||||||
|
return exports[RSGExport]:GetCoreObject().Functions.GetPlayer(src)
|
||||||
|
end
|
||||||
|
local info = nil
|
||||||
|
Core.Functions.GetPlayerData(function(PlayerData) info = PlayerData end)
|
||||||
|
return info
|
||||||
|
end,
|
||||||
|
GetPlayerMetadata = function(Player, dataToCheck)
|
||||||
|
return Player.PlayerData.metadata[dataToCheck]
|
||||||
|
end,
|
||||||
|
SetPlayerMetadata = function(Player, key, value)
|
||||||
|
return Player.Functions.SetMetaData(key, value)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Player Retrieval
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Retrieves the player object using the active core export.
|
||||||
|
---
|
||||||
|
--- @param source number The server ID of the player.
|
||||||
|
--- @return table|nil table The player object, or nil if no supported core is detected.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local player = GetPlayer(playerId)
|
||||||
|
--- ```
|
||||||
|
function GetPlayer(source)
|
||||||
|
for i = 1, #metaDataFunc do
|
||||||
|
local framework = metaDataFunc[i]
|
||||||
|
if isStarted(framework.framework) then
|
||||||
|
return framework.GetPlayer(source)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Metadata Retrieval
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Retrieves metadata from a player object.
|
||||||
|
---
|
||||||
|
--- If called client-side (player is nil), it triggers a server callback to retrieve metadata.
|
||||||
|
---
|
||||||
|
--- @param player table|nil The player object; if nil, metadata is retrieved via a server callback.
|
||||||
|
--- @param key string The metadata key to retrieve.
|
||||||
|
--- @return any The value of the requested metadata, or nil if not found.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local myMeta = getPlayerMetadata(player, "myKey")
|
||||||
|
--- ```
|
||||||
|
function getPlayerMetadata(player, key)
|
||||||
|
-- Assume this is client side and callback to server to get the data
|
||||||
|
if not player then
|
||||||
|
debugPrint("^6Bridge^7: ^3GetMetadata^7() calling server: "..key)
|
||||||
|
return triggerCallback(getScript()..":server:GetMetadata", key)
|
||||||
|
else
|
||||||
|
-- else grab server metadata about the player
|
||||||
|
for i = 1, #metaDataFunc do
|
||||||
|
local framework = metaDataFunc[i]
|
||||||
|
if isStarted(framework.framework) then
|
||||||
|
|
||||||
|
debugPrint("^6Bridge^7: ^3GetMetadata^7() ^3"..framework.framework.."^7", key)
|
||||||
|
return framework.GetPlayerMetadata(player, key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Register a server callback for retrieving metadata.
|
||||||
|
createCallback(getScript()..":server:GetMetadata", function(source, key)
|
||||||
|
debugPrint("^6Bridge^7: ^3GetMetadata Callback^7 from source: "..tostring(source)..", key: "..tostring(key))
|
||||||
|
local player = GetPlayer(source)
|
||||||
|
if not player then
|
||||||
|
print("Error getting metadata: player not found for source "..tostring(source))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(key) == "table" then
|
||||||
|
local Metadata = {}
|
||||||
|
for _, k in ipairs(key) do
|
||||||
|
Metadata[k] = getPlayerMetadata(player, k)
|
||||||
|
end
|
||||||
|
return Metadata
|
||||||
|
elseif type(key) == "string" then
|
||||||
|
return getPlayerMetadata(player, key)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Metadata Setting
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Sets metadata on a player object.
|
||||||
|
---
|
||||||
|
--- The function updates the player's metadata using the active core export.
|
||||||
|
---
|
||||||
|
--- @param player table The player object.
|
||||||
|
--- @param key string The metadata key to set.
|
||||||
|
--- @param value any The new value for the metadata key.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- setPlayerMetadata(player, "myKey", "newValue")
|
||||||
|
--- ```
|
||||||
|
function setPlayerMetadata(player, key, value)
|
||||||
|
debugPrint("^6Bridge^7: ^3SetMetadata^7() setting metadata^7...")
|
||||||
|
for i = 1, #metaDataFunc do
|
||||||
|
local framework = metaDataFunc[i]
|
||||||
|
if isStarted(framework.framework) then
|
||||||
|
debugPrint("^6Bridge^7: ^3SetMetadata^7() ^3"..framework.framework.."^7", key, value)
|
||||||
|
return framework.GetPlayerMetadata(player, key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
debugPrint("^6Bridge^7: ^1Error setting metadata^7, ^1framework not supported^7?")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Register a server callback for setting metadata.
|
||||||
|
createCallback(getScript()..":server:setPlayerMetadata", function(source, key, value)
|
||||||
|
local src = source
|
||||||
|
debugPrint("SetMetadata callback triggered for source:", src, "key:", key, "value:", value)
|
||||||
|
local player = GetPlayer(src)
|
||||||
|
setPlayerMetadata(player, key, value)
|
||||||
|
print("Metadata set successfully.", key)
|
||||||
|
return true
|
||||||
|
end)
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
--[[
|
||||||
|
Notifications Module
|
||||||
|
----------------------
|
||||||
|
This module provides a unified interface for displaying notifications using various
|
||||||
|
notification systems. The active system is determined by the Config.System.Notify setting.
|
||||||
|
|
||||||
|
Supported systems include:
|
||||||
|
• okok
|
||||||
|
• qb
|
||||||
|
• ox
|
||||||
|
• red (default)
|
||||||
|
• gta (default)
|
||||||
|
• lation
|
||||||
|
• esx
|
||||||
|
]]
|
||||||
|
|
||||||
|
local notifyFunc = {
|
||||||
|
okok = {
|
||||||
|
client =
|
||||||
|
function(title, message, type)
|
||||||
|
TriggerEvent('okokNotify:Alert', title, message, 6000, type)
|
||||||
|
end,
|
||||||
|
server =
|
||||||
|
function(title, message, type, src)
|
||||||
|
TriggerClientEvent('okokNotify:Alert', src, title, message, 6000, type)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
qb = {
|
||||||
|
client =
|
||||||
|
function(title, message, type)
|
||||||
|
TriggerEvent("QBCore:Notify", message, type)
|
||||||
|
end,
|
||||||
|
server =
|
||||||
|
function(title, message, type, src)
|
||||||
|
TriggerClientEvent("QBCore:Notify", src, message, type)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
ox = {
|
||||||
|
client =
|
||||||
|
function(title, message, type)
|
||||||
|
exports.ox_lib:notify({ title = title, description = message, type = type or "success" })
|
||||||
|
end,
|
||||||
|
server =
|
||||||
|
function(title, message, type, src)
|
||||||
|
TriggerClientEvent('ox_lib:notify', src, { title = title, description = message, type = type or "success" })
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
esx = {
|
||||||
|
client =
|
||||||
|
function(title, message, type)
|
||||||
|
exports["esx_notify"]:Notify(type, 4000, message)
|
||||||
|
end,
|
||||||
|
server =
|
||||||
|
function(title, message, type, src)
|
||||||
|
TriggerClientEvent(getScript()..":DisplayESXNotify", src, type, message)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
lation = {
|
||||||
|
client =
|
||||||
|
function(title, message, type)
|
||||||
|
exports.lation_ui:notify({ title = title, message = message, type = type or "success", })
|
||||||
|
end,
|
||||||
|
server =
|
||||||
|
function(title, message, type, src)
|
||||||
|
TriggerClientEvent("lation_ui:notify", src, { title = title, message = message, type = type or "success", })
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
gta = {
|
||||||
|
client =
|
||||||
|
function(title, message, type)
|
||||||
|
exports.jim_bridge:Notify(title, message, type)
|
||||||
|
end,
|
||||||
|
server =
|
||||||
|
function(title, message, type, src)
|
||||||
|
TriggerClientEvent("jim-bridge:Notify", src, title, message, type)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
red = {
|
||||||
|
client =
|
||||||
|
function(title, message, type)
|
||||||
|
TriggerEvent("jim-redui:Notify", title, message, type)
|
||||||
|
end,
|
||||||
|
server =
|
||||||
|
function(title, message, type, src)
|
||||||
|
TriggerClientEvent("jim-redui:Notify", src, title, message, type)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
--- Displays notifications to the player using the configured notification system.
|
||||||
|
---
|
||||||
|
--- Supports multiple notification systems based on Config.System.Notify. Can be triggered from both
|
||||||
|
--- client and server contexts.
|
||||||
|
---
|
||||||
|
--- @param title string|nil The notification title (optional for some systems).
|
||||||
|
--- @param message string The main message content.
|
||||||
|
--- @param type string The notification type ("success", "error", "info").
|
||||||
|
--- @param src number|nil Optional server ID; if provided, the notification is sent to that player.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- -- Client-side usage without specifying a player (shows to the current player)
|
||||||
|
--- triggerNotify("Success", "You have completed the task!", "success")
|
||||||
|
---
|
||||||
|
--- -- Server-side usage specifying a player by their server ID
|
||||||
|
--- triggerNotify("Alert", "You have been warned for misconduct.", "error", playerId)
|
||||||
|
--- ```
|
||||||
|
function triggerNotify(title, message, type, src)
|
||||||
|
if not Config or not Config.System or not Config.System.Notify then
|
||||||
|
print("Notify triggered but not set up")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if src then
|
||||||
|
if Config.System.EventDebug then print(tostring(title).." - "..tostring(message).." - "..tostring(type).." - "..tostring(src)) end
|
||||||
|
notifyFunc[Config.System.Notify].server(title, message, type, src)
|
||||||
|
else
|
||||||
|
if Config.System.EventDebug then print(tostring(title).." - "..tostring(message).." - "..tostring(type).." - "..tostring(src)) end
|
||||||
|
notifyFunc[Config.System.Notify].client(title, message, type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- ESX Notifications
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Registers a server-side event to display ESX notifications to clients.
|
||||||
|
---
|
||||||
|
--- Listens for DisplayESXNotify events and triggers the ESX notification on the client.
|
||||||
|
---
|
||||||
|
--- @param type string The notification type.
|
||||||
|
--- @param text string The notification message.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- TriggerClientEvent(getScript()..":DisplayESXNotify", playerId, "success", "New achievement unlocked!")
|
||||||
|
--- ```
|
||||||
|
RegisterNetEvent(getScript()..":DisplayESXNotify", function(type, text)
|
||||||
|
exports["esx_notify"]:Notify(type, 4000, text)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- GTA-style Notifications
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Displays GTA-style text notifications using native GTA functions.
|
||||||
|
---
|
||||||
|
--- Selects an appropriate icon based on the current script (if applicable) and renders the notification.
|
||||||
|
---
|
||||||
|
--- @param title string The notification title/identifier (used to select an icon).
|
||||||
|
--- @param text string The notification message.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- TriggerEvent(getScript()..":DisplayGTANotify", "taxiname", "Taxi service has arrived.")
|
||||||
|
--- ```
|
||||||
|
--RegisterNetEvent(getScript()..":DisplayGTANotify", function(title, text)
|
||||||
|
-- local iconTable = {}
|
||||||
|
-- if getScript() == "jim-npcservice" then
|
||||||
|
-- iconTable = {
|
||||||
|
-- [Loc[Config.Lan].notify["taxiname"]] = "CHAR_TAXI",
|
||||||
|
-- [Loc[Config.Lan].notify["limoname"]] = "CHAR_CASINO",
|
||||||
|
-- [Loc[Config.Lan].notify["ambiname"]] = "CHAR_CALL911",
|
||||||
|
-- [Loc[Config.Lan].notify["pilotname"]] = "CHAR_DEFAULT",
|
||||||
|
-- [Loc[Config.Lan].notify["planename"]] = "CHAR_BOATSITE2",
|
||||||
|
-- [Loc[Config.Lan].notify["heliname"]] = "CHAR_BOATSITE2",
|
||||||
|
-- }
|
||||||
|
-- end
|
||||||
|
--
|
||||||
|
-- BeginTextCommandThefeedPost("STRING")
|
||||||
|
-- AddTextComponentSubstringKeyboardDisplay(text)
|
||||||
|
-- EndTextCommandThefeedPostMessagetext(
|
||||||
|
-- iconTable[title] or "CHAR_DEFAULT",
|
||||||
|
-- iconTable[title] or "CHAR_DEFAULT",
|
||||||
|
-- true, 1, title, nil, text
|
||||||
|
-- )
|
||||||
|
-- EndTextCommandThefeedPostTicker(true, false)
|
||||||
|
--end)
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
--[[
|
||||||
|
Phone Mails Module
|
||||||
|
------------------
|
||||||
|
This module handles sending phone mails using different phone systems.
|
||||||
|
Supported systems include:
|
||||||
|
- gksphone
|
||||||
|
- yflip-phone
|
||||||
|
- roadphone
|
||||||
|
- lb-phone
|
||||||
|
- qb-phone
|
||||||
|
- jpr-phonesystem
|
||||||
|
]]
|
||||||
|
|
||||||
|
local phoneFunc = {
|
||||||
|
{ phone = "gksphone",
|
||||||
|
sendMail = function(mailData)
|
||||||
|
exports["gksphone"]:SendNewMail(mailData)
|
||||||
|
end,
|
||||||
|
sendInvoice =
|
||||||
|
function(mailData)
|
||||||
|
-- Defensive check for required fields
|
||||||
|
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "label" }
|
||||||
|
for _, field in ipairs(required) do
|
||||||
|
if mailData[field] == nil then
|
||||||
|
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
MySQL.Async.execute(
|
||||||
|
'INSERT INTO gksphone_invoices (citizenid, amount, society, sender, sendercitizenid, label) VALUES (@citizenid, @amount, @society, @sender, @sendercitizenid, @label)',
|
||||||
|
{
|
||||||
|
['@citizenid'] = mailData.billedCitizenid,
|
||||||
|
['@amount'] = mailData.amount,
|
||||||
|
['@society'] = mailData.job,
|
||||||
|
['@sender'] = mailData.name,
|
||||||
|
['@sendercitizenid'] = mailData.billerCitizenid,
|
||||||
|
['@label'] = mailData.label
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ phone = "yflip-phone",
|
||||||
|
sendMail =
|
||||||
|
function(mailData)
|
||||||
|
TriggerServerEvent(getScript()..":yflip:SendMail", mailData)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ phone = "roadphone",
|
||||||
|
sendMail =
|
||||||
|
function(mailData)
|
||||||
|
-- Convert HTML line breaks to newlines for roadphone.
|
||||||
|
mailData.message = mailData.message:gsub("%<br>", "\n")
|
||||||
|
exports["roadphone"]:sendMail(mailData)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ phone = "lb-phone",
|
||||||
|
sendMail =
|
||||||
|
function(mailData)
|
||||||
|
-- Convert HTML line breaks to newlines for lb-phone.
|
||||||
|
mailData.message = mailData.message:gsub("%<br>", "\n")
|
||||||
|
TriggerServerEvent(getScript()..":lbphone:SendMail", mailData)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ phone = "qb-phone",
|
||||||
|
sendMail =
|
||||||
|
function(mailData)
|
||||||
|
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
|
||||||
|
end,
|
||||||
|
sendInvoice =
|
||||||
|
function(mailData)
|
||||||
|
-- Defensive check for required fields
|
||||||
|
local required = { "billedCitizenid", "amount", "job", "name", "billerCitizenid", "src" }
|
||||||
|
for _, field in ipairs(required) do
|
||||||
|
if mailData[field] == nil then
|
||||||
|
print("^1[jim-payments] ERROR:^0 Missing field '" .. field .. "' in mailData")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Safe insert with all parameters present
|
||||||
|
MySQL.Async.insert(
|
||||||
|
'INSERT INTO phone_invoices (citizenid, amount, society, sender, sendercitizenid) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
{
|
||||||
|
mailData.billedCitizenid,
|
||||||
|
mailData.amount,
|
||||||
|
mailData.job,
|
||||||
|
mailData.name,
|
||||||
|
mailData.billerCitizenid
|
||||||
|
},
|
||||||
|
function(id)
|
||||||
|
if id then
|
||||||
|
TriggerClientEvent('qb-phone:client:AcceptorDenyInvoice', mailData.src, id, mailData.name, mailData.job, mailData.billerCitizenid, mailData.amount, GetInvokingResource())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
TriggerClientEvent('qb-phone:RefreshPhone', mailData.src)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ phone = "npwd_qbx_mail",
|
||||||
|
sendMail =
|
||||||
|
function(mailData)
|
||||||
|
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ phone = "jpr-phonesystem",
|
||||||
|
sendMail =
|
||||||
|
function(mailData)
|
||||||
|
TriggerServerEvent(getScript()..":jpr:SendMail", mailData)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ phone = "ef-phone",
|
||||||
|
sendMail =
|
||||||
|
function(mailData)
|
||||||
|
TriggerServerEvent('qb-phone:server:sendNewMail', mailData)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
--- Sends a phone mail using the detected phone system.
|
||||||
|
--- The function iterates through a prioritized list of supported phone systems.
|
||||||
|
--- Once an active system is found (via `isStarted`), the corresponding mail function is executed.
|
||||||
|
---
|
||||||
|
--- @param data table A table containing the mail data.
|
||||||
|
--- - subject (string): The email subject.
|
||||||
|
--- - sender (string): The sender identifier.
|
||||||
|
--- - message (string): The email body content.
|
||||||
|
--- - actions (table|nil): Optional action buttons for the email.
|
||||||
|
--- @usage
|
||||||
|
--- sendPhoneMail({
|
||||||
|
--- subject = "Welcome!",
|
||||||
|
--- sender = "Admin",
|
||||||
|
--- message = "Thank you for joining our server.",
|
||||||
|
--- actions = {
|
||||||
|
--- { label = "Reply", action = replyFunction }
|
||||||
|
--- }
|
||||||
|
--- })
|
||||||
|
function sendPhoneMail(mailData)
|
||||||
|
-- Check each phone system in order and use the first active one.
|
||||||
|
for i = 1, #phoneFunc do
|
||||||
|
local script = phoneFunc[i]
|
||||||
|
if isStarted(script.phone) and script.sendMail then
|
||||||
|
debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player")
|
||||||
|
script.sendMail(mailData)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function sendPhoneInvoice(data)
|
||||||
|
if not data.src then return end
|
||||||
|
|
||||||
|
-- Check each phone system in order and use the first active one.
|
||||||
|
|
||||||
|
for i = 1, #phoneFunc do
|
||||||
|
local script = phoneFunc[i]
|
||||||
|
if isStarted(script.phone) and script.sendInvoice then
|
||||||
|
debugPrint("^6Bridge^7[^3"..script.phone.."^7]: ^2Sending mail to player^7", data.src)
|
||||||
|
script.sendInvoice(data)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
print("^6Bridge^7: ^1ERROR ^2Sending mail to player ^7- ^2No supported phone found")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Phone System Event Handlers
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Handles sending mail for lb-phone.
|
||||||
|
--- Listens for the `lbphone:SendMail` event and sends an email using lb-phone's API.
|
||||||
|
---
|
||||||
|
--- @event lbphone:SendMail
|
||||||
|
--- @param data table The mail data.
|
||||||
|
--- - subject (string): The email subject.
|
||||||
|
--- - message (string): The email content.
|
||||||
|
--- - buttons (table|nil): Optional action buttons (mapped from data.actions if present).
|
||||||
|
RegisterNetEvent(getScript()..":lbphone:SendMail", function(data)
|
||||||
|
local src = source
|
||||||
|
local phoneNumber = exports["lb-phone"]:GetEquippedPhoneNumber(src)
|
||||||
|
local emailAddress = exports["lb-phone"]:GetEmailAddress(phoneNumber)
|
||||||
|
-- Map actions to buttons if provided.
|
||||||
|
data.buttons = data.actions or data.buttons
|
||||||
|
|
||||||
|
exports["lb-phone"]:SendMail({
|
||||||
|
to = emailAddress,
|
||||||
|
subject = data.subject,
|
||||||
|
message = data.message,
|
||||||
|
actions = data.buttons,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Handles sending mail for yflip-phone.
|
||||||
|
--- Listens for the `yflip:SendMail` event and sends an email using yflip-phone's API.
|
||||||
|
---
|
||||||
|
--- @event yflip:SendMail
|
||||||
|
--- @param data table The mail data.
|
||||||
|
--- - subject (string): The email subject.
|
||||||
|
--- - sender (string): The sender identifier.
|
||||||
|
--- - message (string): The email content.
|
||||||
|
--- - buttons (table|nil): Optional action buttons.
|
||||||
|
RegisterNetEvent(getScript()..":yflip:SendMail", function(data)
|
||||||
|
local src = source
|
||||||
|
exports["yflip-phone"]:SendMail({
|
||||||
|
title = data.subject,
|
||||||
|
sender = data.sender,
|
||||||
|
senderDisplayName = data.sender,
|
||||||
|
content = data.message,
|
||||||
|
actions = data.buttons,
|
||||||
|
}, 'source', src)
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Handles sending mail for jpr-phonesystem.
|
||||||
|
--- Listens for the `jpr:SendMail` event and sends an email using jpr-phonesystem's API.
|
||||||
|
---
|
||||||
|
--- @event jpr:SendMail
|
||||||
|
--- @param data table The mail data.
|
||||||
|
--- - subject (string): The email subject.
|
||||||
|
--- - sender (string): The sender identifier.
|
||||||
|
--- - message (string): The email content.
|
||||||
|
--- - buttons (table|nil): Optional action buttons.
|
||||||
|
RegisterNetEvent(getScript()..":jpr:SendMail", function(data)
|
||||||
|
local src = source
|
||||||
|
local Player = Core.Functions.GetPlayer(src)
|
||||||
|
TriggerEvent('jpr-phonesystem:server:sendEmail', {
|
||||||
|
Assunto = data.subject, -- Email subject
|
||||||
|
Conteudo = data.message, -- Email content
|
||||||
|
Enviado = data.sender, -- Sender information
|
||||||
|
Destinatario = Player.PlayerData.citizenid, -- Recipient identifier
|
||||||
|
Event = {}, -- Optional event details
|
||||||
|
})
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,185 @@
|
|||||||
|
--[[
|
||||||
|
PolyZone Management Module
|
||||||
|
----------------------------
|
||||||
|
This module automatically detects the available polyzone library (ox_lib or PolyZone)
|
||||||
|
and creates polygonal and circular zones accordingly. It also provides a function to remove
|
||||||
|
previously created zones.
|
||||||
|
|
||||||
|
Functions:
|
||||||
|
• createPoly(data) - Creates a polygonal zone.
|
||||||
|
• createCirclePoly(data) - Creates a circular zone.
|
||||||
|
• removePolyZone(Location) - Removes a created zone.
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
local polyCreation = {
|
||||||
|
{
|
||||||
|
polyZoneScript = OXLibExport,
|
||||||
|
createPoly = function(data)
|
||||||
|
data.minZ = data.minZ or -20.0
|
||||||
|
data.maxZ = data.maxZ or 1000.0
|
||||||
|
data.thickness = ((data.maxZ / 2) - (data.minZ / 2)) * 2
|
||||||
|
|
||||||
|
local mid = data.maxZ - ((data.maxZ / 2) - (data.minZ / 2))
|
||||||
|
for i = 1, #data.points do
|
||||||
|
data.points[i] = vec3(data.points[i].x, data.points[i].y, mid)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib.zones.poly(data)
|
||||||
|
end,
|
||||||
|
createCircle = function(data)
|
||||||
|
return lib.zones.sphere(data)
|
||||||
|
end,
|
||||||
|
removeZone = function(Location)
|
||||||
|
Location:remove()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
polyZoneScript = "PolyZone",
|
||||||
|
createPoly = function(data)
|
||||||
|
local zone = PolyZone:Create(data.points, {
|
||||||
|
name = data.name,
|
||||||
|
minZ = data.minZ or nil,
|
||||||
|
maxZ = data.maxZ or nil,
|
||||||
|
debugPoly = data.debug
|
||||||
|
})
|
||||||
|
zone:onPlayerInOut(function(isPointInside)
|
||||||
|
if isPointInside then
|
||||||
|
data.onEnter()
|
||||||
|
else
|
||||||
|
data.onExit()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return zone
|
||||||
|
end,
|
||||||
|
createCircle = function(data)
|
||||||
|
local zone = CircleZone:Create(data.coords, data.radius, {
|
||||||
|
name = data.name,
|
||||||
|
debugPoly = data.debug
|
||||||
|
})
|
||||||
|
zone:onPlayerInOut(function(isPointInside)
|
||||||
|
if isPointInside then
|
||||||
|
data.onEnter()
|
||||||
|
else
|
||||||
|
data.onExit()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
return zone
|
||||||
|
end,
|
||||||
|
removeZone = function(Location)
|
||||||
|
Location:destroy()
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Polygonal Zone Creation
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Creates a polygonal zone using the detected polyzone library (ox_lib or PolyZone).
|
||||||
|
---
|
||||||
|
--- Automatically checks which polyzone script is active. When using ox_lib, it converts the provided
|
||||||
|
--- 2D points to 3D (setting a constant z value) and sets a thickness value. For PolyZone, it creates the zone
|
||||||
|
--- and attaches onEnter and onExit callbacks.
|
||||||
|
---
|
||||||
|
--- @param data table Zone configuration table with the following keys:
|
||||||
|
--- - name (string): The zone's identifier.
|
||||||
|
--- - debug (boolean): Whether debug mode is enabled.
|
||||||
|
--- - points (table): A list of vec2 points defining the polygon.
|
||||||
|
--- - onEnter (function): Callback when a player enters the zone.
|
||||||
|
--- - onExit (function): Callback when a player exits the zone.
|
||||||
|
---
|
||||||
|
--- @return table|nil table Returns the created zone object or nil if creation failed.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
---```lua
|
||||||
|
---createPoly({
|
||||||
|
--- name = 'testZone',
|
||||||
|
--- debug = true,
|
||||||
|
--- points = { vec2(100.0, 100.0), vec2(200.0, 100.0), vec2(200.0, 200.0), vec2(100.0, 200.0) },
|
||||||
|
--- onEnter = function() print("Entered Test Zone") end,
|
||||||
|
--- onExit = function() print("Exited Test Zone") end,
|
||||||
|
---})
|
||||||
|
---```
|
||||||
|
function createPoly(data)
|
||||||
|
for i = 1, #polyCreation do
|
||||||
|
local script = polyCreation[i]
|
||||||
|
if isStarted(script.polyZoneScript) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new poly with ^7'^4"..script.polyZoneScript.."^7': "..data.name)
|
||||||
|
return script.createPoly(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("^4ERROR^7: ^2No PolyZone creation script detected ^7")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Circular Zone Creation
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Creates a circular zone using the detected polyzone library (ox_lib or PolyZone).
|
||||||
|
---
|
||||||
|
--- When using ox_lib, it creates a sphere zone. For PolyZone, it creates a CircleZone and attaches
|
||||||
|
--- onEnter and onExit callbacks.
|
||||||
|
---
|
||||||
|
--- @param data table Zone configuration with the following keys:
|
||||||
|
--- - name (string): The zone's identifier.
|
||||||
|
--- - coords (vector3): The center of the circle.
|
||||||
|
--- - radius (number): The radius of the circle.
|
||||||
|
--- - onEnter (function): Callback when a player enters the zone.
|
||||||
|
--- - onExit (function): Callback when a player exits the zone.
|
||||||
|
---
|
||||||
|
--- @return table|nil table Returns the created circular zone object or nil if creation failed.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- createCirclePoly({
|
||||||
|
--- name = 'circleZone',
|
||||||
|
--- coords = vector3(150.0, 150.0, 20.0),
|
||||||
|
--- radius = 50.0,
|
||||||
|
--- onEnter = function() print("Entered Circle Zone") end,
|
||||||
|
--- onExit = function() print("Exited Circle Zone") end,
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
function createCirclePoly(data)
|
||||||
|
for i = 1, #polyCreation do
|
||||||
|
local script = polyCreation[i]
|
||||||
|
if isStarted(script.polyZoneScript) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Cricle^2 poly with ^7"..script.polyZoneScript.." "..data.name)
|
||||||
|
debugPrint("^6Bridge^7: ^2Zone Stats - Coords: "..formatCoord(data.coords).." Radius: "..data.radius)
|
||||||
|
return script.createCircle(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("^4ERROR^7: ^2No PolyZone creation script detected ^7")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- PolyZone Removal Function
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Removes a previously created polyzone.
|
||||||
|
---
|
||||||
|
--- Detects the active polyzone library and calls the appropriate removal method.
|
||||||
|
---
|
||||||
|
--- @param Location table The zone object to be removed.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local zone = createPoly({...})
|
||||||
|
---
|
||||||
|
--- removePolyZone(zone)
|
||||||
|
--- ```
|
||||||
|
function removePolyZone(Location)
|
||||||
|
for i = 1, #polyCreation do
|
||||||
|
local script = polyCreation[i]
|
||||||
|
if isStarted(script.polyZoneScript) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Removing ^3"..script.polyZoneScript.." ^2Zone^7")
|
||||||
|
script.removeZone(Location)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
local cacheOrigScale = {}
|
||||||
|
local initialOffset = {}
|
||||||
|
|
||||||
|
--- Sets the scale of an entity.
|
||||||
|
---
|
||||||
|
--- This function scales an entity by adjusting its forward, right, and up vectors.
|
||||||
|
--- It also applies an initial offset to maintain the entity's position relative to the ground.
|
||||||
|
---
|
||||||
|
---@param entity number The entity ID to scale.
|
||||||
|
---@param scale number The scale factor to apply to the entity.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- -- Scale an entity to twice its original size
|
||||||
|
--- SetEntityScale(entityId, 2.0)
|
||||||
|
--- ```
|
||||||
|
function SetEntityScale(entity, scale)
|
||||||
|
local forward, right, up = GetEntityMatrix(entity)
|
||||||
|
if not cacheOrigScale[entity] then
|
||||||
|
cacheOrigScale[entity] = {
|
||||||
|
forward = forward,
|
||||||
|
right = right,
|
||||||
|
up = up
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local minDim, maxDim = GetModelDimensions(GetEntityModel(entity))
|
||||||
|
local originalHeight = maxDim.z - minDim.z
|
||||||
|
local newHeight = originalHeight * scale
|
||||||
|
initialOffset[entity] = (newHeight - originalHeight) / 3
|
||||||
|
|
||||||
|
local forwardTemp = cacheOrigScale[entity].forward * scale
|
||||||
|
local rightTemp = cacheOrigScale[entity].right * scale
|
||||||
|
local upTemp = cacheOrigScale[entity].up * scale
|
||||||
|
|
||||||
|
-- Apply the initial offset to the current position
|
||||||
|
local currentPosition = GetEntityCoords(entity)
|
||||||
|
local newPosition = vector3(currentPosition.x, currentPosition.y, currentPosition.z + initialOffset[entity])
|
||||||
|
|
||||||
|
SetEntityMatrix(entity, forwardTemp, rightTemp, upTemp, currentPosition)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Resets the scale of an entity to its original values.
|
||||||
|
---
|
||||||
|
--- This function restores an entity's original forward, right, and up vectors,
|
||||||
|
--- effectively undoing any scaling applied by `SetEntityScale`.
|
||||||
|
---
|
||||||
|
---@param entity number The entity ID to reset.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- -- Reset the scale of an entity
|
||||||
|
--- resetScale(entityId)
|
||||||
|
--- ```
|
||||||
|
function resetScale(entity)
|
||||||
|
if cacheOrigScale[entity] then
|
||||||
|
SetEntityMatrix(entity, cacheOrigScale[entity].forward, cacheOrigScale[entity].right, cacheOrigScale[entity].up, GetEntityCoords(entity))
|
||||||
|
cacheOrigScale[entity] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
CreateThread(function()
|
||||||
|
-- Example usage:
|
||||||
|
-- local prop = makeProp({prop = "v_res_r_figcat", coords = vec4(-1025.88, -1417.58, 5.43, 76.30)}, false, false)
|
||||||
|
-- local ped = makePed(`a_c_cat_01`, vec4(-1022.42, -1429.97, 13.79, 68.36), true, false, nil)
|
||||||
|
-- SetEntityCollision(prop, false, true)
|
||||||
|
|
||||||
|
SetEntityScale(prop, 12)
|
||||||
|
--[[CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(1000)
|
||||||
|
resetScale(prop)
|
||||||
|
Wait(1000)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
]]
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
--[[
|
||||||
|
BigMessage Module
|
||||||
|
-----------------
|
||||||
|
This module provides a flexible way to display large, attention-grabbing messages
|
||||||
|
on screen using a Scaleform movie ("MP_BIG_MESSAGE_FREEMODE"). It supports multiple
|
||||||
|
message types (mission passed, colored shard, old-style, simple shard, rank-up, weapon purchased,
|
||||||
|
and large multiplayer messages), including customizable transitions and durations.
|
||||||
|
]]
|
||||||
|
|
||||||
|
BigMessage = {}
|
||||||
|
BigMessage.__index = BigMessage
|
||||||
|
|
||||||
|
--- Creates a new BigMessage instance.
|
||||||
|
--- @return table table A new BigMessage object.
|
||||||
|
function BigMessage:new()
|
||||||
|
local self = setmetatable({}, BigMessage)
|
||||||
|
self.scaleform = nil
|
||||||
|
self.startTime = 0
|
||||||
|
self.duration = 0
|
||||||
|
self.transition = "TRANSITION_OUT"
|
||||||
|
self.transitionDuration = 0.15
|
||||||
|
self.transitionPreventAutoExpansion = false
|
||||||
|
self.transitionExecuted = false
|
||||||
|
self.manualDispose = false
|
||||||
|
self.isDisplaying = false
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Loads the Scaleform movie if it has not been loaded yet.
|
||||||
|
function BigMessage:Load()
|
||||||
|
if self.scaleform then return end
|
||||||
|
self.scaleform = RequestScaleformMovie("MP_BIG_MESSAGE_FREEMODE")
|
||||||
|
while not HasScaleformMovieLoaded(self.scaleform) do
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Disposes of the Scaleform movie.
|
||||||
|
--- If manualDispose is true, executes a transition before disposing.
|
||||||
|
function BigMessage:Dispose()
|
||||||
|
if not self.scaleform then return end
|
||||||
|
|
||||||
|
if self.manualDispose then
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, self.transition)
|
||||||
|
ScaleformMovieMethodAddParamBool(false)
|
||||||
|
ScaleformMovieMethodAddParamFloat(self.transitionDuration)
|
||||||
|
ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
-- Wait a fraction of the transition duration (in milliseconds)
|
||||||
|
Wait((self.transitionDuration * 0.5) * 1000)
|
||||||
|
self.manualDispose = false
|
||||||
|
end
|
||||||
|
|
||||||
|
self.startTime = 0
|
||||||
|
self.transitionExecuted = false
|
||||||
|
SetScaleformMovieAsNoLongerNeeded(self.scaleform)
|
||||||
|
self.scaleform = nil
|
||||||
|
self.isDisplaying = false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Updates the display by drawing the Scaleform movie fullscreen.
|
||||||
|
function BigMessage:Update()
|
||||||
|
if not self.scaleform then return end
|
||||||
|
|
||||||
|
DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
|
||||||
|
|
||||||
|
if self.manualDispose then return end
|
||||||
|
|
||||||
|
if self.startTime ~= 0 and (GetGameTimer() - self.startTime) > self.duration then
|
||||||
|
if not self.transitionExecuted then
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, self.transition)
|
||||||
|
ScaleformMovieMethodAddParamBool(false)
|
||||||
|
ScaleformMovieMethodAddParamFloat(self.transitionDuration)
|
||||||
|
ScaleformMovieMethodAddParamBool(self.transitionPreventAutoExpansion)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
self.transitionExecuted = true
|
||||||
|
-- Extend duration slightly for smooth transition
|
||||||
|
self.duration = self.duration + ((self.transitionDuration * 0.5) * 1000)
|
||||||
|
else
|
||||||
|
self:Dispose()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Sets the transition properties for disposing the message.
|
||||||
|
--- @param transition string The transition function name (default: "TRANSITION_OUT").
|
||||||
|
--- @param duration number The duration for the transition (default: 0.4).
|
||||||
|
--- @param preventAutoExpansion boolean Whether to prevent auto-expansion (default: true).
|
||||||
|
function BigMessage:SetTransition(transition, duration, preventAutoExpansion)
|
||||||
|
self.transition = transition or "TRANSITION_OUT"
|
||||||
|
self.transitionDuration = duration or 0.4
|
||||||
|
self.transitionPreventAutoExpansion = preventAutoExpansion or true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Starts a thread to continuously update the HUD until the message is done.
|
||||||
|
function BigMessage:StartUpdate()
|
||||||
|
if self.isDisplaying then return end
|
||||||
|
|
||||||
|
self.isDisplaying = true
|
||||||
|
CreateThread(function()
|
||||||
|
while self.isDisplaying do
|
||||||
|
Wait(0)
|
||||||
|
self:Update()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a mission passed message.
|
||||||
|
--- @param msg string The message to display.
|
||||||
|
--- @param duration number|nil The duration (in milliseconds) to display the message (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose the Scaleform after display (default: false).
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- BigMessage:ShowMissionPassedMessage("MISSION PASSED", 5000)
|
||||||
|
--- ```
|
||||||
|
function BigMessage:ShowMissionPassedMessage(msg, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(msg)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString("")
|
||||||
|
ScaleformMovieMethodAddParamInt(100)
|
||||||
|
ScaleformMovieMethodAddParamBool(true)
|
||||||
|
ScaleformMovieMethodAddParamInt(0)
|
||||||
|
ScaleformMovieMethodAddParamBool(true)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a colored shard message.
|
||||||
|
--- @param msg string The main message.
|
||||||
|
--- @param desc string The description text.
|
||||||
|
--- @param textColor number The text color index.
|
||||||
|
--- @param bgColor number The background color index.
|
||||||
|
--- @param duration number|nil Duration in milliseconds (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose the Scaleform (default: false).
|
||||||
|
function BigMessage:ShowColoredShard(msg, desc, textColor, bgColor, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CENTERED_MP_MESSAGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(msg)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(desc)
|
||||||
|
ScaleformMovieMethodAddParamInt(textColor)
|
||||||
|
ScaleformMovieMethodAddParamInt(bgColor)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays an old-style mission passed message.
|
||||||
|
--- @param msg string The message.
|
||||||
|
--- @param duration number|nil Duration in milliseconds (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose (default: false).
|
||||||
|
function BigMessage:ShowOldMessage(msg, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_MISSION_PASSED_MESSAGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(msg)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a simple shard message.
|
||||||
|
--- @param msg string The main message.
|
||||||
|
--- @param subtitle string The subtitle text.
|
||||||
|
--- @param duration number|nil Duration in milliseconds (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose (default: false).
|
||||||
|
function BigMessage:ShowSimpleShard(msg, subtitle, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_CREW_RANKUP_MP_MESSAGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(msg)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(subtitle)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a rank-up message.
|
||||||
|
--- @param msg string The main message.
|
||||||
|
--- @param subtitle string The subtitle text.
|
||||||
|
--- @param rank number The rank level achieved.
|
||||||
|
--- @param duration number|nil Duration in milliseconds (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose (default: false).
|
||||||
|
function BigMessage:ShowRankupMessage(msg, subtitle, rank, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_BIG_MP_MESSAGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(msg)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(subtitle)
|
||||||
|
ScaleformMovieMethodAddParamInt(rank)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString("")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString("")
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a weapon purchased message.
|
||||||
|
--- @param bigMessage string The main message.
|
||||||
|
--- @param weaponName string The name of the weapon purchased.
|
||||||
|
--- @param weaponHash number The weapon hash.
|
||||||
|
--- @param duration number|nil Duration in milliseconds (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose (default: false).
|
||||||
|
function BigMessage:ShowWeaponPurchasedMessage(bigMessage, weaponName, weaponHash, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_WEAPON_PURCHASED")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(bigMessage)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(weaponName)
|
||||||
|
ScaleformMovieMethodAddParamInt(weaponHash)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString("")
|
||||||
|
ScaleformMovieMethodAddParamInt(100)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a large multiplayer message.
|
||||||
|
--- @param msg string The main message.
|
||||||
|
--- @param duration number|nil Duration in milliseconds (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose (default: false).
|
||||||
|
function BigMessage:ShowMpMessageLarge(msg, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_CENTERED_MP_MESSAGE_LARGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(msg)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString("")
|
||||||
|
ScaleformMovieMethodAddParamInt(100)
|
||||||
|
ScaleformMovieMethodAddParamBool(true)
|
||||||
|
ScaleformMovieMethodAddParamInt(100)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "TRANSITION_IN")
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a "Wasted" multiplayer message.
|
||||||
|
--- @param msg string The main message.
|
||||||
|
--- @param subtitle string The subtitle text.
|
||||||
|
--- @param duration number|nil Duration in milliseconds (default: 5000).
|
||||||
|
--- @param manualDispose boolean|nil Whether to manually dispose (default: false).
|
||||||
|
function BigMessage:ShowMpWastedMessage(msg, subtitle, duration, manualDispose)
|
||||||
|
duration = duration or 5000
|
||||||
|
self:Load()
|
||||||
|
self.startTime = GetGameTimer()
|
||||||
|
self.manualDispose = manualDispose or false
|
||||||
|
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SHOW_SHARD_WASTED_MP_MESSAGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(msg)
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(subtitle)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
self.duration = duration
|
||||||
|
self:StartUpdate()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Starts the update loop for displaying the message.
|
||||||
|
function BigMessage:StartUpdate()
|
||||||
|
if self.isDisplaying then return end
|
||||||
|
self.isDisplaying = true
|
||||||
|
CreateThread(function()
|
||||||
|
while self.isDisplaying do
|
||||||
|
Wait(0)
|
||||||
|
self:Update()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create an instance of BigMessage and return it.
|
||||||
|
BigMessage = BigMessage:new()
|
||||||
|
|
||||||
|
return BigMessage
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
--[[
|
||||||
|
CountdownHandler Module
|
||||||
|
-------------------------
|
||||||
|
This module provides a countdown HUD using a Scaleform movie ("COUNTDOWN").
|
||||||
|
It handles loading, updating, and disposing of the scaleform, as well as
|
||||||
|
playing sounds and displaying messages for each countdown tick.
|
||||||
|
|
||||||
|
TriggerNetEvent(getScript()..":startCountdown", 5, 25)
|
||||||
|
]]
|
||||||
|
|
||||||
|
CountdownHandler = {}
|
||||||
|
CountdownHandler.__index = CountdownHandler
|
||||||
|
|
||||||
|
--- Creates a new CountdownHandler instance.
|
||||||
|
--- @return table table A new CountdownHandler object.
|
||||||
|
function CountdownHandler:new()
|
||||||
|
local self = setmetatable({}, CountdownHandler)
|
||||||
|
self.scaleform = nil
|
||||||
|
self.renderCountdown = false
|
||||||
|
self.colour = { r = 255, g = 255, b = 255, a = 255 }
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Loads the "COUNTDOWN" scaleform movie.
|
||||||
|
function CountdownHandler:Load()
|
||||||
|
if self.scaleform then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.scaleform = RequestScaleformMovie("COUNTDOWN")
|
||||||
|
while not HasScaleformMovieLoaded(self.scaleform) do
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Disposes of the currently loaded scaleform movie.
|
||||||
|
function CountdownHandler:Dispose()
|
||||||
|
if self.scaleform then
|
||||||
|
SetScaleformMovieAsNoLongerNeeded(self.scaleform)
|
||||||
|
self.scaleform = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Updates the HUD by drawing the scaleform movie fullscreen.
|
||||||
|
function CountdownHandler:Update()
|
||||||
|
if self.scaleform then
|
||||||
|
DrawScaleformMovieFullscreen(self.scaleform, 255, 255, 255, 255, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a message on the countdown HUD.
|
||||||
|
--- @param message string The message to display.
|
||||||
|
function CountdownHandler:ShowMessage(message)
|
||||||
|
local r, g, b, a = self.colour.r, self.colour.g, self.colour.b, self.colour.a
|
||||||
|
|
||||||
|
-- Set the message in the scaleform.
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "SET_MESSAGE")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(message)
|
||||||
|
ScaleformMovieMethodAddParamInt(r)
|
||||||
|
ScaleformMovieMethodAddParamInt(g)
|
||||||
|
ScaleformMovieMethodAddParamInt(b)
|
||||||
|
ScaleformMovieMethodAddParamBool(true)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
-- Trigger a fade effect (optional).
|
||||||
|
BeginScaleformMovieMethod(self.scaleform, "FADE_MP")
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(message)
|
||||||
|
ScaleformMovieMethodAddParamInt(r)
|
||||||
|
ScaleformMovieMethodAddParamInt(g)
|
||||||
|
ScaleformMovieMethodAddParamInt(b)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Starts the countdown HUD.
|
||||||
|
--- @param number number|nil The starting number for the countdown (default: 3).
|
||||||
|
--- @param hudColour number|nil The HUD colour index (default: 18).
|
||||||
|
--- @return boolean boolean True when the countdown has finished.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- if CountdownHandler:Start(5, 25) then
|
||||||
|
--- -- When run in an if statement, the script will wait until its finished to continue
|
||||||
|
--- print("Countdown Complete")
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function CountdownHandler:Start(number, hudColour)
|
||||||
|
local finished = false
|
||||||
|
number = number or 3
|
||||||
|
hudColour = hudColour or 18
|
||||||
|
|
||||||
|
-- Get HUD colour using framework function; alternatives could be added here.
|
||||||
|
local r, g, b, a = GetHudColour(hudColour)
|
||||||
|
self.colour = { r = r, g = g, b = b, a = a }
|
||||||
|
|
||||||
|
self:Load()
|
||||||
|
|
||||||
|
self.renderCountdown = true
|
||||||
|
CreateThread(function()
|
||||||
|
while self.renderCountdown do
|
||||||
|
Wait(0)
|
||||||
|
self:Update()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Countdown logic
|
||||||
|
CreateThread(function()
|
||||||
|
local currentNumber = number
|
||||||
|
while currentNumber > 0 do
|
||||||
|
playSound("Count")
|
||||||
|
self:ShowMessage(tostring(currentNumber))
|
||||||
|
Wait(1000)
|
||||||
|
currentNumber = currentNumber - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
playSound("Go")
|
||||||
|
self:ShowMessage("GO")
|
||||||
|
finished = true
|
||||||
|
|
||||||
|
Wait(1000)
|
||||||
|
self.renderCountdown = false
|
||||||
|
self:Dispose()
|
||||||
|
finished = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
while not finished do Wait(10) end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create a singleton instance of CountdownHandler.
|
||||||
|
CountdownHandler = CountdownHandler:new()
|
||||||
|
|
||||||
|
-- Register an event to start the countdown.
|
||||||
|
RegisterNetEvent(getScript()..":startCountdown", function(number, hudColour)
|
||||||
|
CountdownHandler:Start(number, hudColour)
|
||||||
|
end)
|
||||||
|
|
||||||
|
return CountdownHandler
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
-------------------------------------------------------------
|
||||||
|
-- Debug Text Display Functionality
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Draws debug text on the screen if debugMode is enabled.
|
||||||
|
---
|
||||||
|
--- Calculates a background rectangle based on the number of text lines and renders each line on-screen.
|
||||||
|
---
|
||||||
|
--- @param textTable table An array of strings to display.
|
||||||
|
--- @param loc vector2 (Optional) Top-left coordinate for the text box (default: vec2(0.05, 0.65)).
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
---CreateThread(function()
|
||||||
|
--- while true do
|
||||||
|
--- debugScaleForm({
|
||||||
|
--- "Line 1: Debug info",
|
||||||
|
--- "Line 2: More info"
|
||||||
|
--- })
|
||||||
|
--- Wait(0)
|
||||||
|
--- end
|
||||||
|
---end)
|
||||||
|
--- ```
|
||||||
|
function debugScaleForm(textTable, loc)
|
||||||
|
if debugMode then
|
||||||
|
loc = loc or vec2(0.05, 0.65)
|
||||||
|
|
||||||
|
local lineHeight = 0.025 -- Height per line.
|
||||||
|
local totalHeight = #textTable * lineHeight
|
||||||
|
local boxPadding = 0.01 -- Padding around the text.
|
||||||
|
local size = vec2(0.18, totalHeight + boxPadding * 2)
|
||||||
|
|
||||||
|
-- Draw background rectangle.
|
||||||
|
DrawRect(loc.x + size.x / 2, loc.y + size.y / 2, size.x, size.y, 0, 0, 0, 200)
|
||||||
|
|
||||||
|
-- Render each line of text.
|
||||||
|
for i = 1, #textTable do
|
||||||
|
SetTextScale(0.30, 0.30)
|
||||||
|
BeginTextCommandDisplayText("STRING")
|
||||||
|
AddTextComponentSubstringKeyboardDisplay(textTable[i])
|
||||||
|
EndTextCommandDisplayText(loc.x + 0.005, loc.y + (i - 1) * lineHeight + 0.01)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
-------------------------------------------------------------
|
||||||
|
-- Instructional Buttons Functionality
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Loads and draws instructional buttons on-screen using a scaleform movie.
|
||||||
|
---
|
||||||
|
--- Requests the "instructional_buttons" scaleform, clears previous data, sets clear space,
|
||||||
|
--- creates data slots for each button option provided in `info`, and then draws the scaleform fullscreen.
|
||||||
|
---
|
||||||
|
--- @param info table An array of tables, where each table represents a button option:
|
||||||
|
--- - keys (table): An array of key codes (e.g., {38, 29}) to display.
|
||||||
|
--- - text (string): The label for the button.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
---CreateThread(function()
|
||||||
|
--- while true do
|
||||||
|
--- makeInstructionalButtons({
|
||||||
|
--- { keys = {38, 29}, text = "Open Menu" },
|
||||||
|
--- { keys = {45}, text = "Close Menu" }
|
||||||
|
--- })
|
||||||
|
--- Wait(0)
|
||||||
|
--- end
|
||||||
|
---end)
|
||||||
|
--- ```
|
||||||
|
function makeInstructionalButtons(info)
|
||||||
|
local build = RequestScaleformMovie("instructional_buttons")
|
||||||
|
while not HasScaleformMovieLoaded(build) do Wait(0) end
|
||||||
|
|
||||||
|
-- Draw the scaleform fullscreen (initial draw).
|
||||||
|
DrawScaleformMovieFullscreen(build, 255, 255, 255, 0, 0)
|
||||||
|
|
||||||
|
-- Clear previous instructions.
|
||||||
|
BeginScaleformMovieMethod(build, "CLEAR_ALL")
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
-- Set clear spacing between buttons.
|
||||||
|
BeginScaleformMovieMethod(build, "SET_CLEAR_SPACE")
|
||||||
|
ScaleformMovieMethodAddParamInt(200)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
-- Add each button option to the scaleform.
|
||||||
|
for i = 1, #info do
|
||||||
|
BeginScaleformMovieMethod(build, "SET_DATA_SLOT")
|
||||||
|
ScaleformMovieMethodAddParamInt(i - 1)
|
||||||
|
for k = 1, #info[i].keys do
|
||||||
|
ScaleformMovieMethodAddParamPlayerNameString(GetControlInstructionalButton(2, info[i].keys[k], true))
|
||||||
|
end
|
||||||
|
BeginTextCommandScaleformString("STRING")
|
||||||
|
AddTextComponentSubstringKeyboardDisplay(info[i].text)
|
||||||
|
EndTextCommandScaleformString()
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Draw the instructional buttons.
|
||||||
|
BeginScaleformMovieMethod(build, "DRAW_INSTRUCTIONAL_BUTTONS")
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
-- Set a translucent black background.
|
||||||
|
BeginScaleformMovieMethod(build, "SET_BACKGROUND_COLOUR")
|
||||||
|
ScaleformMovieMethodAddParamInt(0)
|
||||||
|
ScaleformMovieMethodAddParamInt(0)
|
||||||
|
ScaleformMovieMethodAddParamInt(0)
|
||||||
|
ScaleformMovieMethodAddParamInt(80)
|
||||||
|
EndScaleformMovieMethod()
|
||||||
|
|
||||||
|
-- Final full-screen draw with full opacity.
|
||||||
|
DrawScaleformMovieFullscreen(build, 255, 255, 255, 255, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- EXPERIMENTAL --
|
||||||
|
-- RedM Button Prompts --
|
||||||
|
-- Creates the promot, then shows it, this needs to be run in a loop
|
||||||
|
local promptGroups = {}
|
||||||
|
|
||||||
|
function makeRedInstructionalButtons(info, title)
|
||||||
|
if not promptGroups[title] then -- Create group if not exists
|
||||||
|
promptGroups[title] = {
|
||||||
|
title = CreateVarString(10, 'LITERAL_STRING', title),
|
||||||
|
id = GetRandomIntInRange(0, 0xffffff),
|
||||||
|
prompts = {},
|
||||||
|
}
|
||||||
|
for i = 1, #info do
|
||||||
|
promptGroups[title].prompts[i] = {
|
||||||
|
keys = info[i].keys,
|
||||||
|
text = info[i].text,
|
||||||
|
}
|
||||||
|
local keyTitle = CreateVarString(10, 'LITERAL_STRING', info[i].text)
|
||||||
|
-- Create one prompt per entry
|
||||||
|
local promptSet = UiPromptRegisterBegin()
|
||||||
|
-- Register all keys for this prompt
|
||||||
|
for k = 1, #info[i].keys do
|
||||||
|
PromptSetControlAction(promptSet, info[i].keys[k])
|
||||||
|
end
|
||||||
|
PromptSetText(promptSet, keyTitle)
|
||||||
|
PromptSetEnabled(promptSet, true)
|
||||||
|
PromptSetVisible(promptSet, true)
|
||||||
|
PromptSetGroup(promptSet, promptGroups[title].id)
|
||||||
|
PromptRegisterEnd(promptSet)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
PromptSetActiveGroupThisFrame(promptGroups[title].id, promptGroups[title].title)
|
||||||
|
end
|
||||||
|
|
||||||
|
onResourceStop(function()
|
||||||
|
for k, v in pairs(promptGroups) do
|
||||||
|
print("^5Bridge^7: ^2Removing Prompt Group^7: ^3" .. k .. "^7")
|
||||||
|
PromptDelete(promptGroups[k].id, 1)
|
||||||
|
end
|
||||||
|
end, true)
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
-------------------------------------------------------------
|
||||||
|
-- 3D Text Rendering
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Draws 3D text at specified world coordinates.
|
||||||
|
---
|
||||||
|
--- Configures text properties, draws the text, and displays a background rectangle behind it.
|
||||||
|
---
|
||||||
|
--- @param coord table A vector3 with x, y, and z coordinates.
|
||||||
|
--- @param text string The text to display.
|
||||||
|
--- @param highlight boolean (Optional) If true, highlights parts of the text.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- CreateThread(function()
|
||||||
|
--- while true do
|
||||||
|
--- DrawText3D(vector3(100, 200, 300), "Hello World", true)
|
||||||
|
--- Wait(0)
|
||||||
|
--- end
|
||||||
|
--- end)
|
||||||
|
--- ```
|
||||||
|
function DrawText3D(coord, text, highlight)
|
||||||
|
SetTextScale(0.30, 0.30)
|
||||||
|
SetTextFont(0)
|
||||||
|
SetTextProportional(1)
|
||||||
|
SetTextColour(255, 255, 255, 215)
|
||||||
|
SetTextEntry("STRING")
|
||||||
|
SetTextCentre(true)
|
||||||
|
|
||||||
|
local totalLength = string.len(text)
|
||||||
|
local textMaxLength = 99 -- max 99
|
||||||
|
local text = totalLength > textMaxLength and text:sub(1, totalLength - (totalLength - textMaxLength)) or text
|
||||||
|
AddTextComponentString(highlight and text:gsub("%~w~", "~y~") or text)
|
||||||
|
SetDrawOrigin(coord.x, coord.y, coord.z, 0)
|
||||||
|
DrawText(0.0, 0.0)
|
||||||
|
local count, length = GetLineCountAndMaxLength(text)
|
||||||
|
|
||||||
|
local padding = 0.005
|
||||||
|
local heightFactor = (count / 43) + padding
|
||||||
|
local weightFactor = (length / 150) + padding
|
||||||
|
|
||||||
|
local height = (heightFactor / 2) - padding / 1
|
||||||
|
local width = (weightFactor / 2) - padding / 1
|
||||||
|
|
||||||
|
DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
|
||||||
|
ClearDrawOrigin()
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculates the number of lines and the maximum line length from the given text.
|
||||||
|
---
|
||||||
|
--- @param text string The text to analyze.
|
||||||
|
--- @return number, number The line count and maximum line length.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local count, maxLen = GetLineCountAndMaxLength("Hello World")
|
||||||
|
--- ```
|
||||||
|
function GetLineCountAndMaxLength(text)
|
||||||
|
local lineCount, maxLength = 0, 0
|
||||||
|
for line in text:gmatch("[^\n]+") do
|
||||||
|
lineCount += 1
|
||||||
|
local lineLength = string.len(line)
|
||||||
|
if lineLength > maxLength then
|
||||||
|
maxLength = lineLength
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if lineCount == 0 then lineCount = 1 end
|
||||||
|
return lineCount, maxLength
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Additional UI Helpers
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Displays a help message on the screen.
|
||||||
|
---
|
||||||
|
--- @param text string The message to display.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- DisplayHelpMsg("Press E to interact")
|
||||||
|
--- ```
|
||||||
|
function DisplayHelpMsg(text)
|
||||||
|
BeginTextCommandDisplayHelp("STRING")
|
||||||
|
AddTextComponentScaleform(text)
|
||||||
|
EndTextCommandDisplayHelp(0, true, false, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Displays a "Saving/Loading" spinner with a custom message.
|
||||||
|
---
|
||||||
|
--- @param text string The message to display alongside the spinner.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- displaySpinner("Saving data...")
|
||||||
|
--- ```
|
||||||
|
function displaySpinner(text)
|
||||||
|
BeginTextCommandBusyspinnerOn('STRING')
|
||||||
|
AddTextComponentSubstringPlayerName(text)
|
||||||
|
EndTextCommandBusyspinnerOn(4)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Stops the "Saving/Loading" spinner.
|
||||||
|
---
|
||||||
|
--- This function should only be called client-side.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- stopSpinner()
|
||||||
|
--- ```
|
||||||
|
function stopSpinner()
|
||||||
|
if not isServer() then
|
||||||
|
BusyspinnerOff()
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
--- Creates and displays a timer HUD on the screen.
|
||||||
|
--- Draws a title (if provided) and a series of timer bars from the supplied data.
|
||||||
|
---
|
||||||
|
--- @param title string|nil Optional title to display at the top of the HUD.
|
||||||
|
--- @param data table A table of timer bar entries. Each entry should include:
|
||||||
|
--- - stat (string): The statistic name.
|
||||||
|
--- - value (string): The value to display.
|
||||||
|
--- - multi (number|nil): Optional, indicates multiple checkpoints (e.g., progress levels).
|
||||||
|
--- @param alpha number|nil Optional alpha value (transparency) for the HUD; defaults to 255.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- createTimerHud("Timer", {
|
||||||
|
--- { stat = "Health", value = "85%" },
|
||||||
|
--- { stat = "Armor", value = "50%", multi = 2 },
|
||||||
|
--- { stat = "Stamina", value = "100%" },
|
||||||
|
--- }, 255)
|
||||||
|
--- ```
|
||||||
|
function createTimerHud(title, data, alpha)
|
||||||
|
loadTextureDict("timerbars")
|
||||||
|
|
||||||
|
local loc = vec2(0.89, 0.90)
|
||||||
|
alpha = alpha or 255 -- Default to fully opaque if alpha is not provided
|
||||||
|
|
||||||
|
if title then
|
||||||
|
local x = loc.x+0.037
|
||||||
|
local y = 0.1
|
||||||
|
|
||||||
|
DrawSprite("timerbars", "all_black_bg", x, y, 0.12, 0.05, 0.0, 255, 255, 255, alpha)
|
||||||
|
SetTextScale(0.80, 0.80)
|
||||||
|
SetTextWrap(0.75, 0.985)
|
||||||
|
SetTextJustification(2)
|
||||||
|
SetTextFont(4)
|
||||||
|
SetTextColour(255, 255, 255, alpha)
|
||||||
|
BeginTextCommandDisplayText("STRING")
|
||||||
|
AddTextComponentSubstringKeyboardDisplay("~y~"..title)
|
||||||
|
EndTextCommandDisplayText(x+0.06, y - 0.026)
|
||||||
|
end
|
||||||
|
|
||||||
|
local displayIndex = 0
|
||||||
|
for i = #data, 1, -1 do
|
||||||
|
local space = 0.044 * displayIndex
|
||||||
|
|
||||||
|
DrawSprite("timerbars", "all_black_bg", loc.x+0.02, loc.y - space, 0.15, 0.04, 0.0, 255, 255, 255, alpha)
|
||||||
|
SetTextScale(0.0, 0.35)
|
||||||
|
SetTextWrap(0.5, 0.92)
|
||||||
|
SetTextJustification(2)
|
||||||
|
SetTextColour(255, 255, 255, alpha)
|
||||||
|
BeginTextCommandDisplayText("STRING")
|
||||||
|
AddTextComponentSubstringKeyboardDisplay(data[i].stat:upper())
|
||||||
|
EndTextCommandDisplayText(loc.x-0.05, (loc.y - space) - 0.0125)
|
||||||
|
|
||||||
|
SetTextScale(0.55, 0.55)
|
||||||
|
SetTextWrap(0.85, 0.98 - (data[i].multi and 0.026 or 0.0))
|
||||||
|
SetTextFont(4)
|
||||||
|
SetTextJustification(2)
|
||||||
|
SetTextColour(255, 255, 255, alpha)
|
||||||
|
if data[i].multi then
|
||||||
|
local startX = 0.071
|
||||||
|
DrawSprite("timerbars", "circle_checkpoints",
|
||||||
|
loc.x + startX, (loc.y - space)+0.005,
|
||||||
|
0.011, 0.018, 0.0, 255, 191, 0, 200)
|
||||||
|
|
||||||
|
DrawSprite("timerbars", "circle_checkpoints",
|
||||||
|
loc.x + (startX + 0.008), (loc.y - space)+0.005,
|
||||||
|
0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 1 and 200 or 75)
|
||||||
|
|
||||||
|
DrawSprite("timerbars", "circle_checkpoints",
|
||||||
|
loc.x + (startX + 0.016), (loc.y - space)+0.005,
|
||||||
|
0.011, 0.018, 0.0, 255, 191, 0, data[i].multi > 2 and 200 or 75)
|
||||||
|
end
|
||||||
|
BeginTextCommandDisplayText("STRING")
|
||||||
|
AddTextComponentSubstringKeyboardDisplay(data[i].value)
|
||||||
|
EndTextCommandDisplayText(loc.x - 0.02, (loc.y - space) - 0.017)
|
||||||
|
displayIndex += 1
|
||||||
|
end
|
||||||
|
makeInstructionalButtons({ { text = "Exit", keys = { 194 }}})
|
||||||
|
end
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
local skillCheckFunc = {
|
||||||
|
qb = {
|
||||||
|
start = function(data)
|
||||||
|
local Skillbar = exports["qb-minigames"]:Skillbar()
|
||||||
|
if Skillbar then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
ox = {
|
||||||
|
start = function(data)
|
||||||
|
local Skillbar = exports[OXLibExport]:skillCheck(
|
||||||
|
{
|
||||||
|
"easy",
|
||||||
|
"easy",
|
||||||
|
"easy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if Skillbar then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
gta = {
|
||||||
|
start = function(data)
|
||||||
|
local Skillbar = exports.jim_bridge:skillCheck()
|
||||||
|
if Skillbar then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
lation = {
|
||||||
|
start = function(data)
|
||||||
|
local Skillbar = exports.lation_ui:skillCheck("",
|
||||||
|
{
|
||||||
|
"easy",
|
||||||
|
"easy",
|
||||||
|
"easy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4"
|
||||||
|
})
|
||||||
|
if Skillbar then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function skillCheck(data)
|
||||||
|
if Config.System.skillCheck then
|
||||||
|
return skillCheckFunc[Config.System.skillCheck].start(data)
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
--[[
|
||||||
|
Society Banking Module
|
||||||
|
------------------------
|
||||||
|
This module provides functions to interact with society bank accounts across
|
||||||
|
different banking systems. Supported systems include:
|
||||||
|
• qb-banking
|
||||||
|
• esx_society *testing*
|
||||||
|
• Renewed-Banking
|
||||||
|
• fd_banking
|
||||||
|
• okokBanking
|
||||||
|
• Tgiann-bank
|
||||||
|
]]
|
||||||
|
|
||||||
|
local societyFunc = {
|
||||||
|
{ bankName = "Renewed-Banking",
|
||||||
|
getAccount =
|
||||||
|
function(society)
|
||||||
|
return exports["Renewed-Banking"]:getAccountMoney(society)
|
||||||
|
end,
|
||||||
|
chargeSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports['Renewed-Banking']:removeAccountMoney(society, amount)
|
||||||
|
end,
|
||||||
|
fundSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports['Renewed-Banking']:addAccountMoney(society, amount)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ bankName = "fd_banking",
|
||||||
|
getAccount =
|
||||||
|
function(society)
|
||||||
|
return exports["fd_banking"]:GetAccount(society)
|
||||||
|
end,
|
||||||
|
chargeSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports["fd_banking"]:RemoveMoney(society, amount)
|
||||||
|
end,
|
||||||
|
fundSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports["fd_banking"]:AddMoney(society, amount)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ bankName = "okokBanking",
|
||||||
|
getAccount =
|
||||||
|
function(society)
|
||||||
|
return exports['okokBanking']:GetAccount(society)
|
||||||
|
end,
|
||||||
|
chargeSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports['okokBanking']:RemoveMoney(society, amount)
|
||||||
|
end,
|
||||||
|
fundSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports['okokBanking']:AddMoney(society, amount)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ bankName = "tgiann-bank",
|
||||||
|
getAccount =
|
||||||
|
function(society)
|
||||||
|
if Jobs[society] then
|
||||||
|
return exports["tgiann-bank"]:GetJobAccountBalance(society)
|
||||||
|
else
|
||||||
|
return exports["tgiann-bank"]:GetGangAccountBalance(society)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
chargeSociety =
|
||||||
|
function(society, amount)
|
||||||
|
if Jobs[society] then
|
||||||
|
exports["tgiann-bank"]:RemoveJobMoney(society, amount)
|
||||||
|
else
|
||||||
|
exports["tgiann-bank"]:RemoveGangMoney(society, amount)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
fundSociety =
|
||||||
|
function(society, amount)
|
||||||
|
if Jobs[society] then
|
||||||
|
exports["tgiann-bank"]:AddJobMoney(society, amount)
|
||||||
|
else
|
||||||
|
exports["tgiann-bank"]:AddGangMoney(society, amount)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ bankName = "qb-banking",
|
||||||
|
getAccount =
|
||||||
|
function(society)
|
||||||
|
if not exports["qb-banking"]:GetAccount(society) then
|
||||||
|
if Jobs[society] then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports["qb-banking"]:CreateJobAccount(society, 0)
|
||||||
|
Wait(150)
|
||||||
|
elseif Gangs[society] then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports["qb-banking"]:CreateGangAccount(society, 0)
|
||||||
|
Wait(150)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return exports["qb-banking"]:GetAccountBalance(society)
|
||||||
|
end,
|
||||||
|
chargeSociety =
|
||||||
|
function(society, amount)
|
||||||
|
if not exports["qb-banking"]:GetAccount(society) then
|
||||||
|
if Jobs[society] then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports["qb-banking"]:CreateJobAccount(society, 0) Wait(150) -- make new account if return "null"
|
||||||
|
elseif Gangs[society] then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports["qb-banking"]:CreateGangAccount(society, 0) Wait(150) -- make new account if return "null"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
exports["qb-banking"]:RemoveMoney(society, amount)
|
||||||
|
end,
|
||||||
|
fundSociety =
|
||||||
|
function(society, amount)
|
||||||
|
if not exports["qb-banking"]:GetAccount(society) then
|
||||||
|
if Jobs[society] then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports["qb-banking"]:CreateJobAccount(society, 0)
|
||||||
|
Wait(150)
|
||||||
|
elseif Gangs[society] then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3qb-banking^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports["qb-banking"]:CreateGangAccount(society, 0)
|
||||||
|
Wait(150)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
exports["qb-banking"]:AddMoney(society, amount)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ bankName = "esx_society",
|
||||||
|
getAccount =
|
||||||
|
function(society)
|
||||||
|
local checkExist = exports.esx_society:GetSociety(society)
|
||||||
|
if checkExist == nil then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports.esx_society:registerSociety(
|
||||||
|
society,
|
||||||
|
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
|
||||||
|
society,
|
||||||
|
society,
|
||||||
|
society,
|
||||||
|
{type = "public"}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local p = promise.new()
|
||||||
|
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
|
||||||
|
p:resolve(account.money)
|
||||||
|
end)
|
||||||
|
local newAmount = Citizen.Await(p)
|
||||||
|
return newAmount
|
||||||
|
end,
|
||||||
|
chargeSociety =
|
||||||
|
function(society, amount)
|
||||||
|
local checkExist = exports.esx_society:GetSociety(society)
|
||||||
|
if checkExist == nil then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports.esx_society:registerSociety(
|
||||||
|
society,
|
||||||
|
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
|
||||||
|
society,
|
||||||
|
society,
|
||||||
|
society,
|
||||||
|
{type = "public"}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
|
||||||
|
if amount > 0 and account.money >= amount then
|
||||||
|
account.removeMoney(amount)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
fundSociety =
|
||||||
|
function(society, amount)
|
||||||
|
local checkExist = exports.esx_society:GetSociety(society)
|
||||||
|
if checkExist == nil then
|
||||||
|
print("^6Bridge^7: ^2Making new bank account in ^7'^3esx_society^7' ^2for ^7'^3"..society.."^7'")
|
||||||
|
exports.esx_society:registerSociety(
|
||||||
|
society,
|
||||||
|
Gangs[society] and Gangs[society].label or Jobs[society] and Jobs[society].label,
|
||||||
|
society,
|
||||||
|
society,
|
||||||
|
society,
|
||||||
|
{type = "public"}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
TriggerEvent('esx_addonaccount:getSharedAccount', checkExist.account, function(account)
|
||||||
|
account.addMoney(amount)
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ bankName = "wasabi_banking",
|
||||||
|
getAccount =
|
||||||
|
function(society)
|
||||||
|
return exports['wasabi_banking']:GetAccountBalance(society, 'society')
|
||||||
|
end,
|
||||||
|
chargeSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports['wasabi_banking']:RemoveMoney('society', society, amount)
|
||||||
|
end,
|
||||||
|
fundSociety =
|
||||||
|
function(society, amount)
|
||||||
|
exports['wasabi_banking']:AddMoney('society', society, amount)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
--- Retrieves the current balance of a society's bank account.
|
||||||
|
--- @param society string The identifier of the society.
|
||||||
|
--- @return number number The current account balance.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local balance = getSocietyAccount("police")
|
||||||
|
--- print("Police account balance: $"..balance)
|
||||||
|
--- ```
|
||||||
|
function getSocietyAccount(society)
|
||||||
|
local amount = 0
|
||||||
|
if society == nil or society == "none" then return amount end
|
||||||
|
|
||||||
|
for i = 1, #societyFunc do
|
||||||
|
local script = societyFunc[i]
|
||||||
|
if isStarted(script.bankName) then
|
||||||
|
local amount = script.getAccount(society)
|
||||||
|
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Retrieved account ^7'^6"..society.."^7' ($"..tostring(amount)..")")
|
||||||
|
return amount
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("^1Error^7: ^3GetSocietyAccount^7: ^2No supported banking script found")
|
||||||
|
|
||||||
|
return amount
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Deducts funds from a society's bank account.
|
||||||
|
--- @param society string The identifier of the society.
|
||||||
|
--- @param amount number The amount of money to remove.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- chargeSociety("police", 1000)
|
||||||
|
--- ```
|
||||||
|
function chargeSociety(society, amount)
|
||||||
|
|
||||||
|
for i = 1, #societyFunc do
|
||||||
|
local script = societyFunc[i]
|
||||||
|
if isStarted(script.bankName) then
|
||||||
|
script.chargeSociety(society, amount)
|
||||||
|
local newAmount = getSocietyAccount(society)
|
||||||
|
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Removing $"..amount.." from account '^6"..society.."^7' ($"..tostring(newAmount)..")")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("^1Error^7: ^3ChargeSociety^7: ^2No supported banking script found")
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Adds funds to a society's bank account.
|
||||||
|
--- @param society string The identifier of the society.
|
||||||
|
--- @param amount number The amount of money to add.
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- fundSociety("police", 500)
|
||||||
|
--- ```
|
||||||
|
function fundSociety(society, amount)
|
||||||
|
|
||||||
|
for i = 1, #societyFunc do
|
||||||
|
local script = societyFunc[i]
|
||||||
|
if isStarted(script.bankName) then
|
||||||
|
script.fundSociety(society, amount)
|
||||||
|
local newAmount = getSocietyAccount(society)
|
||||||
|
debugPrint("^6Bridge^7: ^3"..script.bankName.."^7: ^2Adding ^7$"..amount.." ^2to account ^7'^6"..society.."^7' ($"..tostring(newAmount)..")")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("^1Error^7: ^3FundSociety^7: ^2No supported banking script found")
|
||||||
|
end
|
||||||
|
|
||||||
|
if isServer() then
|
||||||
|
createCallback(getScript()..":server:getAccount", function(source, society)
|
||||||
|
if society then
|
||||||
|
local getMoney = getSocietyAccount(society)
|
||||||
|
return getMoney
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end)
|
||||||
|
end
|
||||||
@@ -0,0 +1,625 @@
|
|||||||
|
--[[
|
||||||
|
Experimental GTA In-World Text Prompts Targets Module
|
||||||
|
-------------------------------------------------------
|
||||||
|
This module handles the creation, removal, and management of in-world text targets
|
||||||
|
for interacting with entities and zones using GTA text prompts. It supports multiple
|
||||||
|
targeting systems: OX Target, QB Target, or a fallback using DrawText3D.
|
||||||
|
|
||||||
|
Available functionalities:
|
||||||
|
• createEntityTarget - Creates a target for a specific entity.
|
||||||
|
• createBoxTarget - Creates a box-shaped zone target.
|
||||||
|
• createCircleTarget - Creates a circular zone target.
|
||||||
|
• createModelTarget - Creates a target for specified models.
|
||||||
|
• removeEntityTarget - Removes a target from an entity.
|
||||||
|
• removeZoneTarget - Removes a zone target.
|
||||||
|
|
||||||
|
Fallback: If no targeting system is detected (or if disabled via Config.System.DontUseTarget),
|
||||||
|
the module uses DrawText3D prompts. This is experimental and may not work as expected.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local targetFunc = {
|
||||||
|
{ targetName = OXTargetExport,
|
||||||
|
entityTarget =
|
||||||
|
function(entity, opts, dist)
|
||||||
|
local options = {}
|
||||||
|
for i = 1, #opts do
|
||||||
|
options[i] = {
|
||||||
|
icon = opts[i].icon,
|
||||||
|
label = opts[i].label,
|
||||||
|
items = opts[i].item or nil,
|
||||||
|
groups = opts[i].job or opts[i].gang,
|
||||||
|
onSelect = opts[i].action,
|
||||||
|
distance = dist,
|
||||||
|
canInteract = opts[i].canInteract or nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return exports[OXTargetExport]:addLocalEntity(entity, options)
|
||||||
|
end,
|
||||||
|
|
||||||
|
boxTarget =
|
||||||
|
function(data, opts, dist)
|
||||||
|
local options = {}
|
||||||
|
for i = 1, #opts do
|
||||||
|
options[i] = {
|
||||||
|
icon = opts[i].icon,
|
||||||
|
label = opts[i].label,
|
||||||
|
items = opts[i].item or nil,
|
||||||
|
groups = opts[i].groups or opts[i].job or opts[i].gang,
|
||||||
|
onSelect = opts[i].onSelect or opts[i].action,
|
||||||
|
distance = dist,
|
||||||
|
canInteract = opts[i].canInteract or nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
data[5].maxZ = data[5].maxZ or (data[2].z + 0.80)
|
||||||
|
data[5].minZ = data[5].minZ or data[2].z - 1.05
|
||||||
|
local thickness = ((data[5].maxZ / 2) - (data[5].minZ / 2)) * 2
|
||||||
|
local mid = data[5].maxZ - ((data[5].maxZ / 2) - (data[5].minZ / 2))
|
||||||
|
|
||||||
|
data[2] = vec3(data[2].x, data[2].y, mid) -- force the coord to middle of the minZ and maxZ
|
||||||
|
|
||||||
|
local target = exports[OXTargetExport]:addBoxZone({
|
||||||
|
coords = data[2],
|
||||||
|
size = vec3(data[4], data[3], thickness), -- size uses the math to determine how high it needs to be
|
||||||
|
rotation = data[5].heading,
|
||||||
|
debug = data[5].debugPoly,
|
||||||
|
options = options
|
||||||
|
})
|
||||||
|
return target
|
||||||
|
end,
|
||||||
|
|
||||||
|
circleTarget =
|
||||||
|
function(data, opts, dist)
|
||||||
|
local options = {}
|
||||||
|
for i = 1, #opts do
|
||||||
|
options[i] = {
|
||||||
|
icon = opts[i].icon,
|
||||||
|
label = opts[i].label,
|
||||||
|
items = opts[i].item or nil,
|
||||||
|
groups = opts[i].job or opts[i].gang,
|
||||||
|
onSelect = opts[i].onSelect or opts[i].action,
|
||||||
|
distance = dist,
|
||||||
|
canInteract = opts[i].canInteract or nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
local target = exports[OXTargetExport]:addSphereZone({
|
||||||
|
coords = data[2],
|
||||||
|
radius = data[3],
|
||||||
|
debug = data[4].debugPoly,
|
||||||
|
options = options
|
||||||
|
})
|
||||||
|
return target
|
||||||
|
end,
|
||||||
|
|
||||||
|
modelTarget =
|
||||||
|
function(models, opts, dist)
|
||||||
|
local options = {}
|
||||||
|
for i = 1, #opts do
|
||||||
|
options[i] = {
|
||||||
|
icon = opts[i].icon,
|
||||||
|
label = opts[i].label,
|
||||||
|
items = opts[i].item or nil,
|
||||||
|
groups = opts[i].job or opts[i].gang,
|
||||||
|
onSelect = opts[i].action,
|
||||||
|
distance = dist,
|
||||||
|
canInteract = opts[i].canInteract or nil,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return exports[OXTargetExport]:addModel(models, options)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetEntity =
|
||||||
|
function(entity)
|
||||||
|
exports[OXTargetExport]:removeLocalEntity(entity, nil)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetZone =
|
||||||
|
function(target)
|
||||||
|
exports[OXTargetExport]:removeZone(target, true)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetModel =
|
||||||
|
function(model)
|
||||||
|
exports[OXTargetExport]:removeModel(model, nil)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ targetName = QBTargetExport,
|
||||||
|
entityTarget =
|
||||||
|
function(entity, opts, dist)
|
||||||
|
local options = { options = opts, distance = dist }
|
||||||
|
return exports[QBTargetExport]:AddTargetEntity(entity, options)
|
||||||
|
end,
|
||||||
|
|
||||||
|
boxTarget =
|
||||||
|
function(data, opts, dist)
|
||||||
|
local options = { options = opts, distance = dist }
|
||||||
|
local target = exports[QBTargetExport]:AddBoxZone(data[1], data[2], data[3], data[4], data[5], options)
|
||||||
|
return data[1]
|
||||||
|
end,
|
||||||
|
|
||||||
|
circleTarget =
|
||||||
|
function(data, opts, dist)
|
||||||
|
local options = { options = opts, distance = dist }
|
||||||
|
local target = exports[QBTargetExport]:AddCircleZone(data[1], data[2], data[3], data[4], options)
|
||||||
|
return data[1]
|
||||||
|
end,
|
||||||
|
|
||||||
|
modelTarget =
|
||||||
|
function(models, opts, dist)
|
||||||
|
local options = { options = opts, distance = dist }
|
||||||
|
return exports[QBTargetExport]:AddTargetModel(models, options)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetEntity =
|
||||||
|
function(entity)
|
||||||
|
exports[QBTargetExport]:RemoveTargetEntity(entity)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetZone =
|
||||||
|
function(target)
|
||||||
|
exports[QBTargetExport]:RemoveZone(target)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetModel =
|
||||||
|
function(model)
|
||||||
|
exports[QBTargetExport]:RemoveTargetModel(model, "Test")
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ targetName = "jim_bridge",
|
||||||
|
entityTarget =
|
||||||
|
function(entity, opts, dist)
|
||||||
|
return exports.jim_bridge:createEntityTarget(entity, opts, dist)
|
||||||
|
end,
|
||||||
|
|
||||||
|
boxTarget =
|
||||||
|
function(data, opts, dist)
|
||||||
|
return exports.jim_bridge:createZoneTarget(data, opts, dist)
|
||||||
|
end,
|
||||||
|
|
||||||
|
circleTarget =
|
||||||
|
function(data, opts, dist)
|
||||||
|
return exports.jim_bridge:createZoneTarget(data, opts, dist)
|
||||||
|
end,
|
||||||
|
|
||||||
|
modelTarget =
|
||||||
|
function(models, opts, dist)
|
||||||
|
return exports.jim_bridge:createModelTarget(models, opts, dist)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetEntity =
|
||||||
|
function(entity)
|
||||||
|
exports.jim_bridge:removeEntityTarget(entity)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetZone =
|
||||||
|
function(target)
|
||||||
|
exports.jim_bridge:removeZoneTarget(target)
|
||||||
|
end,
|
||||||
|
|
||||||
|
removeTargetModel =
|
||||||
|
function(model)
|
||||||
|
exports.jim_bridge:removeZoneTarget(model)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Utility Data & Tables
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
-- Tables for storing created targets for the fallback system and zone management.
|
||||||
|
local targetEntities = {} -- For entity targets.
|
||||||
|
local boxTargets = {} -- For box-shaped zone targets.
|
||||||
|
local circleTargets = {} -- For circular zone targets.
|
||||||
|
local modelTargets = {}
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Entity Target Creation
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Creates a target for an entity with specified options and interaction distance.
|
||||||
|
--- Supports different targeting systems (OX Target, QB Target, or custom DrawText3D)
|
||||||
|
--- based on the server configuration.
|
||||||
|
---
|
||||||
|
--- @param entity number The entity ID for which the target is created.
|
||||||
|
--- @param opts table Array of option tables. Each option should include:
|
||||||
|
--- - icon (string): The icon to display.
|
||||||
|
--- - label (string): The text label for the option.
|
||||||
|
--- - item (string|nil): (Optional) An associated item.
|
||||||
|
--- - job (string|nil): (Optional) The job required to interact.
|
||||||
|
--- - gang (string|nil): (Optional) The gang required to interact.
|
||||||
|
--- - action (function|nil): (Optional) The function executed on selection.
|
||||||
|
--- @param dist number The interaction distance for the target.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
---createEntityTarget(entityId, {
|
||||||
|
--- {
|
||||||
|
--- action = function()
|
||||||
|
--- openStorage()
|
||||||
|
--- end,
|
||||||
|
--- icon = "fas fa-box",
|
||||||
|
--- job = "police",
|
||||||
|
--- label = "Open Storage",
|
||||||
|
--- },
|
||||||
|
---}, 2.0)
|
||||||
|
--- ```
|
||||||
|
function createEntityTarget(entity, opts, dist)
|
||||||
|
-- Store the target entity for later cleanup.
|
||||||
|
targetEntities[#targetEntities + 1] = entity
|
||||||
|
|
||||||
|
-- if force target off, use jim_bridge built in target functions
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
exports.jim_bridge:createEntityTarget(entity, opts, dist)
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6jim_bridge ^2for entity ^7"..entity)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Entity ^2target with ^6"..script.targetName.." ^2for entity ^7"..entity)
|
||||||
|
return script.entityTarget(entity, opts, dist)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Box Zone Target Creation
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Creates a box-shaped target zone with specified options and interaction distance.
|
||||||
|
--- Supports different targeting systems based on the server configuration.
|
||||||
|
---@param data table A table containing the box zone configuration.
|
||||||
|
--- - name (`string`): The name identifier for the zone.
|
||||||
|
--- - coords (`vector3`): The center coordinates of the box.
|
||||||
|
--- - width (`number`): The width of the box.
|
||||||
|
--- - height (`number`): The height of the box.
|
||||||
|
--- - options (`table`): A table with additional options:
|
||||||
|
--- - heading (`number`): The rotation angle of the box.
|
||||||
|
--- - debugPoly (`boolean`): Whether to enable debug mode for the zone.
|
||||||
|
---
|
||||||
|
---@param opts table A table of option configurations for the target.
|
||||||
|
--- - icon (`string`): The icon to display for the option.
|
||||||
|
--- - label (`string`): The label text for the option.
|
||||||
|
--- - item (`string|nil`): (Optional) The item associated with the option.
|
||||||
|
--- - job (`string|nil`): (Optional) The job required to interact with the option.
|
||||||
|
--- - gang (`string|nil`): (Optional) The gang required to interact with the option.
|
||||||
|
--- - onSelect (`function|nil`): (Optional) The function to execute when the option is selected.
|
||||||
|
---@param dist number The interaction distance for the target.
|
||||||
|
---
|
||||||
|
---@return string|table name identifier or target object of the created zone.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
---```lua
|
||||||
|
---createBoxTarget(
|
||||||
|
--- {
|
||||||
|
--- 'storageBox',
|
||||||
|
--- vector3(100.0, 200.0, 30.0),
|
||||||
|
--- 2.0,
|
||||||
|
--- 2.0,
|
||||||
|
--- {
|
||||||
|
--- name = 'storageBox',
|
||||||
|
--- heading = 100.0,
|
||||||
|
--- debugPoly = true,
|
||||||
|
--- minZ = 27.0,
|
||||||
|
--- maxZ = 32.0,
|
||||||
|
--- },
|
||||||
|
--- },
|
||||||
|
---{
|
||||||
|
--- {
|
||||||
|
--- action = function()
|
||||||
|
--- openStorage()
|
||||||
|
--- end,
|
||||||
|
--- icon = "fas fa-box",
|
||||||
|
--- job = "police",
|
||||||
|
--- label = "Open Storage",
|
||||||
|
--- },
|
||||||
|
---}, 2.0)
|
||||||
|
---```
|
||||||
|
function createBoxTarget(data, opts, dist)
|
||||||
|
-- if force target off, use jim_bridge built in target functions
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Box ^2target with ^6jim_bridge ^7"..data[1])
|
||||||
|
return exports.jim_bridge:createZoneTarget(data, opts, dist)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Box ^2target with ^6"..script.targetName.." ^7"..data[1])
|
||||||
|
if data[5].minZ or data[5].maxZ then
|
||||||
|
local adMinZ, adMaxZ
|
||||||
|
data[5].minZ, data[5].maxZ, adMinZ, adMaxZ = adjustMinMaxZ(data[2], { minZ = data[5].minZ, maxZ = data[5].maxZ })
|
||||||
|
if adMinZ or adMaxZ then
|
||||||
|
print("^5Debug^7: ^2Auto adjusted ^7'^4"..data[1].."^7' ^2minZ and maxZ because ^1they weren't set correctly ^2remove or fix them for this target")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local target = script.boxTarget(data, opts, dist)
|
||||||
|
boxTargets[#boxTargets + 1] = target
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function createPropTarget(data, opts, dist)
|
||||||
|
-- Audo create a location based on a coord and a prop models dimensions
|
||||||
|
local width, depth, _ = GetPropDimensions(data[3])
|
||||||
|
local min, max = GetModelDimensions(data[3])
|
||||||
|
local coordAdjustment = data[2] - vec4(0, 0, 1.03, 0)
|
||||||
|
local newData = {
|
||||||
|
[1] = data[1],
|
||||||
|
[2] = coordAdjustment.xyz,
|
||||||
|
[3] = width + 0.1,
|
||||||
|
[4] = depth + 0.1,
|
||||||
|
[5] = {
|
||||||
|
name = data[1],
|
||||||
|
heading = coordAdjustment.w - 90.0,
|
||||||
|
debugPoly = debugMode,
|
||||||
|
minZ = coordAdjustment.z + (min.z) - 0.1,
|
||||||
|
maxZ = coordAdjustment.z + (max.z) + 0.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[3] then -- If spawning a prop, make entity target instead (ignores depth and width stuff)
|
||||||
|
makeDistProp({ prop = data[3], coords = data[2] }, true, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if force target off, use jim_bridge built in target functions
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Box ^2target with ^6jim_bridge ^7"..data[1])
|
||||||
|
return exports.jim_bridge:createZoneTarget(newData, opts, dist)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Box ^2target with ^6"..script.targetName.." ^7"..data[1])
|
||||||
|
local target = script.boxTarget(newData, opts, dist)
|
||||||
|
boxTargets[#boxTargets + 1] = target
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Circle Zone Target Creation
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Creates a circular target zone with specified options and interaction distance.
|
||||||
|
--- Supports different targeting systems based on server configuration.
|
||||||
|
---
|
||||||
|
---@param data table A table containing the circle zone configuration.
|
||||||
|
--- - name (`string`): The name identifier for the zone.
|
||||||
|
--- - coords (`vector3`): The center coordinates of the circle.
|
||||||
|
--- - radius (`number`): The radius of the circle.
|
||||||
|
--- - options (`table`): A table with additional options:
|
||||||
|
--- - debugPoly (`boolean`): Whether to enable debug mode for the zone.
|
||||||
|
---
|
||||||
|
---@param opts table A table of option configurations for the target.
|
||||||
|
--- - icon (`string`): The icon to display for the option.
|
||||||
|
--- - label (`string`): The label text for the option.
|
||||||
|
--- - item (`string|nil`): (Optional) The item associated with the option.
|
||||||
|
--- - job (`string|nil`): (Optional) The job required to interact with the option.
|
||||||
|
--- - gang (`string|nil`): (Optional) The gang required to interact with the option.
|
||||||
|
--- - onSelect (`function|nil`): (Optional) The function to execute when the option is selected.
|
||||||
|
---@param dist number The interaction distance for the target.
|
||||||
|
---
|
||||||
|
---@return string|table name identifier or target object of the created zone.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- createCircleTarget({
|
||||||
|
--- name = 'centralPark',
|
||||||
|
--- coords = vector3(200.0, 300.0, 40.0),
|
||||||
|
--- radius = 50.0,
|
||||||
|
--- options = { debugPoly = false }
|
||||||
|
--- }, {
|
||||||
|
--- { icon = "fas fa-tree", label = "Relax", action = relaxAction }
|
||||||
|
--- }, 2.0)
|
||||||
|
--- ```
|
||||||
|
function createCircleTarget(data, opts, dist)
|
||||||
|
|
||||||
|
-- if force target off, use jim_bridge built in target functions
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Sphere ^2target with ^6jim_bridge ^7"..data[1])
|
||||||
|
return exports.jim_bridge:createZoneTarget(data, opts, dist)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Sphere ^2target with ^6"..script.targetName.." ^7"..data[1])
|
||||||
|
local target = script.circleTarget(data, opts, dist)
|
||||||
|
circleTargets[#circleTargets + 1] = target
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Model Target Creation
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Creates a target for models with specified options and interaction distance.
|
||||||
|
--- Supports different targeting systems (OX Target, QB Target) based on server configuration.
|
||||||
|
---
|
||||||
|
--- @param models table Array of model identifiers.
|
||||||
|
--- @param opts table Array of option tables (same structure as in createEntityTarget).
|
||||||
|
--- @param dist number The interaction distance for the target.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
---createModelTarget({ model1, model2 },
|
||||||
|
---{
|
||||||
|
--- {
|
||||||
|
--- action = function()
|
||||||
|
--- openStorage()
|
||||||
|
--- end,
|
||||||
|
--- icon = "fas fa-box",
|
||||||
|
--- job = "police",
|
||||||
|
--- label = "Open Storage",
|
||||||
|
--- },
|
||||||
|
---}, 2.0)
|
||||||
|
---```
|
||||||
|
function createModelTarget(models, opts, dist)
|
||||||
|
|
||||||
|
-- if force target off, use jim_bridge built in target functions
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Model ^2target with ^6jim_bridge^7")
|
||||||
|
return exports.jim_bridge:createModelTarget(models, opts, dist)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
debugPrint("^6Bridge^7: ^2Creating new ^3Model ^2target with ^6"..script.targetName.."^7")
|
||||||
|
local target = script.modelTarget(models, opts, dist)
|
||||||
|
modelTargets[#modelTargets + 1] = target
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Target Removal Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Removes a previously created entity target.
|
||||||
|
---
|
||||||
|
--- @param entity number The entity ID whose target should be removed.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- removeEntityTarget(entityId)
|
||||||
|
--- ```
|
||||||
|
function removeEntityTarget(entity)
|
||||||
|
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
exports.jim_bridge:removeEntityTarget(entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
script.removeTargetEntity(entity)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Removes a previously created zone target.
|
||||||
|
---
|
||||||
|
--- @param target string|table The name identifier or target object of the zone to remove.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- removeZoneTarget('centralPark')
|
||||||
|
--- removeZoneTarget(targetObject)
|
||||||
|
--- ```
|
||||||
|
function removeZoneTarget(target)
|
||||||
|
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
exports.jim_bridge:removeZoneTarget(target)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
script.removeTargetZone(target)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Removes a previously created model target.
|
||||||
|
---
|
||||||
|
--- @param model table The model ID whose target should be removed.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- removeModelTarget(model)
|
||||||
|
--- ```
|
||||||
|
function removeModelTarget(model)
|
||||||
|
|
||||||
|
if Config.System.DontUseTarget then
|
||||||
|
exports.jim_bridge:removeZoneTarget(model)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for target script and use that
|
||||||
|
for i = 1, #targetFunc do
|
||||||
|
local script = targetFunc[i]
|
||||||
|
if isStarted(script.targetName) then
|
||||||
|
script.removeTargetModel(model)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ShowFloatingHelpNotification(coord, text, highlight)
|
||||||
|
AddTextEntry("FloatingText", text)
|
||||||
|
SetFloatingHelpTextWorldPosition(1, coord.x, coord.y, coord.z)
|
||||||
|
SetFloatingHelpTextStyle(1, 1, 62, -1, 3, 0)
|
||||||
|
BeginTextCommandDisplayHelp("FloatingText")
|
||||||
|
EndTextCommandDisplayHelp(2, false, false, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Cleanup on Resource Stop
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
local function CleanupTargets()
|
||||||
|
-- Remove entity targets.
|
||||||
|
for i = 1, #targetEntities do
|
||||||
|
if isStarted(OXTargetExport) then
|
||||||
|
exports[OXTargetExport]:removeLocalEntity(targetEntities[i], nil)
|
||||||
|
elseif isStarted(QBTargetExport) then
|
||||||
|
exports[QBTargetExport]:RemoveTargetEntity(targetEntities[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Remove box zone targets.
|
||||||
|
for i = 1, #boxTargets do
|
||||||
|
if isStarted(OXTargetExport) then
|
||||||
|
exports[OXTargetExport]:removeZone(boxTargets[i], true)
|
||||||
|
elseif isStarted(QBTargetExport) then
|
||||||
|
exports[QBTargetExport]:RemoveZone(boxTargets[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- Remove circle zone targets.
|
||||||
|
for i = 1, #circleTargets do
|
||||||
|
if isStarted(OXTargetExport) then
|
||||||
|
exports[OXTargetExport]:removeZone(circleTargets[i], true)
|
||||||
|
elseif isStarted(QBTargetExport) then
|
||||||
|
exports[QBTargetExport]:RemoveZone(circleTargets[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
onPlayerUnload(function()
|
||||||
|
CleanupTargets()
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- When the current resource stops, remove all targets.
|
||||||
|
onResourceStop(function()
|
||||||
|
CleanupTargets()
|
||||||
|
end, true)
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
--[[
|
||||||
|
Vehicle Info & Properties Module
|
||||||
|
----------------------------------
|
||||||
|
This module provides utilities for:
|
||||||
|
- Retrieving vehicle information from a Vehicles table.
|
||||||
|
- Getting and setting vehicle properties using the active framework.
|
||||||
|
- Comparing vehicle property differences.
|
||||||
|
- Synchronizing vehicle properties across clients.
|
||||||
|
- Managing network control of vehicles.
|
||||||
|
- Finding the closest vehicle to a given position.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Cached vehicle info to avoid unnecessary re-searches.
|
||||||
|
local lastCar, carInfo = nil, {}
|
||||||
|
|
||||||
|
--- Searches the 'Vehicles' table for a specific vehicle's details.
|
||||||
|
--- If the vehicle differs from the last searched, it retrieves its model and updates the carInfo table.
|
||||||
|
--- The table includes the vehicle's name, price, and class information.
|
||||||
|
---
|
||||||
|
--- @param vehicle number The entity ID of the vehicle to search for.
|
||||||
|
--- @return table|nil table A table containing the vehicle's details or nil if the vehicle is invalid.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local info = searchCar(vehicleEntity)
|
||||||
|
--- print(info.name, info.price, info.class.name, info.class.index)
|
||||||
|
--- ```
|
||||||
|
function searchCar(vehicle)
|
||||||
|
if lastCar ~= vehicle then -- If same car, use previous info
|
||||||
|
lastCar = vehicle
|
||||||
|
carInfo = {}
|
||||||
|
local model = GetEntityModel(vehicle)
|
||||||
|
local classlist = {
|
||||||
|
"Compacts", --1
|
||||||
|
"Sedans", --2
|
||||||
|
"SUVs", --3
|
||||||
|
"Coupes", --4
|
||||||
|
"Muscle", --5
|
||||||
|
"Sports Classics", --6
|
||||||
|
"Sports", --7
|
||||||
|
"Super", --8
|
||||||
|
"Motorcycles", --9
|
||||||
|
"Off-road", --10
|
||||||
|
"Industrial", --11
|
||||||
|
"Utility", --12
|
||||||
|
"Vans", --13
|
||||||
|
"Cycles", --14
|
||||||
|
"Boats", --15
|
||||||
|
"Helicopters", --16
|
||||||
|
"Planes", --17
|
||||||
|
"Service", --18
|
||||||
|
"Emergency", --19
|
||||||
|
"Military", --20
|
||||||
|
"Commercial", --21
|
||||||
|
"Trains", --22
|
||||||
|
"Open Wheel", --23
|
||||||
|
}
|
||||||
|
if Vehicles then
|
||||||
|
for k, v in pairs(Vehicles) do
|
||||||
|
if tonumber(v.hash) == model or joaat(v.hash) == model or joaat(v.model) == model then
|
||||||
|
debugPrint("^6Bridge^7: ^2Vehicle info found in^7 ^4Vehicles^7 ^2table^7: ^6"..(v.hash and v.hash or v.model).. " ^7(^6"..Vehicles[k].name.."^7)")
|
||||||
|
carInfo.name = Vehicles[k].name.." "..Vehicles[k].brand
|
||||||
|
carInfo.price = Vehicles[k].price
|
||||||
|
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not carInfo.name then
|
||||||
|
debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
|
||||||
|
carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
|
||||||
|
carInfo.price = 0
|
||||||
|
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
|
||||||
|
end
|
||||||
|
return carInfo
|
||||||
|
else
|
||||||
|
if not carInfo.name then
|
||||||
|
debugPrint("^6Bridge^7: ^2Vehicle ^1not ^2found in ^4vehicles^7 ^2table^7: ^6"..model.." ^7(^6"..GetDisplayNameFromVehicleModel(model):lower().."^7)")
|
||||||
|
carInfo.name = string.upper(GetMakeNameFromVehicleModel(model).." "..GetDisplayNameFromVehicleModel(model))
|
||||||
|
carInfo.price = 0
|
||||||
|
carInfo.class = classlist[GetVehicleClass(vehicle) + 1], GetVehicleClass(vehicle)
|
||||||
|
end
|
||||||
|
-- without this the function returned nil and anything doing
|
||||||
|
-- searchCar(vehicle).class would throw
|
||||||
|
return carInfo
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return carInfo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Vehicle Properties Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Retrieves the properties of a given vehicle using the active framework.
|
||||||
|
---
|
||||||
|
--- @param vehicle number The entity ID of the vehicle.
|
||||||
|
--- @return table|nil table A table containing the vehicle's properties or nil if invalid.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local props = getVehicleProperties(vehicleEntity)
|
||||||
|
--- if props then
|
||||||
|
--- -- Use vehicle properties
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function getVehicleProperties(vehicle)
|
||||||
|
if not vehicle then return nil end
|
||||||
|
|
||||||
|
local propertyFunc = {
|
||||||
|
{ framework = OXLibExport,
|
||||||
|
func = function(vehicle)
|
||||||
|
return lib.getVehicleProperties(vehicle)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{ framework = QBExport,
|
||||||
|
func = function(vehicle)
|
||||||
|
return Core.Functions.GetVehicleProperties(vehicle)
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i = 1, #propertyFunc do
|
||||||
|
local prop = propertyFunc[i]
|
||||||
|
if isStarted(prop.framework) then
|
||||||
|
local properties = prop.func(vehicle)
|
||||||
|
debugPrint("^6Bridge^7: ^2Getting Vehicle Properties ^7[^6"..prop.framework.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..properties.model.."^7] - [^3"..properties.plate.."^7]")
|
||||||
|
return properties
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Sets the properties of a given vehicle if changes are detected.
|
||||||
|
--- It compares the current properties with the new ones and applies the update using the active framework.
|
||||||
|
---
|
||||||
|
--- @param vehicle number The entity ID of the vehicle.
|
||||||
|
--- @param props table The new properties to apply.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- setVehicleProperties(vehicleEntity, newProperties)
|
||||||
|
--- ```
|
||||||
|
function setVehicleProperties(vehicle, props)
|
||||||
|
if checkDifferences(vehicle, props) then
|
||||||
|
if not DoesEntityExist(vehicle) then
|
||||||
|
print("Unable to set vehicle properties for '"..vehicle.."' (^1entity does not exist^7)")
|
||||||
|
end
|
||||||
|
|
||||||
|
if isStarted(QBExport) and not isStarted(QBXExport) then
|
||||||
|
Core.Functions.SetVehicleProperties(vehicle, props)
|
||||||
|
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..QBExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
|
||||||
|
|
||||||
|
elseif isStarted(OXLibExport) then
|
||||||
|
lib.setVehicleProperties(vehicle, props, false)
|
||||||
|
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
debugPrint("^6Bridge^7: ^2No Changes Found ^7 [^3"..vehicle.."^7] - [^3"..GetEntityModel(vehicle).."^7/^3"..props.model.."^7] - [^3"..props.plate.."^7]")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks for differences between the current and new vehicle properties.
|
||||||
|
--- Compares properties using JSON encoding for deep comparison and logs differences.
|
||||||
|
---
|
||||||
|
--- @param vehicle number The entity ID of the vehicle.
|
||||||
|
--- @param newProps table The new properties to compare.
|
||||||
|
--- @return boolean `true` if differences are found; `false` otherwise.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- if checkDifferences(vehicleEntity, newProperties) then
|
||||||
|
--- setVehicleProperties(vehicleEntity, newProperties)
|
||||||
|
--- end
|
||||||
|
--- ```
|
||||||
|
function checkDifferences(vehicle, newProps)
|
||||||
|
local oldProps = getVehicleProperties(vehicle)
|
||||||
|
debugPrint("^6Bridge^7: ^2Finding differences in ^3Vehicle Properties^7")
|
||||||
|
local differencesFound = true
|
||||||
|
|
||||||
|
for k in pairs(oldProps) do
|
||||||
|
if json.encode(oldProps[k]) ~= json.encode(newProps[k]) then
|
||||||
|
differencesFound = true
|
||||||
|
debugPrint("^6Bridge^7: ^5Old ^7[^3"..k.."^7] - "..json.encode(oldProps[k], { indent = true }))
|
||||||
|
debugPrint("^6Bridge^7: ^5New ^7[^3"..k.."^7] - "..json.encode(newProps[k], { indent = true }))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return differencesFound
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Vehicle Properties Synchronization
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Event handler for setting vehicle properties received from the server.
|
||||||
|
--- Listens for the `ox:setVehicleProperties` event and applies the properties.
|
||||||
|
---
|
||||||
|
--- @event `getScript()..ox:setVehicleProperties`
|
||||||
|
--- @param netId number The network ID of the vehicle.
|
||||||
|
--- @param props table The new vehicle properties.
|
||||||
|
RegisterNetEvent(getScript()..":ox:setVehicleProperties", function(netId, props)
|
||||||
|
local vehicle = NetworkGetEntityFromNetworkId(netId)
|
||||||
|
local value = props
|
||||||
|
Entity(vehicle).state[getScript()..':setVehicleProperties'] = value
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Handles state bag changes for updating vehicle properties.
|
||||||
|
--- When the state bag changes, the new properties are applied to the vehicle.
|
||||||
|
---
|
||||||
|
--- @param bagName string The state bag's name.
|
||||||
|
--- @param key string The key that changed.
|
||||||
|
--- @param value table The new state value.
|
||||||
|
AddStateBagChangeHandler(getScript()..':setVehicleProperties', '', function(bagName, _, value)
|
||||||
|
if not value or not GetEntityFromStateBagName then return end
|
||||||
|
local entity = GetEntityFromStateBagName(bagName)
|
||||||
|
local networked = not bagName:find('localEntity')
|
||||||
|
debugPrint("^6Bridge^7: ^2Setting Vehicle Properties ^7[^6"..OXLibExport.."^7] - [^3"..entity.."^7] - [^3"..GetEntityModel(entity).."^7] - [^3"..value.plate.."^7]")
|
||||||
|
|
||||||
|
if networked then return end
|
||||||
|
|
||||||
|
if lib.setVehicleProperties(entity, value) then
|
||||||
|
Entity(entity).state:set('setVehicleProperties', nil, true)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Vehicle Control Functions
|
||||||
|
-------------------------------------------------------------
|
||||||
|
|
||||||
|
--- This function ensures that the vehicle is controlled by the current player and is set as a mission entity.
|
||||||
|
--- It requests network control and sets the vehicle accordingly to synchronize changes across clients.
|
||||||
|
---
|
||||||
|
---@param entity number The entity ID of the vehicle to push.
|
||||||
|
---
|
||||||
|
---@usage
|
||||||
|
--- ```lua
|
||||||
|
--- pushVehicle(vehicleEntity)
|
||||||
|
--- ```
|
||||||
|
function pushVehicle(entity)
|
||||||
|
SetVehicleModKit(entity, 0)
|
||||||
|
if entity ~= 0 and DoesEntityExist(entity) then
|
||||||
|
-- Request network control if not already controlled.
|
||||||
|
if not NetworkHasControlOfEntity(entity) then
|
||||||
|
debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Requesting network control of vehicle^7.")
|
||||||
|
NetworkRequestControlOfEntity(entity)
|
||||||
|
local timeout = 2000
|
||||||
|
while timeout > 0 and not NetworkHasControlOfEntity(entity) do
|
||||||
|
Wait(100)
|
||||||
|
timeout = timeout - 100
|
||||||
|
end
|
||||||
|
if NetworkHasControlOfEntity(entity) then
|
||||||
|
debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Network now has control of the entity^7.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Set as mission entity if not already set.
|
||||||
|
if not IsEntityAMissionEntity(entity) then
|
||||||
|
debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Setting vehicle as a ^7'^2mission^7' ^2entity^7.")
|
||||||
|
SetEntityAsMissionEntity(entity, true, true)
|
||||||
|
local timeout = 2000
|
||||||
|
while timeout > 0 and not IsEntityAMissionEntity(entity) do
|
||||||
|
Wait(100)
|
||||||
|
timeout = timeout - 100
|
||||||
|
end
|
||||||
|
if IsEntityAMissionEntity(entity) then
|
||||||
|
debugPrint("^6Bridge^7: ^3pushEnt^7: ^2Vehicle is a ^7'^2mission^7'^2 entity^7.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add entitty named version
|
||||||
|
function pushEnt(...) pushVehicle(...) end
|
||||||
|
|
||||||
|
|
||||||
|
--- Finds the closest vehicle to the specified coordinates.
|
||||||
|
--- The function uses different APIs based on whether a source is provided.
|
||||||
|
---
|
||||||
|
--- @param coords table|vector3 (Optional) The reference coordinates. If nil, uses the player's position.
|
||||||
|
--- @param src boolean (Optional) If true, uses GetPlayerPed(source) and GetAllVehicles.
|
||||||
|
--- @return number closestVehicle The closest vehicle entity and its distance.
|
||||||
|
--- @return number closestDistance The distance of the closest vehicle.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local closestVeh, distance = getClosestVehicle({ x = 100, y = 200, z = 30 }, src)
|
||||||
|
--- ```
|
||||||
|
function getClosestVehicle(coords, src)
|
||||||
|
local ped, vehicles, closestDistance, closestVehicle
|
||||||
|
|
||||||
|
if src then
|
||||||
|
-- if checking server side cache src's ped and use server native
|
||||||
|
ped = GetPlayerPed(src)
|
||||||
|
vehicles = GetAllVehicles()
|
||||||
|
else
|
||||||
|
-- if checking client side cache local ped and use client native
|
||||||
|
ped = PlayerPedId()
|
||||||
|
vehicles = GetGamePool('CVehicle')
|
||||||
|
end
|
||||||
|
|
||||||
|
local closestDistance, closestVehicle = -1, -1
|
||||||
|
|
||||||
|
if coords then
|
||||||
|
if type(coords) == 'table' then
|
||||||
|
coords = vec3(coords.x, coords.y, coords.z)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
coords = GetEntityCoords(ped)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #vehicles, 1 do
|
||||||
|
local vehicleCoords = GetEntityCoords(vehicles[i])
|
||||||
|
local distance = #(vehicleCoords - coords.xyz)
|
||||||
|
|
||||||
|
if closestDistance == -1 or distance < closestDistance then
|
||||||
|
closestDistance = distance
|
||||||
|
closestVehicle = vehicles[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return closestVehicle, closestDistance
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Checks whether a vehicle is owned or not using the plate as reference
|
||||||
|
---
|
||||||
|
--- @param plate string The plate of the vehicle to check
|
||||||
|
--- @return boolean whether vehicle is owned
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- local plate = "ABCD1234"
|
||||||
|
--- local isVehicleOwned = isVehicleOwned(plate)
|
||||||
|
--- ```
|
||||||
|
|
||||||
|
local vehiclesOwned = {}
|
||||||
|
|
||||||
|
function isVehicleOwned(plate)
|
||||||
|
-- If already checked, cache it to reduce database calls
|
||||||
|
if vehiclesOwned[plate] == true then
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
-- Find frameworks vehicle table and search sql for if vehicle plate is owned
|
||||||
|
local sqlTable = "player_vehicles"
|
||||||
|
local vehDatabase = {
|
||||||
|
|
||||||
|
{ framework = ESXExport,
|
||||||
|
sqlTable = "owned_vehicles"
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = QBExport,
|
||||||
|
sqlTable = "player_vehicles"
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = QBXExport,
|
||||||
|
sqlTable = "player_vehicles"
|
||||||
|
},
|
||||||
|
|
||||||
|
{ framework = OXCoreExport,
|
||||||
|
sqlTable = "owned_vehicles"
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, #vehDatabase do
|
||||||
|
local framework = vehDatabase[i]
|
||||||
|
if isStarted(framework.framework) then
|
||||||
|
sqlTable = framework.sqlTable
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = MySQL.query.await("SELECT 1 from "..sqlTable.." WHERE plate = ?", { plate })
|
||||||
|
if json.encode(result) ~= "[]" then
|
||||||
|
vehiclesOwned[plate] = true -- Cache ownership for later checks
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,694 @@
|
|||||||
|
WarMenu = { }
|
||||||
|
WarMenu.__index = WarMenu
|
||||||
|
|
||||||
|
if not isServer() then
|
||||||
|
|
||||||
|
-- Deprecated
|
||||||
|
WarMenu.debug = false
|
||||||
|
function WarMenu.SetDebugEnabled(enabled)
|
||||||
|
end
|
||||||
|
function WarMenu.IsDebugEnabled()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
---
|
||||||
|
|
||||||
|
local menus = { }
|
||||||
|
local keys = { down = 187, scrollDown = 242, up = 188, scrollUp = 241, left = 189, right = 190, select = 191, accept = 237, back = 194, cancel = 238 }
|
||||||
|
|
||||||
|
local skipInputNextFrame = true
|
||||||
|
local optionCount = 0
|
||||||
|
|
||||||
|
local currentKey = nil
|
||||||
|
local currentMenu = nil
|
||||||
|
|
||||||
|
local toolTipWidth = 0.153
|
||||||
|
|
||||||
|
local spriteWidth = 0.027
|
||||||
|
local spriteHeight = spriteWidth * GetAspectRatio()
|
||||||
|
|
||||||
|
local titleHeight = 0.101
|
||||||
|
local titleYOffset = 0.021
|
||||||
|
local titleFont = 1
|
||||||
|
local titleScale = 1.0
|
||||||
|
|
||||||
|
local buttonHeight = 0.038
|
||||||
|
local buttonFont = 0
|
||||||
|
local buttonScale = 0.365
|
||||||
|
local buttonTextXOffset = 0.005
|
||||||
|
local buttonTextYOffset = 0.005
|
||||||
|
local buttonSpriteXOffset = 0.002
|
||||||
|
local buttonSpriteYOffset = 0.005
|
||||||
|
|
||||||
|
local defaultStyle = {
|
||||||
|
x = 0.0175,
|
||||||
|
y = 0.025,
|
||||||
|
width = 0.23,
|
||||||
|
maxOptionCountOnScreen = 10,
|
||||||
|
titleVisible = true,
|
||||||
|
titleColor = { 0, 0, 0, 255 },
|
||||||
|
titleBackgroundColor = { 245, 127, 23, 255 },
|
||||||
|
titleBackgroundSprite = nil,
|
||||||
|
subTitleColor = { 245, 127, 23, 255 },
|
||||||
|
textColor = { 254, 254, 254, 255 },
|
||||||
|
subTextColor = { 189, 189, 189, 255 },
|
||||||
|
focusTextColor = { 0, 0, 0, 255 },
|
||||||
|
focusColor = { 245, 245, 245, 255 },
|
||||||
|
backgroundColor = { 0, 0, 0, 160 },
|
||||||
|
subTitleBackgroundColor = { 0, 0, 0, 255 },
|
||||||
|
buttonPressedSound = { name = 'SELECT', set = 'HUD_FRONTEND_DEFAULT_SOUNDSET' }, --https://pastebin.com/0neZdsZ5
|
||||||
|
}
|
||||||
|
|
||||||
|
local function IsNavigatedDown()
|
||||||
|
return IsControlJustReleased(2, keys.down) or IsControlJustReleased(2, keys.scrollDown)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function IsNavigatedUp()
|
||||||
|
return IsControlJustReleased(2, keys.up) or IsControlJustReleased(2, keys.scrollUp)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function IsSelectedPressed()
|
||||||
|
return IsControlJustReleased(2, keys.select) or IsControlJustReleased(2, keys.accept)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function IsBackPressed()
|
||||||
|
return IsControlJustReleased(2, keys.back) or IsControlJustReleased(2, keys.cancel)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setMenuProperty(id, property, value)
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local menu = menus[id]
|
||||||
|
if menu then
|
||||||
|
menu[property] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setStyleProperty(id, property, value)
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local menu = menus[id]
|
||||||
|
|
||||||
|
if menu then
|
||||||
|
if not menu.overrideStyle then
|
||||||
|
menu.overrideStyle = { }
|
||||||
|
end
|
||||||
|
|
||||||
|
menu.overrideStyle[property] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getStyleProperty(property, menu)
|
||||||
|
menu = menu or currentMenu
|
||||||
|
|
||||||
|
if menu.overrideStyle then
|
||||||
|
local value = menu.overrideStyle[property]
|
||||||
|
if value ~= nil then
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return menu.style and menu.style[property] or defaultStyle[property]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getTitleHeight()
|
||||||
|
return getStyleProperty('titleVisible') and titleHeight or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function copyTable(t)
|
||||||
|
if type(t) ~= 'table' then
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = { }
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
result[k] = copyTable(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setMenuVisible(id, visible, holdCurrentOption)
|
||||||
|
if currentMenu then
|
||||||
|
if visible then
|
||||||
|
if currentMenu.id == id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if currentMenu.id ~= id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if visible then
|
||||||
|
local menu = menus[id]
|
||||||
|
|
||||||
|
if not currentMenu then
|
||||||
|
menu.currentOption = 1
|
||||||
|
else
|
||||||
|
if not holdCurrentOption then
|
||||||
|
menus[currentMenu.id].currentOption = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
currentMenu = menu
|
||||||
|
skipInputNextFrame = true
|
||||||
|
|
||||||
|
SetUserRadioControlEnabled(false)
|
||||||
|
HudWeaponWheelIgnoreControlInput(true)
|
||||||
|
else
|
||||||
|
HudWeaponWheelIgnoreControlInput(false)
|
||||||
|
SetUserRadioControlEnabled(true)
|
||||||
|
|
||||||
|
currentMenu = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setTextParams(font, color, scale, center, shadow, alignRight, wrapFrom, wrapTo)
|
||||||
|
SetTextFont(font)
|
||||||
|
SetTextColour(color[1], color[2], color[3], color[4] or 255)
|
||||||
|
SetTextScale(scale, scale)
|
||||||
|
|
||||||
|
if shadow then
|
||||||
|
SetTextDropShadow()
|
||||||
|
end
|
||||||
|
|
||||||
|
if center then
|
||||||
|
SetTextCentre(true)
|
||||||
|
elseif alignRight then
|
||||||
|
SetTextRightJustify(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not wrapFrom or not wrapTo then
|
||||||
|
wrapFrom = wrapFrom or getStyleProperty('x')
|
||||||
|
wrapTo = wrapTo or getStyleProperty('x') + getStyleProperty('width') - buttonTextXOffset
|
||||||
|
end
|
||||||
|
|
||||||
|
SetTextWrap(wrapFrom, wrapTo)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getLinesCount(text, x, y)
|
||||||
|
BeginTextCommandLineCount('TWOSTRINGS')
|
||||||
|
AddTextComponentString(tostring(text))
|
||||||
|
return EndTextCommandGetLineCount(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawText(text, x, y)
|
||||||
|
BeginTextCommandDisplayText('TWOSTRINGS')
|
||||||
|
AddTextComponentString(tostring(text))
|
||||||
|
EndTextCommandDisplayText(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawRect(x, y, width, height, color)
|
||||||
|
DrawRect(x, y, width, height, color[1], color[2], color[3], color[4] or 255)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getCurrentIndex()
|
||||||
|
if currentMenu.currentOption <= getStyleProperty('maxOptionCountOnScreen') and optionCount <= getStyleProperty('maxOptionCountOnScreen') then
|
||||||
|
return optionCount
|
||||||
|
elseif optionCount > currentMenu.currentOption - getStyleProperty('maxOptionCountOnScreen') and optionCount <= currentMenu.currentOption then
|
||||||
|
return optionCount - (currentMenu.currentOption - getStyleProperty('maxOptionCountOnScreen'))
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawTitle()
|
||||||
|
if not getStyleProperty('titleVisible') then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = getStyleProperty('x') + getStyleProperty('width') / 2
|
||||||
|
local y = getStyleProperty('y') + titleHeight / 2
|
||||||
|
|
||||||
|
if getStyleProperty('titleBackgroundSprite') then
|
||||||
|
DrawSprite(getStyleProperty('titleBackgroundSprite').dict, getStyleProperty('titleBackgroundSprite').name, x, y, getStyleProperty('width'), titleHeight, 0., 255, 255, 255, 255)
|
||||||
|
else
|
||||||
|
drawRect(x, y, getStyleProperty('width'), titleHeight, getStyleProperty('titleBackgroundColor'))
|
||||||
|
end
|
||||||
|
|
||||||
|
if currentMenu.title then
|
||||||
|
setTextParams(titleFont, getStyleProperty('titleColor'), titleScale, true)
|
||||||
|
drawText(currentMenu.title, x, y - titleHeight / 2 + titleYOffset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawSubTitle()
|
||||||
|
local x = getStyleProperty('x') + getStyleProperty('width') / 2
|
||||||
|
local y = getStyleProperty('y') + getTitleHeight() + buttonHeight / 2
|
||||||
|
|
||||||
|
drawRect(x, y, getStyleProperty('width'), buttonHeight, getStyleProperty('subTitleBackgroundColor'))
|
||||||
|
|
||||||
|
setTextParams(buttonFont, getStyleProperty('subTitleColor'), buttonScale, false)
|
||||||
|
drawText(currentMenu.subTitle, getStyleProperty('x') + buttonTextXOffset, y - buttonHeight / 2 + buttonTextYOffset)
|
||||||
|
|
||||||
|
if optionCount > getStyleProperty('maxOptionCountOnScreen') then
|
||||||
|
setTextParams(buttonFont, getStyleProperty('subTitleColor'), buttonScale, false, false, true)
|
||||||
|
drawText(tostring(currentMenu.currentOption)..' / '..tostring(optionCount), getStyleProperty('x') + getStyleProperty('width'), y - buttonHeight / 2 + buttonTextYOffset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawButton(text, subText)
|
||||||
|
local currentIndex = getCurrentIndex()
|
||||||
|
if not currentIndex then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local backgroundColor = nil
|
||||||
|
local textColor = nil
|
||||||
|
local subTextColor = nil
|
||||||
|
local shadow = false
|
||||||
|
|
||||||
|
if currentMenu.currentOption == optionCount then
|
||||||
|
backgroundColor = getStyleProperty('focusColor')
|
||||||
|
textColor = getStyleProperty('focusTextColor')
|
||||||
|
subTextColor = getStyleProperty('focusTextColor')
|
||||||
|
else
|
||||||
|
backgroundColor = getStyleProperty('backgroundColor')
|
||||||
|
textColor = getStyleProperty('textColor')
|
||||||
|
subTextColor = getStyleProperty('subTextColor')
|
||||||
|
shadow = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local x = getStyleProperty('x') + getStyleProperty('width') / 2
|
||||||
|
local y = getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * currentIndex) - buttonHeight / 2
|
||||||
|
|
||||||
|
drawRect(x, y, getStyleProperty('width'), buttonHeight, backgroundColor)
|
||||||
|
|
||||||
|
setTextParams(buttonFont, textColor, buttonScale, false, shadow)
|
||||||
|
drawText(text, getStyleProperty('x') + buttonTextXOffset, y - (buttonHeight / 2) + buttonTextYOffset)
|
||||||
|
|
||||||
|
if subText then
|
||||||
|
setTextParams(buttonFont, subTextColor, buttonScale, false, shadow, true)
|
||||||
|
drawText(subText, getStyleProperty('x') + buttonTextXOffset, y - buttonHeight / 2 + buttonTextYOffset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.CreateMenu(id, title, subTitle, style)
|
||||||
|
-- Default settings
|
||||||
|
local menu = { }
|
||||||
|
|
||||||
|
-- Members
|
||||||
|
menu.id = id
|
||||||
|
menu.previousMenu = nil
|
||||||
|
menu.currentOption = 1
|
||||||
|
menu.title = title
|
||||||
|
menu.subTitle = subTitle and string.upper(subTitle) or 'INTERACTION MENU'
|
||||||
|
|
||||||
|
-- Style
|
||||||
|
if style then
|
||||||
|
menu.style = style
|
||||||
|
end
|
||||||
|
|
||||||
|
menus[id] = menu
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.CreateSubMenu(id, parent, subTitle, style)
|
||||||
|
local parentMenu = menus[parent]
|
||||||
|
if not parentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
WarMenu.CreateMenu(id, parentMenu.title, subTitle and string.upper(subTitle) or parentMenu.subTitle)
|
||||||
|
|
||||||
|
local menu = menus[id]
|
||||||
|
|
||||||
|
menu.previousMenu = parent
|
||||||
|
|
||||||
|
if parentMenu.overrideStyle then
|
||||||
|
menu.overrideStyle = copyTable(parentMenu.overrideStyle)
|
||||||
|
end
|
||||||
|
|
||||||
|
if style then
|
||||||
|
menu.style = style
|
||||||
|
elseif parentMenu.style then
|
||||||
|
menu.style = copyTable(parentMenu.style)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.CurrentMenu()
|
||||||
|
return currentMenu and currentMenu.id or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.OpenMenu(id)
|
||||||
|
if id and menus[id] then
|
||||||
|
PlaySoundFrontend(-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
|
||||||
|
setMenuVisible(id, true, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.IsMenuOpened(id)
|
||||||
|
return currentMenu and currentMenu.id == id
|
||||||
|
end
|
||||||
|
WarMenu.Begin = WarMenu.IsMenuOpened
|
||||||
|
|
||||||
|
function WarMenu.IsAnyMenuOpened()
|
||||||
|
return currentMenu ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.IsMenuAboutToBeClosed()
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.CloseMenu()
|
||||||
|
if currentMenu then
|
||||||
|
setMenuVisible(currentMenu.id, false)
|
||||||
|
optionCount = 0
|
||||||
|
currentKey = nil
|
||||||
|
PlaySoundFrontend(-1, 'QUIT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.ToolTip(text, width, flipHorizontal)
|
||||||
|
if not currentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local currentIndex = getCurrentIndex()
|
||||||
|
if not currentIndex then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
width = width or toolTipWidth
|
||||||
|
|
||||||
|
local x = nil
|
||||||
|
if not flipHorizontal then
|
||||||
|
x = getStyleProperty('x') + getStyleProperty('width') + width / 2 + buttonTextXOffset
|
||||||
|
else
|
||||||
|
x = getStyleProperty('x') - width / 2 - buttonTextXOffset
|
||||||
|
end
|
||||||
|
|
||||||
|
local textX = x - (width / 2) + buttonTextXOffset
|
||||||
|
setTextParams(buttonFont, getStyleProperty('textColor'), buttonScale, false, true, false, textX, textX + width - (buttonTextYOffset * 2))
|
||||||
|
local linesCount = getLinesCount(text, textX, getStyleProperty('y'))
|
||||||
|
|
||||||
|
local height = GetTextScaleHeight(buttonScale, buttonFont) * (linesCount + 1) + buttonTextYOffset
|
||||||
|
local y = getStyleProperty('y') + getTitleHeight() + (buttonHeight * currentIndex) + height / 2
|
||||||
|
|
||||||
|
drawRect(x, y, width, height, getStyleProperty('backgroundColor'))
|
||||||
|
|
||||||
|
y = y - (height / 2) + buttonTextYOffset
|
||||||
|
drawText(text, textX, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.Button(text, subText)
|
||||||
|
if not currentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
optionCount = optionCount + 1
|
||||||
|
|
||||||
|
drawButton(text, subText)
|
||||||
|
|
||||||
|
local pressed = false
|
||||||
|
|
||||||
|
if currentMenu.currentOption == optionCount then
|
||||||
|
if currentKey == keys.select then
|
||||||
|
pressed = true
|
||||||
|
PlaySoundFrontend(-1, getStyleProperty('buttonPressedSound').name, getStyleProperty('buttonPressedSound').set, true)
|
||||||
|
elseif currentKey == keys.left or currentKey == keys.right then
|
||||||
|
PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return pressed
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SpriteButton(text, dict, name, r, g, b, a)
|
||||||
|
if not currentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pressed = WarMenu.Button(text)
|
||||||
|
|
||||||
|
local currentIndex = getCurrentIndex()
|
||||||
|
if not currentIndex then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not HasStreamedTextureDictLoaded(dict) then
|
||||||
|
RequestStreamedTextureDict(dict)
|
||||||
|
end
|
||||||
|
DrawSprite(dict, name, getStyleProperty('x') + getStyleProperty('width') - spriteWidth / 2 - buttonSpriteXOffset, getStyleProperty('y') + getTitleHeight() + buttonHeight + (buttonHeight * currentIndex) - spriteHeight / 2 + buttonSpriteYOffset, spriteWidth, spriteHeight, 0., r or 255, g or 255, b or 255, a or 255)
|
||||||
|
|
||||||
|
return pressed
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.InputButton(text, windowTitleEntry, defaultText, maxLength, subText)
|
||||||
|
if not currentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pressed = WarMenu.Button(text, subText)
|
||||||
|
local inputText = nil
|
||||||
|
|
||||||
|
if pressed then
|
||||||
|
DisplayOnscreenKeyboard(1, windowTitleEntry or 'FMMC_MPM_NA', '', defaultText or '', '', '', '', maxLength or 255)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
DisableAllControlActions(0)
|
||||||
|
|
||||||
|
local status = UpdateOnscreenKeyboard()
|
||||||
|
if status == 2 then
|
||||||
|
break
|
||||||
|
elseif status == 1 then
|
||||||
|
inputText = GetOnscreenKeyboardResult()
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
Citizen.Wait(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return pressed, inputText
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.MenuButton(text, id, subText)
|
||||||
|
if not currentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pressed = WarMenu.Button(text, subText)
|
||||||
|
|
||||||
|
if pressed then
|
||||||
|
currentMenu.currentOption = optionCount
|
||||||
|
setMenuVisible(currentMenu.id, false)
|
||||||
|
setMenuVisible(id, true, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
return pressed
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.CheckBox(text, checked, callback)
|
||||||
|
if not currentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = nil
|
||||||
|
if currentMenu.currentOption == optionCount + 1 then
|
||||||
|
name = checked and 'shop_box_tickb' or 'shop_box_blankb'
|
||||||
|
else
|
||||||
|
name = checked and 'shop_box_tick' or 'shop_box_blank'
|
||||||
|
end
|
||||||
|
|
||||||
|
local pressed = WarMenu.SpriteButton(text, 'commonmenu', name)
|
||||||
|
|
||||||
|
if pressed then
|
||||||
|
checked = not checked
|
||||||
|
if callback then callback(checked) end
|
||||||
|
end
|
||||||
|
|
||||||
|
return pressed
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.ComboBox(text, items, currentIndex, selectedIndex, callback)
|
||||||
|
if not currentMenu then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local itemsCount = #items
|
||||||
|
local selectedItem = items[currentIndex]
|
||||||
|
local isCurrent = currentMenu.currentOption == optionCount + 1
|
||||||
|
selectedIndex = selectedIndex or currentIndex
|
||||||
|
|
||||||
|
if itemsCount > 1 and isCurrent then
|
||||||
|
selectedItem = '← '..tostring(selectedItem)..' →'
|
||||||
|
end
|
||||||
|
|
||||||
|
local pressed = WarMenu.Button(text, selectedItem)
|
||||||
|
|
||||||
|
if pressed then
|
||||||
|
selectedIndex = currentIndex
|
||||||
|
elseif isCurrent then
|
||||||
|
if currentKey == keys.left then
|
||||||
|
if currentIndex > 1 then currentIndex = currentIndex - 1 else currentIndex = itemsCount end
|
||||||
|
elseif currentKey == keys.right then
|
||||||
|
if currentIndex < itemsCount then currentIndex = currentIndex + 1 else currentIndex = 1 end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if callback then callback(currentIndex, selectedIndex) end
|
||||||
|
return pressed, currentIndex
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.Display()
|
||||||
|
if currentMenu then
|
||||||
|
if not IsPauseMenuActive() then
|
||||||
|
ClearAllHelpMessages()
|
||||||
|
HudWeaponWheelIgnoreSelection()
|
||||||
|
DisablePlayerFiring(PlayerId(), true)
|
||||||
|
DisableControlAction(0, 25, true)
|
||||||
|
|
||||||
|
drawTitle()
|
||||||
|
drawSubTitle()
|
||||||
|
|
||||||
|
currentKey = nil
|
||||||
|
|
||||||
|
if skipInputNextFrame then
|
||||||
|
skipInputNextFrame = false
|
||||||
|
else
|
||||||
|
if IsNavigatedDown() then
|
||||||
|
PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
|
||||||
|
|
||||||
|
if currentMenu.currentOption < optionCount then
|
||||||
|
currentMenu.currentOption = currentMenu.currentOption + 1
|
||||||
|
else
|
||||||
|
currentMenu.currentOption = 1
|
||||||
|
end
|
||||||
|
elseif IsNavigatedUp() then
|
||||||
|
PlaySoundFrontend(-1, 'NAV_UP_DOWN', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
|
||||||
|
|
||||||
|
if currentMenu.currentOption > 1 then
|
||||||
|
currentMenu.currentOption = currentMenu.currentOption - 1
|
||||||
|
else
|
||||||
|
currentMenu.currentOption = optionCount
|
||||||
|
end
|
||||||
|
elseif IsControlJustReleased(2, keys.left) then
|
||||||
|
currentKey = keys.left
|
||||||
|
elseif IsControlJustReleased(2, keys.right) then
|
||||||
|
currentKey = keys.right
|
||||||
|
elseif IsSelectedPressed() then
|
||||||
|
currentKey = keys.select
|
||||||
|
elseif IsBackPressed() then
|
||||||
|
if menus[currentMenu.previousMenu] then
|
||||||
|
setMenuVisible(currentMenu.previousMenu, true)
|
||||||
|
PlaySoundFrontend(-1, 'BACK', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
|
||||||
|
else
|
||||||
|
WarMenu.CloseMenu()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
optionCount = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
WarMenu.End = WarMenu.Display
|
||||||
|
|
||||||
|
function WarMenu.CurrentOption()
|
||||||
|
if currentMenu and optionCount ~= 0 then
|
||||||
|
return currentMenu.currentOption
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.IsItemHovered()
|
||||||
|
if not currentMenu or optionCount == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return currentMenu.currentOption == optionCount
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.IsItemSelected()
|
||||||
|
return currentKey == keys.select and WarMenu.IsItemHovered()
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetTitle(id, title)
|
||||||
|
setMenuProperty(id, 'title', title)
|
||||||
|
end
|
||||||
|
WarMenu.SetMenuTitle = WarMenu.SetTitle
|
||||||
|
|
||||||
|
function WarMenu.SetSubTitle(id, text)
|
||||||
|
setMenuProperty(id, 'subTitle', string.upper(text))
|
||||||
|
end
|
||||||
|
WarMenu.SetMenuSubTitle = WarMenu.SetSubTitle
|
||||||
|
|
||||||
|
function WarMenu.SetMenuStyle(id, style)
|
||||||
|
setMenuProperty(id, 'style', style)
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuTitleVisible(id, visible)
|
||||||
|
setStyleProperty(id, 'titleVisible', visible)
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuX(id, x)
|
||||||
|
setStyleProperty(id, 'x', x)
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuY(id, y)
|
||||||
|
setStyleProperty(id, 'y', y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuWidth(id, width)
|
||||||
|
setStyleProperty(id, 'width', width)
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuMaxOptionCountOnScreen(id, count)
|
||||||
|
setStyleProperty(id, 'maxOptionCountOnScreen', count)
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetTitleColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'titleColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
WarMenu.SetMenuTitleColor = WarMenu.SetTitleColor
|
||||||
|
|
||||||
|
function WarMenu.SetMenuSubTitleColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'subTitleColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuSubTitleBackgroundColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'subTitleBackgroundColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetTitleBackgroundColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'titleBackgroundColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
WarMenu.SetMenuTitleBackgroundColor = WarMenu.SetTitleBackgroundColor
|
||||||
|
|
||||||
|
function WarMenu.SetTitleBackgroundSprite(id, dict, name)
|
||||||
|
RequestStreamedTextureDict(dict)
|
||||||
|
setStyleProperty(id, 'titleBackgroundSprite', { dict = dict, name = name })
|
||||||
|
end
|
||||||
|
WarMenu.SetMenuTitleBackgroundSprite = WarMenu.SetTitleBackgroundSprite
|
||||||
|
|
||||||
|
function WarMenu.SetMenuBackgroundColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'backgroundColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuTextColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'textColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuSubTextColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'subTextColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuFocusColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'focusColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuFocusTextColor(id, r, g, b, a)
|
||||||
|
setStyleProperty(id, 'focusTextColor', { r, g, b, a })
|
||||||
|
end
|
||||||
|
|
||||||
|
function WarMenu.SetMenuButtonPressedSound(id, name, set)
|
||||||
|
setStyleProperty(id, 'buttonPressedSound', { name = name, set = set })
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
--- Registers a command with the active command system.
|
||||||
|
--- This function supports multiple command systems (OXLib, qb-core, ESX Legacy).
|
||||||
|
---
|
||||||
|
--- @param command string The name of the command to register.
|
||||||
|
--- @param options table A table containing command options.
|
||||||
|
--- - help (`string`): The help description for the command.
|
||||||
|
--- - params (`table`): A table of parameters for the command.
|
||||||
|
--- - callback (`function`): The function to execute when the command is called.
|
||||||
|
--- - autocomplete (`function|nil`): (Optional) A function for autocompletion.
|
||||||
|
--- - restrictedGroup (`string|nil`): (Optional) The user group required to execute the command.
|
||||||
|
---
|
||||||
|
--- @usage
|
||||||
|
--- ```lua
|
||||||
|
--- registerCommand("greet", {
|
||||||
|
--- "Greets the player",
|
||||||
|
--- { name = "name", help = "Name of the player to greet" },
|
||||||
|
--- nil,
|
||||||
|
--- function(source, args) print("Hello, "..args[1].."!") end,
|
||||||
|
--- "admin"
|
||||||
|
--- })
|
||||||
|
--- ```
|
||||||
|
function registerCommand(command, options)
|
||||||
|
-- Name the options
|
||||||
|
local optionTable = {
|
||||||
|
helpInfo = options[1] or nil, -- help info that pops up in chat box
|
||||||
|
subText = options[2] or nil, -- sub help info for each arg
|
||||||
|
argsRequired = options[3] or nil, -- command only works if you enter args
|
||||||
|
funct = options[4] or nil, -- function that will run when command is triggered
|
||||||
|
restriction = options[5] or nil -- admin group that can use the group or `nil`
|
||||||
|
}
|
||||||
|
|
||||||
|
local commandResource = ""
|
||||||
|
if isStarted(OXLibExport) then
|
||||||
|
commandResource = OXLibExport
|
||||||
|
lib.addCommand(command,
|
||||||
|
{
|
||||||
|
help = optionTable.helpInfo,
|
||||||
|
params = optionTable.subText,
|
||||||
|
restricted = optionTable.restriction and "group."..optionTable.restriction or nil
|
||||||
|
},
|
||||||
|
optionTable.funct)
|
||||||
|
|
||||||
|
elseif isStarted(QBExport) and not isStarted(QBXExport) then
|
||||||
|
commandResource = QBExport
|
||||||
|
Core.Commands.Add(command,
|
||||||
|
optionTable.helpInfo,
|
||||||
|
optionTable.subText,
|
||||||
|
optionTable.argsRequired,
|
||||||
|
optionTable.funct,
|
||||||
|
optionTable.restriction or nil
|
||||||
|
)
|
||||||
|
|
||||||
|
elseif isStarted(RSGExport) then
|
||||||
|
commandResource = RSGExport
|
||||||
|
Core.Commands.Add(command,
|
||||||
|
optionTable.helpInfo,
|
||||||
|
optionTable.subText,
|
||||||
|
optionTable.argsRequired,
|
||||||
|
optionTable.funct,
|
||||||
|
optionTable.restriction or nil
|
||||||
|
)
|
||||||
|
|
||||||
|
elseif isStarted(ESXExport) then
|
||||||
|
commandResource = ESXExport
|
||||||
|
ESX.RegisterCommand(command,
|
||||||
|
optionTable.restriction or 'admin',
|
||||||
|
function(xPlayer, args, showError)
|
||||||
|
optionTable.funct(xPlayer.source, args, showError)
|
||||||
|
end,
|
||||||
|
false,
|
||||||
|
{ help = options[1] }
|
||||||
|
)
|
||||||
|
|
||||||
|
end
|
||||||
|
if commandResource ~= "" then
|
||||||
|
debugPrint("^6Bridge^7: ^2Registering ^3Command^2 with ^4"..commandResource.."^7:", "^7/"..command)
|
||||||
|
else
|
||||||
|
print("^1ERROR^7: ^1Couldn't find supported framework to register command^7:", "/"..command)
|
||||||
|
end
|
||||||
|
end
|
||||||
+275
@@ -0,0 +1,275 @@
|
|||||||
|
gameName = not IsDuplicityVersion() and GetCurrentGameName()
|
||||||
|
|
||||||
|
Exports = {
|
||||||
|
QBExport = "qb-core",
|
||||||
|
QBXExport = "qbx_core",
|
||||||
|
ESXExport = "es_extended",
|
||||||
|
OXCoreExport = "ox_core",
|
||||||
|
|
||||||
|
OXInv = "ox_inventory",
|
||||||
|
QBInv = "qb-inventory",
|
||||||
|
PSInv = "ps-inventory",
|
||||||
|
CoreInv = "core_inventory",
|
||||||
|
CodeMInv = "codem-inventory",
|
||||||
|
OrigenInv = "origen_inventory",
|
||||||
|
TgiannInv = "tgiann-inventory",
|
||||||
|
JPRInv = "jpr-inventory",
|
||||||
|
|
||||||
|
OXLibExport = "ox_lib",
|
||||||
|
|
||||||
|
QBMenuExport = "qb-menu",
|
||||||
|
|
||||||
|
QBTargetExport = "qb-target",
|
||||||
|
OXTargetExport = "ox_target",
|
||||||
|
|
||||||
|
-- REDM
|
||||||
|
RSGExport = "rsg-core",
|
||||||
|
RSGInv = "rsg-inventory",
|
||||||
|
|
||||||
|
VorpExport = "vorp_core",
|
||||||
|
VorpInv = "vorp_inventory",
|
||||||
|
VorpMenu = "vorp_menu",
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Required variables
|
||||||
|
debugMode = true
|
||||||
|
|
||||||
|
QBInvNew = true
|
||||||
|
|
||||||
|
InventoryWeight = 120000
|
||||||
|
|
||||||
|
-- Corruptted transfer file Checks -- Not Ready Yet ⚠️
|
||||||
|
|
||||||
|
--CreateThread(function()
|
||||||
|
-- local res = GetCurrentResourceName()
|
||||||
|
-- local ok, val = pcall(function()
|
||||||
|
-- return exports[res]:ping_encrypted()
|
||||||
|
-- end)
|
||||||
|
--
|
||||||
|
-- if not ok or val ~= "ok" then
|
||||||
|
-- print("\n^6===============================================================================")
|
||||||
|
-- print(("\n^1Corruption Warning^7: ^1[^7%s^1] Encrypted files failed to initialize.^7\n"):format(res))
|
||||||
|
-- print("^3Common causes^7:")
|
||||||
|
-- print(" • Possible corrupt files")
|
||||||
|
-- print(" • Damaged or partial upload (FTP/zip/unzip issue).")
|
||||||
|
-- print(" • Outdated artifact version\n")
|
||||||
|
-- print("^2How To Fix^7:")
|
||||||
|
-- print(" • Update your server artifact version")
|
||||||
|
-- print(" • Re-download asset from Keymaster")
|
||||||
|
-- print(" • Use WinSCP to upload")
|
||||||
|
-- print(" • Avoid editing any encrypted files")
|
||||||
|
-- print(" • Restart the server")
|
||||||
|
-- print("\n^6===============================================================================^7\n")
|
||||||
|
-- end
|
||||||
|
--end)
|
||||||
|
|
||||||
|
-- Missing/Broken Config file check
|
||||||
|
if not Config or not Config.System then
|
||||||
|
print("\n^6=================================================================================")
|
||||||
|
print("^6=================================================================================\n")
|
||||||
|
print(" ^1ERROR^7: jim_bridge ^1can't find the ^7Config^1 table in ^7"..GetCurrentResourceName())
|
||||||
|
print(" ^1It is either missing, or there is an error inside it stopping it from loading^7\n")
|
||||||
|
print("^6=================================================================================")
|
||||||
|
print("^6=================================================================================^7\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- [[ Server.Cfg Convar Check ]]--
|
||||||
|
-- Check server convars for hard set defaults, otherwise it uses per script configurations
|
||||||
|
if Config and Config.System then
|
||||||
|
debugMode = Config.System.Debug
|
||||||
|
if Config.System.Debug then
|
||||||
|
if GetConvar("jim_DisableDebug", "false") == "true" then
|
||||||
|
debugMode = false
|
||||||
|
end
|
||||||
|
if GetConvar("jim_DisableEventDebug", "false") == "true" then
|
||||||
|
Config.System.EventDebug = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Config.System.Menu = GetConvar("jim_menuScript", Config.System.Menu)
|
||||||
|
Config.System.Notify = GetConvar("jim_notifyScript", Config.System.Notify or "gta")
|
||||||
|
Config.System.ProgressBar = GetConvar("jim_progressBarScript", Config.System.ProgressBar or "gta")
|
||||||
|
Config.System.drawText = GetConvar("jim_drawTextScript", Config.System.drawText or "gta")
|
||||||
|
Config.System.skillCheck = GetConvar("jim_skillCheckScript", Config.System.skillCheck or "gta")
|
||||||
|
|
||||||
|
if GetConvar("jim_dontUseTarget", "false") == "true" then
|
||||||
|
Config.System.DontUseTarget = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Load the core files here instead of in fxmanifests
|
||||||
|
-- Forcefully loads the files and quietly, compared to fxmanifest complaining if they don't exist when you don't need them all
|
||||||
|
local filesToLoad = {
|
||||||
|
Shared = {
|
||||||
|
[Exports.OXLibExport] = { "init.lua" },
|
||||||
|
[Exports.OXCoreExport] = { "lib/init.lua" },
|
||||||
|
[Exports.ESXExport] = { "imports.lua" },
|
||||||
|
[Exports.QBXExport] = { "modules/playerdata.lua" },
|
||||||
|
},
|
||||||
|
Client = {
|
||||||
|
["PolyZone"] = { "client.lua", "BoxZone.lua", "EntityZone.lua", "CircleZone.lua", "ComboZone.lua" },
|
||||||
|
["warmenu"] = { "warmenu.lua", },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Shared framework file loader
|
||||||
|
for Type, ScriptTable in pairs(filesToLoad) do
|
||||||
|
if Type == "Client" and IsDuplicityVersion() then
|
||||||
|
goto continue
|
||||||
|
else
|
||||||
|
for scriptName, files in pairs(ScriptTable) do
|
||||||
|
local state = GetResourceState(scriptName)
|
||||||
|
-- if the resource is started, load the file
|
||||||
|
if state == "started" then
|
||||||
|
if scriptName == Exports.OXLibExport and GetResourceState(Exports.OXCoreExport):find("start") then
|
||||||
|
if debugMode then print("OX_Core found, skipping OX_Lib loading") end
|
||||||
|
goto skip
|
||||||
|
end
|
||||||
|
-- Force items into a table if they are not
|
||||||
|
if type(files) == "string" then
|
||||||
|
files = { files }
|
||||||
|
end
|
||||||
|
for _, file in pairs(files) do
|
||||||
|
--print("^5CoreLoader^7: '"..scriptName.."/"..file.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
|
||||||
|
local fileLoader = assert(load(LoadResourceFile(scriptName, (file)), ('@@'..scriptName..'/'..file)))
|
||||||
|
fileLoader()
|
||||||
|
if debugMode then
|
||||||
|
print("^5CoreLoader^7: ^2loaded ^1Core ^2file^7: ^3"..scriptName.."^7/^3"..(file):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").." ^2into ^7'"..GetCurrentResourceName().."'")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::skip::
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if script is in server, but not started warn the user
|
||||||
|
if state == "uninitialized" or state == "stopped" then
|
||||||
|
print("^5CoreLoader^7: ^3"..scriptName.." ^1 found but it wasn't started^7.")
|
||||||
|
print("^1Check your ^3server^7.^3cfg ^1load order^7")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- debugging only, if the script is missing, warn the user
|
||||||
|
if state == "missing" then
|
||||||
|
if debugMode then
|
||||||
|
print("^5CoreLoader^7: ^3"..scriptName.." ^2not found^7, ^2skipping")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Load files here into the invoking script
|
||||||
|
for _, v in pairs({ -- This is a specific load order
|
||||||
|
|
||||||
|
'helpers.lua', -- needs to be first
|
||||||
|
'_loaders.lua',
|
||||||
|
|
||||||
|
'_eventDebug.lua',
|
||||||
|
'callback.lua',
|
||||||
|
'coreloader.lua',
|
||||||
|
|
||||||
|
'duifunctions.lua',
|
||||||
|
|
||||||
|
-- Native Scaleforms
|
||||||
|
'scaleforms/scaleform_basic.lua',
|
||||||
|
'scaleforms/bigMessageInstance.lua',
|
||||||
|
'scaleforms/countDownHandler.lua',
|
||||||
|
'scaleforms/debugScaleform.lua',
|
||||||
|
'scaleforms/instructionalButtons.lua',
|
||||||
|
'scaleforms/timerBars.lua',
|
||||||
|
|
||||||
|
-- Required functions
|
||||||
|
'make/loaders.lua',
|
||||||
|
'make/makeBlip.lua',
|
||||||
|
'make/makePed.lua',
|
||||||
|
'make/pedTasks.lua',
|
||||||
|
'make/makeProp.lua',
|
||||||
|
'make/makeVeh.lua',
|
||||||
|
'make/cameras.lua',
|
||||||
|
'make/progressBars.lua',
|
||||||
|
|
||||||
|
'wrapperfunctions.lua',
|
||||||
|
'polyZone.lua',
|
||||||
|
'clientItemAllowlist.lua',
|
||||||
|
'itemcontrol.lua',
|
||||||
|
'playerfunctions.lua',
|
||||||
|
'metaHandlers.lua',
|
||||||
|
'jobfunctions.lua',
|
||||||
|
'societybank.lua',
|
||||||
|
'phones.lua',
|
||||||
|
|
||||||
|
-- Interactions
|
||||||
|
'targets.lua',
|
||||||
|
'contextmenus.lua',
|
||||||
|
'input.lua',
|
||||||
|
'notify.lua',
|
||||||
|
'drawText.lua',
|
||||||
|
'skillcheck.lua',
|
||||||
|
|
||||||
|
-- Crafting / Shops / Stashes
|
||||||
|
'crafting.lua',
|
||||||
|
|
||||||
|
-- Kind of "other"
|
||||||
|
'isAnimal.lua',
|
||||||
|
'scaleEntity.lua',
|
||||||
|
'vehicles.lua',
|
||||||
|
'effects.lua',
|
||||||
|
'make/ropeControl.lua',
|
||||||
|
|
||||||
|
--'warmenu.lua',
|
||||||
|
'_authToken.lua',
|
||||||
|
-- Do version check last
|
||||||
|
'_scriptversioncheck.lua'
|
||||||
|
}) do
|
||||||
|
if debugMode then
|
||||||
|
--print("^5Loading^7: 'jim_bridge/shared/"..v.."' ^2into ^7'"..GetCurrentResourceName().."' ...")
|
||||||
|
end
|
||||||
|
local fileLoader = assert(load(LoadResourceFile('jim_bridge', ('shared/'..v)), ('@@jim_bridge/shared/'..v)))
|
||||||
|
fileLoader()
|
||||||
|
if debugMode then
|
||||||
|
print("^5CoreLoader^7: ^2loaded file^7: ^3"..(v):gsub("/", "^7/^3"):gsub("%.lua", "^7.lua").."^7")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Auto Detection of Old/New QBInv/PSInv versions --
|
||||||
|
|
||||||
|
if isStarted(QBInv) then
|
||||||
|
if isServer() then
|
||||||
|
if not checkExportExists(QBInv, "CreateShop") then
|
||||||
|
print("^6Bridge^7: ^2Can^7'^2t ^2find new ^4"..QBInv.."^2 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
|
||||||
|
QBInvNew = false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if checkExportExists(QBInv, "GetTrunkSize") then
|
||||||
|
print("^6Bridge^7: ^2Found old ^4"..QBInv.."^3 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
|
||||||
|
QBInvNew = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if isStarted(PSInv) then
|
||||||
|
if isServer() then
|
||||||
|
if not checkExportExists(PSInv, "CreateShop") then
|
||||||
|
print("^6Bridge^7: ^2Can^7'^2t ^2find new ^4"..PSInv.."^2 export^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
|
||||||
|
QBInvNew = false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local function versionCompare(v1, v2)
|
||||||
|
local split = function(v) local t = {} for s in v:gmatch("[^.]+") do t[#t+1] = tonumber(s) end return t end
|
||||||
|
local a, b = split(v1), split(v2)
|
||||||
|
for i = 1, math.max(#a, #b) do
|
||||||
|
local ai, bi = a[i] or 0, b[i] or 0
|
||||||
|
if ai > bi then return true
|
||||||
|
elseif ai < bi then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if versionCompare(GetResourceMetadata(PSInv, "version", 0), "1.0.6") then
|
||||||
|
print("^6Bridge^7: ^4"..PSInv.."^2 Version above ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1true^7")
|
||||||
|
QBInvNew = true
|
||||||
|
else
|
||||||
|
print("^6Bridge^7: ^4"..PSInv.."^2 Version ^1below ^31.0.6^7, ^2forcing ^1QBInvNew ^2to ^1false^7")
|
||||||
|
QBInvNew = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
local alcoholCount = 0
|
||||||
|
local drugCount = 0
|
||||||
|
local purgeTimer = 300000
|
||||||
|
|
||||||
|
local minDrugCount = 3
|
||||||
|
local maxDrugCount = 7
|
||||||
|
|
||||||
|
local minAlcoholCount = 2
|
||||||
|
local midAlcoholCount = 4
|
||||||
|
local maxAlcoholCount = 7
|
||||||
|
|
||||||
|
local alcoholEffectData = {
|
||||||
|
levels = {
|
||||||
|
["min"] = {
|
||||||
|
effect = "DrugsMichaelAliensFightIn",
|
||||||
|
movement = "move_m@drunk@slightlydrunk",
|
||||||
|
camShake = 0.5,
|
||||||
|
canStumble = false,
|
||||||
|
},
|
||||||
|
["mid"] = {
|
||||||
|
effect = "DrugsMichaelAliensFightIn",
|
||||||
|
movement = "move_m@drunk@moderatedrunk",
|
||||||
|
canRagdoll = true,
|
||||||
|
camShake = 0.5,
|
||||||
|
canStumble = true,
|
||||||
|
stumbleChance = 0.8,
|
||||||
|
drunkDriving = true,
|
||||||
|
},
|
||||||
|
["max"] = {
|
||||||
|
effect = "DrugsMichaelAliensFightIn",
|
||||||
|
movement = "move_m@drunk@verydrunk",
|
||||||
|
canRagdoll = true,
|
||||||
|
camShake = 2.8,
|
||||||
|
canStumble = true,
|
||||||
|
stumbleChance = 0.6,
|
||||||
|
drunkDriving = true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local drugEffectData = {
|
||||||
|
levels = {
|
||||||
|
["min"] = {
|
||||||
|
--effect = "SwitchHUDTrevorIn",
|
||||||
|
--movement = "move_m@drunk@slightlydrunk",
|
||||||
|
},
|
||||||
|
["max"] = {
|
||||||
|
effect = "SwitchHUDTrevorIn",
|
||||||
|
movement = "move_m@drunk@slightlydrunk",
|
||||||
|
camShake = 0.2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Alcohol Thread
|
||||||
|
function addAlocholCount(count, canOD)
|
||||||
|
alcoholCount = alcoholCount + count
|
||||||
|
--print("Alcohol count increased, current amount:", alcoholCount)
|
||||||
|
CreateThread(function()
|
||||||
|
runAlcoholThread()
|
||||||
|
end)
|
||||||
|
if alcoholCount >= maxAlcoholCount then
|
||||||
|
-- max
|
||||||
|
startAlcoholEffect(alcoholEffectData.levels["max"], "max")
|
||||||
|
if canOD then
|
||||||
|
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) - math.random(10, 15))
|
||||||
|
end
|
||||||
|
elseif alcoholCount < maxAlcoholCount and alcoholCount >= midAlcoholCount then
|
||||||
|
-- mid
|
||||||
|
startAlcoholEffect(alcoholEffectData.levels["mid"], "mid")
|
||||||
|
TriggerEvent("evidence:client:SetStatus", "heavyalcohol", 200)
|
||||||
|
if canOD then
|
||||||
|
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) - math.random(5, 10))
|
||||||
|
end
|
||||||
|
elseif alcoholCount >= 1 then
|
||||||
|
-- min
|
||||||
|
TriggerEvent("evidence:client:SetStatus", "alcohol", 200)
|
||||||
|
startAlcoholEffect(alcoholEffectData.levels["min"], "min")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function getAlcoholCount()
|
||||||
|
return alcoholCount
|
||||||
|
end
|
||||||
|
|
||||||
|
function removeAlcoholCount(count)
|
||||||
|
alcoholCount = alcoholCount - count
|
||||||
|
if alcoholCount < minAlcoholCount then
|
||||||
|
clearCurrentAlcoholEffect()
|
||||||
|
end
|
||||||
|
--print("Alcohol count decreased, current amount:", alcoholCount)
|
||||||
|
end
|
||||||
|
|
||||||
|
local alcoholThreadRun = false
|
||||||
|
function runAlcoholThread()
|
||||||
|
if alcoholThreadRun == true then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
alcoholThreadRun = true
|
||||||
|
end
|
||||||
|
--print("Alcohol Thread triggered")
|
||||||
|
while alcoholCount > 0 do
|
||||||
|
Wait(purgeTimer)
|
||||||
|
removeAlcoholCount(1)
|
||||||
|
if alcoholCount <= 0 then
|
||||||
|
clearCurrentAlcoholEffect()
|
||||||
|
alcoholThreadRun = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
exports("getAlcoholCount",getAlcoholCount)
|
||||||
|
exports("addAlocholCount", addAlocholCount)
|
||||||
|
exports("removeAlcoholCount", removeAlcoholCount)
|
||||||
|
|
||||||
|
|
||||||
|
-- EFFECT Management
|
||||||
|
|
||||||
|
local currentAlcoholEffect = ""
|
||||||
|
local alcoholEffect = ""
|
||||||
|
|
||||||
|
function clearCurrentAlcoholEffect()
|
||||||
|
local Ped = PlayerPedId()
|
||||||
|
if alcoholEffect ~= "" then
|
||||||
|
AnimpostfxStop(alcoholEffect)
|
||||||
|
ResetPedMovementClipset(Ped, 0.0)
|
||||||
|
ClearTimecycleModifier()
|
||||||
|
ResetScenarioTypesEnabled()
|
||||||
|
SetPedIsDrunk(Ped, false)
|
||||||
|
SetPedMotionBlur(Ped, false)
|
||||||
|
end
|
||||||
|
currentAlcoholEffect = ""
|
||||||
|
alcoholEffect = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
function startAlcoholEffect(data, level)
|
||||||
|
local Ped = PlayerPedId()
|
||||||
|
if currentAlcoholEffect == level then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
clearCurrentAlcoholEffect()
|
||||||
|
currentAlcoholEffect = level
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Start effect using sent data
|
||||||
|
if alcoholEffect ~= data.effect then
|
||||||
|
alcoholEffect = data.effect
|
||||||
|
AnimpostfxPlay(data.effect, 0, true)
|
||||||
|
if data.movement then
|
||||||
|
RequestAnimSet(data.movement)
|
||||||
|
while not HasAnimSetLoaded(data.movement) do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
SetPedMovementClipset(Ped, data.movement, 3.0)
|
||||||
|
end
|
||||||
|
SetPedCanRagdoll(Ped, true)
|
||||||
|
if data.camShake then
|
||||||
|
ShakeGameplayCam("DRUNK_SHAKE", data.camShake)
|
||||||
|
end
|
||||||
|
SetTimecycleModifier("Drunk")
|
||||||
|
SetPedMotionBlur(Ped, true)
|
||||||
|
SetPedIsDrunk(Ped, true)
|
||||||
|
|
||||||
|
if data.drivingEffect or data.canStumble then
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
local effectCheck = level
|
||||||
|
local lastStumbleTime = GetGameTimer() + 1200
|
||||||
|
local lastDriveTime = GetGameTimer() + 6000
|
||||||
|
while effectCheck == currentAlcoholEffect do
|
||||||
|
if data.camStumble and GetGameTimer() > lastStumbleTime and math.random() > (data.stumbleChance or 0.8) then
|
||||||
|
lastStumbleTime = GetGameTimer() + 1200
|
||||||
|
SetPedToRagdoll(Ped, 5000, 5000, 0, true, true, false)
|
||||||
|
end
|
||||||
|
if data.driving and GetGameTimer() > lastDriveTime then
|
||||||
|
local inVeh, veh, seat = GetSeatPedIsIn()
|
||||||
|
if inVeh then
|
||||||
|
lastDriveTime = GetGameTimer() + 6000
|
||||||
|
if seat == -1 then
|
||||||
|
if math.random() < 0.62 then
|
||||||
|
StartVehicleHorn(veh, 1000, "NORMAL", false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Wait(1000)
|
||||||
|
end
|
||||||
|
--print("Effect loop broken")
|
||||||
|
end)
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Helper
|
||||||
|
function GetSeatPedIsIn()
|
||||||
|
local inVeh, veh, seat = false, 0, 0
|
||||||
|
local veh = GetVehiclePedIsIn(PlayerPedId(), false)
|
||||||
|
if veh ~= 0 then
|
||||||
|
if GetPedInVehicleSeat(veh, -1) == PlayerPedId() then
|
||||||
|
inVeh = true
|
||||||
|
seat = -1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return inVeh, veh, seat
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Drug thread
|
||||||
|
function addDrugCount(count, canOD)
|
||||||
|
drugCount = drugCount + count
|
||||||
|
--print("Drug count increased, current amount:", drugCount)
|
||||||
|
if drugCount >= minDrugCount and drugCount <= maxDrugCount then
|
||||||
|
startDrugEffect(drugEffectData.levels["min"], "min")
|
||||||
|
elseif drugCount > maxDrugCount then
|
||||||
|
if canOD then
|
||||||
|
SetEntityHealth(PlayerPedId(), GetEntityHealth(PlayerPedId()) - math.random(10, 15))
|
||||||
|
end
|
||||||
|
startDrugEffect(drugEffectData.levels["max"], "max")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function getDrugCount()
|
||||||
|
return drugCount
|
||||||
|
end
|
||||||
|
|
||||||
|
function removeDrugCount(count)
|
||||||
|
--print("Drug count decreased, current amount:", drugCount)
|
||||||
|
drugCount = drugCount - count
|
||||||
|
end
|
||||||
|
|
||||||
|
local drugThreadRun = false
|
||||||
|
function runDrugThread()
|
||||||
|
if drugThreadRun == true then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
drugThreadRun = true
|
||||||
|
end
|
||||||
|
--print("Drug Thread triggered")
|
||||||
|
while drugCount > 0 do
|
||||||
|
Wait(purgeTimer)
|
||||||
|
removeDrugCount(1)
|
||||||
|
if drugCount <= 0 then
|
||||||
|
drugThreadRun = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
exports("getDrugCount", getDrugCount)
|
||||||
|
exports("addDrugCount", addDrugCount)
|
||||||
|
exports("removeDrugCount", removeDrugCount)
|
||||||
|
|
||||||
|
|
||||||
|
-- EFFECT Management
|
||||||
|
|
||||||
|
local currentDrugEffect = ""
|
||||||
|
local drugEffect = ""
|
||||||
|
function startDrugEffect(data, level)
|
||||||
|
local Ped = PlayerPedId()
|
||||||
|
if currentDrugEffect == level then
|
||||||
|
return
|
||||||
|
else
|
||||||
|
clearCurrentAlcoholEffect()
|
||||||
|
currentDrugEffect = level
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Start effect using sent data
|
||||||
|
if drugEffect ~= data.effect then
|
||||||
|
drugEffect = data.effect
|
||||||
|
AnimpostfxPlay(data.effect, 0, true)
|
||||||
|
if data.movement then
|
||||||
|
RequestAnimSet(data.movement)
|
||||||
|
while not HasAnimSetLoaded(data.movement) do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
SetPedMovementClipset(Ped, data.movement, 3.0)
|
||||||
|
end
|
||||||
|
SetPedCanRagdoll(Ped, true)
|
||||||
|
if data.camShake then
|
||||||
|
ShakeGameplayCam("DRUNK_SHAKE", data.camShake)
|
||||||
|
end
|
||||||
|
SetTimecycleModifier("Drunk")
|
||||||
|
SetPedMotionBlur(Ped, true)
|
||||||
|
SetPedIsDrunk(Ped, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,266 @@
|
|||||||
|
local notifications = {}
|
||||||
|
local spacing = 60 -- vertical spacing between notifications
|
||||||
|
local activeDrawing = false -- flag for drawing loop state
|
||||||
|
|
||||||
|
local function loadTextureDict(dict)
|
||||||
|
if not HasStreamedTextureDictLoaded(dict) then
|
||||||
|
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--- GTA Section ---
|
||||||
|
|
||||||
|
local notifTypes = {
|
||||||
|
success = "✔️",
|
||||||
|
error = "❌",
|
||||||
|
warning = "⚠️",
|
||||||
|
police = "🚓",
|
||||||
|
ambulance = "🚑",
|
||||||
|
}
|
||||||
|
|
||||||
|
function gtaNotify(title, message, emoji, src)
|
||||||
|
local notif = {
|
||||||
|
title = title,
|
||||||
|
message = message,
|
||||||
|
emoji = notifTypes[emoji] or "❔",
|
||||||
|
state = "enter",
|
||||||
|
startTime = GetGameTimer(),
|
||||||
|
progress = 0,
|
||||||
|
holdTime = 8000,
|
||||||
|
slideDuration = 100,
|
||||||
|
currentOffset = 0,
|
||||||
|
}
|
||||||
|
table.insert(notifications, notif)
|
||||||
|
|
||||||
|
-- Activate drawing loop if not already running
|
||||||
|
-- This allows it to be silent until the first notifcation is called, then the loop starts
|
||||||
|
if not activeDrawing then
|
||||||
|
activeDrawing = true
|
||||||
|
StartGTADrawingLoop()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Drawing loop as a separate controlled thread
|
||||||
|
function StartGTADrawingLoop()
|
||||||
|
--loadTextureDict("timerbars")
|
||||||
|
CreateThread(function()
|
||||||
|
while #notifications > 0 do
|
||||||
|
local currentTime = GetGameTimer()
|
||||||
|
|
||||||
|
-- Update notifications vertical offset
|
||||||
|
for i, notif in ipairs(notifications) do
|
||||||
|
local target = (#notifications - i) * spacing
|
||||||
|
notif.currentOffset += (target - notif.currentOffset) * 0.1
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = #notifications, 1, -1 do
|
||||||
|
local notif = notifications[i]
|
||||||
|
|
||||||
|
if notif.state == "enter" then
|
||||||
|
local elapsed = currentTime - notif.startTime
|
||||||
|
notif.progress = math.min(elapsed / notif.slideDuration, 1.0)
|
||||||
|
if notif.progress >= 1.0 then
|
||||||
|
notif.state = "hold"
|
||||||
|
notif.holdStart = currentTime
|
||||||
|
end
|
||||||
|
elseif notif.state == "hold" then
|
||||||
|
if currentTime - notif.holdStart >= notif.holdTime then
|
||||||
|
notif.state = "exit"
|
||||||
|
notif.exitStart = currentTime
|
||||||
|
end
|
||||||
|
elseif notif.state == "exit" then
|
||||||
|
local elapsed = currentTime - notif.exitStart
|
||||||
|
notif.progress = 1.0 - math.min(elapsed / notif.slideDuration, 1.0)
|
||||||
|
if notif.progress <= 0 then
|
||||||
|
table.remove(notifications, i)
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local startX, targetX = 1.0, 0.8
|
||||||
|
local posX = startX - (startX - targetX) * notif.progress
|
||||||
|
local posY = 0.05 + (notif.currentOffset / 1080)
|
||||||
|
|
||||||
|
-- Background sprite
|
||||||
|
DrawSprite("timerbars", "all_black_bg", posX + 0.11, posY + 0.025, 0.2, 0.053, 0.0, 255, 255, 255, 255)
|
||||||
|
|
||||||
|
-- Title text
|
||||||
|
local moveMessage = false
|
||||||
|
if not notif.title or notif.title == "" then
|
||||||
|
moveMessage = true
|
||||||
|
else
|
||||||
|
drawGTANotiText(8, 0.4, vec2(0.75, 0.975), notif.title, vec2(posX, posY))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Message text
|
||||||
|
drawGTANotiText(4, 0.3, vec2(0.75, 0.975), notif.message, vec2(posX, posY + (moveMessage and 0.015 or 0.03)))
|
||||||
|
|
||||||
|
-- Emoji
|
||||||
|
drawGTANotiText(0, 0.3, vec2(0.75, 0.995), notif.emoji, vec2(posX, posY + 0.015))
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
activeDrawing = false -- No notifications left, pause drawing
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function drawGTANotiText(font, scale, wrap, string, pos)
|
||||||
|
SetTextFont(font)
|
||||||
|
SetTextScale(scale, scale)
|
||||||
|
SetTextWrap(wrap.x, wrap.y)
|
||||||
|
SetTextJustification(2)
|
||||||
|
SetTextColour(255, 255, 255, 255)
|
||||||
|
SetTextOutline()
|
||||||
|
SetTextEntry("STRING")
|
||||||
|
AddTextComponentString(string)
|
||||||
|
DrawText(pos.x, pos.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
------------------
|
||||||
|
-- REDM SECTION --
|
||||||
|
------------------
|
||||||
|
---
|
||||||
|
---
|
||||||
|
local REDMnotifTypes = {
|
||||||
|
success = vec3(0, 150, 0),
|
||||||
|
error = vec3(150, 0, 0),
|
||||||
|
warning = vec3(0, 150, 150),
|
||||||
|
police = vec3(0, 0, 150),
|
||||||
|
ambulance = vec3(0, 0, 150),
|
||||||
|
}
|
||||||
|
|
||||||
|
function redNotify(title, message, style)
|
||||||
|
local notif = {
|
||||||
|
title = title and CreateVarString(10, "LITERAL_STRING", title) or nil,
|
||||||
|
message = message and CreateVarString(10, "LITERAL_STRING", message) or nil,
|
||||||
|
state = "enter",
|
||||||
|
startTime = GetGameTimer(),
|
||||||
|
style = REDMnotifTypes[style] or vec3(0, 0, 0),
|
||||||
|
progress = 0,
|
||||||
|
holdTime = 8000,
|
||||||
|
slideDuration = 100,
|
||||||
|
currentOffset = 0,
|
||||||
|
}
|
||||||
|
table.insert(notifications, notif)
|
||||||
|
|
||||||
|
-- Activate drawing loop if not already running
|
||||||
|
if not activeDrawing then
|
||||||
|
activeDrawing = true
|
||||||
|
StartREDMDrawingLoop()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Drawing loop as a separate controlled thread
|
||||||
|
function StartREDMDrawingLoop()
|
||||||
|
CreateThread(function()
|
||||||
|
loadTextureDict("generic_textures")
|
||||||
|
while #notifications > 0 do
|
||||||
|
local currentTime = GetGameTimer()
|
||||||
|
|
||||||
|
-- Update notifications vertical offset
|
||||||
|
for i, notif in ipairs(notifications) do
|
||||||
|
local target = (#notifications - i) * spacing
|
||||||
|
notif.currentOffset += (target - notif.currentOffset) * 0.1
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = #notifications, 1, -1 do
|
||||||
|
local notif = notifications[i]
|
||||||
|
|
||||||
|
if notif.state == "enter" then
|
||||||
|
local elapsed = currentTime - notif.startTime
|
||||||
|
notif.progress = math.min(elapsed / notif.slideDuration, 1.0)
|
||||||
|
if notif.progress >= 1.0 then
|
||||||
|
notif.state = "hold"
|
||||||
|
notif.holdStart = currentTime
|
||||||
|
end
|
||||||
|
elseif notif.state == "hold" then
|
||||||
|
if currentTime - notif.holdStart >= notif.holdTime then
|
||||||
|
notif.state = "exit"
|
||||||
|
notif.exitStart = currentTime
|
||||||
|
end
|
||||||
|
elseif notif.state == "exit" then
|
||||||
|
local elapsed = currentTime - notif.exitStart
|
||||||
|
notif.progress = 1.0 - math.min(elapsed / notif.slideDuration, 1.0)
|
||||||
|
if notif.progress <= 0 then
|
||||||
|
table.remove(notifications, i)
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local startX, targetX = 1.0, 0.8
|
||||||
|
local posX = startX - (startX - targetX) * notif.progress
|
||||||
|
local posY = 0.05 + (notif.currentOffset / 1080)
|
||||||
|
|
||||||
|
-- Background sprite
|
||||||
|
DrawSprite("generic_textures", "inkroller_1a", posX + 0.09, posY + 0.025, 0.2, 0.053, 180.0, notif.style.x, notif.style.y, notif.style.z, 150)
|
||||||
|
|
||||||
|
-- Title text
|
||||||
|
local moveMessage = false
|
||||||
|
if not notif.title or notif.title == "" then
|
||||||
|
moveMessage = true
|
||||||
|
else
|
||||||
|
drawREDMNotiText(1, 0.4, vec2(0.75, 0.975), notif.title, vec2(posX, posY))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Message text
|
||||||
|
drawREDMNotiText(6, 0.3, vec2(0.75, 0.975), notif.message, vec2(posX, posY + (moveMessage and 0.0145 or 0.025)))
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
Wait(0)
|
||||||
|
end
|
||||||
|
activeDrawing = false -- No notifications left, pause drawing
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function drawREDMNotiText(font, scale, wrap, string, pos)
|
||||||
|
SetTextFontForCurrentCommand(font)
|
||||||
|
SetTextScale(scale, scale)
|
||||||
|
SetTextColor(255, 255, 255, 255)
|
||||||
|
SetTextWrap(wrap.x, wrap.y)
|
||||||
|
SetTextJustification(2)
|
||||||
|
SetTextDropshadow(1, 0, 0, 0, 200)
|
||||||
|
DisplayText(string, pos.x, pos.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Exports --
|
||||||
|
-- Example:
|
||||||
|
-- TriggerEvent("jim_bridge:Notify", nil, "Short test message", "success")
|
||||||
|
--
|
||||||
|
-- exports["jim_bridge"]:Notify(nil, "Short test message", "success")
|
||||||
|
--
|
||||||
|
|
||||||
|
RegisterNetEvent("jim_bridge:RedNotify", redNotify)
|
||||||
|
RegisterNetEvent("jim_bridge:GTANotify", gtaNotify)
|
||||||
|
|
||||||
|
RegisterNetEvent("jim_bridge:Notify", function(...)
|
||||||
|
if GetCurrentGameName() == "rdr3" then
|
||||||
|
redNotify(...)
|
||||||
|
else
|
||||||
|
gtaNotify(...)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function Notify(...)
|
||||||
|
if GetCurrentGameName() == "rdr3" then
|
||||||
|
redNotify(...)
|
||||||
|
else
|
||||||
|
gtaNotify(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
exports("Notify", Notify)
|
||||||
|
|
||||||
|
-- Example testing notifications (can remove after testing)
|
||||||
|
--CreateThread(function()
|
||||||
|
-- TriggerEvent("jim_bridge:Notify", nil, "Short test message", "success")
|
||||||
|
-- Wait(3000)
|
||||||
|
-- TriggerEvent("jim_bridge:Notify", "Error!", "This is a longer example message to show stacking.", "error")
|
||||||
|
-- Wait(3000)
|
||||||
|
-- TriggerEvent("jim_bridge:Notify", "Test!", "This notification has to 'type' set.", nil )
|
||||||
|
-- Wait(4000)
|
||||||
|
--end)
|
||||||
@@ -0,0 +1,345 @@
|
|||||||
|
local inProgress = false
|
||||||
|
|
||||||
|
local function loadTextureDict(dict)
|
||||||
|
if not HasStreamedTextureDictLoaded(dict) then
|
||||||
|
while not HasStreamedTextureDictLoaded(dict) do RequestStreamedTextureDict(dict) Wait(5) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function loadAnimDict(animDict)
|
||||||
|
if not DoesAnimDictExist(animDict) then
|
||||||
|
print("^6Bridge^7: ^1ERROR^7: ^2Anim Dictionary^7 - '^6"..animDict.."^7' ^2does not exist in server") return
|
||||||
|
else
|
||||||
|
while not HasAnimDictLoaded(animDict) do RequestAnimDict(animDict) Wait(5) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function playAnim(animDict, animName, duration, flag, ped, speed)
|
||||||
|
loadAnimDict(animDict)
|
||||||
|
TaskPlayAnim(ped and ped or PlayerPedId(), animDict, animName, speed or 8.0, speed or -8.0, duration or 30000, flag or 50, 1, false, false, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stopAnim(animDict, animName, ped)
|
||||||
|
StopAnimTask(ped or PlayerPedId(), animDict, animName, 0.5)
|
||||||
|
StopAnimTask(ped or PlayerPedId(), animName, animDict, 0.5)
|
||||||
|
RemoveAnimDict(animDict)
|
||||||
|
end
|
||||||
|
|
||||||
|
function redProgressBar(data)
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
|
||||||
|
local result = nil
|
||||||
|
|
||||||
|
loadTextureDict("generic_textures")
|
||||||
|
if inProgress then return false end
|
||||||
|
inProgress = true
|
||||||
|
local wait = debugMode and 1000 or data.time
|
||||||
|
local endTime = GetGameTimer() + wait
|
||||||
|
|
||||||
|
LocalPlayer.state:set("inv_busy", true, true)
|
||||||
|
TriggerEvent('inventory:client:busy:status', true)
|
||||||
|
TriggerEvent('canUseInventoryAndHotbar:toggle', not true)
|
||||||
|
|
||||||
|
-- Setup Animation/Task if specified
|
||||||
|
if data.dict then
|
||||||
|
playAnim(data.dict, data.anim, -1, data.flag or 32)
|
||||||
|
elseif data.task then
|
||||||
|
TaskStartScenarioInPlace(ped, data.task, -1, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Progress bar rendering loop
|
||||||
|
CreateThread(function()
|
||||||
|
while GetGameTimer() < endTime and inProgress do
|
||||||
|
Wait(0)
|
||||||
|
local elapsed = GetGameTimer()
|
||||||
|
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
|
||||||
|
if percentage < 0 then percentage = 0 end
|
||||||
|
if percentage > 100 then percentage = 100 end
|
||||||
|
|
||||||
|
-- Draw your segmented progress bar
|
||||||
|
ShowRedProgressBar(percentage, data.label, ("%.0f%%"):format(percentage))
|
||||||
|
|
||||||
|
-- Controls to disable during progress
|
||||||
|
if data.disableMouse then
|
||||||
|
DisableControlAction(0, `INPUT_LOOK_LR`, true)
|
||||||
|
DisableControlAction(0, `INPUT_LOOK_UD`, true)
|
||||||
|
DisableControlAction(0, `INPUT_VEH_MOUSE_CONTROL_OVERRIDE`, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.disableMovement then
|
||||||
|
DisableControlAction(0, `INPUT_MOVE_LR`, true)
|
||||||
|
DisableControlAction(0, `INPUT_MOVE_UD`, true)
|
||||||
|
DisableControlAction(0, `INPUT_DUCK`, true)
|
||||||
|
DisableControlAction(0, `INPUT_SPRINT`, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.disableCarMovement then
|
||||||
|
DisableControlAction(0, `INPUT_VEH_MOVE_LEFT_ONLY`, true)
|
||||||
|
DisableControlAction(0, `INPUT_VEH_MOVE_RIGHT_ONLY`, true)
|
||||||
|
DisableControlAction(0, `INPUT_VEH_ACCELERATE`, true)
|
||||||
|
DisableControlAction(0, `INPUT_VEH_BRAKE`, true)
|
||||||
|
DisableControlAction(0, `INPUT_VEH_EXIT`, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.disableCombat then
|
||||||
|
DisablePlayerFiring(PlayerId(), true)
|
||||||
|
DisableControlAction(0, `INPUT_ATTACK`, true)
|
||||||
|
DisableControlAction(0, `INPUT_AIM`, true)
|
||||||
|
DisableControlAction(0, `INPUT_OPEN_WHEEL_MENU`, true)
|
||||||
|
DisableControlAction(0, `INPUT_DETONATE`, true)
|
||||||
|
DisableControlAction(0, `INPUT_THROW_GRENADE`, true)
|
||||||
|
DisableControlAction(0, `INPUT_MELEE_ATTACK`, true)
|
||||||
|
DisableControlAction(0, `INPUT_MELEE_GRAPPLE_ATTACK`, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.cancel and (
|
||||||
|
IsControlJustReleased(0, `INPUT_FRONTEND_RS`) or
|
||||||
|
IsControlJustReleased(0, `INPUT_SWITCH_SHOULDER`) or
|
||||||
|
IsControlJustReleased(0, `INPUT_GAME_MENU_TAB_RIGHT_SECONDARY`) or
|
||||||
|
IsControlJustReleased(0, `INPUT_FRONTEND_CANCEL`) or
|
||||||
|
IsControlJustReleased(0, `INPUT_GAME_MENU_CANCEL`) or
|
||||||
|
IsControlJustReleased(0, `INPUT_FRONTEND_RRIGHT`) or
|
||||||
|
IsControlJustReleased(0, `INPUT_QUIT`) or
|
||||||
|
IsControlJustReleased(0, `INPUT_MINIGAME_QUIT`)
|
||||||
|
) then
|
||||||
|
inProgress = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Wait for completion or cancel
|
||||||
|
while GetGameTimer() < endTime and inProgress do
|
||||||
|
Wait(100)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Cleanup animations/tasks
|
||||||
|
if data.dict then
|
||||||
|
stopAnim(data.dict, data.anim, ped)
|
||||||
|
end
|
||||||
|
if data.task then
|
||||||
|
ClearPedTasks(ped)
|
||||||
|
end
|
||||||
|
|
||||||
|
result = inProgress
|
||||||
|
inProgress = false
|
||||||
|
|
||||||
|
while result == nil do Wait(10) end
|
||||||
|
|
||||||
|
-- Cleanup
|
||||||
|
FreezeEntityPosition(ped, false)
|
||||||
|
|
||||||
|
LocalPlayer.state:set("inv_busy", false, true)
|
||||||
|
TriggerEvent('inventory:client:busy:status', false)
|
||||||
|
TriggerEvent('canUseInventoryAndHotbar:toggle', not false)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function ShowRedProgressBar(percentage, title, level)
|
||||||
|
local loc = vec2(0.40, 0.90)
|
||||||
|
local size = vec2(0.3, 0.03)
|
||||||
|
local tickCount = 10 -- visually split into 10 segments (9 inner lines)
|
||||||
|
local barHeight = size.y / 3.4
|
||||||
|
local barWidth = 0.21 -- tuned to your existing layout
|
||||||
|
local barLeft = (loc.x - size.x / 4) + 0.075
|
||||||
|
local barCenter = barLeft + barWidth / 2
|
||||||
|
local lineW = 0.001
|
||||||
|
local lineH = barHeight + 0.00
|
||||||
|
|
||||||
|
-- Background plate
|
||||||
|
DrawSprite("generic_textures", "inkroller_1a", loc.x + 0.1, loc.y - 0.01, 0.25, 0.07, 180.0, 0, 0, 0, 200)
|
||||||
|
|
||||||
|
-- Title (left)
|
||||||
|
SetTextFontForCurrentCommand(6)
|
||||||
|
SetTextScale(0.35, 0.35)
|
||||||
|
SetTextColor(255, 255, 255, 255)
|
||||||
|
SetTextDropshadow(1, 0, 0, 0, 200)
|
||||||
|
BgDisplayText(title, loc.x - size.x / 4 + 0.074, loc.y - 0.034)
|
||||||
|
|
||||||
|
-- Percentage (right)
|
||||||
|
SetTextFontForCurrentCommand(1)
|
||||||
|
SetTextScale(0.35, 0.35)
|
||||||
|
SetTextColor(255, 255, 255, 255)
|
||||||
|
SetTextDropshadow(1, 0, 0, 0, 200)
|
||||||
|
BgDisplayText(level, loc.x - size.x / 4 + 0.246, loc.y - 0.030)
|
||||||
|
|
||||||
|
-- Track (bg)
|
||||||
|
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
|
||||||
|
|
||||||
|
-- Fill
|
||||||
|
local fillWidth = barWidth * (percentage / 100.0)
|
||||||
|
if fillWidth > 0.0 then
|
||||||
|
local fillCenter = barLeft + (fillWidth / 2.0)
|
||||||
|
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 255, 0, 0, 200) -- red fill
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Tick lines (9 inner lines for 10 segments)
|
||||||
|
for i = 1, (tickCount - 1) do
|
||||||
|
local x = barLeft + (barWidth * (i / tickCount))
|
||||||
|
DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function gtaProgressBar(data)
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
|
||||||
|
local result = nil
|
||||||
|
|
||||||
|
loadTextureDict("timerbars")
|
||||||
|
if inProgress then return false end
|
||||||
|
inProgress = true
|
||||||
|
local wait = debugMode and 1000 or data.time
|
||||||
|
local endTime = GetGameTimer() + wait
|
||||||
|
|
||||||
|
-- Setup Animation/Task if specified
|
||||||
|
if data.dict then
|
||||||
|
playAnim(data.dict, data.anim, -1, data.flag or 32)
|
||||||
|
elseif data.task then
|
||||||
|
TaskStartScenarioInPlace(ped, data.task, -1, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Progress bar rendering loop
|
||||||
|
CreateThread(function()
|
||||||
|
while GetGameTimer() < endTime and inProgress do
|
||||||
|
Wait(0)
|
||||||
|
local elapsed = GetGameTimer()
|
||||||
|
local percentage = ((elapsed - (endTime - wait)) / wait) * 100
|
||||||
|
|
||||||
|
if percentage < 0 then percentage = 0 end
|
||||||
|
if percentage > 100 then percentage = 100 end
|
||||||
|
|
||||||
|
-- Draw your segmented progress bar
|
||||||
|
ShowGTAProgressBar(percentage, data.label, ("%.0f%%"):format(percentage))
|
||||||
|
|
||||||
|
-- Controls to disable during progress
|
||||||
|
DisablePlayerFiring(PlayerId(), true)
|
||||||
|
DisableControlAction(0, 25, true) -- Disable aim
|
||||||
|
DisableControlAction(0, 21, true) -- Disable sprint
|
||||||
|
DisableControlAction(0, 30, true) -- Disable move left/right
|
||||||
|
DisableControlAction(0, 31, true) -- Disable move forward/back
|
||||||
|
DisableControlAction(0, 36, true) -- Disable stealth
|
||||||
|
|
||||||
|
if data.cancel and (IsControlJustReleased(0, 202) or IsControlJustReleased(0, 177) or IsControlJustReleased(0, 73)) then
|
||||||
|
inProgress = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result = inProgress
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
-- Wait for completion or cancel
|
||||||
|
while result == nil do Wait(10) end
|
||||||
|
inProgress = false
|
||||||
|
|
||||||
|
-- Cleanup animations/tasks
|
||||||
|
if data.dict then
|
||||||
|
stopAnim(data.dict, data.anim, ped)
|
||||||
|
end
|
||||||
|
if data.task then
|
||||||
|
ClearPedTasks(ped)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Cleanup
|
||||||
|
FreezeEntityPosition(ped, false)
|
||||||
|
|
||||||
|
LocalPlayer.state:set("inv_busy", false, true)
|
||||||
|
TriggerEvent('inventory:client:busy:status', false)
|
||||||
|
TriggerEvent('canUseInventoryAndHotbar:toggle', not false)
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function ShowGTAProgressBar(percentage, title, level)
|
||||||
|
local loc = vec2(0.37, 0.90)
|
||||||
|
local size = vec2(0.3, 0.03)
|
||||||
|
local tickCount = 10
|
||||||
|
local barHeight = size.y / 3.4
|
||||||
|
local barWidth = 0.19
|
||||||
|
local barLeft = (loc.x - size.x / 4) + 0.075
|
||||||
|
local barCenter = barLeft + barWidth / 2
|
||||||
|
local lineW = 0.001
|
||||||
|
local lineH = barHeight + 0.00
|
||||||
|
|
||||||
|
-- Background plates
|
||||||
|
DrawSprite("timerbars", "all_black_bg", loc.x + 0.028, loc.y - 0.01, 0.15, 0.07, 0.0, 255, 255, 255, 255)
|
||||||
|
DrawSprite("timerbars", "all_black_bg", loc.x + 0.170, loc.y - 0.01, 0.15, 0.07, 180.0, 255, 255, 255, 255)
|
||||||
|
|
||||||
|
-- Title (left)
|
||||||
|
SetTextFont(0)
|
||||||
|
SetTextProportional(1)
|
||||||
|
SetTextScale(0.35, 0.35)
|
||||||
|
SetTextColour(255, 255, 255, 255)
|
||||||
|
SetTextDropshadow(0, 0, 0, 0, 255)
|
||||||
|
SetTextDropShadow()
|
||||||
|
SetTextOutline()
|
||||||
|
SetTextEntry("STRING")
|
||||||
|
AddTextComponentString(title)
|
||||||
|
DrawText(loc.x - size.x / 4 + 0.074, loc.y - 0.034)
|
||||||
|
|
||||||
|
-- Percentage (right)
|
||||||
|
SetTextFont(0)
|
||||||
|
SetTextProportional(1)
|
||||||
|
SetTextScale(0.35, 0.25)
|
||||||
|
SetTextColour(255, 255, 255, 255)
|
||||||
|
SetTextEntry("STRING")
|
||||||
|
AddTextComponentString(level)
|
||||||
|
DrawText(loc.x - size.x / 4 + 0.246, loc.y - 0.030)
|
||||||
|
|
||||||
|
-- Track (bg)
|
||||||
|
DrawRect(barCenter, loc.y, barWidth, barHeight, 100, 100, 100, 255)
|
||||||
|
|
||||||
|
-- Fill
|
||||||
|
local fillWidth = barWidth * (percentage / 100.0)
|
||||||
|
if fillWidth > 0.0 then
|
||||||
|
local fillCenter = barLeft + (fillWidth / 2.0)
|
||||||
|
DrawRect(fillCenter, loc.y, fillWidth, barHeight, 93, 182, 229, 255) -- GTA blue
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Tick lines
|
||||||
|
for i = 1, (tickCount - 1) do
|
||||||
|
local x = barLeft + (barWidth * (i / tickCount))
|
||||||
|
DrawRect(x, loc.y, lineW, lineH, 0, 0, 0, 120)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function stopProgressBar() inProgress = false end
|
||||||
|
function isProgressBar() return inProgress end
|
||||||
|
|
||||||
|
function progressBar(...)
|
||||||
|
if GetCurrentGameName() == "rdr3" then
|
||||||
|
return redProgressBar(...)
|
||||||
|
else
|
||||||
|
return gtaProgressBar(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
exports("progressBar", function(...)
|
||||||
|
return progressBar(...)
|
||||||
|
end)
|
||||||
|
exports("gtaProgressBar", function(...)
|
||||||
|
return gtaProgressBar(...)
|
||||||
|
end)
|
||||||
|
exports("redProgressBar", function(...)
|
||||||
|
return redProgressBar(...)
|
||||||
|
end)
|
||||||
|
|
||||||
|
exports("stopProgressBar", stopProgressBar)
|
||||||
|
exports("isProgressBar", isProgressBar)
|
||||||
|
|
||||||
|
--RegisterCommand("testprog", function()
|
||||||
|
-- print("test")
|
||||||
|
-- if progressBar({
|
||||||
|
-- label = "Processing...",
|
||||||
|
-- time = 10000,
|
||||||
|
-- disableMovement = true,
|
||||||
|
-- --dict = "amb@world_human_hang_out_street@female_hold_arm@base",
|
||||||
|
-- --anim = "base",
|
||||||
|
-- --flag = 49,
|
||||||
|
-- cancel = true,
|
||||||
|
-- }) then
|
||||||
|
-- Notify("Success!", "Short test message", "success")
|
||||||
|
-- else
|
||||||
|
-- Notify("Error!", "This is a longer example message to show stacking.", "error")
|
||||||
|
-- end
|
||||||
|
--end)
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
local activeSkillCheck = false
|
||||||
|
|
||||||
|
function gtaSkillCheck()
|
||||||
|
if activeSkillCheck then return end
|
||||||
|
local result = false
|
||||||
|
local successes = 0
|
||||||
|
local barsRequired = 3
|
||||||
|
|
||||||
|
for bar = 1, barsRequired do
|
||||||
|
activeSkillCheck = true
|
||||||
|
local width, height = 0.2, 0.01
|
||||||
|
local x, y = 0.5, 0.8
|
||||||
|
|
||||||
|
-- Random highlighted zone
|
||||||
|
local highlightSize = math.random(10, 20) / 100
|
||||||
|
local highlightStart = math.random(10, 50) / 100
|
||||||
|
local highlightEnd = highlightStart + highlightSize
|
||||||
|
local highlightAlpha = 0
|
||||||
|
local cursorPos = 0.0
|
||||||
|
local cursorSpeed = 0.025
|
||||||
|
local movingRight = true
|
||||||
|
|
||||||
|
while activeSkillCheck do
|
||||||
|
Wait(0)
|
||||||
|
|
||||||
|
createScaleBars(x, y, width, height)
|
||||||
|
|
||||||
|
local pulse = (math.sin(GetGameTimer() / 250) + 1) / 2 -- Creates a pulsing effect
|
||||||
|
highlightAlpha = math.floor(150 + (pulse * 105)) -- Pulsing between 150 and 255 alpha
|
||||||
|
|
||||||
|
-- Draw highlighted zone (success area) with pulsing effect
|
||||||
|
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, 93, 182, 229, highlightAlpha )
|
||||||
|
-- Draw moving cursor
|
||||||
|
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
|
||||||
|
|
||||||
|
-- Move cursor
|
||||||
|
if movingRight then
|
||||||
|
cursorPos += cursorSpeed
|
||||||
|
if cursorPos >= 1.0 then movingRight = false end
|
||||||
|
else
|
||||||
|
cursorPos -= cursorSpeed
|
||||||
|
if cursorPos <= 0.0 then movingRight = true end
|
||||||
|
end
|
||||||
|
|
||||||
|
if IsControlJustPressed(0, 177) then -- Backspace to cancel
|
||||||
|
local displayTime = GetGameTimer() + 2000
|
||||||
|
PlaySoundFrontend(-1, 'Highlight_Cancel', 'DLC_HEIST_PLANNING_BOARD_SOUNDS', 1)
|
||||||
|
while GetGameTimer() < displayTime do
|
||||||
|
Wait(0)
|
||||||
|
|
||||||
|
createScaleBars(x, y, width, height)
|
||||||
|
|
||||||
|
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, 228, 52, 52, 255)
|
||||||
|
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
|
||||||
|
|
||||||
|
drawGTASuccessText(x, y, "Failed", 228, 52, 52)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check for keypress (E)
|
||||||
|
if IsControlJustPressed(0, 38) then
|
||||||
|
activeSkillCheck = false
|
||||||
|
result = cursorPos >= highlightStart and cursorPos <= highlightEnd
|
||||||
|
if result then
|
||||||
|
PlaySoundFrontend(-1, "YES", "HUD_FRONTEND_DEFAULT_SOUNDSET", true)
|
||||||
|
else
|
||||||
|
PlaySoundFrontend(-1, 'Highlight_Cancel', 'DLC_HEIST_PLANNING_BOARD_SOUNDS', 1)
|
||||||
|
end
|
||||||
|
local displayTime = GetGameTimer() + 2000
|
||||||
|
while GetGameTimer() < displayTime do
|
||||||
|
Wait(0)
|
||||||
|
|
||||||
|
createScaleBars(x, y, width, height)
|
||||||
|
|
||||||
|
-- Draw highlighted zone
|
||||||
|
DrawRect(x - width / 2 + (highlightStart + highlightSize / 2) * width, y, highlightSize * width, height+0.001, result and 93 or 228, result and 182 or 52, result and 229 or 52, 180)
|
||||||
|
|
||||||
|
-- Draw stationary cursor at result position
|
||||||
|
DrawRect(x - width / 2 + cursorPos * width, y, 0.002, height + 0.01, 255, 255, 255, 255)
|
||||||
|
-- Display result text
|
||||||
|
drawGTASuccessText(x, y, result and "Success" or "Failed", result and 114 or 228, result and 204 or 52, result and 144 or 52)
|
||||||
|
end
|
||||||
|
if result then
|
||||||
|
successes += 1
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
activeSkillCheck = false
|
||||||
|
print("^5GTAUI^7: ^2Skill Check Result^7: ^3" .. successes .. "^7/^3" .. barsRequired.."^7")
|
||||||
|
return successes == barsRequired
|
||||||
|
end
|
||||||
|
|
||||||
|
function drawGTASuccessText(x, y, text, r, g, b)
|
||||||
|
SetTextFont(8)
|
||||||
|
SetTextScale(0.45, 0.45)
|
||||||
|
SetTextColour(r, g, b, 255)
|
||||||
|
SetTextDropshadow(0, 0, 0, 0, 255)
|
||||||
|
SetTextEdge(2, 0, 0, 0, 150)
|
||||||
|
SetTextDropShadow()
|
||||||
|
SetTextOutline()
|
||||||
|
SetTextCentre(true)
|
||||||
|
SetTextEntry("STRING")
|
||||||
|
SetTextCentre(true)
|
||||||
|
SetTextEntry("STRING")
|
||||||
|
AddTextComponentString(text)
|
||||||
|
DrawText(x, y + 0.03)
|
||||||
|
end
|
||||||
|
|
||||||
|
function createScaleBars(x, y, width, height)
|
||||||
|
-- Draw background box
|
||||||
|
DrawSprite("timerbars", "all_black_bg", x - (width / 4) - 0.006, y, (width / 2) + 0.08, height + 0.04, 0.0, 255, 255, 255, 255)
|
||||||
|
DrawSprite("timerbars", "all_black_bg", x + (width / 4) + 0.006, y, (width / 2) + 0.08, height + 0.04, 180.0, 255, 255, 255, 255)
|
||||||
|
-- Draw full bar (dark background)
|
||||||
|
DrawRect(x, y, width, height, 100, 100, 100, 255)
|
||||||
|
end
|
||||||
|
|
||||||
|
exports("skillCheck", function(...)
|
||||||
|
if GetCurrentGameName() == "rdr3" then
|
||||||
|
--redSkillCheck(...)
|
||||||
|
else
|
||||||
|
gtaSkillCheck(...)
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
-- Global Key Table, defined once.
|
||||||
|
local KEY_TABLE = { 38, 29, 47, 23, 45, 159, 162, 163 }
|
||||||
|
|
||||||
|
-- Mapping of key codes to human-readable key names.
|
||||||
|
local Keys = {
|
||||||
|
[322] = "ESC", [288] = "F1", [289] = "F2", [170] = "F3", [166] = "F5",
|
||||||
|
[167] = "F6", [168] = "F7", [169] = "F8", [56] = "F9", [57] = "F10",
|
||||||
|
[243] = "~", [157] = "1", [158] = "2", [160] = "3", [164] = "4", [165] = "5",
|
||||||
|
[159] = "6", [161] = "7", [162] = "8", [163] = "9", [84] = "-", [83] = "=",
|
||||||
|
[177] = "BACKSPACE", [37] = "TAB",
|
||||||
|
[44] = "Q", [32] = "W", [38] = "E", [45] = "R", [245] = "T", [246] = "Y",
|
||||||
|
[303] = "U", [199] = "P",
|
||||||
|
[39] = "[", [40] = "]", [18] = "ENTER", [137] = "CAPS",
|
||||||
|
[34] = "A", [8] = "S", [9] = "D", [23] = "F", [47] = "G",
|
||||||
|
[74] = "H", [311] = "K", [182] = "L", [21] = "LEFTSHIFT",
|
||||||
|
[20] = "Z", [73] = "X", [26] = "C", [0] = "V", [29] = "B", [249] = "N",
|
||||||
|
[244] = "M", [82] = ",", [81] = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- ===== Ownership + indexes =====
|
||||||
|
-- TextTargets: key -> target data (coords/entity/models/options/etc.)
|
||||||
|
local TextTargets = {}
|
||||||
|
-- targetEntities kept for parity (not strictly required)
|
||||||
|
local targetEntities = {}
|
||||||
|
|
||||||
|
-- registry: key -> owner, owner -> set(keys)
|
||||||
|
local TargetRegistry = { byKey = {}, byResource = {} }
|
||||||
|
|
||||||
|
local function getOwnerResource()
|
||||||
|
return GetInvokingResource() or GetCurrentResourceName() or "unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function registerTarget(owner, key)
|
||||||
|
-- move key across owners if replacing
|
||||||
|
local prev = TargetRegistry.byKey[key]
|
||||||
|
if prev and TargetRegistry.byResource[prev] then
|
||||||
|
TargetRegistry.byResource[prev][key] = nil
|
||||||
|
end
|
||||||
|
TargetRegistry.byKey[key] = owner
|
||||||
|
TargetRegistry.byResource[owner] = TargetRegistry.byResource[owner] or {}
|
||||||
|
TargetRegistry.byResource[owner][key] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function removeTargetKey(key, reason)
|
||||||
|
local owner = TargetRegistry.byKey[key]
|
||||||
|
if TextTargets[key] then
|
||||||
|
TextTargets[key] = nil
|
||||||
|
-- print("^6Bridge^7:^5 Target^7: ^2Removed target '%s'%s", key, reason and (" ("..reason..")") or "")
|
||||||
|
end
|
||||||
|
if owner then
|
||||||
|
if TargetRegistry.byResource[owner] then
|
||||||
|
TargetRegistry.byResource[owner][key] = nil
|
||||||
|
end
|
||||||
|
TargetRegistry.byKey[key] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
AddEventHandler("onResourceStop", function(res)
|
||||||
|
local owned = TargetRegistry.byResource[res]
|
||||||
|
if not owned then return end
|
||||||
|
local cnt = 0
|
||||||
|
for key in pairs(owned) do
|
||||||
|
removeTargetKey(key, "resource stopped: "..res)
|
||||||
|
cnt = cnt + 1
|
||||||
|
end
|
||||||
|
TargetRegistry.byResource[res] = nil
|
||||||
|
-- print("^6Bridge^7:^5 Target^7: ^2Cleared "..cnt.." target(s) from '"..res.."'")
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ===== Helpers =====
|
||||||
|
local function vecKey(v)
|
||||||
|
-- stable rounded coord string for entity dedupe when name isn't provided
|
||||||
|
return ("%.3f,%.3f,%.3f"):format(v.x, v.y, v.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bakeButtons(opts)
|
||||||
|
local tempText = {}
|
||||||
|
for i = 1, #opts do
|
||||||
|
opts[i].key = KEY_TABLE[i]
|
||||||
|
tempText[#tempText + 1] =
|
||||||
|
(" ~b~[~w~%s~b~] ~w~%s"):format(Keys[opts[i].key] or ("K"..opts[i].key), opts[i].label or ("Option "..i))
|
||||||
|
end
|
||||||
|
return tempText
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Update cached text blob
|
||||||
|
local function updateCachedText(target)
|
||||||
|
target.text = table.concat(target.buttontext, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ===== Public API: Create targets =====
|
||||||
|
-- ENTITY: createEntityTarget(entity, opts, dist, nameOpt?)
|
||||||
|
function createEntityTarget(entity, opts, dist, name)
|
||||||
|
startTargetLoop()
|
||||||
|
|
||||||
|
if not entity or entity == 0 then return end
|
||||||
|
targetEntities[#targetEntities + 1] = entity
|
||||||
|
|
||||||
|
local owner = getOwnerResource()
|
||||||
|
local coords = GetEntityCoords(entity)
|
||||||
|
local key = tostring(name) or ("entity@" .. vecKey(coords))
|
||||||
|
|
||||||
|
-- Always overwrite on same key
|
||||||
|
local buttontext = bakeButtons(opts)
|
||||||
|
TextTargets[key] = {
|
||||||
|
_key = key,
|
||||||
|
_type = "entity",
|
||||||
|
_owner = owner,
|
||||||
|
entity = entity,
|
||||||
|
coords = vec3(coords.x, coords.y, coords.z),
|
||||||
|
buttontext = buttontext,
|
||||||
|
options = opts,
|
||||||
|
dist = dist,
|
||||||
|
}
|
||||||
|
updateCachedText(TextTargets[key])
|
||||||
|
registerTarget(owner, key)
|
||||||
|
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ENTITY target '"..key.."' by '"..owner.."' @ "..formatCoord(coords))
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ZONE: createZoneTarget(data, opts, dist)
|
||||||
|
-- Expect data[1] = id/name, data[2] = vec3 coords (as in your original)
|
||||||
|
function createZoneTarget(data, opts, dist)
|
||||||
|
startTargetLoop()
|
||||||
|
|
||||||
|
local owner = getOwnerResource()
|
||||||
|
local zname = tostring(data[1] or ("zone@"..vecKey(data[2] or vec3(0,0,0))))
|
||||||
|
local coords = data[2]
|
||||||
|
|
||||||
|
local buttontext = bakeButtons(opts)
|
||||||
|
TextTargets[zname] = {
|
||||||
|
_key = zname,
|
||||||
|
_type = "zone",
|
||||||
|
_owner = owner,
|
||||||
|
coords = coords,
|
||||||
|
buttontext = buttontext,
|
||||||
|
options = opts,
|
||||||
|
dist = dist,
|
||||||
|
}
|
||||||
|
updateCachedText(TextTargets[zname])
|
||||||
|
registerTarget(owner, zname)
|
||||||
|
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated ZONE target '"..zname.."' by '"..owner.."' @ "..formatCoord(coords))
|
||||||
|
return zname
|
||||||
|
end
|
||||||
|
|
||||||
|
-- MODEL: createModelTarget(models, opts, dist, nameOpt?)
|
||||||
|
function createModelTarget(models, opts, dist, name)
|
||||||
|
startTargetLoop()
|
||||||
|
|
||||||
|
local owner = getOwnerResource()
|
||||||
|
if type(models) ~= "table" then models = { models } end
|
||||||
|
|
||||||
|
local key
|
||||||
|
if name then
|
||||||
|
key = tostring(name)
|
||||||
|
else
|
||||||
|
local parts = {}
|
||||||
|
for i, m in ipairs(models) do parts[i] = tostring(m) end
|
||||||
|
key = "model_" .. table.concat(parts, "_")
|
||||||
|
end
|
||||||
|
|
||||||
|
local buttontext = bakeButtons(opts)
|
||||||
|
TextTargets[key] = {
|
||||||
|
_key = key,
|
||||||
|
_type = "model",
|
||||||
|
_owner = owner,
|
||||||
|
models = models,
|
||||||
|
buttontext = buttontext,
|
||||||
|
options = opts,
|
||||||
|
dist = dist,
|
||||||
|
coords = vec3(0, 0, 0), -- will be updated by the refresher
|
||||||
|
}
|
||||||
|
updateCachedText(TextTargets[key])
|
||||||
|
registerTarget(owner, key)
|
||||||
|
-- print("^6Bridge^7:^5 Target^7: ^2Added/Updated MODEL target '"..key.."' by '"..owner.."' (models: "..table.concat(models, ",")..")")
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ===== Public API: Remove targets =====
|
||||||
|
-- Entity removal accepts entity handle or key string.
|
||||||
|
function removeEntityTarget(entityOrKey)
|
||||||
|
local key = nil
|
||||||
|
if type(entityOrKey) == "string" then
|
||||||
|
key = entityOrKey
|
||||||
|
elseif type(entityOrKey) == "number" then
|
||||||
|
for k, t in pairs(TextTargets) do
|
||||||
|
if t._type == "entity" and t.entity == entityOrKey then key = k break end
|
||||||
|
end
|
||||||
|
if not key then
|
||||||
|
-- Fallback: try coord-key match
|
||||||
|
local c = GetEntityCoords(entityOrKey)
|
||||||
|
local guess = "entity@"..vecKey(c)
|
||||||
|
if TextTargets[guess] then key = guess end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if key then removeTargetKey(key, "removeEntityTarget") end
|
||||||
|
end
|
||||||
|
|
||||||
|
function removeZoneTarget(key)
|
||||||
|
if not key then return end
|
||||||
|
removeTargetKey(key, "removeZoneTarget")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- For models, pass the returned key from createModelTarget (recommended).
|
||||||
|
function removeModelTarget(key)
|
||||||
|
if not key then return end
|
||||||
|
removeTargetKey(key, "removeModelTarget")
|
||||||
|
end
|
||||||
|
|
||||||
|
exports("createEntityTarget", createEntityTarget)
|
||||||
|
exports("createZoneTarget", createZoneTarget)
|
||||||
|
exports("createModelTarget", createModelTarget)
|
||||||
|
|
||||||
|
exports("removeEntityTarget", removeEntityTarget)
|
||||||
|
exports("removeZoneTarget", removeZoneTarget)
|
||||||
|
exports("removeModelTarget", removeModelTarget)
|
||||||
|
|
||||||
|
-------------------------------------------------------------
|
||||||
|
-- Fallback: DrawText3D Targets (Experimental)
|
||||||
|
-------------------------------------------------------------
|
||||||
|
local started = false
|
||||||
|
function startTargetLoop()
|
||||||
|
if started then return end
|
||||||
|
started = true
|
||||||
|
|
||||||
|
-- lazy include (unchanged from your original)
|
||||||
|
Config = { System = {} }
|
||||||
|
local fileLoader = assert(load(LoadResourceFile("jim_bridge", ('starter.lua')), ('@@jim_bridge/starter.lua')))
|
||||||
|
fileLoader()
|
||||||
|
|
||||||
|
-- Model Entity Refresher (kept)
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
local pedCoords = GetEntityCoords(PlayerPedId())
|
||||||
|
for _, target in pairs(TextTargets) do
|
||||||
|
if target.models then
|
||||||
|
for _, model in ipairs(target.models) do
|
||||||
|
local entity = GetClosestObjectOfType(pedCoords.x, pedCoords.y, pedCoords.z, target.dist, model, false, false, false)
|
||||||
|
if entity and entity ~= 0 then
|
||||||
|
target.entity = entity
|
||||||
|
target.coords = GetEntityCoords(entity)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Wait(3000) -- Refresh every 3s
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Main Target Loop (unchanged logic, just uses new TextTargets entries)
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
local pedCoords = GetEntityCoords(ped)
|
||||||
|
local camCoords = GetGameplayCamCoord()
|
||||||
|
local camRot = GetGameplayCamRot(2)
|
||||||
|
local camForward = RotationToDirection(camRot)
|
||||||
|
|
||||||
|
local closestTarget, closestDist, targetEntity = nil, math.huge, nil
|
||||||
|
|
||||||
|
for _, target in pairs(TextTargets) do
|
||||||
|
local coords = target.coords
|
||||||
|
if coords then
|
||||||
|
local dist = #(pedCoords - coords)
|
||||||
|
if dist <= target.dist then
|
||||||
|
local normVec = normalizeVector(coords - camCoords)
|
||||||
|
local dot = camForward.x * normVec.x + camForward.y * normVec.y + camForward.z * normVec.z
|
||||||
|
|
||||||
|
if dot > 0.5 and dist < closestDist then
|
||||||
|
closestTarget = target
|
||||||
|
closestDist = dist
|
||||||
|
targetEntity = target.entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, target in pairs(TextTargets) do
|
||||||
|
if not target.coords then goto continue end
|
||||||
|
local dist = #(pedCoords - target.coords)
|
||||||
|
if dist > target.dist then goto continue end
|
||||||
|
local isClosest = (target == closestTarget)
|
||||||
|
|
||||||
|
for i, opt in ipairs(target.options) do
|
||||||
|
if IsControlJustPressed(0, opt.key) and isClosest then
|
||||||
|
if (not target.canInteract or target.canInteract()) and
|
||||||
|
(not opt.item or hasItem(opt.item)) and
|
||||||
|
(not opt.job or hasJob(opt.job, nil)) then
|
||||||
|
if opt.onSelect then opt.onSelect(targetEntity) end
|
||||||
|
if opt.action then opt.action(targetEntity) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local baseZ, lineHeight = target.coords.z + 1.0, -0.16
|
||||||
|
local lineOffset = 0
|
||||||
|
|
||||||
|
for i, opt in ipairs(target.options) do
|
||||||
|
if (not target.canInteract or target.canInteract()) and
|
||||||
|
(not opt.item or hasItem(opt.item)) and
|
||||||
|
(not opt.job or hasJob(opt.job, nil)) then
|
||||||
|
DrawText3D(vec3(target.coords.x, target.coords.y, baseZ + lineHeight * lineOffset), target.buttontext[i], isClosest)
|
||||||
|
lineOffset = lineOffset + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
|
||||||
|
Wait(1)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function DrawText3D(coord, text, highlight)
|
||||||
|
SetTextScale(0.30, 0.30)
|
||||||
|
SetTextFont(0)
|
||||||
|
SetTextProportional(1)
|
||||||
|
SetTextColour(255, 255, 255, 215)
|
||||||
|
SetTextEntry("STRING")
|
||||||
|
SetTextCentre(true)
|
||||||
|
|
||||||
|
local totalLength = string.len(text)
|
||||||
|
local textMaxLength = 99
|
||||||
|
local txt = totalLength > textMaxLength and text:sub(1, textMaxLength) or text
|
||||||
|
AddTextComponentString(highlight and txt:gsub("%~w~", "~y~") or txt)
|
||||||
|
SetDrawOrigin(coord.x, coord.y, coord.z, 0)
|
||||||
|
DrawText(0.0, 0.0)
|
||||||
|
local count, length = GetLineCountAndMaxLength(txt)
|
||||||
|
|
||||||
|
local padding = 0.005
|
||||||
|
local heightFactor = (count / 43) + padding
|
||||||
|
local weightFactor = (length / 150) + padding
|
||||||
|
|
||||||
|
local height = (heightFactor / 2) - padding / 1
|
||||||
|
local width = (weightFactor / 2) - padding / 1
|
||||||
|
|
||||||
|
DrawRect(0.0, height, width, heightFactor, 0, 0, 0, 150)
|
||||||
|
ClearDrawOrigin()
|
||||||
|
end
|
||||||
|
|
||||||
|
function GetLineCountAndMaxLength(text)
|
||||||
|
local lineCount, maxLength = 0, 0
|
||||||
|
for line in text:gmatch("[^\n]+") do
|
||||||
|
lineCount = lineCount + 1
|
||||||
|
local lineLength = string.len(line)
|
||||||
|
if lineLength > maxLength then
|
||||||
|
maxLength = lineLength
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if lineCount == 0 then lineCount = 1 end
|
||||||
|
return lineCount, maxLength
|
||||||
|
end
|
||||||
|
|
||||||
|
function RotationToDirection(rot)
|
||||||
|
local adjust = math.pi / 180
|
||||||
|
return vec3(
|
||||||
|
-math.sin(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
|
||||||
|
math.cos(adjust * rot.z) * math.abs(math.cos(adjust * rot.x)),
|
||||||
|
math.sin(adjust * rot.x)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function normalizeVector(vec)
|
||||||
|
local len = math.sqrt(vec.x^2 + vec.y^2 + vec.z^2)
|
||||||
|
if len ~= 0 then
|
||||||
|
return vec3(vec.x / len, vec.y / len, vec.z / len)
|
||||||
|
else
|
||||||
|
return vec3(0, 0, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
2.1.09
|
||||||
|
|
||||||
|
- Add 100 wait for stopCam to reduce slow movement
|
||||||
|
- Add support for rcore_clothing in openClothing() - Thanks desypher
|
||||||
|
- Add hunger and thirst calls to getPlayer() - Thanks oosayeroo
|
||||||
|
- Add support for tgg-billing - Thanks GhostInLowGear
|
||||||
|
- Fix hasJob() checking players who haven't logged in yet
|
||||||
|
- Add exploit possibility for removeItem with minus numbers
|
||||||
|
- Fix movement being allowed when crafting
|
||||||
|
- Make distExploitCheck() more verbose
|
||||||
|
- CodeM slot fix, force to number (because numbers are apparently strings)
|
||||||
|
- ESX change to check statebag for hunger and thirst info
|
||||||
|
|
||||||
|
https://github.com/jimathy/jim_bridge/releases/latest
|
||||||
-1009
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user