diff --git a/CoreRT.dll b/CoreRT.dll
index 324701aff..a585e8093 100644
Binary files a/CoreRT.dll and b/CoreRT.dll differ
diff --git a/FXServer.exe b/FXServer.exe
index c20987a1d..ed0fae145 100644
Binary files a/FXServer.exe and b/FXServer.exe differ
diff --git a/botan.dll b/botan.dll
index 398037f1e..f0de0be3d 100644
Binary files a/botan.dll and b/botan.dll differ
diff --git a/cfx_curl_x86_64.dll b/cfx_curl_x86_64.dll
index 5477a2e34..da1bd6d79 100644
Binary files a/cfx_curl_x86_64.dll and b/cfx_curl_x86_64.dll differ
diff --git a/citizen-devtools.dll b/citizen-devtools.dll
index 28d1a8c0d..45fc8950f 100644
Binary files a/citizen-devtools.dll and b/citizen-devtools.dll differ
diff --git a/citizen-resources-core.dll b/citizen-resources-core.dll
index 0fe54cfe5..989706894 100644
Binary files a/citizen-resources-core.dll and b/citizen-resources-core.dll differ
diff --git a/citizen-resources-metadata-lua.dll b/citizen-resources-metadata-lua.dll
index 6ea0966d0..71025106c 100644
Binary files a/citizen-resources-metadata-lua.dll and b/citizen-resources-metadata-lua.dll differ
diff --git a/citizen-scripting-core.dll b/citizen-scripting-core.dll
index 39cfbedfa..f4aaf50e8 100644
Binary files a/citizen-scripting-core.dll and b/citizen-scripting-core.dll differ
diff --git a/citizen-scripting-lua.dll b/citizen-scripting-lua.dll
index c2d084bf3..6aafa8e6a 100644
Binary files a/citizen-scripting-lua.dll and b/citizen-scripting-lua.dll differ
diff --git a/citizen-scripting-lua54.dll b/citizen-scripting-lua54.dll
index 89a0cf75a..a82265ec2 100644
Binary files a/citizen-scripting-lua54.dll and b/citizen-scripting-lua54.dll differ
diff --git a/citizen-scripting-mono.dll b/citizen-scripting-mono.dll
index 38b3817c4..c812b50e3 100644
Binary files a/citizen-scripting-mono.dll and b/citizen-scripting-mono.dll differ
diff --git a/citizen-scripting-v8node.dll b/citizen-scripting-v8node.dll
index bc5d0d2a7..21b165b03 100644
Binary files a/citizen-scripting-v8node.dll and b/citizen-scripting-v8node.dll differ
diff --git a/citizen-server-fxdk.dll b/citizen-server-fxdk.dll
index a06986f9f..e249e6093 100644
Binary files a/citizen-server-fxdk.dll and b/citizen-server-fxdk.dll differ
diff --git a/citizen-server-gui.dll b/citizen-server-gui.dll
index 7e1565d69..485e41ebf 100644
Binary files a/citizen-server-gui.dll and b/citizen-server-gui.dll differ
diff --git a/citizen-server-impl.dll b/citizen-server-impl.dll
index 50465d53a..356bf797f 100644
Binary files a/citizen-server-impl.dll and b/citizen-server-impl.dll differ
diff --git a/citizen-server-instance.dll b/citizen-server-instance.dll
index 2b1684946..f008f7ded 100644
Binary files a/citizen-server-instance.dll and b/citizen-server-instance.dll differ
diff --git a/citizen-server-main.dll b/citizen-server-main.dll
index c2d1b6ee8..b08dfd3a8 100644
Binary files a/citizen-server-main.dll and b/citizen-server-main.dll differ
diff --git a/citizen-server-monitor.dll b/citizen-server-monitor.dll
index b083ed1d0..7d84d79ab 100644
Binary files a/citizen-server-monitor.dll and b/citizen-server-monitor.dll differ
diff --git a/citizen-server-net.dll b/citizen-server-net.dll
index d2ec16b85..55c1bb216 100644
Binary files a/citizen-server-net.dll and b/citizen-server-net.dll differ
diff --git a/citizen-server-state-fivesv.dll b/citizen-server-state-fivesv.dll
index 81e84be66..098a790af 100644
Binary files a/citizen-server-state-fivesv.dll and b/citizen-server-state-fivesv.dll differ
diff --git a/citizen-server-state-rdr3sv.dll b/citizen-server-state-rdr3sv.dll
index 25070efea..bb536a225 100644
Binary files a/citizen-server-state-rdr3sv.dll and b/citizen-server-state-rdr3sv.dll differ
diff --git a/conhost-server.dll b/conhost-server.dll
index 88f4aab87..1aa213045 100644
Binary files a/conhost-server.dll and b/conhost-server.dll differ
diff --git a/debug-script.dll b/debug-script.dll
index ed6a72c29..c379c92ed 100644
Binary files a/debug-script.dll and b/debug-script.dll differ
diff --git a/devcon.dll b/devcon.dll
index 0d9ba8bab..22d700d45 100644
Binary files a/devcon.dll and b/devcon.dll differ
diff --git a/http-client.dll b/http-client.dll
index f918f8b85..03f402a6b 100644
Binary files a/http-client.dll and b/http-client.dll differ
diff --git a/hypnonema.db b/hypnonema.db
index 6d13ac037..c8882f01f 100644
Binary files a/hypnonema.db and b/hypnonema.db differ
diff --git a/imgui.dll b/imgui.dll
index 25fcbcba6..27caafa33 100644
Binary files a/imgui.dll and b/imgui.dll differ
diff --git a/libuv.dll b/libuv.dll
index 07f3ef94d..bd6ba753f 100644
Binary files a/libuv.dll and b/libuv.dll differ
diff --git a/net-base.dll b/net-base.dll
index b922c2c48..6b023b84c 100644
Binary files a/net-base.dll and b/net-base.dll differ
diff --git a/net-http-server.dll b/net-http-server.dll
index 4fdf0243e..4ea25e7e2 100644
Binary files a/net-http-server.dll and b/net-http-server.dll differ
diff --git a/net-tcp-server.dll b/net-tcp-server.dll
index 8aafce801..479c5c61a 100644
Binary files a/net-tcp-server.dll and b/net-tcp-server.dll differ
diff --git a/nng.dll b/nng.dll
index 66aecc1ee..f65e380f0 100644
Binary files a/nng.dll and b/nng.dll differ
diff --git a/node.dll b/node.dll
index 08136b436..7fcc58eae 100644
Binary files a/node.dll and b/node.dll differ
diff --git a/resources/EGRP-HUD/client.lua b/resources/EGRP-HUD/client.lua
index 5274817c0..ad9260dc0 100644
--- a/resources/EGRP-HUD/client.lua
+++ b/resources/EGRP-HUD/client.lua
@@ -292,7 +292,10 @@ Citizen.CreateThread(function()
drawTimeText = ""
end
Citizen.Wait(1)
- local player = GetPlayerPed(-1)
+
+ DrawTextAOP(AOPxNew, AOPyNew, 1.0,1.0,0.45, drawTimeText, 255, 255, 255, 255)
+ DrawTextAOP(AOPxNew, AOPyNew2, 1.0,1.0,0.45, "~r~Discord: ~w~elite-gaming.gg" , 255, 255, 255, 255)
+ --[[local player = GetPlayerPed(-1)
local veh = GetVehiclePedIsIn(player)
local mph = math.ceil(GetEntitySpeed(veh) * 2.23)
@@ -321,7 +324,7 @@ Citizen.CreateThread(function()
DrawTextAOP(AOPxNew, AOPyNew, 1.0,1.0,0.45, drawTimeText, 255, 255, 255, 255)
DrawTextAOP(AOPxNew, AOPyNew2, 1.0,1.0,0.45, "~w~Current " .. featColor .. "AOP: ~w~" .. FaxCurAOP .. featColor , 255, 255, 255, 255)
end
- end
+ end]]--
end
end)
diff --git a/resources/EGRP-LoadingScreen/index.html b/resources/EGRP-LoadingScreen/index.html
index 669af4bc2..ec16a2228 100644
--- a/resources/EGRP-LoadingScreen/index.html
+++ b/resources/EGRP-LoadingScreen/index.html
@@ -9,7 +9,7 @@
-
+
diff --git a/resources/Engine-Toggle/cl_vengine.lua b/resources/Engine-Toggle/cl_vengine.lua
index 4e4a4f6c2..353ce8fee 100644
--- a/resources/Engine-Toggle/cl_vengine.lua
+++ b/resources/Engine-Toggle/cl_vengine.lua
@@ -1,6 +1,6 @@
-- Configuration
-local button = 56 -- 167 (F6 by default) Set to F9
+local button = 9999 -- 167 (F6 by default) Removed
local commandEnabled = true -- (false by default) If you set this to true, typing "/engine" in chat will also toggle your engine.
-- You're all set now!
diff --git a/resources/RageUI/LICENSE.html b/resources/RageUI/LICENSE.html
new file mode 100644
index 000000000..5b633d968
--- /dev/null
+++ b/resources/RageUI/LICENSE.html
@@ -0,0 +1,694 @@
+
+
+
+
+ GNU General Public License v3.0 - GNU Project - Free Software Foundation (FSF)
+
+
+
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
Preamble
+
+
The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+
The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+
When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+
To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+
For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+
Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+
For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+
Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+
Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+
The precise terms and conditions for copying, distribution and
+modification follow.
+
+
TERMS AND CONDITIONS
+
+
0. Definitions.
+
+
“This License” refers to version 3 of the GNU General Public License.
+
+
“Copyright” also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+
“The Program” refers to any copyrightable work licensed under this
+License. Each licensee is addressed as “you”. “Licensees” and
+“recipients” may be individuals or organizations.
+
+
To “modify” a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a “modified version” of the
+earlier work or a work “based on” the earlier work.
+
+
A “covered work” means either the unmodified Program or a work based
+on the Program.
+
+
To “propagate” a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+
To “convey” a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+
An interactive user interface displays “Appropriate Legal Notices”
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+
1. Source Code.
+
+
The “source code” for a work means the preferred form of the work
+for making modifications to it. “Object code” means any non-source
+form of a work.
+
+
A “Standard Interface” means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+
The “System Libraries” of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+“Major Component”, in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+
The “Corresponding Source” for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+
The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+
The Corresponding Source for a work in source code form is that
+same work.
+
+
2. Basic Permissions.
+
+
All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+
You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+
Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+
No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+
When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+
4. Conveying Verbatim Copies.
+
+
You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+
You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+
5. Conveying Modified Source Versions.
+
+
You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+
+
a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+
b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ “keep intact all notices”.
+
+
c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+
d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+
+
A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+“aggregate” if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+
6. Conveying Non-Source Forms.
+
+
You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+
+
a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+
b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+
c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+
d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+
e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+
+
A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+
A “User Product” is either (1) a “consumer product”, which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, “normally used” refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+
“Installation Information” for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+
If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+
The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+
Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+
7. Additional Terms.
+
+
“Additional permissions” are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+
When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+
Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+
+
a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+
b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+
c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+
d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+
e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+
f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+
+
All other non-permissive additional terms are considered “further
+restrictions” within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+
If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+
Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+
8. Termination.
+
+
You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+
However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+
Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+
Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+
9. Acceptance Not Required for Having Copies.
+
+
You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+
10. Automatic Licensing of Downstream Recipients.
+
+
Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+
An “entity transaction” is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+
You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+
11. Patents.
+
+
A “contributor” is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's “contributor version”.
+
+
A contributor's “essential patent claims” are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, “control” includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+
In the following three paragraphs, a “patent license” is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To “grant” such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+
If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. “Knowingly relying” means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+
If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+
A patent license is “discriminatory” if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+
Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+
12. No Surrender of Others' Freedom.
+
+
If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+
13. Use with the GNU Affero General Public License.
+
+
Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+
14. Revised Versions of this License.
+
+
The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+
Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License “or any later version” applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+
If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+
Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+
15. Disclaimer of Warranty.
+
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+
16. Limitation of Liability.
+
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+
17. Interpretation of Sections 15 and 16.
+
+
If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+
END OF TERMS AND CONDITIONS
+
+
How to Apply These Terms to Your New Programs
+
+
If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+
To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the “copyright” line and a pointer to where the full notice is found.
+
+
<one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+
+
Also add information on how to contact you by electronic and paper mail.
+
+
If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+
<program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+
+
The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an “about box”.
+
+
You should also get your employer (if you work as a programmer) or school,
+if any, to sign a “copyright disclaimer” for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+
The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
+
+
+
\ No newline at end of file
diff --git a/resources/RageUI/RMenu.lua b/resources/RageUI/RMenu.lua
new file mode 100644
index 000000000..04b00f05e
--- /dev/null
+++ b/resources/RageUI/RMenu.lua
@@ -0,0 +1,98 @@
+---
+--- Generated by EmmyLua(https://github.com/EmmyLua)
+--- Created by Dylan Malandain.
+--- DateTime: 29/10/2019 02:40
+---
+
+---@class RageUI
+RageUI = {}
+
+---@class Item
+RageUI.Item = {}
+
+---@class Panel
+RageUI.Panel = {}
+
+---@class Window
+RageUI.Window = {}
+
+---@class RMenu
+RMenu = setmetatable({}, RMenu)
+
+---@type table
+local TotalMenus = {}
+
+---Add
+---@param Type string
+---@param Name string
+---@param Menu table
+---@return RMenu
+---@public
+function RMenu.Add(Type, Name, Menu)
+ if RMenu[Type] ~= nil then
+ RMenu[Type][Name] = {
+ Menu = Menu
+ }
+ else
+ RMenu[Type] = {}
+ RMenu[Type][Name] = {
+ Menu = Menu
+ }
+ end
+ return table.insert(TotalMenus, Menu)
+end
+
+---Get
+---@param Type string
+---@param Name string
+---@return table
+---@public
+function RMenu:Get(Type, Name)
+ if self[Type] ~= nil and self[Type][Name] ~= nil then
+ return self[Type][Name].Menu
+ end
+end
+
+---GetType
+---@param Type string
+---@return table
+---@public
+function RMenu:GetType(Type)
+ if self[Type] ~= nil then
+ return self[Type]
+ end
+end
+
+---Settings
+---@param Type string
+---@param Name string
+---@param Settings string
+---@param Value any optional
+---@return void
+---@public
+function RMenu:Settings(Type, Name, Settings, Value)
+ if Value ~= nil then
+ self[Type][Name][Settings] = Value
+ else
+ return self[Type][Name][Settings]
+ end
+end
+
+---Delete
+---@param Type string
+---@param Name string
+---@return void
+---@public
+function RMenu:Delete(Type, Name)
+ self[Type][Name] = nil
+ collectgarbage()
+end
+
+---DeleteType
+---@param Type string
+---@return void
+---@public
+function RMenu:DeleteType(Type)
+ self[Type] = nil
+ collectgarbage()
+end
diff --git a/resources/RageUI/components/Audio.lua b/resources/RageUI/components/Audio.lua
new file mode 100644
index 000000000..eecb07aa7
--- /dev/null
+++ b/resources/RageUI/components/Audio.lua
@@ -0,0 +1,35 @@
+---
+--- Generated by EmmyLua(https://github.com/EmmyLua)
+--- Created by Dylan Malandain.
+--- DateTime: 24/07/2019 03:38
+---
+
+
+---PlaySound
+---
+--- Reference : N/A
+---
+---@param Library string
+---@param Sound string
+---@param IsLooped boolean
+---@return nil
+---@public
+function RageUI.PlaySound(Library, Sound, IsLooped)
+ local audioId
+ if not IsLooped then
+ PlaySoundFrontend(-1, Sound, Library, true)
+ else
+ if not audioId then
+ Citizen.CreateThread(function()
+ audioId = GetSoundId()
+ PlaySoundFrontend(audioId, Sound, Library, true)
+ Citizen.Wait(0.01)
+ StopSound(audioId)
+ ReleaseSoundId(audioId)
+ audioId = nil
+ end)
+ end
+ end
+end
+
+
diff --git a/resources/RageUI/components/Enum.lua b/resources/RageUI/components/Enum.lua
new file mode 100644
index 000000000..3a0121f4b
--- /dev/null
+++ b/resources/RageUI/components/Enum.lua
@@ -0,0 +1,30 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+---@class Enum
+local enums = {
+ __index = function(table, key)
+ if rawget(table.enums, key) then
+ return key
+ end
+ end
+}
+
+---Enum
+---@param t table
+---@return Enum
+function RageUI.Enum(t)
+ local e = { enums = t }
+ return setmetatable(e, enums)
+end
diff --git a/resources/RageUI/components/Keys.lua b/resources/RageUI/components/Keys.lua
new file mode 100644
index 000000000..6927fe085
--- /dev/null
+++ b/resources/RageUI/components/Keys.lua
@@ -0,0 +1,44 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+
+---@class Keys
+Keys = {};
+
+---Register
+---@param Controls string
+---@param ControlName string
+---@param Description string
+---@param Action function
+---@return Keys
+---@public
+function Keys.Register(Controls, ControlName, Description, Action)
+ local _Keys = {
+ CONTROLS = Controls
+ }
+ RegisterKeyMapping(string.format('rageui-%s', ControlName), Description, "keyboard", Controls)
+ RegisterCommand(string.format('rageui-%s', ControlName), function(source, args)
+ if (Action ~= nil) then
+ Action();
+ end
+ end, false)
+ return setmetatable(_Keys, Keys)
+end
+
+---Exists
+---@param Controls string
+---@return boolean
+function Keys:Exists(Controls)
+ return self.CONTROLS == Controls and true or false
+end
diff --git a/resources/RageUI/components/Rectangle.lua b/resources/RageUI/components/Rectangle.lua
new file mode 100644
index 000000000..78ec8a942
--- /dev/null
+++ b/resources/RageUI/components/Rectangle.lua
@@ -0,0 +1,18 @@
+---RenderRectangle
+---
+--- Reference : https://github.com/iTexZoz/NativeUILua_Reloaded/blob/master/UIElements/UIResRectangle.lua#L84
+---
+---@param X number
+---@param Y number
+---@param Width number
+---@param Height number
+---@param R number
+---@param G number
+---@param B number
+---@param A number
+---@return nil
+---@public
+function RenderRectangle(X, Y, Width, Height, R, G, B, A)
+ local X, Y, Width, Height = (tonumber(X) or 0) / 1920, (tonumber(Y) or 0) / 1080, (tonumber(Width) or 0) / 1920, (tonumber(Height) or 0) / 1080
+ DrawRect(X + Width * 0.5, Y + Height * 0.5, Width, Height, tonumber(R) or 255, tonumber(G) or 255, tonumber(B) or 255, tonumber(A) or 255)
+end
diff --git a/resources/RageUI/components/Sprite.lua b/resources/RageUI/components/Sprite.lua
new file mode 100644
index 000000000..836daace1
--- /dev/null
+++ b/resources/RageUI/components/Sprite.lua
@@ -0,0 +1,27 @@
+---RenderSprite
+---
+--- Reference : https://github.com/iTexZoz/NativeUILua_Reloaded/blob/master/UIElements/Sprite.lua#L90
+---
+---@param TextureDictionary string
+---@param TextureName string
+---@param X number
+---@param Y number
+---@param Width number
+---@param Height number
+---@param Heading number
+---@param R number
+---@param G number
+---@param B number
+---@param A number
+---@return nil
+---@public
+function RenderSprite(TextureDictionary, TextureName, X, Y, Width, Height, Heading, R, G, B, A)
+ ---@type number
+ local X, Y, Width, Height = (tonumber(X) or 0) / 1920, (tonumber(Y) or 0) / 1080, (tonumber(Width) or 0) / 1920, (tonumber(Height) or 0) / 1080
+
+ if not HasStreamedTextureDictLoaded(TextureDictionary) then
+ RequestStreamedTextureDict(TextureDictionary, true)
+ end
+
+ DrawSprite(TextureDictionary, TextureName, X + Width * 0.5, Y + Height * 0.5, Width, Height, Heading or 0, tonumber(R) or 255, tonumber(G) or 255, tonumber(B) or 255, tonumber(A) or 255)
+end
\ No newline at end of file
diff --git a/resources/RageUI/components/Text.lua b/resources/RageUI/components/Text.lua
new file mode 100644
index 000000000..1bc2e0abc
--- /dev/null
+++ b/resources/RageUI/components/Text.lua
@@ -0,0 +1,155 @@
+---StringToArray
+---
+--- Reference : Frazzle <3
+---
+---@param str string
+function StringToArray(str)
+ local charCount = #str
+ local strCount = math.ceil(charCount / 99)
+ local strings = {}
+
+ for i = 1, strCount do
+ local start = (i - 1) * 99 + 1
+ local clamp = math.clamp(#string.sub(str, start), 0, 99)
+ local finish = ((i ~= 1) and (start - 1) or 0) + clamp
+
+ strings[i] = string.sub(str, start, finish)
+ end
+
+ return strings
+end
+
+
+---AddText
+---
+--- Reference : Frazzle <3
+---
+---@param str string
+function AddText(str)
+ local str = tostring(str)
+ local charCount = #str
+
+ if charCount < 100 then
+ AddTextComponentSubstringPlayerName(str)
+ else
+ local strings = StringToArray(str)
+
+ for s = 1, #strings do
+ AddTextComponentSubstringPlayerName(strings[s])
+ end
+ end
+end
+
+
+---GetLineCount
+---
+--- Reference : Frazzle <3
+---
+---@param Text string
+---@param X number
+---@param Y number
+---@param Font number
+---@param Scale number
+---@param R number
+---@param G number
+---@param B number
+---@param A number
+---@param Alignment string
+---@param DropShadow boolean
+---@param Outline boolean
+---@param WordWrap number
+---@return function
+---@public
+function GetLineCount(Text, X, Y, Font, Scale, R, G, B, A, Alignment, DropShadow, Outline, WordWrap)
+ ---@type table
+ local Text, X, Y = tostring(Text), (tonumber(X) or 0) / 1920, (tonumber(Y) or 0) / 1080
+ SetTextFont(Font or 0)
+ SetTextScale(1.0, Scale or 0)
+ SetTextColour(tonumber(R) or 255, tonumber(G) or 255, tonumber(B) or 255, tonumber(A) or 255)
+ if DropShadow then
+ SetTextDropShadow()
+ end
+ if Outline then
+ SetTextOutline()
+ end
+ if Alignment ~= nil then
+ if Alignment == 1 or Alignment == "Center" or Alignment == "Centre" then
+ SetTextCentre(true)
+ elseif Alignment == 2 or Alignment == "Right" then
+ SetTextRightJustify(true)
+ end
+ end
+ if tonumber(WordWrap) and tonumber(WordWrap) ~= 0 then
+ if Alignment == 1 or Alignment == "Center" or Alignment == "Centre" then
+ SetTextWrap(X - ((WordWrap / 1920) / 2), X + ((WordWrap / 1920) / 2))
+ elseif Alignment == 2 or Alignment == "Right" then
+ SetTextWrap(0, X)
+ else
+ SetTextWrap(X, X + (WordWrap / 1920))
+ end
+ else
+ if Alignment == 2 or Alignment == "Right" then
+ SetTextWrap(0, X)
+ end
+ end
+
+ BeginTextCommandLineCount("CELL_EMAIL_BCON")
+ AddText(Text)
+ return GetTextScreenLineCount(X, Y)
+end
+
+---RenderText
+---
+--- Reference : https://github.com/iTexZoz/NativeUILua_Reloaded/blob/master/UIElements/UIResText.lua#L189
+---
+---@param Text string
+---@param X number
+---@param Y number
+---@param Font number
+---@param Scale number
+---@param R number
+---@param G number
+---@param B number
+---@param A number
+---@param Alignment string
+---@param DropShadow boolean
+---@param Outline boolean
+---@param WordWrap number
+---@return nil
+---@public
+function RenderText(Text, X, Y, Font, Scale, R, G, B, A, Alignment, DropShadow, Outline, WordWrap)
+ ---@type table
+ local Text, X, Y = tostring(Text), (tonumber(X) or 0) / 1920, (tonumber(Y) or 0) / 1080
+ SetTextFont(Font or 0)
+ SetTextScale(1.0, Scale or 0)
+ SetTextColour(tonumber(R) or 255, tonumber(G) or 255, tonumber(B) or 255, tonumber(A) or 255)
+ if DropShadow then
+ SetTextDropShadow()
+ end
+ if Outline then
+ SetTextOutline()
+ end
+ if Alignment ~= nil then
+ if Alignment == 1 or Alignment == "Center" or Alignment == "Centre" then
+ SetTextCentre(true)
+ elseif Alignment == 2 or Alignment == "Right" then
+ SetTextRightJustify(true)
+ end
+ end
+ if tonumber(WordWrap) and tonumber(WordWrap) ~= 0 then
+ if Alignment == 1 or Alignment == "Center" or Alignment == "Centre" then
+ SetTextWrap(X - ((WordWrap / 1920) / 2), X + ((WordWrap / 1920) / 2))
+ elseif Alignment == 2 or Alignment == "Right" then
+ SetTextWrap(0, X)
+ else
+ SetTextWrap(X, X + (WordWrap / 1920))
+ end
+ else
+ if Alignment == 2 or Alignment == "Right" then
+ SetTextWrap(0, X)
+ end
+ end
+ BeginTextCommandDisplayText("CELL_EMAIL_BCON")
+ AddText(Text)
+ EndTextCommandDisplayText(X, Y)
+end
diff --git a/resources/RageUI/components/Visual.lua b/resources/RageUI/components/Visual.lua
new file mode 100644
index 000000000..a2c084423
--- /dev/null
+++ b/resources/RageUI/components/Visual.lua
@@ -0,0 +1,64 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+---@class Visual
+Visual = Visual or {};
+
+local function AddLongString(txt)
+ for i = 100, string.len(txt), 99 do
+ local sub = string.sub(txt, i, i + 99)
+ AddTextComponentSubstringPlayerName(sub)
+ end
+end
+
+function Visual.Popup()
+
+end
+
+function Visual.Radar()
+
+end
+
+function Visual.Subtitle(text, time)
+ ClearPrints()
+ BeginTextCommandPrint("STRING")
+ AddTextComponentSubstringPlayerName(text)
+ EndTextCommandPrint(time and math.ceil(time) or 0, true)
+end
+
+function Visual.FloatingHelpText(text, sound, loop)
+ BeginTextCommandDisplayHelp("jamyfafi")
+ AddTextComponentSubstringPlayerName(text)
+ if string.len(text) > 99 then
+ AddLongString(text)
+ end
+ EndTextCommandDisplayHelp(0, loop or 0, sound or true, -1)
+end
+
+function Visual.Prompt(text, spinner)
+ BeginTextCommandBusyspinnerOn("STRING")
+ AddTextComponentSubstringPlayerName(text)
+ EndTextCommandBusyspinnerOn(spinner or 1)
+end
+
+function Visual.PromptDuration(duration, text, spinner)
+ Citizen.CreateThread(function()
+ Citizen.Wait(0)
+ Visual.Prompt(text, spinner)
+ Citizen.Wait(duration)
+ if (BusyspinnerIsOn()) then
+ BusyspinnerOff();
+ end
+ end)
+end
diff --git a/resources/RageUI/fxmanifest.lua b/resources/RageUI/fxmanifest.lua
new file mode 100644
index 000000000..c2197a2af
--- /dev/null
+++ b/resources/RageUI/fxmanifest.lua
@@ -0,0 +1,20 @@
+fx_version 'adamant'
+games { 'gta5' };
+
+name 'RageUI';
+description 'RageUI, and a project specially created to replace the NativeUILua-Reloaded library. This library allows to create menus similar to the one of Grand Theft Auto online.'
+
+client_scripts {
+ "RMenu.lua",
+ "menu/RageUI.lua",
+ "menu/Menu.lua",
+ "menu/MenuController.lua",
+ "components/*.lua",
+ "menu/elements/*.lua",
+ "menu/items/*.lua",
+ "menu/panels/*.lua",
+ "menu/windows/*.lua",
+
+}
+
+
diff --git a/resources/RageUI/menu/Menu.lua b/resources/RageUI/menu/Menu.lua
new file mode 100644
index 000000000..c61965ea1
--- /dev/null
+++ b/resources/RageUI/menu/Menu.lua
@@ -0,0 +1,385 @@
+---
+--- Generated by EmmyLua(https://github.com/EmmyLua)
+--- Created by Dylan Malandain.
+--- DateTime: 21/04/2019 21:20
+---
+
+---CreateMenu
+---@param Title string
+---@param Subtitle string
+---@param X number
+---@param Y number
+---@param TextureDictionary string
+---@param TextureName string
+---@param R number
+---@param G number
+---@param B number
+---@param A number
+---@return RageUIMenus
+---@public
+function RageUI.CreateMenu(Title, Subtitle, X, Y, TextureDictionary, TextureName, R, G, B, A)
+
+ ---@type table
+ local Menu = {}
+ Menu.Display = {};
+
+ Menu.InstructionalButtons = {}
+
+ Menu.Display.Header = true;
+ Menu.Display.Glare = true;
+ Menu.Display.Subtitle = true;
+ Menu.Display.Background = true;
+ Menu.Display.Navigation = true;
+ Menu.Display.InstructionalButton = true;
+
+ Menu.Title = Title or ""
+ Menu.TitleFont = 6
+ Menu.TitleScale = 1.0
+ Menu.Subtitle = string.upper(Subtitle) or nil
+ Menu.SubtitleHeight = -37
+ Menu.Description = nil
+ Menu.DescriptionHeight = RageUI.Settings.Items.Description.Background.Height
+ Menu.X = X or 0
+ Menu.Y = Y or 0
+ Menu.Parent = nil
+ Menu.WidthOffset = RageUI.UI.Style[RageUI.UI.Current].Width
+ Menu.Open = false
+ Menu.Controls = RageUI.Settings.Controls
+ Menu.Index = 1
+ Menu.Sprite = { Dictionary = TextureDictionary or "commonmenu", Texture = TextureName or "interaction_bgd", Color = { R = R, G = G, B = B, A = A } }
+ Menu.Rectangle = nil
+ Menu.Pagination = { Minimum = 1, Maximum = 10, Total = 10 }
+ Menu.Safezone = true
+ Menu.SafeZoneSize = nil
+ Menu.EnableMouse = false
+ Menu.Options = 0
+ Menu.Closable = true
+ Menu.InstructionalScaleform = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS")
+ Menu.CursorStyle = 1
+
+ if string.starts(Menu.Subtitle, "~") then
+ Menu.PageCounterColour = string.sub(Menu.Subtitle, 1, 3)
+ else
+ Menu.PageCounterColour = ""
+ end
+
+ if Menu.Subtitle ~= "" then
+ local SubtitleLineCount = GetLineCount(Menu.Subtitle, Menu.X + RageUI.Settings.Items.Subtitle.Text.X, Menu.Y + RageUI.Settings.Items.Subtitle.Text.Y, 0, RageUI.Settings.Items.Subtitle.Text.Scale, 245, 245, 245, 255, nil, false, false, RageUI.Settings.Items.Subtitle.Background.Width + Menu.WidthOffset)
+
+ if SubtitleLineCount > 1 then
+ Menu.SubtitleHeight = 18 * SubtitleLineCount
+ else
+ Menu.SubtitleHeight = 0
+ end
+ end
+
+ Citizen.CreateThread(function()
+ if not HasScaleformMovieLoaded(Menu.InstructionalScaleform) then
+ Menu.InstructionalScaleform = RequestScaleformMovie("INSTRUCTIONAL_BUTTONS")
+ while not HasScaleformMovieLoaded(Menu.InstructionalScaleform) do
+ Citizen.Wait(0)
+ end
+ end
+ end)
+
+ Citizen.CreateThread(function()
+ local ScaleformMovie = RequestScaleformMovie("MP_MENU_GLARE")
+ while not HasScaleformMovieLoaded(ScaleformMovie) do
+ Citizen.Wait(0)
+ end
+ end)
+
+ return setmetatable(Menu, RageUI.Menus)
+end
+
+---CreateSubMenu
+---@param ParentMenu function
+---@param Title string
+---@param Subtitle string
+---@param X number
+---@param Y number
+---@param TextureDictionary string
+---@param TextureName string
+---@param R number
+---@param G number
+---@param B number
+---@param A number
+---@return RageUIMenus
+---@public
+function RageUI.CreateSubMenu(ParentMenu, Title, Subtitle, X, Y, TextureDictionary, TextureName, R, G, B, A)
+ if ParentMenu ~= nil then
+ if ParentMenu() then
+ local Menu = RageUI.CreateMenu(Title or ParentMenu.Title, string.upper(Subtitle) or string.upper(ParentMenu.Subtitle), X or ParentMenu.X, Y or ParentMenu.Y)
+ Menu.Parent = ParentMenu
+ Menu.WidthOffset = ParentMenu.WidthOffset
+ Menu.Safezone = ParentMenu.Safezone
+ if ParentMenu.Sprite then
+ Menu.Sprite = { Dictionary = TextureDictionary or ParentMenu.Sprite.Dictionary, Texture = TextureName or ParentMenu.Sprite.Texture, Color = { R = R or ParentMenu.Sprite.Color.R, G = G or ParentMenu.Sprite.Color.G, B = B or ParentMenu.Sprite.Color.B, A = A or ParentMenu.Sprite.Color.A } }
+ else
+ Menu.Rectangle = ParentMenu.Rectangle
+ end
+ return setmetatable(Menu, RageUI.Menus)
+ else
+ return nil
+ end
+ else
+ return nil
+ end
+end
+
+function RageUI.Menus:DisplayHeader(boolean)
+ self.Display.Header = boolean;
+ return self.Display.Header;
+end
+
+function RageUI.Menus:DisplayGlare(boolean)
+ self.Display.Glare = boolean;
+ return self.Display.Glare;
+end
+
+function RageUI.Menus:DisplaySubtitle(boolean)
+ self.Display.Subtitle = boolean;
+ return self.Display.Subtitle;
+end
+
+function RageUI.Menus:DisplayNavigation(boolean)
+ self.Display.Navigation = boolean;
+ return self.Display.Navigation;
+end
+
+function RageUI.Menus:DisplayInstructionalButton(boolean)
+ self.Display.InstructionalButton = boolean;
+ return self.Display.InstructionalButton;
+end
+
+---SetTitle
+---@param Title string
+---@return nil
+---@public
+function RageUI.Menus:SetTitle(Title)
+ self.Title = Title
+end
+
+function RageUI.Menus:SetStyleSize(Value)
+ local witdh
+ if Value >= 0 and Value <= 100 then
+ witdh = Value
+ else
+ witdh = 100
+ end
+ self.WidthOffset = witdh
+end
+
+---GetStyleSize
+---@return any
+---@public
+function RageUI.Menus:GetStyleSize()
+ if (self.WidthOffset == 100) then
+ return "RageUI"
+ elseif (self.WidthOffset == 0) then
+ return "NativeUI";
+ else
+ return self.WidthOffset;
+ end
+end
+
+---SetStyleSize
+---@param Int string
+---@return void
+---@public
+function RageUI.Menus:SetCursorStyle(Int)
+ self.CursorStyle = Int or 1 or 0
+ SetMouseCursorSprite(Int)
+end
+
+---ResetCursorStyle
+---@return void
+---@public
+function RageUI.Menus:ResetCursorStyle()
+ self.CursorStyle = 1
+ SetMouseCursorSprite(1)
+end
+
+---UpdateCursorStyle
+---@return void
+---@public
+function RageUI.Menus:UpdateCursorStyle()
+ SetMouseCursorSprite(self.CursorStyle)
+end
+
+---RefreshIndex
+---@return void
+---@public
+function RageUI.Menus:RefreshIndex()
+ self.Index = 1
+end
+
+---SetSubtitle
+---@param Subtitle string
+---@return nil
+---@public
+function RageUI.Menus:SetSubtitle(Subtitle)
+
+ self.Subtitle = string.upper(Subtitle) or string.upper(self.Subtitle)
+
+ if string.starts(self.Subtitle, "~") then
+ self.PageCounterColour = string.sub(self.Subtitle, 1, 3)
+ else
+ self.PageCounterColour = ""
+ end
+ if self.Subtitle ~= "" then
+ local SubtitleLineCount = GetLineCount(self.Subtitle, self.X + RageUI.Settings.Items.Subtitle.Text.X, self.Y + RageUI.Settings.Items.Subtitle.Text.Y, 0, RageUI.Settings.Items.Subtitle.Text.Scale, 245, 245, 245, 255, nil, false, false, RageUI.Settings.Items.Subtitle.Background.Width + self.WidthOffset)
+
+ if SubtitleLineCount > 1 then
+ self.SubtitleHeight = 18 * SubtitleLineCount
+ else
+ self.SubtitleHeight = 0
+ end
+
+ else
+ self.SubtitleHeight = -37
+ end
+end
+
+---PageCounter
+---@param Subtitle string
+---@return nil
+---@public
+function RageUI.Menus:SetPageCounter(Subtitle)
+ self.PageCounter = Subtitle
+end
+
+---EditSpriteColor
+---@param Colors table
+---@return nil
+---@public
+function RageUI.Menus:EditSpriteColor(color)
+ if self.Sprite.Dictionary == "commonmenu" then
+ self.Sprite.Color = color
+ end
+end
+---SetPosition
+---@param X number
+---@param Y number
+---@return nil
+---@public
+function RageUI.Menus:SetPosition(X, Y)
+ self.X = tonumber(X) or self.X
+ self.Y = tonumber(Y) or self.Y
+end
+
+---SetTotalItemsPerPage
+---@param Value number
+---@return nil
+---@public
+function RageUI.Menus:SetTotalItemsPerPage(Value)
+ self.Pagination.Total = tonumber(Value) or self.Pagination.Total
+end
+
+---SetRectangleBanner
+---@param R number
+---@param G number
+---@param B number
+---@param A number
+---@return nil
+---@public
+function RageUI.Menus:SetRectangleBanner(R, G, B, A)
+ self.Rectangle = { R = tonumber(R) or 255, G = tonumber(G) or 255, B = tonumber(B) or 255, A = tonumber(A) or 255 }
+ self.Sprite = nil
+end
+
+---SetSpriteBanner
+---@param TextureDictionary string
+---@param Texture string
+---@return nil
+---@public
+function RageUI.Menus:SetSpriteBanner(TextureDictionary, Texture)
+ self.Sprite = { Dictionary = TextureDictionary or "commonmenu", Texture = Texture or "interaction_bgd" }
+ self.Rectangle = nil
+end
+
+function RageUI.Menus:Closable(boolean)
+ if type(boolean) == "boolean" then
+ self.Closable = boolean
+ else
+ error("Type is not boolean")
+ end
+end
+
+function RageUI.Menus:AddInstructionButton(button)
+ if type(button) == "table" and #button == 2 then
+ table.insert(self.InstructionalButtons, button)
+ self.UpdateInstructionalButtons(true);
+ end
+end
+
+function RageUI.Menus:RemoveInstructionButton(button)
+ if type(button) == "table" then
+ for i = 1, #self.InstructionalButtons do
+ if button == self.InstructionalButtons[i] then
+ table.remove(self.InstructionalButtons, i)
+ self.UpdateInstructionalButtons(true);
+ break
+ end
+ end
+ else
+ if tonumber(button) then
+ if self.InstructionalButtons[tonumber(button)] then
+ table.remove(self.InstructionalButtons, tonumber(button))
+ self.UpdateInstructionalButtons(true);
+ end
+ end
+ end
+end
+
+function RageUI.Menus:UpdateInstructionalButtons(Visible)
+
+ if not Visible then
+ return
+ end
+
+ BeginScaleformMovieMethod(self.InstructionalScaleform, "CLEAR_ALL")
+ EndScaleformMovieMethod()
+
+ BeginScaleformMovieMethod(self.InstructionalScaleform, "TOGGLE_MOUSE_BUTTONS")
+ ScaleformMovieMethodAddParamInt(0)
+ EndScaleformMovieMethod()
+
+ BeginScaleformMovieMethod(self.InstructionalScaleform, "CREATE_CONTAINER")
+ EndScaleformMovieMethod()
+
+ BeginScaleformMovieMethod(self.InstructionalScaleform, "SET_DATA_SLOT")
+ ScaleformMovieMethodAddParamInt(0)
+ PushScaleformMovieMethodParameterButtonName(GetControlInstructionalButton(2, 176, 0))
+ PushScaleformMovieMethodParameterString(GetLabelText("HUD_INPUT2"))
+ EndScaleformMovieMethod()
+
+ if self.Closable then
+ BeginScaleformMovieMethod(self.InstructionalScaleform, "SET_DATA_SLOT")
+ ScaleformMovieMethodAddParamInt(1)
+ PushScaleformMovieMethodParameterButtonName(GetControlInstructionalButton(2, 177, 0))
+ PushScaleformMovieMethodParameterString(GetLabelText("HUD_INPUT3"))
+ EndScaleformMovieMethod()
+ end
+
+ local count = 2
+
+ if (self.InstructionalButtons ~= nil) then
+ for i = 1, #self.InstructionalButtons do
+ if self.InstructionalButtons[i] then
+ if #self.InstructionalButtons[i] == 2 then
+ BeginScaleformMovieMethod(self.InstructionalScaleform, "SET_DATA_SLOT")
+ ScaleformMovieMethodAddParamInt(count)
+ PushScaleformMovieMethodParameterButtonName(self.InstructionalButtons[i][1])
+ PushScaleformMovieMethodParameterString(self.InstructionalButtons[i][2])
+ EndScaleformMovieMethod()
+ count = count + 1
+ end
+ end
+ end
+ end
+
+ BeginScaleformMovieMethod(self.InstructionalScaleform, "DRAW_INSTRUCTIONAL_BUTTONS")
+ ScaleformMovieMethodAddParamInt(-1)
+ EndScaleformMovieMethod()
+end
diff --git a/resources/RageUI/menu/MenuController.lua b/resources/RageUI/menu/MenuController.lua
new file mode 100644
index 000000000..cc79a7845
--- /dev/null
+++ b/resources/RageUI/menu/MenuController.lua
@@ -0,0 +1,350 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+RageUI.LastControl = false
+
+local ControlActions = {
+ 'Left',
+ 'Right',
+ 'Select',
+ 'Click',
+}
+
+---GoUp
+---@param Options number
+---@return nil
+---@public
+function RageUI.GoUp(Options)
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ Options = CurrentMenu.Options
+ if CurrentMenu() then
+ if (Options ~= 0) then
+ if Options > CurrentMenu.Pagination.Total then
+ if CurrentMenu.Index <= CurrentMenu.Pagination.Minimum then
+ if CurrentMenu.Index == 1 then
+ CurrentMenu.Pagination.Minimum = Options - (CurrentMenu.Pagination.Total - 1)
+ CurrentMenu.Pagination.Maximum = Options
+ CurrentMenu.Index = Options
+ else
+ CurrentMenu.Pagination.Minimum = (CurrentMenu.Pagination.Minimum - 1)
+ CurrentMenu.Pagination.Maximum = (CurrentMenu.Pagination.Maximum - 1)
+ CurrentMenu.Index = CurrentMenu.Index - 1
+ end
+ else
+ CurrentMenu.Index = CurrentMenu.Index - 1
+ end
+ else
+ if CurrentMenu.Index == 1 then
+ CurrentMenu.Pagination.Minimum = Options - (CurrentMenu.Pagination.Total - 1)
+ CurrentMenu.Pagination.Maximum = Options
+ CurrentMenu.Index = Options
+ else
+ CurrentMenu.Index = CurrentMenu.Index - 1
+ end
+ end
+
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].UpDown.audioName, Audio[Audio.Use].UpDown.audioRef)
+ RageUI.LastControl = true
+ if (CurrentMenu.onIndexChange ~= nil) then
+ Citizen.CreateThread(function()
+ CurrentMenu.onIndexChange(CurrentMenu.Index)
+ end)
+ end
+ else
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Error.audioName, Audio[Audio.Use].Error.audioRef)
+ end
+ end
+ end
+end
+
+---GoDown
+---@param Options number
+---@return nil
+---@public
+function RageUI.GoDown(Options)
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ Options = CurrentMenu.Options
+ if CurrentMenu() then
+ if (Options ~= 0) then
+ if Options > CurrentMenu.Pagination.Total then
+ if CurrentMenu.Index >= CurrentMenu.Pagination.Maximum then
+ if CurrentMenu.Index == Options then
+ CurrentMenu.Pagination.Minimum = 1
+ CurrentMenu.Pagination.Maximum = CurrentMenu.Pagination.Total
+ CurrentMenu.Index = 1
+ else
+ CurrentMenu.Pagination.Maximum = (CurrentMenu.Pagination.Maximum + 1)
+ CurrentMenu.Pagination.Minimum = CurrentMenu.Pagination.Maximum - (CurrentMenu.Pagination.Total - 1)
+ CurrentMenu.Index = CurrentMenu.Index + 1
+ end
+ else
+ CurrentMenu.Index = CurrentMenu.Index + 1
+ end
+ else
+ if CurrentMenu.Index == Options then
+ CurrentMenu.Pagination.Minimum = 1
+ CurrentMenu.Pagination.Maximum = CurrentMenu.Pagination.Total
+ CurrentMenu.Index = 1
+ else
+ CurrentMenu.Index = CurrentMenu.Index + 1
+ end
+ end
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].UpDown.audioName, Audio[Audio.Use].UpDown.audioRef)
+ RageUI.LastControl = false
+ if (CurrentMenu.onIndexChange ~= nil) then
+ Citizen.CreateThread(function()
+ CurrentMenu.onIndexChange(CurrentMenu.Index)
+ end)
+ end
+ else
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Error.audioName, Audio[Audio.Use].Error.audioRef)
+ end
+ end
+ end
+end
+
+function RageUI.GoActionControl(Controls, Action)
+ if Controls[Action or 'Left'].Enabled then
+ for Index = 1, #Controls[Action or 'Left'].Keys do
+ if not Controls[Action or 'Left'].Pressed then
+ if IsDisabledControlJustPressed(Controls[Action or 'Left'].Keys[Index][1], Controls[Action or 'Left'].Keys[Index][2]) then
+ Controls[Action or 'Left'].Pressed = true
+ Citizen.CreateThread(function()
+ Controls[Action or 'Left'].Active = true
+ Citizen.Wait(0.01)
+ Controls[Action or 'Left'].Active = false
+ Citizen.Wait(175)
+ while Controls[Action or 'Left'].Enabled and IsDisabledControlPressed(Controls[Action or 'Left'].Keys[Index][1], Controls[Action or 'Left'].Keys[Index][2]) do
+ Controls[Action or 'Left'].Active = true
+ Citizen.Wait(1)
+ Controls[Action or 'Left'].Active = false
+ Citizen.Wait(124)
+ end
+ Controls[Action or 'Left'].Pressed = false
+ if (Action ~= ControlActions[5]) then
+ Citizen.Wait(10)
+ end
+ end)
+ break
+ end
+ end
+ end
+ end
+end
+
+function RageUI.GoActionControlSlider(Controls, Action)
+ if Controls[Action].Enabled then
+ for Index = 1, #Controls[Action].Keys do
+ if not Controls[Action].Pressed then
+ if IsDisabledControlJustPressed(Controls[Action].Keys[Index][1], Controls[Action].Keys[Index][2]) then
+ Controls[Action].Pressed = true
+ Citizen.CreateThread(function()
+ Controls[Action].Active = true
+ Citizen.Wait(1)
+ Controls[Action].Active = false
+ while Controls[Action].Enabled and IsDisabledControlPressed(Controls[Action].Keys[Index][1], Controls[Action].Keys[Index][2]) do
+ Controls[Action].Active = true
+ Citizen.Wait(1)
+ Controls[Action].Active = false
+ end
+ Controls[Action].Pressed = false
+ end)
+ break
+ end
+ end
+ end
+ end
+end
+
+---Controls
+---@return nil
+---@public
+function RageUI.Controls()
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+ if CurrentMenu.Open then
+
+ local Controls = CurrentMenu.Controls;
+ ---@type number
+ local Options = CurrentMenu.Options
+ RageUI.Options = CurrentMenu.Options
+ if CurrentMenu.EnableMouse then
+ DisableAllControlActions(2)
+ end
+
+ if not IsInputDisabled(2) then
+ for Index = 1, #Controls.Enabled.Controller do
+ EnableControlAction(Controls.Enabled.Controller[Index][1], Controls.Enabled.Controller[Index][2], true)
+ end
+ else
+ for Index = 1, #Controls.Enabled.Keyboard do
+ EnableControlAction(Controls.Enabled.Keyboard[Index][1], Controls.Enabled.Keyboard[Index][2], true)
+ end
+ end
+
+ if Controls.Up.Enabled then
+ for Index = 1, #Controls.Up.Keys do
+ if not Controls.Up.Pressed then
+ if IsDisabledControlJustPressed(Controls.Up.Keys[Index][1], Controls.Up.Keys[Index][2]) then
+ Controls.Up.Pressed = true
+ Citizen.CreateThread(function()
+ RageUI.GoUp(Options)
+ Citizen.Wait(175)
+ while Controls.Up.Enabled and IsDisabledControlPressed(Controls.Up.Keys[Index][1], Controls.Up.Keys[Index][2]) do
+ RageUI.GoUp(Options)
+ Citizen.Wait(50)
+ end
+ Controls.Up.Pressed = false
+ end)
+ break
+ end
+ end
+ end
+ end
+
+ if Controls.Down.Enabled then
+ for Index = 1, #Controls.Down.Keys do
+ if not Controls.Down.Pressed then
+ if IsDisabledControlJustPressed(Controls.Down.Keys[Index][1], Controls.Down.Keys[Index][2]) then
+ Controls.Down.Pressed = true
+ Citizen.CreateThread(function()
+ RageUI.GoDown(Options)
+ Citizen.Wait(175)
+ while Controls.Down.Enabled and IsDisabledControlPressed(Controls.Down.Keys[Index][1], Controls.Down.Keys[Index][2]) do
+ RageUI.GoDown(Options)
+ Citizen.Wait(50)
+ end
+ Controls.Down.Pressed = false
+ end)
+ break
+ end
+ end
+ end
+ end
+
+ for i = 1, #ControlActions do
+ RageUI.GoActionControl(Controls, ControlActions[i])
+ end
+
+ RageUI.GoActionControlSlider(Controls, 'SliderLeft')
+ RageUI.GoActionControlSlider(Controls, 'SliderRight')
+
+ if Controls.Back.Enabled then
+ for Index = 1, #Controls.Back.Keys do
+ if not Controls.Back.Pressed then
+ if IsDisabledControlJustPressed(Controls.Back.Keys[Index][1], Controls.Back.Keys[Index][2]) then
+ Controls.Back.Pressed = true
+ Citizen.Wait(10)
+ break
+ end
+ end
+ end
+ end
+
+ end
+ end
+ end
+end
+
+---Navigation
+---@return nil
+---@public
+function RageUI.Navigation()
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (CurrentMenu.Display.Navigation) then
+ if CurrentMenu.EnableMouse then
+ SetMouseCursorActiveThisFrame()
+ end
+ if RageUI.Options > CurrentMenu.Pagination.Total then
+
+ ---@type boolean
+ local UpHovered = false
+
+ ---@type boolean
+ local DownHovered = false
+
+ if not CurrentMenu.SafeZoneSize then
+ CurrentMenu.SafeZoneSize = { X = 0, Y = 0 }
+
+ if CurrentMenu.Safezone then
+ CurrentMenu.SafeZoneSize = RageUI.GetSafeZoneBounds()
+
+ SetScriptGfxAlign(76, 84)
+ SetScriptGfxAlignParams(0, 0, 0, 0)
+ end
+ end
+
+ if CurrentMenu.EnableMouse then
+ UpHovered = RageUI.IsMouseInBounds(CurrentMenu.X + CurrentMenu.SafeZoneSize.X, CurrentMenu.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height)
+ DownHovered = RageUI.IsMouseInBounds(CurrentMenu.X + CurrentMenu.SafeZoneSize.X, CurrentMenu.Y + RageUI.Settings.Items.Navigation.Rectangle.Height + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height)
+
+ if CurrentMenu.Controls.Click.Active then
+ if UpHovered then
+ RageUI.GoUp(RageUI.Options)
+ elseif DownHovered then
+ RageUI.GoDown(RageUI.Options)
+ end
+ end
+
+ if UpHovered then
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height, 30, 30, 30, 255)
+ else
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height, 0, 0, 0, 200)
+ end
+
+ if DownHovered then
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + RageUI.Settings.Items.Navigation.Rectangle.Height + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height, 30, 30, 30, 255)
+ else
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + RageUI.Settings.Items.Navigation.Rectangle.Height + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height, 0, 0, 0, 200)
+ end
+ else
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + RageUI.Settings.Items.Navigation.Rectangle.Y, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height, 0, 0, 0, 200)
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + RageUI.Settings.Items.Navigation.Rectangle.Height + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + RageUI.Settings.Items.Navigation.Rectangle.Y, RageUI.Settings.Items.Navigation.Rectangle.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Navigation.Rectangle.Height, 0, 0, 0, 200)
+ end
+ RenderSprite(RageUI.Settings.Items.Navigation.Arrows.Dictionary, RageUI.Settings.Items.Navigation.Arrows.Texture, CurrentMenu.X + RageUI.Settings.Items.Navigation.Arrows.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + RageUI.Settings.Items.Navigation.Arrows.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, RageUI.Settings.Items.Navigation.Arrows.Width, RageUI.Settings.Items.Navigation.Arrows.Height)
+ RageUI.ItemOffset = RageUI.ItemOffset + (RageUI.Settings.Items.Navigation.Rectangle.Height * 2)
+ end
+ end
+ end
+end
+
+---GoBack
+---@return nil
+---@public
+function RageUI.GoBack()
+ local CurrentMenu = RageUI.CurrentMenu
+ if CurrentMenu ~= nil then
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Back.audioName, Audio[Audio.Use].Back.audioRef)
+ if CurrentMenu.Parent ~= nil then
+ if CurrentMenu.Parent() then
+ RageUI.NextMenu = CurrentMenu.Parent
+ else
+ RageUI.NextMenu = nil
+ RageUI.Visible(CurrentMenu, false)
+ end
+ else
+ RageUI.NextMenu = nil
+ RageUI.Visible(CurrentMenu, false)
+ end
+ end
+end
diff --git a/resources/RageUI/menu/RageUI.lua b/resources/RageUI/menu/RageUI.lua
new file mode 100644
index 000000000..dea50c863
--- /dev/null
+++ b/resources/RageUI/menu/RageUI.lua
@@ -0,0 +1,620 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+print("^4RageUI - https://github.com/iTexZoz/RageUI - OpenSource Advanced UI Api^0")
+
+function math.round(num, numDecimalPlaces)
+ return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num))
+end
+
+function string.starts(String, Start)
+ return string.sub(String, 1, string.len(Start)) == Start
+end
+
+---@class RageUIMenus
+RageUI.Menus = setmetatable({}, RageUI.Menus)
+
+---@type table
+---@return boolean
+RageUI.Menus.__call = function()
+ return true
+end
+
+---@type table
+RageUI.Menus.__index = RageUI.Menus
+
+---@type table
+RageUI.CurrentMenu = nil
+
+---@type table
+RageUI.NextMenu = nil
+
+---@type number
+RageUI.Options = 0
+
+---@type number
+RageUI.ItemOffset = 0
+
+---@type number
+RageUI.StatisticPanelCount = 0
+
+---@class UISize
+RageUI.UI = {
+ Current = "NativeUI",
+ Style = {
+ RageUI = {
+ Width = 0
+ },
+ NativeUI = {
+ Width = 0
+ }
+ }
+}
+
+---@class Settings
+RageUI.Settings = {
+ Debug = false,
+ Controls = {
+ Up = {
+ Enabled = true,
+ Active = false,
+ Pressed = false,
+ Keys = {
+ { 0, 172 },
+ { 1, 172 },
+ { 2, 172 },
+ { 0, 241 },
+ { 1, 241 },
+ { 2, 241 },
+ },
+ },
+ Down = {
+ Enabled = true,
+ Active = false,
+ Pressed = false,
+ Keys = {
+ { 0, 173 },
+ { 1, 173 },
+ { 2, 173 },
+ { 0, 242 },
+ { 1, 242 },
+ { 2, 242 },
+ },
+ },
+ Left = {
+ Enabled = true,
+ Active = false,
+ Pressed = false,
+ Keys = {
+ { 0, 174 },
+ { 1, 174 },
+ { 2, 174 },
+ },
+ },
+ Right = {
+ Enabled = true,
+ Pressed = false,
+ Active = false,
+ Keys = {
+ { 0, 175 },
+ { 1, 175 },
+ { 2, 175 },
+ },
+ },
+ SliderLeft = {
+ Enabled = true,
+ Active = false,
+ Pressed = false,
+ Keys = {
+ { 0, 174 },
+ { 1, 174 },
+ { 2, 174 },
+ },
+ },
+ SliderRight = {
+ Enabled = true,
+ Pressed = false,
+ Active = false,
+ Keys = {
+ { 0, 175 },
+ { 1, 175 },
+ { 2, 175 },
+ },
+ },
+ Select = {
+ Enabled = true,
+ Pressed = false,
+ Active = false,
+ Keys = {
+ { 0, 201 },
+ { 1, 201 },
+ { 2, 201 },
+ },
+ },
+ Back = {
+ Enabled = true,
+ Active = false,
+ Pressed = false,
+ Keys = {
+ { 0, 177 },
+ { 1, 177 },
+ { 2, 177 },
+ { 0, 199 },
+ { 1, 199 },
+ { 2, 199 },
+ },
+ },
+ Click = {
+ Enabled = true,
+ Active = false,
+ Pressed = false,
+ Keys = {
+ { 0, 24 },
+ },
+ },
+ Enabled = {
+ Controller = {
+ { 0, 2 }, -- Look Up and Down
+ { 0, 1 }, -- Look Left and Right
+ { 0, 25 }, -- Aim
+ { 0, 24 }, -- Attack
+ },
+ Keyboard = {
+ { 0, 201 }, -- Select
+ { 0, 195 }, -- X axis
+ { 0, 196 }, -- Y axis
+ { 0, 187 }, -- Down
+ { 0, 188 }, -- Up
+ { 0, 189 }, -- Left
+ { 0, 190 }, -- Right
+ { 0, 202 }, -- Back
+ { 0, 217 }, -- Select
+ { 0, 242 }, -- Scroll down
+ { 0, 241 }, -- Scroll up
+ { 0, 239 }, -- Cursor X
+ { 0, 240 }, -- Cursor Y
+ { 0, 31 }, -- Move Up and Down
+ { 0, 30 }, -- Move Left and Right
+ { 0, 21 }, -- Sprint
+ { 0, 22 }, -- Jump
+ { 0, 23 }, -- Enter
+ { 0, 75 }, -- Exit Vehicle
+ { 0, 71 }, -- Accelerate Vehicle
+ { 0, 72 }, -- Vehicle Brake
+ { 0, 59 }, -- Move Vehicle Left and Right
+ { 0, 89 }, -- Fly Yaw Left
+ { 0, 9 }, -- Fly Left and Right
+ { 0, 8 }, -- Fly Up and Down
+ { 0, 90 }, -- Fly Yaw Right
+ { 0, 76 }, -- Vehicle Handbrake
+ },
+ },
+ },
+ Audio = {
+ Id = nil,
+ Use = "NativeUI",
+ RageUI = {
+ UpDown = {
+ audioName = "HUD_FREEMODE_SOUNDSET",
+ audioRef = "NAV_UP_DOWN",
+ },
+ LeftRight = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "NAV_LEFT_RIGHT",
+ },
+ Select = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "SELECT",
+ },
+ Back = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "BACK",
+ },
+ Error = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "ERROR",
+ },
+ Slider = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "CONTINUOUS_SLIDER",
+ Id = nil
+ },
+ },
+ NativeUI = {
+ UpDown = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "NAV_UP_DOWN",
+ },
+ LeftRight = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "NAV_LEFT_RIGHT",
+ },
+ Select = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "SELECT",
+ },
+ Back = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "BACK",
+ },
+ Error = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "ERROR",
+ },
+ Slider = {
+ audioName = "HUD_FRONTEND_DEFAULT_SOUNDSET",
+ audioRef = "CONTINUOUS_SLIDER",
+ Id = nil
+ },
+ }
+ },
+ Items = {
+ Title = {
+ Background = { Width = 431, Height = 107 },
+ Text = { X = 215, Y = 20, Scale = 1.15 },
+ },
+ Subtitle = {
+ Background = { Width = 431, Height = 37 },
+ Text = { X = 8, Y = 3, Scale = 0.35 },
+ PreText = { X = 425, Y = 3, Scale = 0.35 },
+ },
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 0, Width = 431 },
+ Navigation = {
+ Rectangle = { Y = 4, Width = 431, Height = 18 },
+ Offset = 5,
+ Arrows = { Dictionary = "commonmenu", Texture = "shop_arrows_upanddown", X = 190, Y = 0, Width = 45, Height = 45 },
+ },
+ Description = {
+ Bar = { Y = 8, Width = 431, Height = 4 },
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 8, Width = 431, Height = 30 },
+ Text = { X = 8, Y = 10, Scale = 0.35 },
+ },
+ },
+ Panels = {
+ Grid = {
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 4, Width = 431, Height = 275 },
+ Grid = { Dictionary = "pause_menu_pages_char_mom_dad", Texture = "nose_grid", X = 115.5, Y = 47.5, Width = 200, Height = 200 },
+ Circle = { Dictionary = "mpinventory", Texture = "in_world_circle", X = 115.5, Y = 47.5, Width = 20, Height = 20 },
+ Text = {
+ Top = { X = 215.5, Y = 15, Scale = 0.35 },
+ Bottom = { X = 215.5, Y = 250, Scale = 0.35 },
+ Left = { X = 57.75, Y = 130, Scale = 0.35 },
+ Right = { X = 373.25, Y = 130, Scale = 0.35 },
+ },
+ },
+ Percentage = {
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 4, Width = 431, Height = 76 },
+ Bar = { X = 9, Y = 50, Width = 413, Height = 10 },
+ Text = {
+ Left = { X = 25, Y = 15, Scale = 0.35 },
+ Middle = { X = 215.5, Y = 15, Scale = 0.35 },
+ Right = { X = 398, Y = 15, Scale = 0.35 },
+ },
+ },
+ },
+}
+
+function RageUI.SetScaleformParams(scaleform, data)
+ data = data or {}
+ for k, v in pairs(data) do
+ PushScaleformMovieFunction(scaleform, v.name)
+ if v.param then
+ for _, par in pairs(v.param) do
+ if math.type(par) == "integer" then
+ PushScaleformMovieFunctionParameterInt(par)
+ elseif type(par) == "boolean" then
+ PushScaleformMovieFunctionParameterBool(par)
+ elseif math.type(par) == "float" then
+ PushScaleformMovieFunctionParameterFloat(par)
+ elseif type(par) == "string" then
+ PushScaleformMovieFunctionParameterString(par)
+ end
+ end
+ end
+ if v.func then
+ v.func()
+ end
+ PopScaleformMovieFunctionVoid()
+ end
+end
+
+function RageUI.IsMouseInBounds(X, Y, Width, Height)
+ local MX, MY = math.round(GetControlNormal(2, 239) * 1920) / 1920, math.round(GetControlNormal(2, 240) * 1080) / 1080
+ X, Y = X / 1920, Y / 1080
+ Width, Height = Width / 1920, Height / 1080
+ return (MX >= X and MX <= X + Width) and (MY > Y and MY < Y + Height)
+end
+
+function RageUI.GetSafeZoneBounds()
+ local SafeSize = GetSafeZoneSize()
+ SafeSize = math.round(SafeSize, 2)
+ SafeSize = (SafeSize * 100) - 90
+ SafeSize = 10 - SafeSize
+
+ local W, H = 1920, 1080
+
+ return { X = math.round(SafeSize * ((W / H) * 5.4)), Y = math.round(SafeSize * 5.4) }
+end
+
+function RageUI.Visible(Menu, Value)
+ if Menu ~= nil and Menu() then
+ if Value == true or Value == false then
+ if Value then
+ if RageUI.CurrentMenu ~= nil then
+ if RageUI.CurrentMenu.Closed ~= nil then
+ RageUI.CurrentMenu.Closed()
+ end
+ RageUI.CurrentMenu.Open = not Value
+ Menu:UpdateInstructionalButtons(Value);
+ Menu:UpdateCursorStyle();
+
+ end
+ RageUI.CurrentMenu = Menu
+ else
+ RageUI.CurrentMenu = nil
+ end
+ Menu.Open = Value
+ RageUI.Options = 0
+ RageUI.ItemOffset = 0
+ RageUI.LastControl = false
+ else
+ return Menu.Open
+ end
+ end
+end
+
+function RageUI.CloseAll()
+ if RageUI.CurrentMenu ~= nil then
+ local parent = RageUI.CurrentMenu.Parent
+ while parent ~= nil do
+ parent.Index = 1
+ parent.Pagination.Minimum = 1
+ parent.Pagination.Maximum = parent.Pagination.Total
+ parent = parent.Parent
+ end
+ RageUI.CurrentMenu.Index = 1
+ RageUI.CurrentMenu.Pagination.Minimum = 1
+ RageUI.CurrentMenu.Pagination.Maximum = RageUI.CurrentMenu.Pagination.Total
+ RageUI.CurrentMenu.Open = false
+ RageUI.CurrentMenu = nil
+ end
+ RageUI.Options = 0
+ RageUI.ItemOffset = 0
+ ResetScriptGfxAlign()
+end
+
+function RageUI.Banner()
+ local CurrentMenu = RageUI.CurrentMenu
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (CurrentMenu.Display.Header) then
+ RageUI.ItemsSafeZone(CurrentMenu)
+ if CurrentMenu.Sprite.Dictionary then
+ RenderSprite(CurrentMenu.Sprite.Dictionary, CurrentMenu.Sprite.Texture, CurrentMenu.X, CurrentMenu.Y, RageUI.Settings.Items.Title.Background.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Title.Background.Height, nil)
+ else
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y, RageUI.Settings.Items.Title.Background.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Title.Background.Height, CurrentMenu.Rectangle.R, CurrentMenu.Rectangle.G, CurrentMenu.Rectangle.B, CurrentMenu.Rectangle.A)
+ end
+ if (CurrentMenu.Display.Glare) then
+ local ScaleformMovie = RequestScaleformMovie("MP_MENU_GLARE")
+ ---@type number
+ local Glarewidth = RageUI.Settings.Items.Title.Background.Width
+ ---@type number
+ local Glareheight = RageUI.Settings.Items.Title.Background.Height
+ ---@type number
+ local GlareX = CurrentMenu.X / 1920 + (CurrentMenu.SafeZoneSize.X / (64.399 - (CurrentMenu.WidthOffset * 0.065731)))
+ ---@type number
+ local GlareY = CurrentMenu.Y / 1080 + CurrentMenu.SafeZoneSize.Y / 33.195020746888
+ RageUI.SetScaleformParams(ScaleformMovie, {
+ { name = "SET_DATA_SLOT", param = { GetGameplayCamRelativeHeading() } }
+ })
+ DrawScaleformMovie(ScaleformMovie, GlareX, GlareY, Glarewidth / 430, Glareheight / 100, 255, 255, 255, 255, 0)
+ end
+ RenderText(CurrentMenu.Title, CurrentMenu.X + RageUI.Settings.Items.Title.Text.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + RageUI.Settings.Items.Title.Text.Y, CurrentMenu.TitleFont, CurrentMenu.TitleScale, 255, 255, 255, 255, 1)
+ RageUI.ItemOffset = RageUI.ItemOffset + RageUI.Settings.Items.Title.Background.Height
+ end
+ end
+end
+
+function RageUI.Subtitle()
+ local CurrentMenu = RageUI.CurrentMenu
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (CurrentMenu.Display.Subtitle) then
+ RageUI.ItemsSafeZone(CurrentMenu)
+ if CurrentMenu.Subtitle ~= "" then
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + RageUI.ItemOffset, RageUI.Settings.Items.Subtitle.Background.Width + CurrentMenu.WidthOffset, RageUI.Settings.Items.Subtitle.Background.Height + CurrentMenu.SubtitleHeight, 0, 0, 0, 255)
+ RenderText(CurrentMenu.Subtitle, CurrentMenu.X + RageUI.Settings.Items.Subtitle.Text.X, CurrentMenu.Y + RageUI.Settings.Items.Subtitle.Text.Y + RageUI.ItemOffset, 0, RageUI.Settings.Items.Subtitle.Text.Scale, 245, 245, 245, 255, nil, false, false, RageUI.Settings.Items.Subtitle.Background.Width + CurrentMenu.WidthOffset)
+ if CurrentMenu.Index > CurrentMenu.Options or CurrentMenu.Index < 0 then
+ CurrentMenu.Index = 1
+ end
+ if (CurrentMenu ~= nil) then
+ if (CurrentMenu.Index > CurrentMenu.Pagination.Total) then
+ local offset = CurrentMenu.Index - CurrentMenu.Pagination.Total
+ CurrentMenu.Pagination.Minimum = 1 + offset
+ CurrentMenu.Pagination.Maximum = CurrentMenu.Pagination.Total + offset
+ else
+ CurrentMenu.Pagination.Minimum = 1
+ CurrentMenu.Pagination.Maximum = CurrentMenu.Pagination.Total
+ end
+ end
+ if CurrentMenu.PageCounter == nil then
+ RenderText(CurrentMenu.PageCounterColour .. CurrentMenu.Index .. " / " .. CurrentMenu.Options, CurrentMenu.X + RageUI.Settings.Items.Subtitle.PreText.X + CurrentMenu.WidthOffset, CurrentMenu.Y + RageUI.Settings.Items.Subtitle.PreText.Y + RageUI.ItemOffset, 0, RageUI.Settings.Items.Subtitle.PreText.Scale, 245, 245, 245, 255, 2)
+ else
+ RenderText(CurrentMenu.PageCounter, CurrentMenu.X + RageUI.Settings.Items.Subtitle.PreText.X + CurrentMenu.WidthOffset, CurrentMenu.Y + RageUI.Settings.Items.Subtitle.PreText.Y + RageUI.ItemOffset, 0, RageUI.Settings.Items.Subtitle.PreText.Scale, 245, 245, 245, 255, 2)
+ end
+ RageUI.ItemOffset = RageUI.ItemOffset + RageUI.Settings.Items.Subtitle.Background.Height
+ end
+ end
+ end
+end
+
+function RageUI.Background()
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (CurrentMenu.Display.Background) then
+ RageUI.ItemsSafeZone(CurrentMenu)
+ SetScriptGfxDrawOrder(0)
+ RenderSprite(RageUI.Settings.Items.Background.Dictionary, RageUI.Settings.Items.Background.Texture, CurrentMenu.X, CurrentMenu.Y + RageUI.Settings.Items.Background.Y + CurrentMenu.SubtitleHeight, RageUI.Settings.Items.Background.Width + CurrentMenu.WidthOffset, RageUI.ItemOffset, 0, 0, 0, 0, 255)
+ SetScriptGfxDrawOrder(1)
+ end
+ end
+end
+
+function RageUI.Description()
+ local CurrentMenu = RageUI.CurrentMenu;
+ local Description = RageUI.Settings.Items.Description;
+ if CurrentMenu ~= nil and CurrentMenu.Description ~= nil then
+ if CurrentMenu() then
+ RageUI.ItemsSafeZone(CurrentMenu)
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + Description.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Description.Bar.Width + CurrentMenu.WidthOffset, Description.Bar.Height, 0, 0, 0, 255)
+ RenderSprite(Description.Background.Dictionary, Description.Background.Texture, CurrentMenu.X, CurrentMenu.Y + Description.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Description.Background.Width + CurrentMenu.WidthOffset, CurrentMenu.DescriptionHeight, 0, 0, 0, 255)
+ RenderText(CurrentMenu.Description, CurrentMenu.X + Description.Text.X, CurrentMenu.Y + Description.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Description.Text.Scale, 255, 255, 255, 255, nil, false, false, Description.Background.Width + CurrentMenu.WidthOffset - 8.0)
+ RageUI.ItemOffset = RageUI.ItemOffset + CurrentMenu.DescriptionHeight + Description.Bar.Y
+ end
+ end
+end
+
+function RageUI.Render()
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+ if CurrentMenu.Safezone then
+ ResetScriptGfxAlign()
+ end
+
+ if (CurrentMenu.Display.InstructionalButton) then
+ if not CurrentMenu.InitScaleform then
+ CurrentMenu:UpdateInstructionalButtons(true)
+ CurrentMenu.InitScaleform = true
+ end
+ DrawScaleformMovieFullscreen(CurrentMenu.InstructionalScaleform, 255, 255, 255, 255, 0)
+ end
+ CurrentMenu.Options = RageUI.Options
+ CurrentMenu.SafeZoneSize = nil
+ RageUI.Controls()
+ RageUI.Options = 0
+ RageUI.StatisticPanelCount = 0
+ RageUI.ItemOffset = 0
+ if CurrentMenu.Controls.Back.Enabled and CurrentMenu.Closable then
+ if CurrentMenu.Controls.Back.Pressed then
+ CurrentMenu.Controls.Back.Pressed = false
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Back.audioName, Audio[Audio.Use].Back.audioRef)
+
+ if CurrentMenu.Closed ~= nil then
+ collectgarbage()
+ CurrentMenu.Closed()
+ end
+
+ if CurrentMenu.Parent ~= nil then
+ if CurrentMenu.Parent() then
+ RageUI.NextMenu = CurrentMenu.Parent
+ CurrentMenu:UpdateCursorStyle()
+ else
+ RageUI.NextMenu = nil
+ RageUI.Visible(CurrentMenu, false)
+ end
+ else
+ RageUI.NextMenu = nil
+ RageUI.Visible(CurrentMenu, false)
+ end
+ end
+ end
+ if RageUI.NextMenu ~= nil then
+ if RageUI.NextMenu() then
+ RageUI.Visible(CurrentMenu, false)
+ RageUI.Visible(RageUI.NextMenu, true)
+ CurrentMenu.Controls.Select.Active = false
+ RageUI.NextMenu = nil
+ RageUI.LastControl = false
+ end
+ end
+ end
+ end
+end
+
+function RageUI.ItemsDescription(CurrentMenu, Description, Selected)
+ ---@type table
+ if Description ~= "" or Description ~= nil then
+ local SettingsDescription = RageUI.Settings.Items.Description;
+ if Selected and CurrentMenu.Description ~= Description then
+ CurrentMenu.Description = Description or nil
+ ---@type number
+ local DescriptionLineCount = GetLineCount(CurrentMenu.Description, CurrentMenu.X + SettingsDescription.Text.X, CurrentMenu.Y + SettingsDescription.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsDescription.Text.Scale, 255, 255, 255, 255, nil, false, false, SettingsDescription.Background.Width + (CurrentMenu.WidthOffset - 5.0))
+ if DescriptionLineCount > 1 then
+ CurrentMenu.DescriptionHeight = SettingsDescription.Background.Height * DescriptionLineCount
+ else
+ CurrentMenu.DescriptionHeight = SettingsDescription.Background.Height + 7
+ end
+ end
+ end
+end
+
+function RageUI.ItemsMouseBounds(CurrentMenu, Selected, Option, SettingsButton)
+ ---@type boolean
+ local Hovered = false
+ Hovered = RageUI.IsMouseInBounds(CurrentMenu.X + CurrentMenu.SafeZoneSize.X, CurrentMenu.Y + SettingsButton.Rectangle.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.Rectangle.Width + CurrentMenu.WidthOffset, SettingsButton.Rectangle.Height)
+ if Hovered and not Selected then
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + SettingsButton.Rectangle.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.Rectangle.Width + CurrentMenu.WidthOffset, SettingsButton.Rectangle.Height, 255, 255, 255, 20)
+ if CurrentMenu.Controls.Click.Active then
+ CurrentMenu.Index = Option
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Error.audioName, Audio[Audio.Use].Error.audioRef)
+ end
+ end
+ return Hovered;
+end
+
+function RageUI.ItemsSafeZone(CurrentMenu)
+ if not CurrentMenu.SafeZoneSize then
+ CurrentMenu.SafeZoneSize = { X = 0, Y = 0 }
+ if CurrentMenu.Safezone then
+ CurrentMenu.SafeZoneSize = RageUI.GetSafeZoneBounds()
+ SetScriptGfxAlign(76, 84)
+ SetScriptGfxAlignParams(0, 0, 0, 0)
+ end
+ end
+end
+
+function RageUI.CurrentIsEqualTo(Current, To, Style, DefaultStyle)
+ return Current == To and Style or DefaultStyle or {};
+end
+
+function RageUI.IsVisible(Menu, Items, Panels)
+ if (RageUI.Visible(Menu)) and (UpdateOnscreenKeyboard() ~= 0) and (UpdateOnscreenKeyboard() ~= 3) then
+ RageUI.Banner()
+ RageUI.Subtitle()
+ if (Items ~= nil) then
+ Items()
+ end
+ RageUI.Background();
+ RageUI.Navigation();
+ RageUI.Description();
+ if (Panels ~= nil) then
+ Panels()
+ end
+ RageUI.Render()
+ end
+end
+
+---SetStyleAudio
+---@param StyleAudio string
+---@return void
+---@public
+function RageUI.SetStyleAudio(StyleAudio)
+ RageUI.Settings.Audio.Use = StyleAudio or "RageUI"
+end
+
+function RageUI.GetStyleAudio()
+ return RageUI.Settings.Audio.Use or "RageUI"
+end
+
diff --git a/resources/RageUI/menu/elements/ItemsBadge.lua b/resources/RageUI/menu/elements/ItemsBadge.lua
new file mode 100644
index 000000000..571f75fc5
--- /dev/null
+++ b/resources/RageUI/menu/elements/ItemsBadge.lua
@@ -0,0 +1,286 @@
+RageUI.BadgeStyle = {
+ -- DEFAULT BADGE
+ None = function()
+ return {
+ BadgeTexture = "",
+ BadgeDictionary = "commonmenu"
+ }
+ end,
+ BronzeMedal = function()
+ return {
+ BadgeTexture = "mp_medal_bronze",
+ }
+ end,
+ GoldMedal = function()
+ return {
+ BadgeTexture = "mp_medal_gold",
+ }
+ end,
+ SilverMedal = function()
+ return {
+ BadgeTexture = "medal_silver",
+ }
+ end,
+ Alert = function()
+ return {
+ BadgeTexture = "mp_alerttriangle",
+ }
+ end,
+ Crown = function(Selected)
+ return {
+ BadgeTexture = "mp_hostcrown",
+ BadgeColour = Selected and { R = 0, G = 0, B = 0, A = 255 } or { R = 255, G = 255, B = 255, A = 255 }
+ }
+ end,
+ Ammo = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_ammo_icon_b" or "shop_ammo_icon_a",
+ }
+ end,
+ Armour = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_armour_icon_b" or "shop_armour_icon_a",
+ }
+ end,
+ Barber = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_barber_icon_b" or "shop_barber_icon_a",
+ }
+ end,
+ Clothes = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_clothing_icon_b" or "shop_clothing_icon_a",
+ }
+ end,
+ Franklin = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_franklin_icon_b" or "shop_franklin_icon_a",
+ }
+ end,
+ Bike = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_garage_bike_icon_b" or "shop_garage_bike_icon_a",
+ }
+ end,
+ Car = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_garage_icon_b" or "shop_garage_icon_a",
+ }
+ end,
+ Boat = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_boat_black" or "mp_specitem_boat",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Heli = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_heli_black" or "mp_specitem_heli",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Plane = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_plane_black" or "mp_specitem_plane",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ BoatPickup = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_boatpickup_black" or "mp_specitem_boatpickup",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Card = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_keycard_black" or "mp_specitem_keycard",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Gun = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_gunclub_icon_b" or "shop_gunclub_icon_a",
+ }
+ end,
+ Heart = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_health_icon_b" or "shop_health_icon_a",
+ }
+ end,
+ Makeup = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_makeup_icon_b" or "shop_makeup_icon_a",
+ }
+ end,
+ Mask = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_mask_icon_b" or "shop_mask_icon_a",
+ }
+ end,
+ Michael = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_michael_icon_b" or "shop_michael_icon_a",
+ }
+ end,
+ Star = function()
+ return {
+ BadgeTexture = "shop_new_star",
+ }
+ end,
+ Tattoo = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_tattoos_icon_b" or "shop_tattoos_icon_a",
+ }
+ end,
+ Trevor = function(Selected)
+ return {
+ BadgeTexture = Selected and "shop_trevor_icon_b" or "shop_trevor_icon_a",
+ }
+ end,
+ Lock = function(Selected)
+ return {
+ BadgeTexture = "shop_lock",
+ BadgeColour = Selected and { R = 0, G = 0, B = 0, A = 255 } or { R = 255, G = 255, B = 255, A = 255 }
+ }
+ end,
+ Tick = function(Selected)
+ return {
+ BadgeTexture = "shop_tick_icon",
+ BadgeColour = Selected and { R = 0, G = 0, B = 0, A = 255 } or { R = 255, G = 255, B = 255, A = 255 }
+ }
+ end,
+ Key = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_cuffkeys_black" or "mp_specitem_cuffkeys",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Coke = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_coke_black" or "mp_specitem_coke",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Heroin = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_heroin_black" or "mp_specitem_heroin",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Meth = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_meth_black" or "mp_specitem_meth",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Weed = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_weed_black" or "mp_specitem_weed",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Package = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_package_black" or "mp_specitem_package",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ Cash = function(Selected)
+ return {
+ BadgeTexture = Selected and "mp_specitem_cash_black" or "mp_specitem_cash",
+ BadgeDictionary = "mpinventory"
+ }
+ end,
+ RP = function(Selected)
+ return {
+ BadgeTexture = "mp_anim_rp",
+ BadgeDictionary = "mphud"
+ }
+ end,
+ LSPD = function()
+ return {
+ BadgeTexture = "mpgroundlogo_cops",
+ BadgeDictionary = "3dtextures"
+ }
+ end,
+ Vagos = function()
+ return {
+ BadgeTexture = "mpgroundlogo_vagos",
+ BadgeDictionary = "3dtextures"
+ }
+ end,
+ Bikers = function()
+ return {
+ BadgeTexture = "mpgroundlogo_bikers",
+ BadgeDictionary = "3dtextures"
+ }
+ end,
+
+ -- CASINO
+ Badbeat = function()
+ return {
+ BadgeTexture = "badbeat",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ CashingOut = function()
+ return {
+ BadgeTexture = "cashingout",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ FullHouse = function()
+ return {
+ BadgeTexture = "fullhouse",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ HighRoller = function()
+ return {
+ BadgeTexture = "highroller",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ HouseKeeping = function()
+ return {
+ BadgeTexture = "housekeeping",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ LooseCheng = function()
+ return {
+ BadgeTexture = "loosecheng",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ LuckyLucky = function()
+ return {
+ BadgeTexture = "luckylucky",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ PlayToWin = function()
+ return {
+ BadgeTexture = "playtowin",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ StraightFlush = function()
+ return {
+ BadgeTexture = "straightflush",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ StrongArmTactics = function()
+ return {
+ BadgeTexture = "strongarmtactics",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+ TopPair = function()
+ return {
+ BadgeTexture = "toppair",
+ BadgeDictionary = "mpawardcasino"
+ }
+ end,
+}
\ No newline at end of file
diff --git a/resources/RageUI/menu/elements/ItemsColour.lua b/resources/RageUI/menu/elements/ItemsColour.lua
new file mode 100644
index 000000000..3e620958f
--- /dev/null
+++ b/resources/RageUI/menu/elements/ItemsColour.lua
@@ -0,0 +1,225 @@
+---
+--- Generated by EmmyLua(https://github.com/EmmyLua)
+--- Created by Dylan Malandain.
+--- DateTime: 24/07/2019 02:26
+---
+
+RageUI.ItemsColour = {
+ PureWhite = { 255, 255, 255, 255 },
+ White = { 240, 240, 240, 255 },
+ Black = { 0, 0, 0, 255 },
+ Grey = { 155, 155, 155, 255 },
+ GreyLight = { 205, 205, 205, 255 },
+ GreyDark = { 77, 77, 77, 255 },
+ Red = { 224, 50, 50, 255 },
+ RedLight = { 240, 153, 153, 255 },
+ RedDark = { 112, 25, 25, 255 },
+ Blue = { 93, 182, 229, 255 },
+ BlueLight = { 174, 219, 242, 255 },
+ BlueDark = { 47, 92, 115, 255 },
+ Yellow = { 240, 200, 80, 255 },
+ YellowLight = { 254, 235, 169, 255 },
+ YellowDark = { 126, 107, 41, 255 },
+ Orange = { 255, 133, 85, 255 },
+ OrangeLight = { 255, 194, 170, 255 },
+ OrangeDark = { 127, 66, 42, 255 },
+ Green = { 114, 204, 114, 255 },
+ GreenLight = { 185, 230, 185, 255 },
+ GreenDark = { 57, 102, 57, 255 },
+ Purple = { 132, 102, 226, 255 },
+ PurpleLight = { 192, 179, 239, 255 },
+ PurpleDark = { 67, 57, 111, 255 },
+ Pink = { 203, 54, 148, 255 },
+ RadarHealth = { 53, 154, 71, 255 },
+ RadarArmour = { 93, 182, 229, 255 },
+ RadarDamage = { 235, 36, 39, 255 },
+ NetPlayer1 = { 194, 80, 80, 255 },
+ NetPlayer2 = { 156, 110, 175, 255 },
+ NetPlayer3 = { 255, 123, 196, 255 },
+ NetPlayer4 = { 247, 159, 123, 255 },
+ NetPlayer5 = { 178, 144, 132, 255 },
+ NetPlayer6 = { 141, 206, 167, 255 },
+ NetPlayer7 = { 113, 169, 175, 255 },
+ NetPlayer8 = { 211, 209, 231, 255 },
+ NetPlayer9 = { 144, 127, 153, 255 },
+ NetPlayer10 = { 106, 196, 191, 255 },
+ NetPlayer11 = { 214, 196, 153, 255 },
+ NetPlayer12 = { 234, 142, 80, 255 },
+ NetPlayer13 = { 152, 203, 234, 255 },
+ NetPlayer14 = { 178, 98, 135, 255 },
+ NetPlayer15 = { 144, 142, 122, 255 },
+ NetPlayer16 = { 166, 117, 94, 255 },
+ NetPlayer17 = { 175, 168, 168, 255 },
+ NetPlayer18 = { 232, 142, 155, 255 },
+ NetPlayer19 = { 187, 214, 91, 255 },
+ NetPlayer20 = { 12, 123, 86, 255 },
+ NetPlayer21 = { 123, 196, 255, 255 },
+ NetPlayer22 = { 171, 60, 230, 255 },
+ NetPlayer23 = { 206, 169, 13, 255 },
+ NetPlayer24 = { 71, 99, 173, 255 },
+ NetPlayer25 = { 42, 166, 185, 255 },
+ NetPlayer26 = { 186, 157, 125, 255 },
+ NetPlayer27 = { 201, 225, 255, 255 },
+ NetPlayer28 = { 240, 240, 150, 255 },
+ NetPlayer29 = { 237, 140, 161, 255 },
+ NetPlayer30 = { 249, 138, 138, 255 },
+ NetPlayer31 = { 252, 239, 166, 255 },
+ NetPlayer32 = { 240, 240, 240, 255 },
+ SimpleBlipDefault = { 159, 201, 166, 255 },
+ MenuBlue = { 140, 140, 140, 255 },
+ MenuGreyLight = { 140, 140, 140, 255 },
+ MenuBlueExtraDark = { 40, 40, 40, 255 },
+ MenuYellow = { 240, 160, 0, 255 },
+ MenuYellowDark = { 240, 160, 0, 255 },
+ MenuGreen = { 240, 160, 0, 255 },
+ MenuGrey = { 140, 140, 140, 255 },
+ MenuGreyDark = { 60, 60, 60, 255 },
+ MenuHighlight = { 30, 30, 30, 255 },
+ MenuStandard = { 140, 140, 140, 255 },
+ MenuDimmed = { 75, 75, 75, 255 },
+ MenuExtraDimmed = { 50, 50, 50, 255 },
+ BriefTitle = { 95, 95, 95, 255 },
+ MidGreyMp = { 100, 100, 100, 255 },
+ NetPlayer1Dark = { 93, 39, 39, 255 },
+ NetPlayer2Dark = { 77, 55, 89, 255 },
+ NetPlayer3Dark = { 124, 62, 99, 255 },
+ NetPlayer4Dark = { 120, 80, 80, 255 },
+ NetPlayer5Dark = { 87, 72, 66, 255 },
+ NetPlayer6Dark = { 74, 103, 83, 255 },
+ NetPlayer7Dark = { 60, 85, 88, 255 },
+ NetPlayer8Dark = { 105, 105, 64, 255 },
+ NetPlayer9Dark = { 72, 63, 76, 255 },
+ NetPlayer10Dark = { 53, 98, 95, 255 },
+ NetPlayer11Dark = { 107, 98, 76, 255 },
+ NetPlayer12Dark = { 117, 71, 40, 255 },
+ NetPlayer13Dark = { 76, 101, 117, 255 },
+ NetPlayer14Dark = { 65, 35, 47, 255 },
+ NetPlayer15Dark = { 72, 71, 61, 255 },
+ NetPlayer16Dark = { 85, 58, 47, 255 },
+ NetPlayer17Dark = { 87, 84, 84, 255 },
+ NetPlayer18Dark = { 116, 71, 77, 255 },
+ NetPlayer19Dark = { 93, 107, 45, 255 },
+ NetPlayer20Dark = { 6, 61, 43, 255 },
+ NetPlayer21Dark = { 61, 98, 127, 255 },
+ NetPlayer22Dark = { 85, 30, 115, 255 },
+ NetPlayer23Dark = { 103, 84, 6, 255 },
+ NetPlayer24Dark = { 35, 49, 86, 255 },
+ NetPlayer25Dark = { 21, 83, 92, 255 },
+ NetPlayer26Dark = { 93, 98, 62, 255 },
+ NetPlayer27Dark = { 100, 112, 127, 255 },
+ NetPlayer28Dark = { 120, 120, 75, 255 },
+ NetPlayer29Dark = { 152, 76, 93, 255 },
+ NetPlayer30Dark = { 124, 69, 69, 255 },
+ NetPlayer31Dark = { 10, 43, 50, 255 },
+ NetPlayer32Dark = { 95, 95, 10, 255 },
+ Bronze = { 180, 130, 97, 255 },
+ Silver = { 150, 153, 161, 255 },
+ Gold = { 214, 181, 99, 255 },
+ Platinum = { 166, 221, 190, 255 },
+ Gang1 = { 29, 100, 153, 255 },
+ Gang2 = { 214, 116, 15, 255 },
+ Gang3 = { 135, 125, 142, 255 },
+ Gang4 = { 229, 119, 185, 255 },
+ SameCrew = { 252, 239, 166, 255 },
+ Freemode = { 45, 110, 185, 255 },
+ PauseBg = { 0, 0, 0, 255 },
+ Friendly = { 93, 182, 229, 255 },
+ Enemy = { 194, 80, 80, 255 },
+ Location = { 240, 200, 80, 255 },
+ Pickup = { 114, 204, 114, 255 },
+ PauseSingleplayer = { 114, 204, 114, 255 },
+ FreemodeDark = { 22, 55, 92, 255 },
+ InactiveMission = { 154, 154, 154, 255 },
+ Damage = { 194, 80, 80, 255 },
+ PinkLight = { 252, 115, 201, 255 },
+ PmMitemHighlight = { 252, 177, 49, 255 },
+ ScriptVariable = { 0, 0, 0, 255 },
+ Yoga = { 109, 247, 204, 255 },
+ Tennis = { 241, 101, 34, 255 },
+ Golf = { 214, 189, 97, 255 },
+ ShootingRange = { 112, 25, 25, 255 },
+ FlightSchool = { 47, 92, 115, 255 },
+ NorthBlue = { 93, 182, 229, 255 },
+ SocialClub = { 234, 153, 28, 255 },
+ PlatformBlue = { 11, 55, 123, 255 },
+ PlatformGreen = { 146, 200, 62, 255 },
+ PlatformGrey = { 234, 153, 28, 255 },
+ FacebookBlue = { 66, 89, 148, 255 },
+ IngameBg = { 0, 0, 0, 255 },
+ Darts = { 114, 204, 114, 255 },
+ Waypoint = { 164, 76, 242, 255 },
+ Michael = { 101, 180, 212, 255 },
+ Franklin = { 171, 237, 171, 255 },
+ Trevor = { 255, 163, 87, 255 },
+ GolfP1 = { 240, 240, 240, 255 },
+ GolfP2 = { 235, 239, 30, 255 },
+ GolfP3 = { 255, 149, 14, 255 },
+ GolfP4 = { 246, 60, 161, 255 },
+ WaypointLight = { 210, 166, 249, 255 },
+ WaypointDark = { 82, 38, 121, 255 },
+ PanelLight = { 0, 0, 0, 255 },
+ MichaelDark = { 72, 103, 116, 255 },
+ FranklinDark = { 85, 118, 85, 255 },
+ TrevorDark = { 127, 81, 43, 255 },
+ ObjectiveRoute = { 240, 200, 80, 255 },
+ PausemapTint = { 0, 0, 0, 255 },
+ PauseDeselect = { 100, 100, 100, 255 },
+ PmWeaponsPurchasable = { 45, 110, 185, 255 },
+ PmWeaponsLocked = { 240, 240, 240, 255 },
+ ScreenBg = { 0, 0, 0, 255 },
+ Chop = { 224, 50, 50, 255 },
+ PausemapTintHalf = { 0, 0, 0, 255 },
+ NorthBlueOfficial = { 0, 71, 133, 255 },
+ ScriptVariable2 = { 0, 0, 0, 255 },
+ H = { 33, 118, 37, 255 },
+ HDark = { 37, 102, 40, 255 },
+ T = { 234, 153, 28, 255 },
+ TDark = { 225, 140, 8, 255 },
+ HShard = { 20, 40, 0, 255 },
+ ControllerMichael = { 48, 255, 255, 255 },
+ ControllerFranklin = { 48, 255, 0, 255 },
+ ControllerTrevor = { 176, 80, 0, 255 },
+ ControllerChop = { 127, 0, 0, 255 },
+ VideoEditorVideo = { 53, 166, 224, 255 },
+ VideoEditorAudio = { 162, 79, 157, 255 },
+ VideoEditorText = { 104, 192, 141, 255 },
+ HbBlue = { 29, 100, 153, 255 },
+ HbYellow = { 234, 153, 28, 255 },
+ VideoEditorScore = { 240, 160, 1, 255 },
+ VideoEditorAudioFadeout = { 59, 34, 57, 255 },
+ VideoEditorTextFadeout = { 41, 68, 53, 255 },
+ VideoEditorScoreFadeout = { 82, 58, 10, 255 },
+ HeistBackground = { 37, 102, 40, 255 },
+ VideoEditorAmbient = { 240, 200, 80, 255 },
+ VideoEditorAmbientFadeout = { 80, 70, 34, 255 },
+ Gb = { 255, 133, 85, 255 },
+ G = { 255, 194, 170, 255 },
+ B = { 255, 133, 85, 255 },
+ LowFlow = { 240, 200, 80, 255 },
+ LowFlowDark = { 126, 107, 41, 255 },
+ G1 = { 247, 159, 123, 255 },
+ G2 = { 226, 134, 187, 255 },
+ G3 = { 239, 238, 151, 255 },
+ G4 = { 113, 169, 175, 255 },
+ G5 = { 160, 140, 193, 255 },
+ G6 = { 141, 206, 167, 255 },
+ G7 = { 181, 214, 234, 255 },
+ G8 = { 178, 144, 132, 255 },
+ G9 = { 0, 132, 114, 255 },
+ G10 = { 216, 85, 117, 255 },
+ G11 = { 30, 100, 152, 255 },
+ G12 = { 43, 181, 117, 255 },
+ G13 = { 233, 141, 79, 255 },
+ G14 = { 137, 210, 215, 255 },
+ G15 = { 134, 125, 141, 255 },
+ Adversary = { 109, 34, 33, 255 },
+ DegenRed = { 255, 0, 0, 255 },
+ DegenYellow = { 255, 255, 0, 255 },
+ DegenGreen = { 0, 255, 0, 255 },
+ DegenCyan = { 0, 255, 255, 255 },
+ DegenBlue = { 0, 0, 255, 255 },
+ DegenMagenta = { 255, 0, 255, 255 },
+ Stunt1 = { 38, 136, 234, 255 },
+ Stunt2 = { 224, 50, 50, 255 },
+}
+
diff --git a/resources/RageUI/menu/elements/PanelColour.lua b/resources/RageUI/menu/elements/PanelColour.lua
new file mode 100644
index 000000000..86707c2a1
--- /dev/null
+++ b/resources/RageUI/menu/elements/PanelColour.lua
@@ -0,0 +1,69 @@
+RageUI.PanelColour = {
+ HairCut = {
+ { 22, 19, 19 }, -- 0
+ { 30, 28, 25 }, -- 1
+ { 76, 56, 45 }, -- 2
+ { 69, 34, 24 }, -- 3
+ { 123, 59, 31 }, -- 4
+ { 149, 68, 35 }, -- 5
+ { 165, 87, 50 }, -- 6
+ { 175, 111, 72 }, -- 7
+ { 159, 105, 68 }, -- 8
+ { 198, 152, 108 }, -- 9
+ { 213, 170, 115 }, -- 10
+ { 223, 187, 132 }, -- 11
+ { 202, 164, 110 }, -- 12
+ { 238, 204, 130 }, -- 13
+ { 229, 190, 126 }, -- 14
+ { 250, 225, 167 }, -- 15
+ { 187, 140, 96 }, -- 16
+ { 163, 92, 60 }, -- 17
+ { 144, 52, 37 }, -- 18
+ { 134, 21, 17 }, -- 19
+ { 164, 24, 18 }, -- 20
+ { 195, 33, 24 }, -- 21
+ { 221, 69, 34 }, -- 22
+ { 229, 71, 30 }, -- 23
+ { 208, 97, 56 }, -- 24
+ { 113, 79, 38 }, -- 25
+ { 132, 107, 95 }, -- 26
+ { 185, 164, 150 }, -- 27
+ { 218, 196, 180 }, -- 28
+ { 247, 230, 217 }, -- 29
+ { 102, 72, 93 }, -- 30
+ { 162, 105, 138 }, -- 31
+ { 171, 174, 11 }, -- 32
+ { 239, 61, 200 }, -- 33
+ { 255, 69, 152 }, -- 34
+ { 255, 178, 191 }, -- 35
+ { 12, 168, 146 }, -- 36
+ { 8, 146, 165 }, -- 37
+ { 11, 82, 134 }, -- 38
+ { 118, 190, 117 }, -- 39
+ { 52, 156, 104 }, -- 40
+ { 22, 86, 85 }, -- 41
+ { 152, 177, 40 }, -- 42
+ { 127, 162, 23 }, -- 43
+ { 241, 200, 98 }, -- 44
+ { 238, 178, 16 }, -- 45
+ { 224, 134, 14 }, -- 46
+ { 247, 157, 15 }, -- 47
+ { 243, 143, 16 }, -- 48
+ { 231, 70, 15 }, -- 49
+ { 255, 101, 21 }, -- 50
+ { 254, 91, 34 }, -- 51
+ { 252, 67, 21 }, -- 52
+ { 196, 12, 15 }, -- 53
+ { 143, 10, 14 }, -- 54
+ { 44, 27, 22 }, -- 55
+ { 80, 51, 37 }, -- 56
+ { 98, 54, 37 }, -- 57
+ { 60, 31, 24 }, -- 58
+ { 69, 43, 32 }, -- 59
+ { 8, 10, 14 }, -- 60
+ { 212, 185, 158 }, -- 61
+ { 212, 185, 158 }, -- 62
+ { 213, 170, 115 }, -- 63
+ },
+}
+
diff --git a/resources/RageUI/menu/items/UIButton.lua b/resources/RageUI/menu/items/UIButton.lua
new file mode 100644
index 000000000..89ff37812
--- /dev/null
+++ b/resources/RageUI/menu/items/UIButton.lua
@@ -0,0 +1,117 @@
+---@type table
+local SettingsButton = {
+ Rectangle = { Y = 0, Width = 431, Height = 38 },
+ Text = { X = 8, Y = 3, Scale = 0.33 },
+ LeftBadge = { Y = -2, Width = 40, Height = 40 },
+ RightBadge = { X = 385, Y = -2, Width = 40, Height = 40 },
+ RightText = { X = 420, Y = 4, Scale = 0.35 },
+ SelectedSprite = { Dictionary = "commonmenu", Texture = "gradient_nav", Y = 0, Width = 431, Height = 38 },
+}
+
+---ButtonWithStyle
+---@param Label string
+---@param Description string
+---@param Style table
+---@param Enabled boolean
+---@param Callback function
+---@param Submenu table
+---@return nil
+---@public
+function RageUI.Button(Label, Description, Style, Enabled, Callback, Submenu)
+
+
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+
+ ---@type number
+ local Option = RageUI.Options + 1
+
+ if CurrentMenu.Pagination.Minimum <= Option and CurrentMenu.Pagination.Maximum >= Option then
+
+ ---@type boolean
+ local Selected = CurrentMenu.Index == Option
+
+ RageUI.ItemsSafeZone(CurrentMenu)
+
+ local LeftBadgeOffset = ((Style.LeftBadge == RageUI.BadgeStyle.None or tonumber(Style.LeftBadge) == nil) and 0 or 27)
+ local RightBadgeOffset = ((Style.RightBadge == RageUI.BadgeStyle.None or tonumber(Style.RightBadge) == nil) and 0 or 32)
+
+ local Hovered = false;
+ if Style.Color ~= nil then
+ if Style.Color.BackgroundColor ~= nil then
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height, Style.Color.BackgroundColor[1], Style.Color.BackgroundColor[2], Style.Color.BackgroundColor[3])
+ end
+ end
+ ---@type boolean
+ if CurrentMenu.EnableMouse == true then
+ Hovered = RageUI.ItemsMouseBounds(CurrentMenu, Selected, Option, SettingsButton);
+ end
+ if Selected then
+ if Style.Color == nil then
+ RenderSprite(SettingsButton.SelectedSprite.Dictionary, SettingsButton.SelectedSprite.Texture, CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height)
+ end
+
+ if Style.Color ~= nil and Style.Color.HightLightColor ~= nil then
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height, Style.Color.HightLightColor[1], Style.Color.HightLightColor[2], Style.Color.HightLightColor[3])
+ else
+ RenderSprite(SettingsButton.SelectedSprite.Dictionary, SettingsButton.SelectedSprite.Texture, CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height)
+ end
+ end
+
+ if type(Style) == 'table' then
+ if Style.LeftBadge ~= nil then
+ if Style.LeftBadge ~= RageUI.BadgeStyle.None and tonumber(Style.LeftBadge) ~= nil then
+ RenderSprite(RageUI.GetBadgeDictionary(Style.LeftBadge, Selected), RageUI.GetBadgeTexture(Style.LeftBadge, Selected), CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, RageUI.GetBadgeColour(Style.LeftBadge, Selected))
+ end
+ end
+ if Style.RightBadge ~= nil then
+ if Style.RightBadge ~= RageUI.BadgeStyle.None and tonumber(Style.RightBadge) ~= nil then
+ RenderSprite(RageUI.GetBadgeDictionary(Style.RightBadge, Selected), RageUI.GetBadgeTexture(Style.RightBadge, Selected), CurrentMenu.X + SettingsButton.RightBadge.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.RightBadge.Width, SettingsButton.RightBadge.Height, 0, RageUI.GetBadgeColour(Style.RightBadge, Selected))
+ end
+ end
+ end
+
+ if Enabled == true or Enabled == nil then
+ if Selected then
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 0, 0, 0, Style.RightLabelOpacity or 255, 2)
+ end
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 0, 0, 0, 255)
+ else
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 245, 245, 245, Style.RightLabelOpacity or 255, 2)
+ end
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 245, 245, 245, 255)
+ end
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 163, 159, 148, 255)
+ end
+
+ RageUI.ItemOffset = RageUI.ItemOffset + SettingsButton.Rectangle.Height
+
+ RageUI.ItemsDescription(CurrentMenu, Description, Selected);
+
+ if (Enabled) then
+ if Selected and (CurrentMenu.Controls.Select.Active or (Hovered and CurrentMenu.Controls.Click.Active)) then
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef)
+ if (Callback.onSelected ~= nil) and (Selected) then
+ Callback.onSelected();
+ end
+ if Submenu ~= nil then
+ if Submenu() then
+ RageUI.NextMenu = Submenu
+ end
+ end
+ end
+ end
+ end
+ RageUI.Options = RageUI.Options + 1
+
+ end
+ end
+
+end
diff --git a/resources/RageUI/menu/items/UICheckBox.lua b/resources/RageUI/menu/items/UICheckBox.lua
new file mode 100644
index 000000000..b44b8c5f3
--- /dev/null
+++ b/resources/RageUI/menu/items/UICheckBox.lua
@@ -0,0 +1,183 @@
+---@type table
+local SettingsButton = {
+ Rectangle = { Y = 0, Width = 431, Height = 38 },
+ Text = { X = 8, Y = 3, Scale = 0.33 },
+ LeftBadge = { Y = -2, Width = 40, Height = 40 },
+ RightBadge = { X = 385, Y = -2, Width = 40, Height = 40 },
+ RightText = { X = 420, Y = 4, Scale = 0.35 },
+ SelectedSprite = { Dictionary = "commonmenu", Texture = "gradient_nav", Y = 0, Width = 431, Height = 38 },
+}
+
+---@type table
+local SettingsCheckbox = {
+ Dictionary = "commonmenu", Textures = {
+ "shop_box_blankb", -- 1
+ "shop_box_tickb", -- 2
+ "shop_box_blank", -- 3
+ "shop_box_tick", -- 4
+ "shop_box_crossb", -- 5
+ "shop_box_cross", -- 6
+ },
+ X = 380, Y = -6, Width = 50, Height = 50
+}
+
+RageUI.CheckboxStyle = {
+ Tick = 1,
+ Cross = 2
+}
+
+---StyleCheckBox
+---@param Selected number
+---@param Checked boolean
+---@param Box number
+---@param BoxSelect number
+---@return nil
+local function StyleCheckBox(Selected, Checked, Box, BoxSelect, OffSet)
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+ if OffSet == nil then
+ OffSet = 0
+ end
+ if Selected then
+ if Checked then
+ RenderSprite(SettingsCheckbox.Dictionary, SettingsCheckbox.Textures[Box], CurrentMenu.X + SettingsCheckbox.X + CurrentMenu.WidthOffset - OffSet, CurrentMenu.Y + SettingsCheckbox.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsCheckbox.Width, SettingsCheckbox.Height)
+ else
+ RenderSprite(SettingsCheckbox.Dictionary, SettingsCheckbox.Textures[1], CurrentMenu.X + SettingsCheckbox.X + CurrentMenu.WidthOffset - OffSet, CurrentMenu.Y + SettingsCheckbox.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsCheckbox.Width, SettingsCheckbox.Height)
+ end
+ else
+ if Checked then
+ RenderSprite(SettingsCheckbox.Dictionary, SettingsCheckbox.Textures[BoxSelect], CurrentMenu.X + SettingsCheckbox.X + CurrentMenu.WidthOffset - OffSet, CurrentMenu.Y + SettingsCheckbox.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsCheckbox.Width, SettingsCheckbox.Height)
+ else
+ RenderSprite(SettingsCheckbox.Dictionary, SettingsCheckbox.Textures[3], CurrentMenu.X + SettingsCheckbox.X + CurrentMenu.WidthOffset - OffSet, CurrentMenu.Y + SettingsCheckbox.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsCheckbox.Width, SettingsCheckbox.Height)
+ end
+ end
+end
+
+
+function RageUI.Checkbox(Label, Description, Checked, Style, Actions)
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+
+ ---@type number
+ local Option = RageUI.Options + 1
+ if CurrentMenu.Pagination.Minimum <= Option and CurrentMenu.Pagination.Maximum >= Option then
+ ---@type number
+ local Selected = CurrentMenu.Index == Option
+ local LeftBadgeOffset = ((Style.LeftBadge == RageUI.BadgeStyle.None or Style.LeftBadge == nil) and 0 or 27)
+ local RightBadgeOffset = ((Style.RightBadge == RageUI.BadgeStyle.None or Style.RightBadge == nil) and 0 or 32)
+ local BoxOffset = 0
+ RageUI.ItemsSafeZone(CurrentMenu)
+
+ local Hovered = false;
+
+ ---@type boolean
+ if CurrentMenu.EnableMouse == true and (CurrentMenu.CursorStyle == 0) or (CurrentMenu.CursorStyle == 1) then
+ Hovered = RageUI.ItemsMouseBounds(CurrentMenu, Selected, Option, SettingsButton);
+ end
+ if Selected then
+ RenderSprite(SettingsButton.SelectedSprite.Dictionary, SettingsButton.SelectedSprite.Texture, CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height)
+ end
+
+ if type(Style) == "table" then
+ if Style.Enabled == true or Style.Enabled == nil then
+ if Selected then
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 0, 0, 0, 255)
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 245, 245, 245, 255)
+ end
+ if type(Style) == 'table' then
+ if Style.LeftBadge ~= nil then
+ if Style.LeftBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.LeftBadge(Selected)
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+ if Style.RightBadge ~= nil then
+ if Style.RightBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.RightBadge(Selected)
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X + SettingsButton.RightBadge.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.RightBadge.Width, SettingsButton.RightBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+ end
+ else
+ ---@type table
+ local LeftBadge = RageUI.BadgeStyle.Lock
+ ---@type number
+ local LeftBadgeOffset = ((LeftBadge == RageUI.BadgeStyle.None or LeftBadge == nil) and 0 or 27)
+
+ if Selected then
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 0, 0, 0, 255)
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 163, 159, 148, 255)
+ end
+
+ if LeftBadge ~= RageUI.BadgeStyle.None and LeftBadge ~= nil then
+ local BadgeData = LeftBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour.A or 255)
+ end
+ end
+
+ if Style.Enabled == true or Style.Enabled == nil then
+ if Selected then
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 0, 0, 0, 255, 2)
+ BoxOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ end
+ else
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 245, 245, 245, 255, 2)
+ BoxOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ end
+ end
+ end
+
+ BoxOffset = RightBadgeOffset + BoxOffset
+ if Style.Style ~= nil then
+ if Style.Style == RageUI.CheckboxStyle.Tick then
+ StyleCheckBox(Selected, Checked, 2, 4, BoxOffset)
+ elseif Style.Style == RageUI.CheckboxStyle.Cross then
+ StyleCheckBox(Selected, Checked, 5, 6, BoxOffset)
+ else
+ StyleCheckBox(Selected, Checked, 2, 4, BoxOffset)
+ end
+ else
+ StyleCheckBox(Selected, Checked, 2, 4, BoxOffset)
+ end
+
+ if Selected and (CurrentMenu.Controls.Select.Active or (Hovered and CurrentMenu.Controls.Click.Active)) and (Style.Enabled == true or Style.Enabled == nil) then
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef)
+ Checked = not Checked
+ if (Checked) then
+ if (Actions.onChecked ~= nil) then
+ Actions.onChecked();
+ end
+ else
+ if (Actions.onUnChecked ~= nil) then
+ Actions.onUnChecked();
+ end
+ end
+ end
+ else
+ error("UICheckBox Style is not a `table`")
+ end
+
+ RageUI.ItemOffset = RageUI.ItemOffset + SettingsButton.Rectangle.Height
+
+ RageUI.ItemsDescription(CurrentMenu, Description, Selected)
+
+ if (Actions.onSelected ~= nil) and (Selected) then
+ Actions.onSelected(Checked);
+ end
+
+ end
+ RageUI.Options = RageUI.Options + 1
+ end
+ end
+end
+
+
diff --git a/resources/RageUI/menu/items/UIList.lua b/resources/RageUI/menu/items/UIList.lua
new file mode 100644
index 000000000..539db5c69
--- /dev/null
+++ b/resources/RageUI/menu/items/UIList.lua
@@ -0,0 +1,158 @@
+---@type table
+local SettingsButton = {
+ Rectangle = { Y = 0, Width = 431, Height = 38 },
+ Text = { X = 8, Y = 3, Scale = 0.33 },
+ LeftBadge = { Y = -2, Width = 40, Height = 40 },
+ RightBadge = { X = 385, Y = -2, Width = 40, Height = 40 },
+ RightText = { X = 420, Y = 4, Scale = 0.35 },
+ SelectedSprite = { Dictionary = "commonmenu", Texture = "gradient_nav", Y = 0, Width = 431, Height = 38 },
+}
+
+---@type table
+local SettingsList = {
+ LeftArrow = { Dictionary = "commonmenu", Texture = "arrowleft", X = 378, Y = 3, Width = 30, Height = 30 },
+ RightArrow = { Dictionary = "commonmenu", Texture = "arrowright", X = 400, Y = 3, Width = 30, Height = 30 },
+ Text = { X = 403, Y = 3, Scale = 0.35 },
+}
+
+function RageUI.List(Label, Items, Index, Description, Style, Enabled, Actions, Submenu)
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+
+ ---@type number
+ local Option = RageUI.Options + 1
+
+ if CurrentMenu.Pagination.Minimum <= Option and CurrentMenu.Pagination.Maximum >= Option then
+
+ ---@type number
+ local Selected = CurrentMenu.Index == Option
+
+ ---@type boolean
+ local LeftArrowHovered, RightArrowHovered = false, false
+
+ RageUI.ItemsSafeZone(CurrentMenu)
+
+ local Hovered = false;
+ local LeftBadgeOffset = ((Style.LeftBadge == RageUI.BadgeStyle.None or Style.LeftBadge == nil) and 0 or 27)
+ local RightBadgeOffset = ((Style.RightBadge == RageUI.BadgeStyle.None or Style.RightBadge == nil) and 0 or 32)
+ local RightOffset = 0
+ ---@type boolean
+ if CurrentMenu.EnableMouse == true and (CurrentMenu.CursorStyle == 0) or (CurrentMenu.CursorStyle == 1) then
+ Hovered = RageUI.ItemsMouseBounds(CurrentMenu, Selected, Option, SettingsButton);
+ end
+ local ListText = (type(Items[Index]) == "table") and string.format("← %s →", Items[Index].Name) or string.format("← %s →", Items[Index]) or "NIL"
+
+ if Selected then
+ RenderSprite(SettingsButton.SelectedSprite.Dictionary, SettingsButton.SelectedSprite.Texture, CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height)
+ end
+ if Enabled == true or Enabled == nil then
+ if Selected then
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 0, 0, 0, 255, 2)
+ RightOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ end
+ else
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RightOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 245, 245, 245, 255, 2)
+ end
+ end
+ end
+ RightOffset = RightBadgeOffset * 1.3 + RightOffset
+ if Enabled == true or Enabled == nil then
+ if Selected then
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 0, 0, 0, 255)
+ RenderText(ListText, CurrentMenu.X + SettingsList.Text.X + 15 + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsList.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsList.Text.Scale, 0, 0, 0, 255, 2)
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 245, 245, 245, 255)
+ RenderText(ListText, CurrentMenu.X + SettingsList.Text.X + 15 + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsList.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsList.Text.Scale, 245, 245, 245, 255, 2)
+ end
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 163, 159, 148, 255)
+ if Selected then
+ RenderText(ListText, CurrentMenu.X + SettingsList.Text.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsList.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsList.Text.Scale, 163, 159, 148, 255, 2)
+ else
+ RenderText(ListText, CurrentMenu.X + SettingsList.Text.X + 15 + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsList.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsList.Text.Scale, 163, 159, 148, 255, 2)
+ end
+ end
+
+ if type(Style) == "table" then
+ if Style.Enabled == true or Style.Enabled == nil then
+ if type(Style) == 'table' then
+ if Style.LeftBadge ~= nil then
+ if Style.LeftBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.LeftBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+
+ if Style.RightBadge ~= nil then
+ if Style.RightBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.RightBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X + SettingsButton.RightBadge.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.RightBadge.Width, SettingsButton.RightBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+ end
+ else
+ ---@type table
+ local LeftBadge = RageUI.BadgeStyle.Lock
+ ---@type number
+ if LeftBadge ~= RageUI.BadgeStyle.None and LeftBadge ~= nil then
+ local BadgeData = LeftBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour.A or 255)
+ end
+ end
+ else
+ error("UICheckBox Style is not a `table`")
+ end
+
+ RageUI.ItemOffset = RageUI.ItemOffset + SettingsButton.Rectangle.Height
+
+ RageUI.ItemsDescription(CurrentMenu, Description, Selected);
+
+ if Selected and (CurrentMenu.Controls.Left.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) and not (CurrentMenu.Controls.Right.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) then
+ Index = Index - 1
+ if Index < 1 then
+ Index = #Items
+ end
+ if (Actions.onListChange ~= nil) then
+ Actions.onListChange(Index, Items[Index]);
+ end
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].LeftRight.audioName, Audio[Audio.Use].LeftRight.audioRef)
+ elseif Selected and (CurrentMenu.Controls.Right.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) and not (CurrentMenu.Controls.Left.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) then
+ Index = Index + 1
+ if Index > #Items then
+ Index = 1
+ end
+ if (Actions.onListChange ~= nil) then
+ Actions.onListChange(Index, Items[Index]);
+ end
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].LeftRight.audioName, Audio[Audio.Use].LeftRight.audioRef)
+ end
+
+ if Selected and (CurrentMenu.Controls.Select.Active or ((Hovered and CurrentMenu.Controls.Click.Active) and (not LeftArrowHovered and not RightArrowHovered))) then
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef)
+
+ if (Actions.onSelected ~= nil) then
+ Actions.onSelected(Index, Items[Index]);
+ end
+
+ if Submenu ~= nil and type(Submenu) == "table" then
+ RageUI.NextMenu = Submenu[Index]
+ end
+ end
+ end
+
+ RageUI.Options = RageUI.Options + 1
+ end
+ end
+end
diff --git a/resources/RageUI/menu/items/UISeparator.lua b/resources/RageUI/menu/items/UISeparator.lua
new file mode 100644
index 000000000..642b9ee79
--- /dev/null
+++ b/resources/RageUI/menu/items/UISeparator.lua
@@ -0,0 +1,46 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+---@type table
+local SettingsButton = {
+ Rectangle = { Y = 0, Width = 431, Height = 29 },
+ Text = { X = 8, Y = 0, Scale = 0.33 },
+}
+
+function RageUI.Separator(Label)
+ local CurrentMenu = RageUI.CurrentMenu
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+ local Option = RageUI.Options + 1
+ if CurrentMenu.Pagination.Minimum <= Option and CurrentMenu.Pagination.Maximum >= Option then
+ if (Label ~= nil) then
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + (CurrentMenu.WidthOffset * 2.5 ~= 0 and CurrentMenu.WidthOffset * 2.5 or 200), CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 245, 245, 245, 255, 1)
+ end
+ RageUI.ItemOffset = RageUI.ItemOffset + SettingsButton.Rectangle.Height
+ if (CurrentMenu.Index == Option) then
+ if (RageUI.LastControl) then
+ CurrentMenu.Index = Option - 1
+ if (CurrentMenu.Index < 1) then
+ CurrentMenu.Index = RageUI.CurrentMenu.Options
+ end
+ else
+ CurrentMenu.Index = Option + 1
+ end
+ end
+ end
+ RageUI.Options = RageUI.Options + 1
+ end
+ end
+end
+
diff --git a/resources/RageUI/menu/items/UISlider.lua b/resources/RageUI/menu/items/UISlider.lua
new file mode 100644
index 000000000..c1e9e5d32
--- /dev/null
+++ b/resources/RageUI/menu/items/UISlider.lua
@@ -0,0 +1,173 @@
+---@type table
+local SettingsButton = {
+ Rectangle = { Y = 0, Width = 431, Height = 38 },
+ Text = { X = 8, Y = 3, Scale = 0.33 },
+ LeftBadge = { Y = -2, Width = 40, Height = 40 },
+ RightBadge = { X = 385, Y = -2, Width = 40, Height = 40 },
+ RightText = { X = 420, Y = 4, Scale = 0.35 },
+ SelectedSprite = { Dictionary = "commonmenu", Texture = "gradient_nav", Y = 0, Width = 431, Height = 38 },
+}
+
+---@type table
+local SettingsSlider = {
+ Background = { X = 250, Y = 14.5, Width = 150, Height = 9 },
+ Slider = { X = 250, Y = 14.5, Width = 75, Height = 9 },
+ Divider = { X = 323.5, Y = 9, Width = 2.5, Height = 20 },
+ LeftArrow = { Dictionary = "commonmenutu", Texture = "arrowleft", X = 235, Y = 11.5, Width = 15, Height = 15 },
+ RightArrow = { Dictionary = "commonmenutu", Texture = "arrowright", X = 400, Y = 11.5, Width = 15, Height = 15 },
+}
+
+function RageUI.Slider(Label, ProgressStart, ProgressMax, IncrementQty, Description, Divider, Style, Enabled, Actions)
+
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+ local Audio = RageUI.Settings.Audio
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+ local Items = {}
+ for i = 1, ProgressMax do
+ table.insert(Items, i)
+ end
+ ---@type number
+ local Option = RageUI.Options + 1
+
+ if CurrentMenu.Pagination.Minimum <= Option and CurrentMenu.Pagination.Maximum >= Option then
+
+ ---@type number
+ local Selected = CurrentMenu.Index == Option
+
+ ---@type boolean
+ local LeftArrowHovered, RightArrowHovered = false, false
+
+ RageUI.ItemsSafeZone(CurrentMenu)
+
+ local Hovered = false;
+ --if type(Style) == "table" then
+ local LeftBadgeOffset = ((Style.LeftBadge == RageUI.BadgeStyle.None or Style.LeftBadge == nil) and 0 or 27)
+ local RightBadgeOffset = ((Style.RightBadge == RageUI.BadgeStyle.None or Style.RightBadge == nil) and 0 or 32)
+ --end
+ local RightOffset = 0
+ ---@type boolean
+ if CurrentMenu.EnableMouse == true and (CurrentMenu.CursorStyle == 0) or (CurrentMenu.CursorStyle == 1) then
+ Hovered = RageUI.ItemsMouseBounds(CurrentMenu, Selected, Option, SettingsButton);
+ end
+
+ if Selected then
+ RenderSprite(SettingsButton.SelectedSprite.Dictionary, SettingsButton.SelectedSprite.Texture, CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height)
+ LeftArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.SafeZoneSize.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height)
+ RightArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.SafeZoneSize.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height)
+ end
+ if Enabled == true or Enabled == nil then
+ if Selected then
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 0, 0, 0, 255, 2)
+ RightOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ end
+ else
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RightOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 245, 245, 245, 255, 2)
+ end
+ end
+ end
+ RightOffset = RightOffset + RightBadgeOffset
+ if Enabled == true or Enabled == nil then
+ if Selected then
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 0, 0, 0, 255)
+
+ RenderSprite(SettingsSlider.LeftArrow.Dictionary, SettingsSlider.LeftArrow.Texture, CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height, 0, 0, 0, 0, 255)
+ RenderSprite(SettingsSlider.RightArrow.Dictionary, SettingsSlider.RightArrow.Texture, CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height, 0, 0, 0, 0, 255)
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 245, 245, 245, 255)
+ end
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 163, 159, 148, 255)
+
+ if Selected then
+ RenderSprite(SettingsSlider.LeftArrow.Dictionary, SettingsSlider.LeftArrow.Texture, CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height, 163, 159, 148, 255)
+ RenderSprite(SettingsSlider.RightArrow.Dictionary, SettingsSlider.RightArrow.Texture, CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height, 163, 159, 148, 255)
+ end
+ end
+
+ if type(Style) == "table" then
+ if Style.Enabled == true or Style.Enabled == nil then
+ if type(Style) == 'table' then
+ if Style.LeftBadge ~= nil then
+ if Style.LeftBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.LeftBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+
+ if Style.RightBadge ~= nil then
+ if Style.RightBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.RightBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X + SettingsButton.RightBadge.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.RightBadge.Width, SettingsButton.RightBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+ end
+ else
+ ---@type table
+ local LeftBadge = RageUI.BadgeStyle.Lock
+
+ if LeftBadge ~= RageUI.BadgeStyle.None and LeftBadge ~= nil then
+ local BadgeData = LeftBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour.A or 255)
+ end
+ end
+ else
+ error("UICheckBox Style is not a `table`")
+ end
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Background.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.Background.Width, SettingsSlider.Background.Height, 4, 32, 57, 255)
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Slider.X + (((SettingsSlider.Background.Width - SettingsSlider.Slider.Width) / (#Items - 1)) * (ProgressStart - 1)) + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.Slider.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.Slider.Width, SettingsSlider.Slider.Height, 57, 116, 200, 255)
+ if Divider then
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Divider.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.Divider.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.Divider.Width, SettingsSlider.Divider.Height, 245, 245, 245, 255)
+ end
+
+ RageUI.ItemOffset = RageUI.ItemOffset + SettingsButton.Rectangle.Height
+
+ RageUI.ItemsDescription(CurrentMenu, Description, Selected);
+
+ if Enabled == true or Enabled == nil then
+ if Selected and (CurrentMenu.Controls.Left.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) and not (CurrentMenu.Controls.Right.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) then
+ ProgressStart = ProgressStart - IncrementQty or 1
+ if ProgressStart < 1 then
+ ProgressStart = #Items
+ end
+ if (Actions.onSliderChange ~= nil) then
+ Actions.onSliderChange(ProgressStart);
+ end
+ RageUI.PlaySound(Audio[Audio.Use].LeftRight.audioName, Audio[Audio.Use].LeftRight.audioRef)
+ elseif Selected and (CurrentMenu.Controls.Right.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) and not (CurrentMenu.Controls.Left.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) then
+ ProgressStart = ProgressStart + IncrementQty or 1
+ if ProgressStart > #Items then
+ ProgressStart = 1
+ end
+ if (Actions.onSliderChange ~= nil) then
+ Actions.onSliderChange(ProgressStart);
+ end
+ RageUI.PlaySound(Audio[Audio.Use].LeftRight.audioName, Audio[Audio.Use].LeftRight.audioRef)
+ end
+
+ if Selected and (CurrentMenu.Controls.Select.Active or ((Hovered and CurrentMenu.Controls.Click.Active) and (not LeftArrowHovered and not RightArrowHovered))) then
+ if (Actions.onSelected ~= nil) then
+ Actions.onSelected(ProgressStart);
+ end
+ if Style.MuteOnSelected ~= nil then
+ if Style.MuteOnSelected ~= true then
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef)
+ end
+ else
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef)
+ end
+ end
+ end
+ end
+
+ RageUI.Options = RageUI.Options + 1
+ end
+ end
+end
\ No newline at end of file
diff --git a/resources/RageUI/menu/items/UISliderHeritage.lua b/resources/RageUI/menu/items/UISliderHeritage.lua
new file mode 100644
index 000000000..e20ff2be8
--- /dev/null
+++ b/resources/RageUI/menu/items/UISliderHeritage.lua
@@ -0,0 +1,118 @@
+---@type table
+local SettingsButton = {
+ Rectangle = { Y = 0, Width = 431, Height = 38 },
+ Text = { X = 8, Y = 3, Scale = 0.33 },
+ SelectedSprite = { Dictionary = "commonmenu", Texture = "gradient_nav", Y = 0, Width = 431, Height = 38 },
+}
+
+---@type table
+local SettingsSlider = {
+ Background = { X = 250, Y = 14.5, Width = 150, Height = 9 },
+ Slider = { X = 250, Y = 14.5, Width = 75, Height = 9 },
+ Divider = { X = 323.5, Y = 9, Width = 2.5, Height = 20 },
+ LeftArrow = { Dictionary = "mpleaderboard", Texture = "leaderboard_female_icon", X = 215, Y = 0, Width = 40, Height = 40 },
+ RightArrow = { Dictionary = "mpleaderboard", Texture = "leaderboard_male_icon", X = 395, Y = 0, Width = 40, Height = 40 },
+}
+
+local Items = {}
+for i = 1, 10 do
+ table.insert(Items, i)
+end
+
+function RageUI.UISliderHeritage(Label, ItemIndex, Description, Actions, Value)
+
+ local CurrentMenu = RageUI.CurrentMenu;
+ local Audio = RageUI.Settings.Audio
+
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+
+ ---@type number
+ local Option = RageUI.Options + 1
+
+ if CurrentMenu.Pagination.Minimum <= Option and CurrentMenu.Pagination.Maximum >= Option then
+
+ ---@type number
+ local value = Value or 0.1
+ local Selected = CurrentMenu.Index == Option
+
+ ---@type boolean
+ local LeftArrowHovered, RightArrowHovered = false, false
+
+ RageUI.ItemsSafeZone(CurrentMenu)
+
+ local Hovered = false;
+ local RightOffset = 0
+
+ ---@type boolean
+ if CurrentMenu.EnableMouse == true and (CurrentMenu.CursorStyle == 0) or (CurrentMenu.CursorStyle == 1) then
+ Hovered = RageUI.ItemsMouseBounds(CurrentMenu, Selected, Option, SettingsButton);
+ end
+
+ if Selected then
+ RenderSprite(SettingsButton.SelectedSprite.Dictionary, SettingsButton.SelectedSprite.Texture, CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height)
+ LeftArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.SafeZoneSize.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height)
+ RightArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.SafeZoneSize.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height)
+ end
+
+ RightOffset = RightOffset
+
+ if Selected then
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 0, 0, 0, 255)
+
+ RenderSprite(SettingsSlider.LeftArrow.Dictionary, SettingsSlider.LeftArrow.Texture, CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height, 0, 0, 0, 0, 255)
+ RenderSprite(SettingsSlider.RightArrow.Dictionary, SettingsSlider.RightArrow.Texture, CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height, 0, 0, 0, 0, 255)
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 245, 245, 245, 255)
+
+ RenderSprite(SettingsSlider.LeftArrow.Dictionary, SettingsSlider.LeftArrow.Texture, CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height, 0, 255, 255, 255, 255)
+ RenderSprite(SettingsSlider.RightArrow.Dictionary, SettingsSlider.RightArrow.Texture, CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height, 0, 255, 255, 255, 255)
+ end
+
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Background.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.Background.Width, SettingsSlider.Background.Height, 4, 32, 57, 255)
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Slider.X + (((SettingsSlider.Background.Width - SettingsSlider.Slider.Width) / (#Items)) * (ItemIndex)) + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.Slider.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.Slider.Width, SettingsSlider.Slider.Height, 57, 116, 200, 255)
+
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Divider.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.Divider.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.Divider.Width, SettingsSlider.Divider.Height, 245, 245, 245, 255)
+
+ RageUI.ItemOffset = RageUI.ItemOffset + SettingsButton.Rectangle.Height
+
+ RageUI.ItemsDescription(CurrentMenu, Description, Selected);
+
+ if Selected and (CurrentMenu.Controls.SliderLeft.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) and not (CurrentMenu.Controls.SliderRight.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) then
+ ItemIndex = ItemIndex - value
+ if ItemIndex < 0.1 then
+ ItemIndex = 0.0
+ else
+ RageUI.PlaySound(Audio[Audio.Use].Slider.audioName, Audio[Audio.Use].Slider.audioRef, true)
+ end
+ if (Actions.onSliderChange ~= nil) then
+ Actions.onSliderChange(ItemIndex / 10, ItemIndex);
+ end
+ elseif Selected and (CurrentMenu.Controls.SliderRight.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) and not (CurrentMenu.Controls.SliderLeft.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) then
+ ItemIndex = ItemIndex + value
+ if ItemIndex > #Items then
+ ItemIndex = 10
+ else
+ RageUI.PlaySound(Audio[Audio.Use].Slider.audioName, Audio[Audio.Use].Slider.audioRef, true)
+ end
+ if (Actions.onSliderChange ~= nil) then
+ Actions.onSliderChange(ItemIndex / 10, ItemIndex);
+ end
+ end
+
+ if Selected and (CurrentMenu.Controls.Select.Active or ((Hovered and CurrentMenu.Controls.Click.Active) and (not LeftArrowHovered and not RightArrowHovered))) then
+ if (Actions.onSelected ~= nil) then
+ Actions.onSelected(ItemIndex / 10, ItemIndex);
+ end
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef, false)
+ end
+
+ end
+
+ RageUI.Options = RageUI.Options + 1
+ end
+ end
+end
+
+
+
diff --git a/resources/RageUI/menu/items/UISliderProgress.lua b/resources/RageUI/menu/items/UISliderProgress.lua
new file mode 100644
index 000000000..7774cd671
--- /dev/null
+++ b/resources/RageUI/menu/items/UISliderProgress.lua
@@ -0,0 +1,186 @@
+---
+--- Generated by EmmyLua(https://github.com/EmmyLua)
+--- Created by Dylan Malandain.
+--- DateTime: 21/11/2019 01:33
+---
+
+---@type table
+local SettingsButton = {
+ Rectangle = { Y = 0, Width = 431, Height = 38 },
+ Text = { X = 8, Y = 3, Scale = 0.33 },
+ LeftBadge = { Y = -2, Width = 40, Height = 40 },
+ RightBadge = { X = 385, Y = -2, Width = 40, Height = 40 },
+ RightText = { X = 420, Y = 4, Scale = 0.35 },
+ SelectedSprite = { Dictionary = "commonmenu", Texture = "gradient_nav", Y = 0, Width = 431, Height = 38 },
+}
+
+---@type table
+local SettingsSlider = {
+ Background = { X = 250, Y = 14.5, Width = 150, Height = 9 },
+ Slider = { X = 250, Y = 14.5, Width = 150, Height = 9 },
+ LeftArrow = { Dictionary = "commonmenutu", Texture = "arrowleft", X = 235, Y = 11.5, Width = 15, Height = 15 },
+ RightArrow = { Dictionary = "commonmenutu", Texture = "arrowright", X = 400, Y = 11.5, Width = 15, Height = 15 },
+}
+
+---Slider
+---@param Label string
+---@param ProgressStart number
+---@param ProgressMax number
+---@param Description string
+---@param Enabled boolean
+---@param Callback function
+function RageUI.SliderProgress(Label, ProgressStart, ProgressMax, Description, Style, Enabled, Actions)
+
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+ local Audio = RageUI.Settings.Audio
+
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+
+ local Items = {}
+ for i = 1, ProgressMax do
+ table.insert(Items, i)
+ end
+ ---@type number
+ local Option = RageUI.Options + 1
+
+ if CurrentMenu.Pagination.Minimum <= Option and CurrentMenu.Pagination.Maximum >= Option then
+
+ ---@type number
+ local Selected = CurrentMenu.Index == Option
+
+ ---@type boolean
+ local LeftArrowHovered, RightArrowHovered = false, false
+
+ RageUI.ItemsSafeZone(CurrentMenu)
+
+ local Hovered = false;
+ local LeftBadgeOffset = ((Style.LeftBadge == RageUI.BadgeStyle.None or Style.LeftBadge == nil) and 0 or 27)
+ local RightBadgeOffset = ((Style.RightBadge == RageUI.BadgeStyle.None or Style.RightBadge == nil) and 0 or 32)
+ local RightOffset = 0
+ ---@type boolean
+ if CurrentMenu.EnableMouse == true and (CurrentMenu.CursorStyle == 0) or (CurrentMenu.CursorStyle == 1) then
+ Hovered = RageUI.ItemsMouseBounds(CurrentMenu, Selected, Option, SettingsButton);
+ end
+
+ if Selected then
+ RenderSprite(SettingsButton.SelectedSprite.Dictionary, SettingsButton.SelectedSprite.Texture, CurrentMenu.X, CurrentMenu.Y + SettingsButton.SelectedSprite.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.SelectedSprite.Width + CurrentMenu.WidthOffset, SettingsButton.SelectedSprite.Height)
+ LeftArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.SafeZoneSize.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height)
+ RightArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.SafeZoneSize.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height)
+ end
+ if Enabled == true or Enabled == nil then
+ if Selected then
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 0, 0, 0, 255, 2)
+ RightOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ end
+ else
+ if Style.RightLabel ~= nil and Style.RightLabel ~= "" then
+ RightOffset = MeasureStringWidth(Style.RightLabel, 0, 0.35)
+ RenderText(Style.RightLabel, CurrentMenu.X + SettingsButton.RightText.X - RightBadgeOffset + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightText.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.RightText.Scale, 245, 245, 245, 255, 2)
+ end
+ end
+ end
+ RightOffset = RightOffset + RightBadgeOffset
+ if Enabled == true or Enabled == nil then
+ if Selected then
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 0, 0, 0, 255)
+
+ RenderSprite(SettingsSlider.LeftArrow.Dictionary, SettingsSlider.LeftArrow.Texture, CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height, 0, 0, 0, 0, 255)
+ RenderSprite(SettingsSlider.RightArrow.Dictionary, SettingsSlider.RightArrow.Texture, CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height, 0, 0, 0, 0, 255)
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 245, 245, 245, 255)
+ end
+ else
+ RenderText(Label, CurrentMenu.X + SettingsButton.Text.X + LeftBadgeOffset, CurrentMenu.Y + SettingsButton.Text.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, SettingsButton.Text.Scale, 163, 159, 148, 255)
+
+ if Selected then
+ RenderSprite(SettingsSlider.LeftArrow.Dictionary, SettingsSlider.LeftArrow.Texture, CurrentMenu.X + SettingsSlider.LeftArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.LeftArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.LeftArrow.Width, SettingsSlider.LeftArrow.Height, 163, 159, 148, 255)
+ RenderSprite(SettingsSlider.RightArrow.Dictionary, SettingsSlider.RightArrow.Texture, CurrentMenu.X + SettingsSlider.RightArrow.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.RightArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.RightArrow.Width, SettingsSlider.RightArrow.Height, 163, 159, 148, 255)
+ end
+ end
+
+ if type(Style) == "table" then
+ if Style.Enabled == true or Style.Enabled == nil then
+ if type(Style) == 'table' then
+ if Style.LeftBadge ~= nil then
+ if Style.LeftBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.LeftBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+
+ if Style.RightBadge ~= nil then
+ if Style.RightBadge ~= RageUI.BadgeStyle.None then
+ local BadgeData = Style.RightBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X + SettingsButton.RightBadge.X + CurrentMenu.WidthOffset, CurrentMenu.Y + SettingsButton.RightBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.RightBadge.Width, SettingsButton.RightBadge.Height, 0, BadgeData.BadgeColour and BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour and BadgeData.BadgeColour.A or 255)
+ end
+ end
+ end
+ else
+ ---@type table
+ local LeftBadge = RageUI.BadgeStyle.Lock
+
+ if LeftBadge ~= RageUI.BadgeStyle.None and LeftBadge ~= nil then
+ local BadgeData = LeftBadge(Selected)
+
+ RenderSprite(BadgeData.BadgeDictionary or "commonmenu", BadgeData.BadgeTexture or "", CurrentMenu.X, CurrentMenu.Y + SettingsButton.LeftBadge.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsButton.LeftBadge.Width, SettingsButton.LeftBadge.Height, 0, BadgeData.BadgeColour.R or 255, BadgeData.BadgeColour.G or 255, BadgeData.BadgeColour.B or 255, BadgeData.BadgeColour.A or 255)
+ end
+ end
+ else
+ error("UICheckBox Style is not a `table`")
+ end
+
+ if (type(Style.ProgressBackgroundColor) == "table") then
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Background.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, SettingsSlider.Background.Width, SettingsSlider.Background.Height, Style.ProgressBackgroundColor.R, Style.ProgressBackgroundColor.G, Style.ProgressBackgroundColor.B, Style.ProgressBackgroundColor.A)
+ else
+ error("Style ProgressBackgroundColor is not a table or undefined")
+ end
+
+ if (type(Style.ProgressColor) == "table") then
+ RenderRectangle(CurrentMenu.X + SettingsSlider.Slider.X + CurrentMenu.WidthOffset - RightOffset, CurrentMenu.Y + SettingsSlider.Slider.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, (((SettingsSlider.Slider.Width) / (#Items - 1)) * (ProgressStart - 1)), SettingsSlider.Slider.Height, Style.ProgressColor.R, Style.ProgressColor.G, Style.ProgressColor.B, Style.ProgressColor.A)
+ else
+ error("Style ProgressColor is not a table or undefined")
+ end
+
+ RageUI.ItemOffset = RageUI.ItemOffset + SettingsButton.Rectangle.Height
+
+ RageUI.ItemsDescription(CurrentMenu, Description, Selected);
+
+ if Selected and (CurrentMenu.Controls.Left.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) and not (CurrentMenu.Controls.Right.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) then
+ ProgressStart = ProgressStart - 1
+ if ProgressStart < 1 then
+ ProgressStart = #Items
+ end
+ if (Actions.onSliderChange ~= nil) then
+ Actions.onSliderChange(ProgressStart);
+ end
+ RageUI.PlaySound(Audio[Audio.Use].LeftRight.audioName, Audio[Audio.Use].LeftRight.audioRef)
+ elseif Selected and (CurrentMenu.Controls.Right.Active or (CurrentMenu.Controls.Click.Active and RightArrowHovered)) and not (CurrentMenu.Controls.Left.Active or (CurrentMenu.Controls.Click.Active and LeftArrowHovered)) then
+ ProgressStart = ProgressStart + 1
+ if ProgressStart > #Items then
+ ProgressStart = 1
+ end
+ if (Actions.onSliderChange ~= nil) then
+ Actions.onSliderChange(ProgressStart);
+ end
+ RageUI.PlaySound(Audio[Audio.Use].LeftRight.audioName, Audio[Audio.Use].LeftRight.audioRef)
+ end
+
+ if Selected and (CurrentMenu.Controls.Select.Active or ((Hovered and CurrentMenu.Controls.Click.Active) and (not LeftArrowHovered and not RightArrowHovered))) then
+ if (Actions.onSelected ~= nil) then
+ Actions.onSelected(ProgressStart);
+ end
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef)
+ end
+ end
+
+ RageUI.Options = RageUI.Options + 1
+ end
+ end
+end
+
+
diff --git a/resources/RageUI/menu/panels/UIColourPanel.lua b/resources/RageUI/menu/panels/UIColourPanel.lua
new file mode 100644
index 000000000..151875dbc
--- /dev/null
+++ b/resources/RageUI/menu/panels/UIColourPanel.lua
@@ -0,0 +1,100 @@
+---@type table
+local Colour = {
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 4, Width = 431, Height = 112 },
+ LeftArrow = { Dictionary = "commonmenu", Texture = "arrowleft", X = 7.5, Y = 15, Width = 30, Height = 30 },
+ RightArrow = { Dictionary = "commonmenu", Texture = "arrowright", X = 393.5, Y = 15, Width = 30, Height = 30 },
+ Header = { X = 215.5, Y = 15, Scale = 0.35 },
+ Box = { X = 15, Y = 55, Width = 44.5, Height = 44.5 },
+ SelectedRectangle = { X = 15, Y = 47, Width = 44.5, Height = 8 },
+}
+
+---ColourPanel
+---@param Title string
+---@param Colours thread
+---@param MinimumIndex number
+---@param CurrentIndex number
+---@param Callback function
+---@return nil
+---@public
+function RageUI.ColourPanel(Title, Colours, MinimumIndex, CurrentIndex, Action, Index)
+
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (CurrentMenu.Index == Index) then
+
+ ---@type number
+ local Maximum = (#Colours > 9) and 9 or #Colours
+
+ ---@type boolean
+ local Hovered = RageUI.IsMouseInBounds(CurrentMenu.X + Colour.Box.X + CurrentMenu.SafeZoneSize.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.Box.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, (Colour.Box.Width * Maximum), Colour.Box.Height)
+
+ ---@type number
+ local LeftArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + Colour.LeftArrow.X + CurrentMenu.SafeZoneSize.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.LeftArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.LeftArrow.Width, Colour.LeftArrow.Height)
+
+ ---@type number
+ local RightArrowHovered = RageUI.IsMouseInBounds(CurrentMenu.X + Colour.RightArrow.X + CurrentMenu.SafeZoneSize.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.RightArrow.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.RightArrow.Width, Colour.RightArrow.Height)
+
+ ---@type boolean
+ local Selected = false
+
+ RenderSprite(Colour.Background.Dictionary, Colour.Background.Texture, CurrentMenu.X, CurrentMenu.Y + Colour.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.Background.Width + CurrentMenu.WidthOffset, Colour.Background.Height)
+ RenderSprite(Colour.LeftArrow.Dictionary, Colour.LeftArrow.Texture, CurrentMenu.X + Colour.LeftArrow.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.LeftArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.LeftArrow.Width, Colour.LeftArrow.Height)
+ RenderSprite(Colour.RightArrow.Dictionary, Colour.RightArrow.Texture, CurrentMenu.X + Colour.RightArrow.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.RightArrow.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.RightArrow.Width, Colour.RightArrow.Height)
+
+ RenderRectangle(CurrentMenu.X + Colour.SelectedRectangle.X + (Colour.Box.Width * (CurrentIndex - MinimumIndex)) + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.SelectedRectangle.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.SelectedRectangle.Width, Colour.SelectedRectangle.Height, 245, 245, 245, 255)
+
+ for Index = 1, Maximum do
+ RenderRectangle(CurrentMenu.X + Colour.Box.X + (Colour.Box.Width * (Index - 1)) + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.Box.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.Box.Width, Colour.Box.Height, table.unpack(Colours[MinimumIndex + Index - 1]))
+ end
+
+ RenderText((Title and Title or "") .. " (" .. CurrentIndex .. " of " .. #Colours .. ")", CurrentMenu.X + RageUI.Settings.Panels.Grid.Text.Top.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + RageUI.Settings.Panels.Grid.Text.Top.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, RageUI.Settings.Panels.Grid.Text.Top.Scale, 245, 245, 245, 255, 1)
+
+ if Hovered or LeftArrowHovered or RightArrowHovered then
+ if RageUI.Settings.Controls.Click.Active then
+ Selected = true
+
+ if LeftArrowHovered then
+ CurrentIndex = CurrentIndex - 1
+
+ if CurrentIndex < 1 then
+ CurrentIndex = #Colours
+ MinimumIndex = #Colours - Maximum + 1
+ elseif CurrentIndex < MinimumIndex then
+ MinimumIndex = MinimumIndex - 1
+ end
+ elseif RightArrowHovered then
+ CurrentIndex = CurrentIndex + 1
+
+ if CurrentIndex > #Colours then
+ CurrentIndex = 1
+ MinimumIndex = 1
+ elseif CurrentIndex > MinimumIndex + Maximum - 1 then
+ MinimumIndex = MinimumIndex + 1
+ end
+ elseif Hovered then
+ for Index = 1, Maximum do
+ if RageUI.IsMouseInBounds(CurrentMenu.X + Colour.Box.X + (Colour.Box.Width * (Index - 1)) + CurrentMenu.SafeZoneSize.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Colour.Box.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Colour.Box.Width, Colour.Box.Height) then
+ CurrentIndex = MinimumIndex + Index - 1
+ end
+ end
+ end
+
+ if (Action.onColorChange ~= nil) then
+ Action.onColorChange(MinimumIndex, CurrentIndex)
+ end
+ end
+ end
+
+ RageUI.ItemOffset = RageUI.ItemOffset + Colour.Background.Height + Colour.Background.Y
+
+ if (Hovered or LeftArrowHovered or RightArrowHovered) and RageUI.Settings.Controls.Click.Active then
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Select.audioName, Audio[Audio.Use].Select.audioRef)
+ end
+ end
+ end
+end
+
+
diff --git a/resources/RageUI/menu/panels/UIGridPanel.lua b/resources/RageUI/menu/panels/UIGridPanel.lua
new file mode 100644
index 000000000..b6e4f8e22
--- /dev/null
+++ b/resources/RageUI/menu/panels/UIGridPanel.lua
@@ -0,0 +1,127 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+local GridType = RageUI.Enum {
+ Default = 1,
+ Horizontal = 2,
+ Vertical = 3
+}
+
+local GridSprite = {
+ [GridType.Default] = { Dictionary = "pause_menu_pages_char_mom_dad", Texture = "nose_grid", },
+ [GridType.Horizontal] = { Dictionary = "RageUI", Texture = "horizontal_grid", },
+ [GridType.Vertical] = { Dictionary = "RageUI", Texture = "vertical_grid", },
+}
+
+local Grid = {
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 4, Width = 431, Height = 275 },
+ Grid = { X = 115.5, Y = 47.5, Width = 200, Height = 200 },
+ Circle = { Dictionary = "mpinventory", Texture = "in_world_circle", X = 115.5, Y = 47.5, Width = 20, Height = 20 },
+ Text = {
+ Top = { X = 215.5, Y = 15, Scale = 0.35 },
+ Bottom = { X = 215.5, Y = 250, Scale = 0.35 },
+ Left = { X = 57.75, Y = 130, Scale = 0.35 },
+ Right = { X = 373.25, Y = 130, Scale = 0.35 },
+ },
+}
+
+local function UIGridPanel(Type, StartedX, StartedY, TopText, BottomText, LeftText, RightText, Action, Index)
+ local CurrentMenu = RageUI.CurrentMenu
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and ((CurrentMenu.Index == Index)) then
+ local X = Type == GridType.Default and StartedX or Type == GridType.Horizontal and StartedX or Type == GridType.Vertical and 0.5
+ local Y = Type == GridType.Default and StartedY or Type == GridType.Horizontal and 0.5 or Type == GridType.Vertical and StartedY
+ local Hovered = RageUI.IsMouseInBounds(CurrentMenu.X + Grid.Grid.X + CurrentMenu.SafeZoneSize.X + 20, CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + 20, Grid.Grid.Width + CurrentMenu.WidthOffset - 40, Grid.Grid.Height - 40)
+ local Selected = false
+ local CircleX = CurrentMenu.X + Grid.Grid.X + (CurrentMenu.WidthOffset / 2) + 20
+ local CircleY = CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + 20
+ if X < 0.0 or X > 1.0 then
+ X = 0.0
+ end
+ if Y < 0.0 or Y > 1.0 then
+ Y = 0.0
+ end
+ CircleX = CircleX + ((Grid.Grid.Width - 40) * X) - (Grid.Circle.Width / 2)
+ CircleY = CircleY + ((Grid.Grid.Height - 40) * Y) - (Grid.Circle.Height / 2)
+ RenderSprite(Grid.Background.Dictionary, Grid.Background.Texture, CurrentMenu.X, CurrentMenu.Y + Grid.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Grid.Background.Width + CurrentMenu.WidthOffset, Grid.Background.Height)
+ RenderSprite(GridSprite[Type].Dictionary, GridSprite[Type].Texture, CurrentMenu.X + Grid.Grid.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Grid.Grid.Width, Grid.Grid.Height)
+ RenderSprite(Grid.Circle.Dictionary, Grid.Circle.Texture, CircleX, CircleY, Grid.Circle.Width, Grid.Circle.Height)
+ if (Type == GridType.Default) then
+ RenderText(TopText or "", CurrentMenu.X + Grid.Text.Top.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Top.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Top.Scale, 245, 245, 245, 255, 1)
+ RenderText(BottomText or "", CurrentMenu.X + Grid.Text.Bottom.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Bottom.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Bottom.Scale, 245, 245, 245, 255, 1)
+ RenderText(LeftText or "", CurrentMenu.X + Grid.Text.Left.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Left.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Left.Scale, 245, 245, 245, 255, 1)
+ RenderText(RightText or "", CurrentMenu.X + Grid.Text.Right.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Right.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Right.Scale, 245, 245, 245, 255, 1)
+ end
+ if (Type == GridType.Vertical) then
+ RenderText(TopText or "", CurrentMenu.X + Grid.Text.Top.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Top.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Top.Scale, 245, 245, 245, 255, 1)
+ RenderText(BottomText or "", CurrentMenu.X + Grid.Text.Bottom.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Bottom.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Bottom.Scale, 245, 245, 245, 255, 1)
+ end
+ if (Type == GridType.Horizontal) then
+ RenderText(LeftText or "", CurrentMenu.X + Grid.Text.Left.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Left.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Left.Scale, 245, 245, 245, 255, 1)
+ RenderText(RightText or "", CurrentMenu.X + Grid.Text.Right.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Grid.Text.Right.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Grid.Text.Right.Scale, 245, 245, 245, 255, 1)
+ end
+ if Hovered then
+ if IsDisabledControlPressed(0, 24) then
+ Selected = true
+ CircleX = math.round(GetControlNormal(2, 239) * 1920) - CurrentMenu.SafeZoneSize.X - (Grid.Circle.Width / 2)
+ CircleY = math.round(GetControlNormal(2, 240) * 1080) - CurrentMenu.SafeZoneSize.Y - (Grid.Circle.Height / 2)
+ if CircleX > (CurrentMenu.X + Grid.Grid.X + (CurrentMenu.WidthOffset / 2) + 20 + Grid.Grid.Width - 40) then
+ CircleX = CurrentMenu.X + Grid.Grid.X + (CurrentMenu.WidthOffset / 2) + 20 + Grid.Grid.Width - 40
+ elseif CircleX < (CurrentMenu.X + Grid.Grid.X + 20 - (Grid.Circle.Width / 2)) then
+ CircleX = CurrentMenu.X + Grid.Grid.X + 20 - (Grid.Circle.Width / 2)
+ end
+ if CircleY > (CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + 20 + Grid.Grid.Height - 40) then
+ CircleY = CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + 20 + Grid.Grid.Height - 40
+ elseif CircleY < (CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + 20 - (Grid.Circle.Height / 2)) then
+ CircleY = CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + 20 - (Grid.Circle.Height / 2)
+ end
+ X = math.round((CircleX - (CurrentMenu.X + Grid.Grid.X + (CurrentMenu.WidthOffset / 2) + 20) + (Grid.Circle.Width / 2)) / (Grid.Grid.Width - 40), 2)
+ Y = math.round((CircleY - (CurrentMenu.Y + Grid.Grid.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + 20) + (Grid.Circle.Height / 2)) / (Grid.Grid.Height - 40), 2)
+ if (X ~= StartedX) and (Y ~= StartedY) then
+ Action.onPositionChange(X, Y)
+ end
+ StartedX = X;
+ StartedY = Y;
+ if X > 1.0 then
+ X = 1.0
+ end
+ if Y > 1.0 then
+ Y = 1.0
+ end
+ end
+ end
+ RageUI.ItemOffset = RageUI.ItemOffset + Grid.Background.Height + Grid.Background.Y
+ if Hovered and Selected then
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Slider.audioName, Audio[Audio.Use].Slider.audioRef, true)
+ if (Action.onSelected ~= nil) then
+ Action.onSelected(X, Y);
+ end
+ end
+
+ end
+ end
+end
+
+function RageUI.Grid(StartedX, StartedY, TopText, BottomText, LeftText, RightText, Action, Index)
+ UIGridPanel(GridType.Default, StartedX, StartedY, TopText, BottomText, LeftText, RightText, Action, Index)
+end
+
+function RageUI.GridHorizontal(StartedX, LeftText, RightText, Action, Index)
+ UIGridPanel(GridType.Horizontal, StartedX, nil, nil, nil, LeftText, RightText, Action, Index)
+end
+
+function RageUI.GridVertical(StartedY, TopText, BottomText, Action, Index)
+ UIGridPanel(GridType.Vertical, nil, StartedY, TopText, BottomText, nil, nil, Action, Index)
+end
diff --git a/resources/RageUI/menu/panels/UIPercentagePanel.lua b/resources/RageUI/menu/panels/UIPercentagePanel.lua
new file mode 100644
index 000000000..f033f737c
--- /dev/null
+++ b/resources/RageUI/menu/panels/UIPercentagePanel.lua
@@ -0,0 +1,81 @@
+local Percentage = {
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 4, Width = 431, Height = 76 },
+ Bar = { X = 9, Y = 50, Width = 413, Height = 10 },
+ Text = {
+ Left = { X = 25, Y = 15, Scale = 0.35 },
+ Middle = { X = 215.5, Y = 15, Scale = 0.35 },
+ Right = { X = 398, Y = 15, Scale = 0.35 },
+ },
+}
+
+---PercentagePanel
+---@param Percent number
+---@param HeaderText string
+---@param MinText string
+---@param MaxText string
+---@param Callback function
+---@param Index number
+---@return nil
+---@public
+function RageUI.PercentagePanel(Percent, HeaderText, MinText, MaxText, Action, Index)
+ local CurrentMenu = RageUI.CurrentMenu
+
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (Index == nil or (CurrentMenu.Index == Index)) then
+
+ ---@type boolean
+ local Hovered = RageUI.IsMouseInBounds(CurrentMenu.X + Percentage.Bar.X + CurrentMenu.SafeZoneSize.X, CurrentMenu.Y + Percentage.Bar.Y + CurrentMenu.SafeZoneSize.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset - 4, Percentage.Bar.Width + CurrentMenu.WidthOffset, Percentage.Bar.Height + 8)
+
+ ---@type boolean
+ local Selected = false
+
+ ---@type number
+ local Progress = Percentage.Bar.Width
+
+ if Percent < 0.0 then
+ Percent = 0.0
+ elseif Percent > 1.0 then
+ Percent = 1.0
+ end
+
+ Progress = Progress * Percent
+
+ RenderSprite(Percentage.Background.Dictionary, Percentage.Background.Texture, CurrentMenu.X, CurrentMenu.Y + Percentage.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Percentage.Background.Width + CurrentMenu.WidthOffset, Percentage.Background.Height)
+ RenderRectangle(CurrentMenu.X + Percentage.Bar.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Percentage.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Percentage.Bar.Width, Percentage.Bar.Height, 87, 87, 87, 255)
+ RenderRectangle(CurrentMenu.X + Percentage.Bar.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Percentage.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Progress, Percentage.Bar.Height, 245, 245, 245, 255)
+
+ RenderText(HeaderText or "Opacity", CurrentMenu.X + Percentage.Text.Middle.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Percentage.Text.Middle.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Percentage.Text.Middle.Scale, 245, 245, 245, 255, 1)
+ RenderText(MinText or "0%", CurrentMenu.X + Percentage.Text.Left.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Percentage.Text.Left.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Percentage.Text.Left.Scale, 245, 245, 245, 255, 1)
+ RenderText(MaxText or "100%", CurrentMenu.X + Percentage.Text.Right.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + Percentage.Text.Right.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Percentage.Text.Right.Scale, 245, 245, 245, 255, 1)
+
+ if Hovered then
+ if IsDisabledControlPressed(0, 24) then
+ Selected = true
+
+ Progress = math.round(GetControlNormal(2, 239) * 1920) - CurrentMenu.SafeZoneSize.X - (CurrentMenu.X + Percentage.Bar.X + (CurrentMenu.WidthOffset / 2))
+
+ if Progress < 0 then
+ Progress = 0
+ elseif Progress > (Percentage.Bar.Width) then
+ Progress = Percentage.Bar.Width
+ end
+
+ Percent = math.round(Progress / Percentage.Bar.Width, 2)
+ if (Action.onProgressChange ~= nil) then
+ Action.onProgressChange(Percent)
+ end
+ end
+ end
+
+ RageUI.ItemOffset = RageUI.ItemOffset + Percentage.Background.Height + Percentage.Background.Y
+
+ if Hovered and Selected then
+ local Audio = RageUI.Settings.Audio
+ RageUI.PlaySound(Audio[Audio.Use].Slider.audioName, Audio[Audio.Use].Slider.audioRef, true)
+ if (Action.onSelected ~= nil) then
+ Action.onSelected(Percent)
+ end
+ end
+ end
+ end
+end
diff --git a/resources/RageUI/menu/panels/UIStatisticsPanel.lua b/resources/RageUI/menu/panels/UIStatisticsPanel.lua
new file mode 100644
index 000000000..0f049923d
--- /dev/null
+++ b/resources/RageUI/menu/panels/UIStatisticsPanel.lua
@@ -0,0 +1,68 @@
+local Statistics = {
+ Background = { Dictionary = "commonmenu", Texture = "gradient_bgd", Y = 4, Width = 431, Height = 42 },
+ Text = {
+ Left = { X = -40, Y = 15, Scale = 0.35 },
+ },
+ Bar = { Right = 8, Y = 27, Width = 200, Height = 10, OffsetRatio = 0.5 },
+ Divider = {
+ [1] = { X = 200, Y = 27, Width = 2, Height = 10 },
+ [2] = { X = 200, Y = 27, Width = 2, Height = 10 },
+ [3] = { X = 200, Y = 27, Width = 2, Height = 10 },
+ [4] = { X = 200, Y = 27, Width = 2, Height = 10 },
+ [5] = { X = 200, Y = 27, Width = 2, Height = 10 },
+ }
+}
+
+function RageUI.StatisticPanel(Percent, Text, Index)
+ local CurrentMenu = RageUI.CurrentMenu
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (Index == nil or (CurrentMenu.Index == Index)) then
+
+ ---@type number
+ local BarWidth = Statistics.Bar.Width + CurrentMenu.WidthOffset * Statistics.Bar.OffsetRatio
+
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + Statistics.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + (RageUI.StatisticPanelCount * 42), Statistics.Background.Width + CurrentMenu.WidthOffset, Statistics.Background.Height, 0, 0, 0, 170)
+ RenderText(Text or "", CurrentMenu.X + 8.0, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Text.Left.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Statistics.Text.Left.Scale, 245, 245, 245, 255, 0)
+ RenderRectangle(CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right + CurrentMenu.WidthOffset, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, BarWidth, Statistics.Bar.Height, 87, 87, 87, 255)
+ RenderRectangle(CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right + CurrentMenu.WidthOffset, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Percent * BarWidth, Statistics.Bar.Height, 255, 255, 255, 255)
+ for i = 1, #Statistics.Divider, 1 do
+ RenderRectangle((CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right) + i * ((BarWidth - (#Statistics.Divider / Statistics.Divider[i].Width)) / (#Statistics.Divider + 1)) + CurrentMenu.WidthOffset, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Divider[i].Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Statistics.Divider[i].Width, Statistics.Divider[i].Height, 0, 0, 0, 255)
+ end
+ RageUI.StatisticPanelCount = RageUI.StatisticPanelCount + 1
+ end
+ end
+end
+
+function RageUI.StatisticPanelAdvanced(Text, Percent, RGBA1, Percent2, RGBA2, RGBA3, Index)
+ local CurrentMenu = RageUI.CurrentMenu
+ if CurrentMenu ~= nil then
+ if CurrentMenu() and (Index == nil or (CurrentMenu.Index == Index)) then
+
+ RGBA1 = RGBA1 or { 255, 255, 255, 255 }
+ local BarWidth = Statistics.Bar.Width + CurrentMenu.WidthOffset * Statistics.Bar.OffsetRatio
+
+ ---@type number
+ RenderRectangle(CurrentMenu.X, CurrentMenu.Y + Statistics.Background.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset + (RageUI.StatisticPanelCount * 42), Statistics.Background.Width + CurrentMenu.WidthOffset, Statistics.Background.Height, 0, 0, 0, 170)
+ RenderText(Text or "", CurrentMenu.X + 8.0, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Text.Left.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, 0, Statistics.Text.Left.Scale, 245, 245, 245, 255, 0)
+ RenderRectangle(CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right + CurrentMenu.WidthOffset, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, BarWidth, Statistics.Bar.Height, 87, 87, 87, 255)
+ RenderRectangle(CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right + CurrentMenu.WidthOffset, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Percent * BarWidth, Statistics.Bar.Height, RGBA1[1], RGBA1[2], RGBA1[3], RGBA1[4])
+ RGBA2 = RGBA2 or { 0, 153, 204, 255 }
+ RGBA3 = RGBA3 or { 185, 0, 0, 255 }
+
+ if Percent2 and Percent2 > 0 then
+ local X = CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right + CurrentMenu.WidthOffset + Percent * BarWidth
+ RenderRectangle(X, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Percent2 * BarWidth, Statistics.Bar.Height, RGBA2[1], RGBA2[2], RGBA2[3], RGBA2[4])
+ elseif Percent2 and Percent2 < 0 then
+ local X = CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right + CurrentMenu.WidthOffset + Percent * BarWidth
+ RenderRectangle(X, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Bar.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Percent2 * BarWidth, Statistics.Bar.Height, RGBA3[1], RGBA3[2], RGBA3[3], RGBA3[4])
+ end
+
+ for i = 1, #Statistics.Divider, 1 do
+ RenderRectangle((CurrentMenu.X + RageUI.Settings.Items.Title.Background.Width - BarWidth - Statistics.Bar.Right) + i * ((BarWidth - (#Statistics.Divider / Statistics.Divider[i].Width)) / (#Statistics.Divider + 1)) + CurrentMenu.WidthOffset, (RageUI.StatisticPanelCount * 40) + CurrentMenu.Y + Statistics.Divider[i].Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Statistics.Divider[i].Width, Statistics.Divider[i].Height, 0, 0, 0, 255)
+ end
+
+ RageUI.StatisticPanelCount = RageUI.StatisticPanelCount + 1
+ end
+ end
+end
+
diff --git a/resources/RageUI/menu/windows/UIHeritage.lua b/resources/RageUI/menu/windows/UIHeritage.lua
new file mode 100644
index 000000000..e155073e7
--- /dev/null
+++ b/resources/RageUI/menu/windows/UIHeritage.lua
@@ -0,0 +1,52 @@
+---
+--- @author Dylan MALANDAIN
+--- @version 2.0.0
+--- @since 2020
+---
+--- RageUI Is Advanced UI Libs in LUA for make beautiful interface like RockStar GAME.
+---
+---
+--- Commercial Info.
+--- Any use for commercial purposes is strictly prohibited and will be punished.
+---
+--- @see RageUI
+---
+
+
+---@type table
+local Heritage = {
+ Background = { Dictionary = "pause_menu_pages_char_mom_dad", Texture = "mumdadbg", Width = 431, Height = 228 },
+ Mum = { Dictionary = "char_creator_portraits", X = 25, Width = 228, Height = 228 },
+ Dad = { Dictionary = "char_creator_portraits", X = 195, Width = 228, Height = 228 },
+}
+
+---@type Window
+function RageUI.Window.Heritage(Mum, Dad)
+ ---@type table
+ local CurrentMenu = RageUI.CurrentMenu;
+ if CurrentMenu ~= nil then
+ if CurrentMenu() then
+ if Mum < 0 or Mum > 21 then
+ Mum = 0
+ end
+ if Dad < 0 or Dad > 23 then
+ Dad = 0
+ end
+ if Mum == 21 then
+ Mum = "special_female_" .. (tonumber(string.sub(Mum, 2, 2)) - 1)
+ else
+ Mum = "female_" .. Mum
+ end
+ if Dad >= 21 then
+ Dad = "special_male_" .. (tonumber(string.sub(Dad, 2, 2)) - 1)
+ else
+ Dad = "male_" .. Dad
+ end
+ RenderSprite(Heritage.Background.Dictionary, Heritage.Background.Texture, CurrentMenu.X, CurrentMenu.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Heritage.Background.Width + (CurrentMenu.WidthOffset / 1), Heritage.Background.Height)
+ RenderSprite(Heritage.Dad.Dictionary, Dad, CurrentMenu.X + Heritage.Dad.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Heritage.Dad.Width, Heritage.Dad.Height)
+ RenderSprite(Heritage.Mum.Dictionary, Mum, CurrentMenu.X + Heritage.Mum.X + (CurrentMenu.WidthOffset / 2), CurrentMenu.Y + CurrentMenu.SubtitleHeight + RageUI.ItemOffset, Heritage.Mum.Width, Heritage.Mum.Height)
+ RageUI.ItemOffset = RageUI.ItemOffset + Heritage.Background.Height
+ end
+ end
+end
+
diff --git a/resources/RageUI/stream/RageUI.ytd b/resources/RageUI/stream/RageUI.ytd
new file mode 100644
index 000000000..a5c5075a0
--- /dev/null
+++ b/resources/RageUI/stream/RageUI.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f9f6e2f48af177814cd432b58140a2c28502b4a5647d317ed9d9764904b3c31
+size 2405
diff --git a/resources/RageUI/stream/casinoui_cards_blackjack.ytd b/resources/RageUI/stream/casinoui_cards_blackjack.ytd
new file mode 100644
index 000000000..15a6373f0
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_cards_blackjack.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:61f897b27f1fe6e995f9f7157ad6952030054054b93ff3bb61b1744fce0ac27b
+size 6827
diff --git a/resources/RageUI/stream/casinoui_cards_blackjack_high.ytd b/resources/RageUI/stream/casinoui_cards_blackjack_high.ytd
new file mode 100644
index 000000000..0eafaa5b6
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_cards_blackjack_high.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7645e5b79102106c977a911508970d54402e092046e88d367a56e65528992067
+size 7880
diff --git a/resources/RageUI/stream/casinoui_cards_three.ytd b/resources/RageUI/stream/casinoui_cards_three.ytd
new file mode 100644
index 000000000..30ef9dbe2
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_cards_three.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c5e4c973fb28411518d8d17cc55f5cba3146e67abf460eb8835553fc0620b94e
+size 6840
diff --git a/resources/RageUI/stream/casinoui_cards_three_high.ytd b/resources/RageUI/stream/casinoui_cards_three_high.ytd
new file mode 100644
index 000000000..d40b14e5a
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_cards_three_high.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c90bb2cfb78173820a53dee067d036a5534cad6a4a1927b8735c66fdf56e9fab
+size 7912
diff --git a/resources/RageUI/stream/casinoui_insidetrack.ytd b/resources/RageUI/stream/casinoui_insidetrack.ytd
new file mode 100644
index 000000000..75af3635a
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_insidetrack.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fe07f36753a130b728f89aecbf751cf70dd0cd26c19a714a0089fa7c30474eb6
+size 14932
diff --git a/resources/RageUI/stream/casinoui_lucky_wheel.ytd b/resources/RageUI/stream/casinoui_lucky_wheel.ytd
new file mode 100644
index 000000000..dd299aad1
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_lucky_wheel.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0195ed7e11fb9b9bd3bd6668431f4b324c2a736aa12a047243d5ac409e171274
+size 12732
diff --git a/resources/RageUI/stream/casinoui_roulette.ytd b/resources/RageUI/stream/casinoui_roulette.ytd
new file mode 100644
index 000000000..ccc019822
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_roulette.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:46ef6aa847509f102fd4d04bcac6030b5379cf6d316fb958237315358a405005
+size 10266
diff --git a/resources/RageUI/stream/casinoui_roulette_high.ytd b/resources/RageUI/stream/casinoui_roulette_high.ytd
new file mode 100644
index 000000000..eaeb71849
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_roulette_high.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8580f1e754dfc099a520c2a17bf899eebb9c5319a8c650dc32ffd8878664bbcb
+size 11258
diff --git a/resources/RageUI/stream/casinoui_slots_angel.ytd b/resources/RageUI/stream/casinoui_slots_angel.ytd
new file mode 100644
index 000000000..0db8d7825
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_angel.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b247500df47becd51c542c69d2d442fe22fc58eb55cf1b08ed4b4ed599110d7c
+size 21516
diff --git a/resources/RageUI/stream/casinoui_slots_deity.ytd b/resources/RageUI/stream/casinoui_slots_deity.ytd
new file mode 100644
index 000000000..bcb5e8c6f
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_deity.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c28bd179dc68864d936b60e08ddfdf0f2c4a5b85e5a51a95acaa0adecaebbb7
+size 26879
diff --git a/resources/RageUI/stream/casinoui_slots_diamond.ytd b/resources/RageUI/stream/casinoui_slots_diamond.ytd
new file mode 100644
index 000000000..61d1a3ab6
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_diamond.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bce3d6ccedbe5094dae700ce1e5bb0b56fade7cb9ba5a884b7b74ca7bca69689
+size 21251
diff --git a/resources/RageUI/stream/casinoui_slots_evacuator.ytd b/resources/RageUI/stream/casinoui_slots_evacuator.ytd
new file mode 100644
index 000000000..75d058be1
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_evacuator.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fa71160f5c773bf7fb1292d714d7cd8ab10590dafdd9f330dc320b09f4a741b8
+size 20612
diff --git a/resources/RageUI/stream/casinoui_slots_fame.ytd b/resources/RageUI/stream/casinoui_slots_fame.ytd
new file mode 100644
index 000000000..52030901f
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_fame.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c0cc5b47e888a365cb59f2d8288449b098dd8c34ebc340218677fd72e1aca364
+size 27865
diff --git a/resources/RageUI/stream/casinoui_slots_impotent.ytd b/resources/RageUI/stream/casinoui_slots_impotent.ytd
new file mode 100644
index 000000000..85c46fe4d
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_impotent.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:879813969c3e29b6c76089f0093f5eb373b1e0a4463535c159f07239341e623d
+size 21128
diff --git a/resources/RageUI/stream/casinoui_slots_knife.ytd b/resources/RageUI/stream/casinoui_slots_knife.ytd
new file mode 100644
index 000000000..5a4ab2fd0
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_knife.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6e8f5d33f3e53a7e5d45b10e64e7b712c48feaf32db88827c9b93fed6b59d9f2
+size 16893
diff --git a/resources/RageUI/stream/casinoui_slots_ranger.ytd b/resources/RageUI/stream/casinoui_slots_ranger.ytd
new file mode 100644
index 000000000..872c25ba3
--- /dev/null
+++ b/resources/RageUI/stream/casinoui_slots_ranger.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e0bfc17fe602e5a801e17d336436e8c908cff5001018ed1602dd324a03d3c482
+size 23303
diff --git a/resources/RageUI/stream/mpawardcasino.ytd b/resources/RageUI/stream/mpawardcasino.ytd
new file mode 100644
index 000000000..7468a7ff6
--- /dev/null
+++ b/resources/RageUI/stream/mpawardcasino.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3d92e570f1dc99a51cd4db928ba635cd96866160e3912445cee69dbdf754d863
+size 35483
diff --git a/resources/RageUI/stream/root_cause.ytd b/resources/RageUI/stream/root_cause.ytd
new file mode 100644
index 000000000..83d4090b8
--- /dev/null
+++ b/resources/RageUI/stream/root_cause.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:69f7ad44d6df665ef228674f5bda58fb3ee7511ecd768c096ff7c77c1a2f758a
+size 568284
diff --git a/resources/StreetLabel/config.lua b/resources/StreetLabel/config.lua
index 9617f04c6..21d7c2057 100644
--- a/resources/StreetLabel/config.lua
+++ b/resources/StreetLabel/config.lua
@@ -37,4 +37,4 @@
town_a = 255
-- Determine rather the HUD should only display when player(s) are inside a vehicle or not
- checkForVehicle = true
\ No newline at end of file
+ checkForVehicle = false
\ No newline at end of file
diff --git a/resources/Timetrials/scores.txt b/resources/Timetrials/scores.txt
index 25b051696..6bdbf2ccf 100644
--- a/resources/Timetrials/scores.txt
+++ b/resources/Timetrials/scores.txt
@@ -1 +1 @@
-{"Race | BCSO Training Course":{"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":100457},"Nissan Skyline GTR":{"player":"ThatGuyJacobee","car":"Nissan Skyline GTR","time":105983},"Mercedes AMG GTS":{"player":"vJack","car":"Mercedes AMG GTS","time":78151},"2016 Dodge Ram":{"player":"vJack","car":"2016 Dodge Ram","time":119099},"2013 Dodge Charger":{"player":"vJack","car":"2013 Dodge Charger","time":94246},"Neon":{"player":"ThunderBird","car":"Neon","time":105527}},"Race | Lakeside Loop":{"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":196803},"Ferrari 458":{"player":"ThatGuyJacobee","car":"Ferrari 458","time":177461},"SNOWMOBILE 2020":{"player":"[C-146] E. Cartman","car":"SNOWMOBILE 2020","time":107015},"EGRP Staff Buggy":{"player":"[EGRP-02]King Rodriguez","car":"EGRP Staff Buggy","time":43710},"Ford Crown Vic 2011":{"player":"AnySpiderBat","car":"Ford Crown Vic 2011","time":231931}},"Hollywood Hills":{"NULL":{"player":"Kryplos125","car":"NULL","time":120053}},"Race | Airport Drag Race":{"NULL":{"player":"DR-KingRodriguez","car":"NULL","time":9270}},"Race | Timetrial Event - 04/04/21":{"Ford Velociraptor 6x6":{"player":"ThatGuyJacobee","car":"Ford Velociraptor 6x6","time":132814},"Chevrolet Silverado 1980":{"player":"King Rodriguez","car":"Chevrolet Silverado 1980","time":156244},"Gauntlet Classic Custom":{"player":"[C-291][GCF-34][MS-13] K. Lee","car":"Gauntlet Classic Custom","time":191695}},"North Rallycross ":{"Neon":{"player":"ThunderBird","car":"Neon","time":351612}},"Race | Hollywood Hills":{"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":102048},"Audi R8 V10":{"player":"ThatGuyJacobee","car":"Audi R8 V10","time":100677},"Mercedes AMG GTS":{"player":"vJack","car":"Mercedes AMG GTS","time":85823}},"Race | Sandy Airfield":{"Formula 1":{"player":"vJack","car":"Formula 1","time":15914},"Nissan Skyline GTR":{"player":"ThatGuyJacobee","car":"Nissan Skyline GTR","time":29921},"2016 Ford Explorer":{"player":"King Rodriguez","car":"2016 Ford Explorer","time":26526},"Ford Raptor F-150":{"player":"King Rodriguez","car":"Ford Raptor F-150","time":28515},"Chevy Camaro20":{"player":"Max Rodriguez","car":"Chevy Camaro20","time":32202},"Mini JCW 2020":{"player":"King Rodriguez","car":"Mini JCW 2020","time":29879},"Space Docker":{"player":"DyslexicStoner240.","car":"Space Docker","time":29122},"BMW M5 Plus":{"player":"King Rodriguez","car":"BMW M5 Plus","time":18019},"Ferrari 458":{"player":"Woltage.2","car":"Ferrari 458","time":25352},"2014 Dodge Charger":{"player":"King Rodriguez","car":"2014 Dodge Charger","time":29132},"Audi S3 2015":{"player":"King Rodriguez","car":"Audi S3 2015","time":25742},"NULL":{"player":"Max ","car":"NULL","time":26029},"Toyota Sienna 2021":{"player":"Max Rodriguez","car":"Toyota Sienna 2021","time":34685},"BMW i8":{"player":"King Rodriguez","car":"BMW i8","time":24743},"F-22A":{"player":"Yalaina-DDR8","car":"F-22A","time":90697},"Mercedes AMG GTS":{"player":"vJack","car":"Mercedes AMG GTS","time":19029},"Bugatti Chiron 2017":{"player":"King Rodriguez","car":"Bugatti Chiron 2017","time":22371},"Kawasaki Ninja ZX10R":{"player":"King Rodriguez","car":"Kawasaki Ninja ZX10R","time":28422},"Yamaha R6":{"player":"ThatGuyJacobee","car":"Yamaha R6","time":28188},"Jeep TrackHawk 2018":{"player":"King Rodriguez","car":"Jeep TrackHawk 2018","time":22887}},"Highway Sprint":{"NULL":{"player":"vJack","car":"NULL","time":93300}},"BCSO Training Course":{"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":26915},"Jester":{"player":"michael82","car":"Jester","time":37835}},"Race | North GP":{"NULL":{"player":"SOGGYBEAN","car":"NULL","time":42917},"Mercedes AMG GTS":{"player":"vJack","car":"Mercedes AMG GTS","time":217283},"SNOWMOBILE 2020":{"player":"ThatGuyJacobee","car":"SNOWMOBILE 2020","time":387242},"Windsor Drop":{"player":"[C-81][GCF-145] R. Freeman","car":"Windsor Drop","time":66673},"BMW M5":{"player":"Stormzy","car":"BMW M5","time":115460},"2014 Pierce prescue":{"player":"Klimek07","car":"2014 Pierce prescue","time":83300}},"Sandy Airfield":{"Jester":{"player":"michael82","car":"Jester","time":47982},"Neon":{"player":"ThunderBird","car":"Neon","time":30312},"BMX":{"player":"vJack","car":"BMX","time":21014},"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":5607},"FH-1 Hunter":{"player":"*ZbyniU*","car":"FH-1 Hunter","time":36289},"Maverick":{"player":"vJack","car":"Maverick","time":29165},"RE-7B":{"player":"*ZbyniU*","car":"RE-7B","time":28456},"Vigilante":{"player":"*ZbyniU*","car":"Vigilante","time":6172},"Oppressor":{"player":"michael82","car":"Oppressor","time":21360}},"Airport Circuit":{"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":22916},"Neon":{"player":"ThunderBird","car":"Neon","time":92652},"P-996 LAZER":{"player":"ThatGuyJacobee","car":"P-996 LAZER","time":59216}},"Cannonball Run":{"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":81298}},"Race | Highway Sprint":{"NULL":{"player":"DR-KingRodriguez","car":"NULL","time":98074},"Ferrari 458":{"player":"ThatGuyJacobee","car":"Ferrari 458","time":138033},"Lamborghini Aventador AVJ":{"player":"Kaleb","time":240744,"car":"Lamborghini Aventador AVJ"},"Deveste Eight":{"player":"jb3794339","car":"Deveste Eight","time":181173}},"North GP":{"Baller":{"player":"dcapone00","car":"Baller","time":410118},"Neon":{"player":"ThunderBird","car":"Neon","time":354856},"NULL":{"player":"vJack","car":"NULL","time":191650}},"Lakeside Loop":{"NULL":{"player":"DR*KINGPORNHUB","car":"NULL","time":206147}},"Race | Airport Circuit":{"Ferrari 458":{"player":"ThatGuyJacobee","car":"Ferrari 458","time":69880},"Nissan Skyline GTR":{"player":"ThatGuyJacobee","car":"Nissan Skyline GTR","time":83978},"Formula 1":{"player":"vJack","car":"Formula 1","time":42751}},"Race | Cannonball Run":{"2013 Dodge Charger":{"player":"vJack","car":"2013 Dodge Charger","time":263368}},"Observatory Loop":{"NULL":{"player":"ThatGuyJacobee","car":"NULL","time":34126}},"Race | Airport Drag":{"NULL":{"player":"B. LAO","car":"NULL","time":12630},"Formula 1":{"player":"King Rodriguez","car":"Formula 1","time":7629},"Nissan Skyline GTR":{"player":"ThatGuyJacobee","car":"Nissan Skyline GTR","time":9693},"2010 Chevy Tahoe":{"player":"King Rodriguez","car":"2010 Chevy Tahoe","time":12821},"Police Corvette 2019":{"player":"Woltage.2","car":"Police Corvette 2019","time":14484},"BMW M5 Plus":{"player":"King Rodriguez","car":"BMW M5 Plus","time":8652}},"Race | Observatory Loop":{"Mazda Miata":{"player":"[C-291][GCF-34][MS-13] K. Lee","car":"Mazda Miata","time":79814},"Formula 1":{"player":"ThatGuyJacobee","car":"Formula 1","time":54744},"Nissan Skyline GTR":{"player":"[C-291][GCF-34][MS-13] K. Lee","car":"Nissan Skyline GTR","time":67881},"Porsche 911 GT3RS":{"player":"[YJ-18] Sinapsey","car":"Porsche 911 GT3RS","time":106410},"NULL":{"player":"[YJ-18] Sinapsey","car":"NULL","time":55388},"Mercedes AMG GTS":{"player":"vJack","car":"Mercedes AMG GTS","time":56747},"2013 Dodge Charger":{"player":"vJack","car":"2013 Dodge Charger","time":64004},"BMW M5":{"player":"[C-291][GCF-34][MS-13] K. Lee","car":"BMW M5","time":81837},"BMW M5 Plus":{"player":"[EGRP-02]King Rodriguez","car":"BMW M5 Plus","time":70752},"Gauntlet Classic Custom":{"player":"[C-291][GCF-34][MS-13] K. Lee","car":"Gauntlet Classic Custom","time":76765}},"Race | Hillside Loop":{"Bronco Wildtrak 2021":{"player":"ThatGuyJacobee","car":"Bronco Wildtrak 2021","time":210714},"BMW M8 Widebody":{"player":"[C-291][GCF-34][MS-13] K. Lee","car":"BMW M8 Widebody","time":195442},"Gauntlet Classic Custom":{"player":"[C-291][GCF-34][MS-13] K. Lee","car":"Gauntlet Classic Custom","time":230432}},"Airport Drag Race":{"NULL":{"player":"iwan.ralphs","car":"NULL","time":7030}}}
\ No newline at end of file
+{"Race | Lakeside Loop":{"EGRP Staff Buggy":{"car":"EGRP Staff Buggy","player":"[EGRP-02]King Rodriguez","time":43710},"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":196803},"Ferrari 458":{"car":"Ferrari 458","player":"ThatGuyJacobee","time":177461},"SNOWMOBILE 2020":{"car":"SNOWMOBILE 2020","player":"[C-146] E. Cartman","time":107015},"Ford Crown Vic 2011":{"car":"Ford Crown Vic 2011","player":"AnySpiderBat","time":231931}},"North GP":{"Baller":{"car":"Baller","player":"dcapone00","time":410118},"NULL":{"car":"NULL","player":"vJack","time":191650},"Neon":{"car":"Neon","player":"ThunderBird","time":354856}},"Race | Hillside Loop":{"Gauntlet Classic Custom":{"car":"Gauntlet Classic Custom","player":"[C-291][GCF-34][MS-13] K. Lee","time":230432},"Bronco Wildtrak 2021":{"car":"Bronco Wildtrak 2021","player":"ThatGuyJacobee","time":210714},"BMW M8 Widebody":{"car":"BMW M8 Widebody","player":"[C-291][GCF-34][MS-13] K. Lee","time":195442}},"Cannonball Run":{"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":81298}},"Race | Cannonball Run":{"2013 Dodge Charger":{"car":"2013 Dodge Charger","player":"vJack","time":263368}},"Race | Airport Circuit":{"Ferrari 458":{"car":"Ferrari 458","player":"ThatGuyJacobee","time":69880},"Nissan Skyline GTR":{"car":"Nissan Skyline GTR","player":"ThatGuyJacobee","time":83978},"Formula 1":{"car":"Formula 1","player":"vJack","time":42751}},"Lakeside Loop":{"NULL":{"car":"NULL","player":"DR*KINGPORNHUB","time":206147}},"Race | Airport Drag Race":{"NULL":{"car":"NULL","player":"DR-KingRodriguez","time":9270}},"Observatory Loop":{"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":34126}},"Highway Sprint":{"NULL":{"car":"NULL","player":"vJack","time":93300}},"BCSO Training Course":{"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":26915},"Jester":{"car":"Jester","player":"michael82","time":37835}},"Race | Sandy Airfield":{"Jeep TrackHawk 2018":{"car":"Jeep TrackHawk 2018","player":"King Rodriguez","time":22887},"Mini JCW 2020":{"car":"Mini JCW 2020","player":"King Rodriguez","time":29879},"BMW M5 Plus":{"car":"BMW M5 Plus","player":"King Rodriguez","time":18019},"Chevy Camaro20":{"car":"Chevy Camaro20","player":"Max Rodriguez","time":32202},"Ford Raptor F-150":{"car":"Ford Raptor F-150","player":"King Rodriguez","time":28515},"Yamaha R6":{"car":"Yamaha R6","player":"ThatGuyJacobee","time":28188},"NULL":{"car":"NULL","player":"Max ","time":26029},"2014 Dodge Charger":{"car":"2014 Dodge Charger","player":"King Rodriguez","time":29132},"2016 Ford Explorer":{"car":"2016 Ford Explorer","player":"King Rodriguez","time":26526},"Toyota Sienna 2021":{"car":"Toyota Sienna 2021","player":"Max Rodriguez","time":34685},"Space Docker":{"car":"Space Docker","player":"DyslexicStoner240.","time":29122},"Formula 1":{"car":"Formula 1","player":"vJack","time":15914},"Ferrari 458":{"car":"Ferrari 458","player":"Woltage.2","time":25352},"Mercedes AMG GTS":{"car":"Mercedes AMG GTS","player":"vJack","time":19029},"Audi S3 2015":{"car":"Audi S3 2015","player":"King Rodriguez","time":25742},"Bugatti Chiron 2017":{"car":"Bugatti Chiron 2017","player":"King Rodriguez","time":22371},"F-22A":{"car":"F-22A","player":"Yalaina-DDR8","time":90697},"BMW i8":{"car":"BMW i8","player":"King Rodriguez","time":24743},"Kawasaki Ninja ZX10R":{"car":"Kawasaki Ninja ZX10R","player":"King Rodriguez","time":28422},"Nissan Skyline GTR":{"car":"Nissan Skyline GTR","player":"ThatGuyJacobee","time":29921}},"Race | Timetrial Event - 04/04/21":{"Gauntlet Classic Custom":{"car":"Gauntlet Classic Custom","player":"[C-291][GCF-34][MS-13] K. Lee","time":191695},"Ford Velociraptor 6x6":{"car":"Ford Velociraptor 6x6","player":"ThatGuyJacobee","time":132814},"Chevrolet Silverado 1980":{"car":"Chevrolet Silverado 1980","player":"King Rodriguez","time":156244}},"Airport Circuit":{"P-996 LAZER":{"car":"P-996 LAZER","player":"ThatGuyJacobee","time":59216},"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":22916},"Neon":{"car":"Neon","player":"ThunderBird","time":92652}},"Race | North GP":{"Mercedes AMG GTS":{"car":"Mercedes AMG GTS","player":"vJack","time":217283},"NULL":{"car":"NULL","player":"SOGGYBEAN","time":42917},"BMW M5":{"car":"BMW M5","player":"Stormzy","time":115460},"Windsor Drop":{"car":"Windsor Drop","player":"[C-81][GCF-145] R. Freeman","time":66673},"SNOWMOBILE 2020":{"car":"SNOWMOBILE 2020","player":"ThatGuyJacobee","time":387242},"2014 Pierce prescue":{"car":"2014 Pierce prescue","player":"Klimek07","time":83300}},"Hollywood Hills":{"NULL":{"car":"NULL","player":"Kryplos125","time":120053}},"Airport Drag Race":{"NULL":{"car":"NULL","player":"iwan.ralphs","time":7030}},"Sandy Airfield":{"FH-1 Hunter":{"car":"FH-1 Hunter","player":"*ZbyniU*","time":36289},"BMX":{"car":"BMX","player":"vJack","time":21014},"Maverick":{"car":"Maverick","player":"vJack","time":29165},"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":5607},"Neon":{"car":"Neon","player":"ThunderBird","time":30312},"Vigilante":{"car":"Vigilante","player":"*ZbyniU*","time":6172},"Jester":{"car":"Jester","player":"michael82","time":47982},"RE-7B":{"car":"RE-7B","player":"*ZbyniU*","time":28456},"Oppressor":{"car":"Oppressor","player":"michael82","time":21360}},"Race | Airport Drag":{"2010 Chevy Tahoe":{"car":"2010 Chevy Tahoe","player":"King Rodriguez","time":12821},"NULL":{"car":"NULL","player":"B. LAO","time":12630},"Police Corvette 2019":{"car":"Police Corvette 2019","player":"Woltage.2","time":14484},"BMW M5 Plus":{"car":"BMW M5 Plus","player":"King Rodriguez","time":8652},"Formula 1":{"car":"Formula 1","player":"King Rodriguez","time":7629},"Nissan Skyline GTR":{"car":"Nissan Skyline GTR","player":"ThatGuyJacobee","time":9693}},"Race | BCSO Training Course":{"Mercedes AMG GTS":{"car":"Mercedes AMG GTS","player":"vJack","time":78151},"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":100457},"Neon":{"car":"Neon","player":"ThunderBird","time":105527},"Nissan Skyline GTR":{"car":"Nissan Skyline GTR","player":"ThatGuyJacobee","time":105983},"2013 Dodge Charger":{"car":"2013 Dodge Charger","player":"vJack","time":94246},"2016 Dodge Ram":{"car":"2016 Dodge Ram","player":"vJack","time":119099}},"Race | Observatory Loop":{"2013 Dodge Charger":{"car":"2013 Dodge Charger","player":"vJack","time":64004},"BMW M5 Plus":{"car":"BMW M5 Plus","player":"[EGRP-02]King Rodriguez","time":70752},"Porsche 911 GT3RS":{"car":"Porsche 911 GT3RS","player":"[YJ-18] Sinapsey","time":106410},"Mazda Miata":{"car":"Mazda Miata","player":"[C-291][GCF-34][MS-13] K. Lee","time":79814},"Ford F-150 Lightning 2022":{"car":"Ford F-150 Lightning 2022","player":"ThatGuyJacobee","time":68440},"Mercedes AMG GTS":{"car":"Mercedes AMG GTS","player":"vJack","time":56747},"NULL":{"car":"NULL","player":"[YJ-18] Sinapsey","time":55388},"BMW M5":{"car":"BMW M5","player":"[C-291][GCF-34][MS-13] K. Lee","time":81837},"Gauntlet Classic Custom":{"car":"Gauntlet Classic Custom","player":"[C-291][GCF-34][MS-13] K. Lee","time":76765},"Formula 1":{"car":"Formula 1","player":"ThatGuyJacobee","time":54744},"Nissan Skyline GTR":{"car":"Nissan Skyline GTR","player":"[C-291][GCF-34][MS-13] K. Lee","time":67881}},"Race | Highway Sprint":{"Ferrari 458":{"car":"Ferrari 458","player":"ThatGuyJacobee","time":138033},"Lamborghini Aventador AVJ":{"car":"Lamborghini Aventador AVJ","player":"Kaleb","time":240744},"NULL":{"car":"NULL","player":"DR-KingRodriguez","time":98074},"Deveste Eight":{"car":"Deveste Eight","player":"jb3794339","time":181173}},"Race | Hollywood Hills":{"Ford F-150 Lightning 2022":{"car":"Ford F-150 Lightning 2022","player":"ThatGuyJacobee","time":105278},"Mercedes AMG GTS":{"car":"Mercedes AMG GTS","player":"vJack","time":85823},"NULL":{"car":"NULL","player":"ThatGuyJacobee","time":102048},"Audi R8 V10":{"car":"Audi R8 V10","player":"ThatGuyJacobee","time":100677}},"North Rallycross ":{"Neon":{"car":"Neon","player":"ThunderBird","time":351612}}}
\ No newline at end of file
diff --git a/resources/Wheel-Damage/.github/FUNDING.yml b/resources/Wheel-Damage/.github/FUNDING.yml
new file mode 100644
index 000000000..a51efd948
--- /dev/null
+++ b/resources/Wheel-Damage/.github/FUNDING.yml
@@ -0,0 +1,13 @@
+# 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: azeroth
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/resources/Wheel-Damage/LICENSE b/resources/Wheel-Damage/LICENSE
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/resources/Wheel-Damage/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/resources/Wheel-Damage/README.md b/resources/Wheel-Damage/README.md
new file mode 100644
index 000000000..5bbe68336
--- /dev/null
+++ b/resources/Wheel-Damage/README.md
@@ -0,0 +1,37 @@
+
+
+[](https://github.com/Azerothwav) [](https://forum.cfx.re/t/realistic-vehicle-failure-repair-fix/4887760/2) [](https://www.youtube.com/channel/UCH7coJ4d1gqh8BMMHacGQ5A) [](https://www.lua.org) [](https://ko-fi.com/azeroth)
+
+# Installation
+ curl https://github.com/Azerothwav/Az_wheeldamage
+
+# Informations
+This script allows you to add a new element of realism to your server by adding the possibility of losing wheels.
+
+
+
+## Requirements to trigger the loss of wheels:
+- Make a jump that lasts more than 1 second (configurable in the config.lua)
+- Make a collision with a certain speed (configurable in the config.lua)
+
+
+The wheels are removable to be put back on the original vehicle.
+
+
+
+## Integration for other script
+A trigger is available to allow your mechanic script to repair the damaged vehicle.
+
+ TriggerEvent("az_wheel:fixvehicle")
+
+## Limitation
+The limitations of GTA make that the motorcycle wheels are not detachable, so they are immune. I also limited the wheels for boats, planes and helicopters. The off-road wheels allow to override the fact of losing a wheel via height but not via collisions.
+
+## Known bugs:
+- Vehicles can sometimes bug and go under the map (rare but it can happen)
+
+## Framework
+**QBCore / ESX / Standalone / Custom**
+
+# Preview
+https://www.youtube.com/watch?v=TYFLSRikyyA&t=1s
diff --git a/resources/Wheel-Damage/client/client.lua b/resources/Wheel-Damage/client/client.lua
new file mode 100644
index 000000000..8ae3faa7d
--- /dev/null
+++ b/resources/Wheel-Damage/client/client.lua
@@ -0,0 +1,466 @@
+Config.InitFrameWork()
+
+local WheelAlreadyBroken = {}
+local objroue = {}
+table.insert(WheelAlreadyBroken, {index = 'none', vehicule = 'none'})
+
+local RouePos = {
+ [0] = {bone = 'wheel_lf', props = 'prop_wheel_01'},
+ [1] = {bone = 'wheel_rf', props = 'prop_wheel_01'},
+ [2] = {bone = 'wheel_lr', props = 'prop_wheel_01'},
+ [3] = {bone = 'wheel_rr', props = 'prop_wheel_01'},
+}
+
+startcheck = true
+local havefind = false
+local havefind2 = false
+local havefind3 = false
+local havefind4 = false
+Citizen.CreateThread(function()
+ while startcheck do
+ local vehicle = GetVehiclePedIsIn(GetPlayerPed(-1), false)
+ local wait = 1000
+ if vehicle ~= 0 and GetPedInVehicleSeat(GetVehiclePedIsIn(PlayerPedId()), -1) == PlayerPedId() then
+ local classveh = GetVehicleClass(vehicle)
+ local classwheel = GetVehicleWheelType(vehicle)
+ if classveh == 14 or classveh == 15 or classveh == 16 or classveh == 8 or classwheel == 4 then
+ else
+ wait = 0
+ local roue = GetVehicleNumberOfWheels(vehicle) - 1
+ for i = 0, roue, 1 do
+ if tonumber(GetVehicleWheelSuspensionCompression(vehicle, i)) == 0.00 and not injump and GetVehicleWheelXOffset(vehicle, i) ~= '-nan' then
+ injump = true
+ Citizen.SetTimeout(Config.TimeToDetachWheel, function()
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i then
+ havefind = true
+ end
+ end
+ if not havefind then
+ ChechIfDetach(vehicle, i)
+ else
+ if i < 3 then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i + 1 then
+ havefind2 = true
+ end
+ end
+ if not havefind2 then
+ ChechIfDetach(vehicle, i + 1)
+ else
+ if i < 3 then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i + 2 then
+ havefind3 = true
+ end
+ end
+ if not havefind3 then
+ ChechIfDetach(vehicle, i + 2)
+ else
+ if i < 3 then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i + 3 then
+ havefind4 = true
+ end
+ end
+ if not havefind4 then
+ ChechIfDetach(vehicle, i + 3)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end)
+ end
+ end
+ end
+ end
+ Citizen.Wait(wait)
+ end
+end)
+local inchek = false
+Citizen.CreateThread(function()
+ lastkm = nil
+ oldcompteurlaunch = false
+ local wait = 1000
+ while startcheck do
+ local vehicle = GetVehiclePedIsIn(GetPlayerPed(-1), false)
+ if vehicle ~= 0 and GetPedInVehicleSeat(GetVehiclePedIsIn(PlayerPedId()), -1) == PlayerPedId() then
+ local classveh = GetVehicleClass(vehicle)
+ if classveh == 14 or classveh == 15 or classveh == 16 or classveh == 8 then
+ else
+ wait = 0
+ if not oldcompteurlaunch then
+ oldcompteurlaunch = true
+ Citizen.SetTimeout(1000, function()
+ lastkm = math.ceil(GetEntitySpeed(vehicle) * 3.6)
+ oldcompteurlaunch = false
+ end)
+ end
+ if lastkm then
+ if HasEntityCollidedWithAnything(vehicle) and lastkm >= Config.KmMax then
+ if not inchek and GetVehicleWheelXOffset(vehicle, i) ~= '-nan' then
+ inchek = true
+ local roue = GetVehicleNumberOfWheels(vehicle) - 1
+ for i = 0, roue, 1 do
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i then
+ havefind = true
+ end
+ end
+ if not havefind then
+ ChechIfDetachGround(vehicle, i)
+ else
+ if i < 3 then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i + 1 then
+ havefind2 = true
+ end
+ end
+ if not havefind2 then
+ ChechIfDetachGround(vehicle, i + 1)
+ else
+ if i < 3 then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i + 2 then
+ havefind3 = true
+ end
+ end
+ if not havefind3 then
+ ChechIfDetachGround(vehicle, i + 2)
+ else
+ if i < 3 then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i + 3 then
+ havefind4 = true
+ end
+ end
+ if not havefind4 then
+ ChechIfDetachGround(vehicle, i + 3)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ Citizen.Wait(wait)
+ end
+end)
+
+local wheelpickup = false
+
+if Config.CommandFix then
+ RegisterCommand("startfix", function()
+ for k, v in pairs(WheelAlreadyBroken) do
+ local playercoords = GetEntityCoords(GetPlayerPed(-1))
+ local vehicle = GetVehiclePedIsIn(GetPlayerPed(-1), false)
+ if vehicle ~= 0 then
+ if vehicle == v.vehicule then
+ wheelpickup = true
+ SetCurrentPedWeapon(GetPlayerPed(-1), 0xA2719263)
+ AttachEntityToEntity(v.obj, GetPlayerPed(-1), GetPedBoneIndex(GetPlayerPed(-1), 28422), 0.0, 0.0, 0.0, 135.0, 0.0, 0.0, 1, 1, 0, 0, 2, 1)
+ Config.SendNotification(Config.Lang["StartFixWheel"])
+ Citizen.Wait(5000)
+ startdistancevehiclevar = true
+ Startdistancevehicle(v.vehicule, v.obj, v.index)
+ end
+ else
+ Config.SendNotification(Config.Lang["EnterInVehicleToFix"])
+ end
+ end
+ end, false)
+end
+
+if Config.TriggerFix then
+ RegisterNetEvent('az_wheel:fixvehicle')
+ AddEventHandler('az_wheel:fixvehicle', function()
+ for k, v in pairs(WheelAlreadyBroken) do
+ local playercoords = GetEntityCoords(GetPlayerPed(-1))
+ local vehicle = GetVehiclePedIsIn(GetPlayerPed(-1), false)
+ if vehicle ~= 0 then
+ if vehicle == v.vehicule then
+ wheelpickup = true
+ SetCurrentPedWeapon(GetPlayerPed(-1), 0xA2719263)
+ AttachEntityToEntity(v.obj, GetPlayerPed(-1), GetPedBoneIndex(GetPlayerPed(-1), 28422), 0.0, 0.0, 0.0, 135.0, 0.0, 0.0, 1, 1, 0, 0, 2, 1)
+ Config.SendNotification(Config.Lang["StartFixWheel"])
+ Citizen.Wait(5000)
+ startdistancevehiclevar = true
+ Startdistancevehicle(v.vehicule, v.obj, v.index)
+ end
+ else
+ Config.SendNotification(Config.Lang["EnterInVehicleToFix"])
+ end
+ end
+ end)
+end
+
+Citizen.CreateThread(function()
+ while true do
+ local wait = 1000
+ for k, v in pairs(WheelAlreadyBroken) do
+ local playercoords = GetEntityCoords(GetPlayerPed(-1))
+ local coordsroue = GetEntityCoords(v.obj)
+ if GetDistanceBetweenCoords(playercoords, coordsroue, true) < 2 then
+ wait = 0
+ if not wheelpickup then
+ DrawText3Ds(coordsroue.x, coordsroue.y, coordsroue.z + 0.50, Config.Lang["PressToPickWheel"])
+ end
+ if IsControlJustReleased(0, 38) and not wheelpickup then
+ wheelpickup = true
+ SetCurrentPedWeapon(GetPlayerPed(-1), 0xA2719263)
+ AttachEntityToEntity(v.obj, GetPlayerPed(-1), GetPedBoneIndex(GetPlayerPed(-1), 28422), 0.0, 0.0, 0.0, 135.0, 0.0, 0.0, 1, 1, 0, 0, 2, 1)
+ startdistancevehiclevar = true
+ Startdistancevehicle(v.vehicule, v.obj, v.index)
+ end
+ end
+ end
+ Citizen.Wait(wait)
+ end
+end)
+
+local sendtoserver = false
+function Startdistancevehicle(vehicule, obj, index)
+ local wait = 1000
+ local draw3dtext = false
+ Citizen.CreateThread(function()
+ while startdistancevehiclevar do
+ local vehiclecoords = GetEntityCoords(vehicule)
+ local playercoords = GetEntityCoords(GetPlayerPed(-1))
+ local vehicleheading = GetEntityHeading(vehicule)
+ if GetDistanceBetweenCoords(playercoords, vehiclecoords, true) < 3 then
+ wait = 0
+ if not draw3dtext then
+ DrawText3Ds(vehiclecoords.x, vehiclecoords.y, vehiclecoords.z, Config.Lang["PressToFixWheel"])
+ end
+ if IsControlJustReleased(0, 38) then
+ local findplayerin = false
+ for l = 0, 5, 1 do
+ if GetPedInVehicleSeat(vehicule, l - 1) ~= 0 then
+ findplayerin = true
+ end
+ end
+ if not findplayerin then
+ draw3dtext = true
+ Citizen.SetTimeout(5000, function()
+ sendtoserver = false
+ end)
+ local phantomveh = CreateVehicle(GetHashKey("baller"), vehiclecoords, vehicleheading, false, false)
+ FreezeEntityPosition(phantomveh, true)
+ SetEntityCollision(phantomveh, false, false)
+ SetEntityVisible(phantomveh, false, false)
+ for k, v in pairs(RouePos) do
+ if k == index then
+ local roueposition = GetWorldPositionOfEntityBone(phantomveh, GetEntityBoneIndexByName(phantomveh, v.bone))
+ TaskStartScenarioInPlace(GetPlayerPed(-1), "CODE_HUMAN_MEDIC_KNEEL", 0, true)
+ Citizen.Wait(Config.TimeToAttachWheel)
+ ClearPedTasksImmediately(GetPlayerPed(-1))
+ if not sendtoserver then
+ sendtoserver = true
+ TriggerServerEvent('az_wheel:updatewheel', VehToNet(vehicule), index, GetVehicleWheelXOffset(phantomveh, index), ObjToNet(obj))
+ end
+ DeleteEntity(phantomveh)
+ end
+ end
+ Citizen.SetTimeout(2500, function()
+ wheelpickup = false
+ if index == 0 then
+ if havefind then
+ havefind = false
+ end
+ elseif index == 1 then
+ if havefind2 then
+ havefind2 = false
+ end
+ elseif index == 2 then
+ if havefind3 then
+ havefind3 = false
+ end
+ elseif index == 3 then
+ if havefind4 then
+ havefind4 = false
+ end
+ end
+ end)
+ startdistancevehiclevar = false
+ else
+ Config.SendNotification(Config.Lang["PlayerInVeh"])
+ end
+ end
+ end
+ Citizen.Wait(wait)
+ end
+ end)
+end
+
+function DrawText3Ds(x,y,z, text)
+ local onScreen,_x,_y=World3dToScreen2d(x,y,z)
+ local px,py,pz=table.unpack(GetGameplayCamCoords())
+
+ SetTextScale(0.32, 0.32)
+ SetTextFont(4)
+ SetTextProportional(1)
+ SetTextColour(255, 255, 255, 255)
+ SetTextEntry("STRING")
+ SetTextCentre(1)
+ AddTextComponentString(text)
+ DrawText(_x,_y)
+ local factor = (string.len(text)) / 500
+ DrawRect(_x,_y+0.0125, 0.015+ factor, 0.03, 0, 0, 0, 80)
+end
+
+function ChechIfDetachGround(vehicle, i)
+ if not incooldowncheckground then
+ local findwheel = false
+ local havefind = false
+ local inground = false
+ incooldowncheckground = true
+ Citizen.SetTimeout(1000, function()
+ incooldowncheckground = false
+ inchek = false
+ end)
+ if GetVehicleWheelXOffset(vehicle, i) ~= '-nan' then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i then
+ havefind = true
+ end
+ if v.obj == objroue[i] then
+ havefind = true
+ end
+ end
+ if not havefind then
+ for k, v in pairs(RouePos) do
+ if k == i then
+ if objroue[i] == nil then
+ local roueposition = GetWorldPositionOfEntityBone(vehicle, GetEntityBoneIndexByName(vehicle, v.bone))
+ if not HasModelLoaded(v.props) then
+ RequestModel(v.props)
+ while not HasModelLoaded(v.props) do
+ Citizen.Wait(1)
+ end
+ end
+ objroue[i] = CreateObject(v.props, roueposition, false)
+ TriggerServerEvent('az_wheel:setoffsetserver', VehToNet(vehicle), i, -math.random(1000, 1025), objroue[i])
+ end
+ end
+ end
+ else
+ if i < 3 then
+ ChechIfDetachGround(vehicle, i + 1)
+ end
+ end
+ end
+ end
+end
+
+function ChechIfDetach(vehicle, i)
+ if not incooldowncheck then
+ local havefind = false
+ local inground = false
+ local hauteur = 0
+ incooldowncheck = true
+ Citizen.SetTimeout(1000, function()
+ incooldowncheck = false
+ injump = false
+ if inchek then
+ inchek = false
+ end
+ end)
+ if tonumber(GetVehicleWheelSuspensionCompression(vehicle, i)) == 0.00 and GetVehicleWheelXOffset(vehicle, i) ~= '-nan' then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index == i then
+ havefind = true
+ end
+ if v.obj and v.obj ~= nil then
+ if v.obj == objroue[i] then
+ havefind = true
+ end
+ end
+ end
+ if not havefind then
+ while not inground do
+ for n = 0, 4, 1 do
+ hauteur = hauteur + tonumber(GetVehicleWheelSuspensionCompression(vehicle, i))
+ if hauteur > 0 then
+ inground = true
+ end
+ end
+ Citizen.Wait(0)
+ end
+ for k, v in pairs(RouePos) do
+ if k == i then
+ if objroue[i] == nil then
+ local roueposition = GetWorldPositionOfEntityBone(vehicle, GetEntityBoneIndexByName(vehicle, v.bone))
+ if not HasModelLoaded(v.props) then
+ RequestModel(v.props)
+ while not HasModelLoaded(v.props) do
+ Citizen.Wait(1)
+ end
+ end
+ objroue[i] = CreateObject(v.props, roueposition, false)
+ TriggerServerEvent('az_wheel:setoffsetserver', VehToNet(vehicle), i, -math.random(1000, 1025), objroue[i])
+ end
+ end
+ end
+ else
+ if i < 3 then
+ ChechIfDetach(vehicle, i + 1)
+ end
+ end
+ end
+ end
+end
+
+RegisterNetEvent('az_wheel:setoffset')
+AddEventHandler('az_wheel:setoffset', function(veh, index, offset, obj)
+ if index and veh and offset then
+ table.insert(WheelAlreadyBroken, {index = index, vehicule = NetToVeh(veh), obj = obj})
+ SetVehicleWheelXOffset(NetToVeh(veh), index, offset)
+ end
+end)
+
+RegisterNetEvent('az_wheel:updatewheelclient')
+AddEventHandler('az_wheel:updatewheelclient', function(veh, index, offset, obj)
+ if index and veh and offset and obj then
+ SetVehicleWheelXOffset(NetToVeh(veh), index, offset)
+ for z, w in pairs(WheelAlreadyBroken) do
+ if w.obj == obj and w.index == index then
+ WheelAlreadyBroken[z] = nil
+ end
+ end
+ DeleteEntity(obj)
+ for k, v in pairs(objroue) do
+ if v == obj then
+ objroue[k] = nil
+ end
+ end
+ Citizen.Wait(5000)
+ end
+end)
+
+Citizen.CreateThread(function()
+ while true do
+ local vehicle = GetVehiclePedIsIn(GetPlayerPed(-1), false)
+ if vehicle ~= 0 then
+ if #WheelAlreadyBroken > 0 then
+ for k, v in pairs(WheelAlreadyBroken) do
+ if v.index and v.index ~= nil and v.vehicule and v.vehicule ~= 'none' then
+ if v.vehicule == vehicle then
+ SetEntityCollision(v.vehicule, true, true)
+ SetVehicleWheelXOffset(v.vehicule, v.index, -math.random(1000, 1025))
+ end
+ end
+ end
+ end
+ end
+ Citizen.Wait(0)
+ end
+end)
\ No newline at end of file
diff --git a/resources/Wheel-Damage/config.lua b/resources/Wheel-Damage/config.lua
new file mode 100644
index 000000000..076591ce5
--- /dev/null
+++ b/resources/Wheel-Damage/config.lua
@@ -0,0 +1,38 @@
+Config = {}
+
+Config.FrameWork = "" -- ESX / QBCore / custom
+
+Config.TriggerFix = false
+Config.CommandFix = true
+
+Config.TimeToDetachWheel = 1000
+Config.TimeToAttachWheel = 5000
+Config.KmMax = 175
+
+Config.Lang = {
+ ["StartFixWheel"] = 'Repair of the wheels thrown, please exit the vehicle',
+ ["EnterInVehicleToFix"] = 'No vehicle found, please enter the vehicle to be repaired',
+ ["PressToPickWheel"] = 'Press E to pick up the wheel',
+ ["PressToFixWheel"] = 'Press E to fix the wheel',
+ ["PlayerInVeh"] = 'There is someone in the vehicle'
+}
+
+Config.SendNotification = function(msg)
+ if Config.FrameWork == "ESX" then
+ ESX.ShowNotification(msg)
+ elseif Config.FrameWork == "QBCore" then
+ QBCore.Functions.Notify(msg, 'success')
+ elseif Config.FrameWork == "custom" then
+ print(msg)
+ end
+end
+
+Config.InitFrameWork = function()
+ if Config.FrameWork == "ESX" then
+ ESX = nil TriggerEvent('esx:getSharedObject', function(obj) ESX = obj end)
+ elseif Config.FrameWork == "QBCore" then
+ QBCore = exports['qb-core']:GetCoreObject()
+ elseif Config.FrameWork == "custom" then
+
+ end
+end
diff --git a/resources/Wheel-Damage/fxmanifest.lua b/resources/Wheel-Damage/fxmanifest.lua
new file mode 100644
index 000000000..aa3483afa
--- /dev/null
+++ b/resources/Wheel-Damage/fxmanifest.lua
@@ -0,0 +1,14 @@
+fx_version "adamant"
+game "gta5"
+
+shared_scripts {
+ "config.lua"
+}
+
+client_scripts {
+ 'client/*.lua'
+}
+
+server_scripts {
+ "server/*.lua"
+}
\ No newline at end of file
diff --git a/resources/Wheel-Damage/server/server.lua b/resources/Wheel-Damage/server/server.lua
new file mode 100644
index 000000000..4ea8f431b
--- /dev/null
+++ b/resources/Wheel-Damage/server/server.lua
@@ -0,0 +1,9 @@
+RegisterNetEvent('az_wheel:updatewheel')
+AddEventHandler('az_wheel:updatewheel', function(veh, index, offset, obj)
+ TriggerClientEvent('az_wheel:updatewheelclient', -1, veh, index, offset, obj)
+end)
+
+RegisterNetEvent('az_wheel:setoffsetserver')
+AddEventHandler('az_wheel:setoffsetserver', function(veh, index, offset, obj)
+ TriggerClientEvent('az_wheel:setoffset', -1, veh, index, offset, obj)
+end)
\ No newline at end of file
diff --git a/resources/[EGRP-Discord-Integration]/Badger_Discord_API/config.lua b/resources/[EGRP-Discord-Integration]/Badger_Discord_API/config.lua
index 4ad2cb904..94c5c6988 100644
--- a/resources/[EGRP-Discord-Integration]/Badger_Discord_API/config.lua
+++ b/resources/[EGRP-Discord-Integration]/Badger_Discord_API/config.lua
@@ -28,11 +28,11 @@ Config = {
}
Config.Splash = {
- Header_IMG = 'https://forum.cfx.re/uploads/default/original/3X/a/6/a6ad03c9fb60fa7888424e7c9389402846107c7e.png',
+ Header_IMG = '',
Enabled = false,
Wait = 10, -- How many seconds should splash page be shown for? (Max is 12)
Heading1 = "Welcome to Elite Gaming RP.",
Heading2 = "Make sure to join our Discord and check out our website!",
Discord_Link = 'https://discord.gg/x6PwsHtVjE',
- Website_Link = 'https://elite-gaming.co.uk',
+ Website_Link = 'https://elite-gaming.gg',
}
\ No newline at end of file
diff --git a/resources/[EGRP-Discord-Integration]/Badger_Discord_API/server.lua b/resources/[EGRP-Discord-Integration]/Badger_Discord_API/server.lua
index f14878c80..3e133a5f3 100644
--- a/resources/[EGRP-Discord-Integration]/Badger_Discord_API/server.lua
+++ b/resources/[EGRP-Discord-Integration]/Badger_Discord_API/server.lua
@@ -25,7 +25,7 @@ AddEventHandler('Badger_Discord_API:PlayerLoaded', function()
end)
--card = '{"type":"AdaptiveCard","$schema":"http://adaptivecards.io/schemas/adaptive-card.json","version":"1.2","body":[{"type":"Container","items":[{"type":"TextBlock","text":"Badger_Discord_API","wrap":true,"fontType":"Default","size":"ExtraLarge","weight":"Bolder","color":"Light","horizontalAlignment":"Center"},{"type":"TextBlock","text":"' .. Config.Splash.Heading1 .. '","wrap":true,"size":"Large","weight":"Bolder","color":"Light"},{"type":"TextBlock","text":"' .. Config.Splash.Heading2 .. '","wrap":true,"color":"Light","size":"Medium"},{"type":"ColumnSet","height":"stretch","minHeight":"100px","bleed":true,"horizontalAlignment":"Center","columns":[{"type":"Column","width":"stretch","items":[{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Discord","url":"' .. Config.Splash.Discord_Link .. '","style":"positive"}]}],"height":"stretch"},{"type":"Column","width":"stretch","items":[{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Website","style":"positive","url":"' .. Config.Splash.Website_Link .. '"}]}]}]},{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Click to join Badger\'s Discord","style":"destructive","iconUrl":"https://i.gyazo.com/c629f37bb1aeed2c1bc1768fdc93bc1a.gif","url":"https://discord.com/invite/WjB5VFz"}]}],"style":"default","bleed":true,"height":"stretch","isVisible":true}]}'
-card = '{"type":"AdaptiveCard","$schema":"http://adaptivecards.io/schemas/adaptive-card.json","version":"1.2","body":[{"type":"Image","url":"' .. Config.Splash.Header_IMG .. '","horizontalAlignment":"Center"},{"type":"Container","items":[{"type":"TextBlock","text":"Badger_Discord_API","wrap":true,"fontType":"Default","size":"ExtraLarge","weight":"Bolder","color":"Light","horizontalAlignment":"Center"},{"type":"TextBlock","text":"' .. Config.Splash.Heading1 .. '","wrap":true,"size":"Large","weight":"Bolder","color":"Light", "horizontalAlignment":"Center"},{"type":"TextBlock","text":"' .. Config.Splash.Heading2 .. '","wrap":true,"color":"Light","size":"Medium","horizontalAlignment":"Center"},{"type":"ColumnSet","height":"stretch","minHeight":"100px","bleed":true,"horizontalAlignment":"Center","columns":[{"type":"Column","width":"stretch","items":[{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Discord","url":"' .. Config.Splash.Discord_Link .. '","style":"positive"}]}],"height":"stretch"},{"type":"Column","width":"stretch","items":[{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Website","style":"positive","url":"' .. Config.Splash.Website_Link .. '"}]}]}]},{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Click to join Badger\'s Discord","style":"destructive","iconUrl":"https://i.gyazo.com/c629f37bb1aeed2c1bc1768fdc93bc1a.gif","url":"https://discord.com/invite/WjB5VFz"}]}],"style":"default","bleed":true,"height":"stretch","isVisible":true}]}'
+card = '{"type":"AdaptiveCard","$schema":"http://adaptivecards.io/schemas/adaptive-card.json","version":"1.2","body":[{"type":"Image","url":"' .. Config.Splash.Header_IMG .. '","horizontalAlignment":"Center"},{"type":"Container","items":[{"type":"TextBlock","text":"Server Information","wrap":true,"fontType":"Default","size":"ExtraLarge","weight":"Bolder","color":"Light","horizontalAlignment":"Center"},{"type":"TextBlock","text":"' .. Config.Splash.Heading1 .. '","wrap":true,"size":"Large","weight":"Bolder","color":"Light", "horizontalAlignment":"Center"},{"type":"TextBlock","text":"' .. Config.Splash.Heading2 .. '","wrap":true,"color":"Light","size":"Medium","horizontalAlignment":"Center"},{"type":"ColumnSet","height":"stretch","minHeight":"100px","bleed":true,"horizontalAlignment":"Center","columns":[{"type":"Column","width":"stretch","items":[{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Discord","url":"' .. Config.Splash.Discord_Link .. '","style":"positive"}]}],"height":"stretch"},{"type":"Column","width":"stretch","items":[{"type":"ActionSet","actions":[{"type":"Action.OpenUrl","title":"Website","style":"positive","url":"' .. Config.Splash.Website_Link .. '"}]}]}]}],"style":"default","bleed":true,"height":"stretch","isVisible":true}]}'
if Config.Splash.Enabled then
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
-- Player is connecting
diff --git a/resources/[EGRP-Discord-Integration]/Discord-Logging/server.lua b/resources/[EGRP-Discord-Integration]/Discord-Logging/server.lua
index 66cfcd890..787830595 100644
--- a/resources/[EGRP-Discord-Integration]/Discord-Logging/server.lua
+++ b/resources/[EGRP-Discord-Integration]/Discord-Logging/server.lua
@@ -1,6 +1,6 @@
-- Made by Tazio
-local DISCORD_WEBHOOK = "https://discord.com/api/webhooks/807394115561979935/-gtiRGG6drFwJ_8e2vGG-SxJPhEb-tQTq_0r0664Q8CdtkLvj6BDxmiyE8Oc92VYuCMH"
+local DISCORD_WEBHOOK = "https://discord.com/api/webhooks/982313609268961320/HkhB-ih8xJLBEsntxBDk6xauyWo7mr8SDmWETK7GsnzEfMolxxQXZYnus5lf82Rcv4NQ"
local DISCORD_NAME = "Elite Gaming RP"
local STEAM_KEY = "0C034D20C57C8D6C5A8EED855916981F"
local DISCORD_IMAGE = "https://i.imgur.com/lF8nHnz.jpg" -- default is FiveM logo
diff --git a/resources/enhancedcamera/config.ini b/resources/enhancedcamera/config.ini
index 1e4caf2d3..8af1fc9ad 100644
--- a/resources/enhancedcamera/config.ini
+++ b/resources/enhancedcamera/config.ini
@@ -1,5 +1,5 @@
#The Control to toggle the Menu (default is 344=F11) list at https://wiki.fivem.net/wiki/Controls
-toggleMenu=344
+toggleMenu=56
#Should chase camera be enabled for users to select? (1 - yes, 0 - no)
chaseCameraEnabled=1
#Should drone camera be enabled for users to select? (1 - yes, 0 - no)
diff --git a/resources/fivepd/config/items.json b/resources/fivepd/config/items.json
index 5a5f3d79c..22cf30df5 100644
--- a/resources/fivepd/config/items.json
+++ b/resources/fivepd/config/items.json
@@ -3674,6 +3674,4 @@
"multiplier": 15,
"itemLocation": 0
},
-
-
]
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/cl_plugins.lua b/resources/lvc/PLUGINS/cl_plugins.lua
new file mode 100644
index 000000000..c212f22d5
--- /dev/null
+++ b/resources/lvc/PLUGINS/cl_plugins.lua
@@ -0,0 +1,92 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_plugins.lua
+PURPOSE: Builds RageUI Plugin Menu based on plugins
+settings. Handles Plugin -> LVC event communication
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+-- RAGE UI
+-- Draws specific button with callback to plugins menu if the plugin is found and enabled. (controlled in plugins settings file)
+CreateThread(function()
+ while true do
+ while plugins_installed and IsMenuOpen() do
+ RageUI.IsVisible(RMenu:Get('lvc', 'plugins'), function()
+ -----------------------------------------------------------------------------------------------------------------
+ if tkd_masterswitch ~= nil then
+ RageUI.Button(Lang:t('plugins.menu_tkd'), Lang:t('plugins.menu_tkd_desc'), {RightLabel = '→→→'}, tkd_masterswitch, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'tkdsettings'))
+ end
+ -----------------------------------------------------------------------------------------------------------------
+ if ei_masterswitch ~= nil then
+ RageUI.Button(Lang:t('plugins.menu_ei'), Lang:t('plugins.menu_ei_desc'), {RightLabel = '→→→'}, ei_masterswitch, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'extrasettings'))
+ end
+ -----------------------------------------------------------------------------------------------------------------
+ if ta_masterswitch ~= nil then
+ RageUI.Button(Lang:t('plugins.menu_ta'), Lang:t('plugins.menu_ta_desc'), {RightLabel = '→→→'}, ta_masterswitch, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'tasettings'))
+ end
+ -----------------------------------------------------------------------------------------------------------------
+ if trailer_masterswitch ~= nil then
+ RageUI.Button(Lang:t('plugins.menu_ts'), Lang:t('plugins.menu_ts_desc'), {RightLabel = '→→→'}, trailer_masterswitch, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'trailersettings'))
+ end
+ -----------------------------------------------------------------------------------------------------------------
+ if ec_masterswitch ~= nil then
+ RageUI.Button(Lang:t('plugins.menu_ec'), Lang:t('plugins.menu_ec_desc'), {RightLabel = '→→→'}, ec_masterswitch, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'extracontrols'))
+ end
+ -----------------------------------------------------------------------------------------------------------------
+ end)
+ Wait(0)
+ end
+ Wait(500)
+ end
+end)
+
+-- FUNCTIONS
+-- IsPluginMenuOpen is called inside IsMenuOpen (LVC/UI/cl_ragemenu.lua) to separate them, this is useful for plugin updates separate of main LVC updates.
+local ec_shortcut_menu_visible = false
+function IsPluginMenuOpen()
+ if ec_masterswitch then
+ ec_shortcut_menu_visible = EC.is_menu_open
+ end
+
+ return RageUI.Visible(RMenu:Get('lvc', 'tkdsettings')) or
+ RageUI.Visible(RMenu:Get('lvc', 'extrasettings')) or
+ RageUI.Visible(RMenu:Get('lvc', 'tasettings')) or
+ RageUI.Visible(RMenu:Get('lvc', 'trailersettings')) or
+ RageUI.Visible(RMenu:Get('lvc', 'trailerextras')) or
+ RageUI.Visible(RMenu:Get('lvc', 'trailerdoors')) or
+ RageUI.Visible(RMenu:Get('lvc', 'extracontrols')) or
+ ec_shortcut_menu_visible
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_controls/SETTINGS.lua b/resources/lvc/PLUGINS/extra_controls/SETTINGS.lua
new file mode 100644
index 000000000..b2989bec5
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_controls/SETTINGS.lua
@@ -0,0 +1,21 @@
+--------------------EXTRA INTEGRATION SETTINGS---------------------
+ec_masterswitch = true
+-- Determines if extra_integration plugin can be activated.
+allow_custom_controls = true
+-- Enabled/Disables menu which allows for custom controls to be set.
+--[[ Documentation / Wiki: https://github.com/TrevorBarns/luxart-vehicle-control/wiki/Extra-Controls ]]
+
+ EXTRA_CONTROLS = {
+ ['DEFAULT'] = {
+ -- { '', Extras = {}, Combo = , Key = , (opt.) Audio = < button soundFX> }
+ },
+ }
+
+CONTROLS = {
+ -- COMBOS = { }, List of Controls: https://docs.fivem.net/docs/game-references/controls/
+ -- KEYS = { }
+ -- ex: COMBOS = { 326, 155, 19, 349 }, --LCTRL, LSHIFT, LALT, TAB
+ -- ex: KEYS = { 187, 188, 189, 190, 20 }, -- ARROW LFT, DWN, UP, RGT, Z
+ COMBOS = { },
+ KEYS = { }
+}
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_controls/UI/cl_ragemenu.lua b/resources/lvc/PLUGINS/extra_controls/UI/cl_ragemenu.lua
new file mode 100644
index 000000000..5dd159b67
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_controls/UI/cl_ragemenu.lua
@@ -0,0 +1,243 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_ragemenu.lua
+PURPOSE: Handle RageUI
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+if ec_masterswitch then
+ RMenu.Add('lvc', 'extracontrols', RageUI.CreateSubMenu(RMenu:Get('lvc', 'plugins'),' ', Lang:t('plugins.menu_ec'), 0, 0, "lvc", "lvc_plugin_logo"))
+ RMenu:Get('lvc', 'extracontrols'):DisplayGlare(false)
+ RMenu:Get('lvc', 'extracontrols'):SetTotalItemsPerPage(13)
+
+ --RageUI Confirm UI elements
+ local confirm_d_msg
+ local confirm_s_msg
+ local confirm_l_msg
+ local confirm_s_desc
+ local confirm_l_desc
+ local profile_s_op = 75
+ local profile_l_op = 75
+
+ -- Handle user input to cancel confirmation message for SAVE/LOAD
+ CreateThread(function()
+ while true do
+ while not RageUI.Settings.Controls.Back.Enabled do
+ for Index = 1, #RageUI.Settings.Controls.Back.Keys do
+ if IsDisabledControlJustPressed(RageUI.Settings.Controls.Back.Keys[Index][1], RageUI.Settings.Controls.Back.Keys[Index][2]) then
+ confirm_s_msg = nil
+ confirm_s_desc = nil
+ profile_s_op = 75
+ confirm_l_msg = nil
+ confirm_l_desc = nil
+ profile_l_op = 75
+ confirm_r_msg = nil
+ confirm_d_msg = nil
+ Wait(10)
+ RageUI.Settings.Controls.Back.Enabled = true
+ break
+ end
+ end
+ Wait(0)
+ end
+ Wait(100)
+ end
+ end)
+
+ local is_loop_on = false
+ --[[Loop to be called when dynamically created menus are opened,
+ loop continues until closed updating the EC.is_menu_open var,
+ which is used in cl_plugins.lua for IsPluginsMenuOpen()]]
+ local function StartIsMenuOpenLoop()
+ if not is_loop_on then
+ is_loop_on = true
+ CreateThread(function()
+ while is_loop_on do
+ EC.is_menu_open = false
+ for i, extra_shortcut in ipairs(EC.table) do
+ if RageUI.Visible(RMenu:Get('lvc', 'extracontrols_'..i)) then
+ EC.is_menu_open = true
+ end
+ end
+ if not EC.is_menu_open then
+ is_loop_on = false
+ end
+ Wait(1)
+ end
+ end)
+ end
+ end
+
+ CreateThread(function()
+ Wait(1000)
+ local choice
+ local shortcut_prefix
+ if allow_custom_controls then
+ shortcut_prefix = Lang:t('plugins.ec_shortcut_prefix_change')
+ else
+ shortcut_prefix = Lang:t('plugins.ec_shortcut_prefix_view')
+ end
+
+ while true do
+ RageUI.IsVisible(RMenu:Get('lvc', 'extracontrols'), function()
+ RageUI.Checkbox(Lang:t('menu.enabled'), Lang:t('plugins.ec_enabled_desc'), EC.controls_enabled, {}, {
+ onChecked = function()
+ EC.controls_enabled = true
+ end,
+ onUnChecked = function()
+ EC.controls_enabled = false
+ end
+ })
+
+ RageUI.Separator(Lang:t('plugins.ec_shortcuts_separator'))
+ -- Buttons for dynamic shortcut menu
+ if #EC.table > 0 then
+ for i, extra_shortcut in ipairs(EC.table) do
+ RageUI.Button(extra_shortcut.Name, Lang:t('plugins.ec_shortcut_desc', { prefix = shortcut_prefix }), {RightLabel = '→→→'}, true, {
+ onSelected = function()
+ StartIsMenuOpenLoop()
+ end,
+ }, RMenu:Get('lvc', 'extracontrols'..'_'..i))
+ end
+ if allow_custom_controls then
+ RageUI.Separator(Lang:t('menu.storage'))
+ RageUI.Button(Lang:t('plugins.ec_save'), confirm_s_desc or Lang:t('plugins.ec_save_desc'), {RightLabel = confirm_s_msg or '('.. EC.profile .. ')', RightLabelOpacity = profile_s_op}, true, {
+ onSelected = function()
+ if confirm_s_msg == Lang:t('menu.save_override') then
+ EC:SaveSettings()
+ HUD:ShowNotification(Lang:t('menu.save_success'), true)
+ confirm_s_msg = nil
+ confirm_s_desc = nil
+ profile_s_op = 75
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ profile_s_op = 255
+ confirm_s_msg = Lang:t('menu.save_override')
+ confirm_s_desc = Lang:t('menu.save_override_desc', { profile = EC.profile })
+ confirm_l_msg = nil
+ profile_l_op = 75
+ confirm_r_msg = nil
+ confirm_d_msg = nil
+ end
+ end,
+ })
+ RageUI.Button(Lang:t('plugins.ec_load'), confirm_l_desc or Lang:t('plugins.ec_load_desc'), {RightLabel = confirm_l_msg or '('.. EC.profile .. ')', RightLabelOpacity = profile_l_op}, true, {
+ onSelected = function()
+ if confirm_l_msg == Lang:t('menu.save_override') then
+ EC:LoadSettings()
+ HUD:ShowNotification(Lang:t('menu.load_success'), true)
+ confirm_l_msg = nil
+ confirm_l_desc = nil
+ profile_l_op = 75
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ profile_l_op = 255
+ confirm_l_msg = Lang:t('menu.save_override')
+ confirm_l_desc = Lang:t('menu.load_override')
+ confirm_s_msg = nil
+ profile_s_op = 75
+ confirm_r_msg = nil
+ confirm_d_msg = nil
+ end
+ end,
+ })
+ RageUI.Button(Lang:t('plugins.ec_reset'), Lang:t('plugins.ec_reset_desc'), {RightLabel = confirm_r_msg}, true, {
+ onSelected = function()
+ if confirm_r_msg == Lang:t('menu.save_override') then
+ EC:LoadBackupTable()
+ HUD:ShowNotification(Lang:t('menu.reset_success'), true)
+ confirm_r_msg = nil
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ confirm_r_msg = Lang:t('menu.save_override')
+ confirm_l_msg = nil
+ profile_l_op = 75
+ confirm_s_msg = nil
+ profile_s_op = 75
+ confirm_d_msg = nil
+ end
+ end,
+ })
+ RageUI.Button(Lang:t('plugins.ec_factory_reset'), Lang:t('plugins.ec_factory_reset_desc'), {RightLabel = confirm_d_msg}, true, {
+ onSelected = function()
+ if confirm_d_msg == Lang:t('menu.save_override') then
+ EC:DeleteProfiles()
+ UTIL:Print(Lang:t('plugins.ec_factory_reset_success_console'), true)
+ HUD:ShowNotification(Lang:t('plugins.ec_factory_reset_success_frontend'), true)
+ confirm_d_msg = nil
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ confirm_d_msg = Lang:t('menu.save_override')
+ confirm_l_msg = nil
+ profile_l_op = 75
+ confirm_s_msg = nil
+ profile_s_op = 75
+ confirm_r_msg = nil
+ end
+ end,
+ })
+ end
+ else
+ RageUI.Button(Lang:t('plugins.ec_no_shortcuts'), Lang:t('plugins.ec_no_shortcuts_desc'), {RightLabel = '→→→'}, false, {
+ onSelected = function()
+ end,
+ })
+ end
+ end)
+ if allow_custom_controls then
+ for i, extra_shortcut in ipairs(EC.table) do
+ RageUI.IsVisible(RMenu:Get('lvc', 'extracontrols_'..i), function()
+ RageUI.List(Lang:t('plugins.ec_combo'), EC.approved_combo_strings, EC.combo_id[i], Lang:t('plugins.ec_combo_desc'), {}, EC.combo_id[i] ~= nil, {
+ onListChange = function(Index, Item)
+ EC.combo_id[i] = Index
+ extra_shortcut.Combo = CONTROLS.COMBOS[Index]
+ end,
+ })
+ RageUI.List(Lang:t('plugins.ec_key'), EC.approved_key_strings, EC.key_id[i], Lang:t('plugins.ec_key_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ EC.key_id[i] = Index
+ extra_shortcut.Key = CONTROLS.KEYS[Index]
+ end,
+ })
+ RageUI.Checkbox(Lang:t('plugins.ec_controller_support'), Lang:t('plugins.ec_controller_support_desc'), extra_shortcut.Controller_Support, { Enabled = EC.combo_id[i] == 1}, {
+ onChecked = function()
+ extra_shortcut.Controller_Support = true
+ end,
+ onUnChecked = function()
+ extra_shortcut.Controller_Support = false
+ end
+ })
+ end)
+ end
+ else
+ for i, extra_shortcut in ipairs(EC.table) do
+ RageUI.IsVisible(RMenu:Get('lvc', 'extracontrols_'..i), function()
+ RageUI.Button(Lang:t('plugins.ec_combo'), Lang:t('plugins.ec_combo_desc'), {RightLabel = EC.approved_combo_strings[EC.combo_id[i]]}, true, {})
+ RageUI.Button(Lang:t('plugins.ec_key'), Lang:t('plugins.ec_key_desc'), {RightLabel = EC.approved_key_strings[EC.key_id[i]]}, true, {})
+ end)
+ end
+ end
+
+ Wait(0)
+ end
+ end)
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_controls/UTIL/cl_extracontrol.lua b/resources/lvc/PLUGINS/extra_controls/UTIL/cl_extracontrol.lua
new file mode 100644
index 000000000..b94e3050c
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_controls/UTIL/cl_extracontrol.lua
@@ -0,0 +1,148 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_extracontrol.lua
+PURPOSE: Contains threads, functions to toggle
+extras based on vehicle states / inputs.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+if ec_masterswitch then
+ -- Read controls from controls.json file
+ local CONTROLS_RAW = LoadResourceFile(GetCurrentResourceName(), 'PLUGINS/extra_controls/controls.json')
+ local CONTROLS_LOOKUP = json.decode(CONTROLS_RAW)
+
+ EC = { }
+ EC.table = { }
+ EC.approved_combo_strings = { }
+ EC.approved_key_strings = { }
+ EC.combo_override = false
+ EC.controls_enabled = true
+ EC.combo_id = {}
+ EC.key_id = {}
+ EC.is_menu_open = false
+
+ -- Set RageUI list index to current selection
+ function EC:RefreshRageIndexs()
+ for i, extra_shortcut in ipairs(EC.table) do
+ EC.combo_id[i] = UTIL:IndexOf(CONTROLS.COMBOS, EC.table[i].Combo)
+ EC.key_id[i] = UTIL:IndexOf(CONTROLS.KEYS, EC.table[i].Key)
+ end
+ end
+
+ -- Generate required tables for control modification.
+ CreateThread(function()
+ table.insert(EC.approved_combo_strings, CONTROLS_LOOKUP[1])
+ for i, control_id in ipairs(CONTROLS.COMBOS) do
+ table.insert(EC.approved_combo_strings, CONTROLS_LOOKUP[control_id+2])
+ end
+
+ table.insert(EC.approved_key_strings, CONTROLS_LOOKUP[1])
+ for i, control_id in ipairs(CONTROLS.KEYS) do
+ table.insert(EC.approved_key_strings, CONTROLS_LOOKUP[control_id+2])
+ end
+ table.insert(CONTROLS.COMBOS, 1, 0)
+ table.insert(CONTROLS.KEYS, 1, 0)
+ end)
+
+ -- Control Handling
+ CreateThread(function()
+ while true do
+ if EC.controls_enabled and not IsMenuOpen() and not key_lock then
+ if player_is_emerg_driver and #EC.table > 0 then
+ for _, tog_table in ipairs(EC.table) do
+ if ( tog_table.Combo == 0 or IsControlPressed(0, tog_table.Combo) ) and ( IsUsingKeyboard(0) or tog_table.Controller_Support ) then
+ if IsControlJustPressed(0, tog_table.Key) and ( IsUsingKeyboard(0) or tog_table.Controller_Support ) then
+ if tog_table.State == nil then
+ tog_table.State = true
+ else
+ tog_table.State = not tog_table.State
+ if tog_table.Audio then
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ end
+ end
+ UTIL:TogVehicleExtras(veh, tog_table.Extras, tog_table.State)
+ end
+ end
+ end
+ else
+ Wait(500)
+ end
+ else
+ Wait(500)
+ end
+ Wait(0)
+ end
+ end)
+
+ ---------------------------------------------------------------------
+ --Triggered when vehicle changes (cl_lvc.lua)
+ RegisterNetEvent('lvc:onVehicleChange')
+ AddEventHandler('lvc:onVehicleChange', function()
+ if player_is_emerg_driver and veh ~= nil then
+ EC.table, EC.profile = UTIL:GetProfileFromTable('EXTRA CONTROLS', EXTRA_CONTROLS, veh, true)
+ if EC.profile ~= false then
+ Wait(500)
+ EC:SetBackupTable()
+ EC:LoadSettings()
+
+ -- Dynamically create shortcut menus
+ if #EC.table > 0 then
+ for i, extra_shortcut in ipairs(EC.table) do
+ RMenu.Add('lvc', 'extracontrols_'..i, RageUI.CreateSubMenu(RMenu:Get('lvc', 'extracontrols'),' ', extra_shortcut.Name), 0, 0, "lvc", "lvc_plugin_logo")
+ RMenu:Get('lvc', 'extracontrols_'..i):DisplayGlare(false)
+ end
+ end
+
+ --[[Verify all controls are approved, if not reset to none and notify and set default parameters]]
+ for i, tog_table in pairs(EC.table) do
+ local found_combo = false
+ local found_key = false
+
+ for i, control in pairs(CONTROLS.COMBOS) do
+ if tog_table.Combo == control then
+ found_combo = true
+ end
+ end
+
+ if not found_combo then
+ HUD:ShowNotification(Lang:t('plugins.ec_not_approved_frontend'), true)
+ UTIL:Print(Lang:t('plugins.ec_not_approved_console', { control = tog_table.Combo, type = 'COMBO' }), true)
+ tog_table.Combo = 0
+ end
+
+ for i, control in pairs(CONTROLS.KEYS) do
+ if tog_table.Key == control then
+ found_key = true
+ end
+ end
+
+ if not found_key then
+ HUD:ShowNotification(Lang:t('plugins.ec_not_approved_frontend'), true)
+ UTIL:Print(Lang:t('plugins.ec_not_approved_console', { control = tog_table.Key, type = 'KEY' }), true)
+ tog_table.Key = 0
+ end
+ end
+
+ EC:RefreshRageIndexs()
+ end
+ end
+ end)
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_controls/UTIL/cl_storage.lua b/resources/lvc/PLUGINS/extra_controls/UTIL/cl_storage.lua
new file mode 100644
index 000000000..f3dbfdd26
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_controls/UTIL/cl_storage.lua
@@ -0,0 +1,102 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_storage.lua
+PURPOSE: Handle plugin storage.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+if ec_masterswitch then
+ local save_prefix = 'lvc_'..community_id..'_EC_'
+ local backup_table
+
+ function EC:SaveSettings()
+ local save_paragrams = { }
+ for i, shortcut in pairs(EC.table) do
+ local save_paragram = { }
+ save_paragram.Name = shortcut.Name
+ save_paragram.Combo = shortcut.Combo
+ save_paragram.Key = shortcut.Key
+ save_paragram.Controller_Support = shortcut.Controller_Support
+ table.insert(save_paragrams, save_paragram)
+ end
+ SetResourceKvp(save_prefix..EC.profile, json.encode(save_paragrams))
+ end
+
+ function EC:LoadSettings()
+ local save_paragrams = GetResourceKvpString(save_prefix..EC.profile)
+ if save_paragrams ~= nil then
+ save_paragrams = json.decode(save_paragrams)
+ --Iterate through all EC tables in save_paragrams (KVP table)
+ for i, save_data in pairs(save_paragrams) do
+ save_data.used = false
+ --Iterate through current EC table (does the extra specific shortcut still exist)
+ for j, shortcut in pairs(EC.table) do
+ if save_data.Name == shortcut.Name then
+ if UTIL:IndexOf(CONTROLS.COMBOS, shortcut.Combo) ~= nil then
+ shortcut.Combo = save_data.Combo
+ else
+ UTIL:Print(Lang:t('plugins.ec_fail_load_console', { name = shortcut.Name, control = shortcut.Combo }), true)
+ HUD:ShowNotification(Lang:t('plugins.ec_fail_load_frontend', { name = shortcut.Name }), true)
+ end
+ if UTIL:IndexOf(CONTROLS.KEYS, shortcut.Key) then
+ shortcut.Key = save_data.Key
+ else
+ UTIL:Print(Lang:t('plugins.ec_fail_load_console', { name = shortcut.Name, control = shortcut.Key }), true)
+ HUD:ShowNotification(Lang:t('plugins.ec_fail_load_frontend', { name = shortcut.Name }), true)
+ end
+ if shortcut.Controller_Support ~= nil then
+ shortcut.Controller_Support = save_data.Controller_Support
+ end
+ save_data.used = true
+ end
+ end
+ end
+
+ for i, save_data in pairs(save_paragrams) do
+ if not save_data.used then
+ UTIL:Print(Lang:t('plugins.ec_save_not_used'), true)
+ end
+ end
+ EC:RefreshRageIndexs()
+ end
+ end
+
+ function EC:DeleteProfiles()
+ STORAGE:DeleteKVPs(save_prefix)
+ end
+
+ function EC:SetBackupTable()
+ --[[set default parameters if missing from backup]]
+ for i, tog_table in pairs(EC.table) do
+ if tog_table.Audio == nil then
+ tog_table.Audio = false
+ end
+ if tog_table.Controller_Support == nil then
+ tog_table.Controller_Support = true
+ end
+ end
+ end
+
+ function EC:LoadBackupTable()
+ EC.table = json.decode(backup_table)
+ EC:RefreshRageIndexs()
+ end
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_controls/UTIL/sv_version.lua b/resources/lvc/PLUGINS/extra_controls/UTIL/sv_version.lua
new file mode 100644
index 000000000..447c17d64
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_controls/UTIL/sv_version.lua
@@ -0,0 +1,33 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: sv_version.lua
+PURPOSE: Handle plugin version checking.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+local plugin_name = 'extra_controls'
+local plugin_version = '1.1.1'
+
+RegisterServerEvent('lvc:plugins_getVersions')
+AddEventHandler('lvc:plugins_getVersions', function()
+ TriggerEvent('lvc:plugins_storePluginVersion', plugin_name, plugin_version)
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_controls/controls.json b/resources/lvc/PLUGINS/extra_controls/controls.json
new file mode 100644
index 000000000..cd2ae9e99
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_controls/controls.json
@@ -0,0 +1 @@
+["NONE / DISABLED", "V | BACK","MOUSE RIGHT | RIGHT STICK","MOUSE DOWN | RIGHT STICK","(NONE) | RIGHT STICK","MOUSE DOWN | RIGHT STICK","(NONE) | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","(NONE) | R3","S | LEFT STICK","D | LEFT STICK","PAGEUP | LT","PAGEDOWN | RT","MOUSE DOWN | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","SCROLLWHEEL DOWN | DPAD RIGHT","SCROLLWHEEL UP | DPAD LEFT","SCROLLWHEEL DOWN | (NONE)","SCROLLWHEEL UP | (NONE)","ENTER / LEFT MOUSE BUTTON / SPACEBAR | A","LEFT ALT | DPAD DOWN","Z | DPAD DOWN","LEFT SHIFT | A","SPACEBAR | X","F | Y","LEFT MOUSE BUTTON | RT","RIGHT MOUSE BUTTON | LT","C | R3","ARROW UP / SCROLLWHEEL BUTTON (PRESS) | DPAD UP","(NONE) | L3","B | R3","D | LEFT STICK","S | LEFT STICK","W | LEFT STICK","S | LEFT STICK","A | LEFT STICK","D | LEFT STICK","LEFT CTRL | L3","TAB | LB","E | LB","[ | LEFT STICK","] | LEFT STICK","[ | LEFT STICK","] | DPAD UP","[ | DPAD DOWN","Q | RB","R | B","E | DPAD RIGHT","G | DPAD LEFT","Z | DPAD DOWN","F | Y","SCROLLWHEEL DOWN | R3","E | DPAD RIGHT","Q | DPAD LEFT","(NONE) | Y","E | DPAD RIGHT","SPACEBAR | RB","F9 | Y","F10 | B","G | DPAD LEFT","D | LEFT STICK","LEFT CTRL | LEFT STICK","LEFT SHIFT | LEFT STICK","LEFT CTRL | LEFT STICK","A | LEFT STICK","D | LEFT STICK","(NONE) | (NONE)","MOUSE RIGHT | RIGHT STICK","MOUSE DOWN | RIGHT STICK","RIGHT MOUSE BUTTON | LB","LEFT MOUSE BUTTON | RB","RIGHT MOUSE BUTTON | A","W | RT","S | LT","X | A","H | DPAD RIGHT","F | Y","SPACEBAR | RB","W | LT","S | RT","C | R3","R | B",". | (NONE)",", | (NONE)","= | (NONE)","- | (NONE)","Q | DPAD LEFT","E | L3","W | RT","S | LT","A | LB","D | RB","RIGHT MOUSE BUTTON | LT","LEFT MOUSE BUTTON | RT","(NONE) | R3","(NONE) | (NONE)","MOUSE DOWN | RIGHT STICK","NUMPAD- / SCROLLWHEEL UP | (NONE)","NUMPAD+ / SCROLLWHEEL DOWN | (NONE)","MOUSE RIGHT | RIGHT STICK","SCROLLWHEEL UP | X","[ | (NONE)","H | DPAD RIGHT","SPACEBAR | RB","E | DPAD RIGHT","H | DPAD RIGHT","X | A","LEFT MOUSE BUTTON | (NONE)","NUMPAD 6 | LEFT STICK","NUMPAD 4 | LEFT STICK","NUMPAD 6 | LEFT STICK","NUMPAD 5 | LEFT STICK","NUMPAD 8 | LEFT STICK","NUMPAD 5 | LEFT STICK","G | L3","RIGHT MOUSE BUTTON | A","SCROLLWHEEL UP | DPAD LEFT","[ | (NONE)","NUMPAD 7 | LB","NUMPAD 9 | RB","E | DPAD RIGHT","X | A","INSERT | R3","LEFT MOUSE BUTTON | (NONE)","NUMPAD 6 | LEFT STICK","NUMPAD 4 | LEFT STICK","NUMPAD 6 | LEFT STICK","NUMPAD 5 | LEFT STICK","NUMPAD 8 | LEFT STICK","NUMPAD 5 | LEFT STICK","W | RT","S | LT","LEFT SHIFT | X","LEFT CTRL | A","A | LB","D | RB","LEFT MOUSE BUTTON | (NONE)","W | A","CAPSLOCK | A","Q | LT","S | RT","R | B","Q | A","LEFT MOUSE BUTTON | RT","SPACEBAR | X","F / LEFT MOUSE BUTTON | Y","F | Y","D | LEFT STICK","A | LEFT STICK","D | LEFT STICK","S | LEFT STICK","W | LEFT STICK","S | LEFT STICK","Q | LB","E | RB","X | A","LEFT SHIFT | (NONE)","(NONE) | (NONE)","1 | (NONE)","2 | (NONE)","6 | (NONE)","3 | (NONE)","7 | (NONE)","8 | (NONE)","9 | (NONE)","4 | (NONE)","5 | (NONE)","F5 | (NONE)","F6 | (NONE)","F7 | (NONE)","F8 (CONSOLE) | (NONE)","F3 | B","CAPSLOCK | (NONE)","ARROW UP | DPAD UP","ARROW DOWN | DPAD DOWN","ARROW LEFT | DPAD LEFT","ARROW RIGHT | DPAD RIGHT","ENTER / LEFT MOUSE BUTTON | A","BACKSPACE / ESC / RIGHT MOUSE BUTTON | B","DELETE | Y","SPACEBAR | X","SCROLLWHEEL DOWN | (NONE)","SCROLLWHEEL UP | (NONE)","L | RT","G | RB","E | R3","F | LB","X | L3","ARROW DOWN | DPAD DOWN","ARROW UP | DPAD UP","ARROW LEFT | DPAD LEFT","ARROW RIGHT | DPAD RIGHT","ENTER | A","TAB | Y","(NONE) | X","BACKSPACE | B","D | LEFT STICK","S | LEFT STICK","] | RIGHT STICK","SCROLLWHEEL DOWN | RIGHT STICK","P | START","ESC | (NONE)","ENTER / NUMPAD ENTER | A","BACKSPACE / ESC | B","SPACEBAR | X","TAB | Y","Q | LB","E | RB","PAGE DOWN | LT","PAGE UP | RT","LEFT SHIFT | L3","LEFT CTRL | R3","TAB | RB","HOME | BACK","HOME | RB","DELETE | X","ENTER | A","SPACEBAR | X","CAPSLOCK | BACK","D | LEFT STICK","S | LEFT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE DOWN | RIGHT STICK","RIGHT MOUSE BUTTON | Y","LEFT MOUSE BUTTON | A","LEFT CTRL | X","RIGHT MOUSE BUTTON | B","(NONE) | LB","(NONE) | RB","(NONE) | LT","LEFT MOUSE BUTTON | RT","(NONE) | L3","(NONE) | R3","W | DPAD UP","S | DPAD DOWN","A | DPAD LEFT","D | DPAD RIGHT","V | BACK","LEFT MOUSE BUTTON | (NONE)","RIGHT MOUSE BUTTON | (NONE)","(NONE) | (NONE)","(NONE) | (NONE)","SCROLLWHEEL UP | (NONE)","SCROLLWHEEL DOWN | (NONE)","~ / ` | (NONE)","M | BACK","T | (NONE)","Y | (NONE)","(NONE) | (NONE)","(NONE) | (NONE)","N | (NONE)","R | L3","F | R3","X | LT","C | RT","LEFT SHIFT | (NONE)","SPACEBAR | A","DELETE | X","LEFT MOUSE BUTTON | RT","(NONE) | A","(NONE) | X","(NONE) | RT","SCROLLWHEEL UP | DPAD LEFT","SCROLLWHEEL DOWN | DPAD RIGHT","R | B","Q | A","(NONE) | (NONE)","D | LEFT STICK","D | LEFT STICK","S | LEFT STICK","S | LEFT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE DOWN | RIGHT STICK","MOUSE DOWN | RIGHT STICK","[ | RIGHT STICK","[ | RIGHT STICK","[ | LEFT STICK","[ | LEFT STICK","D | LEFT STICK","D | LEFT STICK","LEFT CTRL | LEFT STICK","LEFT CTRL | LEFT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","F1 | A","F2 | X","MOUSE RIGHT | RIGHT STICK","MOUSE DOWN | RIGHT STICK","(NONE) | RIGHT STICK","(NONE) | RIGHT STICK","(NONE) | RIGHT STICK","(NONE) | RIGHT STICK","DELETE | X","DELETE | Y","SPACEBAR | A","ARROW DOWN | LB","ARROW UP | RB","M | A","S | (NONE)","U | DPAD UP","H | R3","B | (NONE)","N | (NONE)","ARROW RIGHT | DPAD RIGHT","ARROW LEFT | DPAD LEFT","T | DPAD DOWN","R | BACK","K | DPAD DOWN","[ | DPAD LEFT","] | DPAD RIGHT","NUMPAD + | RB","NUMPAD - | LB","PAGE UP | (NONE)","PAGE DOWN | (NONE)","F5 | START","C | (NONE)","V | (NONE)","SPACEBAR | (NONE)","ESC | (NONE)","X | (NONE)","C | (NONE)","V | (NONE)","LEFT CTRL | (NONE)","F5 | (NONE)","SPACEBAR | RT","LEFT MOUSE BUTTON | (NONE)","RIGHT MOUSE BUTTON | (NONE)","RIGHT MOUSE BUTTON | (NONE)","MOUSE DOWN | RIGHT STICK","MOUSE RIGHT | RIGHT STICK","SCROLLWHEEL DOWN | LEFT STICK","SCROLLWHEEL UP | LEFT STICK","SCROLLWHEEL DOWN | LEFT STICK","X | A","A | LEFT STICK","D | LEFT STICK","LEFT SHIFT | LEFT STICK","LEFT CTRL | LEFT STICK","D | LEFT STICK","LEFT CTRL | LEFT STICK","F11 | DPAD RIGHT","X | A","LEFT MOUSE BUTTON | LB","RIGHT MOUSE BUTTON | RB","SCROLLWHEEL BUTTON (PRESS) | Y","TAB | X","E | L3","E | L3","LEFT SHIFT | L3","SPACEBAR | A","X | A","E | DPAD RIGHT","E | DPAD RIGHT","X | A","(NONE) | RB","(NONE) | (NONE)"]
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_integration/SETTINGS.lua b/resources/lvc/PLUGINS/extra_integration/SETTINGS.lua
new file mode 100644
index 000000000..64b7bec77
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_integration/SETTINGS.lua
@@ -0,0 +1,34 @@
+--------------------EXTRA INTEGRATION SETTINGS---------------------
+ei_masterswitch = true
+-- Determines if extra_integration plugin can be activated.
+ei_run_out_of_vehicle = false
+-- Continue running state checks when player is out of vehicle, only after EI vehicle was last driven. (this is necessary for proper seat / door check)
+-- Disable this to improve runtime efficiency. (Default: Disabled / False)
+brakes_ei_enabled = true
+-- Enables brake pressure integration.
+reverse_ei_enabled = true
+-- Enables reverse gear integration.
+indicators_ei_enabled = true
+-- Enables indicator integration. Requires extras to be mapped to indicator light_id. Flashes are not synced without this.
+takedown_ei_enabled = true
+-- Enables takedown integration.
+seat_ei_enabled = true
+-- Enabled driver seat detection.
+door_ei_enabled = true
+-- Enables driver & passenger door triggers.
+siren_controller_ei_enabled = true
+-- Enables air horn, siren, aux siren, and manual tone state triggers.
+auto_brake_lights = true
+-- Enables auto-set client side brake lights on stop (speed = 0) for both emergency and non-emergency vehicles.
+auto_park = true
+-- Turns off brake lights after being stopped for auto_park_time.
+default_blackout_control = ''
+-- Toggles vehicles headlights, brakelights controls: https://pastebin.com/u9ewvWWZ (Default: None/Disabled)
+
+--[[ Documentation / Wiki: https://github.com/TrevorBarns/luxart-vehicle-control/wiki/Extra-Integrations ]]
+EXTRA_ASSIGNMENTS = {
+ ['DEFAULT'] = {} -- autopark functionality requires default table
+ --[] = {
+ --[] = { repair = , toggle = {}, reverse = }
+ -- },
+}
diff --git a/resources/lvc/PLUGINS/extra_integration/UI/cl_ragemenu.lua b/resources/lvc/PLUGINS/extra_integration/UI/cl_ragemenu.lua
new file mode 100644
index 000000000..dc6b6779c
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_integration/UI/cl_ragemenu.lua
@@ -0,0 +1,58 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_ragemenu.lua
+PURPOSE: Handle RageUI
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+if ei_masterswitch then
+ RMenu.Add('lvc', 'extrasettings', RageUI.CreateSubMenu(RMenu:Get('lvc', 'plugins'),' ', Lang:t('plugins.menu_ei'), 0, 0, "lvc", "lvc_plugin_logo"))
+ RMenu:Get('lvc', 'extrasettings'):DisplayGlare(false)
+
+
+ CreateThread(function()
+ while true do
+ RageUI.IsVisible(RMenu:Get('lvc', 'extrasettings'), function()
+ RageUI.Checkbox(Lang:t('plugins.ei_blackout'), Lang:t('plugins.ei_blackout_desc'), EI:GetBlackOutState(), {Enabled = auto_brake_lights}, {
+ onChecked = function()
+ EI:SetBlackoutState(true)
+ end,
+ onUnChecked = function()
+ EI:SetBlackoutState(false)
+ end
+ })
+ RageUI.List(Lang:t('plugins.ei_auto_park'), {'Off', '1/4', '1/2', '1', '5'}, EI:GetParkTimeIndex(), Lang:t('plugins.ei_auto_park_desc', {timer = ("%1.0f"):format((EI:GetStoppedTimer() / 1000) or 0)}), {}, auto_brake_lights and not EI:GetBlackOutState(), {
+ onListChange = function(Index, Item)
+ if Index > 1 then
+ EI:SetParkTimeIndex(Index)
+ EI:SetAutoPark(true)
+ else
+ EI:SetParkTimeIndex(Index)
+ EI:SetAutoPark(false)
+ end
+ end,
+ })
+ end)
+
+ Wait(0)
+ end
+ end)
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_integration/UTIL/cl_brakelights.lua b/resources/lvc/PLUGINS/extra_integration/UTIL/cl_brakelights.lua
new file mode 100644
index 000000000..2b4d33456
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_integration/UTIL/cl_brakelights.lua
@@ -0,0 +1,38 @@
+--[[
+ PORTIONS OF THE CODE BELOW WAS CREATED BY WolfKnight IN THE WIDELY POPULAR BRRAKELIGHTS RESOURCE.
+ THANKS FOR HIS PERMISSION TO ADAPT FOR USE AND DISTRIBUTION WITH LVC FOR EXTRA INTEGRATION SUPPORT.
+
+ https://forum.cfx.re/t/release-vehicle-brake-lights-1-0-2-client-sync-updated-2019/15322
+
+ Copyright (c) 2022 TrevorBarns
+ Copyright (c) 2017-2022 WolfKnight
+]]
+if auto_brake_lights then
+ local ped = nil
+ local vehicle = nil
+
+ CreateThread( function()
+ while true do
+ ped = GetPlayerPed( -1 )
+ if DoesEntityExist( ped ) and not IsEntityDead( ped ) and IsPedSittingInAnyVehicle( ped ) then
+ vehicle = GetVehiclePedIsIn( ped, false )
+ if GetPedInVehicleSeat( vehicle, -1 ) == ped then
+ if not EI.blackout and not EI.auto_park_state then
+ if GetVehicleClass( veh ) ~= 14 and GetVehicleClass( veh ) ~= 15 and GetVehicleClass( veh ) ~= 16 and GetVehicleClass( veh ) ~= 21 then
+ if ( GetEntitySpeed( vehicle ) < 0.1 and GetIsVehicleEngineRunning( vehicle ) ) then
+ SetVehicleBrakeLights( vehicle, true )
+ end
+ end
+ else
+ SetVehicleBrakeLights(vehicle, false)
+ end
+ else
+ Wait(500)
+ end
+ else
+ Wait(500)
+ end
+ Wait(1)
+ end
+ end )
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_integration/UTIL/cl_extras.lua b/resources/lvc/PLUGINS/extra_integration/UTIL/cl_extras.lua
new file mode 100644
index 000000000..2d7bf95bf
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_integration/UTIL/cl_extras.lua
@@ -0,0 +1,413 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_extras.lua
+PURPOSE: Contains threads, functions to toggle
+extras based on vehicle states / inputs.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+EI = { }
+EI.blackout = false
+EI.auto_park_state = false
+
+if ei_masterswitch then
+ -- Local variable to be set, do not edit.
+ local enabled_triggers = {
+ ['Brake'] = false,
+ ['Reverse'] = false,
+ ['RIndicator'] = false,
+ ['LIndicator'] = false,
+ ['Takedowns'] = false,
+ ['DSeat'] = false,
+ ['DDoor'] = false,
+ ['PDoor'] = false,
+ ['Trunk'] = false,
+ ['MainSiren'] = false,
+ ['AuxSiren'] = false,
+ ['AirHorn'] = false,
+ ['Manu'] = false
+ }
+
+ local auto_park_time_lookup = { [1] = 0, [2] = 15000, [3] = 30000, [4] = 60000, [5] = 300000 }
+ local auto_park_time_index = 2
+
+ local accel_pedal = 0
+ local stopped_timer = 0
+ local extras = { }
+ local profile = false
+ local previous_brake_ei_enabled = false
+
+ ----------------REGISTERED COMMANDS---------------
+ --Toggles blackout mode
+ RegisterCommand('lvcblackout', function(source, args, rawCommand)
+ if player_is_emerg_driver then
+ EI:SetBlackoutState(not EI.blackout)
+ end
+ end)
+
+ RegisterKeyMapping('lvcblackout', Lang:t('plugins.ei_control_desc'), 'keyboard', default_blackout_control)
+ TriggerEvent('chat:addSuggestion', '/lvcblackout', Lang:t('plugins.ei_command_desc'))
+
+ ----------------THREADED FUNCTIONS----------------
+ --[[Startup Initialization]]
+ CreateThread(function()
+ Wait(500)
+ UTIL:FixOversizeKeys(EXTRA_ASSIGNMENTS)
+ end)
+
+ --[[Function caller for extra state checking.]]
+ -- If driver then call RefreshExtras ever 50ms to toggle states.
+ CreateThread( function()
+ while true do
+ if veh ~= nil and profile ~= false then
+ EI:RefreshExtras()
+ else
+ Wait(500)
+ end
+ Wait(50)
+ end
+ end)
+
+ --[[Extra State Trigger Control]]
+ -- Determines vehicles state and sets Triggers
+ CreateThread( function()
+ while true do
+ if veh ~= nil and profile ~= false then
+ if player_is_emerg_driver or run_when_out_of_vehicle then
+ for _, trigger_table in pairs(extras) do
+ if trigger_table.toggle ~= nil then
+ ------------------------------------------------------------
+ --BRAKE LIGHTS
+ if brakes_ei_enabled and enabled_triggers['Brake'] then
+ accel_pedal = GetVehicleThrottleOffset(veh)
+ if ( not auto_park or stopped_timer < auto_park_time_lookup[auto_park_time_index] ) and -- Auto Park Check
+ ( GetControlNormal(1, 72) > 0.1 or -- Brake (LTrigger) 0.0-1.0
+ ( GetControlNormal(1, 72) > 0.0 and GetControlNormal(1, 71) > 0.0 ) or -- Brake & Gas at same time
+ ( GetEntitySpeed(veh) < 0.2 and GetIsVehicleEngineRunning(veh) )) and -- Vehicle is stopped
+ ( not ( accel_pedal < 0.0 or tostring(accel_pedal) == '-0.0')) then -- Is vehicle not reversing or at max reverse speed
+ EI:SetState('Brake', true)
+ elseif trigger_table.active['Brake'] == true then
+ EI:SetState('Brake', false)
+ end
+ end
+ ------------------------------------------------------------
+ --REVERSE LIGHTS
+ if reverse_ei_enabled and enabled_triggers['Reverse'] then
+ accel_pedal = GetVehicleThrottleOffset(veh)
+ if accel_pedal < 0 or tostring(accel_pedal) == '-0.0' then
+ EI:SetState('Reverse', true)
+ elseif trigger_table.active['Reverse'] == true then
+ EI:SetState('Reverse', false)
+ end
+ end
+ ------------------------------------------------------------
+ --INDICATORS
+ if indicators_ei_enabled and enabled_triggers['LIndicator'] or enabled_triggers['RIndicator'] then
+ if state_indic[veh] == 1 then
+ EI:SetState('LIndicator', true)
+ elseif state_indic[veh] == 2 then
+ EI:SetState('RIndicator', true)
+ elseif state_indic[veh] == 3 then
+ EI:SetState('LIndicator', true)
+ EI:SetState('RIndicator', true)
+ elseif trigger_table.active['LIndicator'] or trigger_table.active['RIndicator'] then
+ EI:SetState('LIndicator', false)
+ EI:SetState('RIndicator', false)
+ end
+ end
+ ------------------------------------------------------------
+ --TAKEDOWNS--
+ if takedown_ei_enabled and tkd_masterswitch and enabled_triggers['Takedowns'] then
+ if state_tkd[veh] ~= nil and state_tkd[veh] then
+ EI:SetState('Takedowns', true)
+ elseif trigger_table.active['Takedowns'] == true then
+ EI:SetState('Takedowns', false)
+ end
+ end
+ ------------------------------------------------------------
+ --DOORS--
+ if door_ei_enabled then
+ if enabled_triggers['DDoor'] then
+ if GetVehicleDoorAngleRatio(veh, 0) > 0.0 then
+ EI:SetState('DDoor', true)
+ Wait(100)
+ elseif trigger_table.active['DDoor'] == true then
+ EI:SetState('DDoor', false)
+ end
+ end
+ if enabled_triggers['PDoor'] then
+ if GetVehicleDoorAngleRatio(veh, 1) > 0.0 then
+ EI:SetState('PDoor', true)
+ Wait(100)
+ elseif trigger_table.active['PDoor'] == true then
+ EI:SetState('PDoor', false)
+ end
+ end
+ if enabled_triggers['Trunk'] then
+ if GetVehicleDoorAngleRatio(veh, 5) > 0.0 then
+ EI:SetState('Trunk', true)
+ Wait(100)
+ elseif trigger_table.active['Trunk'] == true then
+ EI:SetState('Trunk', false)
+ end
+ end
+ end
+ ------------------------------------------------------------
+ -- SEAT DETECTION (deactivate)
+ if seat_ei_enabled then
+ if enabled_triggers['DSeat'] then
+ if trigger_table.active['DSeat'] == true then
+ Wait(1000)
+ EI:SetState('DSeat', false)
+ end
+ end
+ end
+ ------------------------------------------------------------
+ --MAIN SIREN
+ if siren_controller_ei_enabled then
+ if enabled_triggers['MainSiren'] then
+ if state_lxsiren[veh] ~= nil and state_lxsiren[veh] > 0 then
+ EI:SetState('MainSiren', true)
+ elseif trigger_table.active['MainSiren'] == true then
+ EI:SetState('MainSiren', false)
+ end
+ end
+ --AUXILARY SIREN
+ if enabled_triggers['AuxSiren'] then
+ if state_pwrcall[veh] ~= nil and state_pwrcall[veh] > 0 then
+ EI:SetState('AuxSiren', true)
+ elseif trigger_table.active['AuxSiren'] == true then
+ EI:SetState('AuxSiren', false)
+ end
+ end
+ --AIRHORN
+ if enabled_triggers['AirHorn'] then
+ if actv_horn ~= nil and actv_horn and not actv_manu then
+ EI:SetState('AirHorn', true)
+ elseif trigger_table.active['AirHorn'] == true then
+ EI:SetState('AirHorn', false)
+ end
+ end
+ --MANUAL TONE
+ if enabled_triggers['Manu'] then
+ if actv_manu ~= nil and actv_manu then
+ EI:SetState('Manu', true)
+ elseif trigger_table.active['Manu'] == true then
+ EI:SetState('Manu', false)
+ end
+ end
+ end
+ ------------------------------------------------------------
+ end
+ end
+ Wait(50)
+ else
+ Wait(50)
+ end
+ else
+ Wait(500)
+ end
+ end
+ end)
+
+ --[[Auto Park Control]]
+ -- Turns off brakelights after being stopped for extended period of time, only for LVC menu enabled vehicles (emergency class)
+ CreateThread(function()
+ while true do
+ if veh ~= nil and profile ~= false then
+ if auto_brake_lights and auto_park and not EI.blackout then
+ if player_is_emerg_driver then
+ while GetEntitySpeed(veh) < 0.1 and GetIsVehicleEngineRunning(veh) and auto_park and not EI.blackout do
+ if stopped_timer < auto_park_time_lookup[auto_park_time_index] then
+ Wait(1000)
+ stopped_timer = stopped_timer + 1000
+ end
+ Wait(0)
+ end
+ stopped_timer = 0
+ EI.auto_park_state = false
+ else
+ Wait(500)
+ end
+ else
+ Wait(500)
+ end
+ else
+ Wait(500)
+ end
+ Wait(0)
+ end
+ end)
+
+ CreateThread(function()
+ while true do
+ if stopped_timer > 0 and not EI.auto_park_state then
+ if stopped_timer >= auto_park_time_lookup[auto_park_time_index] then
+ EI.auto_park_state = true
+ end
+ else
+ Wait(1000)
+ end
+ Wait(0)
+ end
+ end)
+
+
+ ---------------------FUNCTIONS--------------------
+ --[Toggles blackout mode]
+ -- Disabled vehicles headlights,
+ function EI:SetBlackoutState(state)
+ EI.blackout = state
+ stopped_timer = 0
+ if EI.blackout then
+ SetVehicleLights(veh, 1)
+ previous_brake_ei_enabled = brakes_ei_enabled
+ brakes_ei_enabled = false
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ else
+ SetVehicleLights(veh, 0)
+ brakes_ei_enabled = previous_brake_ei_enabled
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ end
+ end
+
+ --[[Set state table]]
+ function EI:SetState(trigger_to_set, state)
+ for extra_id, trigger_table in pairs(extras) do
+ if trigger_table.toggle ~= nil then
+ for i, trigger in ipairs(trigger_table.toggle) do
+ if trigger == trigger_to_set then
+ if state then
+ trigger_table.active[trigger_to_set] = true
+ else
+ trigger_table.active[trigger_to_set] = nil
+ end
+ end
+ end
+ end
+ end
+ end
+
+ --[[Set extras state based on state table]]
+ function EI:RefreshExtras()
+ for extra_id, trigger_table in pairs(extras) do
+ local count = 0
+ for i,v in pairs(trigger_table.active) do
+ count = count + 1
+ end
+
+ local active = true
+ if trigger_table.reverse then
+ active = false
+ end
+
+ if count > 0 then
+ UTIL:TogVehicleExtras(veh, extra_id, active, trigger_table.repair or false)
+ else
+ UTIL:TogVehicleExtras(veh, extra_id, not active, trigger_table.repair or false)
+ end
+ end
+ end
+
+ --[[RageUI Menu Getter/Setters]]
+ function EI:GetStoppedTimer()
+ return stopped_timer
+ end
+
+ function EI:GetParkTimeIndex()
+ return auto_park_time_index
+ end
+
+ function EI:SetParkTimeIndex(index)
+ if index ~= nil and auto_park_time_lookup[index] ~= nil then
+ auto_park_time_index = index
+ end
+ end
+
+ function EI:SetAutoPark(state)
+ auto_park = state
+ end
+
+ function EI:GetBlackOutState()
+ return EI.blackout
+ end
+
+ ---------------------------------------------------------------------
+ --Clear brakelights and handle exit vehicle state
+ RegisterNetEvent('lvc:onVehicleExit')
+ AddEventHandler('lvc:onVehicleExit', function()
+ if veh ~= nil and profile ~= false then
+ -- SEAT DETECTION (activation)
+ if seat_ei_enabled then
+ if enabled_triggers['DSeat'] then
+ if not IsVehicleSeatFree(veh, -1) then
+ EI:SetState('DSeat', true)
+ end
+ end
+ end
+ end
+ end)
+
+ --Triggered when vehicle changes (cl_lvc.lua)
+ RegisterNetEvent('lvc:onVehicleChange')
+ AddEventHandler('lvc:onVehicleChange', function()
+ --disable (reset) all triggers to off before setting assigned triggers
+ for trigger, state in ipairs(enabled_triggers) do
+ state = false
+ end
+
+ extras, profile = UTIL:GetProfileFromTable('EXTRA INTEGRATIONS', EXTRA_ASSIGNMENTS, veh, true)
+ if profile then
+ for extra_id, trigger_table in pairs(extras) do
+ --Initialize active tables
+ trigger_table.active = { }
+
+ --Alert if extra table found that does not align with vehicle configuration.
+ if not DoesExtraExist(veh, extra_id) then
+ UTIL:Print(Lang:t('plugins.ei_invalid_exta', { extra = extra_id, profile = profile}), true)
+ end
+
+ --Enable triggers for extras that exist
+ if trigger_table.toggle ~= nil then
+ for _, trigger in pairs(trigger_table.toggle) do
+ if enabled_triggers[trigger] == false then
+ enabled_triggers[trigger] = true
+ end
+ end
+ end
+ if trigger_table.add ~= nil then
+ for _, trigger in pairs(trigger_table.add) do
+ if enabled_triggers[trigger] == false then
+ enabled_triggers[trigger] = true
+ end
+ end
+ end
+ if trigger_table.remove ~= nil then
+ for _, trigger in pairs(trigger_table.remove) do
+ if enabled_triggers[trigger] == false then
+ enabled_triggers[trigger] = true
+ end
+ end
+ end
+ end
+ end
+ end)
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/extra_integration/UTIL/sv_version.lua b/resources/lvc/PLUGINS/extra_integration/UTIL/sv_version.lua
new file mode 100644
index 000000000..980885247
--- /dev/null
+++ b/resources/lvc/PLUGINS/extra_integration/UTIL/sv_version.lua
@@ -0,0 +1,33 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: sv_version.lua
+PURPOSE: Handle plugin version checking.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+local plugin_name = 'extra_integration'
+local plugin_version = '1.2.1'
+
+RegisterServerEvent('lvc:plugins_getVersions')
+AddEventHandler('lvc:plugins_getVersions', function()
+ TriggerEvent('lvc:plugins_storePluginVersion', plugin_name, plugin_version)
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/takedowns/SETTINGS.lua b/resources/lvc/PLUGINS/takedowns/SETTINGS.lua
new file mode 100644
index 000000000..921ea99b7
--- /dev/null
+++ b/resources/lvc/PLUGINS/takedowns/SETTINGS.lua
@@ -0,0 +1,19 @@
+--------------------TAKE DOWNS---------------------
+tkd_masterswitch = true
+-- Enables plugin, allowing users to toggle takedowns.
+tkd_key = 74
+-- Key to toggle TKD light. (default: 74 (H))
+tkd_combokey = 21
+-- Key that needs to be pressed in combination with tkd_key. Set to false to disable. (default: 21 (Left Shift))
+tkd_intensity_default = 100
+-- Overall brightness of TKD light.
+tkd_radius_default = 50
+-- Angle of spread from 0 (narrow/small) to 90 (almost 180 degrees)
+tkd_distance_default = 50
+-- Max distance light can reach.
+tkd_falloff_default = 1000
+-- How fast the light fades/appears dim at distance, this has massive effect on perceived intensity and distance.
+tkd_sync_radius = 400
+-- Distance to 'sync' / display vehicles TKD light. Larger the number the slower the script depending on players and # of TKDs on. (default: 400)
+tkd_highbeam_integration_default = 2
+-- 1 - disabled, 2 - Takedown Set Highbeams, 3 - Highbeams Set Takedowns.
diff --git a/resources/lvc/PLUGINS/takedowns/UI/cl_ragemenu.lua b/resources/lvc/PLUGINS/takedowns/UI/cl_ragemenu.lua
new file mode 100644
index 000000000..2b2dd86fc
--- /dev/null
+++ b/resources/lvc/PLUGINS/takedowns/UI/cl_ragemenu.lua
@@ -0,0 +1,67 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_ragemenu.lua
+PURPOSE: Handle RageUI
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+RMenu.Add('lvc', 'tkdsettings', RageUI.CreateSubMenu(RMenu:Get('lvc', 'plugins'),' ', Lang:t('plugins.menu_tkd'), 0, 0, "lvc", "lvc_plugin_logo"))
+RMenu:Get('lvc', 'tkdsettings'):DisplayGlare(false)
+
+CreateThread(function()
+ while true do
+ --TKD SETTINGS
+ RageUI.IsVisible(RMenu:Get('lvc', 'tkdsettings'), function()
+ RageUI.List(Lang:t('plugins.tkd_integration'), { Lang:t('plugins.tkd_integration_off'), Lang:t('plugins.tkd_integration_set_highbeam'), Lang:t('plugins.tkd_integration_highbeam_set_tkd') }, tkd_mode, Lang:t('plugins.tkd_integration_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ tkd_mode = Index
+ end,
+ })
+ RageUI.List(Lang:t('plugins.tkd_position'), {'1', '2', '3', '4'}, tkd_scheme, Lang:t('plugins.tkd_position_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ tkd_scheme = Index
+ end,
+ })
+ RageUI.Slider(Lang:t('plugins.tkd_intensity'), tkd_intensity, 150, 15, Lang:t('plugins.tkd_intensity_desc'), false, {}, true, {
+ onSliderChange = function(Index)
+ tkd_intensity = Index
+ end,
+ })
+ RageUI.Slider(Lang:t('plugins.tkd_radius'), tkd_radius, 90, 9, Lang:t('plugins.tkd_radius_desc'), false, {}, true, {
+ onSliderChange = function(Index)
+ tkd_radius = Index
+ end,
+ })
+ RageUI.Slider(Lang:t('plugins.tkd_distance'), tkd_distance, 250, 25, Lang:t('plugins.tkd_distance_desc'), false, {}, true, {
+ onSliderChange = function(Index)
+ tkd_distance = Index
+ end,
+ })
+ RageUI.Slider(Lang:t('plugins.tkd_falloff'), tkd_falloff, 2000, 200, Lang:t('plugins.tkd_falloff_desc'), false, {}, true, {
+ onSliderChange = function(Index)
+ tkd_falloff = Index
+ end,
+ })
+ end)
+ Wait(0)
+ end
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/takedowns/UTIL/cl_tkds.lua b/resources/lvc/PLUGINS/takedowns/UTIL/cl_tkds.lua
new file mode 100644
index 000000000..50f4a886d
--- /dev/null
+++ b/resources/lvc/PLUGINS/takedowns/UTIL/cl_tkds.lua
@@ -0,0 +1,198 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_tkds.lua
+PURPOSE: Contains takedown threads, functions, etc.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+local count_tkdclean_timer = 0
+local delay_tkdclean_timer = 400
+local count_bcast_timer = 0
+local delay_bcast_timer = 200
+state_tkd = {}
+local TKDS = {}
+
+local light_start_pos = nil
+local light_end_pos = nil
+local light_direction = nil
+local veh_dist = {}
+
+tkd_intensity = tkd_intensity_default
+tkd_radius = tkd_radius_default
+tkd_distance = tkd_distance_default
+tkd_falloff = tkd_falloff_default
+tkd_mode = tkd_highbeam_integration_default
+tkd_sync_radius = tkd_sync_radius^2
+tkd_scheme = 1
+
+local tkd_scheme_lookup = {
+ { start_y = 1.0, start_z = 1.0, end_y = 10.0, end_z = -1.0},
+ { start_y = 1.0, start_z = 2.0, end_y = 10.0, end_z = 0.0},
+ { start_y = 1.5, start_z = 1.0, end_y = 10.0, end_z = 1.0},
+ { start_y = 2.25, start_z = 1.0, end_y = 10.0, end_z = 1.0},
+}
+
+------TAKE DOWN THREADS------
+CreateThread(function()
+ while true do
+ if tkd_masterswitch then
+ --CLEANUP DEAD TKDS
+ if count_tkdclean_timer > delay_tkdclean_timer then
+ count_tkdclean_timer = 0
+ for k, v in pairs(state_tkd) do
+ if v == true then
+ if not DoesEntityExist(k) or IsEntityDead(k) then
+ state_tkd[k] = nil
+ end
+ end
+ end
+ else
+ count_tkdclean_timer = count_tkdclean_timer + 1
+ end
+
+ if player_is_emerg_driver and UpdateOnscreenKeyboard() ~= 0 then
+ ----- CONTROLS -----
+ if not IsPauseMenuActive() then
+ if not key_lock and tkd_mode ~= 3 then
+ if IsControlPressed(0, tkd_combokey) or tkd_combokey == false then
+ DisableControlAction(0, tkd_key, true)
+ if IsDisabledControlJustReleased(0, tkd_key) then
+ if state_tkd[veh] == true then
+ if tkd_mode == 2 then
+ SetVehicleFullbeam(veh, false)
+ end
+ TKDS:TogTkdStateForVeh(veh, false)
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ else
+ if tkd_mode == 2 then
+ SetVehicleFullbeam(veh, true)
+ end
+ TKDS:TogTkdStateForVeh(veh, true)
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ end
+ HUD:SetItemState('tkd', state_tkd[veh])
+ count_bcast_timer = delay_bcast_timer
+ end
+ end
+ end
+ end
+ end
+ ----- AUTO BROADCAST VEH STATES -----
+ if count_bcast_timer > delay_bcast_timer then
+ count_bcast_timer = 0
+ TriggerServerEvent('lvc:TogTkdState_s', state_tkd[veh])
+ else
+ count_bcast_timer = count_bcast_timer + 1
+ end
+ else
+ Wait(500)
+ end
+ Wait(0)
+ end
+end)
+-----------------------------
+-- TKDs: DrawTakeDowns Thread of vehicles within range
+CreateThread(function()
+ while true do
+ if tkd_masterswitch then
+ for veh,state in pairs(state_tkd) do
+ if veh_dist[veh] ~= nil and veh_dist[veh] < tkd_sync_radius then
+ if state then
+ TKDS:DrawTakeDown(veh)
+ end
+ end
+ end
+ end
+ Wait(0)
+ end
+end)
+
+-- Set vehicles distances in table for DrawTakeDowns Thread
+CreateThread(function()
+ while true do
+ if tkd_masterswitch then
+ for veh,_ in pairs(state_tkd) do
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ veh_dist[veh] = Vdist2(GetEntityCoords(playerped), GetEntityCoords(veh))
+ end
+ end
+ end
+ Wait(500)
+ end
+end)
+
+--Get Headlight State for TKD Trigger
+CreateThread(function()
+ while true do
+ if tkd_masterswitch and player_is_emerg_driver and tkd_mode == 3 then
+ _, veh_lights, veh_headlights = GetVehicleLightsState(veh)
+ if (veh_lights == 1 and veh_headlights == 1) or (veh_lights == 0 and veh_headlights == 1) then
+ TKDS:TogTkdStateForVeh(veh, true)
+ HUD:SetItemState('tkd', state_tkd[veh])
+ elseif state_tkd[veh] then
+ TKDS:TogTkdStateForVeh(veh, false)
+ HUD:SetItemState('tkd', state_tkd[veh])
+ end
+ Wait(50)
+ else
+ Wait(1000)
+ end
+ end
+end)
+
+---------------------------------------------------------------------
+function TKDS:TogTkdStateForVeh(veh, toggle)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ if toggle ~= state_tkd[veh] then
+ state_tkd[veh] = toggle
+ end
+ end
+end
+---------------------------------------------------------------------
+-- Coordinate calculations and drawing of spotlight on passed vehicle.
+function TKDS:DrawTakeDown(veh)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) and veh_dist[veh] ~= nil then
+ light_start_pos = GetOffsetFromEntityInWorldCoords(veh, 0.0, tkd_scheme_lookup[tkd_scheme].start_y, tkd_scheme_lookup[tkd_scheme].start_z)
+ light_end_pos = GetOffsetFromEntityInWorldCoords(veh, 0.0, tkd_scheme_lookup[tkd_scheme].end_y, tkd_scheme_lookup[tkd_scheme].end_z)
+ light_direction = vector3(light_end_pos-light_start_pos)
+ DrawSpotLight(light_start_pos, light_direction, 200, 200, 255, tkd_distance+0.0, tkd_intensity+0.0, 0.0, tkd_radius+0.0, tkd_falloff+veh_dist[veh]/2+0.0)
+
+ if debug_mode then
+ DrawLine(light_start_pos, light_end_pos, 255, 0, 0, 255)
+ end
+ end
+end
+
+---------------------------------------------------------------------
+RegisterNetEvent('lvc:TogTkdState_c')
+AddEventHandler('lvc:TogTkdState_c', function(sender, toggle)
+ local player_s = GetPlayerFromServerId(sender)
+ local ped_s = GetPlayerPed(player_s)
+ if DoesEntityExist(ped_s) and not IsEntityDead(ped_s) then
+ if ped_s ~= GetPlayerPed(-1) then
+ if IsPedInAnyVehicle(ped_s, false) then
+ local veh = GetVehiclePedIsUsing(ped_s)
+ TKDS:TogTkdStateForVeh(veh, toggle)
+ end
+ end
+ end
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/takedowns/UTIL/sv_tkds.lua b/resources/lvc/PLUGINS/takedowns/UTIL/sv_tkds.lua
new file mode 100644
index 000000000..4b8d9ddf2
--- /dev/null
+++ b/resources/lvc/PLUGINS/takedowns/UTIL/sv_tkds.lua
@@ -0,0 +1,30 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: server.lua
+PURPOSE: Handle version checking, syncing sirens,
+and opening links.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+RegisterServerEvent('lvc:TogTkdState_s')
+AddEventHandler('lvc:TogTkdState_s', function(newstate)
+ TriggerClientEvent('lvc:TogTkdState_c', -1, source, newstate)
+end)
diff --git a/resources/lvc/PLUGINS/takedowns/UTIL/sv_version.lua b/resources/lvc/PLUGINS/takedowns/UTIL/sv_version.lua
new file mode 100644
index 000000000..473e7f4e1
--- /dev/null
+++ b/resources/lvc/PLUGINS/takedowns/UTIL/sv_version.lua
@@ -0,0 +1,33 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: sv_version.lua
+PURPOSE: Handle plugin version checking.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+local plugin_name = 'takedowns'
+local plugin_version = '1.0.3'
+
+RegisterServerEvent('lvc:plugins_getVersions')
+AddEventHandler('lvc:plugins_getVersions', function()
+ TriggerEvent('lvc:plugins_storePluginVersion', plugin_name, plugin_version)
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/traffic_advisor/SETTINGS.lua b/resources/lvc/PLUGINS/traffic_advisor/SETTINGS.lua
new file mode 100644
index 000000000..de4704614
--- /dev/null
+++ b/resources/lvc/PLUGINS/traffic_advisor/SETTINGS.lua
@@ -0,0 +1,12 @@
+--------------------TRAFFIC ADVISOR SETTINGS---------------------
+ta_masterswitch = true
+-- Determines if traffic_advisor plugin can be activated.
+ta_combokey = false
+-- Key that needs to be pressed in combination with registered key mapping. Set to false to disable. (default: disabled (false))
+-- List of Controls: https://docs.fivem.net/docs/game-references/controls/
+ta_sync_radius_default = 20
+
+
+--[[ Documentation / Wiki: https://github.com/TrevorBarns/luxart-vehicle-control/wiki/Traffic-Advisor ]]
+
+TA_ASSIGNMENTS = { }
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/traffic_advisor/UI/cl_ragemenu.lua b/resources/lvc/PLUGINS/traffic_advisor/UI/cl_ragemenu.lua
new file mode 100644
index 000000000..d24bcc74c
--- /dev/null
+++ b/resources/lvc/PLUGINS/traffic_advisor/UI/cl_ragemenu.lua
@@ -0,0 +1,77 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_ragemenu.lua
+PURPOSE: Handle RageUI
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+if ta_masterswitch then
+ RMenu.Add('lvc', 'tasettings', RageUI.CreateSubMenu(RMenu:Get('lvc', 'plugins'),' ', Lang:t('plugins.menu_ta'), 0, 0, "lvc", "lvc_plugin_logo"))
+ RMenu:Get('lvc', 'tasettings'):DisplayGlare(false)
+
+ CreateThread(function()
+ while true do
+ --TKD SETTINGS
+ RageUI.IsVisible(RMenu:Get('lvc', 'tasettings'), function()
+ --[[
+ RageUI.List('Combo Key', {'Disabled', 'LSHIFT', 'LCTRL', 'LALT', 'LSHIFT OR (X)', 'LCTRL OR (L3)'}, ta_combokey_index, 'Select key that needs to be held in addition to TA Keys to activate. '~b~( )~s~' indicates controller key.', {}, true, {
+ onListChange = function(Index, Item)
+ ta_combokey_index = Index
+ end,
+ }) ]]
+ RageUI.List(Lang:t('plugins.ta_pattern'), {'1', '2', '3', '4', '5', '6', '7'}, hud_pattern, Lang:t('plugins.ta_pattern_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ hud_pattern = Index
+ HUD:SetItemState('ta_pattern', hud_pattern)
+ HUD:SetItemState('ta', state_ta[veh])
+ end,
+ })
+ --[[
+ RageUI.Checkbox('Disable Incorrect Combo Keys', 'Disables key mapping when a combo key that is not assigned is pressed.', block_incorrect_combo, {}, {
+ onChecked = function()
+ block_incorrect_combo = true
+ end,
+ onUnChecked = function()
+ block_incorrect_combo = false
+ end
+ }) ]]
+ RageUI.Checkbox(Lang:t('plugins.ta_save'), Lang:t('plugins.ta_save_desc'), TA.preserve_ta_state, {}, {
+ onChecked = function()
+ TA.preserve_ta_state = true
+ end,
+ onUnChecked = function()
+ TA.preserve_ta_state = false
+ end
+ })
+ RageUI.Checkbox(Lang:t('plugins.ta_sync'), Lang:t('plugins.ta_sync_desc'), false, {Enabled = false}, {
+ onChecked = function()
+ TA.sync_ta_state = true
+ end,
+ onUnChecked = function()
+ TA.sync_ta_state = false
+ end
+ })
+ end)
+ Wait(0)
+ end
+ end)
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/traffic_advisor/UTIL/cl_advisor.lua b/resources/lvc/PLUGINS/traffic_advisor/UTIL/cl_advisor.lua
new file mode 100644
index 000000000..54b0a4717
--- /dev/null
+++ b/resources/lvc/PLUGINS/traffic_advisor/UTIL/cl_advisor.lua
@@ -0,0 +1,205 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+Traffic Advisor Plugin by Dawson
+---------------------------------------------------
+FILE: cl_advisor.lua
+PURPOSE: Contains threads, functions to
+change traffic advisor state through extras.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+if ta_masterswitch then
+ TA = {}
+ local taExtras = {}
+ local profile = false
+ local temp_hud_disable
+ TA.preserve_ta_state = false
+ TA.sync_ta_state = false
+ TA.block_incorrect_combo = false
+ state_ta = {}
+
+ local count_taclean_timer = 0
+ local delay_taclean_timer = 400
+ local count_bcast_timer = 0
+ local delay_bcast_timer = 200
+
+ ----------------THREADED FUNCTIONS----------------
+ --[[TA State Syncing]]
+ --Broadcasts TA states to other players and cleans up invalid entities.
+ CreateThread(function()
+ while false do --Disabled until syncing implemented.
+ --CLEANUP DEAD TA States
+ if count_taclean_timer > delay_taclean_timer then
+ count_taclean_timer = 0
+ for k, v in pairs(state_ta) do
+ if v > 0 then
+ if not DoesEntityExist(k) or IsEntityDead(k) then
+ state_ta[k] = nil
+ end
+ end
+ end
+ else
+ count_taclean_timer = count_taclean_timer + 1
+ end
+
+ ----- AUTO BROADCAST TA STATES -----
+ if count_bcast_timer > delay_bcast_timer then
+ count_bcast_timer = 0
+ TriggerServerEvent('lvc:SetTAState_s', state_ta[veh])
+ else
+ count_bcast_timer = count_bcast_timer + 1
+ end
+ Wait(0)
+ end
+ end)
+
+ --[[Toggle TA when lights are turned on.]]
+ CreateThread(function()
+ while true do
+ if player_is_emerg_driver then
+ if state_ta[veh] ~= nil and state_ta[veh] > 0 then
+ if not IsVehicleSirenOn(veh) and not temp_hud_disable then
+ HUD:SetItemState('ta', false)
+ temp_hud_disable = true
+ if not TA.preserve_ta_state then
+ if state_ta[veh] == 1 then
+ UTIL:TogVehicleExtras(veh, taExtras.left.off, true)
+ elseif state_ta[veh] == 2 then
+ UTIL:TogVehicleExtras(veh, taExtras.right.off, true)
+ elseif state_ta[veh] == 3 then
+ UTIL:TogVehicleExtras(veh, taExtras.middle.off, true)
+ end
+ state_ta[veh] = 0
+ end
+ elseif IsVehicleSirenOn(veh) and temp_hud_disable then
+ HUD:SetItemState('ta', state_ta[veh])
+ temp_hud_disable = false
+ end
+ else
+ Wait(500)
+ end
+ else
+ Wait(500)
+ end
+ Wait(0)
+ end
+ end)
+
+ RegisterCommand('lvctogleftta', function(source, args, rawCommand)
+ if ta_combokey == false or IsControlPressed(0, ta_combokey) then
+ if player_is_emerg_driver and ( taExtras.lightbar ~= nil or taExtras.lightbar == -1 ) and veh ~= nil and not IsMenuOpen() and not key_lock then
+ if ( IsVehicleExtraTurnedOn(veh, taExtras.lightbar) or taExtras.lightbar == -1 ) and IsVehicleSirenOn(veh) then
+ if state_ta[veh] == 1 then
+ UTIL:TogVehicleExtras(veh, taExtras.left.off, true)
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ state_ta[veh] = 0
+ else
+ UTIL:TogVehicleExtras(veh, taExtras.left.on, true)
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ state_ta[veh] = 1
+ end
+ HUD:SetItemState('ta', state_ta[veh])
+ end
+ end
+ end
+ end)
+
+ RegisterCommand('lvctogrightta', function(source, args, rawCommand)
+ if ta_combokey == false or IsControlPressed(0, ta_combokey) then
+ if player_is_emerg_driver and ( taExtras.lightbar ~= nil or taExtras.lightbar == -1 ) and veh ~= nil and not IsMenuOpen() and not key_lock then
+ if ( IsVehicleExtraTurnedOn(veh, taExtras.lightbar) or taExtras.lightbar == -1 ) and IsVehicleSirenOn(veh) then
+ if state_ta[veh] == 2 then
+ UTIL:TogVehicleExtras(veh, taExtras.right.off, true)
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ state_ta[veh] = 0
+ else
+ UTIL:TogVehicleExtras(veh, taExtras.right.on, true)
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ state_ta[veh] = 2
+ end
+ HUD:SetItemState('ta', state_ta[veh])
+ end
+ end
+ end
+ end)
+
+ RegisterCommand('lvctogmidta', function(source, args, rawCommand)
+ if ta_combokey == false or IsControlPressed(0, ta_combokey) then
+ if player_is_emerg_driver and ( taExtras.lightbar ~= nil or taExtras.lightbar == -1 ) and veh ~= nil and not IsMenuOpen() and not key_lock then
+ if ( IsVehicleExtraTurnedOn(veh, taExtras.lightbar) or taExtras.lightbar == -1 ) and IsVehicleSirenOn(veh) then
+ if state_ta[veh] == 3 then
+ UTIL:TogVehicleExtras(veh, taExtras.middle.off, true)
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ state_ta[veh] = 0
+ else
+ UTIL:TogVehicleExtras(veh, taExtras.middle.on, true)
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ state_ta[veh] = 3
+ end
+ HUD:SetItemState('ta', state_ta[veh])
+ end
+ end
+ end
+ end)
+
+ RegisterKeyMapping('lvctogleftta', Lang:t('plugins.ta_control_desc_left'), 'keyboard', 'left')
+ RegisterKeyMapping('lvctogrightta', Lang:t('plugins.ta_control_desc_right'), 'keyboard', 'right')
+ RegisterKeyMapping('lvctogmidta', Lang:t('plugins.ta_control_desc_middle'), 'keyboard', 'down')
+
+ CreateThread(function()
+ Wait(500)
+ UTIL:FixOversizeKeys(TA_ASSIGNMENTS)
+ end)
+
+ RegisterNetEvent('lvc:onVehicleChange')
+ AddEventHandler('lvc:onVehicleChange', function()
+ if player_is_emerg_driver and veh ~= nil then
+ taExtras, profile = UTIL:GetProfileFromTable('TA ASSIGNMENTS', TA_ASSIGNMENTS, veh, true)
+ hud_pattern = taExtras.hud_pattern or 1
+ HUD:SetItemState('ta_pattern', hud_pattern)
+
+ if state_ta[veh] == nil then
+ state_ta[veh] = 0
+ end
+ end
+ end)
+
+ RegisterNetEvent('lvc:SetTAState_c')
+ AddEventHandler('lvc:SetTAState_c', function(sender, newstate)
+ local player_s = GetPlayerFromServerId(sender)
+ local ped_s = GetPlayerPed(player_s)
+ if DoesEntityExist(ped_s) and not IsEntityDead(ped_s) then
+ if ped_s ~= GetPlayerPed(-1) then
+ if IsPedInAnyVehicle(ped_s, false) then
+ local veh = GetVehiclePedIsUsing(ped_s)
+ TA:SetTAStateForVeh(veh, newstate)
+ end
+ end
+ end
+ end)
+
+ function TA:SetTAStateForVeh(veh, newstate)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ if newstate ~= state_ta[veh] then
+ state_ta[veh] = newstate
+ end
+ end
+ end
+end
diff --git a/resources/lvc/PLUGINS/traffic_advisor/UTIL/sv_advisor.lua b/resources/lvc/PLUGINS/traffic_advisor/UTIL/sv_advisor.lua
new file mode 100644
index 000000000..555eef42f
--- /dev/null
+++ b/resources/lvc/PLUGINS/traffic_advisor/UTIL/sv_advisor.lua
@@ -0,0 +1,31 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+Traffic Advisor Plugin by Dawson
+---------------------------------------------------
+FILE: server.lua
+PURPOSE: Handle version checking, syncing sirens,
+and opening links.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+RegisterServerEvent('lvc:SetTAState_s')
+AddEventHandler('lvc:SetTAState_s', function(newstate)
+ TriggerClientEvent('lvc:SetTAState_c', -1, source, newstate)
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/traffic_advisor/UTIL/sv_version.lua b/resources/lvc/PLUGINS/traffic_advisor/UTIL/sv_version.lua
new file mode 100644
index 000000000..8f06e9916
--- /dev/null
+++ b/resources/lvc/PLUGINS/traffic_advisor/UTIL/sv_version.lua
@@ -0,0 +1,33 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: sv_version.lua
+PURPOSE: Handle plugin version checking.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+local plugin_name = 'traffic_advisor'
+local plugin_version = '1.0.5'
+
+RegisterServerEvent('lvc:plugins_getVersions')
+AddEventHandler('lvc:plugins_getVersions', function()
+ TriggerEvent('lvc:plugins_storePluginVersion', plugin_name, plugin_version)
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/trailer_support/SETTINGS.lua b/resources/lvc/PLUGINS/trailer_support/SETTINGS.lua
new file mode 100644
index 000000000..e22c28e31
--- /dev/null
+++ b/resources/lvc/PLUGINS/trailer_support/SETTINGS.lua
@@ -0,0 +1,7 @@
+--------------------TRAILER SUPPORT SETTINGS---------------------
+trailer_masterswitch = true
+-- Determines if trailer_support plugin can be activated.
+
+--[[ Documentation / Wiki: https://github.com/TrevorBarns/luxart-vehicle-control/wiki/Trailer-Support ]]
+
+TRAILERS = { }
diff --git a/resources/lvc/PLUGINS/trailer_support/UI/cl_ragemenu.lua b/resources/lvc/PLUGINS/trailer_support/UI/cl_ragemenu.lua
new file mode 100644
index 000000000..cd8d5be98
--- /dev/null
+++ b/resources/lvc/PLUGINS/trailer_support/UI/cl_ragemenu.lua
@@ -0,0 +1,149 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_ragemenu.lua
+PURPOSE: Handle RageUI
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+RMenu.Add('lvc', 'trailersettings', RageUI.CreateSubMenu(RMenu:Get('lvc', 'plugins'),' ', Lang:t('plugins.menu_ts'), 0, 0, "lvc", "lvc_plugin_logo"))
+RMenu.Add('lvc', 'trailerextras', RageUI.CreateSubMenu(RMenu:Get('lvc', 'trailersettings'),' ', Lang:t('plugins.ts_menu_extras'), 0, 0, "lvc", "lvc_plugin_logo"))
+RMenu.Add('lvc', 'trailerdoors', RageUI.CreateSubMenu(RMenu:Get('lvc', 'trailersettings'),' ', Lang:t('plugins.ts_menu_doors'), 0, 0, "lvc", "lvc_plugin_logo"))
+RMenu:Get('lvc', 'trailersettings'):DisplayGlare(false)
+RMenu:Get('lvc', 'trailerextras'):DisplayGlare(false)
+RMenu:Get('lvc', 'trailerdoors'):DisplayGlare(false)
+
+local doors = { Lang:t('plugins.ts_door_fl'), Lang:t('plugins.ts_door_fr'), Lang:t('plugins.ts_door_rl'), Lang:t('plugins.ts_door_rr'), Lang:t('plugins.ts_door_hood'), Lang:t('plugins.ts_door_trunk'), Lang:t('plugins.ts_door_extra1'), Lang:t('plugins.ts_door_extra2'), Lang:t('plugins.ts_door_bombbay') }
+local trailer_set = false
+
+CreateThread(function()
+ while true do
+ if trailer ~= nil and trailer ~= 0 then
+ trailer_set = true
+ else
+ trailer_set = false
+ end
+
+ RageUI.IsVisible(RMenu:Get('lvc', 'trailersettings'), function()
+ --Current Trailer Display
+ RageUI.Button(Lang:t('plugins.ts_current'), Lang:t('plugins.ts_current_desc'), {RightLabel = TRAIL:GetTrailerDisplayName()}, true, {
+ onSelected = function()
+ end,
+ })
+
+ --Custom Toggle Buttons
+ if TRAIL.custom_toggles_set then
+ RageUI.Separator(Lang:t('plugins.ts_shortcut_separator'))
+ for i, custom_tog_table in ipairs(TRAIL.TBL) do
+ RageUI.Button(custom_tog_table[1], Lang:t('plugins.ts_shortcut_desc', { shortcut = custom_tog_table[1] }), { }, trailer_set, {
+ onSelected = function()
+ for i, custom_tog in pairs(custom_tog_table[2]) do
+ TRAIL:SetExtraState(custom_tog.Trailer, custom_tog.Extra, custom_tog.State)
+ end
+ end,
+ })
+ end
+ end
+
+ RageUI.Separator(Lang:t('plugins.ts_submenus_separator'))
+
+ -- Sub Menu Buttons
+ RageUI.Button(Lang:t('plugins.ts_menu_extras_button'), Lang:t('plugins.ts_menu_extras_desc'), {RightLabel = '→→→'}, trailer_set, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'trailerextras'))
+ RageUI.Button(Lang:t('plugins.ts_menu_doors_button'), Lang:t('plugins.ts_menu_doors_desc'), {RightLabel = '→→→'}, trailer_set, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'trailerdoors'))
+ end)
+
+
+ --EXTRAS MENU
+ RageUI.IsVisible(RMenu:Get('lvc', 'trailerextras'), function()
+ RageUI.Separator(Lang:t('plugins.ts_truck_separator'))
+ for extra_id=1,14 do
+ if DoesEntityExist(veh) then
+ if DoesExtraExist(veh, extra_id) then
+ RageUI.Checkbox(Lang:t('plugins.ts_extra', { extra = extra_id }), Lang:t('plugins.ts_extra_desc', { extra = extra_id }), IsVehicleExtraTurnedOn(veh, extra_id), {}, {
+ onChecked = function()
+ SetVehicleExtra(veh, extra_id, false)
+ end,
+ onUnChecked = function()
+ SetVehicleExtra(veh, extra_id, true)
+ end
+ })
+ end
+ end
+ end
+ RageUI.Separator(Lang:t('plugins.ts_trailer_separator'))
+ for extra_id=1,14 do
+ if DoesEntityExist(trailer) then
+ if DoesExtraExist(trailer, extra_id) then
+ RageUI.Checkbox(Lang:t('plugins.ts_extra', { extra = extra_id }), Lang:t('plugins.ts_extra_desc', { extra = extra_id }), IsVehicleExtraTurnedOn(trailer, extra_id), {}, {
+ onChecked = function()
+ SetVehicleExtra(trailer, extra_id, false)
+ end,
+ onUnChecked = function()
+ SetVehicleExtra(trailer, extra_id, true)
+ end
+ })
+ end
+ end
+ end
+ end)
+
+ --DOORS MENU
+ RageUI.IsVisible(RMenu:Get('lvc', 'trailerdoors'), function()
+ RageUI.Separator(Lang:t('plugins.ts_truck_separator'))
+ for door_num, door_name in ipairs(doors) do
+ door_num = door_num-1
+ if DoesVehicleHaveDoor(veh, door_num) then
+ RageUI.Button(door_name, Lang:t('plugins.ts_door_desc', { door = door_name }), {}, true, {
+ onSelected = function()
+ if GetVehicleDoorAngleRatio(veh, door_num) > 0 then
+ SetVehicleDoorShut(veh, door_num, true)
+ else
+ SetVehicleDoorOpen(veh, door_num, true, false)
+ end
+ end,
+ })
+ end
+ end
+ RageUI.Separator(Lang:t('plugins.ts_trailer_separator'))
+ for door_num, door_name in ipairs(doors) do
+ door_num = door_num-1
+ if DoesVehicleHaveDoor(trailer, door_num) then
+ RageUI.Button(door_name, Lang:t('plugins.ts_door_desc', { door = door_name }), {}, true, {
+ onSelected = function()
+ if GetVehicleDoorAngleRatio(trailer, door_num) > 0 then
+ SetVehicleDoorShut(trailer, door_num, true)
+ else
+ SetVehicleDoorOpen(trailer, door_num, true, false)
+ end
+ end,
+ })
+ end
+ end
+ end)
+ Wait(0)
+ end
+end)
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/trailer_support/UTIL/cl_trailer.lua b/resources/lvc/PLUGINS/trailer_support/UTIL/cl_trailer.lua
new file mode 100644
index 000000000..aeaa966c4
--- /dev/null
+++ b/resources/lvc/PLUGINS/trailer_support/UTIL/cl_trailer.lua
@@ -0,0 +1,71 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+Traffic Advisor Plugin by Dawson
+---------------------------------------------------
+FILE: cl_trailer.lua
+PURPOSE: Contains threads, functions for trailer
+support.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+TRAIL = {}
+TRAIL.custom_toggles_set = false
+
+CreateThread(function()
+ Wait(500)
+ UTIL:FixOversizeKeys(TRAILERS)
+end)
+
+RegisterNetEvent('lvc:onVehicleChange')
+AddEventHandler('lvc:onVehicleChange', function()
+ if player_is_emerg_driver and veh ~= nil then
+ TRAIL.TBL, profile = UTIL:GetProfileFromTable('TRAILERS', TRAILERS, veh, true)
+
+ if not profile then
+ TRAIL.custom_toggles_set = false
+ else
+ TRAIL.custom_toggles_set = true
+ end
+ end
+end)
+
+function TRAIL:GetTrailerDisplayName()
+ if GetDisplayNameFromVehicleModel(GetEntityModel(trailer)) == 'CARNOTFOUND' then
+ return Lang:t('plugins.ts_not_found')
+ else
+ return GetDisplayNameFromVehicleModel(GetEntityModel(trailer))
+ end
+end
+
+function TRAIL:GetCabDisplayName()
+ return GetDisplayNameFromVehicleModel(GetEntityModel(veh))
+end
+
+function TRAIL:SetExtraState(is_trailer, extra_id, state)
+ if is_trailer then
+ if DoesExtraExist(trailer, extra_id) then
+ SetVehicleExtra(trailer, extra_id, not state)
+ end
+ else
+ if DoesExtraExist(veh, extra_id) then
+ SetVehicleExtra(veh, extra_id, not state)
+ end
+ end
+end
\ No newline at end of file
diff --git a/resources/lvc/PLUGINS/trailer_support/UTIL/sv_version.lua b/resources/lvc/PLUGINS/trailer_support/UTIL/sv_version.lua
new file mode 100644
index 000000000..ba10119d1
--- /dev/null
+++ b/resources/lvc/PLUGINS/trailer_support/UTIL/sv_version.lua
@@ -0,0 +1,33 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: sv_version.lua
+PURPOSE: Handle plugin version checking.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+local plugin_name = 'trailer_support'
+local plugin_version = '1.0.1'
+
+RegisterServerEvent('lvc:plugins_getVersions')
+AddEventHandler('lvc:plugins_getVersions', function()
+ TriggerEvent('lvc:plugins_storePluginVersion', plugin_name, plugin_version)
+end)
\ No newline at end of file
diff --git a/resources/lvc/SETTINGS.lua b/resources/lvc/SETTINGS.lua
new file mode 100644
index 000000000..8ff0f7c08
--- /dev/null
+++ b/resources/lvc/SETTINGS.lua
@@ -0,0 +1,75 @@
+--------------------COMMUNITY ID-------------------
+community_id = 'elitegaming'
+-- Sets a prefix for saved values at the user end, without this people who play on multiple LVC server could face conflicts. **Once set DO NOT CHANGE. It will result in loss of data for end users.**
+-- I recommend something short (4-6 characters) for example a community abbreviation. SPACES ARE NOT ALLOWED.
+
+------------------MENU KEYBINDING------------------
+open_menu_key = 'O'
+-- Sets default key for RegisterKeyMapping. Examples: 'l','F5', etc. DEFAULT: 'O', users may set one in their GTA V > Settings > Hotkeys > FiveM settings.
+-- More info: https://cookbook.fivem.net/2020/01/06/using-the-new-console-key-bindings/
+-- List of Keys: https://pastebin.com/u9ewvWWZ
+
+
+---------------LOCKOUT FUNCTIONALITY---------------
+lockout_default_hotkey = ''
+-- Sets default key for RegisterKeyMapping. Examples: 'l','F5', etc. DEFAULT: NONE, users may set one in their GTA V > Settings > Hotkeys > FiveM settings.
+-- More info: https://cookbook.fivem.net/2020/01/06/using-the-new-console-key-bindings/
+-- List of Keys: https://pastebin.com/u9ewvWWZ
+locked_press_count = 5
+-- Initial press count for reminder e.g. if this is 5 and reminder_rate is 10 then, after 5 key presses it will remind you the first time, after that every 10 key presses.
+reminder_rate = 10
+-- How often, in luxart key presses, to remind you that your siren controller is locked.
+
+-----------------HUD FUNCTIONALITY-----------------
+hud_first_default = true
+-- First state of HUD, otherwise it uses the players KVP setting (previous state).
+
+---------------MAIN SIREN SETTINGS-----------------
+main_siren_settings_masterswitch = true
+-- Enables users to rename siren tones, change siren options. (Cycle / Button)
+park_kill_masterswitch = true
+-- Enables park kill functionality. Setting this to false will not allow users to change from default behaviour this.
+park_kill_default = false
+-- Default setting for park kill mode. (default: true)
+airhorn_interrupt_masterswitch = true
+-- Enables ability to toggle air horn interrupt. Setting this to false will not allow users to change from default behaviour this.
+airhorn_interrupt_default = true
+-- Default setting of the airhorn interrupt for the main siren. (default: true)
+reset_to_standby_masterswitch = true
+-- Enables ability to toggle reset to standby. Setting this to false will not allow users to change from default behaviour this.
+reset_to_standby_default = true
+-- Default setting for Reset-To-Standby functionality. (default: true)
+
+--------------CUSTOM MANU/HORN/SIREN---------------
+custom_manual_tones_master_switch = true
+-- Enables manual tone settings menu items to change which tone is played for the primary and secondary manual tones.
+custom_aux_tones_master_switch = true
+-- Enables auxiliary tone settings menu item so players can change which tone is played when AUX siren (Up-Arrow) is enabled.
+main_siren_set_register_keys_set_defaults = true
+-- Enables RegisterKeyMapping for all main_allowed_tones and sets the default keys to numrow 1-0.
+
+
+--------------TURN SIGNALS / HAZARDS---------------
+hazard_key = 202
+left_signal_key = 84
+right_signal_key = 83
+hazard_hold_duration = 750
+-- Time in milliseconds backspace must be pressed to turn on / off hazard lights.
+
+
+----------------SOUND EFFECT VOLUMES---------------
+button_sfx_scheme_choices = { 'SSP2000', 'SSP3000', 'Cencom', 'ST300' }
+--Customize which button SFX schemes are available. An item here must match exactly the folder name located in `lvc\UI\sounds`, recommend NOT using spaces instead use a dash (e.g. Cencom-Gold)
+default_sfx_scheme_name = 'SSP2000'
+default_on_volume = 0.5
+default_off_volume = 0.7
+default_upgrade_volume = 0.5
+default_downgrade_volume = 0.7
+default_hazards_volume = 0.09
+default_lock_volume = 0.25
+default_lock_reminder_volume = 0.2
+default_reminder_volume = 0.09
+
+
+------------------PLUG-IN SUPPORT------------------
+plugins_installed = true
\ No newline at end of file
diff --git a/resources/lvc/SIRENS.lua b/resources/lvc/SIRENS.lua
new file mode 100644
index 000000000..ac2effe9b
--- /dev/null
+++ b/resources/lvc/SIRENS.lua
@@ -0,0 +1,55 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additions by TrevorBarns
+---------------------------------------------------
+FILE: SIRENS.lua
+PURPOSE: Associate specific sirens with specific
+vehicles. Siren assignments.
+---------------------------------------------------
+SIREN TONE TABLE:
+ ID- Generic Name (SIREN STRING) [vehicles.awc name]
+ 1 - Airhorn (SIRENS_AIRHORN) [AIRHORN_EQD]
+ 2 - Wail (VEHICLES_HORNS_SIREN_1) [SIREN_PA20A_WAIL]
+ 3 - Yelp (VEHICLES_HORNS_SIREN_2) [SIREN_2]
+ 4 - Priority (VEHICLES_HORNS_POLICE_WARNING) [POLICE_WARNING]
+ 5 - CustomA* (RESIDENT_VEHICLES_SIREN_WAIL_01) [SIREN_WAIL_01]
+ 6 - CustomB* (RESIDENT_VEHICLES_SIREN_WAIL_02) [SIREN_WAIL_02]
+ 7 - CustomC* (RESIDENT_VEHICLES_SIREN_WAIL_03) [SIREN_WAIL_03]
+ 8 - CustomD* (RESIDENT_VEHICLES_SIREN_QUICK_01) [SIREN_QUICK_01]
+ 9 - CustomE* (RESIDENT_VEHICLES_SIREN_QUICK_02) [SIREN_QUICK_02]
+ 10 - CustomF* (RESIDENT_VEHICLES_SIREN_QUICK_03) [SIREN_QUICK_03]
+ 11 - Powercall (VEHICLES_HORNS_AMBULANCE_WARNING) [AMBULANCE_WARNING]
+ 12 - FireHorn (VEHICLES_HORNS_FIRETRUCK_WARNING) [FIRE_TRUCK_HORN]
+ 13 - Firesiren (RESIDENT_VEHICLES_SIREN_FIRETRUCK_WAIL_01) [SIREN_FIRETRUCK_WAIL_01]
+ 14 - Firesiren2 (RESIDENT_VEHICLES_SIREN_FIRETRUCK_QUICK_01) [SIREN_FIRETRUCK_QUICK_01]
+]]
+-- CHANGE SIREN NAMES, AUDIONAME, AUDIOREF
+SIRENS = {
+ --[[1]] { Name = 'Airhorn', String = 'SIRENS_AIRHORN', Ref = 0 }, --1
+ --[[2]] { Name = 'Wail', String = 'VEHICLES_HORNS_SIREN_1', Ref = 0 }, --2
+ --[[3]] { Name = 'Yelp', String = 'VEHICLES_HORNS_SIREN_2', Ref = 0 }, --3
+ --[[4]] { Name = 'Priority', String = 'VEHICLES_HORNS_POLICE_WARNING', Ref = 0 }, --4
+ --[[5]] { Name = 'CustomA', String = 'RESIDENT_VEHICLES_SIREN_WAIL_01', Ref = 0 }, --5
+ --[[6]] { Name = 'CustomB', String = 'RESIDENT_VEHICLES_SIREN_WAIL_02', Ref = 0 }, --6
+ --[[7]] { Name = 'CustomC', String = 'RESIDENT_VEHICLES_SIREN_WAIL_03', Ref = 0 }, --7
+ --[[8]] { Name = 'CustomD', String = 'RESIDENT_VEHICLES_SIREN_QUICK_01', Ref = 0 }, --8
+ --[[9]] { Name = 'CustomE', String = 'RESIDENT_VEHICLES_SIREN_QUICK_02', Ref = 0 }, --9
+ --[[10]] { Name = 'CustomF', String = 'RESIDENT_VEHICLES_SIREN_QUICK_03', Ref = 0 }, --10
+ --[[11]] { Name = 'Powercall', String = 'VEHICLES_HORNS_AMBULANCE_WARNING', Ref = 0 }, --11
+ --[[12]] { Name = 'Fire Horn', String = 'VEHICLES_HORNS_FIRETRUCK_WARNING', Ref = 0 }, --12
+ --[[13]] { Name = 'Fire Yelp', String = 'RESIDENT_VEHICLES_SIREN_FIRETRUCK_WAIL_01', Ref = 0 }, --13
+ --[[14]] { Name = 'Fire Wail', String = 'RESIDENT_VEHICLES_SIREN_FIRETRUCK_QUICK_01', Ref = 0 }, --14
+}
+
+--ASSIGN SIRENS TO VEHICLES
+SIREN_ASSIGNMENTS = {
+ --[''] = {tones},
+ ['DEFAULT'] = { 1, 2, 3, 4 },
+ ['FIRETRUK'] = { 12, 13, 14, 11 },
+ ['AMBULAN'] = { 1, 2, 3, 4, 11 },
+ ['LGUARD'] = { 1, 2, 3, 4, 11 },
+}
\ No newline at end of file
diff --git a/resources/lvc/UI/cl_audio.lua b/resources/lvc/UI/cl_audio.lua
new file mode 100644
index 000000000..9dccd1a7d
--- /dev/null
+++ b/resources/lvc/UI/cl_audio.lua
@@ -0,0 +1,115 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_audio.lua
+PURPOSE: NUI Audio Related Functions.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+AUDIO = { }
+local activity_timer = 0
+local activity_reminder_index = 1
+local activity_reminder_lookup = { [2] = 30000, [3] = 60000, [4] = 120000, [5] = 300000, [6] = 600000 }
+
+AUDIO.radio_masterswitch = true
+AUDIO.airhorn_button_SFX = false
+AUDIO.manu_button_SFX = false
+
+AUDIO.button_sfx_scheme_choices = button_sfx_scheme_choices
+AUDIO.button_sfx_scheme = default_sfx_scheme_name
+AUDIO.on_volume = default_on_volume
+AUDIO.off_volume = default_off_volume
+AUDIO.upgrade_volume = default_upgrade_volume
+AUDIO.downgrade_volume = default_downgrade_volume
+AUDIO.hazards_volume = default_hazards_volume
+AUDIO.lock_volume = default_lock_volume
+AUDIO.lock_reminder_volume = default_lock_reminder_volume
+AUDIO.activity_reminder_volume = default_reminder_volume
+
+------ACTIVITY REMINDER FUNCTIONALITY------
+CreateThread(function()
+ while true do
+ while activity_reminder_index > 1 and player_is_emerg_driver do
+ if IsVehicleSirenOn(veh) and state_lxsiren[veh] == 0 and state_pwrcall[veh] == 0 then
+ if activity_timer < 1 then
+ AUDIO:Play('Reminder', AUDIO.activity_reminder_volume)
+ AUDIO:ResetActivityTimer()
+ end
+ end
+ Wait(100)
+ end
+ Wait(1000)
+ end
+end)
+
+-- Activity Reminder Timer
+CreateThread(function()
+ while true do
+ if veh ~= nil then
+ while activity_reminder_index > 1 and IsVehicleSirenOn(veh) and state_lxsiren[veh] == 0 and state_pwrcall[veh] == 0 do
+ if activity_timer > 1 then
+ Wait(1000)
+ activity_timer = activity_timer - 1000
+ else
+ Wait(100)
+ AUDIO:ResetActivityTimer()
+ end
+ end
+ end
+ Wait(1000)
+ end
+end)
+
+---------------------------------------------------------------------
+--[[Play NUI front in audio.]]
+function AUDIO:Play(soundFile, soundVolume, schemeless)
+ local schemeless = schemeless or false
+ if not schemeless then
+ soundFile = AUDIO.button_sfx_scheme .. '/' .. soundFile;
+ end
+
+ SendNUIMessage({
+ _type = 'audio',
+ file = soundFile,
+ volume = soundVolume
+ })
+end
+
+--[[After activity has occurred, reset the activity timer to the selected reminder interval]]
+function AUDIO:ResetActivityTimer()
+ activity_timer = activity_reminder_lookup[activity_reminder_index] or 0
+end
+
+--[[Getter for current time in seconds remaining.]]
+function AUDIO:GetActivityTimer()
+ return activity_timer
+end
+
+--[[After activity has occurred, reset the activity timer to the selected reminder interval]]
+function AUDIO:GetActivityReminderIndex()
+ return activity_reminder_index
+end
+
+--[[Setter for activity reminder index]]
+function AUDIO:SetActivityReminderIndex(index)
+ if index ~= nil then
+ activity_reminder_index = index
+ end
+end
diff --git a/resources/lvc/UI/cl_hud.lua b/resources/lvc/UI/cl_hud.lua
new file mode 100644
index 000000000..6f8d51c2c
--- /dev/null
+++ b/resources/lvc/UI/cl_hud.lua
@@ -0,0 +1,324 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_hud.lua
+PURPOSE: All HUD functions, callbacks, and GTA V
+ front-end functions.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+HUD = { }
+
+local show_HUD = hud_first_default
+local HUD_temp_hidden = false
+local HUD_scale
+local HUD_pos
+local HUD_backlight_mode = 1
+local HUD_backlight_state = false
+
+---------------------------------------------------------------------
+--[[Gets initial HUD scale from JS]]
+CreateThread(function()
+ Wait(500)
+ SendNUIMessage({
+ _type = 'hud:getHudScale',
+ })
+end)
+
+---------------------------------------------------------------------
+--[[Handles HUD back light control.]]
+CreateThread(function()
+ local current_backlight_state
+ while true do
+ if player_is_emerg_driver then
+ while HUD:GetHudBacklightMode() == 1 do
+ local _, veh_lights, veh_headlights = GetVehicleLightsState(veh)
+ if veh_lights == 1 and veh_headlights == 0 and HUD:GetHudBacklightState() == false then
+ HUD:SetHudBacklightState(true)
+ elseif (veh_lights == 1 and veh_headlights == 1) or (veh_lights == 0 and veh_headlights == 1) and HUD:GetHudBacklightState() == false then
+ HUD:SetHudBacklightState(true)
+ elseif (veh_lights == 0 and veh_headlights == 0) and HUD:GetHudBacklightState() == true then
+ HUD:SetHudBacklightState(false)
+ end
+ Wait(500)
+ end
+ end
+ Wait(1000)
+ end
+end)
+
+---------------------------------------------------------------------
+--[[Handles hiding hud when hud is hidden or game is paused.]]
+CreateThread(function()
+ while true do
+ if show_HUD or HUD_temp_hidden then
+ if (not player_is_emerg_driver) or (IsHudHidden() == 1) or (IsPauseMenuActive() == 1) then
+ if not HUD_temp_hidden then
+ HUD:SetHudState(false, true)
+ HUD_temp_hidden = true
+ end
+ elseif player_is_emerg_driver and (IsHudHidden() ~= 1) and (IsPauseMenuActive() ~= 1) and HUD_temp_hidden then
+ HUD:SetHudState(true, true)
+ HUD_temp_hidden = false
+ end
+ end
+ Wait(500)
+ end
+end)
+
+------------------------------------------------
+--[[Getter for HUD State (whether hud is enabled).]]
+function HUD:GetHudState()
+ return show_HUD
+end
+
+--[[Setter for HUD State temp changes the state temporarily for pausing/hud hiding.]]
+function HUD:SetHudState(state, temporary)
+ local temporary = temporary or false
+ if not temporary then
+ show_HUD = state
+ end
+ HUD:SetItemState('hud', state)
+end
+
+------------------------------------------------
+--[[Getter for HUD scale. Updates local save from JS and returns.]]
+function HUD:GetHudScale()
+ SendNUIMessage({
+ _type = 'hud:getHudScale'
+ })
+ return HUD_scale or 0.6
+end
+
+--[[Setter for HUD scale. Updates JS & CSS.]]
+function HUD:SetHudScale(scale)
+ if scale ~= nil then
+ SendNUIMessage({
+ _type = 'hud:setHudScale',
+ scale = scale,
+ })
+ end
+end
+
+--[[Callback for JS -> LUA to set HUD_scale with current CSS]]
+RegisterNUICallback('hud:sendHudScale', function(scale, cb)
+ HUD_scale = scale
+end )
+
+------------------------------------------------
+--[[Toggles HUD images based on their state on/off]]
+function HUD:SetItemState(item, state)
+ SendNUIMessage({
+ _type = 'hud:setItemState',
+ item = item,
+ state = state
+ })
+end
+
+------------------------------------------------
+--[[HUD Backlight Modes: 1 - auto, 2 - off, 3 - on]]
+function HUD:GetHudBacklightMode()
+ return HUD_backlight_mode
+end
+
+function HUD:SetHudBacklightMode(mode)
+ if mode ~= nil then
+ HUD_backlight_mode = mode
+
+ if mode == 2 then
+ HUD:SetHudBacklightState(false)
+ elseif mode == 3 then
+ HUD:SetHudBacklightState(true)
+ end
+ end
+end
+
+function HUD:GetHudBacklightState()
+ return HUD_backlight_state
+end
+
+function HUD:SetHudBacklightState(state)
+ if state ~= nil then
+ HUD_backlight_state = state
+ if state then
+ HUD:SetItemState('time', 'night')
+ else
+ HUD:SetItemState('time', 'day')
+ end
+
+ HUD:RefreshHudItemStates()
+ end
+end
+
+------------------------------------------------
+--[[Verifies HUD item states are correct]]
+function HUD:RefreshHudItemStates()
+ if state_lxsiren[veh] ~= nil and state_lxsiren[veh] > 0 or actv_lxsrnmute_temp then
+ HUD:SetItemState('siren', true)
+ else
+ HUD:SetItemState('siren', false)
+ end
+
+ if state_pwrcall[veh] ~= nil and state_pwrcall[veh] > 0 then
+ HUD:SetItemState('siren', true)
+ end
+
+ if state_airmanu[veh] ~= nil and state_airmanu[veh] > 0 then
+ HUD:SetItemState('horn', true)
+ else
+ HUD:SetItemState('horn', false)
+ end
+
+ if state_tkd ~= nil and state_tkd[veh] ~= nil and state_tkd[veh] then
+ HUD:SetItemState('tkd', true)
+ else
+ HUD:SetItemState('tkd', false)
+ end
+
+ if key_lock then
+ HUD:SetItemState('lock', true)
+ else
+ HUD:SetItemState('lock', false)
+ end
+
+ if state_ta ~= nil and state_ta[veh] ~= nil then
+ HUD:SetItemState('ta', state_ta[veh])
+ else
+ HUD:SetItemState('ta', 0)
+ end
+
+ HUD:SetItemState('switch', IsVehicleSirenOn(veh))
+end
+
+------------------------------------------------
+--[[Setter for HUD position, used when loading save data.]]
+function HUD:SetHudPosition(data)
+ HUD_pos = data
+ SendNUIMessage({
+ _type = 'hud:setHudPosition',
+ pos = HUD_pos,
+ })
+end
+
+--[[Getter for HUD position, used when saving data.]]
+function HUD:GetHudPosition()
+ return HUD_pos
+end
+
+--[[Sets HUD position based off backup stored in JS, in case HUD is off screen.]]
+function HUD:ResetPosition()
+ SendNUIMessage({
+ _type = 'hud:resetPosition',
+ })
+end
+
+--[[Callback for JS -> LUA to set HUD_pos with current position to save.]]
+RegisterNUICallback( 'hud:setHudPositon', function(data, cb)
+ HUD_pos = data
+ STORAGE:SaveHUDSettings()
+end )
+
+------------------------------------------------
+--[[Sets NUI focus for move mode.]]
+function HUD:SetMoveMode(state)
+ SetNuiFocus( state, state )
+end
+
+--[[Sets NUI focus to false when right-click, esc, etc. are clicked.]]
+RegisterNUICallback( 'hud:setMoveState', function(state, cb)
+ SetNuiFocus(state, state)
+end )
+
+------------------------------------------------
+--On screen GTA V notification
+function HUD:ShowNotification(text, override)
+ override = override or false
+ if debug_mode or override then
+ SetNotificationTextEntry('STRING')
+ AddTextComponentString(text)
+ DrawNotification(false, true)
+ end
+end
+
+------------------------------------------------
+--Drawn On Screen Text at X, Y
+function HUD:ShowText(x, y, align, text, scale, label)
+ scale = scale or 0.4
+ SetTextJustification(align)
+ SetTextFont(0)
+ SetTextProportional(1)
+ SetTextScale(0.0, scale)
+ SetTextColour(128, 128, 128, 255)
+ SetTextDropshadow(0, 0, 0, 0, 255)
+ SetTextEdge(1, 0, 0, 0, 255)
+ SetTextDropShadow()
+ SetTextOutline()
+ if text ~= nil then
+ SetTextEntry('STRING')
+ AddTextComponentString(text)
+ else
+ SetTextEntry(label)
+ end
+ DrawText(x, y)
+ ResetScriptGfxAlign()
+end
+
+------------------------------------------------
+--Full screen Confirmation Message
+function HUD:FrontEndAlert(title, subtitle, options)
+ AddTextEntry('FACES_WARNH2', title)
+ AddTextEntry('QM_NO_0', subtitle)
+ local result = -1
+ while result == -1 do
+ DrawFrontendAlert('FACES_WARNH2', 'QM_NO_0', 0, 0, '', 0, -1, 0, '', '', false, 0)
+ HUD:ShowText(0.5, 0.75, 0, options, 0.75)
+ if IsDisabledControlJustReleased(2, 202) then
+ return false
+ end
+ if IsDisabledControlJustReleased(2, 201) then
+ return true
+ end
+ Wait(0)
+ end
+end
+
+------------------------------------------------
+--Get User Input from Keyboard
+function HUD:KeyboardInput(input_title, existing_text, max_length)
+ AddTextEntry('custom_keyboard_title', input_title)
+ DisplayOnscreenKeyboard(1, 'custom_keyboard_title', '', existing_text, '', '', '', max_length)
+
+ while UpdateOnscreenKeyboard() ~= 1 and UpdateOnscreenKeyboard() ~= 2 do
+ Wait(0)
+ end
+
+ if UpdateOnscreenKeyboard() ~= 2 then
+ local result = GetOnscreenKeyboardResult()
+ Wait(500)
+ if result ~= '' then
+ return result
+ else
+ return nil
+ end
+ else
+ Wait(500)
+ return nil
+ end
+end
\ No newline at end of file
diff --git a/resources/lvc/UI/cl_locale.lua b/resources/lvc/UI/cl_locale.lua
new file mode 100644
index 000000000..789744de0
--- /dev/null
+++ b/resources/lvc/UI/cl_locale.lua
@@ -0,0 +1,161 @@
+--[[-------------------------------------------------------------------
+QBCore Framework
+Copyright (C) 2021 Joshua Eger
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see
+-----------------------------------------------------------------------
+The code below and any function calls related to translation / locale
+are licensed above with credit to copyright holder(s).
+Source:
+---------------------------------------------------------------------]]
+
+--- @class Locale
+Locale = {}
+Locale.__index = Locale
+
+local function translateKey(phrase, subs)
+ if type(phrase) ~= 'string' then
+ error('TypeError: translateKey function expects arg #1 to be a string')
+ end
+
+ -- Substituions
+ if not subs then
+ return phrase
+ end
+
+ -- We should be escaping gsub just in case of any
+ -- shenanigans with nested template patterns or injection
+
+ -- Create and copy our return string
+ local result = phrase
+
+ -- Initial Scan over result looking for substituions
+ for k, v in pairs(subs) do
+ local templateToFind = '%%{' .. k .. '}'
+ result = result:gsub(templateToFind, tostring(v)) -- string to allow all types
+ end
+
+ return result
+end
+
+
+--- Constructor function for a new Locale class instance
+--- @param opts table - Constructor opts param
+--- @return Locale
+function Locale:new(opts)
+ local self = {}
+ setmetatable(self, Locale)
+ self.warnOnMissing = opts.warnOnMissing or true
+
+ self.phrases = {}
+ self:extend(opts.phrases or {})
+
+ return self
+end
+
+--- Method for extending an instances phrases map. This is also, used
+--- internally for initial population of phrases field.
+--- @param phrases table - Table of phrase definitions
+--- @param prefix string | nil - Optional prefix used for recursive calls
+--- @return void
+function Locale:extend(phrases, prefix)
+ for key, phrase in pairs(phrases) do
+ local prefixKey = prefix and ('%s.%s'):format(prefix, key) or key
+ -- If this is a nested table, we need to go reeeeeeeeeeeecursive
+ if type(phrase) == 'table' then
+ self:extend(phrase, prefixKey)
+ else
+ self.phrases[prefixKey] = phrase
+ end
+ end
+end
+
+
+--- Clear locale instance phrases
+--- Might be useful for memory management of large phrase maps.
+--- @return void
+function Locale:clear()
+ self.phrases = {}
+end
+
+--- Clears all phrases and replaces it with the passed phrases table
+--- @param phrases table
+function Locale:replace(phrases)
+ phrases = phrases or {}
+ self.clear()
+ self.extend(phrases)
+end
+
+--- Gets & Sets a locale depending on if an argument is passed
+--- @param newLocale string - Optional new locale to set
+--- @return string
+function Locale:locale(newLocale)
+ if (newLocale) then
+ self.currentLocale = newLocale
+ end
+ return self.currentLocale
+end
+
+--- Primary translation method for a phrase of given key
+--- @param key string - The phrase key to target
+--- @param subs table
+--- @return string
+function Locale:t(key, subs)
+ local phrase, result
+ subs = subs or {}
+
+ -- See if the passed key resolves to a valid phrase string
+ if type(self.phrases[key]) == 'string' then
+ phrase = self.phrases[key]
+ -- At this point we know whether the phrase does not exist for this key
+ else
+ if self.warnOnMissing then
+ print(('^3Warning: Missing phrase for key: "%s"'):format(key))
+ end
+ result = key
+ end
+
+ if type(phrase) == 'string' then
+ result = translateKey(phrase, subs)
+ end
+
+ return result
+end
+
+--- Check if a phrase key has already been defined within the Locale instance phrase maps.
+--- @return boolean
+function Locale:has(key)
+ return self.phrases[key] ~= nil
+end
+
+--- Will remove phrase keys from a Locale instance, using recursion/
+--- @param phraseTarget string | table
+--- @param prefix string
+function Locale:delete(phraseTarget, prefix)
+ -- If the target is a string, we know that this is the end
+ -- of nested table tree.
+ if type(phraseTarget) == 'string' then
+ self.phrases[phraseTarget] = nil
+ else
+ for key, phrase in pairs(phraseTarget) do
+ local prefixKey = prefix and prefix .. '.' .. key or key
+
+ if type(phrase) == 'table' then
+ self:delete(phrase, prefixKey)
+ else
+ self.phrases[prefixKey] = nil
+ end
+ end
+ end
+end
diff --git a/resources/lvc/UI/cl_ragemenu.lua b/resources/lvc/UI/cl_ragemenu.lua
new file mode 100644
index 000000000..2a3946852
--- /dev/null
+++ b/resources/lvc/UI/cl_ragemenu.lua
@@ -0,0 +1,654 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_ragemenu.lua
+PURPOSE: Handle RageUI
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+RMenu.Add('lvc', 'main', RageUI.CreateMenu(' ', Lang:t('menu.main'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'maintone', RageUI.CreateSubMenu(RMenu:Get('lvc', 'main'),' ', Lang:t('menu.siren'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'hudsettings', RageUI.CreateSubMenu(RMenu:Get('lvc', 'main'),' ', Lang:t('menu.hud'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'audiosettings', RageUI.CreateSubMenu(RMenu:Get('lvc', 'main'),' ', Lang:t('menu.audio'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'volumesettings', RageUI.CreateSubMenu(RMenu:Get('lvc', 'audiosettings'),' ', Lang:t('menu.audio'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'plugins', RageUI.CreateSubMenu(RMenu:Get('lvc', 'main'),' ', Lang:t('menu.plugins'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'saveload', RageUI.CreateSubMenu(RMenu:Get('lvc', 'main'),' ', Lang:t('menu.storage'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'copyprofile', RageUI.CreateSubMenu(RMenu:Get('lvc', 'saveload'),' ', Lang:t('menu.copy'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu.Add('lvc', 'info', RageUI.CreateSubMenu(RMenu:Get('lvc', 'main'),' ', Lang:t('menu.more_info'), 0, 0, "lvc", "lvc_v3_logo"))
+RMenu:Get('lvc', 'main'):SetTotalItemsPerPage(13)
+RMenu:Get('lvc', 'volumesettings'):SetTotalItemsPerPage(12)
+RMenu:Get('lvc', 'main'):DisplayGlare(false)
+RMenu:Get('lvc', 'maintone'):DisplayGlare(false)
+RMenu:Get('lvc', 'hudsettings'):DisplayGlare(false)
+RMenu:Get('lvc', 'audiosettings'):DisplayGlare(false)
+RMenu:Get('lvc', 'volumesettings'):DisplayGlare(false)
+RMenu:Get('lvc', 'plugins'):DisplayGlare(false)
+RMenu:Get('lvc', 'saveload'):DisplayGlare(false)
+RMenu:Get('lvc', 'copyprofile'):DisplayGlare(false)
+RMenu:Get('lvc', 'info'):DisplayGlare(false)
+
+
+--Strings for Save/Load confirmation, not ideal but it works.
+local ok_to_disable = true
+local confirm_s_msg
+local confirm_l_msg
+local confirm_fr_msg
+local confirm_s_desc
+local confirm_l_desc
+local confirm_fr_desc
+local confirm_c_msg = { }
+local confirm_c_desc = { }
+local profile_c_op = { }
+local profile_s_op = 75
+local profile_l_op = 75
+local sl_btn_debug_msg = ''
+
+local hazard_state = false
+local button_sfx_scheme_id = -1
+local profiles = { }
+local tone_table = { }
+local PMANU_POS, PMANU_ID, SMANU_POS, SMANU_ID, AUX_POS, AUX_ID
+
+local curr_version
+local repo_version
+local newer_version
+local version_description
+local version_formatted
+
+Keys.Register(open_menu_key, 'lvc', Lang:t('control.menu_desc'), function()
+ if not key_lock and player_is_emerg_driver and UpdateOnscreenKeyboard() ~= 0 then
+ if UTIL:GetVehicleProfileName() == 'DEFAULT' then
+ local veh_name = GetDisplayNameFromVehicleModel(GetEntityModel(veh))
+ sl_btn_debug_msg = Lang:t('menu.storage_default_profile_msg', {veh = veh_name})
+ else
+ sl_btn_debug_msg = ''
+ end
+ tone_table = UTIL:GetApprovedTonesTableNameAndID()
+ profiles = STORAGE:GetSavedProfiles()
+ RageUI.Visible(RMenu:Get('lvc', 'main'), not RageUI.Visible(RMenu:Get('lvc', 'main')))
+ end
+end)
+
+---------------------------------------------------------------------
+-- Triggered when vehicle changes (cl_lvc.lua)
+RegisterNetEvent('lvc:onVehicleChange')
+AddEventHandler('lvc:onVehicleChange', function()
+ CreateThread(function()
+ Wait(500)
+ button_sfx_scheme_id = UTIL:IndexOf(AUDIO.button_sfx_scheme_choices, AUDIO.button_sfx_scheme) or 1
+ end)
+end)
+
+--Trims front off tone-strings longer than 36 characters for front-end display
+local function TrimToneString(tone_string)
+ if #tone_string > 36 then
+ local trim_amount = #tone_string - 33
+ tone_string = string.format("...%s", string.sub(tone_string, trim_amount, 37))
+ end
+
+ return tone_string
+end
+-- Returns true if any menu is open
+function IsMenuOpen()
+ return RageUI.Visible(RMenu:Get('lvc', 'main')) or
+ RageUI.Visible(RMenu:Get('lvc', 'maintone')) or
+ RageUI.Visible(RMenu:Get('lvc', 'hudsettings')) or
+ RageUI.Visible(RMenu:Get('lvc', 'audiosettings')) or
+ RageUI.Visible(RMenu:Get('lvc', 'volumesettings')) or
+ RageUI.Visible(RMenu:Get('lvc', 'saveload')) or
+ RageUI.Visible(RMenu:Get('lvc', 'copyprofile')) or
+ RageUI.Visible(RMenu:Get('lvc', 'info')) or
+ RageUI.Visible(RMenu:Get('lvc', 'plugins')) or
+ IsPluginMenuOpen()
+end
+
+-- Handle user input to cancel confirmation message for SAVE/LOAD
+CreateThread(function()
+ while true do
+ while not RageUI.Settings.Controls.Back.Enabled do
+ for Index = 1, #RageUI.Settings.Controls.Back.Keys do
+ if IsDisabledControlJustPressed(RageUI.Settings.Controls.Back.Keys[Index][1], RageUI.Settings.Controls.Back.Keys[Index][2]) then
+ confirm_s_msg = nil
+ confirm_s_desc = nil
+ profile_s_op = 75
+ confirm_l_msg = nil
+ confirm_l_desc = nil
+ profile_l_op = 75
+ confirm_r_msg = nil
+ confirm_fr_msg = nil
+ for i, _ in ipairs(profiles) do
+ profile_c_op[i] = 75
+ confirm_c_msg[i] = nil
+ confirm_c_desc[i] = nil
+ end
+ Wait(10)
+ RageUI.Settings.Controls.Back.Enabled = true
+ break
+ end
+ end
+ Wait(0)
+ end
+ Wait(100)
+ end
+end)
+
+-- Handle Disabling Controls while menu open
+CreateThread(function()
+ Wait(1000)
+ while true do
+ while IsMenuOpen() do
+ DisableControlAction(0, 27, true)
+ DisableControlAction(0, 99, true)
+ DisableControlAction(0, 172, true)
+ DisableControlAction(0, 173, true)
+ DisableControlAction(0, 174, true)
+ DisableControlAction(0, 175, true)
+ Wait(0)
+ end
+ Wait(100)
+ end
+end)
+
+-- Close menu when player exits vehicle
+CreateThread(function()
+ while true do
+ if IsMenuOpen() then
+ if (not player_is_emerg_driver) then
+ RageUI.CloseAll()
+ end
+ end
+ Wait(500)
+ end
+end)
+
+-- Resource start version handling
+CreateThread(function()
+ Wait(500)
+ curr_version = STORAGE:GetCurrentVersion()
+ repo_version = STORAGE:GetRepoVersion()
+ newer_version = STORAGE:GetIsNewerVersion()
+ version_description = Lang:t('menu.latest_version_desc')
+ version_formatted = curr_version or Lang:t('info.unknown')
+
+ if newer_version == 'older' then
+ version_description, version_formatted = Lang:t('menu.old_version_desc'), '~o~~h~'..curr_version
+ elseif newer_version == 'newer' then
+ version_description = Lang:t('menu.experimental_version_desc')
+ elseif newer_version == 'unknown' then
+ version_description = Lang:t('menu.unknown_version_desc')
+ end
+end)
+
+CreateThread(function()
+ while true do
+ --Main Menu Visible
+ RageUI.IsVisible(RMenu:Get('lvc', 'main'), function()
+ RageUI.Separator(Lang:t('menu.siren_settings_seperator'))
+ RageUI.Button(Lang:t('menu.siren'), Lang:t('menu.siren_desc'), {RightLabel = '→→→'}, true, {
+ }, RMenu:Get('lvc', 'maintone'))
+
+
+ if custom_manual_tones_master_switch then
+ --PRIMARY MANUAL TONE List
+ --Get Current Tone ID and index ToneTable offset by 1 to correct airhorn missing
+ PMANU_POS = UTIL:GetTonePos('PMANU')
+ PMANU_ID = UTIL:GetToneID('PMANU')
+ if PMANU_POS ~= -1 then
+ RageUI.List(Lang:t('menu.primary_manu'), tone_table, PMANU_POS-1, Lang:t('menu.primary_manu_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ UTIL:SetToneByID('PMANU', Item.Value)
+ end,
+ onSelected = function()
+ proposed_name = HUD:KeyboardInput(Lang:t('menu.rename_tone', { tone_string = TrimToneString(SIRENS[PMANU_ID].String) }), SIRENS[PMANU_ID].Name, 15)
+ if proposed_name ~= nil then
+ UTIL:ChangeToneString(PMANU_POS, proposed_name)
+ tone_table = UTIL:GetApprovedTonesTableNameAndID()
+ end
+ end,
+ })
+ end
+
+ --SECONDARY MANUAL TONE List
+ --Get Current Tone ID and index ToneTable offset by 1 to correct airhorn missing
+ SMANU_POS = UTIL:GetTonePos('SMANU')
+ SMANU_ID = UTIL:GetToneID('SMANU')
+ if SMANU_POS ~= -1 then
+ RageUI.List(Lang:t('menu.secondary_manu'), tone_table, SMANU_POS-1, Lang:t('menu.secondary_manu_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ UTIL:SetToneByID('SMANU', Item.Value)
+ end,
+ onSelected = function()
+ proposed_name = HUD:KeyboardInput(Lang:t('menu.rename_tone', { tone_string = TrimToneString(SIRENS[SMANU_ID].String) }), SIRENS[SMANU_ID].Name, 15)
+ if proposed_name ~= nil then
+ UTIL:ChangeToneString(SMANU_POS, proposed_name)
+ tone_table = UTIL:GetApprovedTonesTableNameAndID()
+ end
+ end,
+ })
+ end
+ end
+
+ --AUXILARY MANUAL TONE List
+ --Get Current Tone ID and index ToneTable offset by 1 to correct airhorn missing
+ if custom_aux_tones_master_switch then
+ --AST List
+ AUX_POS = UTIL:GetTonePos('AUX')
+ AUX_ID = UTIL:GetToneID('AUX')
+ if AUX_POS ~= -1 then
+ RageUI.List(Lang:t('menu.aux_tone'), tone_table, AUX_POS-1, Lang:t('menu.aux_tone_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ UTIL:SetToneByID('AUX', Item.Value)
+ end,
+ onSelected = function()
+ proposed_name = HUD:KeyboardInput(Lang:t('menu.rename_tone', { tone_string = TrimToneString(SIRENS[AUX_ID].String) }), SIRENS[AUX_ID].Name, 15)
+ if proposed_name ~= nil then
+ UTIL:ChangeToneString(AUX_POS, proposed_name)
+ tone_table = UTIL:GetApprovedTonesTableNameAndID()
+ end
+ end,
+ })
+ end
+ end
+
+ --SIREN PARK KILL
+ if park_kill_masterswitch then
+ RageUI.Checkbox(Lang:t('menu.siren_park_kill'), Lang:t('menu.siren_park_kill_desc'), park_kill, {}, {
+ onSelected = function(Index)
+ park_kill = Index
+ end
+ })
+ end
+ --MAIN MENU TO SUBMENU BUTTONS
+ RageUI.Separator(Lang:t('menu.other_settings_seperator'))
+ RageUI.Button(Lang:t('menu.hud'), Lang:t('menu.hud_desc'), {RightLabel = '→→→'}, true, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'hudsettings'))
+ RageUI.Button(Lang:t('menu.audio'), Lang:t('menu.audio_desc'), {RightLabel = '→→→'}, true, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'audiosettings'))
+ RageUI.Separator(Lang:t('menu.misc_settings_seperator'))
+ if plugins_installed then
+ RageUI.Button(Lang:t('menu.plugins'), Lang:t('menu.plugins_desc'), {RightLabel = '→→→'}, true, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'plugins'))
+ end
+ RageUI.Button(Lang:t('menu.storage'), Lang:t('menu.storage_desc'), {RightLabel = '→→→'}, true, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'saveload'))
+ RageUI.Button(Lang:t('menu.more_info'), Lang:t('menu.more_info_desc'), {RightLabel = '→→→'}, true, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'info'))
+ end)
+ ---------------------------------------------------------------------
+ ----------------------------MAIN TONE MENU---------------------------
+ ---------------------------------------------------------------------
+ RageUI.IsVisible(RMenu:Get('lvc', 'maintone'), function()
+ local approved_tones = UTIL:GetApprovedTonesTable()
+ if airhorn_interrupt_masterswitch then
+ RageUI.Checkbox(Lang:t('menu.airhorn_interrupt'), Lang:t('menu.airhorn_interrupt_desc'), tone_airhorn_intrp, {}, {
+ onChecked = function()
+ tone_airhorn_intrp = true
+ end,
+ onUnChecked = function()
+ tone_airhorn_intrp = false
+ end,
+ })
+ end
+ if reset_to_standby_masterswitch then
+ RageUI.Checkbox(Lang:t('menu.reset_standby'), Lang:t('menu.reset_standby_desc'), tone_main_reset_standby, {}, {
+ onChecked = function()
+ tone_main_reset_standby = true
+ end,
+ onUnChecked = function()
+ tone_main_reset_standby = false
+ end,
+ })
+ end
+
+ if main_siren_settings_masterswitch then
+ RageUI.Separator(Lang:t('menu.tone_options_seperator'))
+ for i, tone in pairs(approved_tones) do
+ if i ~= 1 then
+ RageUI.List(SIRENS[tone].Name, { Lang:t('menu.cycle_button'), Lang:t('menu.cycle_only'), Lang:t('menu.button_only'), Lang:t('menu.disabled') }, UTIL:GetToneOption(tone), '~g~Cycle:~s~ play as you cycle through sirens.\n~g~Button:~s~ play when registered key is pressed.\n~b~Select to rename siren tones.', {}, true, {
+ onListChange = function(Index, Item)
+ if UTIL:IsOkayToDisable() or Index < 3 then
+ UTIL:SetToneOption(tone, Index)
+ else
+ HUD:ShowNotification(Lang:t('menu.unable_to_disable'), true)
+ end
+ end,
+ onSelected = function()
+ proposed_name = HUD:KeyboardInput(Lang:t('menu.rename_tone', { tone_string = TrimToneString(SIRENS[tone].String) }), SIRENS[tone].Name, 15)
+ if proposed_name ~= nil then
+ UTIL:ChangeToneString(tone, proposed_name)
+ tone_table = UTIL:GetApprovedTonesTableNameAndID()
+ end
+ end,
+ })
+ end
+ end
+ end
+ end)
+ ---------------------------------------------------------------------
+ -------------------------OTHER SETTINGS MENU-------------------------
+ ---------------------------------------------------------------------
+ --HUD SETTINGS
+ RageUI.IsVisible(RMenu:Get('lvc', 'hudsettings'), function()
+ local hud_state = HUD:GetHudState()
+ local hud_backlight_mode = HUD:GetHudBacklightMode()
+ RageUI.Checkbox(Lang:t('menu.enabled'), Lang:t('menu.hud_enabled_desc'), hud_state, {}, {
+ onChecked = function()
+ HUD:SetHudState(true)
+ end,
+ onUnChecked = function()
+ HUD:SetHudState(false)
+ end,
+ })
+ RageUI.Button(Lang:t('menu.hud_move_mode'), Lang:t('menu.hud_move_mode_desc'), {}, hud_state, {
+ onSelected = function()
+ HUD:SetMoveMode(true, true)
+ end,
+ });
+ RageUI.Slider(Lang:t('menu.hud_scale'), 4*HUD:GetHudScale(), 6, 0.2, Lang:t('menu.hud_scale_desc'), false, {}, hud_state, {
+ onSliderChange = function(Index)
+ HUD:SetHudScale(Index/4)
+ end,
+ });
+ RageUI.List(Lang:t('menu.hud_backlight'), {Lang:t('menu.hud_backlight_auto'), Lang:t('menu.hud_backlight_off'), Lang:t('menu.hud_backlight_on') }, hud_backlight_mode, Lang:t('menu.hud_backlight_desc'), {}, hud_state, {
+ onListChange = function(Index, Item)
+ hud_backlight_mode = Index
+ HUD:SetHudBacklightMode(hud_backlight_mode)
+ end,
+ })
+ RageUI.Button(Lang:t('menu.hud_reset'), Lang:t('menu.hud_reset_desc'), {}, hud_state, {
+ onSelected = function()
+ HUD:ResetPosition()
+ HUD:SetHudState(false)
+ HUD:SetHudState(true)
+ end,
+ });
+ end)
+ --AUDIO SETTINGS MENU
+ RageUI.IsVisible(RMenu:Get('lvc', 'audiosettings'), function()
+ RageUI.Checkbox(Lang:t('menu.audio_radio'), Lang:t('menu.audio_radio_desc'), AUDIO.radio_masterswitch, {}, {
+ onChecked = function()
+ AUDIO.radio_masterswitch = true
+ end,
+ onUnChecked = function()
+ AUDIO.radio_masterswitch = false
+ end,
+ })
+ RageUI.Separator(Lang:t('menu.audio_sfx_separator'))
+ RageUI.List(Lang:t('menu.audio_scheme'), AUDIO.button_sfx_scheme_choices, button_sfx_scheme_id, Lang:t('menu.audio_scheme_desc'), {}, true, {
+ onListChange = function(Index, Item)
+ button_sfx_scheme_id = Index
+ AUDIO.button_sfx_scheme = AUDIO.button_sfx_scheme_choices[button_sfx_scheme_id]
+ end,
+ })
+ RageUI.Checkbox(Lang:t('menu.audio_manu_sfx'), Lang:t('menu.audio_manu_sfx_desc'), AUDIO.manu_button_SFX, {}, {
+ onChecked = function()
+ AUDIO.manu_button_SFX = true
+ end,
+ onUnChecked = function()
+ AUDIO.manu_button_SFX = false
+ end,
+ })
+ RageUI.Checkbox(Lang:t('menu.audio_horn_sfx'), Lang:t('menu.audio_horn_sfx_desc'), AUDIO.airhorn_button_SFX, {}, {
+ onChecked = function()
+ AUDIO.airhorn_button_SFX = true
+ end,
+ onUnChecked = function()
+ AUDIO.airhorn_button_SFX = false
+ end,
+ })
+ RageUI.List(Lang:t('menu.audio_activity_reminder'), {'Off', '1/2', '1', '2', '5', '10'}, AUDIO:GetActivityReminderIndex(), Lang:t('menu.audio_activity_reminder_desc', { timer = ("%1.0f"):format(AUDIO:GetActivityTimer() / 1000) or 0}), {}, true, {
+ onListChange = function(Index, Item)
+ AUDIO:SetActivityReminderIndex(Index)
+ AUDIO:ResetActivityTimer()
+ end,
+ })
+ RageUI.Button(Lang:t('menu.audio_volumes'), Lang:t('menu.audio_volumes_desc'), {RightLabel = '→→→'}, true, {
+ onSelected = function()
+ end,
+ }, RMenu:Get('lvc', 'volumesettings'))
+ end)
+ --VOLUME SETTINGS MENU
+ RageUI.IsVisible(RMenu:Get('lvc', 'volumesettings'), function()
+ RageUI.Slider(Lang:t('menu.on_volume'), (AUDIO.on_volume*100), 100, 2, Lang:t('menu.on_volume_desc'), true, {MuteOnSelected = true}, true, {
+ onSliderChange = function(Index)
+ AUDIO.on_volume = (Index / 100)
+ end,
+ onSelected = function(Index, Item)
+ AUDIO:Play('On', AUDIO.on_volume)
+ end,
+ })
+ RageUI.Slider(Lang:t('menu.off_volume'), (AUDIO.off_volume*100), 100, 2, Lang:t('menu.off_volume_desc'), true, {MuteOnSelected = true}, true, {
+ onSliderChange = function(Index)
+ AUDIO.off_volume = (Index/100)
+ end,
+ onSelected = function(Index, Item)
+ AUDIO:Play('Off', AUDIO.off_volume)
+ end,
+ })
+ RageUI.Slider(Lang:t('menu.upgrade_volume'), (AUDIO.upgrade_volume*100), 100, 2, Lang:t('menu.upgrade_volume_desc'), true, {MuteOnSelected = true}, true, {
+ onSliderChange = function(Index)
+ AUDIO.upgrade_volume = (Index/100)
+ end,
+ onSelected = function(Index, Item)
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ end,
+ })
+ RageUI.Slider(Lang:t('menu.downgrade_volume'), (AUDIO.downgrade_volume*100), 100, 2, Lang:t('menu.downgrade_volume_desc'), true, {MuteOnSelected = true}, true, {
+ onSliderChange = function(Index)
+ AUDIO.downgrade_volume = (Index/100)
+ end,
+ onSelected = function(Index, Item)
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ end,
+ })
+ RageUI.Slider(Lang:t('menu.reminder_volume'), (AUDIO.activity_reminder_volume*500), 100, 2, Lang:t('menu.reminder_volume_desc'), true, {MuteOnSelected = true}, true, {
+ onSliderChange = function(Index)
+ AUDIO.activity_reminder_volume = (Index/500)
+ end,
+ onSelected = function(Index, Item)
+ AUDIO:Play('Reminder', AUDIO.activity_reminder_volume)
+ end,
+ })
+ RageUI.Slider(Lang:t('menu.hazards_volume'), (AUDIO.hazards_volume*100), 100, 2, Lang:t('menu.hazards_volume_desc'), true, {}, true, {
+ onSliderChange = function(Index)
+ AUDIO.hazards_volume = (Index/100)
+ end,
+ onSelected = function(Index, Item)
+ if hazard_state then
+ AUDIO:Play('Hazards_On', AUDIO.hazards_volume, true)
+ else
+ AUDIO:Play('Hazards_Off', AUDIO.hazards_volume, true)
+ end
+ hazard_state = not hazard_state
+ end,
+ })
+ RageUI.Slider(Lang:t('menu.lock_volume'), (AUDIO.lock_volume*100), 100, 2, Lang:t('menu.lock_volume_desc'), true, {}, true, {
+ onSliderChange = function(Index)
+ AUDIO.lock_volume = (Index/100)
+ end,
+ onSelected = function(Index, Item)
+ AUDIO:Play('Key_Lock', AUDIO.lock_volume, true)
+ end,
+ })
+ RageUI.Slider(Lang:t('menu.lock_reminder_volume'), (AUDIO.lock_reminder_volume*100), 100, 2, Lang:t('menu.lock_reminder_volume_desc'), true, {}, true, {
+ onSliderChange = function(Index)
+ AUDIO.lock_reminder_volume = (Index/100)
+ end,
+ onSelected = function(Index, Item)
+ AUDIO:Play('Locked_Press', AUDIO.lock_reminder_volume, true)
+ end,
+ })
+ end)
+ ---------------------------------------------------------------------
+ ----------------------------SAVE LOAD MENU---------------------------
+ ---------------------------------------------------------------------
+ RageUI.IsVisible(RMenu:Get('lvc', 'saveload'), function()
+ RageUI.Button(Lang:t('menu.save'), confirm_s_desc or Lang:t('menu.save_desc') .. ' ' .. sl_btn_debug_msg, {RightLabel = confirm_s_msg or '('.. UTIL:GetVehicleProfileName() .. ')', RightLabelOpacity = profile_s_op}, true, {
+ onSelected = function()
+ if confirm_s_msg == Lang:t('menu.confirm') then
+ STORAGE:SaveSettings()
+ HUD:ShowNotification(Lang:t('menu.save_success'), true)
+ confirm_s_msg = nil
+ confirm_s_desc = nil
+ profile_s_op = 75
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ profile_s_op = 255
+ confirm_s_msg = Lang:t('menu.confirm')
+ confirm_s_desc = Lang:t('menu.save_override_desc', { profile = UTIL:GetVehicleProfileName() })
+ confirm_l_msg = nil
+ profile_l_op = 75
+ confirm_r_msg = nil
+ confirm_fr_msg = nil
+ end
+ end,
+ })
+ RageUI.Button(Lang:t('menu.load'), confirm_l_desc or Lang:t('menu.load_desc') .. ' ' .. sl_btn_debug_msg, {RightLabel = confirm_l_msg or '('.. UTIL:GetVehicleProfileName() .. ')', RightLabelOpacity = profile_l_op}, true, {
+ onSelected = function()
+ if confirm_l_msg == Lang:t('menu.confirm') then
+ STORAGE:LoadSettings()
+ tone_table = UTIL:GetApprovedTonesTableNameAndID()
+ HUD:ShowNotification(Lang:t('menu.load_success'), true)
+ confirm_l_msg = nil
+ confirm_l_desc = nil
+ profile_l_op = 75
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ profile_l_op = 255
+ confirm_l_msg = Lang:t('menu.confirm')
+ confirm_l_desc = Lang:t('menu.load_override')
+ confirm_s_msg = nil
+ profile_s_op = 75
+ confirm_r_msg = nil
+ confirm_fr_msg = nil
+ end
+ end,
+ })
+ RageUI.Separator(Lang:t('menu.advanced_separator'))
+ RageUI.Button(Lang:t('menu.copy'), Lang:t('menu.copy_desc'), {RightLabel = '→→→'}, #profiles > 0, {}, RMenu:Get('lvc', 'copyprofile'))
+ RageUI.Button(Lang:t('menu.reset'), Lang:t('menu.reset_desc'), {RightLabel = confirm_r_msg}, true, {
+ onSelected = function()
+ if confirm_r_msg == Lang:t('menu.confirm') then
+ STORAGE:ResetSettings()
+ HUD:ShowNotification(Lang:t('menu.reset_success'), true)
+ confirm_r_msg = nil
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ confirm_r_msg = Lang:t('menu.confirm')
+ confirm_l_msg = nil
+ profile_l_op = 75
+ confirm_s_msg = nil
+ profile_s_op = 75
+ confirm_fr_msg = nil
+ end
+ end,
+ })
+ RageUI.Button(Lang:t('menu.factory_reset'), Lang:t('menu.factory_reset_desc'), {RightLabel = confirm_fr_msg}, true, {
+ onSelected = function()
+ if confirm_fr_msg == Lang:t('menu.confirm') then
+ RageUI.CloseAll()
+ Wait(100)
+ local choice = HUD:FrontEndAlert(Lang:t('warning.warning'), Lang:t('warning.factory_reset'), Lang:t('warning.facory_reset_options'))
+ if choice then
+ STORAGE:FactoryReset()
+ else
+ RageUI.Visible(RMenu:Get('lvc', 'saveload'), true)
+ end
+ confirm_fr_msg = nil
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ confirm_fr_msg = Lang:t('menu.confirm')
+ confirm_l_msg = nil
+ profile_l_op = 75
+ confirm_s_msg = nil
+ profile_s_op = 75
+ confirm_r_msg = nil
+ end
+ end,
+ })
+ end)
+
+ --Copy Profiles Menu
+ RageUI.IsVisible(RMenu:Get('lvc', 'copyprofile'), function()
+ for i, profile_name in ipairs(profiles) do
+ profile_c_op[i] = profile_c_op[i] or 75
+ RageUI.Button(profile_name, confirm_c_desc[i] or Lang:t('menu.load_copy_desc', { profile = profile_name }), {RightLabel = confirm_c_msg[i] or Lang:t('menu.load_copy'), RightLabelOpacity = profile_c_op[i]}, true, {
+ onSelected = function()
+ if confirm_c_msg[i] == Lang:t('menu.confirm') then
+ STORAGE:LoadSettings(profile_name)
+ tone_table = UTIL:GetApprovedTonesTableNameAndID()
+ HUD:ShowNotification(Lang:t('menu.load_success'), true)
+ confirm_c_msg[i] = nil
+ confirm_c_desc[i] = nil
+ profile_c_op[i] = 75
+ else
+ RageUI.Settings.Controls.Back.Enabled = false
+ for j, _ in ipairs(profiles) do
+ if i ~= j then
+ profile_c_op[j] = 75
+ confirm_c_msg[j] = nil
+ confirm_c_desc[j] = nil
+ end
+ end
+ profile_c_op[i] = 255
+ confirm_c_msg[i] = Lang:t('menu.confirm')
+ confirm_c_desc[i] = Lang:t('menu.load_override')
+ end
+ end,
+ })
+ end
+ end)
+ ---------------------------------------------------------------------
+ ----------------------------MORE INFO MENU---------------------------
+ ---------------------------------------------------------------------
+ RageUI.IsVisible(RMenu:Get('lvc', 'info'), function()
+ RageUI.Button(Lang:t('menu.current_version'), Lang:t('menu.version_string', { ver = version_formatted, ver_desc = version_description }), { RightLabel = version_formatted }, true, {
+ onSelected = function()
+ end,
+ });
+ if newer_version == 'older' then
+ RageUI.Button(Lang:t('menu.latest_version'), Lang:t('menu.latest_version_desc', { ver = repo_version }), {RightLabel = repo_version or Lang:t('info.unknown')}, true, {
+ onSelected = function()
+ end,
+ });
+ end
+ RageUI.Button(Lang:t('menu.about_credits'), Lang:t('menu.about_credits_desc'), {}, true, {
+ onSelected = function()
+ end,
+ });
+ RageUI.Button('Website', 'Learn more about Luxart Engineering and it\'s products at ~b~https://www.luxartengineering.com~w~!', {}, true, {
+ onSelected = function()
+ end,
+ });
+ end)
+ Wait(0)
+ end
+end)
\ No newline at end of file
diff --git a/resources/lvc/UI/html/index.html b/resources/lvc/UI/html/index.html
new file mode 100644
index 000000000..ed49da267
--- /dev/null
+++ b/resources/lvc/UI/html/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/lvc/UI/html/lvc.js b/resources/lvc/UI/html/lvc.js
new file mode 100644
index 000000000..5d76d0050
--- /dev/null
+++ b/resources/lvc/UI/html/lvc.js
@@ -0,0 +1,219 @@
+/*
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Made by TrevorBarns
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+*/
+
+var time_folder = "day/";
+var ta_pattern = "ta/pattern_3/";
+var audioPlayer = null;
+var soundID = 0;
+var scale = 0.6;
+var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
+
+const elements =
+{
+ sirenbox: document.getElementById("sirenbox"),
+ lswitch: document.getElementById("slide"),
+ siren: document.getElementById("siren"),
+ horn: document.getElementById("horn"),
+ tkd: document.getElementById("tkd"),
+ lock: document.getElementById("lock"),
+ ta: document.getElementById("ta"),
+}
+
+const backup =
+{
+ left: elements.sirenbox.style.left,
+ top: elements.sirenbox.style.top,
+}
+
+
+window.addEventListener('message', function(event) {
+ var type = event.data._type;
+ if (type == "audio") {
+ playSound(event.data.file, event.data.volume);
+ }else if ( type == "setResourceName" ) {
+ resourceName = event.data.name
+ }else if (type == "hud:setItemState") {
+
+ var item = event.data.item;
+ var state = event.data.state;
+
+ switch ( item ){
+ case "hud":
+ if ( state == true ) {
+ elements.sirenbox.style.display = "inline";
+ }else{
+ elements.sirenbox.style.display = "none";
+ }
+ break;
+ case "switch":
+ if ( state == true ) {
+ elements.lswitch.src= "../textures/"+ time_folder + "slide_on.png";
+ }else{
+ elements.lswitch.src= "../textures/"+ time_folder + "slide_off.png";
+ }
+ break;
+ case "siren":
+ if ( state == true ) {
+ elements.siren.src= "../textures/" + time_folder + "siren_on.png";
+ }else{
+ elements.siren.src= "../textures/" + time_folder + "siren_off.png";
+ }
+ break;
+ case "horn":
+ if ( state == true ) {
+ elements.horn.src= "../textures/" + time_folder + "horn_on.png";
+ }else{
+ elements.horn.src= "../textures/" + time_folder + "horn_off.png";
+ }
+ break;
+ case "tkd":
+ if ( state == true ) {
+ elements.tkd.src= "../textures/" + time_folder + "tkd_on.png";
+ }else{
+ elements.tkd.src= "../textures/" + time_folder + "tkd_off.png";
+ }
+ break;
+ case "lock":
+ if ( state == true ) {
+ elements.lock.src= "../textures/" + time_folder + "lock_on.png";
+ }else{
+ elements.lock.src= "../textures/" + time_folder + "lock_off.png";
+ }
+ break;
+ case "ta":
+ if ( state == 1 ) {
+ elements.ta.src = "../textures/" + ta_pattern + "ta_left.gif";
+ }else if ( state == 2 ){
+ elements.ta.src = "../textures/" + ta_pattern + "ta_right.gif";
+ }else if ( state == 3 ){
+ elements.ta.src = "../textures/" + ta_pattern + "ta_center.gif";
+ }else if ( state == 0 ){
+ elements.ta.src = "../textures/" + time_folder + "ta_off.gif";
+ }
+ break;
+ case "ta_pattern":
+ ta_pattern = "ta/pattern_" + state + "/"
+ break;
+ case "time":
+ time_folder = state + "/"
+ break;
+ default:
+ break;
+ }
+ }else if ( type == "hud:setHudScale" ){
+ scale = event.data.scale
+ elements.sirenbox.style.transform = "scale(" + scale + " )";
+ }else if ( type == "hud:getHudScale" ){
+ sendData( "hud:sendHudScale", scale = scale );
+ }else if ( type == "hud:setHudPosition" ){
+ try{
+ elements.sirenbox.style.left = event.data.pos.left;
+ elements.sirenbox.style.top = event.data.pos.top;
+ }catch(error)
+ {}
+ }else if ( type == "hud:resetPosition" ){
+ elements.sirenbox.style.left = backup.left;
+ elements.sirenbox.style.top = backup.top;
+ }
+});
+
+
+// Exit HUD Move Mode
+$( document ).keyup( function( event ) {
+ // Esc Backspace Space
+ if ( event.keyCode == 27 || event.keyCode == 9 || event.keyCode == 32 )
+ {
+ sendData( "hud:setHudPositon", data = { left: elements.sirenbox.style.left, top: elements.sirenbox.style.top } );
+ sendData( "hud:setMoveState", state = false );
+ }
+} );
+
+$( document ).contextmenu( function() {
+ sendData( "hud:setHudPositon", data = { left: elements.sirenbox.style.left, top: elements.sirenbox.style.top } );
+ sendData( "hud:setMoveState", state = false );
+} );
+
+
+// This function is used to send data back through to the LUA side
+function sendData( name, data ) {
+ $.post( "https://lvc/" + name, JSON.stringify( data ), function( datab ) {
+ if ( datab != "ok" ) {
+ console.log( datab );
+ }
+ } );
+}
+
+
+//Credit to xotikorukx playSound Fn.
+function playSound(file, volume){
+ if (audioPlayer != null) {
+ audioPlayer.pause();
+ }
+
+ soundID++;
+
+ audioPlayer = new Audio("../sounds/" + file + ".ogg");
+ audioPlayer.volume = volume;
+ var didPlayPromise = audioPlayer.play();
+
+ if (didPlayPromise === undefined) {
+ audioPlayer = null; //The audio player crashed. Reset it so it doesn't crash the next sound.
+ } else {
+ didPlayPromise.then(_ => { //This does not execute until the audio is playing.
+ }).catch(error => {
+ audioPlayer = null; //The audio player crashed. Reset it so it doesn't crash the next sound.
+ })
+ }
+}
+
+
+// Drag to move functions below.
+elements.sirenbox.onmousedown = dragMouseDown;
+
+function dragMouseDown(e) {
+ e = e || window.event;
+ e.preventDefault();
+ // get the mouse cursor position at startup:
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ document.onmouseup = closeDragElement;
+ // call a function whenever the cursor moves:
+ document.onmousemove = elementDrag;
+}
+
+function elementDrag(e) {
+ e = e || window.event;
+ e.preventDefault();
+ // calculate the new cursor position:
+ pos1 = pos3 - e.clientX;
+ pos2 = pos4 - e.clientY;
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ // set the element's new position:
+ elements.sirenbox.style.top = (elements.sirenbox.offsetTop - pos2) + "px";
+ elements.sirenbox.style.left = (elements.sirenbox.offsetLeft - pos1) + "px";
+}
+
+function closeDragElement() {
+ // stop moving when mouse button is released:
+ document.onmouseup = null;
+ document.onmousemove = null;
+}
diff --git a/resources/lvc/UI/html/style.css b/resources/lvc/UI/html/style.css
new file mode 100644
index 000000000..1a82fd239
--- /dev/null
+++ b/resources/lvc/UI/html/style.css
@@ -0,0 +1,38 @@
+body {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+.slide {
+ padding-top: 52px;
+ margin-left: 6px;
+ z-index: 950;
+}
+
+.button {
+ margin-right: -3px;
+}
+
+#buttonbox {
+ display: grid;
+ grid-template-areas: 'siren horn tkd lock';
+ position: absolute;
+ float: right;
+ right: 17px;
+ top: 24px;
+ z-index: 900;
+}
+
+#sirenbox {
+ display: none;
+ position: absolute;
+ overflow: hidden;
+ width: 602px;
+ height: 161px;
+ left: 0%;
+ top: 68%;
+ transform-origin: left top;
+ transform: scale(0.6);
+ background-image: url("../textures/background.png");
+}
\ No newline at end of file
diff --git a/resources/lvc/UI/locale/de.lua b/resources/lvc/UI/locale/de.lua
new file mode 100644
index 000000000..9c15fc1ea
--- /dev/null
+++ b/resources/lvc/UI/locale/de.lua
@@ -0,0 +1,285 @@
+local Translations = {
+ warning = {
+ warning = 'Warnung',
+ -- WRONG BRANCH NOTIFIER --
+ wrong_branch_warning = '~b~LVC~w~: ~o~Warnung~w~: Dies ist der Entwicklungs-Branch (master)',
+ wrong_branch_info = '~y~DIESE VERSION IST IN ENTWICKLUNG UND WIRD NICHT\nFÜR DEN PRODUKTIONSGEBRAUCH EMPFOHLEN. WENN DIES EIN FEHLER WAR, DANN DOWNLOAD DIE\nNEUESTEN STABLE-RELEASE UNTER:\n~g~github.com/TrevorBarns/luxart-vehicle-control~p~~h~/releases~h~',
+ wrong_branch_mute = '~b~ZUM STUMMSCHALTEN~w~: Setze die CONVAR "~o~experimental~w~" auf "~o~true~w~" in der fxmanifest.',
+ profile_missing = '^3LVC(%{ver}) WARNUNG: "DEFAULT" Tabelle fehlt aus der %{tbl} Tabelle. Es wird eine leere Tabelle für %{model} verwendet.',
+ -- SIREN CONTROLLER --
+ too_few_tone_frontend = '~b~LVC ~y~Warnung %{code}:~s~ Zu wenig Sirenen zugewiesen.',
+ too_few_tone_console = '^3LVC(%{ver}) Warnung %{code}: Zu wenig Sirenen zugewiesen. Mindestens %{req_tone_count} Töne erforderlich. (UTIL:SetToneByPos(%{tone_string}, %{pos})',
+ tone_position_nil_frontend = '~b~LVC ~y~Warnung %{code}:~s~ Es wurde versucht einen Ton einzustellen, wobei kein approved_tones ausfindig gemacht werden konnte. Siehe Konsole.',
+ tone_position_nil_console = '^3LVC(%{ver}) Warnung %{code}: Es wurde versucht den Ton "%{tone_string}" einzustellen, aber es konnte die Tabelle approved_tones nicht ausfindig gemacht werden. (UTIL:SetToneByPos(%{tone_string}, %{pos}). Versuchen Sie, die Einstelluingen auf die Werkseinstellungen zurückzusetzen, da dies nach einer Änderung der Sirenentonzuordnung auftreten kann.',
+ tone_id_nil_frontend = '~b~LVC ~y~Warnung %{code}:~s~ Es wurde versucht einen Ton einzustellen, welcher nicht in approved_tones gefunden werden konnte. Siehe Konsole.',
+ tone_id_nil_console = '^3LVC(%{ver}) Warnung %{code}: Es wurde versucht den Ton %{tone_string} einzustellen, aber es konnte die Pos %{tone_id} in der approved_tones nicht gefunden werden. (UTIL:SetToneByPos(%{tone_string}, %{pos}). Versuchen Sie, die Einstelluingen auf die Werkseinstellungen zurückzusetzen, da dies nach einer Änderung der Sirenentonzuordnung auftreten kann.',
+ factory_reset = 'Sind Sie sicher, dass Sie alle gespeicherten LVC-Daten löschen und auf die Werkseinstellungen zurücksetzen möchten?',
+ facory_reset_options= '~g~Nein: Escape \t ~r~Ja: Enter',
+ },
+ error = {
+ -- CONFIG ERRORS --
+ missing_community_id_frontend = '~b~~h~LVC~h~ ~r~~h~KONFIGURATIONSFEHLER~h~~s~: COMMUNITY-ID FEHLT. SIEHE LOGS. SERVER-ENTWICKLER KONTAKTIEREN.',
+ missing_community_id_console = '^KONFIGURATIONSFEHLER: COMMUNITY ID NICHT GESETZT, DIES IST ERFORDERLICH, UM KONFLIKTE FÜR SPIELER ZU VERMEIDEN, DIE AUF MEHREREN SERVERN MIT LVC SPIELEN. BITTE SETZEN SIE DIES IN DER SETTINGS.LUA.',
+ invalid_resource_name_frontend = '~b~~h~LVC~h~ ~r~~h~KONFIGURATIONSFEHLER~h~~s~: UNGÜLTIGER RESSOURCENNAME. SIEHE LOGS. SERVER-ENTWICKLER KONTAKTIEREN.',
+ invalid_resource_name_console = '^KONFIGURATIONSFEHLER: UNGÜLTIGER RESSOURCENNAME. BITTE ÜBERPRÜFEN SIE, OB DER NAME DES RESSOURCENORDNERS "^3lvc^1" (CASE-SENSITIVE) LAUTET. DIES IST FÜR DIE ORDNUNGSGEMÄSSE SPEICHER-/LADEFUNKTION ERFORDERLICH. BITTE UMBENENNEN, AKTUALISIEREN UND SICHERSTELLEN.',
+ resource_conflict_frontend = '~b~~h~LVC~h~ ~r~~h~KONFLIKTFEHLER~h~~s~: RESSOURCENKONFLIKT. SIEHE KONSOLE.',
+ resource_conflict_console = '^1LVC ERROR: "lux_vehcontrol" LÄUFT BEREITS UND STEHT IM KONFLIKT MIT LVC. BITTE STOPPEN SIE "lux_vehcontrol" UND STARTEN SIE LVC NEU.',
+ -- FRONTEND ERRORS --
+ reg_keymap_nil_1 = '~b~~h~LVC~h~ ~r~~h~ERROR 2~h~~s~: Nullwert gefangen.\nDetails: (%{i}, %{proposed_tone}, %{profile_name})',
+ reg_keymap_nil_2 = '~b~~h~LVC~h~ ~r~~h~ERROR 2~h~~s~: Versuchen Sie, das Fahrzeug zu wechseln und wieder zurückzuschalten ODER die Profileinstellungen zu laden (falls vorhanden).',
+ profile_nil_table_frontend = '~b~~h~LVC~h~ ~r~ERROR: %{tbl} hat versucht, das Profil aus der Tabelle "nil" abzurufen. Siehe Konsole.',
+ profile_nil_table_console = '^1LVC(%{ver}) ERROR: %{tbl} versucht, das Profil aus der Tabelle "nil" zu holen. Dies wird normalerweise durch ein ungültiges Zeichen oder eine fehlende { }-Klammer in der SIRENS.lua verursacht. (https://git.io/JDVhK)',
+ },
+ info = {
+ locked = 'Sirenensteuergerät: ~r~Gesperrt',
+ unlocked = 'Sirenensteuergerät: ~g~Ungesperrt',
+ debug_mode_frontend = '~y~~h~Info:~h~ ~s~Debug-Modus eingestellt auf %{state}. Siehe Konsole.',
+ debug_mode_console = '^3LVC Info: Debug-Modus vorübergehend auf %{state} gesetzt. Debug_mode wird nach einem Neustart der Ressourcen zurückgesetzt, sofern nicht in der fxmanifest festgelegt. Stellen Sie sicher, dass Sie "refresh" ausführen, um die Änderungen von fxmanifest zu sehen.',
+ profile_found = '^4LVC(%{ver}) ^5%{tbl}: ^7Profil %{profile} gefunden für %{model}.',
+ profile_default_console = '^4LVC(%{ver}) ^5%{tbl}: ^7Verwendung des Standardprofils für %{model}.',
+ profile_default_frontend = '~b~LVC~s~: Verwendung des ~b~STANDARD~s~ Profil für \'~o~ %{model} ~s~\'.',
+ extra_on = '^4LVC: ^7%{extra} einschalten',
+ extra_off = '^4LVC: ^7%{extra} ausschalten',
+ unknown = 'unbekannt',
+ unable_to_disable = '~y~~h~Info:~h~ ~s~Luxart Vehicle Control\nAktion verboten, kann nicht alle Sirenen deaktivieren.',
+ factory_reset_success_console = 'Erfolg: Alle gespeicherten Daten wurden gelöscht.',
+ factory_reset_success_frontend = '~g~Erfolg~s~: Sie haben alle gespeicherten Daten gelöscht und LVC zurückgesetzt.',
+ },
+ control = {
+ siren_control_desc = 'LVC Sirene: %{ord_num}',
+ lock_desc = 'LVC: Sperren der Kontrollen',
+ menu_desc = 'LVC: Menü öffnen',
+ },
+ command = {
+ lock_command = 'lvclock',
+ lock_desc = 'Umschalten der Luxart Vehicle Kontroll-Tastenbelegung.',
+ debug_command = 'lvcdebug',
+
+ },
+ menu = {
+ -- MENU SUBTITLES --
+ main = 'Hauptmenü',
+ siren = 'Hauptmenü der Sireneneinstellungen',
+ siren_desc = 'Ändern, welcher/wie jeder verfügbare Primärton verwendet wird.',
+ hud = 'HUD-Einstellungen',
+ hud_desc = 'HUD-Einstellungsmenü öffnen.',
+ audio = 'Audio-Einstellungen',
+ audio_desc = 'Audio-Einstellungsmenü öffnen.',
+ plugins = 'Plugins',
+ plugins_desc = 'Plugins-Einstellungsmenü öffnen.',
+ storage = 'Speicherverwaltung',
+ storage_desc = 'Fahrzeugprofile speichern / laden.',
+ copy = 'Profileinstellungen kopieren',
+ more_info = 'Mehr Informationen',
+ more_info_desc = 'Erfahren Sie mehr über Luxart Vehicle Control.',
+ --------------------
+ airhorn_interrupt = 'Lufthorn Unterbrechungsmodus',
+ airhorn_interrupt_desc = 'Umschalten, ob das Signalhorn die Hauptsirene unterbricht.',
+ reset_standby = 'Zurücksetzen auf Standby',
+ reset_standby_desc = '~g~Aktiviert~s~, die primäre Sirene wird auf die 1. Sirene zurückgesetzt, wenn die Sirene umgeschaltet wird. ~r~Deaktiviert~s~, der zuletzt gespielte Ton wird beim Umschalten der Sirene fortgesetzt.',
+ --------------------
+ primary_manu = 'Manueller Hauptton',
+ primary_manu_desc = 'Ändern Sie Ihren primären manuellen Ton.',
+ secondary_manu = 'Sekundärer manueller Ton',
+ secondary_manu_desc = 'Ändern Sie Ihren sekundären manuellen Ton.',
+ aux_tone = 'Hilfssirenenton',
+ aux_tone_desc = 'Ändern Sie den Ton der Hilfssirene/Doppelsirene.',
+ siren_park_kill = 'Sirene Park Kill-Schalter',
+ siren_park_kill_desc = 'Legt fest, ob die Sirenen automatisch ausgeschaltet werden, wenn Sie Ihr Fahrzeug verlassen.',
+ --------------------
+ siren_settings_seperator = 'Sirenen-Einstellungen',
+ other_settings_seperator = 'Andere Einstellungen',
+ misc_settings_seperator = 'Sonstiges',
+ tone_options_seperator = 'Klangoptionen',
+ audio_sfx_separator = 'SoundFX-Einstellungen',
+ advanced_separator = 'Erweiterte Einstellungen',
+ --------------------
+ cycle_button = 'Zyklus & Taste',
+ cycle_only = 'Nur Zyklus',
+ button_only = 'Nur Taste',
+ enabled = 'Aktiviert',
+ disabled = 'Deaktiviert',
+ tone_options_desc = '~g~Zyklus:~s~ abspielen, während Sie durch Sirenen zirkulieren.\n~g~Taste:~s~ abspielen, wenn die registrierte Taste gedrückt wird.\n~b~Auswählen, um Sirenentöne umzubenennen.',
+ --------------------
+ hud_enabled_desc = 'Schaltet um, ob das HUD angezeigt wird. Erfordert, dass das GTA V HUD aktiviert ist.',
+ hud_move_mode = 'Verschieben-Modus',
+ hud_move_mode_desc = 'HUD-Position auf dem Bildschirm verschieben. Zum verlassen ~r~Rechtsklick~s~ oder "~r~Esc~s~" drücken.',
+ hud_scale = 'Skala',
+ hud_scale_desc = 'Deckkraft des HUD-Hintergrundrechtecks ändern.',
+ hud_backlight = 'Hintergrundbeleuchtung',
+ hud_backlight_auto = 'Auto',
+ hud_backlight_off = 'Aus',
+ hud_backlight_on = 'An',
+ hud_backlight_desc = 'Ändert das Verhalten der HUD-Hintergrundbeleuchtung. ~b~Auto~s~ wird durch den Zustand des Scheinwerfers bestimmt.',
+ hud_reset = 'Zurücksetzen',
+ hud_reset_desc = 'HUD-Position auf Standard zurücksetzen.',
+ --------------------
+ audio_radio = 'Radio-Steuerung',
+ audio_radio_desc = 'Wenn aktiviert, fungiert die Tilde-Taste (~) als Radiotaste.',
+ audio_scheme = 'Sirenenbox-Schema',
+ audio_scheme_desc = 'Ändern Sie, welche SFX für das Klicken der Sirenenbox verwendet werden sollen.',
+ audio_manu_sfx = 'Manuelle Tastenklicks',
+ audio_manu_sfx_desc = 'Wenn diese Funktion aktiviert ist, aktiviert Ihre manuelle Tontaste den Upgrade-SFX.',
+ audio_horn_sfx = 'Lufthorn-Klicks',
+ audio_horn_sfx_desc = 'Wenn diese Funktion aktiviert ist, aktiviert die Lufthorntaste den Upgrade-SFX.',
+ audio_activity_reminder = 'Aktivitätserinnerung',
+ audio_activity_reminder_desc = 'Empfangen Sie einen Erinnerungston, dass Ihr Licht eingeschaltet ist. Optionen sind in Minuten. Timer (Sekunden): %{timer}',
+ audio_volumes = 'Lautstärken anpassen',
+ audio_volumes_desc = 'Menü Lautstärkeeinstellungen öffnen.',
+ on_volume = 'Ein - Lautstärke',
+ on_volume_desc = 'Lautstärke des Schiebereglers / der Taste einstellen. Spielt, wenn das Licht ~g~eingeschaltet~s~ wird. Drücken Sie Enter, um den Ton abzuspielen.',
+ off_volume = 'Aus - Lautstärke',
+ off_volume_desc = 'Lautstärke des Schiebereglers / der Taste einstellen. Spielt, wenn das Licht ~r~ausgeschaltet~s~ wird. Drücken Sie Enter, um den Ton abzuspielen.',
+ upgrade_volume = 'Upgrade Lautstärke',
+ upgrade_volume_desc = 'Lautstärke der Sirenentaste einstellen. Spielt, wenn die Sirene ~g~eingeschaltet~s~ wird. Drücken Sie Enter, um den Ton abzuspielen.',
+ downgrade_volume = 'Downgrade Lautstärke',
+ downgrade_volume_desc = 'Lautstärke der Sirenentaste einstellen. Spielt, wenn die Sirene ~r~ausgeschaltet~s~ wird. Drücken Sie Enter, um den Ton abzuspielen.',
+ reminder_volume = 'Aktivitätserinnerung Lautstärke',
+ reminder_volume_desc = 'Lautstärke des Aktivitätserinnerungstons einstellen. Spielt, wenn die Lichter ~g~eingeschaltelt~s~ sind, die Sirene ~r~ausgeschaltet~s~ ist und der Timer abgelaufen ist. Drücken Sie Enter, um den Ton abzuspielen.',
+ hazards_volume = 'Gefahren Lautstärke',
+ hazards_volume_desc = 'Lautstärke der Gefahrentaste einstellen. Spielt, wenn Gefahren umgeschaltet werden. Drücken Sie Enter, um den Ton abzuspielen.',
+ lock_volume = 'Sperr-Lautstärke',
+ lock_volume_desc = 'Lautstärke der Sperrbenachrichtigung einstellen. Wird abgespielt, wenn das Sperren der Sirenenbox umgeschaltet wird. Drücken Sie Enter, um den Ton abzuspielen.',
+ lock_reminder_volume = 'Sperrsignal Lautstärke',
+ lock_reminder_volume_desc = 'Legen Sie die Lautstärke des Sperrsignaltons fest. Wird abgespielt, wenn die gesperrten Tasten wiederholt gedrückt werden. Drücken Sie Enter, um den Ton abzuspielen.',
+ --------------------
+ confirm = 'Sind Sie sicher?',
+ save = 'Einstellungen speichern',
+ save_desc = 'LVC-Einstellungen speichern.',
+ save_success = '~g~Erfolg~s~: Ihre Einstellungen wurden gespeichert.',
+ save_override = 'Sind Sie sicher?',
+ save_override_desc = '~r~Dadurch werden alle bereits gespeicherten Daten für dieses Fahrzeugprofil außer Kraft gesetzt (%{profile}).',
+ load = 'Einstellungen laden',
+ load_desc = 'LVC-Einstellungen laden.',
+ load_success = '~g~Erfolg~s~: Ihre Einstellungen wurden geladen.',
+ load_override = '~r~Dadurch werden alle nicht gespeicherten Einstellungen überschrieben..',
+ copy = 'Einstellungen kopieren',
+ copy_desc = 'Profileinstellungen von einem anderen Fahrzeug kopieren.',
+ reset = 'Einstellungen zurücksetzen',
+ reset_desc = '~r~Setzt LVC auf den Standardzustand zurück, wobei vorhandene Speicherungen erhalten bleiben. Setzt ungespeicherte Einstellungen außer Kraft.',
+ reset_success = '~g~Erfolg~s~: Die Einstellungen wurden zurückgesetzt.',
+ factory_reset = 'Werkseinstellung',
+ factory_reset_desc = '~r~Dauerhaftes Löschen von Speicherständen und Zurücksetzen von LVC in den Standardzustand.',
+ load_copy = 'Laden',
+ load_copy_desc = 'Einstellungen aus dem Profil \'~b~%{profile}~s~\' versuchen zu laden.',
+ --------------------
+ storage_default_profile_msg = 'Verwendung des ~b~STANDARD~s~ Profil für \'~b~%{veh}~s~\'.',
+ rename_tone = 'Neue Tonbezeichnung eingeben für %{tone_string}:',
+ --------------------
+ current_version = 'Aktuelle Version',
+ version_string = 'Auf diesem Server läuft %{ver}%{ver_desc}',
+ latest_version = 'Neueste Version',
+ latest_update_desc = 'Das letzte Update ist die Version %{ver}.',
+ latest_version_desc = ', die neueste Version.',
+ old_version_desc = ', eine veraltete Version.',
+ experimental_version_desc = ', eine ~y~experimentelle~s~ Version.',
+ unknown_version_desc = ', die neueste Version konnte nicht ermittelt werden.',
+ about_credits = 'Über uns / Credits',
+ about_credits_desc = 'Ursprünglich entworfen und erstellt von ~b~Lt. Caine~s~. ELS-Soundeffekte von ~b~Faction~s~. Version 3 Erweiterung von ~b~Trevor Barns~s~.\n\nSpezieller Dank an alle Mitwirkenden (siehe GitHub), das RageUI-Team und alle anderen, die beim Betatest geholfen haben. Dies wäre ohne euch alle nicht möglich gewesen!',
+ },
+ plugins ={
+ menu_tkd = 'Takedown-Einstellungen',
+ menu_tkd_desc = 'Menü Takedown-Lichter öffnen. (takedowns)',
+ menu_ei = 'Zusätzliche Integrationseinstellungen',
+ menu_ei_desc = 'Zusätzliches Integrationsmenü öffnen. (extra_integration)',
+ menu_ta = 'Menü Verkehrsinfo öffnen',
+ menu_ta_desc = 'Menü Verkehrsinfo öffnen. (traffic_advisor)',
+ menu_ts = 'Einstellungen für die Anhängerunterstützung',
+ menu_ts_desc = 'Öffnen Sie das Einstellungsmenü für die Anhängerunterstützung. (trailer_support)',
+ menu_ec = 'Einstellungen für zusätzliche Steuerelemente',
+ menu_ec_desc = 'Öffnen Sie das Einstellungsmenü für zusätzliche Steuerelemente. (extra_controls)',
+ -------------------
+ ec_shortcuts_separator = 'Tastenkürzel',
+ ec_shortcut_prefix_change = 'Ändern',
+ ec_shortcut_prefix_view = 'Anschauen',
+ ec_shortcut_desc = '%{prefix} Tastenkürzel-Einstellungen.',
+ ec_enabled_desc = 'Umschalten der Funktionalität zusätzlicher Steuerelemente.',
+ ec_save = 'Speichern von Profilsteuerungen',
+ ec_save_desc = 'Speichern neuer Steuerelemente im clientseitigen Speicher (KVP).',
+ ec_load = 'Laden von Profilsteuerungen',
+ ec_load_desc = 'Laden gespeicherter Steuerelemente aus dem clientseitigen Speicher (KVP).',
+ ec_reset = 'Profilsteuerungen zurücksetzen',
+ ec_reset_desc = '~r~Setzt die Steuerelemente dieses Profils auf die Standardeinstellungen zurück, so dass die vorhandenen Speicherungen erhalten bleiben. Setzt alle nicht gespeicherten Einstellungen außer Kraft.',
+ ec_factory_reset = 'Alle Profilsteuerungen löschen',
+ ec_factory_reset_desc = '~r~Löschen aller Extra Controls gespeicherten Daten aus dem clientseitigen Speicher (KVP).',
+ ec_factory_reset_success_console = 'Erfolg: Alle zusätzlichen Kontrolldaten gelöscht.',
+ ec_factory_reset_success_frontend = '~g~Erfolg~s~: Sie haben alle zusätzlichen Kontrolldaten gelöscht und das Plugin zurückgesetzt.',
+ ec_no_shortcuts = '(Keine)',
+ ec_no_shortcuts_desc = 'Keine Tastaturkürzel gefunden.',
+ ec_combo = 'Kombo',
+ ec_combo_desc = 'Bedienelement, das zusätzlich zur Taste gedrückt werden muss, um Extras umzuschalten. ~m~Format: (TASTATUR | CONTROLLER)',
+ ec_key = 'Taste',
+ ec_key_desc = 'Steuerelement, das zusätzlich zur Kombinationstaste gedrückt werden muss, um Extras umzuschalten. ~m~Format: (TASTATUR | CONTROLLER)',
+ ec_not_approved_console = '^3LVC Warnung P404: versuch das Steuerelement %{control} zu verwenden, konnte aber CONTROLS nicht finden. %{type} Tabelle. Versuchen Sie auf die Werkseinstellungen zurückzusetzen oder melden Sie sich beim Serverentwickler.',
+ ec_not_approved_frontend = '~b~LVC ~y~Warnung P404:~s~ versuch die Kontrolle zu nutzen, die aber nicht zugelassen wurde. Siehe Konsole.',
+ ec_fail_load_console = '^3LVC Warnung: Die gespeicherte Kontrolle für \'${name}\' ist vom Serverentwickler nicht mehr erlaubt. Rückkehr zum Standard. Speichern Sie das Steuerprofil erneut, um diesen Fehler zu beheben. STEUERUNG: %{control}',
+ ec_fail_load_frontend = '~b~LVC ~y~Warnung: Steuerung kann nicht geladen werden für \'%{name}\'. Siehe Konsole.',
+ ec_save_not_used = '^3LVC Info: Es wurden Speicherdaten gefunden, die nicht mit der aktuellen Konfiguration von Extra Controls übereinstimmen. Wahrscheinlich handelt es sich um alte Daten, die inzwischen von einem Serverentwickler geändert wurden. Sie können diese durch erneutes Speichern löschen.',
+ -------------------
+ ei_blackout = 'Verdunklung',
+ ei_blackout_desc = 'Deaktivierte automatische Bremslichter beim Anhalten.',
+ ei_auto_park = 'Automatischer Parkmodus',
+ ei_auto_park_desc = 'Wie lange dauert es nach dem Anhalten, um die automatischen Bremslichter zu deaktivieren und das Fahrzeug zu parken? Die Optionen sind in Minuten angegeben. Timer (Sekunden): %{timer}',
+ ei_control_desc = 'LVC Verdunklung umschalten',
+ ei_command_desc = 'LVC-Verdunklungs-Modus umschalten.',
+ ei_invalid_extra = '^3LVC Info: EXTRA_INTEGRATION Tabelle enthält nicht existierendes Extra: %{extra} für %{profile}.',
+ -------------------
+ tkd_integration = 'Integration',
+ tkd_integration_desc = 'Legt fest, ob Fernlicht automatisch auf Abblendlicht umgeschaltet wird oder umgekehrt.',
+ tkd_integration_off = 'Aus',
+ tkd_integration_set_highbeam = 'TKDs setzen Fernlichter',
+ tkd_integration_highbeam_set_tkd = 'Fernlichter setzen TKDs',
+ tkd_position = 'Position',
+ tkd_position_desc = 'Position',
+ tkd_intensity = 'Intensität',
+ tkd_intensity_desc = 'Helligkeit/Intensität der Take-Downs einstellen.',
+ tkd_radius = 'Radius',
+ tkd_radius_desc = 'Breite der Take-Downs einstellen.',
+ tkd_distance = 'Entfernung',
+ tkd_distance_desc = 'Legen Sie die maximale Entfernung fest, die die Take-Downs zurücklegen können.',
+ tkd_falloff = 'Abstieg',
+ tkd_falloff_desc = 'Einstellen, wie schnell das Licht "abfällt" oder gedämpft erscheint.',
+ -------------------
+ ta_pattern = 'TA HUD-Muster',
+ ta_pattern_desc = 'Ändern Sie das auf den HUD der Verkehrsinfo angezeigte Muster.',
+ ta_save = 'TA-Status speichern',
+ ta_save_desc = 'Erhält den Status der Verkehrsinfo beim Umschalten der Ampel. Wenn Sie diese Option deaktivieren, werden die TA-Extras ausgeschaltet, wenn die Beleuchtung ausgeschaltet wird.',
+ ta_sync = 'Sync TA Status',
+ ta_sync_desc = '~o~Demnächst verfügbar~c ~ Wenn möglich, Synchronisierung des TA-Status mit Fahrzeugen in der Nähe.',
+ ta_control_desc_left = 'LVC Umschalten Links TA',
+ ta_control_desc_right = 'LVC Umschalten Rechts TA',
+ ta_control_desc_middle = 'LVC Umschalten Mitte TA',
+ -------------------
+ ts_menu_extras = 'Anhänger Extras',
+ ts_menu_doors = 'Anhänger-Türen',
+ ts_menu_extras_button = 'Menü Extras',
+ ts_menu_extras_desc = 'Öffnen Sie das Menü, um die zusätzlichen Zustände des Anhängers umzuschalten',
+ ts_menu_doors_button = 'Tür Menü',
+ ts_menu_doors_desc = 'Menü zum Öffnen/Schließen von Türen',
+ ts_door_fl = 'Linke Vordertür',
+ ts_door_fr = 'Rechte Vordertür',
+ ts_door_rl = 'Linke Hintertür',
+ ts_door_rr = 'Rechte Hintertür',
+ ts_door_hood = 'Haube',
+ ts_door_trunk = 'Kofferraum',
+ ts_door_extra1 = 'Extra #1',
+ ts_door_extra2 = 'Extra #2',
+ ts_door_bombbay = 'Waffenschacht',
+ ts_current = 'Aktueller Trailer',
+ ts_current_desc = 'Aktuell ermittelter und angehängter Anhänger .',
+ ts_shortcut_desc = 'Shortcut auslösen %{shortcut}',
+ ts_shortcut_separator = 'Shortcuts',
+ ts_submenus_separator = 'Untermenüs',
+ ts_truck_separator = 'Kabine/LKW',
+ ts_trailer_separator = 'Anhänger',
+ ts_extra = 'Extra #%{extra}',
+ ts_extra_desc = 'Extra #%{extra} umschalten',
+ ts_door_desc = 'Öffnen / Schließen %{door}.',
+ ts_not_found = 'NICHT GEFUNDEN',
+ },
+}
+
+Lang = Locale:new({
+ phrases = Translations,
+ warnOnMissing = true
+})
\ No newline at end of file
diff --git a/resources/lvc/UI/locale/en.lua b/resources/lvc/UI/locale/en.lua
new file mode 100644
index 000000000..b5e3b3c21
--- /dev/null
+++ b/resources/lvc/UI/locale/en.lua
@@ -0,0 +1,291 @@
+local Translations = {
+ warning = {
+ warning = 'Warning',
+ -- WRONG BRANCH NOTIFIER --
+ wrong_branch_warning = '~b~LVC~w~: ~o~Warning~w~: This is the development branch (master)',
+ wrong_branch_info = '~y~THIS VERSION IS IN DEVELOPMENT AND IS NOT RECOMMENDED\nFOR PRODUCTION USE. IF THIS WAS A MISTAKE DOWNLOAD THE\nLATEST STABLE RELEASE AT:\n~g~github.com/TrevorBarns/luxart-vehicle-control~p~~h~/releases~h~',
+ wrong_branch_mute = '~b~TO MUTE THIS~w~: Set CONVAR "~o~experimental~w~" to "~o~true~w~" in fxmanifest.',
+ profile_missing = '^3LVC(%{ver}) WARNING: "DEFAULT" table missing from %{tbl} table. Using empty table for %{model}.',
+ -- SIREN CONTROLLER --
+ too_few_tone_frontend = '~b~LVC ~y~Warning %{code}:~s~ too little sirens assigned.',
+ too_few_tone_console = '^3LVC(%{ver}) Warning %{code}: too little sirens assigned. Minimum %{req_tone_count} tones required. (UTIL:SetToneByPos(%{tone_string}, %{pos})',
+ tone_position_nil_frontend = '~b~LVC ~y~Warning %{code}:~s~ attempted to set tone but, was unable to locate approved_tones. See console.',
+ tone_position_nil_console = '^3LVC(%{ver}) Warning %{code}: attempted to set tone "%{tone_string}" but, was unable to locate approved_tones table. (UTIL:SetToneByPos(%{tone_string}, %{pos}). Try factory resetting as this may occur after siren tone assignments change.',
+ tone_id_nil_frontend = '~b~LVC ~y~Warning %{code}:~s~ attempted to set tone but, was unable to locate in approved_tones. See console.',
+ tone_id_nil_console = '^3LVC(%{ver}) Warning %{code}: attempted to set tone %{tone_string} but, was unable to locate position: %{tone_id} in approved_tones. (UTIL:SetToneByPos(%{tone_string}, %{pos}). Try factory resetting as this may occur after siren tone assignments change.',
+ factory_reset = 'Are you sure you want to delete all saved LVC data and Factory Reset?',
+ facory_reset_options= '~g~No: Escape \t ~r~Yes: Enter',
+ },
+ error = {
+ -- CONFIG ERRORS --
+ missing_community_id_frontend = '~b~~h~LVC~h~ ~r~~h~CONFIG ERROR~h~~s~: COMMUNITY ID MISSING. SEE LOGS. CONTACT SERVER DEVELOPER.',
+ missing_community_id_console = '^1CONFIG ERROR: COMMUNITY ID NOT SET, THIS IS REQUIRED TO PREVENT CONFLICTS FOR PLAYERS WHO PLAY ON MULTIPLE SERVERS WITH LVC. PLEASE SET THIS IN SETTINGS.LUA.',
+ invalid_resource_name_frontend = '~b~~h~LVC~h~ ~r~~h~CONFIG ERROR~h~~s~: INVALID RESOURCE NAME. SEE LOGS. CONTACT SERVER DEVELOPER.',
+ invalid_resource_name_console = '^1CONFIG ERROR: INVALID RESOURCE NAME. PLEASE VERIFY RESOURCE FOLDER NAME READS "^3lvc^1" (CASE-SENSITIVE). THIS IS REQUIRED FOR PROPER SAVE / LOAD FUNCTIONALITY. PLEASE RENAME, REFRESH, AND ENSURE.',
+ resource_conflict_frontend = '~b~~h~LVC~h~ ~r~~h~CONFLICT ERROR~h~~s~: RESOURCE CONFLICT. SEE CONSOLE.',
+ resource_conflict_console = '^1LVC ERROR: DETECTED "lux_vehcontrol" RUNNING, THIS CONFLICTS WITH LVC. PLEASE STOP "lux_vehcontrol" AND RESTART LVC.',
+ profile_none_found_frontend = '~b~~h~LVC~h~ ~r~~h~CONFIG ERROR~h~~s~: DEFAULT TABLE MISSING. SEE LOGS. CONTACT SERVER DEVELOPER.',
+ profile_none_found_console = '^1CONFIG ERROR: UNABLE TO FIND A PROFILE FOR \'^3%{game_name}^1\', AND REQUIRED FALLBACK TABLE \'DEFAULT\' IS NOT PRESENT. (https://bit.ly/LVC-CSATS)',
+
+ -- FRONTEND ERRORS --
+ reg_keymap_nil_1 = '~b~~h~LVC~h~ ~r~~h~ERROR 2~h~~s~: Nil value caught.\ndetails: (%{i}, %{proposed_tone}, %{profile_name})',
+ reg_keymap_nil_2 = '~b~~h~LVC~h~ ~r~~h~ERROR 2~h~~s~: Try switching vehicles and switching back OR loading profile settings (if save present).',
+ profile_nil_table_frontend = '~b~~h~LVC~h~ ~r~ERROR: %{tbl} attempted to get profile from nil table. See console.',
+ profile_nil_table_console = '^1LVC(%{ver}) ERROR: %{tbl} attempted to get profile from nil table. This is typically caused by an invalid character or missing { } brace in SIRENS.lua. (https://git.io/JDVhK)',
+ },
+ info = {
+ locked = 'Siren Control Box: ~r~Locked',
+ unlocked = 'Siren Control Box: ~g~Unlocked',
+ debug_mode_frontend = '~y~~h~Info:~h~ ~s~debug mode set to %{state}. See console.',
+ debug_mode_console = '^3LVC Info: debug mode set to %{state} temporarily. Debug_mode resets after resource restart unless set in fxmanifest. Make sure to run "refresh" to see fxmanifest changes.',
+ profile_found = '^4LVC(%{ver}) ^5%{tbl}: ^7profile %{profile} found for %{model}.',
+ disabled_profile = '^4LVC(%{ver}) ^5%{tbl}: ^1LVC disabled for %{model} using profile %{profile}.',
+ profile_default_console = '^4LVC(%{ver}) ^5%{tbl}: ^7using default profile for %{model}.',
+ profile_default_frontend = '~b~LVC~s~: Using ~b~DEFAULT~s~ profile for \'~o~ %{model} ~s~\'.',
+ extra_on = '^4LVC: ^7Toggling %{extra} on',
+ extra_off = '^4LVC: ^7Toggling %{extra} off',
+ unknown = 'unknown',
+ unable_to_disable = '~y~~h~Info:~h~ ~s~Luxart Vehicle Control\nAction prohibited, cannot disable all sirens.',
+ factory_reset_success_console = 'Success: cleared all save data.',
+ factory_reset_success_frontend = '~g~Success~s~: You have deleted all save data and reset LVC.',
+ },
+ control = {
+ siren_control_desc = 'LVC Siren: %{ord_num}',
+ lock_desc = 'LVC: Lock out controls',
+ menu_desc = 'LVC: Open Menu',
+ },
+ command = {
+ lock_command = 'lvclock',
+ lock_desc = 'Toggle Luxart Vehicle Control Key Binding Lockout.',
+ debug_command = 'lvcdebug',
+
+ },
+ menu = {
+ -- MENU SUBTITLES --
+ main = 'Main Menu',
+ siren = 'Main Siren Settings',
+ siren_desc = 'Change which/how each available primary tone is used.',
+ hud = 'HUD Settings',
+ hud_desc = 'Open HUD settings menu.',
+ audio = 'Audio Settings',
+ audio_desc = 'Open audio settings menu.',
+ plugins = 'Plugins',
+ plugins_desc = 'Open Plugins Menu.',
+ storage = 'Storage Management',
+ storage_desc = 'Save / Load vehicle profiles.',
+ copy = 'Copy Profile Settings',
+ more_info = 'More Information',
+ more_info_desc = 'Learn more about Luxart Vehicle Control.',
+ --------------------
+ airhorn_interrupt = 'Airhorn Interrupt Mode',
+ airhorn_interrupt_desc = 'Toggles whether the airhorn interrupts main siren.',
+ reset_standby = 'Reset to Standby',
+ reset_standby_desc = '~g~Enabled~s~, the primary siren will reset to 1st siren on siren toggle. ~r~Disabled~s~, the last played tone will resume on siren toggle.',
+ --------------------
+ primary_manu = 'Primary Manual Tone',
+ primary_manu_desc = 'Change your primary manual tone.',
+ secondary_manu = 'Secondary Manual Tone',
+ secondary_manu_desc = 'Change your secondary manual tone.',
+ aux_tone = 'Auxiliary Siren Tone',
+ aux_tone_desc = 'Change your auxiliary/dual siren tone.',
+ siren_park_kill ='Siren Park Kill',
+ siren_park_kill_desc = 'Toggles whether your sirens turn off automatically when you exit your vehicle.',
+ --------------------
+ siren_settings_seperator = 'Siren Settings',
+ other_settings_seperator = 'Other Settings',
+ misc_settings_seperator = 'Miscellaneous',
+ tone_options_seperator = 'Tone Options',
+ audio_sfx_separator = 'SoundFX Settings',
+ advanced_separator = 'Advanced Settings',
+ --------------------
+ cycle_button = 'Cycle & Button',
+ cycle_only = 'Cycle Only',
+ button_only = 'Button Only',
+ enabled = 'Enabled',
+ disabled = 'Disabled',
+ tone_options_desc = '~g~Cycle:~s~ play as you cycle through sirens.\n~g~Button:~s~ play when registered key is pressed.\n~b~Select to rename siren tones.',
+ --------------------
+ hud_enabled_desc = 'Toggles whether HUD is displayed. Requires GTA V HUD to be enabled.',
+ hud_move_mode = 'Move Mode',
+ hud_move_mode_desc = 'Move HUD position on screen. To exit ~r~right-click~s~ or hit "~r~Esc~s~".',
+ hud_scale = 'Scale',
+ hud_scale_desc = 'Change scale of the HUD.',
+ hud_backlight = 'Backlight',
+ hud_backlight_auto = 'Auto',
+ hud_backlight_off = 'Off',
+ hud_backlight_on = 'On',
+ hud_backlight_desc = 'Changes HUD backlight behavior. ~b~Auto~s~ is determined by headlight state.',
+ hud_reset = 'Reset',
+ hud_reset_desc = 'Reset HUD position to default.',
+ --------------------
+ audio_radio = 'Radio Controls',
+ audio_radio_desc = 'When enabled, the tilde key will act as a radio wheel key.',
+ audio_scheme = 'Siren Box Scheme',
+ audio_scheme_desc = 'Change what SFX to use for siren box clicks.',
+ audio_manu_sfx = 'Manual Button Clicks',
+ audio_manu_sfx_desc = 'When enabled, your manual tone button will activate the upgrade SFX.',
+ audio_horn_sfx = 'Airhorn Button Clicks',
+ audio_horn_sfx_desc = 'When enabled, your airhorn button will activate the upgrade SFX.',
+ audio_activity_reminder = 'Activity Reminder',
+ audio_activity_reminder_desc = 'Receive reminder tone that your lights are on. Options are in minutes. Timer (sec): %{timer}',
+ audio_volumes = 'Adjust Volumes',
+ audio_volumes_desc = 'Open volume settings menu.',
+ on_volume = 'On Volume',
+ on_volume_desc = 'Set volume of light slider / button. Plays when lights are turned ~g~on~s~. Press Enter to play the sound.',
+ off_volume = 'Off Volume',
+ off_volume_desc = 'Set volume of light slider / button. Plays when lights are turned ~r~off~s~. Press Enter to play the sound.',
+ upgrade_volume = 'Upgrade Volume',
+ upgrade_volume_desc = 'Set volume of siren button. Plays when siren is turned ~g~on~s~. Press Enter to play the sound.',
+ downgrade_volume = 'Downgrade Volume',
+ downgrade_volume_desc = 'Set volume of siren button. Plays when siren is turned ~r~off~s~. Press Enter to play the sound.',
+ reminder_volume = 'Activity Reminder Volume',
+ reminder_volume_desc = 'Set volume of activity reminder tone. Plays when lights are ~g~on~s~, siren is ~r~off~s~, and timer is has finished. Press Enter to play the sound.',
+ hazards_volume = 'Hazards Volume',
+ hazards_volume_desc = 'Set volume of hazards button. Plays when hazards are toggled. Press Enter to play the sound.',
+ lock_volume = 'Lock Volume',
+ lock_volume_desc = 'Set volume of lock notification sound. Plays when siren box lockout is toggled. Press Enter to play the sound.',
+ lock_reminder_volume = 'Lock Reminder Volume',
+ lock_reminder_volume_desc = 'Set volume of lock reminder sound. Plays when locked out keys are pressed repeatedly. Press Enter to play the sound.',
+ --------------------
+ confirm = 'Are you sure?',
+ save = 'Save Settings',
+ save_desc = 'Save LVC settings.',
+ save_success = '~g~Success~s~: Your settings have been saved.',
+ save_override = 'Are you sure?',
+ save_override_desc = '~r~This will override any existing save data for this vehicle profile (%{profile}).',
+ load = 'Load Settings',
+ load_desc = 'Load LVC settings.',
+ load_success = '~g~Success~s~: Your settings have been loaded.',
+ load_override = '~r~This will override any unsaved settings.',
+ copy = 'Copy Settings',
+ copy_desc = 'Copy profile settings from another vehicle.',
+ reset = 'Reset Settings',
+ reset_desc = '~r~Reset LVC to it\'s default state, preserves existing saves. Will override any unsaved settings.',
+ reset_success = '~g~Success~s~: Settings have been reset.',
+ factory_reset = 'Factory Reset',
+ factory_reset_desc = '~r~Permanently delete any saves, resetting LVC to its default state.',
+ load_copy = 'Load',
+ load_copy_desc = 'Attempt to load settings from profile \'~b~%{profile}~s~\'.',
+ --------------------
+ storage_default_profile_msg = 'Using ~b~DEFAULT~s~ profile for \'~b~%{veh}~s~\'.',
+ rename_tone = 'Enter new tone name for %{tone_string}:',
+ --------------------
+ current_version = 'Current Version',
+ version_string = 'This server is running %{ver}%{ver_desc}',
+ latest_version = 'Latest Version',
+ latest_update_desc = 'The latest update is %{ver}.',
+ latest_version_desc = ', the latest version.',
+ old_version_desc = ', an out-of-date version.',
+ experimental_version_desc = ', an ~y~experimental~s~ version.',
+ unknown_version_desc = ', the latest version could not be determined.',
+ about_credits = 'About / Credits',
+ about_credits_desc = 'Originally designed and created by ~b~Lt. Caine~s~. ELS sound effects by ~b~Faction~s~. Version 3 expansion by ~b~Trevor Barns~s~.\n\nSpecial thanks to all contributors (see GitHub), the RageUI team, and everyone else who helped beta test, this would not have been possible without you all!',
+ },
+ plugins ={
+ menu_tkd = 'Takedown Settings',
+ menu_tkd_desc = 'Open takedown lights menu. (takedowns)',
+ menu_ei = 'Extra Integration Settings',
+ menu_ei_desc = 'Open extra integration menu. (extra_integration)',
+ menu_ta = 'Traffic Advisor Settings',
+ menu_ta_desc = 'Open traffic advisor menu. (traffic_advisor)',
+ menu_ts = 'Trailer Support Settings',
+ menu_ts_desc = 'Open trailer support settings menu. (trailer_support)',
+ menu_ec = 'Extra Controls Settings',
+ menu_ec_desc = 'Open extra controls settings menu. (extra_controls)',
+ -------------------
+ ec_shortcuts_separator = 'Shortcuts',
+ ec_shortcut_prefix_change = 'Change',
+ ec_shortcut_prefix_view = 'View',
+ ec_shortcut_desc = '%{prefix} shortcut settings.',
+ ec_enabled_desc = 'Toggle extra controls functionality.',
+ ec_save = 'Save Profile Controls',
+ ec_save_desc = 'Store new controls to client-side storage (KVP).',
+ ec_load = 'Load Profile Controls',
+ ec_load_desc = 'Load saved controls from client-side storage (KVP).',
+ ec_reset = 'Reset Profile Controls',
+ ec_reset_desc = '~r~Reset this profiles controls to default, preserves existing saves. Will override any unsaved settings.',
+ ec_factory_reset = 'Delete All Profile Controls',
+ ec_factory_reset_desc = '~r~Delete all Extra Controls saved data from client-side storage (KVP).',
+ ec_factory_reset_success_console = 'Success: cleared all extra controls data.',
+ ec_factory_reset_success_frontend = '~g~Success~s~: You have deleted all extra controls data and reset the plugin.',
+ ec_no_shortcuts = '(None)',
+ ec_no_shortcuts_desc = 'No shortcuts found.',
+ ec_combo = 'Combo',
+ ec_combo_desc = 'Control that needs to be pressed in addition to key to toggle extras. ~m~Format: (KEYBOARD | CONTROLLER)',
+ ec_key = 'Key',
+ ec_key_desc = 'Control that needs to be pressed in addition to combo-key to toggle extras. ~m~Format: (KEYBOARD | CONTROLLER)',
+ ec_controller_support = 'Controller Support',
+ ec_controller_support_desc = 'When disabled, LVC ignores controller button input. Requires combo-key to be None',
+ ec_not_approved_console = '^3LVC Warning P404: attempted to use control %{control} but, was unable to locate CONTROLS.%{type} table. Try factory resetting or report to server developer.',
+ ec_not_approved_frontend = '~b~LVC ~y~Warning P404:~s~ attempted to use control but was not approved. See console.',
+ ec_fail_load_console = '^3LVC Warning: The saved control for \'${name}\' is no longer permitted by server developer. Reverting to default. Re-save control profile to remove this error. CONTROL: %{control}',
+ ec_fail_load_frontend = '~b~LVC ~y~Warning: Unable to load control for \'%{name}\'. See console.',
+ ec_save_not_used = '^3LVC Info: found save data that did not align with current Extra Controls configuration. Likely old data that has since been changed by a server developer. You can delete this by re-saving.',
+ -------------------
+ ei_blackout = 'Blackout',
+ ei_blackout_desc = 'Disabled auto brake lights on stop.',
+ ei_auto_park = 'Auto Park Mode',
+ ei_auto_park_desc = 'How long after being stopped to disable auto brake lights and put vehicle in "park". Options are in minutes. Timer (sec): %{timer}',
+ ei_control_desc = 'LVC Toggle Blackout',
+ ei_command_desc = 'Toggle LVC Blackout Mode.',
+ ei_invalid_extra = '^3LVC Info: EXTRA_INTEGRATION table contains non-existent extra: %{extra} for %{profile}.',
+ -------------------
+ tkd_integration = 'Integration',
+ tkd_integration_desc = 'Determines whether high-beams will auto toggle take-downs or visa versa.',
+ tkd_integration_off = 'Off',
+ tkd_integration_set_highbeam = 'TKDs Set High-beams',
+ tkd_integration_highbeam_set_tkd = 'High-beams Set TKDs',
+ tkd_position = 'Position',
+ tkd_position_desc = 'Position',
+ tkd_intensity = 'Intensity',
+ tkd_intensity_desc = 'Set brightness/intensity of take-downs.',
+ tkd_radius = 'Radius',
+ tkd_radius_desc = 'Set width of take-downs.',
+ tkd_distance = 'Distance',
+ tkd_distance_desc = 'Set the max distance the take-downs can travel.',
+ tkd_falloff = 'Falloff',
+ tkd_falloff_desc = 'Set how fast light "falls off" or appears dim.',
+ -------------------
+ ta_pattern = 'TA HUD Pattern',
+ ta_pattern_desc = 'Change pattern displayed on HUD traffic advisor indicators.',
+ ta_save = 'Save TA State',
+ ta_save_desc = 'Preserves traffic advisor state on lights toggling. Unchecking this will turn TA extras off when lights are turned off.',
+ ta_sync = 'Sync TA State',
+ ta_sync_desc = '~o~Coming Soon~c ~ When able, sync TA state to nearby vehicles.',
+ ta_control_desc_left = 'LVC Toggle Left TA',
+ ta_control_desc_right = 'LVC Toggle Right TA',
+ ta_control_desc_middle = 'LVC Toggle Middle TA',
+ -------------------
+ ts_menu_extras = 'Trailer Extras',
+ ts_menu_doors = 'Trailer Doors',
+ ts_menu_extras_button = 'Extras Menu',
+ ts_menu_extras_desc = 'Open menu to toggle trailer extra states.',
+ ts_menu_doors_button = 'Doors Menu',
+ ts_menu_doors_desc = 'Open menu to open / close doors.',
+ ts_door_fl = 'Left Front Door',
+ ts_door_fr = 'Right Front Door',
+ ts_door_rl = 'Left Rear Door',
+ ts_door_rr = 'Right Rear Door',
+ ts_door_hood = 'Hood',
+ ts_door_trunk = 'Trunk',
+ ts_door_extra1 = 'Extra #1',
+ ts_door_extra2 = 'Extra #2',
+ ts_door_bombbay = 'Bomb Bay',
+ ts_current = 'Current Trailer',
+ ts_current_desc = 'Current detected trailer attached.',
+ ts_shortcut_desc = 'Trigger shortcut %{shortcut}',
+ ts_shortcut_separator = 'Shortcuts',
+ ts_submenus_separator = 'Submenus',
+ ts_truck_separator = 'Cab/Truck',
+ ts_trailer_separator = 'Trailer',
+ ts_extra = 'Extra #%{extra}',
+ ts_extra_desc = 'Toggle extra #%{extra}',
+ ts_door_desc = 'Open / close %{door}.',
+ ts_not_found = 'NOT FOUND',
+ },
+}
+
+Lang = Locale:new({
+ phrases = Translations,
+ warnOnMissing = true
+})
\ No newline at end of file
diff --git a/resources/lvc/UI/sounds/Cencom/Downgrade.ogg b/resources/lvc/UI/sounds/Cencom/Downgrade.ogg
new file mode 100644
index 000000000..e608835d1
Binary files /dev/null and b/resources/lvc/UI/sounds/Cencom/Downgrade.ogg differ
diff --git a/resources/lvc/UI/sounds/Cencom/Off.ogg b/resources/lvc/UI/sounds/Cencom/Off.ogg
new file mode 100644
index 000000000..8b05fa879
Binary files /dev/null and b/resources/lvc/UI/sounds/Cencom/Off.ogg differ
diff --git a/resources/lvc/UI/sounds/Cencom/On.ogg b/resources/lvc/UI/sounds/Cencom/On.ogg
new file mode 100644
index 000000000..8b05fa879
Binary files /dev/null and b/resources/lvc/UI/sounds/Cencom/On.ogg differ
diff --git a/resources/lvc/UI/sounds/Cencom/Press.ogg b/resources/lvc/UI/sounds/Cencom/Press.ogg
new file mode 100644
index 000000000..7644f0ff5
Binary files /dev/null and b/resources/lvc/UI/sounds/Cencom/Press.ogg differ
diff --git a/resources/lvc/UI/sounds/Cencom/Release.ogg b/resources/lvc/UI/sounds/Cencom/Release.ogg
new file mode 100644
index 000000000..7733b2733
Binary files /dev/null and b/resources/lvc/UI/sounds/Cencom/Release.ogg differ
diff --git a/resources/lvc/UI/sounds/Cencom/Reminder.ogg b/resources/lvc/UI/sounds/Cencom/Reminder.ogg
new file mode 100644
index 000000000..e3ae375e6
Binary files /dev/null and b/resources/lvc/UI/sounds/Cencom/Reminder.ogg differ
diff --git a/resources/lvc/UI/sounds/Cencom/Upgrade.ogg b/resources/lvc/UI/sounds/Cencom/Upgrade.ogg
new file mode 100644
index 000000000..72044b252
Binary files /dev/null and b/resources/lvc/UI/sounds/Cencom/Upgrade.ogg differ
diff --git a/resources/lvc/UI/sounds/Hazards_Off.ogg b/resources/lvc/UI/sounds/Hazards_Off.ogg
new file mode 100644
index 000000000..f877d9bb3
Binary files /dev/null and b/resources/lvc/UI/sounds/Hazards_Off.ogg differ
diff --git a/resources/lvc/UI/sounds/Hazards_On.ogg b/resources/lvc/UI/sounds/Hazards_On.ogg
new file mode 100644
index 000000000..54646657e
Binary files /dev/null and b/resources/lvc/UI/sounds/Hazards_On.ogg differ
diff --git a/resources/lvc/UI/sounds/Key_Lock.ogg b/resources/lvc/UI/sounds/Key_Lock.ogg
new file mode 100644
index 000000000..054f96063
Binary files /dev/null and b/resources/lvc/UI/sounds/Key_Lock.ogg differ
diff --git a/resources/lvc/UI/sounds/Locked_Press.ogg b/resources/lvc/UI/sounds/Locked_Press.ogg
new file mode 100644
index 000000000..01ad7664a
Binary files /dev/null and b/resources/lvc/UI/sounds/Locked_Press.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP2000/Downgrade.ogg b/resources/lvc/UI/sounds/SSP2000/Downgrade.ogg
new file mode 100644
index 000000000..c4f0f6a8d
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP2000/Downgrade.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP2000/Off.ogg b/resources/lvc/UI/sounds/SSP2000/Off.ogg
new file mode 100644
index 000000000..0d21441c7
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP2000/Off.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP2000/On.ogg b/resources/lvc/UI/sounds/SSP2000/On.ogg
new file mode 100644
index 000000000..7c5b2b9e1
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP2000/On.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP2000/Press.ogg b/resources/lvc/UI/sounds/SSP2000/Press.ogg
new file mode 100644
index 000000000..55901abb3
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP2000/Press.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP2000/Release.ogg b/resources/lvc/UI/sounds/SSP2000/Release.ogg
new file mode 100644
index 000000000..c4f0f6a8d
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP2000/Release.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP2000/Reminder.ogg b/resources/lvc/UI/sounds/SSP2000/Reminder.ogg
new file mode 100644
index 000000000..e3ae375e6
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP2000/Reminder.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP2000/Upgrade.ogg b/resources/lvc/UI/sounds/SSP2000/Upgrade.ogg
new file mode 100644
index 000000000..55901abb3
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP2000/Upgrade.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP3000/Downgrade.ogg b/resources/lvc/UI/sounds/SSP3000/Downgrade.ogg
new file mode 100644
index 000000000..314f84fe5
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP3000/Downgrade.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP3000/Off.ogg b/resources/lvc/UI/sounds/SSP3000/Off.ogg
new file mode 100644
index 000000000..26d5300db
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP3000/Off.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP3000/On.ogg b/resources/lvc/UI/sounds/SSP3000/On.ogg
new file mode 100644
index 000000000..26d5300db
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP3000/On.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP3000/Press.ogg b/resources/lvc/UI/sounds/SSP3000/Press.ogg
new file mode 100644
index 000000000..b685e21ea
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP3000/Press.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP3000/Release.ogg b/resources/lvc/UI/sounds/SSP3000/Release.ogg
new file mode 100644
index 000000000..76d05f441
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP3000/Release.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP3000/Reminder.ogg b/resources/lvc/UI/sounds/SSP3000/Reminder.ogg
new file mode 100644
index 000000000..62fda4f98
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP3000/Reminder.ogg differ
diff --git a/resources/lvc/UI/sounds/SSP3000/Upgrade.ogg b/resources/lvc/UI/sounds/SSP3000/Upgrade.ogg
new file mode 100644
index 000000000..314f84fe5
Binary files /dev/null and b/resources/lvc/UI/sounds/SSP3000/Upgrade.ogg differ
diff --git a/resources/lvc/UI/sounds/ST300/Downgrade.ogg b/resources/lvc/UI/sounds/ST300/Downgrade.ogg
new file mode 100644
index 000000000..96392d34f
Binary files /dev/null and b/resources/lvc/UI/sounds/ST300/Downgrade.ogg differ
diff --git a/resources/lvc/UI/sounds/ST300/Off.ogg b/resources/lvc/UI/sounds/ST300/Off.ogg
new file mode 100644
index 000000000..9313c758d
Binary files /dev/null and b/resources/lvc/UI/sounds/ST300/Off.ogg differ
diff --git a/resources/lvc/UI/sounds/ST300/On.ogg b/resources/lvc/UI/sounds/ST300/On.ogg
new file mode 100644
index 000000000..9313c758d
Binary files /dev/null and b/resources/lvc/UI/sounds/ST300/On.ogg differ
diff --git a/resources/lvc/UI/sounds/ST300/Press.ogg b/resources/lvc/UI/sounds/ST300/Press.ogg
new file mode 100644
index 000000000..96392d34f
Binary files /dev/null and b/resources/lvc/UI/sounds/ST300/Press.ogg differ
diff --git a/resources/lvc/UI/sounds/ST300/Release.ogg b/resources/lvc/UI/sounds/ST300/Release.ogg
new file mode 100644
index 000000000..76d05f441
Binary files /dev/null and b/resources/lvc/UI/sounds/ST300/Release.ogg differ
diff --git a/resources/lvc/UI/sounds/ST300/Reminder.ogg b/resources/lvc/UI/sounds/ST300/Reminder.ogg
new file mode 100644
index 000000000..68f07ddf9
Binary files /dev/null and b/resources/lvc/UI/sounds/ST300/Reminder.ogg differ
diff --git a/resources/lvc/UI/sounds/ST300/Upgrade.ogg b/resources/lvc/UI/sounds/ST300/Upgrade.ogg
new file mode 100644
index 000000000..96392d34f
Binary files /dev/null and b/resources/lvc/UI/sounds/ST300/Upgrade.ogg differ
diff --git a/resources/lvc/UI/textures/background.png b/resources/lvc/UI/textures/background.png
new file mode 100644
index 000000000..425ec391f
Binary files /dev/null and b/resources/lvc/UI/textures/background.png differ
diff --git a/resources/lvc/UI/textures/day/horn_off.png b/resources/lvc/UI/textures/day/horn_off.png
new file mode 100644
index 000000000..ef03fbeef
Binary files /dev/null and b/resources/lvc/UI/textures/day/horn_off.png differ
diff --git a/resources/lvc/UI/textures/day/horn_on.png b/resources/lvc/UI/textures/day/horn_on.png
new file mode 100644
index 000000000..dcfbb9db1
Binary files /dev/null and b/resources/lvc/UI/textures/day/horn_on.png differ
diff --git a/resources/lvc/UI/textures/day/lock_off.png b/resources/lvc/UI/textures/day/lock_off.png
new file mode 100644
index 000000000..0eda6b798
Binary files /dev/null and b/resources/lvc/UI/textures/day/lock_off.png differ
diff --git a/resources/lvc/UI/textures/day/lock_on.png b/resources/lvc/UI/textures/day/lock_on.png
new file mode 100644
index 000000000..9b56cc856
Binary files /dev/null and b/resources/lvc/UI/textures/day/lock_on.png differ
diff --git a/resources/lvc/UI/textures/day/siren_off.png b/resources/lvc/UI/textures/day/siren_off.png
new file mode 100644
index 000000000..4f2bb2b33
Binary files /dev/null and b/resources/lvc/UI/textures/day/siren_off.png differ
diff --git a/resources/lvc/UI/textures/day/siren_on.png b/resources/lvc/UI/textures/day/siren_on.png
new file mode 100644
index 000000000..3f9092d0c
Binary files /dev/null and b/resources/lvc/UI/textures/day/siren_on.png differ
diff --git a/resources/lvc/UI/textures/day/slide_off.png b/resources/lvc/UI/textures/day/slide_off.png
new file mode 100644
index 000000000..05208fcc4
Binary files /dev/null and b/resources/lvc/UI/textures/day/slide_off.png differ
diff --git a/resources/lvc/UI/textures/day/slide_on.png b/resources/lvc/UI/textures/day/slide_on.png
new file mode 100644
index 000000000..ad5be7c36
Binary files /dev/null and b/resources/lvc/UI/textures/day/slide_on.png differ
diff --git a/resources/lvc/UI/textures/day/ta_off.gif b/resources/lvc/UI/textures/day/ta_off.gif
new file mode 100644
index 000000000..273012175
Binary files /dev/null and b/resources/lvc/UI/textures/day/ta_off.gif differ
diff --git a/resources/lvc/UI/textures/day/tkd_off.png b/resources/lvc/UI/textures/day/tkd_off.png
new file mode 100644
index 000000000..efeafab49
Binary files /dev/null and b/resources/lvc/UI/textures/day/tkd_off.png differ
diff --git a/resources/lvc/UI/textures/day/tkd_on.png b/resources/lvc/UI/textures/day/tkd_on.png
new file mode 100644
index 000000000..f26c973e6
Binary files /dev/null and b/resources/lvc/UI/textures/day/tkd_on.png differ
diff --git a/resources/lvc/UI/textures/night/horn_off.png b/resources/lvc/UI/textures/night/horn_off.png
new file mode 100644
index 000000000..35f23a25e
Binary files /dev/null and b/resources/lvc/UI/textures/night/horn_off.png differ
diff --git a/resources/lvc/UI/textures/night/horn_on.png b/resources/lvc/UI/textures/night/horn_on.png
new file mode 100644
index 000000000..7813adc37
Binary files /dev/null and b/resources/lvc/UI/textures/night/horn_on.png differ
diff --git a/resources/lvc/UI/textures/night/lock_off.png b/resources/lvc/UI/textures/night/lock_off.png
new file mode 100644
index 000000000..12009ffaf
Binary files /dev/null and b/resources/lvc/UI/textures/night/lock_off.png differ
diff --git a/resources/lvc/UI/textures/night/lock_on.png b/resources/lvc/UI/textures/night/lock_on.png
new file mode 100644
index 000000000..fc5921d8e
Binary files /dev/null and b/resources/lvc/UI/textures/night/lock_on.png differ
diff --git a/resources/lvc/UI/textures/night/siren_off.png b/resources/lvc/UI/textures/night/siren_off.png
new file mode 100644
index 000000000..9f13618de
Binary files /dev/null and b/resources/lvc/UI/textures/night/siren_off.png differ
diff --git a/resources/lvc/UI/textures/night/siren_on.png b/resources/lvc/UI/textures/night/siren_on.png
new file mode 100644
index 000000000..35006b364
Binary files /dev/null and b/resources/lvc/UI/textures/night/siren_on.png differ
diff --git a/resources/lvc/UI/textures/night/slide_off.png b/resources/lvc/UI/textures/night/slide_off.png
new file mode 100644
index 000000000..762fe58a8
Binary files /dev/null and b/resources/lvc/UI/textures/night/slide_off.png differ
diff --git a/resources/lvc/UI/textures/night/slide_on.png b/resources/lvc/UI/textures/night/slide_on.png
new file mode 100644
index 000000000..ad5be7c36
Binary files /dev/null and b/resources/lvc/UI/textures/night/slide_on.png differ
diff --git a/resources/lvc/UI/textures/night/ta_off.gif b/resources/lvc/UI/textures/night/ta_off.gif
new file mode 100644
index 000000000..a1c23f88a
Binary files /dev/null and b/resources/lvc/UI/textures/night/ta_off.gif differ
diff --git a/resources/lvc/UI/textures/night/tkd_off.png b/resources/lvc/UI/textures/night/tkd_off.png
new file mode 100644
index 000000000..66465b1b7
Binary files /dev/null and b/resources/lvc/UI/textures/night/tkd_off.png differ
diff --git a/resources/lvc/UI/textures/night/tkd_on.png b/resources/lvc/UI/textures/night/tkd_on.png
new file mode 100644
index 000000000..486b30ca6
Binary files /dev/null and b/resources/lvc/UI/textures/night/tkd_on.png differ
diff --git a/resources/lvc/UI/textures/ta/pattern_1/ta_center.gif b/resources/lvc/UI/textures/ta/pattern_1/ta_center.gif
new file mode 100644
index 000000000..8e7382e7e
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_1/ta_center.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_1/ta_left.gif b/resources/lvc/UI/textures/ta/pattern_1/ta_left.gif
new file mode 100644
index 000000000..6629c6c67
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_1/ta_left.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_1/ta_right.gif b/resources/lvc/UI/textures/ta/pattern_1/ta_right.gif
new file mode 100644
index 000000000..591336553
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_1/ta_right.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_2/ta_center.gif b/resources/lvc/UI/textures/ta/pattern_2/ta_center.gif
new file mode 100644
index 000000000..008435084
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_2/ta_center.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_2/ta_left.gif b/resources/lvc/UI/textures/ta/pattern_2/ta_left.gif
new file mode 100644
index 000000000..0a2f62e86
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_2/ta_left.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_2/ta_right.gif b/resources/lvc/UI/textures/ta/pattern_2/ta_right.gif
new file mode 100644
index 000000000..030f8089d
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_2/ta_right.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_3/ta_center.gif b/resources/lvc/UI/textures/ta/pattern_3/ta_center.gif
new file mode 100644
index 000000000..be390ab7a
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_3/ta_center.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_3/ta_left.gif b/resources/lvc/UI/textures/ta/pattern_3/ta_left.gif
new file mode 100644
index 000000000..5ee7743f8
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_3/ta_left.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_3/ta_right.gif b/resources/lvc/UI/textures/ta/pattern_3/ta_right.gif
new file mode 100644
index 000000000..aa69a2c1a
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_3/ta_right.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_4/ta_center.gif b/resources/lvc/UI/textures/ta/pattern_4/ta_center.gif
new file mode 100644
index 000000000..47afec2f6
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_4/ta_center.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_4/ta_left.gif b/resources/lvc/UI/textures/ta/pattern_4/ta_left.gif
new file mode 100644
index 000000000..bef3006c5
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_4/ta_left.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_4/ta_right.gif b/resources/lvc/UI/textures/ta/pattern_4/ta_right.gif
new file mode 100644
index 000000000..345a494e7
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_4/ta_right.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_5/ta_center.gif b/resources/lvc/UI/textures/ta/pattern_5/ta_center.gif
new file mode 100644
index 000000000..4bd8ade75
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_5/ta_center.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_5/ta_left.gif b/resources/lvc/UI/textures/ta/pattern_5/ta_left.gif
new file mode 100644
index 000000000..e5a1fc34b
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_5/ta_left.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_5/ta_right.gif b/resources/lvc/UI/textures/ta/pattern_5/ta_right.gif
new file mode 100644
index 000000000..07a75e864
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_5/ta_right.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_6/ta_center.gif b/resources/lvc/UI/textures/ta/pattern_6/ta_center.gif
new file mode 100644
index 000000000..fbfb80431
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_6/ta_center.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_6/ta_left.gif b/resources/lvc/UI/textures/ta/pattern_6/ta_left.gif
new file mode 100644
index 000000000..3cf4cbf66
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_6/ta_left.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_6/ta_right.gif b/resources/lvc/UI/textures/ta/pattern_6/ta_right.gif
new file mode 100644
index 000000000..0471581fd
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_6/ta_right.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_7/ta_center.gif b/resources/lvc/UI/textures/ta/pattern_7/ta_center.gif
new file mode 100644
index 000000000..fa9e60d8c
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_7/ta_center.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_7/ta_left.gif b/resources/lvc/UI/textures/ta/pattern_7/ta_left.gif
new file mode 100644
index 000000000..3cf4cbf66
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_7/ta_left.gif differ
diff --git a/resources/lvc/UI/textures/ta/pattern_7/ta_right.gif b/resources/lvc/UI/textures/ta/pattern_7/ta_right.gif
new file mode 100644
index 000000000..0471581fd
Binary files /dev/null and b/resources/lvc/UI/textures/ta/pattern_7/ta_right.gif differ
diff --git a/resources/lvc/UTIL/cl_lvc.lua b/resources/lvc/UTIL/cl_lvc.lua
new file mode 100644
index 000000000..7f5318456
--- /dev/null
+++ b/resources/lvc/UTIL/cl_lvc.lua
@@ -0,0 +1,861 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_lvc.lua
+PURPOSE: Core Functionality and User Input
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+--GLOBAL VARIABLES used in cl_ragemenu, UTILs, and plug-ins.
+-- GENERAL VARIABLES
+key_lock = false
+playerped = nil
+last_veh = nil
+veh = nil
+trailer = nil
+player_is_emerg_driver = false
+debug_mode = false
+
+-- MAIN SIREN SETTINGS
+tone_main_reset_standby = reset_to_standby_default
+tone_airhorn_intrp = airhorn_interrupt_default
+park_kill = park_kill_default
+
+--LOCAL VARIABLES
+local radio_wheel_active = false
+
+local count_bcast_timer = 0
+local delay_bcast_timer = 300
+
+local count_sndclean_timer = 0
+local delay_sndclean_timer = 400
+
+local actv_ind_timer = false
+local count_ind_timer = 0
+local delay_ind_timer = 180
+
+actv_lxsrnmute_temp = false
+local srntone_temp = 0
+local dsrn_mute = true
+local lights_on = false
+local new_tone = nil
+local tone_mem_id = nil
+local tone_mem_option = nil
+local default_tone = nil
+local default_tone_option = nil
+
+state_indic = {}
+state_lxsiren = {}
+state_pwrcall = {}
+state_airmanu = {}
+
+actv_manu = nil
+actv_horn = nil
+
+local ind_state_o = 0
+local ind_state_l = 1
+local ind_state_r = 2
+local ind_state_h = 3
+
+local snd_lxsiren = {}
+local snd_pwrcall = {}
+local snd_airmanu = {}
+
+-- Local fn forward declaration
+local RegisterKeyMaps, MakeOrdinal
+
+----------------THREADED FUNCTIONS----------------
+-- Set check variable `player_is_emerg_driver` if player is driver of emergency vehicle.
+-- Disables controls faster than previous thread.
+CreateThread(function()
+ if GetResourceState('lux_vehcontrol') ~= 'started' and GetResourceState('lux_vehcontrol') ~= 'starting' then
+ if GetCurrentResourceName() == 'lvc' then
+ if community_id ~= nil and community_id ~= '' then
+ while true do
+ playerped = GetPlayerPed(-1)
+ --IS IN VEHICLE
+ player_is_emerg_driver = false
+ if IsPedInAnyVehicle(playerped, false) then
+ veh = GetVehiclePedIsUsing(playerped)
+ _, trailer = GetVehicleTrailerVehicle(veh)
+ --IS DRIVER
+ if GetPedInVehicleSeat(veh, -1) == playerped then
+ --IS EMERGENCY VEHICLE
+ if GetVehicleClass(veh) == 18 then
+ player_is_emerg_driver = true
+ DisableControlAction(0, 80, true) -- INPUT_VEH_CIN_CAM
+ DisableControlAction(0, 86, true) -- INPUT_VEH_HORN
+ DisableControlAction(0, 172, true) -- INPUT_CELLPHONE_UP
+ end
+ end
+ end
+ Wait(1)
+ end
+ else
+ Wait(1000)
+ HUD:ShowNotification(Lang:t('error.missing_community_id_frontend'), true)
+ UTIL:Print(Lang:t('error.missing_community_id_console'), true)
+ end
+ else
+ Wait(1000)
+ HUD:ShowNotification(Lang:t('error.invalid_resource_name_frontend'), true)
+ UTIL:Print(Lang:t('error.invalid_resource_name_console'), true)
+ end
+ else
+ Wait(1000)
+ HUD:ShowNotification(Lang:t('error.resource_conflict_frontend'), true)
+ UTIL:Print(Lang:t('error.resource_conflict_console'), true)
+ end
+end)
+
+--On resource start/restart
+CreateThread(function()
+ debug_mode = GetResourceMetadata(GetCurrentResourceName(), 'debug_mode', 0) == 'true'
+ TriggerEvent('chat:addSuggestion', Lang:t('command.lock_command'), Lang:t('command.lock_desc'))
+ SetNuiFocus( false )
+
+ UTIL:FixOversizeKeys(SIREN_ASSIGNMENTS)
+ RegisterKeyMaps()
+ STORAGE:SetBackupTable()
+end)
+
+-- Auxiliary Control Handling
+-- Handles radio wheel controls and default horn on siren change playback.
+CreateThread(function()
+ while true do
+ if player_is_emerg_driver then
+ -- RADIO WHEEL
+ if IsControlPressed(0, 243) and AUDIO.radio_masterswitch then
+ while IsControlPressed(0, 243) do
+ radio_wheel_active = true
+ SetControlNormal(0, 85, 1.0)
+ Wait(0)
+ end
+ Wait(100)
+ radio_wheel_active = false
+ else
+ DisableControlAction(0, 85, true) -- INPUT_VEH_RADIO_WHEEL
+ SetVehicleRadioEnabled(veh, false)
+ end
+ end
+ Wait(0)
+ end
+end)
+
+------ON VEHICLE EXIT EVENT TRIGGER------
+CreateThread(function()
+ while true do
+ if player_is_emerg_driver then
+ while playerped ~= nil and veh ~= nil do
+ if GetIsTaskActive(playerped, 2) and GetVehiclePedIsIn(ped, true) then
+ TriggerEvent('lvc:onVehicleExit')
+ Wait(1000)
+ end
+ Wait(0)
+ end
+ end
+ Wait(1000)
+ end
+end)
+
+------VEHICLE CHANGE DETECTION AND TRIGGER------
+CreateThread(function()
+ while true do
+ if player_is_emerg_driver and veh ~= nil then
+ if last_veh == nil then
+ TriggerEvent('lvc:onVehicleChange')
+ else
+ if last_veh ~= veh then
+ TriggerEvent('lvc:onVehicleChange')
+ end
+ end
+ end
+ Wait(1000)
+ end
+end)
+
+------------REGISTERED VEHICLE EVENTS------------
+--Kill siren on Exit
+RegisterNetEvent('lvc:onVehicleExit')
+AddEventHandler('lvc:onVehicleExit', function()
+ if park_kill_masterswitch and park_kill then
+ if not tone_main_reset_standby and state_lxsiren[veh] ~= 0 then
+ UTIL:SetToneByID('MAIN_MEM', state_lxsiren[veh])
+ end
+ SetLxSirenStateForVeh(veh, 0)
+ SetPowercallStateForVeh(veh, 0)
+ SetAirManuStateForVeh(veh, 0)
+ HUD:SetItemState('siren', false)
+ HUD:SetItemState('horn', false)
+ count_bcast_timer = delay_bcast_timer
+ end
+end)
+
+RegisterNetEvent('lvc:onVehicleChange')
+AddEventHandler('lvc:onVehicleChange', function()
+ last_veh = veh
+ UTIL:UpdateApprovedTones(veh)
+ Wait(100) --waiting for JS event handler
+ STORAGE:ResetSettings()
+ UTIL:BuildToneOptions()
+ STORAGE:LoadSettings()
+ HUD:RefreshHudItemStates()
+ SetVehRadioStation(veh, 'OFF')
+ Wait(500)
+ SetVehRadioStation(veh, 'OFF')
+end)
+
+--------------REGISTERED COMMANDS---------------
+--Toggle Debug Mode
+RegisterCommand(Lang:t('command.debug_command'), function(source, args)
+ debug_mode = not debug_mode
+ HUD:ShowNotification(Lang:t('info.debug_mode_frontend', {state = debug_mode}), true)
+ UTIL:Print(Lang:t('info.debug_mode_console', {state = debug_mode}), true)
+ if debug_mode then
+ TriggerEvent('lvc:onVehicleChange')
+ end
+end)
+
+--Toggle LUX lock command
+RegisterCommand(Lang:t('command.lock_command'), function(source, args)
+ if player_is_emerg_driver then
+ key_lock = not key_lock
+ AUDIO:Play('Key_Lock', AUDIO.lock_volume, true)
+ HUD:SetItemState('lock', key_lock)
+ --if HUD is visible do not show notification
+ if not HUD:GetHudState() then
+ if key_lock then
+ HUD:ShowNotification(Lang:t('info.locked'), true)
+ else
+ HUD:ShowNotification(Lang:t('info.unlocked'), true)
+ end
+ end
+ end
+end)
+
+RegisterKeyMapping(Lang:t('command.lock_command'), Lang:t('control.lock_desc'), 'keyboard', lockout_default_hotkey)
+
+------------------------------------------------
+-------------------FUNCTIONS--------------------
+------------------------------------------------
+------------------------------------------------
+--Dynamically Run RegisterCommand and KeyMapping functions for all 14 possible sirens
+--Then at runtime 'slide' all sirens down removing any restricted sirens.
+RegisterKeyMaps = function()
+ for i, _ in ipairs(SIRENS) do
+ if i ~= 1 then
+ local command = '_lvc_siren_' .. i-1
+ local description = Lang:t('control.siren_control_desc', {ord_num = MakeOrdinal(i-1)})
+
+ RegisterCommand(command, function(source, args)
+ if veh ~= nil and player_is_emerg_driver ~= nil then
+ if IsVehicleSirenOn(veh) and player_is_emerg_driver and not key_lock then
+ local proposed_tone = UTIL:GetToneAtPos(i)
+ local tone_option = UTIL:GetToneOption(proposed_tone)
+ if i-1 < #UTIL:GetApprovedTonesTable() then
+ if tone_option ~= nil then
+ if tone_option == 1 or tone_option == 3 then
+ if ( state_lxsiren[veh] ~= proposed_tone or state_lxsiren[veh] == 0 ) then
+ HUD:SetItemState('siren', true)
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ SetLxSirenStateForVeh(veh, proposed_tone)
+ count_bcast_timer = delay_bcast_timer
+ else
+ if state_pwrcall[veh] == 0 then
+ HUD:SetItemState('siren', false)
+ end
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ SetLxSirenStateForVeh(veh, 0)
+ count_bcast_timer = delay_bcast_timer
+ end
+ end
+ else
+ HUD:ShowNotification(Lang:t('error.reg_keymap_nil_1', {i = i, proposed_tone = proposed_tone, profile_name = UTIL:GetVehicleProfileName()}), true)
+ HUD:ShowNotification(Lang:t('error.reg_keymap_nil_2'), true)
+ end
+ end
+ end
+ end
+ end)
+
+ --CHANGE BELOW if you'd like to change which keys are used for example NUMROW1 through 0
+ if i > 0 and i < 11 and main_siren_set_register_keys_set_defaults then
+ RegisterKeyMapping(command, description, 'keyboard', i-1)
+ elseif i == 11 and main_siren_set_register_keys_set_defaults then
+ RegisterKeyMapping(command, description, 'keyboard', '0')
+ else
+ RegisterKeyMapping(command, description, 'keyboard', '')
+ end
+ end
+ end
+end
+
+--Make number into ordinal number, used for FiveM RegisterKeys
+MakeOrdinal = function(number)
+ local sufixes = { 'th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th' }
+ local mod = (number % 100)
+ if mod == 11 or mod == 12 or mod == 13 then
+ return number .. 'th'
+ else
+ return number..sufixes[(number % 10) + 1]
+ end
+end
+
+---------------------------------------------------------------------
+local function CleanupSounds()
+ if count_sndclean_timer > delay_sndclean_timer then
+ count_sndclean_timer = 0
+ for k, v in pairs(state_lxsiren) do
+ if v > 0 then
+ if not DoesEntityExist(k) or IsEntityDead(k) then
+ if snd_lxsiren[k] ~= nil then
+ StopSound(snd_lxsiren[k])
+ ReleaseSoundId(snd_lxsiren[k])
+ snd_lxsiren[k] = nil
+ state_lxsiren[k] = nil
+ end
+ end
+ end
+ end
+ for k, v in pairs(state_pwrcall) do
+ if v > 0 then
+ if not DoesEntityExist(k) or IsEntityDead(k) then
+ if snd_pwrcall[k] ~= nil then
+ StopSound(snd_pwrcall[k])
+ ReleaseSoundId(snd_pwrcall[k])
+ snd_pwrcall[k] = nil
+ state_pwrcall[k] = nil
+ end
+ end
+ end
+ end
+ for k, v in pairs(state_airmanu) do
+ if v == true then
+ if not DoesEntityExist(k) or IsEntityDead(k) or IsVehicleSeatFree(k, -1) then
+ if snd_airmanu[k] ~= nil then
+ StopSound(snd_airmanu[k])
+ ReleaseSoundId(snd_airmanu[k])
+ snd_airmanu[k] = nil
+ state_airmanu[k] = nil
+ end
+ end
+ end
+ end
+ else
+ count_sndclean_timer = count_sndclean_timer + 1
+ end
+end
+---------------------------------------------------------------------
+function TogIndicStateForVeh(veh, newstate)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ if newstate == ind_state_o then
+ SetVehicleIndicatorLights(veh, 0, false) -- R
+ SetVehicleIndicatorLights(veh, 1, false) -- L
+ elseif newstate == ind_state_l then
+ SetVehicleIndicatorLights(veh, 0, false) -- R
+ SetVehicleIndicatorLights(veh, 1, true) -- L
+ elseif newstate == ind_state_r then
+ SetVehicleIndicatorLights(veh, 0, true) -- R
+ SetVehicleIndicatorLights(veh, 1, false) -- L
+ elseif newstate == ind_state_h then
+ SetVehicleIndicatorLights(veh, 0, true) -- R
+ SetVehicleIndicatorLights(veh, 1, true) -- L
+ end
+ state_indic[veh] = newstate
+ end
+end
+
+---------------------------------------------------------------------
+function TogMuteDfltSrnForVeh(veh, toggle)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ DisableVehicleImpactExplosionActivation(veh, toggle)
+ end
+end
+
+---------------------------------------------------------------------
+function SetLxSirenStateForVeh(veh, newstate)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ if newstate ~= state_lxsiren[veh] and newstate ~= nil then
+ if snd_lxsiren[veh] ~= nil then
+ StopSound(snd_lxsiren[veh])
+ ReleaseSoundId(snd_lxsiren[veh])
+ snd_lxsiren[veh] = nil
+ end
+ if newstate ~= 0 then
+ snd_lxsiren[veh] = GetSoundId()
+ PlaySoundFromEntity(snd_lxsiren[veh], SIRENS[newstate].String, veh, SIRENS[newstate].Ref, 0, 0)
+ TogMuteDfltSrnForVeh(veh, true)
+ end
+ state_lxsiren[veh] = newstate
+ end
+ end
+end
+
+---------------------------------------------------------------------
+function SetPowercallStateForVeh(veh, newstate)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ if newstate ~= state_pwrcall[veh] and newstate ~= nil then
+ if snd_pwrcall[veh] ~= nil then
+ StopSound(snd_pwrcall[veh])
+ ReleaseSoundId(snd_pwrcall[veh])
+ snd_pwrcall[veh] = nil
+ end
+ if newstate ~= 0 then
+ snd_pwrcall[veh] = GetSoundId()
+ PlaySoundFromEntity(snd_pwrcall[veh], SIRENS[newstate].String, veh, SIRENS[newstate].Ref, 0, 0)
+ end
+ state_pwrcall[veh] = newstate
+ end
+ end
+end
+
+---------------------------------------------------------------------
+function SetAirManuStateForVeh(veh, newstate)
+ if DoesEntityExist(veh) and not IsEntityDead(veh) then
+ if newstate ~= state_airmanu[veh] and newstate ~= nil then
+ if snd_airmanu[veh] ~= nil then
+ StopSound(snd_airmanu[veh])
+ ReleaseSoundId(snd_airmanu[veh])
+ snd_airmanu[veh] = nil
+ end
+ if newstate ~= 0 then
+ snd_airmanu[veh] = GetSoundId()
+ PlaySoundFromEntity(snd_airmanu[veh], SIRENS[newstate].String, veh, SIRENS[newstate].Ref, 0, 0)
+ end
+ state_airmanu[veh] = newstate
+ end
+ end
+end
+
+------------------------------------------------
+----------------EVENT HANDLERS------------------
+------------------------------------------------
+RegisterNetEvent('lvc:TogIndicState_c')
+AddEventHandler('lvc:TogIndicState_c', function(sender, newstate)
+ local player_s = GetPlayerFromServerId(sender)
+ local ped_s = GetPlayerPed(player_s)
+ if DoesEntityExist(ped_s) and not IsEntityDead(ped_s) then
+ if ped_s ~= GetPlayerPed(-1) then
+ if IsPedInAnyVehicle(ped_s, false) then
+ local veh = GetVehiclePedIsUsing(ped_s)
+ TogIndicStateForVeh(veh, newstate)
+ end
+ end
+ end
+end)
+
+---------------------------------------------------------------------
+RegisterNetEvent('lvc:TogDfltSrnMuted_c')
+AddEventHandler('lvc:TogDfltSrnMuted_c', function(sender)
+ local player_s = GetPlayerFromServerId(sender)
+ local ped_s = GetPlayerPed(player_s)
+ if DoesEntityExist(ped_s) and not IsEntityDead(ped_s) then
+ if ped_s ~= GetPlayerPed(-1) then
+ if IsPedInAnyVehicle(ped_s, false) then
+ local veh = GetVehiclePedIsUsing(ped_s)
+ TogMuteDfltSrnForVeh(veh, true)
+ end
+ end
+ end
+end)
+
+---------------------------------------------------------------------
+RegisterNetEvent('lvc:SetLxSirenState_c')
+AddEventHandler('lvc:SetLxSirenState_c', function(sender, newstate)
+ local player_s = GetPlayerFromServerId(sender)
+ local ped_s = GetPlayerPed(player_s)
+ if DoesEntityExist(ped_s) and not IsEntityDead(ped_s) then
+ if ped_s ~= GetPlayerPed(-1) then
+ if IsPedInAnyVehicle(ped_s, false) then
+ local veh = GetVehiclePedIsUsing(ped_s)
+ SetLxSirenStateForVeh(veh, newstate)
+ end
+ end
+ end
+end)
+
+---------------------------------------------------------------------
+RegisterNetEvent('lvc:SetPwrcallState_c')
+AddEventHandler('lvc:SetPwrcallState_c', function(sender, newstate)
+ local player_s = GetPlayerFromServerId(sender)
+ local ped_s = GetPlayerPed(player_s)
+ if DoesEntityExist(ped_s) and not IsEntityDead(ped_s) then
+ if ped_s ~= GetPlayerPed(-1) then
+ if IsPedInAnyVehicle(ped_s, false) then
+ local veh = GetVehiclePedIsUsing(ped_s)
+ SetPowercallStateForVeh(veh, newstate)
+ end
+ end
+ end
+end)
+
+---------------------------------------------------------------------
+RegisterNetEvent('lvc:SetAirManuState_c')
+AddEventHandler('lvc:SetAirManuState_c', function(sender, newstate)
+ local player_s = GetPlayerFromServerId(sender)
+ local ped_s = GetPlayerPed(player_s)
+ if DoesEntityExist(ped_s) and not IsEntityDead(ped_s) then
+ if ped_s ~= GetPlayerPed(-1) then
+ if IsPedInAnyVehicle(ped_s, false) then
+ local veh = GetVehiclePedIsUsing(ped_s)
+ SetAirManuStateForVeh(veh, newstate)
+ end
+ end
+ end
+end)
+
+
+---------------------------------------------------------------------
+CreateThread(function()
+ while true do
+ CleanupSounds()
+ DistantCopCarSirens(false)
+ ----- IS IN VEHICLE -----
+ if GetPedInVehicleSeat(veh, -1) == playerped then
+ if state_indic[veh] == nil then
+ state_indic[veh] = ind_state_o
+ end
+
+ -- INDIC AUTO CONTROL
+ if actv_ind_timer == true then
+ if state_indic[veh] == ind_state_l or state_indic[veh] == ind_state_r then
+ if GetEntitySpeed(veh) < 6 then
+ count_ind_timer = 0
+ else
+ if count_ind_timer > delay_ind_timer then
+ count_ind_timer = 0
+ actv_ind_timer = false
+ state_indic[veh] = ind_state_o
+ TogIndicStateForVeh(veh, state_indic[veh])
+ count_bcast_timer = delay_bcast_timer
+ else
+ count_ind_timer = count_ind_timer + 1
+ end
+ end
+ end
+ end
+
+ --- IS EMERG VEHICLE ---
+ if GetVehicleClass(veh) == 18 then
+ lights_on = IsVehicleSirenOn(veh)
+ -- FORCE RADIO ENABLED PER FRAME
+ if radio_masterswitch then
+ SetVehicleRadioEnabled(veh, true)
+ end
+
+ if not IsEntityDead(veh) then
+ TogMuteDfltSrnForVeh(veh, true)
+ --- SET INIT TABLE VALUES ---
+ if state_lxsiren[veh] == nil then
+ state_lxsiren[veh] = 0
+ end
+ if state_pwrcall[veh] == nil then
+ state_pwrcall[veh] = 0
+ end
+ if state_airmanu[veh] == nil then
+ state_airmanu[veh] = 0
+ end
+
+ --- IF LIGHTS ARE OFF TURN OFF SIREN ---
+ if not lights_on and state_lxsiren[veh] > 0 then
+ -- SAVE TONE BEFORE TURNING OFF
+ if not tone_main_reset_standby then
+ UTIL:SetToneByID('MAIN_MEM', state_lxsiren[veh])
+ end
+ SetLxSirenStateForVeh(veh, 0)
+ count_bcast_timer = delay_bcast_timer
+ end
+ if not lights_on and state_pwrcall[veh] > 0 then
+ SetPowercallStateForVeh(veh, 0)
+ count_bcast_timer = delay_bcast_timer
+ end
+
+ ----- CONTROLS -----
+ if not IsPauseMenuActive() and UpdateOnscreenKeyboard() ~= 0 and not radio_wheel_active then
+ if not key_lock then
+ ------ TOG DFLT SRN LIGHTS ------
+ if IsDisabledControlJustReleased(0, 85) then
+ if lights_on then
+ AUDIO:Play('Off', AUDIO.off_volume)
+ -- SET NUI IMAGES
+ HUD:SetItemState('switch', false)
+ HUD:SetItemState('siren', false)
+ -- TURN OFF SIRENS (R* LIGHTS)
+ SetVehicleSiren(veh, false)
+ if trailer ~= nil and trailer ~= 0 then
+ SetVehicleSiren(trailer, false)
+ end
+
+ else
+ AUDIO:Play('On', AUDIO.on_volume) -- On
+ -- SET NUI IMAGES
+ HUD:SetItemState('switch', true)
+ -- TURN OFF SIRENS (R* LIGHTS)
+ SetVehicleSiren(veh, true)
+ if trailer ~= nil and trailer ~= 0 then
+ SetVehicleSiren(trailer, true)
+ end
+ end
+ AUDIO:ResetActivityTimer()
+ count_bcast_timer = delay_bcast_timer
+ ------ TOG LX SIREN ------
+ elseif IsDisabledControlJustReleased(0, 19) then
+ if state_lxsiren[veh] == 0 then
+ if lights_on then
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ HUD:SetItemState('siren', true)
+ if not tone_main_reset_standby then
+ -- GET THE SAVED TONE VERIFY IT IS APPROVED, AND NOT DISABLED / BUTTON ONLY
+ tone_mem_id = UTIL:GetToneID('MAIN_MEM')
+ tone_mem_option = UTIL:GetToneOption(tone_mem_id)
+ if UTIL:IsApprovedTone(tone_mem_id) and tone_mem_option ~= 3 and tone_mem_option ~= 4 then
+ SetLxSirenStateForVeh(veh, tone_mem_id)
+ else
+ new_tone = UTIL:GetNextSirenTone(tone_mem_id, veh, true)
+ UTIL:SetToneByID('MAIN_MEM', new_tone)
+ SetLxSirenStateForVeh(veh, new_tone)
+ end
+
+ else
+ default_tone = UTIL:GetToneAtPos(2)
+ default_tone_option = UTIL:GetToneOption(default_tone)
+ if default_tone_option == 3 or default_tone_option == 4 then
+ new_tone = UTIL:GetNextSirenTone(default_tone, veh, true)
+ else
+ new_tone = UTIL:GetToneAtPos(2)
+ end
+ SetLxSirenStateForVeh(veh, new_tone)
+ end
+ end
+ else
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ -- ONLY CHANGE NUI STATE IF PWRCALL IS OFF AS WELL
+ if state_pwrcall[veh] == 0 then
+ HUD:SetItemState('siren', false)
+ end
+ if not tone_main_reset_standby then
+ UTIL:SetToneByID('MAIN_MEM', state_lxsiren[veh])
+ end
+ SetLxSirenStateForVeh(veh, 0)
+ end
+ AUDIO:ResetActivityTimer()
+ count_bcast_timer = delay_bcast_timer
+ -- POWERCALL
+ elseif IsDisabledControlJustReleased(0, 172) and not IsMenuOpen() then
+ if state_pwrcall[veh] == 0 then
+ if lights_on then
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ HUD:SetItemState('siren', true)
+ SetPowercallStateForVeh(veh, UTIL:GetToneID('AUX'))
+ count_bcast_timer = delay_bcast_timer
+ end
+ else
+ AUDIO:Play('Downgrade', AUDIO.downgrade_volume)
+ if state_lxsiren[veh] == 0 then
+ HUD:SetItemState('siren', false)
+ end
+ SetPowercallStateForVeh(veh, 0)
+ end
+ AUDIO:ResetActivityTimer()
+ count_bcast_timer = delay_bcast_timer
+ end
+ -- CYCLE LX SRN TONES
+ if state_lxsiren[veh] > 0 then
+ if IsDisabledControlJustReleased(0, 80) then
+ AUDIO:Play('Upgrade', AUDIO.upgrade_volume)
+ HUD:SetItemState('horn', false)
+ SetLxSirenStateForVeh(veh, UTIL:GetNextSirenTone(state_lxsiren[veh], veh, true))
+ count_bcast_timer = delay_bcast_timer
+ elseif IsDisabledControlPressed(0, 80) then
+ HUD:SetItemState('horn', true)
+ end
+ end
+
+ -- MANU
+ if state_lxsiren[veh] < 1 then
+ if IsDisabledControlPressed(0, 80) then
+ AUDIO:ResetActivityTimer()
+ actv_manu = true
+ HUD:SetItemState('siren', true)
+ else
+ if actv_manu then
+ HUD:SetItemState('siren', false)
+ end
+ actv_manu = false
+ end
+ else
+ if actv_manu then
+ HUD:SetItemState('siren', false)
+ end
+ actv_manu = false
+ end
+
+ -- HORN
+ if IsDisabledControlPressed(0, 86) then
+ actv_horn = true
+ AUDIO:ResetActivityTimer()
+ HUD:SetItemState('horn', true)
+ else
+ if actv_horn or actv_manu then
+ HUD:SetItemState('horn', false)
+ end
+ actv_horn = false
+ end
+
+
+ --AIRHORN AND MANU BUTTON SFX
+ if AUDIO.airhorn_button_SFX then
+ if IsDisabledControlJustPressed(0, 86) then
+ AUDIO:Play('Press', AUDIO.upgrade_volume)
+ end
+ if IsDisabledControlJustReleased(0, 86) then
+ AUDIO:Play('Release', AUDIO.upgrade_volume)
+ end
+ end
+ if AUDIO.manu_button_SFX and state_lxsiren[veh] == 0 then
+ if IsDisabledControlJustPressed(0, 80) then
+ AUDIO:Play('Press', AUDIO.upgrade_volume)
+ end
+ if IsDisabledControlJustReleased(0, 80) then
+ AUDIO:Play('Release', AUDIO.upgrade_volume)
+ end
+ end
+ else
+ if (IsDisabledControlJustReleased(0, 86) or
+ IsDisabledControlJustReleased(0, 172) or
+ IsDisabledControlJustReleased(0, 19) or
+ IsDisabledControlJustReleased(0, 85)) then
+ if locked_press_count % reminder_rate == 0 then
+ AUDIO:Play('Locked_Press', AUDIO.lock_reminder_volume, true) -- lock reminder
+ HUD:ShowNotification('~y~~h~Reminder:~h~ ~s~Your siren control box is ~r~locked~s~.', true)
+ end
+ locked_press_count = locked_press_count + 1
+ end
+ end
+ end
+
+ ---- ADJUST HORN / MANU STATE ----
+ local hmanu_state_new = 0
+ if actv_horn == true and actv_manu == false then
+ hmanu_state_new = UTIL:GetToneID('ARHRN')
+ elseif actv_horn == false and actv_manu == true then
+ hmanu_state_new = UTIL:GetToneID('PMANU')
+ elseif actv_horn == true and actv_manu == true then
+ hmanu_state_new = UTIL:GetToneID('SMANU')
+ end
+ if tone_airhorn_intrp then
+ if hmanu_state_new == UTIL:GetToneID('ARHRN') then
+ if state_lxsiren[veh] > 0 and actv_lxsrnmute_temp == false then
+ srntone_temp = state_lxsiren[veh]
+ SetLxSirenStateForVeh(veh, 0)
+ actv_lxsrnmute_temp = true
+ end
+ else
+ if actv_lxsrnmute_temp == true then
+ SetLxSirenStateForVeh(veh, srntone_temp)
+ actv_lxsrnmute_temp = false
+ end
+ end
+ end
+
+ if state_airmanu[veh] ~= hmanu_state_new then
+ SetAirManuStateForVeh(veh, hmanu_state_new)
+ count_bcast_timer = delay_bcast_timer
+ end
+ end
+ else
+ -- DISABLE SIREN AUDIO FOR ALL VEHICLES NOT VC_EMERGENCY (VEHICLES.META)
+ TogMuteDfltSrnForVeh(veh, true)
+ end
+
+ --- IS ANY LAND VEHICLE ---
+ if GetVehicleClass(veh) ~= 14 and GetVehicleClass(veh) ~= 15 and GetVehicleClass(veh) ~= 16 and GetVehicleClass(veh) ~= 21 then
+ ----- CONTROLS -----
+ if not IsPauseMenuActive() then
+ -- IND L
+ if IsDisabledControlJustReleased(0, left_signal_key) then -- INPUT_VEH_PREV_RADIO_TRACK
+ local cstate = state_indic[veh]
+ if cstate == ind_state_l then
+ state_indic[veh] = ind_state_o
+ actv_ind_timer = false
+ else
+ state_indic[veh] = ind_state_l
+ actv_ind_timer = true
+ end
+ TogIndicStateForVeh(veh, state_indic[veh])
+ count_ind_timer = 0
+ count_bcast_timer = delay_bcast_timer
+ -- IND R
+ elseif IsDisabledControlJustReleased(0, right_signal_key) then -- INPUT_VEH_NEXT_RADIO_TRACK
+ local cstate = state_indic[veh]
+ if cstate == ind_state_r then
+ state_indic[veh] = ind_state_o
+ actv_ind_timer = false
+ else
+ state_indic[veh] = ind_state_r
+ actv_ind_timer = true
+ end
+ TogIndicStateForVeh(veh, state_indic[veh])
+ count_ind_timer = 0
+ count_bcast_timer = delay_bcast_timer
+ -- IND H
+ elseif IsControlPressed(0, hazard_key) then -- INPUT_FRONTEND_CANCEL / Backspace
+ if GetLastInputMethod(0) then -- last input was with kb
+ Wait(hazard_hold_duration)
+ if IsControlPressed(0, hazard_key) then -- INPUT_FRONTEND_CANCEL / Backspace
+ local cstate = state_indic[veh]
+ if cstate == ind_state_h then
+ state_indic[veh] = ind_state_o
+ AUDIO:Play('Hazards_Off', AUDIO.hazards_volume, true) -- Hazards Off
+ else
+ state_indic[veh] = ind_state_h
+ AUDIO:Play('Hazards_On', AUDIO.hazards_volume, true) -- Hazards On
+ end
+ TogIndicStateForVeh(veh, state_indic[veh])
+ actv_ind_timer = false
+ count_ind_timer = 0
+ count_bcast_timer = delay_bcast_timer
+ Wait(300)
+ end
+ end
+ end
+ end
+
+ ----- AUTO BROADCAST VEH STATES -----
+ if count_bcast_timer > delay_bcast_timer then
+ count_bcast_timer = 0
+ --- IS EMERG VEHICLE ---
+ if GetVehicleClass(veh) == 18 then
+ TriggerServerEvent('lvc:TogDfltSrnMuted_s')
+ TriggerServerEvent('lvc:SetLxSirenState_s', state_lxsiren[veh])
+ TriggerServerEvent('lvc:SetPwrcallState_s', state_pwrcall[veh])
+ TriggerServerEvent('lvc:SetAirManuState_s', state_airmanu[veh])
+ end
+ --- IS ANY OTHER VEHICLE ---
+ TriggerServerEvent('lvc:TogIndicState_s', state_indic[veh])
+ else
+ count_bcast_timer = count_bcast_timer + 1
+ end
+ end
+ end
+ Wait(0)
+ end
+end)
\ No newline at end of file
diff --git a/resources/lvc/UTIL/cl_storage.lua b/resources/lvc/UTIL/cl_storage.lua
new file mode 100644
index 000000000..9913bf28e
--- /dev/null
+++ b/resources/lvc/UTIL/cl_storage.lua
@@ -0,0 +1,415 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_storage.lua
+PURPOSE: Handle save/load functions and version
+ checking
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+STORAGE = { }
+
+local save_prefix = 'lvc_'..community_id..'_'
+local repo_version = nil
+local backup_tone_table = {}
+local custom_tone_names = false
+local SIRENS_backup_string = nil
+local profiles = { }
+
+-- forward local fn declaration
+local IsNewerVersion
+
+------------------------------------------------
+--Deletes all saved KVPs for that vehicle profile
+-- This should never be removed. It is the only easy way for end users to delete LVC data.
+RegisterCommand('lvcfactoryreset', function(source, args)
+ local choice = HUD:FrontEndAlert(Lang:t('warning.warning'), Lang:t('warning.factory_reset'), Lang:t('warning.facory_reset_options'))
+ if choice then
+ STORAGE:FactoryReset()
+ end
+end)
+
+function STORAGE:FactoryReset()
+ STORAGE:DeleteKVPs(save_prefix)
+ STORAGE:ResetSettings()
+ UTIL:Print(Lang:t('info.factory_reset_success_console'), true)
+ HUD:ShowNotification(Lang:t('info.factory_reset_success_frontend'), true)
+end
+
+--Prints all KVP keys and values to console
+--if GetResourceMetadata(GetCurrentResourceName(), 'debug_mode', 0) == 'true' then
+ RegisterCommand('lvcdumpkvp', function(source, args)
+ UTIL:Print('^4LVC ^5STORAGE: ^7Dumping KVPs...')
+ local handle = StartFindKvp(save_prefix);
+ local key = FindKvp(handle)
+ while key ~= nil do
+ if GetResourceKvpString(key) ~= nil then
+ UTIL:Print('^4LVC ^5STORAGE Found: ^7"'..key..'" "'..GetResourceKvpString(key)..'", STRING', true)
+ elseif GetResourceKvpInt(key) ~= nil then
+ UTIL:Print('^4LVC ^5STORAGE Found: ^7"'..key..'" "'..GetResourceKvpInt(key)..'", INT', true)
+ elseif GetResourceKvpFloat(key) ~= nil then
+ UTIL:Print('^4LVC ^5STORAGE Found: ^7"'..key..'" "'..GetResourceKvpFloat(key)..'", FLOAT', true)
+ end
+ key = FindKvp(handle)
+ Wait(0)
+ end
+ UTIL:Print('^4LVC ^5STORAGE: ^7Finished Dumping KVPs...')
+ end)
+--end
+------------------------------------------------
+-- Resource Start Initialization
+CreateThread(function()
+ TriggerServerEvent('lvc:GetRepoVersion_s')
+ STORAGE:FindSavedProfiles()
+end)
+
+--[[Function for Deleting KVPs]]
+function STORAGE:DeleteKVPs(prefix)
+ local handle = StartFindKvp(prefix);
+ local key = FindKvp(handle)
+ while key ~= nil do
+ DeleteResourceKvp(key)
+ UTIL:Print('^3LVC Info: Deleting Key \'' .. key .. '\'', true)
+ key = FindKvp(handle)
+ Wait(0)
+ end
+end
+
+--[[Getter for current version used in RageUI.]]
+function STORAGE:GetCurrentVersion()
+ local curr_version = GetResourceMetadata(GetCurrentResourceName(), 'version', 0)
+ if curr_version ~= nil then
+ return curr_version
+ else
+ return 'unknown'
+ end
+end
+
+--[[Getter for repo version used in RageUI.]]
+function STORAGE:GetRepoVersion()
+ return repo_version
+end
+
+--[[Getter for out-of-date notification for RageUI.]]
+function STORAGE:GetIsNewerVersion()
+ return IsNewerVersion(repo_version, STORAGE:GetCurrentVersion())
+end
+
+--[[Saves HUD settings, separated from SaveSettings]]
+function STORAGE:SaveHUDSettings()
+ local hud_save_data = { Show_HUD = HUD:GetHudState(),
+ HUD_Scale = HUD:GetHudScale(),
+ HUD_pos = HUD:GetHudPosition(),
+ HUD_backlight_mode = HUD:GetHudBacklightMode(),
+ }
+ SetResourceKvp(save_prefix .. 'hud_data', json.encode(hud_save_data))
+end
+
+
+--[[Saves all KVP values.]]
+function STORAGE:SaveSettings()
+ UTIL:Print('^4LVC: ^5STORAGE: ^7Saving Settings...')
+ SetResourceKvp(save_prefix..'save_version', STORAGE:GetCurrentVersion())
+
+ --HUD Settings
+ STORAGE:SaveHUDSettings()
+
+ --Tone Names
+ if custom_tone_names then
+ local tone_names = { }
+ for i, siren_pkg in pairs(SIRENS) do
+ table.insert(tone_names, siren_pkg.Name)
+ end
+ SetResourceKvp(save_prefix .. 'tone_names', json.encode(tone_names))
+ UTIL:Print('^4LVC ^5STORAGE: ^7saving '..save_prefix..'tone_names...')
+ end
+
+ --Profile Specific Settings
+ if UTIL:GetVehicleProfileName() ~= nil then
+ local profile_name = string.gsub(UTIL:GetVehicleProfileName(), ' ', '_')
+ if profile_name ~= nil then
+ local tone_options_encoded = json.encode(UTIL:GetToneOptionsTable())
+ local profile_save_data = { PMANU = UTIL:GetToneID('PMANU'),
+ SMANU = UTIL:GetToneID('SMANU'),
+ AUX = UTIL:GetToneID('AUX'),
+ airhorn_intrp = tone_airhorn_intrp,
+ main_reset_standby = tone_main_reset_standby,
+ park_kill = park_kill,
+ tone_options = tone_options_encoded,
+ }
+
+ SetResourceKvp(save_prefix .. 'profile_'..profile_name..'!', json.encode(profile_save_data))
+ UTIL:Print('^4LVC ^5STORAGE: ^7saving '..save_prefix .. 'profile_'..profile_name..'!')
+
+ --Audio Settings
+ local audio_save_data = {
+ radio_masterswitch = AUDIO.radio_masterswitch,
+ button_sfx_scheme = AUDIO.button_sfx_scheme,
+ on_volume = AUDIO.on_volume,
+ off_volume = AUDIO.off_volume,
+ upgrade_volume = AUDIO.upgrade_volume,
+ downgrade_volume = AUDIO.downgrade_volume,
+ activity_reminder_volume = AUDIO.activity_reminder_volume,
+ hazards_volume = AUDIO.hazards_volume,
+ lock_volume = AUDIO.lock_volume,
+ lock_reminder_volume = AUDIO.lock_reminder_volume,
+ airhorn_button_SFX = AUDIO.airhorn_button_SFX,
+ manu_button_SFX = AUDIO.manu_button_SFX,
+ activity_reminder_index = AUDIO:GetActivityReminderIndex(),
+ }
+ SetResourceKvp(save_prefix..'profile_'..profile_name..'_audio_data', json.encode(audio_save_data))
+ UTIL:Print('^4LVC ^5STORAGE: ^7saving profile_'..profile_name..'_audio_data')
+ else
+ HUD:ShowNotification('~b~LVC: ~r~SAVE ERROR~s~: profile_name after gsub is nil.', true)
+ end
+ else
+ HUD:ShowNotification('~b~LVC: ~r~SAVE ERROR~s~: UTIL:GetVehicleProfileName() returned nil.', true)
+ end
+ UTIL:Print('^4LVC ^5STORAGE: ^7Finished Saving Settings...')
+end
+
+------------------------------------------------
+--[[Loads all KVP values.]]
+function STORAGE:LoadSettings(profile_name)
+ UTIL:Print('^4LVC ^5STORAGE: ^7Loading Settings...')
+ local comp_version = GetResourceMetadata(GetCurrentResourceName(), 'compatible', 0)
+ local save_version = GetResourceKvpString(save_prefix .. 'save_version')
+ local incompatible = IsNewerVersion(comp_version, save_version) == 'older'
+
+ --Is save present if so what version
+ if incompatible then
+ AddTextEntry('lvc_mismatch_version','~y~~h~Warning:~h~ ~s~Luxart Vehicle Control Save Version Mismatch.\n~b~Compatible Version: ' .. comp_version .. '\n~o~Save Version: ' .. save_version .. '~s~\nYou may experience issues, to prevent this message from appearing verify settings and resave.')
+ SetNotificationTextEntry('lvc_mismatch_version')
+ DrawNotification(false, true)
+ end
+
+ local hud_save_data = GetResourceKvpString(save_prefix..'hud_data')
+ if hud_save_data ~= nil then
+ hud_save_data = json.decode(hud_save_data)
+ HUD:SetHudState(hud_save_data.Show_HUD)
+ HUD:SetHudScale(hud_save_data.HUD_Scale)
+ HUD:SetHudPosition(hud_save_data.HUD_pos)
+ HUD:SetHudBacklightMode(hud_save_data.HUD_backlight_mode)
+ UTIL:Print('^4LVC ^5STORAGE: ^7loaded HUD data.')
+ end
+
+ if save_version ~= nil then
+ --Tone Names
+ if main_siren_settings_masterswitch then
+ local tone_names = GetResourceKvpString(save_prefix..'tone_names')
+ if tone_names ~= nil then
+ tone_names = json.decode(tone_names)
+ for i, name in pairs(tone_names) do
+ if SIRENS[i] ~= nil then
+ SIRENS[i].Name = name
+ end
+ end
+ end
+ UTIL:Print('^4LVC ^5STORAGE: ^7loaded custom tone names.')
+ end
+
+ --Profile Specific Settings
+ if UTIL:GetVehicleProfileName() ~= false then
+ local profile_name = profile_name or string.gsub(UTIL:GetVehicleProfileName(), ' ', '_')
+ if profile_name ~= nil then
+ local profile_save_data = GetResourceKvpString(save_prefix..'profile_'..profile_name..'!')
+ if profile_save_data ~= nil then
+ profile_save_data = json.decode(profile_save_data)
+ UTIL:SetToneByID('PMANU', profile_save_data.PMANU)
+ UTIL:SetToneByID('SMANU', profile_save_data.SMANU)
+ UTIL:SetToneByID('AUX', profile_save_data.AUX)
+ if main_siren_settings_masterswitch then
+ tone_airhorn_intrp = profile_save_data.airhorn_intrp
+ tone_main_reset_standby = profile_save_data.main_reset_standby
+ park_kill = profile_save_data.park_kill
+ local tone_options = json.decode(profile_save_data.tone_options)
+ if tone_options ~= nil then
+ for tone_id, option in pairs(tone_options) do
+ tone_id = tonumber(tone_id)
+ option = tonumber(option)
+ if SIRENS[tone_id] ~= nil then
+ UTIL:SetToneOption(tone_id, option)
+ end
+ end
+ end
+ end
+ UTIL:Print('^4LVC ^5STORAGE: ^7loaded '..profile_name..'.')
+ end
+ --Audio Settings
+ local audio_save_data = GetResourceKvpString(save_prefix..'profile_'..profile_name..'_audio_data')
+ if audio_save_data ~= nil then
+ audio_save_data = json.decode(audio_save_data)
+ if audio_save_data.radio_masterswitch ~= nil then
+ AUDIO.radio_masterswitch = audio_save_data.radio_masterswitch
+ end
+ AUDIO.button_sfx_scheme = audio_save_data.button_sfx_scheme
+ AUDIO.on_volume = audio_save_data.on_volume
+ AUDIO.off_volume = audio_save_data.off_volume
+ AUDIO.upgrade_volume = audio_save_data.upgrade_volume
+ AUDIO.downgrade_volume = audio_save_data.downgrade_volume
+ AUDIO.activity_reminder_volume = audio_save_data.activity_reminder_volume
+ AUDIO.hazards_volume = audio_save_data.hazards_volume
+ AUDIO.lock_volume = audio_save_data.lock_volume
+ AUDIO.lock_reminder_volume = audio_save_data.lock_reminder_volume
+ AUDIO.airhorn_button_SFX = audio_save_data.airhorn_button_SFX
+ AUDIO.manu_button_SFX = audio_save_data.manu_button_SFX
+ AUDIO:SetActivityReminderIndex(audio_save_data.activity_reminder_index)
+ UTIL:Print('^4LVC ^5STORAGE: ^7loaded audio data.')
+ end
+ else
+ HUD:ShowNotification('~b~LVC:~r~ LOADING ERROR~s~: profile_name after gsub is nil.', true)
+ end
+ end
+ end
+ UTIL:Print('^4LVC ^5STORAGE: ^7Finished Loading Settings...')
+end
+
+------------------------------------------------
+--[[Resets all KVP/menu values to their default.]]
+function STORAGE:ResetSettings()
+ UTIL:Print('^4LVC ^5STORAGE: ^7Resetting Settings...')
+
+ --Storage State
+ custom_tone_names = false
+ profiles = { }
+ STORAGE:FindSavedProfiles()
+
+ --LVC State
+ key_lock = false
+ tone_main_reset_standby = reset_to_standby_default
+ tone_airhorn_intrp = airhorn_interrupt_default
+ park_kill = park_kill_default
+
+ --HUD State
+ HUD:SetHudState(hud_first_default)
+ HUD:SetHudScale(0.7)
+ HUD:ResetPosition()
+ HUD:SetHudBacklightMode(1)
+
+ --Extra Tone Resets
+ UTIL:SetToneByPos('ARHRN', 1)
+ UTIL:SetToneByPos('PMANU', 2)
+ UTIL:SetToneByPos('SMANU', 3)
+ UTIL:SetToneByPos('AUX', 2)
+ UTIL:SetToneByPos('MAIN_MEM', 2)
+
+ STORAGE:RestoreBackupTable()
+ UTIL:BuildToneOptions()
+
+ --Audio Settings
+ AUDIO.radio_masterswitch = true
+ AUDIO.airhorn_button_SFX = false
+ AUDIO.manu_button_SFX = false
+ AUDIO:SetActivityReminderIndex(1)
+
+ AUDIO.button_sfx_scheme = default_sfx_scheme_name
+ AUDIO.on_volume = default_on_volume
+ AUDIO.off_volume = default_off_volume
+ AUDIO.upgrade_volume = default_upgrade_volume
+ AUDIO.downgrade_volume = default_downgrade_volume
+ AUDIO.hazards_volume = default_hazards_volume
+ AUDIO.lock_volume = default_lock_volume
+ AUDIO.lock_reminder_volume = default_lock_reminder_volume
+ AUDIO.activity_reminder_volume = default_reminder_volume
+ UTIL:Print('^4LVC ^5STORAGE: ^7Finished Resetting Settings...')
+end
+
+------------------------------------------------
+--[[Find all profile names of all saved KVP.]]
+function STORAGE:FindSavedProfiles()
+ local handle = StartFindKvp(save_prefix..'profile_');
+ local key = FindKvp(handle)
+ while key ~= nil do
+ if string.match(key, '(.*)!$') then
+ local saved_profile_name = string.match(key, save_prefix..'profile_(.*)!$')
+
+ --Duplicate checking
+ local found = false
+ for _, profile_name in ipairs(profiles) do
+ if profile_name == saved_profile_name then
+ found = true
+ end
+ end
+
+ if not found then
+ table.insert(profiles, saved_profile_name)
+ end
+ end
+ key = FindKvp(handle)
+ Wait(0)
+ end
+end
+
+function STORAGE:GetSavedProfiles()
+ local cur_profile = UTIL:GetVehicleProfileName()
+ for i, profile in ipairs(profiles) do
+ if profile == cur_profile then
+ table.remove(profiles, i)
+ end
+ end
+
+ return profiles
+end
+------------------------------------------------
+--[[Setter for JSON string backup of SIRENS table in case of reset since we modify SIREN table directly.]]
+function STORAGE:SetBackupTable()
+ SIRENS_backup_string = json.encode(SIRENS)
+end
+
+--[[Setter for SIRENS table using backup string of table.]]
+function STORAGE:RestoreBackupTable()
+ SIRENS = json.decode(SIRENS_backup_string)
+end
+
+--[[Setter for bool that is used in saving to determine if tone strings have been modified.]]
+function STORAGE:SetCustomToneStrings(toggle)
+ custom_tone_names = toggle
+end
+
+------------------------------------------------
+--HELPER FUNCTIONS for main siren settings saving:end
+--Compare Version Strings: Is version newer than test_version
+IsNewerVersion = function(version, test_version)
+ if version == nil or test_version == nil then
+ return 'unknown'
+ end
+
+ if type(version) == 'string' then
+ version = semver(version)
+ end
+ if type(test_version) == 'string' then
+ test_version = semver(test_version)
+ end
+
+ if version > test_version then
+ return 'older'
+ elseif version < test_version then
+ return 'newer'
+ elseif version == test_version then
+ return 'equal'
+ end
+end
+
+---------------------------------------------------------------------
+--[[Callback for Server -> Client version update.]]
+RegisterNetEvent('lvc:SendRepoVersion_c')
+AddEventHandler('lvc:SendRepoVersion_c', function(version)
+ repo_version = version
+end)
\ No newline at end of file
diff --git a/resources/lvc/UTIL/cl_utils.lua b/resources/lvc/UTIL/cl_utils.lua
new file mode 100644
index 000000000..23526ebdf
--- /dev/null
+++ b/resources/lvc/UTIL/cl_utils.lua
@@ -0,0 +1,434 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: cl_utils.lua
+PURPOSE: Utilities for siren assignments and tables
+ and other common functions.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+UTIL = { }
+
+local approved_tones = nil
+local tone_options = { }
+local tone_table_names_ids = { }
+local profile = nil
+local tone_main_mem_id = nil
+local tone_PMANU_id = nil
+local tone_SMANU_id = nil
+local tone_AUX_id = nil
+local tone_ARHRN_id = nil
+
+---------------------------------------------------------------------
+--[[Return sub-table for sirens or plugin settings tables, given veh, and name of whatever setting.]]
+function UTIL:GetProfileFromTable(print_name, tbl, veh, ignore_missing_default)
+ local ignore_missing_default = ignore_missing_default or false
+ local veh_name = GetDisplayNameFromVehicleModel(GetEntityModel(veh))
+ local lead_and_trail_wildcard = veh_name:gsub('%d+', '#')
+ local lead = veh_name:match('%d*%a+')
+ local trail = veh_name:gsub(lead, ''):gsub('%d+', '#')
+ local trail_only_wildcard = string.format('%s%s', lead, trail)
+
+ local profile_table, profile
+ if tbl ~= nil then
+ if tbl[veh_name] ~= nil then --Does profile exist as outlined in vehicle.meta
+ profile_table = tbl[veh_name]
+ profile = veh_name
+ UTIL:Print(Lang:t('info.profile_found', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, profile = profile, model = veh_name}))
+ elseif tbl[trail_only_wildcard] ~= nil then --Does profile exist using # as wildcard for any trailing digits.
+ profile_table = tbl[trail_only_wildcard]
+ profile = trail_only_wildcard
+ UTIL:Print(Lang:t('info.profile_found', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, profile = profile, model = veh_name}))
+ elseif tbl[lead_and_trail_wildcard] ~= nil then --Does profile exist using # as wildcard for any digits.
+ profile_table = tbl[lead_and_trail_wildcard]
+ profile = lead_and_trail_wildcard
+ UTIL:Print(Lang:t('info.profile_found', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, profile = profile, model = veh_name}))
+ else
+ if tbl['DEFAULT'] ~= nil then
+ profile_table = tbl['DEFAULT']
+ profile = 'DEFAULT'
+ UTIL:Print(Lang:t('info.profile_default_console', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, model = veh_name}))
+ if print_name == 'SIRENS' then
+ HUD:ShowNotification(Lang:t('info.profile_default_frontend', {model = veh_name}))
+ end
+ else
+ profile_table = { }
+ profile = false
+ if not ignore_missing_default then
+ UTIL:Print(Lang:t('warning.profile_missing', {ver = STORAGE:GetCurrentVersion(), tbl = print_name, model = veh_name}), true)
+ end
+ end
+ end
+ else
+ profile_table = { }
+ profile = false
+ HUD:ShowNotification(Lang:t('error.profile_nil_table', {tbl = print_name}), true)
+ UTIL:Print(Lang:t('error.profile_nil_table_console', {ver = STORAGE:GetCurrentVersion(), tbl = print_name}), true)
+ end
+
+ return profile_table, profile
+end
+
+---------------------------------------------------------------------
+--[[Shorten oversized strings in SIREN_ASSIGNMENTS (SIRENS.LUA).
+ GTA only allows 11 characters. So to reduce confusion we'll shorten it if the user does not.]]
+function UTIL:FixOversizeKeys(TABLE)
+ for i, tbl in pairs(TABLE) do
+ if string.len(i) > 11 then
+ local shortened_gameName = string.sub(i,1,11)
+ TABLE[shortened_gameName] = TABLE[i]
+ TABLE[i] = nil
+ end
+ end
+end
+
+---------------------------------------------------------------------
+--[[Sets profile name and approved_tones table a copy of SIREN_ASSIGNMENTS for this vehicle]]
+function UTIL:UpdateApprovedTones(veh)
+ approved_tones, profile = UTIL:GetProfileFromTable('SIRENS', SIREN_ASSIGNMENTS, veh)
+
+ if profile == false then
+ UTIL:Print(Lang:t('error.profile_none_found_console', {game_name = GetDisplayNameFromVehicleModel(GetEntityModel(veh))}), true)
+ HUD:ShowNotification(Lang:t('error.profile_none_found_frontend'), true)
+ end
+
+ if profile then
+ if not UTIL:IsApprovedTone('MAIN_MEM') then
+ UTIL:SetToneByPos('MAIN_MEM', 2)
+ end
+ if not UTIL:IsApprovedTone('PMANU') then
+ UTIL:SetToneByPos('PMANU', 2)
+ end
+ if not UTIL:IsApprovedTone('SMANU') then
+ UTIL:SetToneByPos('SMANU', 3)
+ end
+ if not UTIL:IsApprovedTone('AUX') then
+ UTIL:SetToneByPos('AUX', 2)
+ end
+ if not UTIL:IsApprovedTone('ARHRN') then
+ UTIL:SetToneByPos('ARHRN', 1)
+ end
+ end
+end
+
+--[[Getter for approved_tones table, used in RageUI]]
+function UTIL:GetApprovedTonesTable()
+ if approved_tones == nil then
+ if veh ~= nil then
+ UpdateApprovedTones(veh)
+ else
+ UpdateApprovedTones('DEFAULT')
+ end
+ end
+ return approved_tones
+end
+---------------------------------------------------------------------
+--[[Builds a table that we store tone_options in (disabled, button & cycle, cycle only, button only).
+ Users can set default option of siren by using optional index .Option in SIREN_ASSIGNMENTS table in SIRENS.LUA]]
+function UTIL:BuildToneOptions()
+ local temp_array = { }
+ local option
+ for i, id in pairs(approved_tones) do
+ if SIRENS[id] ~= nil then
+ option = SIRENS[id].Option or 1
+ temp_array[id] = option
+ end
+ end
+ tone_options = temp_array
+end
+
+--Setter for single tone_option
+function UTIL:SetToneOption(tone_id, option)
+ tone_options[tone_id] = option
+end
+
+--Getter for single tone_option
+function UTIL:GetToneOption(tone_id)
+ return tone_options[tone_id]
+end
+
+--Getter for tone_options table (used for saving)
+function UTIL:GetToneOptionsTable()
+ return tone_options
+end
+---------------------------------------------------------------------
+--[[RageUI requires a specific table layout, this builds it according to SIREN_ASSIGNMENTS > approved_tones.]]
+function UTIL:GetApprovedTonesTableNameAndID()
+ local temp_array = { }
+ for i, tone_id in pairs(approved_tones) do
+ if i ~= 1 then
+ table.insert(temp_array, { Name = SIRENS[tone_id].Name, Value = tone_id } )
+ end
+ end
+ return temp_array
+end
+
+---------------------------------------------------------------------
+--[[Getter for tone id by passing string abbreviation (MAIN_MEM, PMANU, etc.)]]
+function UTIL:GetToneID(tone_string)
+ if tone_string == 'MAIN_MEM' then
+ return tone_main_mem_id
+ elseif tone_string == 'PMANU' then
+ return tone_PMANU_id
+ elseif tone_string == 'SMANU' then
+ return tone_SMANU_id
+ elseif tone_string == 'AUX' then
+ return tone_AUX_id
+ elseif tone_string == 'ARHRN' then
+ return tone_ARHRN_id
+ end
+end
+
+--[[Setter for ToneID by passing string abbreviation of tone (MAIN_MEM, PMANU, etc.) and position of desired tone in approved_tones.]]
+function UTIL:SetToneByPos(tone_string, pos)
+ if profile then
+ if approved_tones[pos] ~= nil then
+ if tone_string == 'MAIN_MEM' then
+ tone_main_mem_id = approved_tones[pos]
+ elseif tone_string == 'PMANU' then
+ tone_PMANU_id = approved_tones[pos]
+ elseif tone_string == 'SMANU' then
+ tone_SMANU_id = approved_tones[pos]
+ elseif tone_string == 'AUX' then
+ tone_AUX_id = approved_tones[pos]
+ elseif tone_string == 'ARHRN' then
+ tone_ARHRN_id = approved_tones[pos]
+ end
+ else
+ HUD:ShowNotification(Lang:t('warning.too_few_tone_frontend', {code = 403}), false)
+ UTIL:Print(Lang:t('warning.too_few_tone_console', {ver = STORAGE:GetCurrentVersion(), code = 403, tone_string = tone_string, pos = pos}), true)
+ end
+ else
+ HUD:ShowNotification(Lang:t('warning.tone_position_nil_frontend', {code = 404}), false)
+ UTIL:Print(Lang:t('warning.tone_position_nil_console', {ver = STORAGE:GetCurrentVersion(), code = 404, tone_string = tone_string, pos = pos}), true)
+ end
+end
+
+--[[Getter for position of passed tone string. Used in RageUI for P/S MANU and AUX Siren.]]
+function UTIL:GetTonePos(tone_string)
+ local current_id = UTIL:GetToneID(tone_string)
+ for i, tone_id in pairs(approved_tones) do
+ if tone_id == current_id then
+ return i
+ end
+ end
+ return -1
+end
+
+--[[Getter for Tone ID at index/pos in approved_tones]]
+function UTIL:GetToneAtPos(pos)
+ if approved_tones[pos] ~= nil then
+ return approved_tones[pos]
+ end
+ return nil
+end
+
+
+--[[Setter for ToneID by passing string abbreviation of tone (MAIN_MEM, PMANU, etc.) and specific ID.]]
+function UTIL:SetToneByID(tone_string, tone_id)
+ if UTIL:IsApprovedTone(tone_id) then
+ if tone_string == 'MAIN_MEM' then
+ tone_main_mem_id = tone_id
+ elseif tone_string == 'PMANU' then
+ tone_PMANU_id = tone_id
+ elseif tone_string == 'SMANU' then
+ tone_SMANU_id = tone_id
+ elseif tone_string == 'AUX' then
+ tone_AUX_id = tone_id
+ elseif tone_string == 'ARHRN' then
+ tone_ARHRN_id = tone_id
+ end
+ else
+ HUD:ShowNotification(Lang:t('warning.tone_id_nil_frontend', {ver = STORAGE:GetCurrentVersion()}), false)
+ UTIL:Print(Lang:t('warning.tone_id_nil_console', {ver = STORAGE:GetCurrentVersion(), tone_string = tone_string, tone_id = tone_id}), true)
+ end
+end
+
+---------------------------------------------------------------------
+--[[Gets next tone based off vehicle profile and current tone.]]
+function UTIL:GetNextSirenTone(current_tone, veh, main_tone, last_pos)
+ local main_tone = main_tone or false
+ local last_pos = last_pos or nil
+ local result
+
+ if last_pos == nil then
+ for i, tone_id in pairs(approved_tones) do
+ if tone_id == current_tone then
+ temp_pos = i
+ break
+ end
+ end
+ else
+ temp_pos = last_pos
+ end
+
+ if temp_pos < #approved_tones then
+ temp_pos = temp_pos+1
+ result = approved_tones[temp_pos]
+ else
+ temp_pos = 2
+ result = approved_tones[2]
+ end
+
+ if main_tone then
+ --Check if the tone is set to 'disable' or 'button-only' if so, find next tone
+ if tone_options[result] > 2 then
+ result = UTIL:GetNextSirenTone(result, veh, main_tone, temp_pos)
+ end
+ end
+
+ return result
+end
+
+---------------------------------------------------------------------
+--[[Get count of approved tones used when mapping RegisteredKeys]]
+function UTIL:GetToneCount()
+ return #approved_tones
+end
+
+---------------------------------------------------------------------
+--[[Ensure not all sirens are disabled / button only]]
+function UTIL:IsOkayToDisable()
+ local count = 0
+ for i, option in pairs(tone_options) do
+ if i ~= 1 then
+ if option < 3 then
+ count = count + 1
+ end
+ end
+ end
+ if count > 1 then
+ return true
+ end
+ return false
+end
+
+------------------------------------------------
+--[[Handle changing of tone_table custom names]]
+function UTIL:ChangeToneString(tone_id, new_name)
+ STORAGE:SetCustomToneStrings(true)
+ SIRENS[tone_id].Name = new_name
+end
+
+------------------------------------------------
+--[[Used to verify tone is allowed before playing.]]
+function UTIL:IsApprovedTone(tone)
+ for i, approved_tone in ipairs(approved_tones) do
+ if approved_tone == tone then
+ return true
+ end
+ end
+ return false
+end
+
+---------------------------------------------------------------------
+--[[Returns String used for saving, loading, and debugging]]
+function UTIL:GetVehicleProfileName()
+ return profile
+end
+
+---------------------------------------------------------------------
+--[[Prints to FiveM console, prints more when debug flag is enabled or overridden for important information]]
+function UTIL:Print(string, override)
+ override = override or false
+ if debug_mode or override then
+ print(string)
+ end
+end
+
+---------------------------------------------------------------------
+--[[Finds index of element in table given table and element.]]
+function UTIL:IndexOf(tbl, tgt)
+ for i, v in pairs(tbl) do
+ if v == tgt then
+ return i
+ end
+ end
+ return nil
+end
+
+---------------------------------------------------------------------
+--[[This function looks like #!*& for user convenience (and my lack of skill or abundance of laziness),
+ it is called when needing to change an extra, it allows users to do things like [''] = { Brake = 1 } while
+ also allowing advanced users to write configs like this [''] = { Brake = { add = { 3, 4 }, remove = { 5, 6 }, repair = true } }
+ which can add and remove multiple different extras at once and adds flag to repair the vehicle
+ for extras that are too large and require the vehicle to be reloaded. Once it figures out the
+ users config layout it calls itself again (recursive) with the id we actually need toggled right now.]]
+function UTIL:TogVehicleExtras(veh, extra_id, state, repair)
+ local repair = repair or false
+ if type(extra_id) == 'table' then
+ -- Toggle Same Extras Mode
+ if extra_id.toggle ~= nil then
+ -- Toggle Multiple Extras
+ if type(extra_id.toggle) == 'table' then
+ for i, singe_extra_id in ipairs(extra_id.toggle) do
+ UTIL:TogVehicleExtras(veh, singe_extra_id, state, extra_id.repair)
+ end
+ -- Toggle a Single Extra (no table)
+ else
+ UTIL:TogVehicleExtras(veh, extra_id.toggle, state, extra_id.repair)
+ end
+ -- Toggle Different Extras Mode
+ elseif extra_id.add ~= nil and extra_id.remove ~= nil then
+ if type(extra_id.add) == 'table' then
+ for i, singe_extra_id in ipairs(extra_id.add) do
+ UTIL:TogVehicleExtras(veh, singe_extra_id, state, extra_id.repair)
+ end
+ else
+ UTIL:TogVehicleExtras(veh, extra_id.add, state, extra_id.repair)
+ end
+ if type(extra_id.remove) == 'table' then
+ for i, singe_extra_id in ipairs(extra_id.remove) do
+ UTIL:TogVehicleExtras(veh, singe_extra_id, not state, extra_id.repair)
+ end
+ else
+ UTIL:TogVehicleExtras(veh, extra_id.remove, not state, extra_id.repair)
+ end
+ end
+ else
+ if state then
+ if not IsVehicleExtraTurnedOn(veh, extra_id) then
+ local doors = { }
+ if repair then
+ for i = 0,6 do
+ doors[i] = GetVehicleDoorAngleRatio(veh, i)
+ end
+ end
+ SetVehicleAutoRepairDisabled(veh, not repair)
+ SetVehicleExtra(veh, extra_id, false)
+ UTIL:Print(Lang:t('info.extra_on', {extra = extra_id}), false)
+ SetVehicleAutoRepairDisabled(veh, false)
+ if repair then
+ for i = 0,6 do
+ if doors[i] > 0.0 then
+ SetVehicleDoorOpen(veh, i, true, false)
+ end
+ end
+ end
+ end
+ else
+ if IsVehicleExtraTurnedOn(veh, extra_id) then
+ SetVehicleExtra(veh, extra_id, true)
+ UTIL:Print(Lang:t('info.extra_off', {extra = extra_id}), false)
+ end
+ end
+ end
+ SetVehicleAutoRepairDisabled(veh, false)
+end
\ No newline at end of file
diff --git a/resources/lvc/UTIL/semver.lua b/resources/lvc/UTIL/semver.lua
new file mode 100644
index 000000000..8bcd88409
--- /dev/null
+++ b/resources/lvc/UTIL/semver.lua
@@ -0,0 +1,232 @@
+--[[
+Checkout semver.lua semantic versioning library for LUA
+https://github.com/kikito/semver.lua
+
+Copyright (c) 2011 Enrique García Cota
+
+MIT LICENSE
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+]]
+
+semver = {
+ _VERSION = '1.2.1',
+ _DESCRIPTION = 'semver for Lua',
+ _URL = 'https://github.com/kikito/semver.lua',
+ _LICENSE = [[
+ MIT LICENSE
+ Copyright (c) 2015 Enrique García Cota
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of tother software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+ The above copyright notice and tother permission notice shall be included
+ in all copies or substantial portions of the Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ ]]
+}
+
+local function checkPositiveInteger(number, name)
+ assert(number >= 0, name .. ' must be a valid positive number')
+ assert(math.floor(number) == number, name .. ' must be an integer')
+end
+
+local function present(value)
+ return value and value ~= ''
+end
+
+-- splitByDot("a.bbc.d") == {"a", "bbc", "d"}
+local function splitByDot(str)
+ str = str or ""
+ local t, count = {}, 0
+ str:gsub("([^%.]+)", function(c)
+ count = count + 1
+ t[count] = c
+ end)
+ return t
+end
+
+local function parsePrereleaseAndBuildWithSign(str)
+ local prereleaseWithSign, buildWithSign = str:match("^(-[^+]+)(+.+)$")
+ if not (prereleaseWithSign and buildWithSign) then
+ prereleaseWithSign = str:match("^(-.+)$")
+ buildWithSign = str:match("^(+.+)$")
+ end
+ assert(prereleaseWithSign or buildWithSign, ("The parameter %q must begin with + or - to denote a prerelease or a build"):format(str))
+ return prereleaseWithSign, buildWithSign
+end
+
+local function parsePrerelease(prereleaseWithSign)
+ if prereleaseWithSign then
+ local prerelease = prereleaseWithSign:match("^-(%w[%.%w-]*)$")
+ assert(prerelease, ("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(prereleaseWithSign))
+ return prerelease
+ end
+end
+
+local function parseBuild(buildWithSign)
+ if buildWithSign then
+ local build = buildWithSign:match("^%+(%w[%.%w-]*)$")
+ assert(build, ("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(buildWithSign))
+ return build
+ end
+end
+
+local function parsePrereleaseAndBuild(str)
+ if not present(str) then return nil, nil end
+
+ local prereleaseWithSign, buildWithSign = parsePrereleaseAndBuildWithSign(str)
+
+ local prerelease = parsePrerelease(prereleaseWithSign)
+ local build = parseBuild(buildWithSign)
+
+ return prerelease, build
+end
+
+local function parseVersion(str)
+ local sMajor, sMinor, sPatch, sPrereleaseAndBuild = str:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")
+ assert(type(sMajor) == 'string', ("Could not extract version number(s) from %q"):format(str))
+ local major, minor, patch = tonumber(sMajor), tonumber(sMinor), tonumber(sPatch)
+ local prerelease, build = parsePrereleaseAndBuild(sPrereleaseAndBuild)
+ return major, minor, patch, prerelease, build
+end
+
+
+-- return 0 if a == b, -1 if a < b, and 1 if a > b
+local function compare(a,b)
+ return a == b and 0 or a < b and -1 or 1
+end
+
+local function compareIds(myId, otherId)
+ if myId == otherId then return 0
+ elseif not myId then return -1
+ elseif not otherId then return 1
+ end
+
+ local selfNumber, otherNumber = tonumber(myId), tonumber(otherId)
+
+ if selfNumber and otherNumber then -- numerical comparison
+ return compare(selfNumber, otherNumber)
+ -- numericals are always smaller than alphanums
+ elseif selfNumber then
+ return -1
+ elseif otherNumber then
+ return 1
+ else
+ return compare(myId, otherId) -- alphanumerical comparison
+ end
+end
+
+local function smallerIdList(myIds, otherIds)
+ local myLength = #myIds
+ local comparison
+
+ for i=1, myLength do
+ comparison = compareIds(myIds[i], otherIds[i])
+ if comparison ~= 0 then
+ return comparison == -1
+ end
+ -- if comparison == 0, continue loop
+ end
+
+ return myLength < #otherIds
+end
+
+local function smallerPrerelease(mine, other)
+ if mine == other or not mine then return false
+ elseif not other then return true
+ end
+
+ return smallerIdList(splitByDot(mine), splitByDot(other))
+end
+
+local methods = {}
+
+function methods:nextMajor()
+ return semver(self.major + 1, 0, 0)
+end
+function methods:nextMinor()
+ return semver(self.major, self.minor + 1, 0)
+end
+function methods:nextPatch()
+ return semver(self.major, self.minor, self.patch + 1)
+end
+
+local mt = { __index = methods }
+function mt:__eq(other)
+ return self.major == other.major and
+ self.minor == other.minor and
+ self.patch == other.patch and
+ self.prerelease == other.prerelease
+ -- notice that build is ignored for precedence in semver 2.0.0
+end
+function mt:__lt(other)
+ if self.major ~= other.major then return self.major < other.major end
+ if self.minor ~= other.minor then return self.minor < other.minor end
+ if self.patch ~= other.patch then return self.patch < other.patch end
+ return smallerPrerelease(self.prerelease, other.prerelease)
+ -- notice that build is ignored for precedence in semver 2.0.0
+end
+-- This works like the "pessimisstic operator" in Rubygems.
+-- if a and b are versions, a ^ b means "b is backwards-compatible with a"
+-- in other words, "it's safe to upgrade from a to b"
+function mt:__pow(other)
+ if self.major == 0 then
+ return self == other
+ end
+ return self.major == other.major and
+ self.minor <= other.minor
+end
+function mt:__tostring()
+ local buffer = { ("%d.%d.%d"):format(self.major, self.minor, self.patch) }
+ if self.prerelease then table.insert(buffer, "-" .. self.prerelease) end
+ if self.build then table.insert(buffer, "+" .. self.build) end
+ return table.concat(buffer)
+end
+
+local function new(major, minor, patch, prerelease, build)
+ assert(major, "At least one parameter is needed")
+
+ if type(major) == 'string' then
+ major,minor,patch,prerelease,build = parseVersion(major)
+ end
+ patch = patch or 0
+ minor = minor or 0
+
+ checkPositiveInteger(major, "major")
+ checkPositiveInteger(minor, "minor")
+ checkPositiveInteger(patch, "patch")
+
+ local result = {major=major, minor=minor, patch=patch, prerelease=prerelease, build=build}
+ return setmetatable(result, mt)
+end
+
+setmetatable(semver, { __call = function(_, ...) return new(...) end })
+semver._VERSION= semver(semver._VERSION)
+
+return semver
diff --git a/resources/lvc/UTIL/sv_lvc.lua b/resources/lvc/UTIL/sv_lvc.lua
new file mode 100644
index 000000000..0e7ae1e04
--- /dev/null
+++ b/resources/lvc/UTIL/sv_lvc.lua
@@ -0,0 +1,190 @@
+--[[
+---------------------------------------------------
+LUXART VEHICLE CONTROL V3 (FOR FIVEM)
+---------------------------------------------------
+Coded by Lt.Caine
+ELS Clicks by Faction
+Additional Modification by TrevorBarns
+---------------------------------------------------
+FILE: server.lua
+PURPOSE: Handle version checking, syncing vehicle
+states.
+---------------------------------------------------
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+---------------------------------------------------
+]]
+
+local experimental = GetResourceMetadata(GetCurrentResourceName(), 'experimental', 0) == 'true'
+local beta_checking = GetResourceMetadata(GetCurrentResourceName(), 'beta_checking', 0) == 'true'
+local curr_version = semver(GetResourceMetadata(GetCurrentResourceName(), 'version', 0))
+local repo_version = ''
+local repo_beta_version = ''
+
+local plugin_count = 0
+local plugins_cv = { } -- table of active plugins current versions plugins_cv = { [''] = }
+local plugins_rv = { } -- table of active plugins repository versions
+
+---------------VEHICLE STATE EVENTS----------------
+RegisterServerEvent('lvc:GetRepoVersion_s')
+AddEventHandler('lvc:GetRepoVersion_s', function()
+ TriggerClientEvent('lvc:SendRepoVersion_c', source, repo_version)
+end)
+
+RegisterServerEvent('lvc:TogDfltSrnMuted_s')
+AddEventHandler('lvc:TogDfltSrnMuted_s', function()
+ TriggerClientEvent('lvc:TogDfltSrnMuted_c', -1, source)
+end)
+
+
+RegisterServerEvent('lvc:SetLxSirenState_s')
+AddEventHandler('lvc:SetLxSirenState_s', function(newstate)
+ TriggerClientEvent('lvc:SetLxSirenState_c', -1, source, newstate)
+end)
+
+RegisterServerEvent('lvc:SetPwrcallState_s')
+AddEventHandler('lvc:SetPwrcallState_s', function(newstate)
+ TriggerClientEvent('lvc:SetPwrcallState_c', -1, source, newstate)
+end)
+
+RegisterServerEvent('lvc:SetAirManuState_s')
+AddEventHandler('lvc:SetAirManuState_s', function(newstate)
+ TriggerClientEvent('lvc:SetAirManuState_c', -1, source, newstate)
+end)
+
+RegisterServerEvent('lvc:TogIndicState_s')
+AddEventHandler('lvc:TogIndicState_s', function(newstate)
+ TriggerClientEvent('lvc:TogIndicState_c', -1, source, newstate)
+end)
+
+-------------VERSION CHECKING & STARTUP------------
+RegisterServerEvent('lvc:plugins_storePluginVersion')
+AddEventHandler('lvc:plugins_storePluginVersion', function(name, version)
+ plugin_count = plugin_count + 1
+ plugins_cv[name] = version
+end)
+
+
+CreateThread( function()
+-- Get LVC version from github
+ PerformHttpRequest('https://raw.githubusercontent.com/TrevorBarns/luxart-vehicle-control/master/version', function(err, responseText, headers)
+ if responseText ~= nil and responseText ~= '' then
+ repo_version = semver(responseText:gsub('\n', ''))
+ end
+ end)
+-- Get LVC beta version from github
+ PerformHttpRequest('https://raw.githubusercontent.com/TrevorBarns/luxart-vehicle-control/master/beta_version', function(err, responseText, headers)
+ if responseText ~= nil and responseText ~= '' then
+ repo_beta_version = semver(responseText:gsub('\n', ''))
+ end
+ end)
+
+ Wait(1000)
+ -- Get currently installed plugin versions (plugins -> 'lvc:plugins_storePluginVersion')
+ TriggerEvent('lvc:plugins_getVersions')
+
+ -- Get repo version for installed plugins
+ for name, _ in pairs(plugins_cv) do
+ PerformHttpRequest('https://raw.githubusercontent.com/TrevorBarns/luxart-vehicle-control/master/PLUGINS/'..name..'/version', function(err, responseText, headers)
+ if responseText ~= nil and responseText ~= '' then
+ plugins_rv[name] = responseText:gsub('\n', '')
+ else
+ plugins_rv[name] = 'UNKWN'
+ end
+ end)
+ end
+ Wait(1000)
+ print('\n\t^7 ________________________________________________________')
+ print('\t|\t^8 __ ^9___ ^7|')
+ print('\t|\t^8 / / ^7 /\\ /\\ ^9/ __\\ ^7|')
+ print('\t|\t^8 / / ^7\\ \\ / / ^9/ / ^7|')
+ print('\t|\t^8 / /___ ^7\\ V / ^9/ /___ ^7|')
+ print('\t|\t^8 \\____/uxart ^7\\_/ ehicle ^9\\____/ontrol ^7|')
+ print('\t|\t |')
+ print(('\t|\t COMMUNITY ID: %-23s|'):format(community_id))
+ print('\t^7|________________________________________________________|')
+ print(('\t|\t INSTALLED: %-27s|'):format(curr_version))
+ if not beta_checking then
+ print(('\t|\t LATEST: %-27s|'):format(repo_version))
+ else
+ if curr_version < repo_beta_version then
+ print(('\t|\t ^3LATEST BETA: %-27s^7|'):format(repo_beta_version))
+ end
+ print(('\t|\t LATEST STABLE: %-27s|'):format(repo_version))
+ end
+ if GetResourceState('lux_vehcontrol') ~= 'started' and GetResourceState('lux_vehcontrol') ~= 'starting' then
+ if GetCurrentResourceName() == 'lvc' then
+ if community_id ~= nil and community_id ~= '' then
+ -- STABLE UPDATE DETECTED
+ if curr_version < repo_version then
+ print('\t^7|________________________________________________________|')
+ print('\t|\t ^8STABLE UPDATE AVAILABLE ^7|')
+ print('\t|^8 DOWNLOAD AT: ^7|')
+ print('\t|^2 github.com/TrevorBarns/luxart-vehicle-control/releases ^7|')
+ elseif beta_checking and curr_version < repo_beta_version then
+ print('\t^7|________________________________________________________|')
+ print('\t|\t ^4BETA UPDATE AVAILABLE ^7|')
+ print('\t|^4 DOWNLOAD AT: ^7|')
+ print('\t|^2 github.com/TrevorBarns/luxart-vehicle-control/releases ^7|')
+ -- EXPERMENTAL VERSION
+ elseif curr_version > repo_version or curr_version == repo_beta_version then
+ print('\t^7|________________________________________________________|')
+ print('\t|\t ^3BETA VERSION ^7|')
+ -- IS THE USER AWARE THEY DOWNLOADED EXPERMENTAL CHECK CONVARS
+ if not experimental then
+ print('\t|^3 THIS VERSION IS IN DEVELOPMENT AND IS NOT RECOMMENDED ^7|')
+ print('\t|^3 BUGS MAY EXIST. IF THIS WAS A MISTAKE DOWNLOAD THE ^7|')
+ print('\t|^3 LATEST STABLE RELEASE AT: ^7|')
+ print('\t|^2 github.com/TrevorBarns/luxart-vehicle-control/releases ^7|')
+ print('\t|^3 TO MUTE THIS: SET CONVAR \'experimental\' to \'true\' ^7|')
+ end
+ end
+
+ -- IF PLUGINS ARE INSTALLED
+ if plugin_count > 0 then
+ print('\t^7|________________________________________________________|')
+ print('\t^7|INSTALLED PLUGINS | INSTALLED | LATEST |')
+ for name, version in pairs(plugins_cv) do
+ local plugin_string
+ if plugins_rv[name] ~= nil and plugins_rv[name] ~= 'UNKWN' and plugins_cv[name] < plugins_rv[name] then
+ plugin_string = ('\t|^8 %-30s^7|^8 %s ^7|^8 %s ^7|^8 UPDATE REQUIRED ^7'):format(name, plugins_cv[name], plugins_rv[name])
+ elseif plugins_rv[name] ~= nil and plugins_cv[name] > plugins_rv[name] or plugins_rv[name] == 'UNKWN' then
+ plugin_string = ('\t|^3 %-30s^7|^3 %s ^7|^3 %s ^7|^3 EXPERIMENTAL VERSION ^7'):format(name, plugins_cv[name], plugins_rv[name])
+ else
+ plugin_string = ('\t| %-30s| %s | %s |'):format(name, plugins_cv[name], plugins_rv[name])
+ end
+ print(plugin_string)
+ end
+ end
+ else -- NO COMMUNITY ID SET
+ print('\t|\t^8 CONFIGURATION ERROR ^7|')
+ print('\t|^8 COMMUNITY ID MISSING, THIS IS REQUIRED TO PREVENT ^7|')
+ print('\t|^8 CONFLICTS FOR PLAYERS WHO PLAY ON MULTIPLE SERVERS ^7|')
+ print('\t|^8 WITH LVC. PLEASE SET THIS IN SETTINGS.LUA. ^7|')
+ end
+ else -- INCORRECT RESOURCE NAME
+ print('\t|\t^8 CONFIGURATION ERROR ^7|')
+ print('\t|^8 INVALID RESOURCE NAME. PLEASE VERIFY RESOURCE FOLDER ^7|')
+ print('\t|^8 NAME READS \'^3lvc^8\' (CASE-SENSITIVE). THIS IS REQUIRED ^7|')
+ print('\t|^8 FOR PROPER SAVE / LOAD FUNCTIONALITY. PLEASE RENAME, ^7|')
+ print('\t|^8 REFRESH, AND ENSURE. ^7|')
+ end
+ else -- RESOURCE CONFLICT
+ print('\t|\t^8 RESOURCE CONFLICT DETECTED ^7|')
+ print('\t|^8 DETECTED "lux_vehcontrol" RUNNING, THIS CONFLICTS WITH ^7|')
+ print('\t|^8 LVC. PLEASE STOP "lux_vehcontrol" AND RESTART LVC. ^7|')
+ end
+ print('\t^7|________________________________________________________|')
+ print('\t^7| Updates, Support, Feedback: ^5discord.link/LVC ^7|')
+ print('\t^7|________________________________________________________|\n\n')
+end)
\ No newline at end of file
diff --git a/resources/lvc/fxmanifest.lua b/resources/lvc/fxmanifest.lua
new file mode 100644
index 000000000..739a9b259
--- /dev/null
+++ b/resources/lvc/fxmanifest.lua
@@ -0,0 +1,86 @@
+------------------------------
+
+fx_version 'adamant'
+games { 'gta5' }
+
+author 'TrevorBarns w/ credits see GitHub'
+description 'A siren / emergency lights controller for FiveM.'
+
+version '3.2.9' -- Readonly version of currently installed version.
+compatible '3.2.2' -- Readonly save reverse compatiability.
+
+------------------------------
+
+beta_checking 'true' -- Notifications for beta revisions and new betas.
+experimental 'false' -- Mute unstable version warning in server console.
+debug_mode 'false' -- More verbose printing on client console.
+
+------------------------------
+
+ui_page('/UI/html/index.html')
+
+dependencies {
+ 'RageUI'
+}
+
+files({
+ 'UI/html/index.html',
+ 'UI/html/lvc.js',
+ 'UI/html/style.css',
+ 'UI/sounds/*.ogg',
+ 'UI/sounds/**/*.ogg',
+ 'UI/textures/**/*.png',
+ 'UI/textures/**/*.gif',
+ 'PLUGINS/**/*.json'
+})
+
+
+shared_script {
+ '/UTIL/semver.lua',
+ '/UI/cl_locale.lua',
+ '/UI/locale/en.lua', -- Set locale / language file here.
+ 'SETTINGS.lua',
+}
+
+client_scripts {
+ ---------------RAGE-UI---------------
+ '@RageUI/RMenu.lua',
+ '@RageUI/menu/RageUI.lua',
+ '@RageUI/menu/Menu.lua',
+ '@RageUI/menu/MenuController.lua',
+ '@RageUI/components/Audio.lua',
+ '@RageUI/components/Enum.lua',
+ '@RageUI/components/Keys.lua',
+ '@RageUI/components/Rectangle.lua',
+ '@RageUI/components/Sprite.lua',
+ '@RageUI/components/Text.lua',
+ '@RageUI/components/Visual.lua',
+ '@RageUI/menu/elements/ItemsBadge.lua',
+ '@RageUI/menu/elements/ItemsColour.lua',
+ '@RageUI/menu/elements/PanelColour.lua',
+ '@RageUI/menu/items/UIButton.lua',
+ '@RageUI/menu/items/UICheckBox.lua',
+ '@RageUI/menu/items/UIList.lua',
+ '@RageUI/menu/items/UISeparator.lua',
+ '@RageUI/menu/items/UISlider.lua',
+ '@RageUI/menu/items/UISliderHeritage.lua',
+ '@RageUI/menu/items/UISliderProgress.lua',
+ '@RageUI/menu/panels/UIColourPanel.lua',
+ '@RageUI/menu/panels/UIGridPanel.lua',
+ '@RageUI/menu/panels/UIPercentagePanel.lua',
+ '@RageUI/menu/panels/UIStatisticsPanel.lua',
+ '@RageUI/menu/windows/UIHeritage.lua',
+ -------------------------------------
+ 'SIRENS.lua',
+ '/UTIL/cl_*.lua',
+ '/UI/cl_*.lua',
+ '/PLUGINS/cl_plugins.lua',
+ '/PLUGINS/**/SETTINGS.lua',
+ '/PLUGINS/**/cl_*.lua',
+}
+
+server_script {
+ '/UTIL/sv_lvc.lua',
+ '/PLUGINS/**/sv_*.lua'
+}
+------------------------------
\ No newline at end of file
diff --git a/resources/lvc/stream/lvc.ytd b/resources/lvc/stream/lvc.ytd
new file mode 100644
index 000000000..9119b26e9
--- /dev/null
+++ b/resources/lvc/stream/lvc.ytd
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8c6412cd03bb5c668a02fcf6ff71d547c2d894891da1c95bcca644eb99f4d1ec
+size 88496
diff --git a/scrbind-base.dll b/scrbind-base.dll
index 58b27bbbf..4e9018142 100644
Binary files a/scrbind-base.dll and b/scrbind-base.dll differ
diff --git a/scripting-server.dll b/scripting-server.dll
index 0500ee201..6ef7ba4e2 100644
Binary files a/scripting-server.dll and b/scripting-server.dll differ
diff --git a/server.cfg b/server.cfg
index 78723caa5..0aa21ff51 100644
--- a/server.cfg
+++ b/server.cfg
@@ -62,7 +62,7 @@ start Hands-Up
start Tackle
start WeazelNewsCam
start Crouch
-start Lux_ELS
+//start Lux_ELS
start CalmAI
start CarCleanUp
start Engine-Toggle
@@ -72,7 +72,7 @@ start Heli-Cam
start Weaponry
start StreetLabel
start EGRP-HUD
-//start EmoteMenu
+start EmoteMenu
start enhancedcamera
start FiveM-Vote
start Lightbars-Menu
@@ -106,7 +106,7 @@ start dpclothing
start Car-Nitro
start ContainerForklift
start inferno-ladders
-start NoDriveBy
+//start NoDriveBy
start InteractSound
start Police-Radio
start Smart-Clamp
@@ -133,14 +133,13 @@ start Cayo-TP
start radio
start assets
start Slash-Tyres
-start Holster-Weapon
//start Animated-Banners
start Hypnonema
start Delete-Gun
start RP-Death
start DeathCam
//start Doorlock
-start wk_wars2x
+//start wk_wars2x
start Firework-Box
start Fighterjet-HUD
start Vehicle-Attachment
@@ -174,6 +173,9 @@ start EGRP-Notifications
//start firehose
//start firehook
start dpemotes-master
+start RageUI
+start lvc
+start Wheel-Damage
#[-----Discord Perms-----]
start RichPresence
@@ -399,9 +401,9 @@ sets tags "Roleplay, Addons, Custom Peds, Sandy Shores, Police, CAD, EMS, Fire,
sets banner_detail "https://i.imgur.com/JdhxvVJ.png"
sets banner_connecting "https://i.imgur.com/JdhxvVJ.png"
-sv_hostname "^1Elite Gaming RP ^0|^8 Christmas Updates ❄️ ^0|^6 vMenu-Based Five M Roleplay Server ^0|^4 Police & EMS 👮🚑 ^0|^2 Custom Scripts 📜 ^0|^9 300+ Vehicles 🚘 ^0|^5 CAD System ✅ ^0|^3 FivePD! 🚓🚒🚑"
+sv_hostname "^1Elite Gaming RP ^0|^8 Server Overhaul 🔁 ^0|^6 vMenu-Based Five M Server ^0|^4 Police & EMS 👮🚑 ^0|^2 Custom Scripts 📜 ^0|^9 300+ Vehicles 🚘 ^0|^5 CAD System ✅ ^0|^3 FivePD 🚓🚒🚑"
sets sv_projectName "Elite Gaming RP"
-sets sv_projectDesc "Fast growing gaming community! elite-gaming.co.uk x elite-bot.com"
+sets sv_projectDesc "Become a first responder today! elite-gaming.gg"
# nested configs!
#exec server_internal.cfg
diff --git a/svadhesive.dll b/svadhesive.dll
index ea7099799..250cfbb43 100644
Binary files a/svadhesive.dll and b/svadhesive.dll differ
diff --git a/vfs-core.dll b/vfs-core.dll
index 5cacf1b5a..7c473128c 100644
Binary files a/vfs-core.dll and b/vfs-core.dll differ
diff --git a/vfs-impl-server.dll b/vfs-impl-server.dll
index 73d03a6c0..503c63405 100644
Binary files a/vfs-impl-server.dll and b/vfs-impl-server.dll differ
diff --git a/voip-server-mumble.dll b/voip-server-mumble.dll
index 1298e65c6..11a862869 100644
Binary files a/voip-server-mumble.dll and b/voip-server-mumble.dll differ