From 3b4077c3c8f0c91efb2e02b01caa28561efa2d47 Mon Sep 17 00:00:00 2001 From: Mycroft Date: Thu, 11 Aug 2022 09:42:04 +0100 Subject: [PATCH] Feat: ESX Property 2.0 --- [esx_addons]/esx_property/LICENSE | 674 ++++++++ [esx_addons]/esx_property/README.md | 94 + [esx_addons]/esx_property/client/cctv.lua | 277 +++ .../esx_property/client/furniture.lua | 407 +++++ .../esx_property/client/html/copy.html | 25 + [esx_addons]/esx_property/client/main.lua | 1527 +++++++++++++++++ [esx_addons]/esx_property/config.lua | 644 +++++++ [esx_addons]/esx_property/fxmanifest.lua | 51 + [esx_addons]/esx_property/properties.json | 1 + [esx_addons]/esx_property/server/main.lua | 1088 ++++++++++++ 10 files changed, 4788 insertions(+) create mode 100644 [esx_addons]/esx_property/LICENSE create mode 100644 [esx_addons]/esx_property/README.md create mode 100644 [esx_addons]/esx_property/client/cctv.lua create mode 100644 [esx_addons]/esx_property/client/furniture.lua create mode 100644 [esx_addons]/esx_property/client/html/copy.html create mode 100644 [esx_addons]/esx_property/client/main.lua create mode 100644 [esx_addons]/esx_property/config.lua create mode 100644 [esx_addons]/esx_property/fxmanifest.lua create mode 100644 [esx_addons]/esx_property/properties.json create mode 100644 [esx_addons]/esx_property/server/main.lua diff --git a/[esx_addons]/esx_property/LICENSE b/[esx_addons]/esx_property/LICENSE new file mode 100644 index 00000000..5c0cc627 --- /dev/null +++ b/[esx_addons]/esx_property/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. + + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 . + +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: + + esx-legacy Copyright (C) 2015-2022 Jérémie N'gadi + 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 +. + + 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 +. \ No newline at end of file diff --git a/[esx_addons]/esx_property/README.md b/[esx_addons]/esx_property/README.md new file mode 100644 index 00000000..b83c47e1 --- /dev/null +++ b/[esx_addons]/esx_property/README.md @@ -0,0 +1,94 @@ +

[ESX] Property

Discord - Website - Documentation + +## Features + +* 95 Premade Properties +* Dynamic Json Storage +* Key Management System +* Garage System +* Furniture System + * Can configure everything in the config + * Editing system + * Easy to use controls + * Furniture Stores + * Keymapped quick access menu +* Shell / IPL support +* Police Raiding +* In-Game property Creator +* Routing Buckets for instancing +* Wardrobe functionality +* OX Inventory Support +* CCTV Camera System + * Night Vision + * Cool HUD overlay + * ability to zoom in/out + * Discord webhook support (copies link to clipboard aswell) +* Admin Menu + * **EVERY** part of the property can be changed! + * Real estate Job Can also access +* Real Estate Job +* Built in Discord Logs +* Highly Configurable + +## Dependencies + +* es_extended +* esx_society *for Player Management* * +* [OX-inventory](https://github.com/overextended/ox_inventory) *For Property Stashes* * +* ESX Context +* ESX Notify +* ESX Progressbar *for Raiding* * +* ESX Datastore *for wardrobe* * +* ESX Clotheshop *for wardrobe* * +* [k4mb1 Starter Shells](https://www.k4mb1maps.com/package/5015840) *only for shell use* * +* [bob74_ipl](https://github.com/Bob74/bob74_ipl) *only for IPL use* * + +> `*` = Optional + +## Create A Property + +* [Menu Preview](https://prnt.sc/Xczb5Fs9xW-t) +* Use the command `property:create` while an admin +* Set Street Number +* Set Price of Property +* Select an Interior + > Note: To Use the Default Shells, you **NEED** [this Shell Pack](https://www.k4mb1maps.com/package/5015840) + + > [Catagory Selection](https://prnt.sc/3RGaIEVTL6zw) + + > [Interior Selection](https://prnt.sc/za9aOSsuqkce) +* Set The Entrance Position + > Note: Uses Player`s Current Coordinates + +## Getting Shell Offset + +* To get the spawn Offset of the shell, use [Loaf`s Offset Finder](https://github.com/loaf-scripts/loaf_offsetfinder) + +* To Get the Wardrobe or Storage offset, go inside the Property, goto where u want them to be and then use the `getoffset` command and it will print the offset in F8 + +## Admin Menu + +* As an Admin, use the command, `property:admin` +* From There, you can select A property +* You Can then either `Manage`, `Teleport To Entrance` or `Delete` +* Allows you to edit all options and evict users +* [Menu Preview](https://prnt.sc/Jpxox2ZcKn9G) +* [Menu Preview 2](https://prnt.sc/IbP-ynHgYSMA) + +# Copyright + + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 . diff --git a/[esx_addons]/esx_property/client/cctv.lua b/[esx_addons]/esx_property/client/cctv.lua new file mode 100644 index 00000000..5585c71e --- /dev/null +++ b/[esx_addons]/esx_property/client/cctv.lua @@ -0,0 +1,277 @@ +--[[ + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 . +]] +function CCTV(PropertyID) + DoScreenFadeOut(500) + Wait(500) + local PlyCoordsBefore = GetEntityCoords(PlayerPedId()) + local Property = Properties[PropertyID] + local CamTakePic = true + if Property.cctv.enabled then + ESX.TriggerServerCallback("esx_property:CCTV", function(CanCCTV) + if CanCCTV then + InCCTV = true + local NightVision = false + local function InstructionButtonMessage(text) + BeginTextCommandScaleformString("STRING") + AddTextComponentScaleform(text) + EndTextCommandScaleformString() + end + + local function CreateInstuctionScaleform(scaleform) + local scaleform = RequestScaleformMovie(scaleform) + while not HasScaleformMovieLoaded(scaleform) do + Wait(0) + end + PushScaleformMovieFunction(scaleform, "CLEAR_ALL") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_CLEAR_SPACE") + PushScaleformMovieFunctionParameterInt(200) + PopScaleformMovieFunctionVoid() + + if Config.CCTV.PictureWebook ~= "" then + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(1) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.Screenshot, true)) + InstructionButtonMessage("Take Picture") + PopScaleformMovieFunctionVoid() + end + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(2) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.Right, true)) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.Left, true)) + InstructionButtonMessage("Left/Right") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(3) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.Down, true)) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.Up, true)) + InstructionButtonMessage("Up/Down") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(4) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.ZoomOut, true)) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.ZoomIn, true)) + InstructionButtonMessage("Zoom In/Out") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(5) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.NightVision, true)) + InstructionButtonMessage("Toggle Night Vision") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(0) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.CCTV.Controls.Exit, true)) + InstructionButtonMessage("Exit Camera") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_BACKGROUND_COLOUR") + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(80) + PopScaleformMovieFunctionVoid() + + return scaleform + end + ESX.CloseContext() + local cctvcam = nil + local angleZ = 0.0 + ClearFocus() + local playerPed = PlayerPedId() + cctvcam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", + vector3(Property.Entrance.x, Property.Entrance.y, Property.Entrance.z + Config.CCTV.HeightAboveDoor), 0, 0, 0, Config.CCTV.FOV) + SetCamRot(cctvcam, Property.cctv.rot.x, Property.cctv.rot.y, Property.cctv.rot.z, 2) + SetCamActive(cctvcam, true) + SetTimecycleModifier("scanline_cam_cheap") + TriggerServerEvent("p_instance:s:leave") + DisableAllControlActions(0) + FreezeEntityPosition(playerPed, true) + SetEntityCollision(playerPed, false, true) + local ShowButtons = true + SetEntityVisible(playerPed, false) + SetTimecycleModifierStrength(2.0) + SetFocusArea(Property.Entrance.x, Property.Entrance.y, Property.Entrance.z, 0.0, 0.0, 0.0) + PointCamAtCoord(cctvcam, vector3(Property.Entrance.x, Property.Entrance.y, Property.Entrance.z + Config.CCTV.HeightAboveDoor)) + RenderScriptCams(true, false, 1, true, false) + Wait(1000) + DoScreenFadeIn(500) + RequestAmbientAudioBank("Phone_Soundset_Franklin", 0, 0) + RequestAmbientAudioBank("HintCamSounds", 0, 0) + while IsCamActive(cctvcam) do + Wait(5) + DisableAllControlActions(0) + EnableControlAction(0, 245, true) + EnableControlAction(0, 246, true) + EnableControlAction(0, 249, true) + HideHudComponentThisFrame(7) + HideHudComponentThisFrame(8) + HideHudComponentThisFrame(9) + HideHudComponentThisFrame(6) + HideHudComponentThisFrame(19) + HideHudAndRadarThisFrame() + + if ShowButtons then + local instructions = CreateInstuctionScaleform("instructional_buttons") + DrawScaleformMovieFullscreen(instructions, 255, 255, 255, 255, 0) + end + -- ROTATE LEFT + local getCameraRot = GetCamRot(cctvcam, 2) + + if IsDisabledControlPressed(0, Config.CCTV.Controls.Left) and getCameraRot.z < Property.cctv.maxleft then + PlaySoundFrontend(-1, " FocusIn", "HintCamSounds", false) + SetCamRot(cctvcam, getCameraRot.x, 0.0, getCameraRot.z + Config.CCTV.RotateSpeed, 2) + end + -- ROTATE RIGHT + if IsDisabledControlPressed(0, Config.CCTV.Controls.Right) and getCameraRot.z > Property.cctv.maxright then + PlaySoundFrontend(-1, " FocusIn", "HintCamSounds", false) + SetCamRot(cctvcam, getCameraRot.x, 0.0, getCameraRot.z - Config.CCTV.RotateSpeed, 2) + end + + -- ROTATE UP + if IsDisabledControlPressed(0, Config.CCTV.Controls.Up) and getCameraRot.x < Config.CCTV.MaxUpRotation then + PlaySoundFrontend(-1, " FocusIn", "HintCamSounds", false) + SetCamRot(cctvcam, getCameraRot.x + Config.CCTV.RotateSpeed, 0.0, getCameraRot.z, 2) + end + + if IsDisabledControlPressed(0, Config.CCTV.Controls.Down) and getCameraRot.x > Config.CCTV.MaxDownRotation then + PlaySoundFrontend(-1, " FocusIn", "HintCamSounds", false) + SetCamRot(cctvcam, getCameraRot.x - Config.CCTV.RotateSpeed, 0.0, getCameraRot.z, 2) + end + + if IsDisabledControlPressed(0, Config.CCTV.Controls.ZoomIn) and GetCamFov(cctvcam) > Config.CCTV.MaxZoom then + SetCamFov(cctvcam, GetCamFov(cctvcam) - 1.0) + end + + if IsDisabledControlPressed(0, Config.CCTV.Controls.ZoomOut) and GetCamFov(cctvcam) < Config.CCTV.MinZoom then + SetCamFov(cctvcam, GetCamFov(cctvcam) + 1.0) + end + + if IsDisabledControlPressed(0, Config.CCTV.Controls.Down) and getCameraRot.x > Config.CCTV.MaxDownRotation then + PlaySoundFrontend(-1, " FocusIn", "HintCamSounds", false) + SetCamRot(cctvcam, getCameraRot.x - Config.CCTV.RotateSpeed, 0.0, getCameraRot.z, 2) + end + + SetTextFont(4) + SetTextScale(0.8, 0.8) + SetTextColour(255, 255, 255, 255) + SetTextDropshadow(0.1, 3, 27, 27, 255) + BeginTextCommandDisplayText('STRING') + AddTextComponentSubstringPlayerName(Property.setName ~= "" and Property.setName or Property.Name) + EndTextCommandDisplayText(0.01, 0.01) + + SetTextFont(4) + SetTextScale(0.7, 0.7) + SetTextColour(255, 255, 255, 255) + SetTextDropshadow(0.1, 3, 27, 27, 255) + BeginTextCommandDisplayText('STRING') + local year --[[ integer ]] , month --[[ integer ]] , day --[[ integer ]] , hour --[[ integer ]] , minute --[[ integer ]] , second --[[ integer ]] = + GetPosixTime() + AddTextComponentSubstringPlayerName("" .. day .. "/" .. month .. "/" .. year .. " " .. hour .. ":" .. minute .. ":" .. second) + EndTextCommandDisplayText(0.01, 0.055) + + SetTextFont(4) + SetTextScale(0.6, 0.6) + SetTextColour(255, 255, 255, 255) + SetTextDropshadow(0.1, 3, 27, 27, 255) + BeginTextCommandDisplayText('STRING') + local Zoom = ((Config.CCTV.FOV - GetCamFov(cctvcam)) / GetCamFov(cctvcam)) * 100 + AddTextComponentSubstringPlayerName("Zoom Level: " .. math.floor(Zoom) .. "%") + EndTextCommandDisplayText(0.01, 0.09) + + SetTextFont(4) + SetTextScale(0.6, 0.6) + SetTextColour(255, 255, 255, 255) + SetTextDropshadow(0.1, 3, 27, 27, 255) + BeginTextCommandDisplayText('STRING') + AddTextComponentSubstringPlayerName(NightVision and "Night Vision: Active" or "CCTV System: Active") + EndTextCommandDisplayText(0.01, 0.12) + + if IsDisabledControlPressed(0, Config.CCTV.Controls.Down) and getCameraRot.x > Config.CCTV.MaxDownRotation then + PlaySoundFrontend(-1, " FocusIn", "HintCamSounds", false) + SetCamRot(cctvcam, getCameraRot.x - Config.CCTV.RotateSpeed, 0.0, getCameraRot.z, 2) + end + + if IsDisabledControlJustPressed(0, 38) then + NightVision = not NightVision + SetNightvision(NightVision) + SetTimecycleModifier("scanline_cam") + end + + if Config.CCTV.PictureWebook ~= "" and IsDisabledControlJustPressed(0, 201) then + if CamTakePic then + ShowButtons = false + Wait(1) + PlaySoundFrontend(-1, "Camera_Shoot", "Phone_Soundset_Franklin", 1) + ESX.TriggerServerCallback("esx_property:GetWebhook", function(hook) + if hook then + exports['screenshot-basic']:requestScreenshotUpload(hook, "files[]", function(data) + local image = json.decode(data) + print(ESX.DumpTable(image)) + ESX.ShowNotification("Picture Taken!", "success") + SendNUIMessage({link = image.attachments[1].proxy_url}) + ESX.ShowNotification("Link Copied To ~b~Cipboard", "success") + ShowButtons = true + CamTakePic = false + SetTimeout(5000, function() + CamTakePic = true + end) + end) + end + end) + else + ESX.ShowNotification("Please Wait Before taking another ~b~Picture", "error") + end + end + + if IsDisabledControlPressed(1, Config.CCTV.Controls.Exit) then + DoScreenFadeOut(1000) + ESX.TriggerServerCallback("esx_property:ExitCCTV", function(CanExit) + if CanExit then + InCCTV = false + Wait(1000) + ClearFocus() + ClearTimecycleModifier() + ClearExtraTimecycleModifier() + RenderScriptCams(false, false, 0, true, false) + DestroyCam(cctvcam, false) + SetFocusEntity(playerPed) + SetNightvision(false) + SetSeethrough(false) + SetEntityCollision(playerPed, true, true) + FreezeEntityPosition(playerPed, false) + SetEntityVisible(playerPed, true) + Wait(1500) + DoScreenFadeIn(1000) + end + end, PropertyID) + break + end + end + end + end, PropertyID) + end +end diff --git a/[esx_addons]/esx_property/client/furniture.lua b/[esx_addons]/esx_property/client/furniture.lua new file mode 100644 index 00000000..4e5ee7c3 --- /dev/null +++ b/[esx_addons]/esx_property/client/furniture.lua @@ -0,0 +1,407 @@ +--[[ + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 Config.Furniture.Enabled then + SpawnedFurniture = {} + RegisterNetEvent("esx_property:syncFurniture", function(PropertyId, Furniture) + if Properties[PropertyId] and Furniture then + Properties[PropertyId].furniture = Furniture + end + end) + + RegisterNetEvent("esx_property:placeFurniture", function(Id, furniture, findex) + if Properties[Id] and InProperty and CurrentId == Id then + ESX.Game.SpawnLocalObject(furniture.Name, vector3(furniture.Pos.x, furniture.Pos.y, furniture.Pos.z - 0.1), function(object) + SetEntityCoordsNoOffset(object, furniture.Pos.x, furniture.Pos.y, furniture.Pos.z) + SetEntityHeading(object, furniture.Heading) + SetEntityAsMissionEntity(object, true, true) + FreezeEntityPosition(object, true) + SpawnedFurniture[findex] = {obj = object, data = furniture} + end) + end + end) + + RegisterNetEvent("esx_property:removeFurniture", function(Id, furniture) + if Properties[Id] and InProperty and CurrentId == Id then + if SpawnedFurniture[furniture] then + DeleteEntity(SpawnedFurniture[furniture].obj) + SpawnedFurniture[furniture] = nil + end + end + end) + + RegisterNetEvent("esx_property:editFurniture", function(Id, furniture, Pos, Heading) + if Properties[Id] and InProperty and CurrentId == Id then + + if SpawnedFurniture[furniture] then + local obj = SpawnedFurniture[furniture].obj + SetEntityCoordsNoOffset(obj, Pos.x, Pos.y, Pos.z) + SetEntityHeading(obj, Heading) + SpawnedFurniture[furniture].data.Pos = Pos + SpawnedFurniture[furniture].data.Heading = Heading + end + end + end) + + ------------------------ Furniture Start ------------------------------- + local inFurniture = false + local CurrentlyEditing = nil + + function TempFurniturePlacement(PropertyId, PropName, PropIndex, PropCatagory, PropPrice, Existing, Pos, Heading) + ESX.CloseContext() + if CurrentlyEditing then + DeleteEntity(CurrentlyEditing) + CurrentlyEditing = nil + end + local function InstructionButtonMessage(text) + BeginTextCommandScaleformString("STRING") + AddTextComponentScaleform(text) + EndTextCommandScaleformString() + end + + local function CreateInstuctionScaleform(scaleform) + local scaleform = RequestScaleformMovie(scaleform) + while not HasScaleformMovieLoaded(scaleform) do + Wait(0) + end + PushScaleformMovieFunction(scaleform, "CLEAR_ALL") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_CLEAR_SPACE") + PushScaleformMovieFunctionParameterInt(200) + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(1) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.MinusX, true)) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.PlusX, true)) + InstructionButtonMessage("X Axis") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(3) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.MinusY, true)) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.PlusY, true)) + InstructionButtonMessage("Y axis") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(5) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Up, true)) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Down, true)) + InstructionButtonMessage("Height") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(7) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.RotateLeft, true)) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.RotateRight, true)) + InstructionButtonMessage("Rotation") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(9) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Confirm, true)) + InstructionButtonMessage("Place Furniture") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT") + PushScaleformMovieFunctionParameterInt(0) + N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Exit, true)) + InstructionButtonMessage("Delete") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS") + PopScaleformMovieFunctionVoid() + + PushScaleformMovieFunction(scaleform, "SET_BACKGROUND_COLOUR") + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(0) + PushScaleformMovieFunctionParameterInt(80) + PopScaleformMovieFunctionVoid() + + return scaleform + end + function GetCameraDirection() + local rotation = GetGameplayCamRot() * (math.pi / 180) + + return vector3(-math.sin(rotation.z), math.cos(rotation.z), math.sin(rotation.x)) + end + local Position = GetEntityCoords(ESX.PlayerData.ped) + local PropSpawn = Position - vector3(0.5, 0.5, 1.0) + ESX.Game.SpawnLocalObject(PropName, PropSpawn, function(CurrentlyEditing) + CurrentlyEditing = CurrentlyEditing + NetworkSetObjectForceStaticBlend(CurrentlyEditing, true) + SetEntityAsMissionEntity(CurrentlyEditing, true, true) + SetEntityCollision(CurrentlyEditing, false, true) + if Existing then + SetEntityCoordsNoOffset(CurrentlyEditing, vector3(Pos.x, Pos.y, Pos.z)) + SetEntityHeading(CurrentlyEditing, Heading) + else + PlaceObjectOnGroundProperly(CurrentlyEditing) + end + FreezeEntityPosition(CurrentlyEditing, true) + while true do + HudForceWeaponWheel(false) + HideHudComponentThisFrame(19) + HideHudComponentThisFrame(20) + local CurrentPos = GetEntityCoords(CurrentlyEditing) + local CurrentHeading = GetEntityHeading(CurrentlyEditing) + local Rotation = GetEntityRotation(CurrentlyEditing, 2) + SetEntityDrawOutline(CurrentlyEditing, true) + SetEntityDrawOutlineColor(55, 140, 191, 200) + SetEntityDrawOutlineShader(1) + local instructions = CreateInstuctionScaleform("instructional_buttons") + DrawScaleformMovieFullscreen(instructions, 255, 255, 255, 255, 0) + Wait(0) + if IsControlJustPressed(0, 202) then + DeleteObject(CurrentlyEditing) + end + + if IsControlPressed(0, Config.Furniture.Controls.MinusY) then + local direction = GetCameraDirection() + CurrentPos = CurrentPos - vector3(direction.x * Config.Furniture.MovementSpeed, direction.y * Config.Furniture.MovementSpeed, 0) + SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos.x, CurrentPos.y, CurrentPos.z) + end + + if IsControlPressed(0, Config.Furniture.Controls.PlusY) then + local direction = GetCameraDirection() + CurrentPos = CurrentPos + vector3(direction.x * Config.Furniture.MovementSpeed, direction.y * Config.Furniture.MovementSpeed, 0) + SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos) + end + + if IsControlPressed(0, Config.Furniture.Controls.Up) then + SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos.x, CurrentPos.y, CurrentPos.z + Config.Furniture.MovementZspeed) + end + + if IsControlPressed(0, Config.Furniture.Controls.Down) then + SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos.x, CurrentPos.y, CurrentPos.z - Config.Furniture.MovementZspeed) + end + if IsControlPressed(0, Config.Furniture.Controls.RotateLeft) then + CurrentHeading = CurrentHeading + Config.Furniture.RotationSpeed + SetEntityRotation(CurrentlyEditing, 0, 0, CurrentHeading, 1, true) + -- SetEntityRotation(CurrentlyEditing, Rotation.x, Rotation.y, Rotation.z + Config.Furniture.RotationSpeed, 2, false) + -- SetEntityHeading(CurrentlyEditing, CurrentHeading + Config.Furniture.RotationSpeed) + end + + if IsControlPressed(0, Config.Furniture.Controls.RotateRight) then + CurrentHeading = CurrentHeading - Config.Furniture.RotationSpeed + SetEntityRotation(CurrentlyEditing, 0, 0, CurrentHeading, 1, true) + -- SetEntityRotation(CurrentlyEditing, Rotation.x, Rotation.y, Rotation.z - Config.Furniture.RotationSpeed, 2, false) + -- SetEntityHeading(CurrentlyEditing, CurrentHeading - Config.Furniture.RotationSpeed) + end + + if IsControlPressed(0, Config.Furniture.Controls.PlusX) then + local direction = GetCameraDirection() + CurrentPos = CurrentPos + vector3(-direction.y * Config.Furniture.MovementSpeed, direction.x * Config.Furniture.MovementSpeed, 0) + SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos) + end + + if IsControlPressed(0, Config.Furniture.Controls.MinusX) then + local direction = GetCameraDirection() + CurrentPos = CurrentPos - vector3(-direction.y * Config.Furniture.MovementSpeed, direction.x * Config.Furniture.MovementSpeed, 0) + SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos) + end + + if IsControlJustPressed(0, Config.Furniture.Controls.Confirm) then + if not Existing then + ESX.OpenContext("right", + {{unslectable = true, title = "Do you want to buy " .. Config.FurnitureCatagories[PropCatagory][PropIndex].title .. "?", + description = "Price: $" .. Config.FurnitureCatagories[PropCatagory][PropIndex].price, icon = "fas fa-shopping-cart"}, + {title = "Yes", value = "buy", icon = "fas fa-check"}, {title = "No", icon = "fas fa-minus"}}, function(menu, element) + if element.value and element.value == "buy" then + ESX.TriggerServerCallback("esx_property:buyFurniture", function(response) + if response then + DeleteEntity(CurrentlyEditing) + ESX.ShowNotification("You Bought ~b~" .. Config.FurnitureCatagories[PropCatagory][PropIndex].title .. "~s~!", "success") + ESX.CloseContext() + else + ESX.ShowNotification("You ~r~Cannot~s~ Buy This Furniture", "error") + end + end, CurrentId, PropName, PropIndex, PropCatagory, vector3(CurrentPos.x, CurrentPos.y, CurrentPos.z), CurrentHeading) + end + end) + else + ESX.TriggerServerCallback("esx_property:editFurniture", function(response) + if response then + DeleteEntity(CurrentlyEditing) + ESX.ShowNotification("You Edited ~b~" .. Config.FurnitureCatagories[PropCatagory][PropPrice].title .. "~s~!", "success") + ESX.CloseContext() + else + ESX.ShowNotification("You ~r~Cannot~s~ Edit This Furniture", "error") + end + end, CurrentId, PropIndex, vector3(CurrentPos.x, CurrentPos.y, CurrentPos.z), CurrentHeading) + end + end + + if DoesEntityExist(CurrentlyEditing) then + inFurniture = true + else + inFurniture = false + for _, Control in pairs(Config.Furniture.Controls) do + EnableControlAction(0, Control, true) + end + break + end + end + end) + end + + function FurnitureItems(house, Store, StoreIndex, Catagory) + local Items = {{unselectable = true, title = Store .. " Store - " .. Catagory, icon = "fas fa-store"}, + {title = "Go Back", value = "go-back", icon = "fas fa-arrow-left"}} + + for k, v in pairs(Config.FurnitureCatagories[Catagory]) do + table.insert(Items, {title = v.title, value = v.name, index = k, description = "Price: " .. v.price, icon = "fas fa-shopping-cart"}) + end + + ESX.OpenContext("right", Items, function(data, element) + if element.value == "go-back" then + FurnitureCatagories(house, Store, StoreIndex) + else + TempFurniturePlacement(house, element.value, element.index, Catagory) + end + end) + end + + function FurnitureCatagories(house, Store, StoreIndex) + local Catagories = {{unselectable = true, title = Store .. " Store", icon = "fas fa-store"}, + {title = "Go Back", value = "go-back", icon = "fas fa-arrow-left"}} + + for k, v in pairs(Config.FurnitureStores[StoreIndex].Catagories) do + table.insert(Catagories, {title = v}) + end + + ESX.OpenContext("right", Catagories, function(data, element) + if element.value == "go-back" then + FurnitureStores(house) + else + FurnitureItems(house, Store, StoreIndex, element.title) + end + end) + end + + function FurnitureEdit(propertyId, furniture) + local furni = SpawnedFurniture[furniture] + local Funiture = Config.FurnitureCatagories[furni.data.Catagory][furni.data.Index] + local Catagories = {{unselectable = true, title = "Edit " .. Funiture.title, icon = "fas fa-store"}, + {title = "Go Back", value = "go-back", icon = "fas fa-arrow-left"}, + {title = "Move", value = "rotate", icon = "fas fa-sync-alt"}, {title = "Delete", value = "delete", icon = "fas fa-trash-alt"}} + + ESX.OpenContext("right", Catagories, function(data, element) + if element.value == "go-back" then + FurnitureEditSelect(propertyId) + end + if element.value == "delete" then + ESX.TriggerServerCallback("esx_property:deleteFurniture", function(response) + if response then + ESX.ShowNotification("You Deleted ~b~" .. Funiture.title .. "~s~!", "success") + FurnitureEditSelect(propertyId) + else + ESX.ShowNotification("You ~r~Cannot~s~ Delete This Furniture", "error") + end + end, propertyId, furniture) + end + if element.value == "rotate" then + ESX.CloseContext() + TempFurniturePlacement(propertyId, furni.data.Name, furniture, furni.data.Catagory, furni.data.Index, true, furni.data.Pos, furni.data.Heading) + end + end) + end + + function FurnitureEditSelect(propertyId) + local Furni = {{unselectable = true, title = "Owned Furniture", icon = "far fa-edit"}, + {title = "Go Back", value = "go-back", icon = "fas fa-arrow-left"}} + + for k, v in pairs(SpawnedFurniture) do + local Funiture = Config.FurnitureCatagories[v.data.Catagory][v.data.Index] + table.insert(Furni, {title = Funiture.title, value = k}) + end + + ESX.OpenContext("right", Furni, function(data, element) + if element.value == "go-back" then + FurnitureMenu(propertyId) + else + FurnitureEdit(propertyId, element.value) + end + end) + end + + function FurnitureStores(house) + local Options = {{unselectable = true, title = "Furniture Stores", icon = "fas fa-store"}, + {title = "Go Back", value = "go-back", icon = "fas fa-arrow-left"}} + + for k, v in pairs(Config.FurnitureStores) do + table.insert(Options, {title = v.title, Store = k}) + end + + ESX.OpenContext("right", Options, function(data, element) + if element.Store then + FurnitureCatagories(house, element.title, element.Store) + elseif element.value == "go-back" then + FurnitureMenu(house) + end + end) + end + + function FurnitureMenu(PropertyId) + local Options = {{unselectable = true, title = "Furniture Management", icon = "fas fa-lamp"}, + {title = "Furniture Shop", description = "Buy Furniture", icon = "fas fa-dollar-sign", value = "store"}} + + if #SpawnedFurniture > 0 then + Options[#Options + 1] = {title = "Reset", description = "Clears All Furniture", icon = "fas fa-trash", value = "reset"} + Options[#Options + 1] = {title = "Edit", description = "Move and/or Delete furniture items", icon = "fas fa-archive", value = "edit"} + + end + ESX.OpenContext("right", Options, function(data, element) + if element.value then + if element.value == "store" then + FurnitureStores(PropertyId) + elseif element.value == "edit" then + FurnitureEditSelect(PropertyId) + elseif element.value == "reset" then + ESX.TriggerServerCallback("esx_property:RemoveAllfurniture", function(Removed) + if Removed then + ESX.ShowNotification("Furniture Reset!", "success") + else + ESX.ShowNotification("You Cannot Reset This Property!", "error") + end + end, PropertyId) + end + end + end) + end + + ESX.RegisterInput("furniture", "(ESX Property) Open Furniture Menu", "keyboard", "M", function() + if InProperty then + ESX.TriggerServerCallback('esx_property:CanOpenFurniture', function(Access) + if Access then + if not inFurniture then + FurnitureMenu(CurrentId) + else + FurnitureStores(CurrentId) + end + else + ESX.ShowNotification("You ~r~Cannot~s~ Access This Menu!", 5000, "error") + end + end, CurrentId) + else + return + end + end) +end diff --git a/[esx_addons]/esx_property/client/html/copy.html b/[esx_addons]/esx_property/client/html/copy.html new file mode 100644 index 00000000..5d62698d --- /dev/null +++ b/[esx_addons]/esx_property/client/html/copy.html @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/[esx_addons]/esx_property/client/main.lua b/[esx_addons]/esx_property/client/main.lua new file mode 100644 index 00000000..a682cd3c --- /dev/null +++ b/[esx_addons]/esx_property/client/main.lua @@ -0,0 +1,1527 @@ +--[[ + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 GameBuild = GetGameBuildNumber() +Properties = {} +CurrentId = 0 +local CurrentDrawing = {} +InProperty = false +PlayerKeys = {} +InCCTV = false +local Blips = {} +local Shell = nil +local PM = Config.PlayerManagement +local CreateThread = CreateThread +local Wait = Wait +local ShowingUIs = {Exit = false, Wardrobe = false, Storage = false, ExitShell = false} +local ox_inventory = exports.ox_inventory +local DoScreenFadeIn = DoScreenFadeIn +local PlayerPedId = PlayerPedId +local GetEntityCoords = GetEntityCoords +local GetEntityHeading = GetEntityHeading +local IsControlJustPressed = IsControlJustPressed +local IsControlPressed = IsControlPressed +local DoScreenFadeOut = DoScreenFadeOut + +DoScreenFadeIn(3000) +function RefreshBlips() + for i = 1, #Blips, 1 do + RemoveBlip(Blips[i]) + Blips[i] = nil + end + + for k, v in pairs(Properties) do + if v.Owned and Config.OwnedBlips then + if v.Owner == ESX.PlayerData.identifier then + local Blip = AddBlipForCoord(v.Entrance.x, v.Entrance.y, v.Entrance.z) + SetBlipSprite(Blip, 40) + SetBlipAsShortRange(Blip, true) + SetBlipScale(Blip, 0.8) + SetBlipColour(Blip, 0) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString(v.Name) + EndTextCommandSetBlipName(Blip) + SetBlipCategory(Blip, 11) + Blips[#Blips + 1] = Blip + elseif PlayerKeys[k] then + local Blip = AddBlipForCoord(v.Entrance.x, v.Entrance.y, v.Entrance.z) + SetBlipSprite(Blip, GameBuild >= 2699 and 811 or 134) + SetBlipAsShortRange(Blip, true) + SetBlipScale(Blip, 0.9) + SetBlipColour(Blip, 26) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString(v.Name) + EndTextCommandSetBlipName(Blip) + SetBlipCategory(Blip, 11) + Blips[#Blips + 1] = Blip + end + elseif not v.Owned and Config.ForSaleBlips then + local Blip = AddBlipForCoord(v.Entrance.x, v.Entrance.y, v.Entrance.z) + SetBlipSprite(Blip, 350) + SetBlipAsShortRange(Blip, true) + SetBlipScale(Blip, 0.7) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString(v.Name) + EndTextCommandSetBlipName(Blip) + SetBlipCategory(Blip, 10) + Blips[#Blips + 1] = Blip + end + end + + if PM.Enabled then + local Blip = AddBlipForCoord(PM.Locations.Entrance) + SetBlipSprite(Blip, 374) + SetBlipColour(Blip, 45) + SetBlipAsShortRange(Blip, true) + SetBlipScale(Blip, 0.7) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString(PM.joblabel .. " Office") + EndTextCommandSetBlipName(Blip) + Blips[#Blips + 1] = Blip + end +end + +RegisterNetEvent("esx_property:syncProperties", function(properties, lastProperty) + while not ESX.PlayerLoaded and not ESX.PlayerData.identifier do + Wait(0) + end + Properties = properties + for house, data in pairs(Properties) do + if data.Keys then + for ident, vaues in pairs(data.Keys) do + if vaues and ident == ESX.PlayerData.identifier then + PlayerKeys[house] = true + end + end + end + end + if lastProperty then + if Properties[lastProperty.id] and (Properties[lastProperty.id].Owner == ESX.PlayerData.identifier or PlayerKeys[lastProperty.id]) then + AttemptHouseEntry(lastProperty.id) + else + ESX.TriggerServerCallback('esx_property:RemoveLastProperty', function() + SetEntityCoords(PlayerPedId(), vector3(lastProperty.coords.x, lastProperty.coords.y, lastProperty.coords.z)) + end) + end + end + RefreshBlips() +end) + +RegisterNetEvent("esx_property:giveKeyAccess", function(Property) + ESX.TriggerServerCallback("esx_property:ShouldHaveKey", function(Should) + if Should then + PlayerKeys[Property] = true + end + end, Property) +end) + +RegisterNetEvent("esx_property:RemoveKey", function(Property) + PlayerKeys[Property] = false +end) + +RegisterNetEvent('esx:setJob') +AddEventHandler('esx:setJob', function(job) + RefreshBlips() +end) + +exports("GetProperties", function() + return Properties +end) + +function OpenInteractionMenu(PropertyId, Interaction) + local Property = Properties[PropertyId] + if Property.Owned then + if Interaction == "Wardrobe" then + Config.WardrobeInteraction(PropertyId, Interaction) + elseif Interaction == "Storage" then + if Config.OxInventory then + exports.ox_inventory:openInventory('stash', {id = 'property-' .. PropertyId, owner = Property.Owner}) + end + end + end +end + +function GiveKeysMenu(Property) + ESX.TriggerServerCallback("esx_property:GetInsidePlayers", function(Players) + local Elements = {{unselectable = true, title = "Nearby Players", icon = "fas fa-user-plus"}, + {title = "Go Back", icon = "fas fa-arrow-left", value = "go_back"}} + for i = 1, #Players, 1 do + Elements[#Elements + 1] = {title = Players[i].Name, icon = "far fa-user", index = Players[i].Id, value = "user"} + end + ESX.OpenContext("right", Elements, function(menu, element) + if element.value == "go_back" then + ManageKeys(Property) + elseif element.value == "user" then + ESX.TriggerServerCallback("esx_property:GiveKey", function(KeyGiven) + if KeyGiven then + ESX.ShowNotification("You gave a key to ~b~" .. element.title .. "~s~.", "success") + ESX.CloseContext() + else + ESX.ShowNotification("You Cannot Give This Player A ~r~Key", "error") + end + end, Property, element.index) + end + end) + end, Property) +end + +function RemoveKeysMenu(Property) + ESX.TriggerServerCallback("esx_property:GetPlayersWithKeys", function(Players) + local Elements = {{unselectable = true, title = "Remove Key From Player", icon = "fas fa-user-plus"}, + {title = "Go Back", icon = "fas fa-arrow-left", value = "go_back"}} + for k, v in pairs(Players) do + local name = v.name + local Id = k + Elements[#Elements + 1] = {title = name, icon = "far fa-user", value = "user", id = Id} + end + ESX.OpenContext("right", Elements, function(menu, element) + if element.value == "go_back" then + ManageKeys(Property) + elseif element.value == "user" then + ESX.TriggerServerCallback("esx_property:RemoveKey", function(KeyGiven) + if KeyGiven then + ESX.ShowNotification("You Revoked ~b~" .. element.title .. "~s~`s Property Access.", "success") + ESX.CloseContext() + else + ESX.ShowNotification("You Cannot ~b~Remove~s~ A ~r~Key~s~ From this Person", "error") + end + end, Property, element.id) + end + end) + end, Property) +end + +function ManageKeys(Property) + ESX.HideUI() + local Elements = {{unselectable = true, title = "Key Management", icon = "fas fa-key"}, + {title = "Go Back", icon = "fas fa-arrow-left", value = "go_back"}, + {title = "Give Keys", icon = "fas fa-plus", value = "give_keys"}, + {title = "Remove Keys", icon = "fas fa-minus", value = "remove_keys"}} + + ESX.OpenContext("right", Elements, function(menu, element) + if element.value == "go_back" then + OpenPropertyMenu(Property) + end + if element.value == "give_keys" then + GiveKeysMenu(Property) + end + if element.value == "remove_keys" then + RemoveKeysMenu(Property) + end + end) +end + +function SetPropertyName(Property) + local Name = Properties[Property].setName ~= "" and Properties[Property].setName or Properties[Property].Name + local Elements = {{unselectable = true, title = "Edit Property Name", icon = "fa-solid fa-signature"}, + {icon = "", title = "Name", input = true, inputType = "text", inputValue = Name, inputPlaceholder = Properties[Property].Name, + name = "setName"}, {icon = "fa-solid fa-check", title = "Confirm", name = "confirm"}} + + ESX.OpenContext("right", Elements, function(menu, element) + if element.name == "confirm" then + ESX.TriggerServerCallback("esx_property:SetPropertyName", function(Set) + if Set then + ESX.ShowNotification("You Have Set The Property Name To ~b~" .. menu.eles[2].inputValue .. "~s~.", "success") + ESX.CloseContext() + else + ESX.ShowNotification("You Cannot Set The Property Name To ~r~" .. menu.eles[2].inputValue .. "~s~.", "error") + end + end, Property, menu.eles[2].inputValue) + end + end) +end + +local SettingValue = "" + +function PropertyMenuElements(PropertyId) + local Property = Properties[PropertyId] + local elements = {{unselectable = true, title = Property.setName ~= "" and Property.setName or Property.Name, icon = "fas fa-home"}} + if Property.Owned then + if Property.Locked then + table.insert(elements, {title = "Door: Locked", icon = "fas fa-lock", value = 'property_unlock'}) + else + table.insert(elements, {title = "Door: Unlocked", icon = "fas fa-unlock", value = 'property_lock'}) + end + if ESX.PlayerData.identifier == Property.Owner then + table.insert(elements, + {title = "Manage Name", description = "Change The Name of the Property.", icon = "fa-solid fa-signature", value = 'property_name'}) + end + if not InProperty then + if ESX.PlayerData.identifier == Property.Owner then + table.insert(elements, {title = "Manage Keys", description = "Give And Remove Property Access.", icon = "fas fa-key", value = 'property_keys'}) + table.insert(elements, + {title = "Sell", description = "Sell This Property For $" .. ESX.Math.GroupDigits(ESX.Round(Property.Price * 0.6)), + icon = "fas fa-dollar-sign", value = 'property_sell'}) + end + if Config.Raiding.Enabled and Property.Locked and ESX.PlayerData.job and ESX.GetPlayerData().job.name == "police" then + table.insert(elements, {title = "Raid", description = "Burst Through The Door", icon = "fas fa-bomb", value = 'property_raid'}) + end + else + if (Config.CCTV.Enabled and Properties[PropertyId].cctv.enabled) and (ESX.PlayerData.identifier == Property.Owner or PlayerKeys[PropertyId]) then + table.insert(elements, {title = "CCTV", description = "View The Property Camera.", icon = "fas fa-video", value = 'property_cctv'}) + end + if (ESX.PlayerData.identifier == Property.Owner or PlayerKeys[PropertyId]) then + if Config.CanCustomiseInventoryAndWardrobePositions then + if Config.OxInventory then + table.insert(elements, {title = "Inventory", description = "Change Position of Property Storage.", + icon = "fa-solid fa-up-down-left-right", value = 'property_inventory'}) + end + table.insert(elements, {title = "Wardrobe", description = "Change Position of Property Wardrobe.", icon = "fa-solid fa-up-down-left-right", + value = 'property_wardrobe'}) + end + table.insert(elements, + {title = "Furniture", description = "Open Furniture Menu.", icon = "fas fa-boxes-stacked", value = 'property_furniture'}) + end + end + if (not Property.Locked or Config.OwnerCanAlwaysEnter and ESX.PlayerData.identifier == Property.Owner) and not InProperty then + table.insert(elements, {title = "Enter", icon = "fas fa-door-open", value = 'property_enter'}) + end + if Property.Locked and not InProperty and + not (Config.OwnerCanAlwaysEnter and ESX.PlayerData.identifier == Property.Owner or PlayerKeys[PropertyId]) then + table.insert(elements, {title = "Knock On Door", icon = "fa-solid fa-hand-sparkles", value = 'property_knock'}) + end + else + if not PM.Enabled then + table.insert(elements, + {title = "Buy", description = "Buy This Property For $" .. ESX.Math.GroupDigits(ESX.Round(Property.Price)), icon = "fas fa-shopping-cart", + value = 'property_buy'}) + else + if ESX.PlayerData.job.name == PM.job and ESX.PlayerData.job.grade >= PM.Permissions.SellProperty then + table.insert(elements, {title = "Sell To Player", description = "Sell This Property For $" .. ESX.Math.GroupDigits(ESX.Round(Property.Price)), + icon = "fas fa-shopping-cart", value = 'property_sell_re'}) + end + end + if not Property.Locked and not InProperty then + table.insert(elements, {title = "View", icon = "fas fa-door-open", value = 'property_enter'}) + end + end + + if InProperty and (not Property.Locked or Config.CanAlwaysExit) then + table.insert(elements, {title = "Exit", icon = "fas fa-sign-out-alt", value = 'property_exit'}) + end + + return elements +end + +function OpenPropertyMenu(PropertyId) + if SettingValue ~= "" then + ESX.ShowNotification("You Are Currently Editing The Property.") + return + end + ESX.HideUI() + local Property = Properties[PropertyId] + local elements = PropertyMenuElements(PropertyId) + + ESX.OpenContext("right", elements, function(menu, element) + if element.value == "property_unlock" then + ESX.TriggerServerCallback("esx_property:toggleLock", function(IsUnlocked) + if IsUnlocked then + local eles = PropertyMenuElements(PropertyId) + exports["esx_context"]:Refresh(eles) + else + ESX.ShowNotification("You Cannot ~b~Unlock~s~ This Property", "error") + end + end, PropertyId) + end + if element.value == "property_lock" then + ESX.TriggerServerCallback("esx_property:toggleLock", function(IsUnlocked) + if IsUnlocked then + local eles = PropertyMenuElements(PropertyId) + exports["esx_context"]:Refresh(eles) + else + ESX.ShowNotification("You Cannot ~b~Lock~s~ This Property", "error") + end + end, PropertyId) + end + if element.value == "property_cctv" then + CCTV(PropertyId) + end + if element.value == "property_raid" then + ESX.TriggerServerCallback("esx_property:CanRaid", function(CanRaid) + if CanRaid then + ESX.Progressbar("~b~Preparing~s~ Raid!", 15000, {FreezePlayer = true, animation = Config.Raiding.Animation, onFinish = function() + ESX.ShowNotification("Raiding...", "success") + AttemptHouseEntry(PropertyId) + end, onCancel = function() + ESX.ShowNotification("~r~Cancelled~s~ Raid", "error") + end}) + else + ESX.ShowNotification("You Cannot ~b~Raid~s~ This Property.", "error") + end + end, PropertyId) + end + if element.value == "property_enter" then + ESX.CloseContext() + AttemptHouseEntry(PropertyId) + end + if element.value == "property_keys" then + ESX.CloseContext() + ManageKeys(PropertyId) + end + if element.value == "property_name" then + ESX.CloseContext() + SetPropertyName(PropertyId) + end + if element.value == "property_furniture" then + ESX.CloseContext() + FurnitureMenu(PropertyId) + end + if element.value == "property_inventory" then + ESX.CloseContext() + ShowingUIs.Exit = false + SettingValue = "Storage" + Wait(20) + ESX.TextUI("Press ~b~[G]~s~ To Set Storage Position") + while SettingValue ~= "" do + Wait(1) + if IsControlJustPressed(0, 47) then + SettingValue = "" + ESX.HideUI() + ESX.TriggerServerCallback("esx_property:SetInventoryPosition", function(Success) + if Success then + ESX.ShowNotification("~b~Storage~s~ Position Set.", "success") + else + ESX.ShowNotification("~r~Cannot~s~ Set ~b~Storage~s~ Position.", "error") + end + end, PropertyId, GetEntityCoords(PlayerPedId())) + break + end + end + end + if element.value == "property_wardrobe" then + ESX.CloseContext() + ShowingUIs.Exit = false + SettingValue = "Wardrobe" + Wait(20) + ESX.TextUI("Press ~b~[G]~s~ To Set Wardrobe Position") + while SettingValue ~= "" do + Wait(1) + if IsControlJustPressed(0, 47) then + SettingValue = "" + ESX.HideUI() + ESX.TriggerServerCallback("esx_property:SetWardrobePosition", function(Success) + if Success then + ESX.ShowNotification("~b~Wardrobe~s~ Position Set.", "success") + else + ESX.ShowNotification("~r~Cannot~s~ Set ~b~Wardrobe~s~ Position.", "error") + end + end, PropertyId, GetEntityCoords(PlayerPedId())) + break + end + end + end + if element.value == "property_exit" then + ESX.CloseContext() + if SettingValue == "" then + AttemptHouseExit(PropertyId) + else + ESX.ShowNotification("Please Finish Setting ~b~" .. SettingValue .. "~s~ Position") + end + end + if element.value == "property_buy" then + ESX.TriggerServerCallback("esx_property:buyProperty", function(IsBought) + if IsBought then + local eles = PropertyMenuElements(PropertyId) + exports["esx_context"]:Refresh(eles) + else + ESX.ShowNotification("You don't have enough money!", "error") + end + end, PropertyId) + end + if element.value == "property_sell_re" then + local Elements = {{unselectable = true, title = "Select Player"}} + ESX.TriggerServerCallback("esx_property:GetNearbyPlayers", function(Players) + if Players then + for i = 1, #Players do + Elements[#Elements + 1] = {title = Players[i].name, value = Players[i].source} + end + + ESX.OpenContext("right", Elements, function(menu, element) + if element.value then + ESX.TriggerServerCallback("esx_property:attemptSellToPlayer", function(IsBought) + if IsBought then + local eles = PropertyMenuElements(PropertyId) + exports["esx_context"]:Refresh(eles) + else + ESX.ShowNotification("Cannot Sell To This Player!", "error") + end + end, PropertyId, element.value) + end + end) + end + end, PropertyId) + end + if element.value == "property_knock" then + ESX.ShowNotification("Knocking On Door...", "success") + ESX.TriggerServerCallback("esx_property:KnockOnDoor", function(HasKnocked) + if not HasKnocked then + ESX.ShowNotification("It Seems Nobody is home...", "error") + end + end, PropertyId) + end + if element.value == "property_sell" then + ESX.TriggerServerCallback("esx_property:sellProperty", function(IsSold) + if IsSold then + local eles = PropertyMenuElements(PropertyId) + exports["esx_context"]:Refresh(eles) + else + ESX.ShowNotification("You ~r~Cannot~s~ Sell this Property!", "error") + end + end, PropertyId) + end + end, function(menu) + CurrentDrawing.Showing = false + end) +end + +function AttemptHouseExit(PropertyId) + local Property = Properties[PropertyId] + + ESX.ShowNotification("Exiting property...", "success") + FreezeEntityPosition(PlayerPedId(), true) + InProperty = false + CurrentId = 0 + SettingValue = "" + ESX.HideUI() + ESX.UI.Menu.CloseAll() + SetRainLevel(-1) + DoScreenFadeOut(1500) + Wait(1500) + TriggerServerEvent("esx_property:leave", PropertyId) + if Shell then + DeleteObject(Shell) + Shell = nil + end + if Config.Furniture.Enabled then + for k, v in pairs(SpawnedFurniture) do + DeleteObject(v.obj) + end + SpawnedFurniture = {} + end + Wait(1500) + FreezeEntityPosition(PlayerPedId(), false) + DoScreenFadeIn(1500) +end + +RegisterCommand("getoffset", function(source) + if InProperty then + local PlayerPed = PlayerPedId() + local Pcoords = GetEntityCoords(PlayerPed) + local Property = Properties[CurrentId] + local Interior = GetInteriorValues(Property.Interior) + print(vector3(Property.Entrance.x, Property.Entrance.y, 2000) - Pcoords) + end +end) + +function AttemptHouseEntry(PropertyId) + local Property = Properties[PropertyId] + local Interior = GetInteriorValues(Property.Interior) + if Interior.type == "shell" and not Config.Shells then + ESX.ShowNotification("This Interior Uses Shells, which are disabled!.", "error") + return + end + if Property.Locked and not ESX.GetPlayerData().job.name == "police" then + ESX.ShowNotification("The door is ~r~locked.", "error") + return + end + ESX.ShowNotification("Entering property...", "success") + CurrentId = PropertyId + ESX.UI.Menu.CloseAll() + local Property = Properties[CurrentId] + FreezeEntityPosition(PlayerPedId(), true) + DoScreenFadeOut(1500) + Wait(1500) + if Interior.type == "shell" then + ESX.Streaming.RequestModel(GetHashKey(Property.Interior), function() + if Shell then + DeleteObject(Shell) + Shell = nil + end + local Pos = vector3(Property.Entrance.x, Property.Entrance.y, 2000) + Shell = CreateObjectNoOffset(GetHashKey(Property.Interior), Pos + Interior.pos, false, false, false) + SetEntityHeading(Shell, 0.0) + while not DoesEntityExist(Shell) do + Wait(1) + end + FreezeEntityPosition(Shell, true) + end) + end + if Properties[PropertyId].furniture then + for k, v in pairs(Properties[PropertyId].furniture) do + ESX.Game.SpawnLocalObject(v.Name, v.Pos, function(object) + SetEntityCoordsNoOffset(object, v.Pos.x, v.Pos.y, v.Pos.z) + SetEntityHeading(object, v.Heading) + SetEntityAsMissionEntity(object, true, true) + FreezeEntityPosition(object, true) + SpawnedFurniture[k] = {obj = object, data = v} + end) + end + end + local ShowingTextUI2 = false + FreezeEntityPosition(PlayerPedId(), false) + TriggerServerEvent("esx_property:enter", PropertyId) + Wait(1500) + DoScreenFadeIn(1800) + InProperty = true + if not Config.OxInventory then + Interior.positions.Storage = nil + Properties[CurrentId].positions.Storage = nil + end + + CreateThread(function() + while InProperty do + local Property = Properties[CurrentId] + local Sleep = 1000 + local Near = false + SetRainLevel(0.0) + local PlayerPed = PlayerPedId() + local PlayerCoords = GetEntityCoords(PlayerPed) + if Interior.type == "shell" then + if #(PlayerCoords - vector3(Property.Entrance.x, Property.Entrance.y, 1999)) < 5.0 then + Sleep = 0 + DrawMarker(27, vector3(Property.Entrance.x, Property.Entrance.y, 2000.2), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 50, 50, 200, 200, + false, false, 2, true, nil, nil, false) + if #(PlayerCoords.xy - vector2(Property.Entrance.x, Property.Entrance.y)) <= 2.5 then + Near = true + if not ShowingUIs.Exit then + + local Pname = Properties[CurrentId].setName ~= "" and Properties[CurrentId].setName or Properties[CurrentId].Name + ESX.TextUI("Press ~b~[E]~s~ to Access ~b~" .. Pname) + ShowingUIs.Exit = true + end + if IsControlJustPressed(0, 38) then + OpenPropertyMenu(CurrentId) + end + else + if not Near and ShowingUIs.Exit and SettingValue == "" then + ShowingUIs.Exit = false + ESX.HideUI() + ESX.CloseContext() + end + end + end + + if Property.Owned then + for k, v in pairs(Properties[CurrentId].positions) do + local v = vector3(v.x, v.y, v.z) + local CanDo = true + if CanDo then + local Poss = vector3(Property.Entrance.x, Property.Entrance.y, 1999) - v + if #(PlayerCoords - Poss) < 5.0 then + Sleep = 0 + DrawMarker(27, Poss, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 200, 50, 50, 200, false, false, 2, true, nil, nil, false) + if #(PlayerCoords - Poss) < 2.0 and SettingValue == "" then + Near = true + if not ShowingUIs[k] then + ShowingUIs[k] = true + + ESX.TextUI("Press ~b~[E]~s~ to Access ~b~" .. k) + end + if IsControlJustPressed(0, 38) then + OpenInteractionMenu(CurrentId, k) + end + else + if not Near and ShowingUIs[k] and SettingValue == "" then + ShowingUIs[k] = false + ESX.HideUI() + end + end + end + end + end + if not Near and ShowingUIs and SettingValue == "" then + ShowingTextUI2 = false + ESX.HideUI() + end + end + else + if #(PlayerCoords - Interior.pos) < 5.0 then + Sleep = 0 + DrawMarker(27, vector3(Interior.pos.xy, Interior.pos.z - 0.98), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 50, 50, 200, 200, false, false, + 2, true, nil, nil, false) + if #(PlayerCoords - Interior.pos) < 2.0 then + if not ShowingUIs.Exit then + ShowingUIs.Exit = true + local Pname = Properties[CurrentId].setName ~= "" and Properties[CurrentId].setName or Properties[CurrentId].Name + ESX.TextUI("Press ~b~[E]~s~ to Access ~b~" .. Pname) + end + if IsControlJustPressed(0, 38) then + OpenPropertyMenu(CurrentId) + end + else + if ShowingUIs.Exit and SettingValue == "" then + ShowingUIs.Exit = false + ESX.HideUI() + ESX.CloseContext() + end + end + end + + if Property.Owned then + for k, v in pairs(Properties[CurrentId].positions) do + v = vector3(v.x, v.y, v.z) + local CanDo = true + + if CanDo then + if #(PlayerCoords - v) < 5.0 then + Sleep = 0 + DrawMarker(27, vector3(v.xy, v.z - 0.98), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 200, 50, 50, 200, false, false, 2, true, nil, + nil, false) + if #(PlayerCoords - v) < 2.0 then + if not ShowingUIs[k] then + ShowingUIs[k] = true + ESX.TextUI("Press ~b~[E]~s~ to Access ~b~" .. k) + end + if IsControlJustPressed(0, 38) then + OpenInteractionMenu(CurrentId, k) + end + else + if ShowingUIs[k] and SettingValue == "" then + ShowingUIs[k] = false + ESX.HideUI() + end + end + end + end + end + end + end + Wait(Sleep) + end + end) +end + +function StoreVehicle(PropertyId) + local Vehicle = GetVehiclePedIsIn(PlayerPedId(), false) + if Vehicle then + local VehProperties = ESX.Game.GetVehicleProperties(Vehicle) + VehProperties.DisplayName = GetLabelText(GetDisplayNameFromVehicleModel(VehProperties.model)) + ESX.TriggerServerCallback("esx_property:StoreVehicle", function(result) + if result then + SetEntityAsMissionEntity(Vehicle, true, true) + DeleteVehicle(Vehicle) + ESX.ShowNotification("Vehicle ~b~Stored!", "success") + return + else + ESX.ShowNotification("You Cannot Store This Vehicle!", "error") + return + end + end, PropertyId, VehProperties) + end +end + +function AccessGarage(PropertyId) + ESX.TriggerServerCallback("esx_property:AccessGarage", function(Vehicles) + if Vehicles then + local elements = {{unselectable = true, icon = "fas fa-warehouse", title = "Property Garage"}} + for k, v in pairs(Vehicles) do + elements[#elements + 1] = {title = v.vehicle.DisplayName .. " | " .. v.vehicle.plate, Properties = v.vehicle, index = k} + end + ESX.OpenContext("right", elements, function(menu, element) + if element.Properties then + ESX.CloseContext() + ESX.ShowNotification("Retrieving ~b~" .. element.Properties.DisplayName .. "~s~ ...") + if ESX.Game.IsSpawnPointClear(vector3(Properties[PropertyId].garage.pos.x, Properties[PropertyId].garage.pos.y, + Properties[PropertyId].garage.pos.z), 3.0) then + ESX.Game.SpawnVehicle(element.Properties.model, Properties[PropertyId].garage.pos, Properties[PropertyId].garage.Heading, + function(vehicle) + SetEntityAsMissionEntity(vehicle, true, true) + ESX.Game.SetVehicleProperties(vehicle, element.Properties) + TaskWarpPedIntoVehicle(PlayerPedId(), vehicle, -1) + SetModelAsNoLongerNeeded(element.Properties.model) + TriggerServerEvent("esx_property:SetVehicleOut", PropertyId, element.index) + end) + end + end + end) + else + ESX.ShowNotification("You Cannot Access This Garage!", "error") + return + end + end, PropertyId) +end + +CreateThread(function() + local ShowingTextUI = false + while true do + local Sleep = 2000 + + if #(Properties) > 0 then + Sleep = 800 + local nearby = false + for i = 1, #(Properties) do + if Properties[i].Entrance then + local GetEntityCoords = GetEntityCoords + local IsControlJustPressed = IsControlJustPressed + local DrawMarker = DrawMarker + local Coords = GetEntityCoords(PlayerPedId()) + local Entrance = vector3(Properties[i].Entrance.x, Properties[i].Entrance.y, Properties[i].Entrance.z) + if not InCCTV then + if #(Coords - Entrance) < 10.0 then + Sleep = 0 + DrawMarker(27, Entrance, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 50, 200, 50, 200, false, false, 2, true, nil, nil, false) + if #(Coords - Entrance) < 1.5 then + nearby = true + local PropertyName = Properties[i].setName ~= "" and Properties[i].setName or Properties[i].Name + if not CurrentDrawing.Showing or CurrentDrawing.Name ~= PropertyName then + CurrentDrawing.Name = PropertyName + CurrentDrawing.Showing = true + ESX.TextUI("Press ~b~[E]~s~ to Access ~b~" .. PropertyName) + end + if IsControlJustPressed(0, 38) then + if Properties[i].Locked and Properties[i].Owned and Properties[i].Owner ~= ESX.PlayerData.identifier and not ESX.PlayerData.job.name == + "police" and not PlayerKeys[i] then + ESX.ShowNotification("This property is ~r~locked.", "error") + else + OpenPropertyMenu(i) + end + end + end + end + end + if Properties[i].garage.enabled and Properties[i].garage.pos and (ESX.PlayerData.identifier == Properties[i].Owner or PlayerKeys[i]) then + local Garage = vector3(Properties[i].garage.pos.x, Properties[i].garage.pos.y, Properties[i].garage.pos.z) + if #(Coords - Garage) < 10.0 then + Sleep = 0 + DrawMarker(36, Garage, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.5, 1.5, 1.5, 50, 200, 50, 200, false, false, 2, true, nil, nil, false) + if #(Coords - Garage) < 3.0 then + nearby = true + if GetVehiclePedIsUsing(ESX.PlayerData.ped) ~= 0 then + local veh = GetVehiclePedIsIn(ESX.PlayerData.ped, false) + local vehmodel = GetEntityModel(veh) + local DisplayName = GetLabelText(GetDisplayNameFromVehicleModel(vehmodel)) + if not CurrentDrawing.Showing or CurrentDrawing.Name ~= DisplayName then + CurrentDrawing.Name = DisplayName + CurrentDrawing.Showing = true + ESX.TextUI("Press ~b~[E]~s~ to Store ~b~" .. DisplayName) + end + else + if not CurrentDrawing.Showing or CurrentDrawing.Name ~= "Garage" then + CurrentDrawing.Name = "Garage" + CurrentDrawing.Showing = true + ESX.TextUI("Press ~b~[E]~s~ to Access ~b~ Garage") + end + end + if IsControlJustPressed(0, 38) then + if GetVehiclePedIsUsing(ESX.PlayerData.ped) ~= 0 then + StoreVehicle(i) + else + AccessGarage(i) + end + end + end + end + end + end + end + if not nearby and CurrentDrawing.Showing and SettingValue == "" then + ESX.HideUI() + CurrentDrawing.Showing = false + ESX.CloseContext() + end + end + Wait(Sleep) + end +end) + +function OpenPMQuickMenu(Action) + if Action == "Entrance" then + DoScreenFadeOut(1500) + Wait(1500) + ESX.TriggerServerCallback("esx_property:PMenterOffice", function(HasEntered) + if HasEntered then + ESX.ShowNotification("~b~Entering~s~ Office.", "success") + else + ESX.ShowNotification("You ~r~Cannot~s~ Enter This Office!", "error") + end + Wait(1500) + DoScreenFadeIn(1500) + end) + elseif Action == "Exit" then + DoScreenFadeOut(1500) + Wait(1500) + ESX.TriggerServerCallback("esx_property:PMexitOffice", function(HasExited) + if HasExited then + ESX.ShowNotification("~b~Exiting~s~ Office.", "success") + else + ESX.ShowNotification("You ~r~Cannot~s~ Exit This Office!", "error") + end + Wait(1500) + DoScreenFadeIn(1500) + end) + elseif Action == "Properties" then + TriggerEvent("esx_property:AdminMenu") + + elseif Action == "ActionsMenu" then + local elements = {{unselectable = true, title = "Real Estate Actions", value = "RealEstateActions"}} + + if ESX.PlayerData.job.name == PM.job then + if ESX.PlayerData.job.grade >= PM.Permissions.CreateProperty then + elements[#elements + 1] = {title = "Create Property", value = "CreateProperty"} + end + if ESX.PlayerData.job.grade >= PM.Permissions.BillPlayers then + elements[#elements + 1] = {title = "Create Bill", value = "bill"} + end + if ESX.PlayerData.job.grade >= PM.Permissions.ManagePropertiesFromQuickActions then + elements[#elements + 1] = {title = "Manage Properties", value = "manage"} + end + end + ESX.OpenContext("right", elements, function(menu, element) + if element.value == "CreateProperty" then + TriggerEvent("esx_property:CreateProperty") + end + if element.value == "manage" then + TriggerEvent("esx_property:AdminMenu") + end + end) + end +end + +ESX.RegisterInput("realestatequickmenu", "(ESX Property) Open Real Estate Quick Actions", "keyboard", "F5", function() + ESX.TriggerServerCallback('esx_property:IsAdmin', function(Access) + if Access then + OpenPMQuickMenu("ActionsMenu") + end + end) +end) + +local PMdrawing = {Entrance = false, Exit = false} +CreateThread(function() + while PM.Enabled do + local Sleep = 2500 + local DrawMarker = DrawMarker + if ESX.IsPlayerLoaded() then + if ESX.PlayerData.job and ESX.PlayerData.job.name == PM.job then + Sleep = 1500 + + local Coords = GetEntityCoords(ESX.PlayerData.ped) + local nearby = false + + for k, v in pairs(PM.Locations) do + local Dist = #(Coords - v) + if Dist <= 10.0 then + nearby = true + Sleep = 0 + DrawMarker(27, v, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 50, 200, 50, 200, false, false, 2, true, nil, nil, false) + if Dist <= 1.5 then + if not PMdrawing[k] then + PMdrawing[k] = true + ESX.TextUI("Press ~b~[E]~s~ to Access ~b~" .. k) + end + if IsControlJustPressed(0, 38) then + OpenPMQuickMenu(k) + end + end + else + if not nearby and PMdrawing[k] then + ESX.HideUI() + PMdrawing[k] = false + ESX.CloseContext() + end + end + end + end + end + Wait(Sleep) + end +end) + +local HouseData = {} + +RegisterNetEvent("esx_property:CreateProperty", function() + ESX.TriggerServerCallback('esx_property:CanCreateProperty', function(data) + if data then + local GetEntityCoords = GetEntityCoords + local GetStreetNameAtCoord = GetStreetNameAtCoord + local GetStreetNameFromHashKey = GetStreetNameFromHashKey + local Pcoords = GetEntityCoords(ESX.PlayerData.ped) + local StreetHash = GetStreetNameAtCoord(Pcoords.x, Pcoords.y, Pcoords.z) + local StreetName = GetStreetNameFromHashKey(StreetHash) + HouseData = {} + local Property = {{unselectable = true, icon = "fas fa-plus", title = "Property Creation"}, + {value = 0, title = "Street Number", icon = "fas fa-list-ol", description = "Set the property Street Number.", input = true, + inputType = "number", inputPlaceholder = "Number...", inputValue = nil, inputMin = 1, inputMax = 90000, index = "hnumber"}, + {title = "Price", icon = "fas fa-dollar-sign", input = true, inputType = "number", description = "Set the property Price.", + inputPlaceholder = "Price...", inputValue = nil, inputMin = 1, inputMax = 50000000, index = "price"}, + {title = "Select Interior", description = "Set the property Interior.", icon = "fas fa-home", index = "interior"}, + {title = "Garage", description = "Set Garage settings (optional)", icon = "fas fa-warehouse", value = {enabled = false}, + index = "garage", disabled = not (Config.Garage.Enabled)}, + {title = "CCTV", description = "Set CCTV settings (optional)", icon = "fas fa-video", + value = {enabled = false, rot = GetGameplayCamRot(2), maxleft = 80, maxright = -20}, index = "cctv", + disabled = not (Config.CCTV.Enabled)}, + {title = "Entrance:", description = "Set the entrance of the property.", icon = "fas fa-map-marker-alt", index = "entrance"}, + {title = "Create Property", icon = "fas fa-check", description = "All Inputs Required to continue.", index = "creation"}} + local function OpenCreate() + local opos = {} + ESX.OpenContext("right", Property, function(menu, element) + if menu.eles[2] and menu.eles[2].inputValue and menu.eles[1].title == "Property Creation" then + Property[2].inputValue = menu.eles[2].inputValue + HouseData.name = menu.eles[2].inputValue .. " " .. StreetName + end + if menu.eles[3] and menu.eles[3].inputValue and menu.eles[1].title == "Property Creation" then + Property[3].inputValue = menu.eles[3].inputValue + HouseData.price = menu.eles[3].inputValue + end + if element.index then + if element.index == "entrance" then + local PlayerPos = GetEntityCoords(ESX.PlayerData.ped) + HouseData.entrance = {x = ESX.Math.Round(PlayerPos.x, 2), y = ESX.Math.Round(PlayerPos.y, 2), z = ESX.Math.Round(PlayerPos.z, 2) - 0.98} + Property[7].title = "Entrance Set." + Property[7].description = "Entrance: " .. HouseData.entrance.x .. ", " .. HouseData.entrance.y .. ", " .. HouseData.entrance.z + OpenCreate() + end + if element.index == "selectedinterior" then + HouseData.interior = element.value + Property[4].title = "Interior Set." + Property[4].description = "Interior: " .. element.title + OpenCreate() + end + if element.index == "IPL" then + local ints = {{unselectable = true, icon = "fas fa-warehouse", title = "IPL Interiors"}} + for i = 1, #(Config.Interiors.IPL) do + ints[#ints + 1] = {title = Config.Interiors.IPL[i].label, index = "selectedinterior", value = Config.Interiors.IPL[i].value} + exports["esx_context"]:Refresh(ints, "right") + end + end + if element.index == "Shells" then + local ints = {{unselectable = true, icon = "fas fa-warehouse", title = "Custom Interiors"}} + for i = 1, #(Config.Interiors.Shells) do + ints[#ints + 1] = {title = Config.Interiors.Shells[i].label, index = "selectedinterior", value = Config.Interiors.Shells[i].value} + exports["esx_context"]:Refresh(ints, "right") + end + end + if element.index == "interior" then + local catsa = {{unselectable = true, icon = "fas fa-warehouse", title = "Interior Types"}, + {title = "IPL Interiors", description = "Native GTA Interiors, Made by R*", index = "IPL"}} + if Config.Shells then + catsa[3] = {title = "Custom Interiors", description = "Custom Interiors, Made by You", index = "Shells"} + end + exports["esx_context"]:Refresh(catsa, "right") + end + if element.index == "return" then + OpenCreate() + end + if element.index == "cctv" then + local status = Property[6].value.enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-video", title = "CCTV Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fas fa-eye" or "fas fa-eye-slash", + description = "Current Status: " .. status, index = "ToggleCCTV"}, + {title = "Set Angle", icon = "fas fa-rotate", disabled = not Property[6].value.enabled, + description = "Sets the Camera angle to your Cameras Direction", index = "SetCCTVangle"}, + {title = "Go Back", icon = "fas fa-arrow-left", description = "return to property creation.", index = "return"}} + exports["esx_context"]:Refresh(opos, "right") + end + if element.index == "ToggleCCTV" then + Property[6].value.enabled = not Property[6].value.enabled + local status = Property[6].value.enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-video", title = "CCTV Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fas fa-eye" or "fas fa-eye-slash", + description = "Current Status: " .. status, index = "ToggleCCTV"}, + {title = "Set Angle", icon = "fas fa-rotate", disabled = not Property[6].value.enabled, + description = "Sets the Camera angle to your Cameras Direction", index = "SetCCTVangle"}, + {title = "Go Back", icon = "fas fa-arrow-left", description = "return to property creation.", index = "return"}} + exports["esx_context"]:Refresh(opos, "right") + end + if Config.Garage.Enabled and element.index == "garage" then + local status = Property[5].value.enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-warehouse", title = "Garage Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fa-solid fa-toggle-on" or "fa-solid fa-toggle-off", + description = "Current Status: " .. status, index = "ToggleGarage"}} + if Property[5].value.enabled then + opos[#opos + 1] = {title = "Set Position", icon = "fas fa-map-marker-alt", disabled = not Property[5].value.enabled, + description = "Sets the Camera angle to your Cameras Direction", index = "SetGaragePos"} + if Property[5].value.pos then + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to property creation.", index = "return"} + end + else + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to property creation.", index = "return"} + end + exports["esx_context"]:Refresh(opos, "right") + end + if element.index == "ToggleGarage" then + Property[5].value.enabled = not Property[5].value.enabled + local status = Property[5].value.enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-warehouse", title = "Garage Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fa-solid fa-toggle-on" or "fa-solid fa-toggle-off", + description = "Current Status: " .. status, index = "ToggleGarage"}} + if Property[5].value.enabled then + opos[#opos + 1] = {title = "Set Position", icon = "fas fa-map-marker-alt", disabled = not Property[5].value.enabled, + description = "Sets the Camera angle to your Cameras Direction", index = "SetGaragePos"} + else + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to property creation.", index = "return"} + end + exports["esx_context"]:Refresh(opos, "right") + end + if element.index == "SetGaragePos" then + ESX.CloseContext() + ESX.TextUI("Press ~b~[E]~s~ to Set Position") + while true do + Wait(0) + if IsControlJustPressed(0, 38) then + local PlayerPos = GetEntityCoords(ESX.PlayerData.ped) + Property[5].value.pos = GetEntityCoords(PlayerPedId()) + Property[5].value.heading = GetEntityHeading(PlayerPedId()) + ESX.HideUI() + OpenCreate() + break + end + end + end + if element.index == "SetCCTVangle" then + ESX.CloseContext() + ESX.TextUI("Press ~b~[E]~s~ to Set Angle") + local stage = "angle" + while true do + Wait(0) + if IsControlJustPressed(0, 38) then + if stage == "angle" then + Property[6].value.rot = GetGameplayCamRot(2) + ESX.TextUI("Press ~b~[E]~s~ to Set Max Right Roation") + stage = "maxright" + elseif stage == "maxright" then + Property[6].value.maxright = GetGameplayCamRot(2).z + ESX.TextUI("Press ~b~[E]~s~ to Set Max Left Roation") + stage = "maxleft" + elseif stage == "maxleft" then + Property[6].value.maxleft = GetGameplayCamRot(2).z + ESX.HideUI() + OpenCreate() + break + end + end + end + end + if element.index == "creation" then + if HouseData.price and HouseData.name and HouseData.entrance and HouseData.interior then + local newProperty = {name = HouseData.name, price = HouseData.price, interior = HouseData.interior, entrance = HouseData.entrance, + cctv = Property[6].value, garage = Property[5].value} + TriggerServerEvent("esx_property:server:createProperty", newProperty) + ESX.ShowNotification("Property created!", "success") + ESX.CloseContext() + HouseData = {} + else + ESX.ShowNotification("Please fill out all fields!", "error") + end + end + end + end) + end + OpenCreate() + end + end) +end) + +RegisterNetEvent("esx_property:AdminMenu", function() + ESX.TriggerServerCallback('esx_property:IsAdmin', function(data) + if data then + + function ManageProperty(currentProperty) + local Interior = GetInteriorValues(Properties[currentProperty].Interior) + ESX.TriggerServerCallback('esx_property:IsAdmin', function(data) + if data then + local opos = {} + local function GetData() + local elements = {{unselectable = true, icon = "fas fa-cogs", title = "Property Management"}, + {title = "Go Back", icon = "fas fa-arrow-left", value = "back"}, + {title = "Toggle Lock", icon = (Properties[currentProperty].Locked and "fas fa-lock") or "fas fa-unlock", + description = "Lock/Unlock The Property.", value = "lock"}, + {title = "Enter", description = "Force Entry Into The Property.", icon = "fas fa-door-open", value = "enter"}, + {title = "Price", icon = "fas fa-dollar-sign", description = "Alter The Price Of The Property.", value = "price"}, + {title = "Set Interior", description = "Renovate The Property`s Interior.", icon = "fas fa-home", value = "interior"}, + {title = "Entrance", description = "Set The Entrance As Your Position.", icon = "fas fa-map-marker-alt", + value = "entrance"}} + if Properties[currentProperty].setName ~= "" then + elements[#elements + 1] = {title = "Clear Custom Name", icon = "fa-solid fa-ban", + description = "Current Name: " .. Properties[currentProperty].setName, value = "remove_custom_name"} + end + if Config.Furniture.Enabled and #(Properties[currentProperty].furniture) > 0 then + elements[#elements + 1] = {title = "Reset Furniture", description = "Delete All Property Furniture", icon = "fas fa-eraser", + value = "refurni"} + end + if Config.Garage.Enabled then + elements[#elements + 1] = {title = "Garage", description = "Change Garage Settings", icon = "fa-solid fa-warehouse", value = "garage"} + end + if Config.CCTV.Enabled then + elements[#elements + 1] = {title = "CCTV", description = "Change CCTV Settings", icon = "fa-solid fa-camera", value = "cctv"} + end + if (Config.OxInventory) and Properties[currentProperty].positions.Storage and + (ESX.Round(Interior.positions.Storage.x, 2) ~= Properties[currentProperty].positions.Storage.x or + ESX.Round(Interior.positions.Storage.y, 2) ~= Properties[currentProperty].positions.Storage.y) then + elements[#elements + 1] = {title = "Reset Storage Position", description = "Set Storage Position To Interior Default.", + icon = "fas fa-eraser", value = "restorage"} + end + if Interior.positions.Wardrobe then + if ESX.Round(Interior.positions.Wardrobe.x, 2) ~= ESX.Round(Properties[currentProperty].positions.Wardrobe.x, 2) or + ESX.Round(Interior.positions.Wardrobe.y, 2) ~= ESX.Round(Properties[currentProperty].positions.Wardrobe.y, 2) then + elements[#elements + 1] = {title = "Reset Wardrobe Position", description = "Set Wardrobe Position To Interior Default.", + icon = "fas fa-eraser", value = "rewardrobe"} + end + end + if Properties[currentProperty].Owned then + elements[#elements + 1] = {title = "Remove Owner", icon = "fas fa-user-times", description = "Evict The Owner Of The Property.", + value = "removeowner"} + end + return elements + end + ESX.OpenContext("right", GetData(), function(menu, element) + if element.value then + if element.value == "lock" then + ESX.TriggerServerCallback("esx_property:toggleLock", function(IsUnlocked) + if IsUnlocked then + ESX.ShowNotification("Lock Toggled!", "success") + exports["esx_context"]:Refresh(GetData()) + else + ESX.ShowNotification("You Cannot Lock This Property", "error") + end + end, currentProperty) + end + if element.value == "enter" then + AttemptHouseEntry(currentProperty) + end + if element.value == "removeowner" then + ESX.TriggerServerCallback("esx_property:evictOwner", function(Evicted) + if Evicted then + ESX.ShowNotification("Owner Evicted!", "success") + exports["esx_context"]:Refresh(GetData()) + else + ESX.ShowNotification("You Cannot Evict This Owner!", "error") + end + end, currentProperty) + end + if element.value == "garage" then + local status = Properties[currentProperty].garage.enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-warehouse", title = "Garage Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fa-solid fa-toggle-on" or "fa-solid fa-toggle-off", + description = "Current Status: " .. status, value = "ToggleGarage"}} + if Properties[currentProperty].garage.enabled then + opos[#opos + 1] = {title = "Set Position", icon = "fas fa-map-marker-alt", + disabled = not Properties[currentProperty].garage.enabled, + description = "Sets the Garage Position to your Players Position", value = "SetGaragePos"} + if Properties[currentProperty].garage.pos then + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management.", + value = "return"} + end + else + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management", value = "return"} + end + exports["esx_context"]:Refresh(opos, "right") + end + if element.value == "cctv" then + local status = Properties[currentProperty].cctv.enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-warehouse", title = "CCTV Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fa-solid fa-toggle-on" or "fa-solid fa-toggle-off", + description = "Current Status: " .. status, value = "ToggleCCTV"}} + if Properties[currentProperty].cctv.enabled then + opos[#opos + 1] = {title = "Set Angle", icon = "fas fa-map-marker-alt", disabled = not Properties[currentProperty].cctv.enabled, + description = "Sets the Angle of the Camera.", value = "SetCCTVangle"} + if Properties[currentProperty].cctv.rot then + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management.", + value = "return"} + end + else + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management", value = "return"} + end + exports["esx_context"]:Refresh(opos, "right") + end + if element.value == "ToggleGarage" then + ESX.TriggerServerCallback("esx_property:toggleGarage", function(IsUnlocked, enabled) + if IsUnlocked then + ESX.ShowNotification("Garage Toggled!", "success") + local status = enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-warehouse", title = "Garage Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fa-solid fa-toggle-on" or "fa-solid fa-toggle-off", + description = "Current Status: " .. status, value = "ToggleGarage"}} + if enabled then + opos[#opos + 1] = {title = "Set Position", icon = "fas fa-map-marker-alt", disabled = not enabled, + description = "Sets the Garage Position to your Players Position", value = "SetGaragePos"} + if Properties[currentProperty].garage.pos then + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management.", + value = "return"} + end + else + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management", + value = "return"} + end + exports["esx_context"]:Refresh(opos, "right") + else + ESX.ShowNotification("You ~r~Cannot~s~ Toggle This Option!", "error") + end + end, currentProperty) + end + if element.value == "ToggleCCTV" then + ESX.TriggerServerCallback("esx_property:toggleCCTV", function(IsUnlocked, enabled) + if IsUnlocked then + ESX.ShowNotification("CCTV Toggled!", "success") + local status = enabled and "Enabled" or "Disabled" + opos = {{unselectable = true, icon = "fas fa-warehouse", title = "CCTV Settings"}, + {title = "Toggle Usage", icon = status == "Enabled" and "fa-solid fa-toggle-on" or "fa-solid fa-toggle-off", + description = "Current Status: " .. status, value = "ToggleCCTV"}} + if enabled then + opos[#opos + 1] = {title = "Set Angle", icon = "fas fa-map-marker-alt", disabled = not enabled, + description = "Sets the Angle of the Camera.", value = "SetCCTVangle"} + if Properties[currentProperty].cctv.rot then + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management.", + value = "return"} + end + else + opos[#opos + 1] = {title = "Go Back", icon = "fas fa-arrow-left", description = "return to Property Management", + value = "return"} + end + exports["esx_context"]:Refresh(opos, "right") + else + ESX.ShowNotification("You ~r~Cannot~s~ Toggle This Option!", "error") + end + end, currentProperty) + end + if element.value == "SetGaragePos" then + ESX.CloseContext() + ESX.TextUI("Press ~b~[E]~s~ to Set Position") + SettingValue = "Garage" + while SettingValue ~= "" do + Wait(0) + if IsControlJustPressed(0, 38) then + ESX.TriggerServerCallback("esx_property:SetGaragePos", function(IsChanged) + if IsChanged then + ESX.HideUI() + SettingValue = "" + ESX.ShowNotification("Position Changed!", "success") + ManageProperty(currentProperty) + else + ESX.ShowNotification("You ~r~Cannot~s~ Change This Option!", "error") + end + end, currentProperty, GetEntityHeading(PlayerPedId())) + break + end + end + end + if element.value == "SetCCTVangle" then + ESX.CloseContext() + ESX.TextUI("Press ~b~[E]~s~ to Set Angle") + local stage = "angle" + SettingValue = "cctv" + local Angles = {} + while stage do + Wait(0) + if IsControlJustPressed(0, 38) then + if stage == "angle" then + Angles.rot = GetGameplayCamRot(2) + ESX.TextUI("Press ~b~[E]~s~ to Set Max Right Roation") + stage = "maxright" + elseif stage == "maxright" then + Angles.maxright = GetGameplayCamRot(2).z + ESX.TextUI("Press ~b~[E]~s~ to Set Max Left Roation") + stage = "maxleft" + elseif stage == "maxleft" then + Angles.maxleft = GetGameplayCamRot(2).z + ESX.TriggerServerCallback("esx_property:SetCCTVangle", function(IsChanged) + if IsChanged then + SettingValue = "" + stage = nil + ESX.HideUI() + ESX.ShowNotification("Angle Changed!", "success") + ManageProperty(currentProperty) + else + ESX.ShowNotification("You ~r~Cannot~s~ Change This Option!", "error") + end + end, currentProperty, Angles) + break + end + end + end + end + if element.value == "remove_custom_name" then + ESX.TriggerServerCallback("esx_property:RemoveCustomName", function(Cleared) + if Cleared then + ESX.ShowNotification("Property Name Reset!", "success") + exports["esx_context"]:Refresh(GetData()) + else + ESX.ShowNotification("You Cannot Reset This Property`s Name!", "error") + end + end, currentProperty) + end + if element.value == "refurni" then + ESX.TriggerServerCallback("esx_property:RemoveAllfurniture", function(Removed) + if Removed then + ESX.ShowNotification("Furniture Reset!", "success") + exports["esx_context"]:Refresh(GetData()) + else + ESX.ShowNotification("You Cannot Reset This Property!", "error") + end + end, currentProperty) + end + if element.value == "restorage" then + ESX.TriggerServerCallback("esx_property:SetInventoryPosition", function(Reset) + if Reset then + ESX.ShowNotification("~b~Storage~s~ Position Reset!", "success") + exports["esx_context"]:Refresh(GetData()) + else + ESX.ShowNotification("You Cannot Reset This Property!", "error") + end + end, currentProperty, Interior.positions.Storage, true) + end + if element.value == "rewardrobe" then + ESX.TriggerServerCallback("esx_property:SetWardrobePosition", function(Reset) + if Reset then + ESX.ShowNotification("~b~Wardrobe~s~ Position Reset!", "success") + exports["esx_context"]:Refresh(GetData()) + else + ESX.ShowNotification("You Cannot Reset This Property!", "error") + end + end, currentProperty, Interior.positions.Wardrobe, true) + end + if element.value == "back" then + AdminOptions(currentProperty) + end + if element.value == "return" then + exports["esx_context"]:Refresh(GetData()) + end + if element.value == "price" then + ESX.UI.Menu.Open('dialog', GetCurrentResourceName(), 'PropertyPrice', {title = "Property Price"}, function(data4, menu4) + if data4.value then + ESX.TriggerServerCallback("esx_property:ChangePrice", function(IsChanged) + if IsChanged then + ESX.ShowNotification("Price Changed!", "success") + menu4.close() + else + ESX.ShowNotification("You Cannot Change this property!", "error") + end + end, currentProperty, tonumber(data4.value)) + end + end, function(data4, menu4) + menu4.close() + end) + end + if element.value == "interior" then + local elements = {{unselectable = true, icon = "fas fa-warehouse", title = "Interior Types"}, + {title = "IPL Interiors", description = "Native GTA Interiors, Made by R*", value = "IPL"}} + if Config.Shells then + elements[3] = {title = "Custom Interiors", description = "Custom Interiors, Made by You", value = "Shells"} + end + ESX.OpenContext("right", elements, function(menu, element) + if element.value then + local elements = {{unselectable = true, icon = "fas fa-warehouse", title = "Interiors"}} + for i = 1, #(Config.Interiors[element.value]) do + elements[#elements + 1] = {title = Config.Interiors[element.value][i].label, value = Config.Interiors[element.value][i].value} + end + ESX.OpenContext("right", elements, function(menu, element) + if element.value then + ESX.TriggerServerCallback("esx_property:ChangeInterior", function(IsChanged) + if IsChanged then + ESX.ShowNotification("Interior Changed!", "success") + ESX.CloseContext() + else + ESX.ShowNotification("You Cannot Change this property!", "error") + end + end, currentProperty, element.value) + end + end) + end + end, function() + ManageProperty(currentProperty) + end) + end + if element.value == "entrance" then + ESX.TriggerServerCallback("esx_property:ChangeEntrance", function(IsChanged) + if IsChanged then + ESX.ShowNotification("Entrance Changed!", "success") + else + ESX.ShowNotification("You Cannot Change this property!", "error") + end + end, currentProperty, GetEntityCoords(ESX.PlayerData.ped)) + end + end + end) + end + end) + end + + function AdminOptions(currentProperty) + ESX.TriggerServerCallback('esx_property:IsAdmin', function(data) + if data then + local elements = {{unselectable = true, icon = "fas fa-home", title = "Property Options"}, + {title = "Go Back", icon = "fas fa-arrow-left", value = "back"}, + {title = "Manage", icon = "fas fa-cogs", description = "Alter This Property's Settings.", value = "manage"}, + {title = "Teleport", description = "Teleport To This Property.", icon = "fas fa-map-marker-alt", value = "goto"}, + {title = "Set GPS", description = "Set GPS position To Property.", icon = "fa-solid fa-location-dot", value = "gps"}, + {title = "Delete", icon = "fas fa-trash-alt", description = "Remove Current Property.", value = "delete"}} + + ESX.OpenContext("right", elements, function(menu, element) + if element.value then + if element.value == "goto" then + SetEntityCoords(ESX.PlayerData.ped, Properties[currentProperty].Entrance.x, Properties[currentProperty].Entrance.y, + Properties[currentProperty].Entrance.z) + ESX.ShowNotification("Teleported to Property!") + end + if element.value == "gps" then + SetNewWaypoint(Properties[currentProperty].Entrance.x, Properties[currentProperty].Entrance.y) + ESX.ShowNotification("GPS Set!") + end + if element.value == "back" then + AdminMenu() + end + if element.value == "delete" then + ESX.TriggerServerCallback("esx_property:deleteProperty", function(response) + if response then + ESX.ShowNotification("Property Deleted!", "success") + ESX.CloseContext() + else + ESX.ShowNotification("You Cannot Delete This Property", "error") + end + end, currentProperty) + end + + if element.value == "manage" then + ManageProperty(currentProperty) + end + end + end) + end + end) + end + + function AdminMenu() + ESX.TriggerServerCallback('esx_property:IsAdmin', function(data) + if data then + local elements = {{unselectable = true, icon = "fas fa-home", title = "Properties Management"}} + for i = 1, #(Properties) do + if Properties[i].Entrance then + local description = "" + if Properties[i].setName ~= "" then + description = description .. "\nName: " .. Properties[i].setName + end + if Properties[i].Owned then + description = description .. "\nOwner: " .. Properties[i].OwnerName + end + table.insert(elements, {title = Properties[i].Name, value = i, description = description, icon = "fas fa-home"}) + end + end + ESX.OpenContext("right", elements, function(menu, element) + if element.value then + ESX.CloseContext() + AdminOptions(element.value) + end + end) + end + end) + end + + AdminMenu() + else + ESX.ShowNotification("You ~r~Cannot~s~ Access This Menu!", 5000, "error") + end + end) +end) + diff --git a/[esx_addons]/esx_property/config.lua b/[esx_addons]/esx_property/config.lua new file mode 100644 index 00000000..b8d22423 --- /dev/null +++ b/[esx_addons]/esx_property/config.lua @@ -0,0 +1,644 @@ +Config = {} + +-------- Note: Can Cause quite abit of clutter --------------- +Config.ForSaleBlips = false -- Add Blips to For Sale Properties +Config.OwnedBlips = true -- Add blips for Owned Properties +--------------------------------------------------------------- + +--------------------- General Settings --------------------------------- +Config.Shells = false -- Enable/Disable Shell Interiors Default: false +Config.CanAlwaysExit = true -- Disabling this allows players to be locked in +Config.OwnerCanAlwaysEnter = true -- Disabling this means the owner can't enter the property unless door is unlocked +Config.MaxNameLength = 20 -- Max Name Length for Owned Properties Default: 20 +Config.CanCustomiseInventoryAndWardrobePositions = true -- Allow users to customise inventory and wardrobe positions Default: true +Config.WipeCustomNameOnSell = true -- Wipe custom name on sell Default: true +Config.WipeFurnitureOnSell = true -- Wipe custom name on sell Default: true +Config.Raiding = { + Enabled = true, -- Enable/Disable Raiding Default: true + CanAdminsRaid = true, -- Can Admins Raid Houses Default: true + ItemRequired = { -- Item Required to Raid -- Remove To Disable + RemoveItem = true, -- Remove Item from Inventory Default: true + name = "bread", -- Item Name Default: "bread" + label = "Bread", -- Item label Default: "Bread" + ItemCount = 1, -- Item Count Default: 1 + }, + Animation = { + type = "Scenario", + Scenario = "WORLD_HUMAN_HAMMERING", + }, +} + + +Config.Garage = { + Enabled = true, -- Enable/Disable Garage Default: true + OwnedVehiclesOnly = true, -- Only allow owned vehicles to be stored Default: true +} + +Config.Logs ={ + Webhook = "", + LogLevel = 1 + + ----------- Log Levels ------------ + -- 0 = No Logs + -- 1 = Logs Major Actions + -- 2 = Logs Major + Minor Actions + -- 3 = Logs All Actions + ------------------------------------- +} + +Config.Furniture = { + Enabled = true, + RotationSpeed = 0.4, -- Object Rotation Speed + MovementSpeed = 0.01, -- Object Movement Speed + MovementZspeed = 0.05, -- Object Z Movement Speed + WipeFurnitureOnSell = true, -- Wipe Furniture On Sell Default: true + Controls = { + PlusX = 174, -- lEFT Arrow + MinusX = 175, -- RIGHT Arrow + RotateRight = 19, -- Alt, + RotateLeft = 21, -- Shift, + Up = 96, -- UP Arrow + Down = 97, -- DOWN Arrow + Confirm = 201, -- ENTER + Exit = 194, -- BACKSPACE + PlusY = 172, -- SHIFT + MinusY = 173, -- SHIFT + } +} + +Config.CCTV = { + Enabled = true, + PictureWebook = "", -- Set a discord webhook here to enable taking pictures of the CCTV (link is copied to user`s clipboard) + HeightAboveDoor = 2.5, -- Height above the door to place the cctv camera + FOV = 80.0, -- Camera Field of View + MaxLeftRotation = 80, + MaxZoom = 30, + MinZoom = 100, + MaxRightRotation = -50, + MaxUpRotation = 10, + MaxDownRotation = -45, + RotateSpeed = 0.3, -- Camera Rotation Speed + Controls = { + Left = 34, -- lEFT Arrow + Right = 35, -- RIGHT Arrow + Screenshot = 201, -- ENTER + NightVision = 38, -- E + ZoomIn = 96, -- UP Arrow + ZoomOut = 97, -- DOWN Arrow + Up = 32, -- UP Arrow + Down = 33, -- DOWN Arrow + Exit = 194, -- BACKSPACE + } +} + + +-------- Groups allowed to use Admin Functions ------------------- +Config.AllowedGroups = { + -- Note: Superadmin is not a group In ESX + "admin" +} + +------------------Interacting With Wardrobe Markers ------------------------------ +Config.WardrobeInteraction = function(PropertyId, Interaction) + ESX.TriggerServerCallback('esx_property:getPlayerDressing', function(dressing) + local elements = {{unselectable = true, icon = "fas fa-tshirt", title = "Wardrobe"}} + + for i=1, #dressing, 1 do + elements[#elements + 1] = { + title = dressing[i], + value = i + } + end + + ESX.OpenContext("left", elements, function(menu, element) + TriggerEvent('skinchanger:getSkin', function(skin) + ESX.TriggerServerCallback('esx_property:getPlayerOutfit', function(clothes) + TriggerEvent('skinchanger:loadClothes', skin, clothes) + TriggerEvent('esx_skin:setLastSkin', skin) + + TriggerEvent('skinchanger:getSkin', function(skin) + TriggerServerEvent('esx_skin:save', skin) + end) + end, element.value) + end) + end) +end) +end + +--------------------- Real Estate Settings ---------- +Config.PlayerManagement = { + Enabled = false, -- Enable/Disable Player Management Default: true + job = "realestateagent", -- Job Required to Manage Players Default: "realestateagent" + joblabel = "Estate Agent", -- Job Label Default: "Estate Agent" + society = "society_realestateagent", -- Society Required to Manage Players Default: "society_realestateagent" + SalePercentage = 0.25, -- Sale Percentage Default: 0.25 -- Note: This is a percentage of the price of the house + jobRanks = { + { + grade = 0, + name = "trainee", + label = "Trainee", + salary = 15, + }, + { + grade = 1, + name = "agent", + label = "Experienced Agent", + salary = 30, + }, + { + grade = 2, + name = "boss", + label = "Chief Agent", + salary = 45, + }, + }, + Permissions = { -- Minimum Grade Required for Action + CreateProperty = 0, -- Create Property + BillPlayers = 1, -- Send Players A Bill + DeleteProperty = 2, -- Delete Property + ViewProperties = 0, -- View All Properties + SellProperty = 0, -- Sell Property to players + ToggleCCTV = 1, -- Change CCTV settings + ToggleLock = 0, -- Change Lock Settings + ToggleGarage = 1, -- Change Garage Settings + SetPropertyName =1, -- Change Property Name + RemovePropertyName = 1, -- Remove Property Name + SetPropertyPrice = 0, -- Change Property Price + ChangeInterior = 1, -- Change Interior + ResetFurniture = 0, -- Reset Furniture + EvictOwner = 0, -- Evict Owners + ChangeEntrance = 0, -- Change Entrance + EditInteriorPositions = 1, -- Edit Interior Positions + ManagePropertiesFromQuickActions = 1, -- Manage Properties From Quick Actions (F6) + }, + Locations = { + Entrance = vector3( -199.151, -575.000, 39.489 ), -- Entrance Location Default: vector3( -199.151, -575.000, 39.489 ) + Exit = vector3( -141.226, -614.166, 167.820 ), -- Exit Location (Interior Location) Default: vector3( -141.226, -614.166, 167.820 ) + Properties = vector3(-138.9228, -634.1255, 167.8504), -- Property Management Menu Default: vector3(-138.9228, -634.1255, 167.8504) + BossMenu = vector3(-126.0031, -641.1303, 167.8504), -- Boss Menu Default: vector3(-126.0031, -641.1303, 167.8504) + } +} + +----------------------Long Lists---------------------------------------- +Config.FurnitureStores = { + { + title = "Ikea", + Catagories = {"Decorations", "Tables", "Sofas", "Bedroom"} + }, + { + title = "Electronics Store", + Catagories = {"Electronics","Kitchen"} + }, +} + +Config.Interiors = { + IPL = { + { + label = "Modern Apartment", + value = "apa_v_mp_h_01_a", + positions = { + Wardrobe = vec3(-797.72, 328.03, 220.42), + Storage = vec3(-795.67, 326.67, 217.037), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(-786.8663, 315.7642, 217.6385) + }, + { + label = "Mody Apartment", + value = "apa_v_mp_h_02_a", + positions = { + Wardrobe = vec3(-797.591187, 327.995605, 220.424194), + Storage = vec3(-795.441772, 326.307678, 217.037354), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(-787.0749, 315.8198, 217.6386) + }, + { + label = "Vibrant Apartment", + value = "apa_v_mp_h_03_a", + positions = { + Wardrobe = vec3(-798.131897, 328.417572, 220.424194), + Storage = vec3(-796.786804, 327.164825, 217.037354),-- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(-786.6245, 315.6175, 217.6385) + }, + { + label = "Regal Apartment", + value = " apa_v_mp_h_07_c", + positions = { + Wardrobe = vector3(-797.6594, 328.4619, 190.5122), + Storage = vector3(-796.0188, 326.7346, 187.3131), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(-787.0574, 315.6567, 187.9135) + }, + { + label = "Tinsel Towers", + value = "tinsel_towers", + positions = { + Wardrobe = vector3(-594.5938, 55.6803, 96.9996), + Storage = vector3(-622.8556, 54.7504, 97.5995), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(-603.7109, 59.0251, 98.2002) + }, + { + label = "Dell Perro Heights", + value = "dell_perro_heights", + positions = { + Wardrobe = vector3(-1467.2574, -536.9599, 50.7325), + Storage = vector3(-1457.0121, -531.0082, 56.9373), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(-1449.7212, -526.1263, 56.9290) + }, + { + label = "Mid-Tier Apartment", + value = "mid-end", + positions = { + Wardrobe = vector3(350.7425, -994.2987, -99.1472), + Storage = vector3(343.8686, -1001.1404, -99.1962), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(346.5085, -1012.7023, -99.1962) + }, + { + label = "Low-End Apartment", + value = "low-end", + positions = { + Wardrobe = vector3(259.9943, -1003.4595, -99.0086), + Storage = vector3(265.9162, -999.3954, -99.0086), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(266.0285, -1007.4129, -101.0086) + }, + { + label = "High-End House 1", + value = "wild_oats", + positions = { + Wardrobe = vector3(-167.4227, 487.7173, 133.0), + Storage = vector3(-174.4463, 492.5583, 130.0436), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(-174.3548, 497.7895, 137.6538) + }, + { + label = "Clubhouse", + value = "bkr_biker_interior_placement_interior_0_biker_dlc_int_01_milo", + positions = { + Wardrobe = vector3(1116.9374, -3162.8467, -36.8705), + Storage = vector3(1112.0820, -3151.4019, -37.5186), -- Only Works with Ox Inventory + }, + type = "ipl", + pos = vector3(1121.1871, -3152.6177, -37.0628) + }, + }, + Shells = { + { + label = "Michael House", + value = "shell_michael", + positions = { + Wardrobe = vector3(-12.71,8.36,-5.8), -- Wardrobe Location + Storage = vector3(-177.77,-2.05,-1.44), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(9.290245, -5.563965, 5.053642) + }, + { + label = "Standard Motel", + value = "standardmotel_shell", + positions = { + Wardrobe = vector3(-1.435, -4.74, -1.0), + Storage = vector3(2.45, -4.37, -1.0), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(0.054924, 2.459473, 1.546776) + }, + { + label = "Mid-Tier apartment", + value = "furnitured_midapart", + positions = { + Wardrobe = vector3(-4.5, -18.99, -1.2), + Storage = vector3(-5.1, -13.68, -1.2), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(-1.434044, 9.802368, 1.511887) + }, + { + label = "Trever`s Trailer", + value = "shell_trevor", + positions = { + Wardrobe = vector3(-0.58, -4.85, -1.0), + Storage = vector3(3.02, -1.467, -1.0), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(-0.156189, 3.585266, 1.398056) + }, + { + label = "Trailer", + value = "shell_trailer", + positions = { + Wardrobe = vector3(3.8, -2.92, -1.0), + Storage = vector3(-5.69, -0.57, -1.12), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(1.282555, 1.713379, 1.469521) + }, + { + label = "Low-End Apartment", + value = "shell_v16low", + positions = { + Wardrobe = vector3(7.31, -6.71, -3.0), + Storage = vector3(0.61, -8.30, -3.0), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(-4.798767, 6.197479, 2.644226) + }, + { + label = "Store", + value = "shell_store1", + positions = { + Storage = vector3(0.03, -8.4, -1.1), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(2.666809, 4.603638, 1.609573) + }, + { + label = "Ranch", + value = "shell_ranch", + positions = { + Wardrobe = vector3(-2.78, -10.76, -1.0), + Storage = vector3(3.1, 8.04, -1.0), -- Only Works with Ox Inventory + }, + type = "shell", + pos = vector3(0.873413, -5.749237, 2.416748) + }, + } +} + +Config.FurnitureCatagories = +{Decorations = {{name = "apa_mp_h_acc_bottle_01", title = "Bottle", price = 700}, + {name = "apa_mp_h_acc_candles_01", title = "Candles", price = 700}, + {name = "p_int_jewel_mirror", title = "Mirror", price = 700}, + {name = "apa_mp_h_acc_dec_plate_01", title = "Decorative Plate", price = 700}, + {name = "apa_mp_h_acc_vase_01", title = "Vase", price = 700}, + {name = "v_res_desktidy", title = "Desk Supplies", price = 700}, + {name = "ex_prop_ashtray_luxe_02", title = "Ashtray", price = 700}, + {name = "v_res_mp_ashtrayb", title = "Ashtray 2", price = 300}, + {name = "prop_bong_01", title = "Bong", price = 700}, + {name = "prop_mr_rasberryclean", title = "Mr Rasberry Clean", price = 700}, + {name = "prop_acc_guitar_01", title = "Guitar", price = 1000}, + {name = "p_planning_board_04", title = "Planning Board", price = 500}, + {name = "prop_hotel_clock_01", title = "Hotel Clock", price = 500}, + {name = "p_cs_pamphlet_01_s", title = "Pamphlet", price = 700}, + {name = "prop_big_clock_01", title = "Big Clock", price = 500}, + {name = "prop_egg_clock_01", title = "Egg Clock", price = 500}, + {name = "prop_ld_greenscreen_01", title = "Green Screen", price = 100}, + {name = "prop_dart_bd_cab_01", title = "Dart", price = 500}, + {name = "prop_dart_bd_01", title = "Dart 2", price = 500}, + {name = "prop_exercisebike", title = "Exercise Bike", price = 500}, + {name = "p_laz_j02_s", title = "Laz", price = 500}, + {name = "v_res_cherubvase", title = "White Vase", price = 500}, + {name = "v_res_d_paddedwall", title = "Padded Wall", price = 500}, + {name = "prop_dummy_01", title = "Dummy", price = 100}, + {name = "prop_el_guitar_01", title = "E Guitar 1", price = 100}, + {name = "prop_el_guitar_02", title = "E Guitar 2", price = 100}, + {name = "prop_el_guitar_03", title = "E Guitar 2", price = 100}, + {name = "prop_ld_greenscreen_01", title = "Green Screen", price = 100}, + {name = "v_res_mbowlornate", title = "Ornate Bowl", price = 300}, + {name = "v_res_mbronzvase", title = "Bronze Vase", price = 300}, + {name = "prop_ceramic_jug_01", title = "Ceramic Jug", price = 100}, + {name = "v_res_m_candle", title = "Candle Large 1", price = 300}, + {name = "v_res_m_candlelrg", title = "Candle Large 2", price = 300}, + {name = "apa_mp_h_acc_candles_06", title = "Candles 1", price = 50}, + {name = "apa_mp_h_acc_candles_05", title = "Candles 2", price = 50}, + {name = "apa_mp_h_acc_candles_04", title = "Candles 3", price = 50}, + {name = "apa_mp_h_acc_rugwools_01", title = "Rug 1", price = 300}, + {name = "apa_mp_h_acc_rugwoolm_01", title = "Rug 2", price = 300}, + {name = "apa_mp_h_acc_rugwooll_04", title = "Rug 3", price = 300}, + {name = "apa_mp_h_acc_rugwooll_03", title = "Rug 4", price = 300}, + {name = "apa_mp_h_acc_rugwoolm_04", title = "Rug 5", price = 300}, + {name = "apa_mp_h_acc_rugwools_03", title = "Rug 6", price = 300}, + {name = "v_res_fh_pouf", title = "Pouf", price = 300}, + {name = "v_res_fh_sculptmod", title = "Sculpture", price = 300}, + {name = "prop_ceramic_jug_01", title = "Ceramic Jug", price = 100}, + {name = "prop_v_5_bclock", title = "Vintage Clock", price = 300}, + {name = "prop_v_15_cars_clock", title = "American Flag Clock", price = 300}, + {name = "prop_sm_19_clock", title = "Modern Clock", price = 300}, + {name = "prop_sports_clock_01", title = "Sports Clock", price = 300}, + {name = "prop_mem_candle_01", title = "Candle 1", price = 300}, + {name = "prop_game_clock_01", title = "Crown Clock", price = 300}, + {name = "prop_game_clock_02", title = "Kronos Clock", price = 300}, + {name = "prop_id2_20_clock", title = "Modern Clock 2", price = 300}, + {name = "ex_office_citymodel_01", title = "CIty name", price = 300}, + {name = "apa_mp_h_acc_dec_head_01", title = "Mask", price = 300}, + {name = "ex_mp_h_acc_vase_06", title = "Vase 1", price = 300}, + {name = "ex_mp_h_acc_vase_02", title = "Red Vase", price = 300}, + {name = "hei_prop_hei_bust_01", title = "Bust", price = 300}, + {name = "prop_arcade_01", title = "Arcade Machine", price = 300}, + {price = 6500, name = 'prop_beer_neon_01', title = 'Neon Sign 1'}, + {price = 6500, name = 'prop_beer_neon_02', title = 'Neon Sign 2'}, + {price = 6500, name = 'prop_beer_neon_03', title = 'Neon Sign 3'}, + {price = 6500, name = 'prop_beer_neon_04', title = 'Neon Sign 3'}, + {price = 6500, name = 'prop_patriotneon', title = 'Neon Sign Patriot'}, + {price = 6500, name = 'prop_barrachneon', title = 'Neon Sign Pussy Beer'}}, + Electronics = {{price = 0, title = 'Lamp', name = 'prop_cd_lamp'}, + {price = 1800, title = 'Shredder', name = 'v_ret_gc_shred'}, + {price = 800, title = 'Antique telephone', name = 'apa_mp_h_acc_phone_01'}, + {price = 14700, title = 'TV wall white-gray with electronics', name = 'apa_mp_h_str_avunitl_04'}, + {price = 6700, title = 'TV wooden wall with television', name = 'apa_mp_h_str_avunitl_01_b'}, + {price = 14300, title = 'Television with yellow speakers', name = 'apa_mp_h_str_avunitm_01'}, + {price = 12000, title = 'TV with white speakers', name = 'apa_mp_h_str_avunitm_03'}, + {price = 12900, title = 'Television with accessories', name = 'apa_mp_h_str_avunits_01'}, + {price = 6900, title = 'TV on a metal table', name = 'apa_mp_h_str_avunits_01'}, + {price = 5500, title = 'Notebook', name = 'bkr_prop_clubhouse_laptop_01a'}, + {price = 4400, title = 'Notebook', name = 'bkr_prop_clubhouse_laptop_01b'}, + {price = 12400, title = 'Money counter', name = 'bkr_prop_money_counter'}, + {price = 700, title = 'Large upright fan', name = 'bkr_prop_weed_fan_floor_01a'}, + {price = 2400, title = 'Tvsmash', name = 'des_tvsmash_start'}, + {price = 2700, title = 'Wall TV', name = 'ex_prop_ex_tv_flat_01'}, + {price = 3500, title = 'Desktop monitor with keyboard', name = 'ex_prop_monitor_01_ex'}, + {price = 4700, title = 'Desktop monitor with keyboard', name = 'ex_prop_trailer_monitor_01'}, + {price = 200, title = 'TV driver', name = 'ex_prop_tv_settop_remote'}, + {price = 2200, title = 'TV box', name = 'ex_prop_tv_settop_box'}, + {price = 1000, title = 'Table fan', name = 'gr_prop_bunker_deskfan_01a'}, + {price = 8400, title = 'Television with speakers and all equipment', name = 'hei_heist_str_avunitl_03'}, + {price = 1700, title = 'telephone landline', name = 'hei_prop_hei_bank_phone_01'}, + {price = 3800, title = 'Alarm', name = 'hei_prop_hei_bio_panel'}, + {price = 2100, title = 'Keyboard', name = 'hei_prop_hei_cs_keyboard'}, + {price = 8900, title = 'Project board', name = 'hei_prop_hei_muster_01'}, + {price = 1800, title = 'WIFI', name = 'hei_prop_server_piece_01'}, + {price = 3300, title = 'White notebook', name = 'p_laptop_02_s'}, + {price = 10500, title = 'Safe', name = 'p_v_43_safe_s'}, + {price = 7900, title = 'Table Hockey', name = 'prop_airhockey_01'}, + {price = 2400, title = 'Portable Radio', name = 'prop_boombox_01'}, + {price = 2500, title = 'DVD player', name = 'prop_cctv_cont_03'}, + {price = 3300, title = 'CD player', name = 'prop_cctv_cont_04'}, + {price = 900, title = 'PC Mouse', name = 'prop_cs_mouse_01'}, + {price = 900, title = 'Wall clock', name = 'prop_game_clock_01'}, + {price = 600, title = 'Wall clock black', name = 'prop_game_clock_02'}, + {price = 3400, title = 'HiFi system', name = 'prop_hifi_01'}, + {price = 1500, title = 'square clock', name = 'prop_hotel_clock_01'}, + {price = 1500, title = 'Clock', name = 'prop_id2_20_clock'}, + {price = 3400, title = 'Nikon Handheld Camera', name = 'prop_ing_camera_01'}, + {price = 1700, title = 'White keyboard', name = 'prop_keyboard_01a'}, + {price = 1600, title = 'Router', name = 'prop_ld_armour'}, + {price = 1700, title = 'Old monitor', name = 'prop_ld_monitor_01'}, + {price = 2300, title = 'Small old monitor', name = 'prop_monitor_01b'}, + {price = 400, title = 'RETRO monitor', name = 'prop_monitor_03b'}, + {price = 5900, title = 'Monitor', name = 'prop_monitor_w_large'}, + {price = 4800, title = 'Repráček', name = 'prop_mp3_dock'}, + {price = 4000, title = 'White PC', name = 'prop_pc_01a'}, + {price = 4800, title = 'Black PC', name = 'prop_pc_02a'}, + {price = 2500, title = 'Mobile', name = 'prop_phone_ing'}, + {price = 2100, title = 'Mobile', name = 'prop_phone_ing_02'}, + {price = 2000, title = 'Mobile', name = 'prop_phone_ing_02_lod'}, + {price = 2300, title = 'Mobile', name = 'prop_phone_ing_03'}, + {price = 1000, title = 'RETRO radio', name = 'prop_radio_01'}, + {price = 1100, title = 'Small speaker', name = 'prop_speaker_05'}, + {price = 1000, title = 'Speaker', name = 'prop_speaker_06'}, + {price = 1900, title = 'Speaker', name = 'prop_speaker_08'}, + {price = 2000, title = 'Old TV', name = 'prop_trev_tv_01'}, + {price = 500, title = 'Old TV', name = 'prop_tv_03'}, + {price = 300, title = 'Old TV', name = 'prop_tv_01'}, + {price = 400, title = 'RETRO TV', name = 'prop_tv_04'}, + {price = 1700, title = 'Old TV', name = 'prop_tv_06'}, + {price = 6500, title = 'Plasma big screen', name = 'prop_tv_flat_01'}, + {price = 12500, title = 'Plasma thin TV', name = 'prop_tv_flat_01_screen'}, + {price = 1600, title = 'Plasma small television', name = 'prop_tv_flat_02'}, + {price = 2100, title = 'Plasma small television', name = 'prop_tv_flat_02b'}, + {price = 900, title = 'Small TV with stand', name = 'prop_tv_flat_03'}, + {price = 300, title = 'Small TV on the wall', name = 'prop_tv_flat_03b'}, + {price = 9200, title = 'Television Michael 50cm', name = 'prop_tv_flat_michael'}, + {price = 1000, title = 'Little US Clock', name = 'prop_v_15_cars_clock'}, + {price = 4000, title = 'Professional camera', name = 'prop_v_cam_01'}, + {price = 4700, title = 'VET player RETRO', name = 'prop_vcr_01'}, + {price = 9900, title = 'Mixing desk', name = 'v_club_vu_deckcase'}, + {price = 1500, title = 'RETRO Laptop', name = 'v_ind_ss_laptop'}, + {price = 2500, title = 'CD seda', name = 'v_res_cdstorage'}, + {price = 3300, title = 'Ipod', name = 'v_res_ipoddock'}, + {price = 2800, title = 'Silver Monitor', name = 'v_res_monitorwidelarge'}, + {price = 2400, title = 'Mouse and as a gift pad', name = 'v_res_mousemat'}, + {price = 100, title = 'PC Headphones', name = 'v_res_pcheadset'}, + {price = 1200, title = 'PC speaker', name = 'v_res_pcspeaker'}, + {price = 5000, title = 'VHS white RETRO player', name = 'v_res_vhsplayer'}, + {price = 2200, title = 'Vacuum Cleaner', name = 'v_res_vacuum'}, + {price = 5400, title = 'Shredder', name = 'v_ret_gc_shred'}, + {price = 100, title = 'Stapler', name = 'v_ret_gc_staple'}, + {price = 1900, title = 'Hardisk', name = 'xm_prop_x17_harddisk_01a'}, + {price = 5300, title = 'Computer', name = 'xm_prop_x17_res_pctower'}, + {price = 12900, title = 'Plasma TV', name = 'xm_prop_x17_tv_flat_01'}, + {price = 6600, title = 'Plasma TV', name = 'xm_prop_x17_tv_flat_02'}, + {price = 14000, title = 'Jukebox', name = 'bkr_prop_clubhouse_jukebox_01b'}, + {price = 1700, title = 'USB', name = 'hei_prop_hst_usb_drive'}, + {price = 700, title = 'Flashlight', name = 'p_cs_police_torch_s'}, + {price = 1000, title = 'Mikofon', name = 'p_ing_microphonel_01'}, + {price = 1600, title = 'Radio', name = 'prop_tapeplayer_01'}, + {price = 4500, title = 'Multifunction Laser Printer', name = 'prop_printer_01'}, + {price = 11200, title = 'Multifunction Laser Printer', name = 'prop_printer_02'}, + {price = 20300, title = 'Jukebox 2', name = 'prop_50s_jukebox'}, + {price = 21200, title = 'Arcade games', name = 'prop_arcade_01'}, + {price = 19400, title = 'Safe', name = 'prop_ld_int_safe_01'}, + {price = 3800, title = 'Astronomical Clock', name = 'prop_v_5_bclock'}, + {price = 22200, title = 'Table with three monitors', name = 'xm_prop_base_staff_desk_01'}, + {price = 17900, title = 'Table with three monitors', name = 'xm_prop_base_staff_desk_02'}, + {price = 1500, title = 'Napkin machine', name = 'prop_handdry_01'}, + {price = 2700, title = 'Washing Machine', name = 'prop_washer_02'}, + {price = 600, title = 'Washing machine with its years', name = 'prop_washer_03'}, + {price = 700, title = 'RETRO washing machine', name = 'v_ret_fh_dryer'}}, + Tables = {{price = 5600, title = 'Glass table', name = 'apa_mp_h_din_table_04'}, + {price = 5000, title = 'Glass table', name = 'apa_mp_h_din_table_11'}, + {price = 5300, title = 'Glass table', name = 'apa_mp_h_tab_sidelrg_07'}, + {price = 1200, title = 'Black Round Table', name = 'apa_mp_h_tab_sidelrg_04'}, + {price = 1600, title = 'Glass table', name = 'apa_mp_h_tab_sidelrg_01'}, + {price = 5700, title = 'Decorative glass table', name = 'apa_mp_h_tab_sidelrg_02'}, + {price = 2600, title = 'Black coffee table', name = 'apa_mp_h_yacht_coffee_table_01'}, + {price = 4200, title = 'Square table', name = 'apa_mp_h_yacht_side_table_01'}, + {price = 3400, title = 'Small table', name = 'gr_dlc_gr_yacht_props_table_01'}, + {price = 4700, title = 'Long table', name = 'gr_dlc_gr_yacht_props_table_02'}, + {price = 1000, title = 'Particleboard table', name = 'prop_rub_table_01'}, + {price = 700, title = 'Particleboard dining table', name = 'prop_rub_table_02'}, + {price = 700, title = 'TV table', name = 'prop_tv_cabinet_03'}, + {price = 300, title = 'TV table', name = 'prop_tv_cabinet_04'}, + {price = 800, title = 'TV table', name = 'prop_tv_cabinet_05'}, + {price = 5300, title = 'Black modern table', name = 'v_ilev_liconftable_sml'}, + {price = 4100, title = 'Bar', name = 'xm_prop_lab_desk_01'}}, + Sofas = {{price = 11500, title = 'Couch with pillows', name = 'prop_couch_01'}, + {price = 6500, title = 'Padded bench', name = 'prop_wait_bench_01'}, + {price = 4400, title = 'White leather sofa', name = 'xm_lab_sofa_01'}}, + Kitchen = {{price = 800, title = 'Kitchen scale', name = 'bkr_prop_coke_scale_01'}, + {price = 1000, title = 'Home coffee maker', name = 'prop_coffee_mac_02'}, + {price = 5200, title = 'Automatic juice mixer', name = 'prop_juice_dispenser'}, + {price = 200, title = 'White wall phone', name = 'prop_office_phone_tnt'}, + {price = 2300, title = 'Fruit Blender', name = 'p_kitch_juicer_s'}, + {price = 1500, title = 'Kettle', name = 'prop_kettle_01'}, + {price = 2600, title = 'Crushing machine', name = 'prop_slush_dispenser'}, + {price = 700, title = 'Coffee pot', name = 'xm_prop_x17_coffee_jug'}, + {price = 3700, title = 'Mini bar fridge', name = 'prop_bar_fridge_03'}, + {price = 800, title = 'Plastic red cup', name = 'apa_prop_cs_plastic_cup_01'}, + {price = 1000, title = 'Trash', name = 'prop_bin_10a'}, + {price = 400, title = 'Universal Cleaner', name = 'prop_blox_spray'}, + {price = 200, title = 'Green bucket', name = 'prop_buck_spade_05'}, + {price = 400, title = 'Blue bucket', name = 'prop_buck_spade_06'}, + {price = 200, title = 'Red bucket', name = 'prop_buck_spade_07'}, + {price = 700, title = 'Cups', name = 'prop_food_cups2'}, + {price = 400, title = 'White Cup', name = 'prop_mug_02'}, + {price = 800, title = 'Bowl with donut lid', name = 'v_res_cakedome'}, + {price = 1000, title = 'Loose tea container', name = 'v_res_fa_pottea'}, + {price = 300, title = 'Deep Plate', name = 'v_res_mbowl'}, + {price = 500, title = 'Paper napkins', name = 'v_ret_ta_paproll'}, + {price = 400, title = 'Glasses', name = 'p_w_grass_gls_s'}, + {price = 300, title = 'Glasses', name = 'prop_cocktail'}, + {price = 600, title = 'Knife', name = 'prop_cs_bowie_knife'}}, + Bedroom = {{price = 3300, title = 'Single bed', name = 'v_res_msonbed'}, + {price = 100, title = 'Double bed', name = 'p_lestersbed_s'}, + {price = 600, title = 'Double bed', name = 'p_v_res_tt_bed_s'}, + {price = 400, title = 'Double modern bed', name = 'apa_mp_h_bed_double_08'}, + {price = 1100, title = 'Double modern bed', name = 'apa_mp_h_bed_wide_05'}, + {price = 2200, title = 'Double modern bed', name = 'apa_mp_h_yacht_bed_02'}, + {price = 700, title = 'Single sofa bed', name = 'ex_prop_exec_bed_01'}, + {price = 3100, title = 'Double modern bed', name = 'hei_heist_bed_double_08'}, + {price = 4400, title = 'Chest of drawers', name = 'apa_mp_h_bed_chestdrawer_02'}, + {price = 400, title = 'RETRO wardrobe', name = 'apa_mp_h_str_shelffloorm_02'}, + {price = 100, title = 'RETRO chest of drawers low', name = 'apa_mp_h_str_sideboardl_11'}, + {price = 2500, title = 'Gray-white low cabinet', name = 'apa_mp_h_str_sideboardl_13'}, + {price = 700, title = 'Wooden chest of drawers', name = 'apa_mp_h_str_sideboardl_14'}, + {price = 500, title = 'Wood brindle feather duster', name = 'apa_mp_h_str_sideboardm_02'}, + {price = 900, title = 'Iron open cabinet', name = 'p_cs_locker_01'}, + {price = 300, title = 'Iron used closed cabinet', name = 'p_cs_locker_01_s'}}} + + + +-------------------DONT TOUCH ------------------------- +Config.OxInventory = ESX.GetConfig().OxInventory -- + -- +function GetInteriorValues(Interior) -- + for _,type in pairs(Config.Interiors) do -- + for _, interior in pairs(type) do -- + if interior.value == Interior then -- + return interior -- + end -- + end -- + end -- +end -- +-------------------------------------------------------- + + +--[[ + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 . +]] \ No newline at end of file diff --git a/[esx_addons]/esx_property/fxmanifest.lua b/[esx_addons]/esx_property/fxmanifest.lua new file mode 100644 index 00000000..285cf170 --- /dev/null +++ b/[esx_addons]/esx_property/fxmanifest.lua @@ -0,0 +1,51 @@ +--[[ + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 . +]] +fx_version 'adamant' + +game 'gta5' +lua54 'yes' + +author 'ESX-Framework' +description 'Official ESX-Legacy Property System' + +version '1.8.0' + +shared_script '@es_extended/imports.lua' +file "client/html/copy.html" +ui_page "client/html/copy.html" + +server_scripts { + '@oxmysql/lib/MySQL.lua', + '@es_extended/locale.lua', + 'locales/*.lua', + 'config.lua', + 'server/*.lua' +} + +client_scripts { + '@es_extended/locale.lua', + 'locales/*.lua', + 'config.lua', + 'client/cctv.lua', + 'client/main.lua', + 'client/furniture.lua' +} + +dependencies { + 'es_extended' +} diff --git a/[esx_addons]/esx_property/properties.json b/[esx_addons]/esx_property/properties.json new file mode 100644 index 00000000..a99deb4e --- /dev/null +++ b/[esx_addons]/esx_property/properties.json @@ -0,0 +1 @@ +[{"Locked":false,"Price":25000,"Entrance":{"z":28.57,"y":6206.16,"x":-467.88},"furniture":[],"plysinside":[],"Keys":[],"Owned":false,"Owner":"","OwnerName":"","setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":90.71163940429688,"maxright":-88.02642059326172,"enabled":true,"rot":{"z":2.7542073726654,"y":2.7045992112562097e-8,"x":-9.43104267120361}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1076 Procopio Dr","Interior":"low-end"},{"Locked":false,"Price":25000,"Entrance":{"z":29.07,"y":6260.23,"x":-448.02},"furniture":[],"plysinside":[],"Keys":[],"Owned":false,"Owner":"","OwnerName":"","setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-113.61099243164064,"maxright":70.65039825439453,"enabled":true,"rot":{"z":162.9940643310547,"y":-0.0,"x":3.75689458847045}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1075 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":27.96,"y":6314.13,"x":-407.35},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-54.77568435668945,"maxright":135.13072204589845,"enabled":true,"rot":{"z":-138.95835876464845,"y":-0.0,"x":-1.16786921024322}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1074 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":28.86,"y":6341.62,"x":-368.23},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-51.56189727783203,"maxright":132.70008850097657,"enabled":true,"rot":{"z":-139.94163513183598,"y":-0.0,"x":-2.56643605232238}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1073 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.52,"y":6400.88,"x":-272.61},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-65.34387969970703,"maxright":123.2501983642578,"enabled":true,"rot":{"z":-152.39810180664066,"y":-0.0,"x":-7.54235982894897}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1071 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.48,"y":6414.46,"x":-246.14},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-148.38644409179688,"maxright":38.33782577514648,"enabled":true,"rot":{"z":128.27305603027345,"y":4.379791960218428e-7,"x":-12.92249584197998}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1071 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.22,"y":6445.48,"x":-229.63},"furniture":[],"plysinside":[],"Price":27500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-135.48313903808598,"maxright":48.30059432983398,"enabled":true,"rot":{"z":137.8870849609375,"y":2.1444458297992242e-7,"x":-5.53862285614013}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"1070 Procopio Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":28.89,"y":6551.76,"x":-130.8},"furniture":[],"plysinside":[],"Price":27500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-46.79578399658203,"maxright":138.85032653808598,"enabled":true,"rot":{"z":-134.93411254882813,"y":1.0691521623584777e-7,"x":3.44796895980834}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"1067 Procopio Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.11,"y":6637.3,"x":-41.72},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-56.39479827880859,"maxright":129.07382202148438,"enabled":true,"rot":{"z":-144.0503692626953,"y":-1.3340336835199196e-8,"x":-0.24611411988735}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1065 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.72,"y":6654.13,"x":-9.62},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-68.07413482666016,"maxright":116.67019653320313,"enabled":true,"rot":{"z":-155.01153564453126,"y":-1.068413766347477e-7,"x":-2.71219563484191}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1064 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.86,"y":6207.33,"x":-356.82},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-45.94052505493164,"maxright":135.7574005126953,"enabled":true,"rot":{"z":-133.8831787109375,"y":-0.0,"x":-2.77191710472106}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1034 Paleto Blvd","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.87,"y":6252.75,"x":-379.92},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":42.09032440185547,"maxright":-131.2773895263672,"enabled":true,"rot":{"z":-40.4444465637207,"y":4.32035335506953e-7,"x":-8.85426330566406}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1034 Procopio Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":31.91,"y":6326.99,"x":-302.19},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":133.55870056152345,"maxright":-42.54082870483398,"enabled":true,"rot":{"z":44.16030502319336,"y":-9.440947223993136e-7,"x":-25.26673126220703}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1047 Paleto Blvd","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":30.9,"y":6225.21,"x":-347.3},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-43.60107040405273,"maxright":134.7738800048828,"enabled":true,"rot":{"z":-137.5096435546875,"y":-0.0,"x":-1.57497417926788}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"1043 Paleto Blvd","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":32.26,"y":6557.45,"x":-15.3},"furniture":[],"plysinside":[],"Price":25000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":43.89247512817383,"maxright":-133.83499145507813,"enabled":true,"rot":{"z":-45.87245941162109,"y":-0.0,"x":-11.80154037475586}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"1059 Paleto Blvd","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":32.1,"y":6568.19,"x":4.43},"furniture":[],"plysinside":[],"Price":27500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-139.30752563476566,"maxright":44.53319549560547,"enabled":true,"rot":{"z":129.4863433837891,"y":2.144746105159357e-7,"x":-5.62087059020996}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"1061 Paleto Blvd","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":31.84,"y":6596.46,"x":31.0},"furniture":[],"plysinside":[],"Price":27500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":-43.43285751342773,"maxright":-159.96804809570313,"enabled":true,"rot":{"z":-94.7632293701172,"y":-0.0,"x":-5.83577966690063}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"1061 Paleto Blvd","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":42.9,"y":4642.33,"x":1725.06},"furniture":[],"plysinside":[],"Price":12500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-63.00457000732422,"y":-0.0,"x":-6.35934734344482}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"2016 Grapeseed Main St","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":42.39,"y":4658.22,"x":1674.01},"furniture":[],"plysinside":[],"Price":12500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":91.53390502929688,"y":2.7858778395284394e-8,"x":-16.72389602661132}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"2015 Grapeseed Main St","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":42.68,"y":4677.34,"x":1718.91},"furniture":[],"plysinside":[],"Price":7500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-93.13695526123049,"y":-0.0,"x":-29.83987236022949}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"2014 Grapeseed Main St","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":42.09,"y":4689.52,"x":1682.85},"furniture":[],"plysinside":[],"Price":7500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":91.7155303955078,"y":-0.0,"x":-6.32857131958007}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"2013 Grapeseed Main St","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":41.03,"y":4739.5,"x":1664.0},"furniture":[],"plysinside":[],"Price":10000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":110.03758239746094,"y":-0.0,"x":-18.96443367004394}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"2011 Grapeseed Main St","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":32.23,"y":3920.59,"x":1880.27},"furniture":[],"plysinside":[],"Price":10000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-77.30774688720703,"y":-0.0,"x":-14.36374378204345}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"3012 Niland Ave","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":32.48,"y":3914.59,"x":1846.06},"furniture":[],"plysinside":[],"Price":9000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":103.60260009765624,"y":-2.1725810483985698e-7,"x":-10.75266551971435}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"3013 Niland Ave","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":36.08,"y":3913.8,"x":1803.57},"furniture":[],"plysinside":[],"Price":11500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-76.59026336669922,"y":-0.0,"x":-20.23343658447265}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"3013 Marina Dr","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":33.45,"y":3657.13,"x":1435.31},"furniture":[],"plysinside":[],"Price":11500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":95.02767944335938,"y":-0.0,"x":-5.87784767150878}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"3025 Lesbos Ln","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":43.5,"y":2607.8,"x":471.15},"furniture":[],"plysinside":[],"Price":12500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-166.2356719970703,"y":-0.0,"x":-20.86568069458007}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"4018 Route 68","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":42.28,"y":3087.1,"x":181.03},"furniture":[],"plysinside":[],"Price":7500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-93.07178497314452,"y":-0.0,"x":-11.2086534500122}},"positions":{"Wardrobe":{"z":-99.00859832763672,"y":-1003.45947265625,"x":259.9942932128906}},"Name":"4013 Joshua Rd","Interior":"low-end"},{"Locked":false,"Owned":false,"Entrance":{"z":73.50999999999999,"y":-593.38,"x":1386.22},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-127.72049713134766,"y":-0.0,"x":-9.48588275909423}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7340 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":73.52,"y":-569.49,"x":1388.94},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-69.5700912475586,"y":-0.0,"x":-9.84068393707275}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7341 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":73.71,"y":-555.82,"x":1373.19},"furniture":[],"plysinside":[],"Price":4500,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-114.24230194091796,"y":-0.0,"x":-12.52571487426757}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7341 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":73.72999999999999,"y":-606.62,"x":1367.33},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-177.74884033203126,"y":-0.0,"x":-10.93433380126953}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7340 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":73.72,"y":-597.26,"x":1341.64},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":62.37366104125976,"y":-0.0,"x":-17.39539337158203}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7339 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":72.91,"y":-546.97,"x":1348.31},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-27.96370315551757,"y":4.342483350683324e-7,"x":-10.56496334075927}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7342 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":72.27,"y":-583.06,"x":1323.3},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":152.2796173095703,"y":4.441767487151084e-7,"x":-16.03893852233886}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7339 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":71.46,"y":-535.91,"x":1328.57},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-111.59012603759766,"y":-0.00000144736395,"x":-53.85140228271484}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7342 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":70.75,"y":-574.08,"x":1301.02},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":164.71957397460938,"y":4.6078031346041828e-7,"x":-22.11302947998047}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7339 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":70.47999999999999,"y":-527.3,"x":1303.1},"furniture":[],"plysinside":[],"Price":45000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-19.26140213012695,"y":4.363083689895575e-7,"x":-11.92860603332519}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7342 Nikola Pl","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":56.84,"y":-729.5,"x":996.89},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":125.62109375,"y":-0.0,"x":-58.0705451965332}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7322 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.24,"y":-716.31,"x":979.11},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":131.13587951660157,"y":-0.0,"x":-15.06083393096923}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7320 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.5,"y":-701.3,"x":970.88},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":170.76788330078126,"y":3.3350637806961464e-9,"x":-0.14884614944458}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7320 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.47,"y":-669.85,"x":960.15},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":115.8887939453125,"y":4.346124455878453e-7,"x":-10.81925582885742}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7319 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.45,"y":-653.51,"x":943.45},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":38.34314727783203,"y":0.00000168464464,"x":-59.54927444458008}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7319 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":58.26,"y":-627.65,"x":980.25},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-144.15000915527345,"y":-0.0,"x":-36.16230392456055}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7318 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.26,"y":-639.71,"x":928.82},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":137.4777984619141,"y":-0.0,"x":-15.65773868560791}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7317 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.47,"y":-615.51,"x":902.95},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":46.20460510253906,"y":-0.00000160100864,"x":-57.77318572998047}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7317 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.47,"y":-608.21,"x":886.77},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":138.07872009277345,"y":-8.718344588487525e-7,"x":-11.68268966674804}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7316 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.18,"y":-583.65,"x":861.7},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-176.59844970703126,"y":-0.0,"x":-3.38041257858276}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7316 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.01,"y":-562.66,"x":844.12},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":12.50613498687744,"y":-2.1754524937023245e-7,"x":-11.14390087127685}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7312 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":56.95,"y":-532.68,"x":850.25},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":85.02394104003906,"y":-5.362977262279856e-8,"x":-5.74017667770385}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7314 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":56.74,"y":-508.98,"x":861.51},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":46.49221038818359,"y":0.00000154111978,"x":-56.35844421386719}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7313 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.11000000000001,"y":-497.87,"x":878.44},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":44.91729354858398,"y":-0.0,"x":-64.80823516845703}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7313 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":58.46,"y":-489.76,"x":906.46},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":19.29909515380859,"y":-0.0,"x":-14.44456386566162}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7311 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":60.1,"y":-477.8,"x":921.78},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-8.25692367553711,"y":-0.0,"x":-19.33738899230957}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7311 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":60.57,"y":-463.17,"x":944.54},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-55.23443603515625,"y":-8.953837777880835e-7,"x":-17.53591918945312}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7309 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":61.81,"y":-451.65,"x":967.16},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":33.32376480102539,"y":8.936335120779404e-7,"x":-17.17726707458496}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7309 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":63.06000000000001,"y":-433.03,"x":987.57},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":34.80197525024414,"y":8.771137913754501e-7,"x":-13.24740600585937}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7307 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":64.36999999999999,"y":-423.38,"x":1010.38},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":123.14452362060549,"y":-4.402644719903037e-7,"x":-14.16050624847412}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7307 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":65.36,"y":-408.36,"x":1028.78},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":41.270263671875,"y":-4.386202476780454e-7,"x":-13.28245830535888}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7305 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":67.25,"y":-378.13,"x":1060.52},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":54.3464241027832,"y":-8.817532943794504e-7,"x":-14.47243404388427}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7305 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":63.52,"y":-469.3,"x":1014.67},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-144.52737426757813,"y":-9.047982416632294e-7,"x":-19.33366966247558}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7306 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":61.16,"y":-502.36,"x":970.55},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-107.44925689697266,"y":-0.0,"x":-8.07564449310302}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7308 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":59.64,"y":-518.94,"x":945.9},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":119.70294189453124,"y":-0.0,"x":-61.49639892578125}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7310 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":58.81,"y":-526.05,"x":924.39},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-151.43536376953126,"y":-0.0,"x":-17.66978073120117}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7312 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.53,"y":-540.58,"x":893.24},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-69.45809936523438,"y":-9.24586970540986e-7,"x":-22.57007217407226}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7312 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":57.39,"y":-569.56,"x":919.7},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":28.74713134765625,"y":4.412431167111208e-7,"x":-14.65564727783203}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7312 Nikola Ave","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":58.75,"y":-541.93,"x":965.25},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":32.70853805541992,"y":-4.381802227726439e-7,"x":-13.03651428222656}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7310 Nikola Ave","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":59.71,"y":-525.76,"x":987.84},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":30.89822769165039,"y":9.366781910102872e-7,"x":-24.28778457641601}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7310 Nikola Ave","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":60.01,"y":-510.98,"x":1006.48},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-65.29899597167969,"y":-0.0,"x":-16.08703422546386}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7308 Nikola Ave","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":63.1,"y":-498.06,"x":1046.29},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":144.13137817382813,"y":9.091479000744585e-7,"x":-20.10037040710449}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7306 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":63.32,"y":-470.58,"x":1051.04},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":76.4620132446289,"y":-0.0,"x":-11.98972129821777}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7306 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":65.28,"y":-448.93,"x":1056.24},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":162.82366943359376,"y":-0.0,"x":-8.78071689605712}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7306 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":64.67999999999999,"y":-484.37,"x":1090.46},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-102.9858856201172,"y":-4.4310172597761268e-7,"x":-15.54805183410644}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7344 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":66.33999999999999,"y":-464.52,"x":1098.58},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-15.62946987152099,"y":2.17306606487e-7,"x":-10.81984233856201}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7346 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":66.81,"y":-438.69,"x":1099.44},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":179.49822998046876,"y":-0.0,"x":-11.32894706726074}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7346 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":66.58,"y":-411.33,"x":1101.06},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-93.88529205322266,"y":-5.391471091797939e-8,"x":-8.21971225738525}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7348 Bridge St","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":67.97,"y":-391.27,"x":1114.44},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-112.49811553955078,"y":-0.0,"x":-24.5419979095459}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7348 West Mirror Drive","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":69.03999999999999,"y":-429.86,"x":1262.35},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":112.37248229980468,"y":-2.144359569911103e-7,"x":-5.51487922668457}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7345 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":69.53999999999999,"y":-458.06,"x":1265.74},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":94.98734283447266,"y":-0.0,"x":-14.86165904998779}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7945 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":69.21,"y":-480.18,"x":1259.51},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":125.15465545654296,"y":-0.0,"x":-10.50986766815185}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7343 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":68.92999999999999,"y":-494.1,"x":1251.53},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":63.34117126464844,"y":-0.0,"x":-16.31183433532715}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7343 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":68.36999999999999,"y":-515.5,"x":1250.9},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":77.79755401611328,"y":-0.0,"x":-17.28229522705078}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7343 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":68.67999999999999,"y":-566.43,"x":1241.53},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":130.3302459716797,"y":-0.0,"x":-22.43321228027343}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7338 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":68.8,"y":-601.65,"x":1240.54},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":91.53223419189452,"y":-1.3435412782314417e-8,"x":-6.82489442825317}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7338 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":68.58999999999999,"y":-620.99,"x":1250.79},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Price":40000,"Owner":"","OwnerName":"","setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":27.77294158935547,"y":-2.145688853261163e-7,"x":-5.87094974517822}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7336 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":67.14,"y":-648.63,"x":1265.69},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-152.5341949462891,"y":-0.0,"x":-14.65502071380615}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7333 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":65.05,"y":-683.53,"x":1270.98},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-171.3050994873047,"y":-0.0,"x":-12.5356969833374}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7333 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":63.93,"y":-702.82,"x":1264.75},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":58.4541015625,"y":0.00000175048376,"x":-12.71638584136962}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7328 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":59.98,"y":-725.27,"x":1229.58},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-88.46087646484375,"y":-0.0,"x":-11.09668827056884}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7328 East Mirror Dr","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":59.82,"y":-696.93,"x":1223.0},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-81.23240661621094,"y":-0.0,"x":-17.37718200683593}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7328 Mirror Park Blvd","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":62.71,"y":-669.31,"x":1221.46},"furniture":[],"plysinside":[],"garage":{"enabled":false,"StoredVehicles":[]},"Owner":"","Price":40000,"setName":"","Keys":[],"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-164.38833618164066,"y":-0.0,"x":-11.55613040924072}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7333 Mirror Park Blvd","Interior":"mid-end"},{"Locked":false,"Owned":false,"Entrance":{"z":65.46,"y":-620.26,"x":1207.46},"furniture":[],"plysinside":[],"Price":40000,"Owner":"","Keys":[],"setName":"","garage":{"enabled":false,"StoredVehicles":[]},"cctv":{"maxleft":80,"maxright":-20,"enabled":false,"rot":{"z":-87.679443359375,"y":1.3399391818325056e-8,"x":-5.38702487945556}},"positions":{"Wardrobe":{"z":-99.14720153808594,"y":-994.2987060546876,"x":350.7424926757813}},"Name":"7336 Mirror Park Blvd","Interior":"mid-end"},{"Locked":false,"Price":40000,"Entrance":{"z":67.08,"y":-598.41,"x":1203.75},"furniture":[],"setName":"","Owned":false,"Owner":"","garage":{"enabled":false},"cctv":{"enabled":false,"maxright":-20,"maxleft":80,"rot":{"x":-16.58763313293457,"y":-0.0,"z":3.90023970603942}},"Keys":[],"Interior":"mid-end","positions":{"Storage":{"x":343.86859130859377,"y":-1001.140380859375,"z":-99.19619750976563},"Wardrobe":{"x":350.74249267578127,"y":-994.2987060546875,"z":-99.14720153808594}},"Name":"7338 Mirror Park Blvd","plysinside":[]},{"Locked":false,"Price":40000,"Entrance":{"z":68.16,"y":-575.59,"x":1201.01},"furniture":[],"setName":"","Owned":false,"Owner":"","garage":{"enabled":false},"cctv":{"enabled":false,"maxright":-20,"maxleft":80,"rot":{"x":-20.23956108093261,"y":9.099596240957908e-7,"z":-37.31895446777344}},"Keys":[],"Interior":"mid-end","positions":{"Storage":{"x":343.86859130859377,"y":-1001.140380859375,"z":-99.19619750976563},"Wardrobe":{"x":350.74249267578127,"y":-994.2987060546875,"z":-99.14720153808594}},"Name":"7338 Mirror Park Blvd","plysinside":[]}] \ No newline at end of file diff --git a/[esx_addons]/esx_property/server/main.lua b/[esx_addons]/esx_property/server/main.lua new file mode 100644 index 00000000..0082eee3 --- /dev/null +++ b/[esx_addons]/esx_property/server/main.lua @@ -0,0 +1,1088 @@ +--[[ + ESX Property - Properties Made Right! + Copyright (C) 2022 ESX-Framework + + 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 function Log(title, color, fields, Level) + if Level <= Config.Logs.LogLevel then + local webHook = Config.Logs.Webhook + local embedData = {{['title'] = title, ['color'] = color, ['footer'] = {['text'] = "ESX-Property | " .. os.date(), + ['icon_url'] = 'https://cdn.discordapp.com/attachments/944789399852417096/1004915039414788116/imageedit_1_2564956129.png'}, + ['fields'] = fields, ['description'] = "", ['author'] = {['name'] = "ESX-Framework | Log Level " .. Level, + ['icon_url'] = 'https://cdn.discordapp.com/emojis/939245183621558362.webp?size=128&quality=lossless'}}} + PerformHttpRequest(webHook, function(err, text, headers) + end, 'POST', json.encode({username = 'ESX-Framework', embeds = embedData}), {['Content-Type'] = 'application/json'}) + end +end + +local PM = Config.PlayerManagement +local Properties = {} + +function PropertiesRefresh() + Properties = {} + local PropertiesList = LoadResourceFile(GetCurrentResourceName(), 'properties.json') + if PropertiesList then + Properties = json.decode(PropertiesList) + Wait(10) + for i = 1, #Properties do + if not Properties[i].furniture then + Properties[i].furniture = {} + end + if not Properties[i].cctv then + Properties[i].cctv = {enabled = false} + end + if not Properties[i].garage then + Properties[i].garage = {enabled = false} + end + if not Properties[i].garage.StoredVehicles then + Properties[i].garage.StoredVehicles = {} + end + if not Properties[i].setName then + Properties[i].setName = "" + end + if not Properties[i].positions then + local Interior = GetInteriorValues(Properties[i].Interior) + Properties[i].positions = Interior.positions + end + Properties[i].plysinside = {} + if Config.OxInventory then + if Properties[i].Owned then + exports.ox_inventory:RegisterStash("property-" .. i, Properties[i].Name, 15, 100000, Properties[i].Owner) + end + else + Properties[i].positions.Storage = nil + end + end + local Players = ESX.GetExtendedPlayers() + Log("ESX-Property Beta Loaded", 11141375, {{name = "Property Count", value = #Properties, inline = true}, + {name = "OX Inventory", value = Config.OxInventory and "Enabled" or "Disabled", inline = true}}, 1) + for _, xPlayer in pairs(Players) do + TriggerClientEvent("esx_property:syncProperties", xPlayer.source, Properties, xPlayer.get("lastProperty")) + end + else + Properties = {} + print("[ERROR]: Properties.json Not Found!") + end +end + +function IsPlayerAdmin(player, action) + local xPlayer = ESX.GetPlayerFromId(player) + + for i = 1, #Config.AllowedGroups do + if xPlayer.group == Config.AllowedGroups[i] then + return true + end + end + + if Config.PlayerManagement.Enabled and action then + if xPlayer.job.name == Config.PlayerManagement.job and xPlayer.job.grade >= Config.PlayerManagement.Permissions[action] then + return true + end + end + + return false +end + +CreateThread(function() + Wait(3000) + PropertiesRefresh() + MySQL.query("ALTER TABLE `users` ADD COLUMN IF NOT EXISTS `last_property` LONGTEXT NULL", function(result) + if result then + print("[^2INFO^7] Adding ^5last_property^7 column to users table") + end + end) + if PM.Enabled then + MySQL.insert("INSERT IGNORE INTO `addon_account` (name, label, shared) VALUES (?, ? , 1)", {PM.society, PM.joblabel}, function(result) + if result then + print("[^2INFO^7] Adding ^5" .. PM.society .. " - " .. PM.joblabel .. "^7 into ^5addon_account^7 table") + end + end) + local Existance = ESX.DoesJobExist(PM.job, 0) + if not Existance then + MySQL.query("INSERT INTO `jobs` VALUES (?, ?, 1)", {PM.job, PM.joblabel}, function(result) + if result then + print("[^2INFO^7] Inserting ^5" .. PM.job .. " - " .. PM.joblabel .. "^7 into ^5jobs^7 table") + end + end) + end + for i = 1, #PM.jobRanks do + local Existance = ESX.DoesJobExist(PM.job, PM.jobRanks[i].grade) + if not Existance then + MySQL.query("INSERT INTO `job_grades` (job_name, grade, name, label, salary, skin_male, skin_female) VALUES (?, ?,?, ?, ?,'{}','{}')", + {PM.job, PM.jobRanks[i].grade, PM.jobRanks[i].name, PM.jobRanks[i].label, PM.jobRanks[i].salary}, function(result) + if result then + print("[^2INFO^7] Inserting ^5" .. PM.job .. " - " .. PM.jobRanks[i].grade .. " - " .. PM.jobRanks[i].label .. + "^7 into ^5job_grades^7 table") + end + end) + end + end + Wait(10) + ESX.RefreshJobs() + TriggerEvent('esx_society:registerSociety', 'realestateagent', 'realestateagent', 'society_realestateagent', 'society_realestateagent', + 'society_realestateagent', {type = 'private'}) + end +end) + +AddEventHandler("esx:playerLoaded", function(playerId, xPlayer) + Wait(1000) + local lastProperty = nil + MySQL.query("SELECT last_property FROM users WHERE identifier = ?", {xPlayer.identifier}, function(result) + if result then + if result[1].last_property then + local Data = json.decode(result[1].last_property) + xPlayer.set("lastProperty", Data) + lastProperty = Data + end + end + TriggerClientEvent("esx_property:syncProperties", playerId, Properties, lastProperty) + end) +end) + +--- Commands +ESX.RegisterCommand("property:refresh", Config.AllowedGroups, function(xPlayer) + PropertiesRefresh() +end, false, {help = "Refresh Properties"}) + +ESX.RegisterCommand("property:create", "user", function(xPlayer) + if IsPlayerAdmin(xPlayer.source) or (PM.Enabled and xPlayer.job.name == PM.job) then + xPlayer.triggerEvent("esx_property:CreateProperty") + end +end, false, {help = "Refresh Properties"}) + +ESX.RegisterCommand("property:admin", Config.AllowedGroups, function(xPlayer) + xPlayer.triggerEvent("esx_property:AdminMenu") +end, false, {help = "Refresh Properties"}) + +-- Buy Property +ESX.RegisterServerCallback("esx_property:buyProperty", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + local Price = Properties[PropertyId].Price + if xPlayer.getAccount("bank").money >= Price then + xPlayer.removeAccountMoney("bank", Price) + Properties[PropertyId].Owner = xPlayer.identifier + Properties[PropertyId].OwnerName = xPlayer.getName() + Properties[PropertyId].Owned = true + Log("Property Bought", 65280, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Price**", value = ESX.Math.GroupDigits(Price), inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}}, 1) + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + if Config.OxInventory then + exports.ox_inventory:RegisterStash("property-" .. PropertyId, Properties[PropertyId].Name, 15, 100000, xPlayer.identifier) + end + end + cb(xPlayer.getAccount("bank").money >= Price) +end) + +ESX.RegisterServerCallback("esx_property:attemptSellToPlayer", function(source, cb, PropertyId, PlayerId) + local xPlayer = ESX.GetPlayerFromId(source) + local xTarget = ESX.GetPlayerFromId(PlayerId) + local Price = Properties[PropertyId].Price + if xTarget and (xTarget.getAccount("bank").money >= Price) and (xPlayer.job.name == PM.job) then + xPlayer.removeAccountMoney("bank", Price) + Properties[PropertyId].Owner = xTarget.identifier + Properties[PropertyId].OwnerName = xTarget.getName() + Properties[PropertyId].Owned = true + Log("Property Sold To Player", 65280, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Price**", value = ESX.Math.GroupDigits(Price), inline = true}, + {name = "**Player**", value = xTarget.getName(), inline = true}, + {name = "**Agent**", value = xPlayer.getName(), inline = true}}, 1) + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + if Config.OxInventory then + exports.ox_inventory:RegisterStash("property-" .. PropertyId, Properties[PropertyId].Name, 15, 100000, xTarget.identifier) + end + if PM.Enabled then + local PlayerPrice = Price * PM.SalePercentage + local SocietyPrice = Price - PlayerPrice + TriggerEvent('esx_addonaccount:getSharedAccount', PM.society, function(account) + account.addMoney(SocietyPrice) + end) + xPlayer.addAccountMoney("bank", PlayerPrice) + end + end + cb(xPlayer.getAccount("bank").money >= Price) +end) + +-- Buy Property +ESX.RegisterServerCallback("esx_property:buyFurniture", function(source, cb, PropertyId, PropName, PropIndex, PropCatagory, pos, heading) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + if xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier]) then + local Price = Config.FurnitureCatagories[PropCatagory][PropIndex].price + if xPlayer.getAccount("bank").money >= Price then + xPlayer.removeAccountMoney("bank", Price) + cb(true) + local furniture = {Name = PropName, Index = PropIndex, Catagory = PropCatagory, Pos = pos, Heading = heading, Price = Price} + table.insert(Properties[PropertyId].furniture, furniture) + for i = 1, #Properties[PropertyId].plysinside do + TriggerClientEvent("esx_property:placeFurniture", Properties[PropertyId].plysinside[i], PropertyId, furniture, + #Properties[PropertyId].furniture) + end + TriggerClientEvent("esx_property:syncFurniture", -1, PropertyId, Properties[PropertyId].furniture) + else + cb(false) + ESX.ShowNotification("You ~r~Cannot~s~ Afford This Furniture.") + end + else + cb(false) + end + Log("Furniture Bought", 3640511, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, {name = "**Has Access**", + value = (xPlayer.identifier == Owner or IsPlayerAdmin(source) or + (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier])) and "Yes" or "No", inline = true}, + {name = "**Prop Name**", value = Config.FurnitureCatagories[PropCatagory][PropIndex].title, inline = true}, + {name = "**Price**", value = tostring(Config.FurnitureCatagories[PropCatagory][PropIndex].price), inline = true}, + {name = "**Can Afford**", + value = xPlayer.getAccount("bank").money >= Config.FurnitureCatagories[PropCatagory][PropIndex].price and "Yes" or "No", inline = true}}, 1) +end) + +-- Selling Property + +ESX.RegisterServerCallback("esx_property:sellProperty", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + if xPlayer.identifier == Owner then + local Price = ESX.Math.Round(Properties[PropertyId].Price * 0.6) + xPlayer.addAccountMoney("bank", Price) + Properties[PropertyId].Owner = "" + Properties[PropertyId].OwnerName = "" + Properties[PropertyId].Owned = false + Properties[PropertyId].Locked = false + Properties[PropertyId].Keys = {} + local furn = #(Properties[PropertyId].furniture) + if Config.WipeFurnitureOnSell then + Properties[PropertyId].furniture = {} + end + if Config.WipeCustomNameOnSell then + Properties[PropertyId].setName = "" + end + Properties[PropertyId].plysinside = {} + Log("Property Sold", 16711680, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Price**", value = ESX.Math.GroupDigits(Price), inline = true}, + {name = "**Owner**", value = xPlayer.getName(), inline = true}, + {name = "**Furniture Count**", value = tostring(furn), inline = true}, + {name = "**Vehicle Count**", value = tostring(#Properties[PropertyId].garage.StoredVehicles), inline = true}}, 1) + if Properties[PropertyId].garage.StoredVehicles then + Properties[PropertyId].garage.StoredVehicles = {} + end + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + if Config.OxInventory then + exports.ox_inventory:ClearInventory("property-" .. PropertyId) + end + end + cb(xPlayer.identifier == Owner) +end) + +-- Admin Menu Options + +ESX.RegisterServerCallback("esx_property:toggleLock", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + if xPlayer.identifier == Owner or IsPlayerAdmin(source, "ToggleLock") or + (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier]) then + Properties[PropertyId].Locked = not Properties[PropertyId].Locked + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + end + Log("Lock Toggled", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Executing User**", value = xPlayer.getName(), inline = true}, + {name = "**Status**", value = Properties[PropertyId].Locked and "Locked" or "Unlocked", inline = true}}, 3) + cb(xPlayer.identifier == Owner or IsPlayerAdmin(source, "ToggleLock") or + (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback("esx_property:toggleGarage", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "ToggleGarage") then + Properties[PropertyId].garage.enabled = not Properties[PropertyId].garage.enabled + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + Log("Property Garage Toggled", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Status**", value = Properties[PropertyId].garage.enabled and "Enabled" or "Disabled", + inline = true}}, 2) + cb(true, Properties[PropertyId].garage.enabled) + else + cb(false) + end +end) + +ESX.RegisterServerCallback("esx_property:toggleCCTV", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "ToggleCCTV") then + Properties[PropertyId].cctv.enabled = not Properties[PropertyId].cctv.enabled + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + cb(true, Properties[PropertyId].cctv.enabled) + Log("Property CCTV Toggled", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Status**", value = Properties[PropertyId].cctv.enabled and "Enabled" or "Disabled", + inline = true}}, 2) + else + cb(false) + end +end) + +ESX.RegisterServerCallback("esx_property:SetGaragePos", function(source, cb, PropertyId, heading) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "ToggleGarage") then + local PlayerPed = GetPlayerPed(source) + local PlayerPos = GetEntityCoords(PlayerPed) + local Property = Properties[PropertyId] + local Original = vector3(Properties[PropertyId].garage.pos.x, Properties[PropertyId].garage.pos.y, Properties[PropertyId].garage.pos.z) + Properties[PropertyId].garage.pos = PlayerPos + Properties[PropertyId].garage.Heading = heading + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + Log("Property Garage Location Changed", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Original Position**", value = tostring(Original), inline = true}, + {name = "**New Position**", value = tostring(PlayerPos), inline = true}}, 2) + cb(true) + else + cb(false) + end +end) + +ESX.RegisterServerCallback("esx_property:SetCCTVangle", function(source, cb, PropertyId, angles) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "ToggleCCTV") then + local Property = Properties[PropertyId] + Properties[PropertyId].cctv.rot = angles.rot + Properties[PropertyId].cctv.maxleft = angles.maxleft + Properties[PropertyId].cctv.maxright = angles.maxright + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + cb(true) + Log("Property CCTV Angle Changed", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}}, 2) + else + cb(false) + end +end) + +ESX.RegisterServerCallback("esx_property:CCTV", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + local Property = Properties[PropertyId] + if xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier]) then + SetEntityCoords(GetPlayerPed(source), vector3(Property.Entrance.x, Property.Entrance.y, Property.Entrance.z + 5.0)) + SetPlayerRoutingBucket(source, 0) + end + Log("Player Entered CCTV", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}}, 3) + cb(xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback("esx_property:ExitCCTV", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + local Property = Properties[PropertyId] + if xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier]) then + local Interior = GetInteriorValues(Property.Interior) + if Interior.type == "shell" then + SetEntityCoords(GetPlayerPed(source), vector3(Property.Entrance.x, Property.Entrance.y, 2001)) + else + SetEntityCoords(GetPlayerPed(source), Interior.pos) + end + SetPlayerRoutingBucket(source, PropertyId + 1) + end + Log("Player Exited CCTV", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}}, 3) + cb(xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback("esx_property:SetPropertyName", function(source, cb, PropertyId, name) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + local Property = Properties[PropertyId] + if xPlayer.identifier == Owner or IsPlayerAdmin(source) then + if name and #name <= Config.MaxNameLength then + Properties[PropertyId].setName = name + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + Log("Property Name Changed", 3640511, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, {name = "**New Name**", value = name, inline = true}}, 2) + end + end + cb((xPlayer.identifier == Owner or IsPlayerAdmin(source, "SetPropertyName")) and name and #name <= Config.MaxNameLength) +end) + +ESX.RegisterServerCallback("esx_property:KnockOnDoor", function(source, cb, PropertyId, name) + local xPlayer = ESX.GetPlayerFromId(source) + local Property = Properties[PropertyId] + local Owner = ESX.GetPlayerFromIdentifier(Property.Owner) + if Owner then + for i = 1, #(Property.plysinside) do + if Property.plysinside[i] == Owner.source then + Owner.showNotification("Someone Is ~b~Knocking~s~ On The Door.", "info") + cb(true) + Log("Player Knocked On Door", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}}, 3) + end + end + cb(false) + else + cb(false) + end +end) + +ESX.RegisterServerCallback("esx_property:RemoveCustomName", function(source, cb, PropertyId, name) + local Property = Properties[PropertyId] + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "RemovePropertyName") then + local n = Properties[PropertyId].setName + Properties[PropertyId].setName = "" + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + Log("Property Name Reset", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Removed Name**", value = n, inline = true}}, 3) + cb(true) + else + cb(false) + end +end) + +ESX.RegisterServerCallback("esx_property:deleteProperty", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "DeleteProperty") then + Log("Property Deleted", 16711680, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName ~= "" and Properties[PropertyId].OwnerName or "N/A", inline = true}, + {name = "**Furniture Count**", value = #(Properties[PropertyId].furniture), inline = true}, + {name = "**Vehicle Count**", value = #(Properties[PropertyId].garage.StoredVehicles), inline = true}}, 1) + table.remove(Properties, PropertyId) + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + if Config.OxInventory then + exports.ox_inventory:ClearInventory("property-" .. PropertyId) + end + end + cb(IsPlayerAdmin(source, "DeleteProperty")) +end) + +ESX.RegisterServerCallback("esx_property:ChangePrice", function(source, cb, PropertyId, NewPrice) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "SetPropertyPrice") then + local Original = Properties[PropertyId].Price + Properties[PropertyId].Price = NewPrice + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + Log("Property Price Changed", 3640511, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, {name = "**Original Price**", value = tostring(Original), inline = true}, + {name = "**New Price**", value = tostring(NewPrice), inline = true}}, 2) + end + cb(IsPlayerAdmin(source, "SetPropertyPrice")) +end) + +ESX.RegisterServerCallback("esx_property:ChangeInterior", function(source, cb, PropertyId, Interior) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "ChangeInterior") then + local Original = GetInteriorValues(Properties[PropertyId].Interior).label + Properties[PropertyId].Interior = Interior + Properties[PropertyId].furniture = {} + Properties[PropertyId].positions = GetInteriorValues(Interior).positions + if not Config.OxInventory then + Properties[PropertyId].positions.Storage = nil + end + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + Log("Property Interior Changed", 3640511, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, {name = "**Original**", value = tostring(Original), inline = true}, + {name = "**New Interior**", value = GetInteriorValues(Properties[PropertyId].Interior).label, inline = true}}, 2) + end + cb(IsPlayerAdmin(source, "ChangeInterior")) +end) + +ESX.RegisterServerCallback("esx_property:RemoveAllfurniture", function(source, cb, PropertyId) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + if xPlayer.identifier == Owner or IsPlayerAdmin(source, "ResetFurniture") or + (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier]) then + for i = 1, #Properties[PropertyId].plysinside do + for furniture = 1, #Properties[PropertyId].furniture do + TriggerClientEvent("esx_property:removeFurniture", Properties[PropertyId].plysinside[i], PropertyId, furniture) + end + end + Properties[PropertyId].furniture = {} + TriggerClientEvent("esx_property:syncFurniture", -1, PropertyId, Properties[PropertyId].furniture) + end + Log("Property Furniture Reset", 16711680, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}}, 1) + cb(xPlayer.identifier == Owner or IsPlayerAdmin(source, "ResetFurniture") or + (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback("esx_property:deleteFurniture", function(source, cb, PropertyId, furnitureIndex) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + if xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier]) then + if Properties[PropertyId].furniture[furnitureIndex] then + Properties[PropertyId].furniture[furnitureIndex] = nil + TriggerClientEvent("esx_property:syncFurniture", -1, PropertyId, Properties[PropertyId].furniture) + end + for i = 1, #Properties[PropertyId].plysinside do + TriggerClientEvent("esx_property:removeFurniture", Properties[PropertyId].plysinside[i], PropertyId, furnitureIndex) + end + end + Log("Property Furniture Deleted", 16711680, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Furniture Name**", value = xPlayer.getName(), inline = true}}, 3) + cb(xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback("esx_property:editFurniture", function(source, cb, PropertyId, furnitureIndex, Pos, Heading) + local xPlayer = ESX.GetPlayerFromId(source) + local Owner = Properties[PropertyId].Owner + if xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier]) then + if Properties[PropertyId].furniture[furnitureIndex] then + Properties[PropertyId].furniture[furnitureIndex].Pos = Pos + Properties[PropertyId].furniture[furnitureIndex].Heading = Heading + TriggerClientEvent("esx_property:syncFurniture", -1, PropertyId, Properties[PropertyId].furniture) + for i = 1, #Properties[PropertyId].plysinside do + TriggerClientEvent("esx_property:editFurniture", Properties[PropertyId].plysinside[i], PropertyId, furnitureIndex, Pos, Heading) + end + end + end + cb(xPlayer.identifier == Owner or IsPlayerAdmin(source) or (Properties[PropertyId].Keys and Properties[PropertyId].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback("esx_property:evictOwner", function(source, cb, PropertyId, Interior) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "EvictOwner") then + local xOwner = ESX.GetPlayerFromIdentifier(Properties[PropertyId].Owner) + if xOwner then + xOwner.showNotification("Your Have Been ~r~Evicted~s~.", "error") + end + local pName = Properties[PropertyId].OwnerName + Properties[PropertyId].Owner = "" + Properties[PropertyId].Owned = false + Properties[PropertyId].OwnerName = "" + Properties[PropertyId].Locked = false + Properties[PropertyId].Keys = {} + if Config.WipeFurnitureOnSell then + Properties[PropertyId].furniture = {} + end + Properties[PropertyId].setName = "" + Properties[PropertyId].plysinside = {} + if Properties[PropertyId].garage.StoredVehicles then + Properties[PropertyId].garage.StoredVehicles = {} + end + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + if Config.OxInventory then + exports.ox_inventory:ClearInventory("property-" .. PropertyId) + end + Log("Property Owner Evicted", 3640511, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, {name = "**Owner**", value = pName, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Has Access**", value = IsPlayerAdmin(source, "EvictOwner") and "Yes" or "No", inline = true}}, 1) + end + cb(IsPlayerAdmin(source, "EvictOwner")) +end) + +ESX.RegisterServerCallback("esx_property:CanRaid", function(source, cb, PropertyId, Interior) + local xPlayer = ESX.GetPlayerFromId(source) + local Can = false + if Config.Raiding.Enabled then + if (Config.CanAdminsRaid and IsPlayerAdmin(source)) or xPlayer.job.name == "police" then + if Config.Raiding.ItemRequired then + local itemCount = xPlayer.getInventoryItem(Config.Raiding.ItemRequired.name).count + if itemCount >= Config.Raiding.ItemRequired.ItemCount then + if Config.Raiding.ItemRequired.RemoveItem then + xPlayer.removeInventoryItem(Config.Raiding.ItemRequired.name, Config.Raiding.ItemRequired.ItemCount) + end + Can = true + else + xPlayer.showNotification("You need ~b~" .. Config.Raiding.ItemRequired.ItemCount .. "x " .. Config.Raiding.ItemRequired.label .. + "~s~ to Raid.", "error") + end + else + Can = true + end + end + end + cb(Can) + if Can then + local xOwner = ESX.GetPlayerFromIdentifier(Properties[PropertyId].Owner) + if xOwner then + xOwner.showNotification("Your Property is Currently Being ~b~Raided!", "error") + end + Wait(15000) + Properties[PropertyId].Locked = false + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + end +end) + +ESX.RegisterServerCallback("esx_property:ChangeEntrance", function(source, cb, PropertyId, Coords) + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "ChangeEntrance") then + local Origonal = Properties[PropertyId].Entrance.x .. "," .. Properties[PropertyId].Entrance.y .. "," .. Properties[PropertyId].Entrance.z + Properties[PropertyId].Entrance = {x = ESX.Math.Round(Coords.x, 2), y = ESX.Math.Round(Coords.y, 2), z = ESX.Math.Round(Coords.z, 2) - 0.8} + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + Log("Property Entrance Changed", 3640511, {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Owner**", value = Properties[PropertyId].OwnerName, inline = true}, + {name = "**Admin**", value = xPlayer.getName(), inline = true}, + {name = "**Has Access**", value = IsPlayerAdmin(source, "ChangeEntrance") and "Yes" or "No", + inline = true}, {name = "**Original**", value = Origonal, inline = true}, + {name = "**New**", + value = Properties[PropertyId].Entrance.x .. "," .. Properties[PropertyId].Entrance.y .. "," .. + Properties[PropertyId].Entrance.z, inline = true}}, 1) + end + cb(IsPlayerAdmin(source, "ChangeEntrance")) +end) + +ESX.RegisterServerCallback("esx_property:SetInventoryPosition", function(source, cb, PropertyId, Coords, Reset) + if Config.OxInventory then + local Property = Properties[PropertyId] + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "EditInteriorPositions") or (Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier]) then + local Interior = GetInteriorValues(Property.Interior) + if Reset then + Properties[PropertyId].positions.Storage = {x = ESX.Math.Round(Coords.x, 2), y = ESX.Math.Round(Coords.y, 2), z = ESX.Math.Round(Coords.z, 2)} + else + if Interior.type == "ipl" then + Properties[PropertyId].positions.Storage = {x = ESX.Math.Round(Coords.x, 2), y = ESX.Math.Round(Coords.y, 2), + z = ESX.Math.Round(Coords.z, 2)} + else + Properties[PropertyId].positions.Storage = vector3(Property.Entrance.x, Property.Entrance.y, 2000) - Coords + end + end + Log("Property Storage Location Set", 3640511, + {{name = "**Property Name**", value = Property.Name, inline = true}, {name = "**Owner**", value = Property.OwnerName, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, {name = "**Has Access**", + value = (IsPlayerAdmin(source, "EditInteriorPositions") or + (Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier])) and "Yes" or "No", inline = true}, + {name = "**Reset?**", value = Reset and "Yes" or "No", inline = true}}, 1) + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + end + cb(IsPlayerAdmin(source, "EditInteriorPositions") or (Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier])) + else + cb(false) + end +end) +-- Wardrobe +ESX.RegisterServerCallback("esx_property:SetWardrobePosition", function(source, cb, PropertyId, Coords, Reset) + local Property = Properties[PropertyId] + local xPlayer = ESX.GetPlayerFromId(source) + if IsPlayerAdmin(source, "EditInteriorPositions") or (Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier]) then + local Interior = GetInteriorValues(Property.Interior) + if Reset then + Properties[PropertyId].positions.Wardrobe = Interior.positions.Wardrobe + else + if Interior.type == "ipl" then + Properties[PropertyId].positions.Wardrobe = + {x = ESX.Math.Round(Coords.x, 2), y = ESX.Math.Round(Coords.y, 2), z = ESX.Math.Round(Coords.z, 2)} + else + Properties[PropertyId].positions.Wardrobe = vector3(Property.Entrance.x, Property.Entrance.y, 1999.8) - Coords + end + Log("Property Wardrobe Location Set", 3640511, + {{name = "**Property Name**", value = Property.Name, inline = true}, {name = "**Owner**", value = Property.OwnerName, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, {name = "**Has Access**", + value = (IsPlayerAdmin(source, "EditInteriorPositions") or + (Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier])) and "Yes" or "No", inline = true}, + {name = "**Reset?**", value = Reset and "Yes" or "No", inline = true}}, 1) + end + TriggerClientEvent("esx_property:syncProperties", -1, Properties) + end + cb(IsPlayerAdmin(source, "EditInteriorPositions") or (Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback('esx_property:getPlayerDressing', function(source, cb) + local xPlayer = ESX.GetPlayerFromId(source) + + TriggerEvent('esx_datastore:getDataStore', 'property', xPlayer.identifier, function(store) + local count = store.count('dressing') + local labels = {} + + for i = 1, count, 1 do + local entry = store.get('dressing', i) + table.insert(labels, entry.label) + end + + cb(labels) + end) +end) + +ESX.RegisterServerCallback('esx_property:GetInsidePlayers', function(source, cb, property) + local Property = Properties[property] + local Players = {} + local xPlayer = ESX.GetPlayerFromId(source) + local NearbyPlayers = Property.plysinside + + for k, v in pairs(NearbyPlayers) do + local xPlayer = ESX.GetPlayerFromId(v) + if not Properties[property].Keys then + Properties[property].Keys = {} + end + if xPlayer.identifier ~= Property.Owner and not Properties[property].Keys[xPlayer.identifier] then + Players[#Players + 1] = {Name = xPlayer.getName(), Id = xPlayer.source} + end + end + cb(Players) +end) + +ESX.RegisterServerCallback('esx_property:GetNearbyPlayers', function(source, cb, property) + local Property = Properties[property] + local Players = {} + local xPlayer = ESX.GetPlayerFromId(source) + local NearbyPlayers = ESX.OneSync.GetPlayersInArea(vector3(Property.Entrance.x, Property.Entrance.y, Property.Entrance.z), 5.0) + Wait(100) + for k, v in pairs(NearbyPlayers) do + local xTarget = ESX.GetPlayerFromId(v.id) + ---if xPlayer.identifier ~= xTarget.identifier then + Players[#Players + 1] = {name = xTarget.getName(), source = xTarget.source} + -- end + end + cb(Players) +end) + +ESX.RegisterServerCallback('esx_property:GetPlayersWithKeys', function(source, cb, property) + local Property = Properties[property] + local Players = {} + local xPlayer = ESX.GetPlayerFromId(source) + if xPlayer.identifier == Property.Owner then + cb(Property.Keys or {}) + end +end) + +ESX.RegisterServerCallback('esx_property:ShouldHaveKey', function(source, cb, property) + local xPlayer = ESX.GetPlayerFromId(source) + cb(Properties[property].Keys[xPlayer.identifier]) +end) + +ESX.RegisterServerCallback('esx_property:GetWebhook', function(source, cb, property) + cb(Config.CCTV.PictureWebook) +end) + +ESX.RegisterServerCallback('esx_property:RemoveLastProperty', function(source, cb, property) + local xPlayer = ESX.GetPlayerFromId(source) + MySQL.query("UPDATE `users` SET `last_property` = NULL WHERE `identifier` = ?", {xPlayer.identifier}) -- Remove Saved Data + SetPlayerRoutingBucket(source, 0) -- Reset Routing Bucket + xPlayer.set("lastProperty", nil) + cb() +end) + +ESX.RegisterServerCallback('esx_property:GiveKey', function(source, cb, property, player) + local xPlayer = ESX.GetPlayerFromId(source) + local xTarget = ESX.GetPlayerFromId(player) + local Property = Properties[property] + + if Property.Owner == xPlayer.identifier then + if not Property.Keys then + Properties[property].Keys = {} + end + + local id = xTarget.identifier + if not Properties[property].Keys[id] then + Property.Keys[id] = {name = xTarget.getName(), identifier = id} + xTarget.showNotification('You Have Been Granted Keys To ~b~' .. Property.Name .. '~s~.', 'success') + xTarget.triggerEvent("esx_property:giveKeyAccess") + cb(true) + else + xPlayer.showNotification('This Player Already Has Keys To ~b~' .. Property.Name .. '~s~.', 'error') + cb(false) + end + else + xPlayer.showNotification('You do ~r~not~s~ own this property.', 'error') + cb(false) + end + Log("Property Key Given", 3640511, + {{name = "**Property Name**", value = Property.Name, inline = true}, {name = "**Owner**", value = Property.OwnerName, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, + {name = "**Has Access**", value = Property.Owner == xPlayer.identifier and "Yes" or "No", inline = true}}, 1) +end) + +ESX.RegisterServerCallback('esx_property:StoreVehicle', function(source, cb, PropertyId, VehicleProperties) + local xPlayer = ESX.GetPlayerFromId(source) + local Property = Properties[PropertyId] + + if Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier] then + if Property.garage.enabled then + if Config.Garage.OwnedVehiclesOnly then + MySQL.scalar("SELECT `owner` FROM `owned_vehicles` WHERE `plate` = ?", {VehicleProperties.plate}, function(result) + if result then + if result == xPlayer.identifier then + Properties[PropertyId].garage.StoredVehicles[#Properties[PropertyId].garage.StoredVehicles + 1] = {owner = xPlayer.identifier, + vehicle = VehicleProperties} + cb(true) + elseif (Properties[PropertyId].Keys[result] or Property.Owner == result) then + Properties[PropertyId].garage.StoredVehicles[#Properties[PropertyId].garage.StoredVehicles + 1] = {owner = xPlayer.identifier, + vehicle = VehicleProperties} + cb(true) + else + cb(false) + end + else + cb(false) + end + end) + else + Properties[PropertyId].garage.StoredVehicles[#Properties[PropertyId].garage.StoredVehicles + 1] = {owner = xPlayer.identifier, + vehicle = VehicleProperties} + cb(true) + end + else + xPlayer.showNotification('Garage Not Enabled On This Property.', 'error') + cb(false) + end + else + xPlayer.showNotification('You ~r~Cannot~s~ Access this property.', 'error') + cb(false) + end + Log("User Attempted To Store Vehicle", 3640511, + {{name = "**Property Name**", value = Property.Name, inline = true}, {name = "**Owner**", value = Property.OwnerName, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, + {name = "**Has Access**", value = (Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier]) and "Yes" or "No", + inline = true}, {name = "**Garage Status**", value = Property.garage.enabled and "Enabled" or "Disabled", inline = true}, + {name = "**Vehicle Name**", value = VehicleProperties.DisplayName, inline = true}}, 2) +end) + +ESX.RegisterServerCallback('esx_property:AccessGarage', function(source, cb, PropertyId, VehicleProperties) + local xPlayer = ESX.GetPlayerFromId(source) + local Property = Properties[PropertyId] + + if Property.Owner == xPlayer.identifier or Properties[PropertyId].Keys[xPlayer.identifier] then + if Property.garage.enabled then + cb(Property.garage.StoredVehicles) + else + xPlayer.showNotification('Garage Not Enabled On This Property.', 'error') + cb(false) + end + else + xPlayer.showNotification('You ~r~Cannot~s~ Access this property.', 'error') + cb(false) + end + + Log("User Opened Garage Menu", 3640511, + {{name = "**Property Name**", value = Property.Name, inline = true}, {name = "**Owner**", value = Property.OwnerName, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, + {name = "**Garage Status**", value = Property.garage.enabled and "Enabled" or "Disabled", inline = true}}, 2) +end) + +ESX.RegisterServerCallback('esx_property:RemoveKey', function(source, cb, property, player) + local xPlayer = ESX.GetPlayerFromId(source) + local xTarget = ESX.GetPlayerFromIdentifier(player) + local Property = Properties[property] + + if Property.Owner == xPlayer.identifier then + if Property.Keys then + if Properties[property].Keys[player] then + Log("Property Key Revoked", 3640511, + {{name = "**Property Name**", value = Property.Name, inline = true}, {name = "**Owner**", value = xPlayer.getName(), inline = true}, + {name = "**Removed From**", value = tostring(Properties[property].Keys[player].name), inline = true}}, 3) + Properties[property].Keys[player] = nil + xTarget.showNotification('Your Key Access To ~b~' .. Property.Name .. '~s~. Has Been ~r~Revoked~s~', 'error') + xTarget.triggerEvent("esx_property:RemoveKeyAccess", property) + cb(true) + else + xPlayer.showNotification('This Player Does Not Have Keys To ~b~' .. Property.Name .. '~s~.', 'error') + cb(false) + end + else + cb(false) + end + else + xPlayer.showNotification('You do ~r~not~s~ own this property.', 'error') + cb(false) + end +end) + +ESX.RegisterServerCallback('esx_property:CanOpenFurniture', function(source, cb, property) + local xPlayer = ESX.GetPlayerFromId(source) + local Property = Properties[property] + cb(Property.Owner == xPlayer.identifier or (Property.Keys and Properties[property].Keys[xPlayer.identifier])) +end) + +ESX.RegisterServerCallback('esx_property:getPlayerOutfit', function(source, cb, num) + local xPlayer = ESX.GetPlayerFromId(source) + + TriggerEvent('esx_datastore:getDataStore', 'property', xPlayer.identifier, function(store) + local outfit = store.get('dressing', num) + cb(outfit.skin) + end) +end) + +-- Player Management +if PM.Enabled then + ESX.RegisterServerCallback('esx_property:PMenterOffice', function(source, cb) + local xPlayer = ESX.GetPlayerFromId(source) + local PlayerPed = GetPlayerPed(source) + + if xPlayer.job.name == PM.job then + SetEntityCoords(PlayerPed, PM.Locations.Exit) + SetPlayerRoutingBucket(source, 1) + cb(true) + else + cb(false) + end + end) + + ESX.RegisterServerCallback('esx_property:PMexitOffice', function(source, cb) + local xPlayer = ESX.GetPlayerFromId(source) + local PlayerPed = GetPlayerPed(source) + + if xPlayer.job.name == PM.job then + SetEntityCoords(PlayerPed, PM.Locations.Entrance) + SetPlayerRoutingBucket(source, 0) + cb(true) + else + cb(false) + end + end) +end + +-- Enter/leave Events + +RegisterNetEvent('esx_property:enter', function(PropertyId) + local player = source + local PlayerPed = GetPlayerPed(player) + local xPlayer = ESX.GetPlayerFromId(player) + local Property = Properties[PropertyId] + local Interior = GetInteriorValues(Property.Interior) + if not Properties[PropertyId].plysinside then + Properties[PropertyId].plysinside = {} + end + table.insert(Properties[PropertyId].plysinside, player) + + local PropertyData = {id = PropertyId, coords = Property.Entrance} -- Save the property data to the table + MySQL.query("UPDATE `users` SET `last_property` = ? WHERE `identifier` = ?", {json.encode(PropertyData), xPlayer.identifier}) -- Save the property data to the database + xPlayer.set("lastProperty", PropertyData) + if Interior.type == "shell" then + SetEntityCoords(PlayerPed, vector3(Property.Entrance.x, Property.Entrance.y, 2001)) + else + SetEntityCoords(PlayerPed, Interior.pos) + SetEntityHeading(PlayerPed, 0.0) + end + SetPlayerRoutingBucket(player, PropertyId + 1) + Log("Player Entered Property", 3640511, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, + {name = "**Property Player Count**", value = tostring(#(Properties[PropertyId].plysinside)), inline = true}}, 3) +end) + +RegisterNetEvent('esx_property:leave', function(PropertyId) + local player = source + local Property = Properties[PropertyId] + local xPlayer = ESX.GetPlayerFromId(player) + MySQL.query("UPDATE `users` SET `last_property` = NULL WHERE `identifier` = ?", {xPlayer.identifier}) -- Remove Saved Data + xPlayer.set("lastProperty", nil) + SetEntityCoords(player, vector3(Property.Entrance.x, Property.Entrance.y, Property.Entrance.z)) + SetEntityHeading(player, 0.0) + SetPlayerRoutingBucket(player, 0) + for i = 1, #(Properties[PropertyId].plysinside) do + if Properties[PropertyId].plysinside[i] == player then + table.remove(Properties[PropertyId].plysinside, i) + break + end + end + Log("Player Left Property", 3640511, + {{name = "**Property Name**", value = Properties[PropertyId].Name, inline = true}, + {name = "**Player**", value = xPlayer.getName(), inline = true}, + {name = "**Property Player Count**", value = tostring(#(Properties[PropertyId].plysinside)), inline = true}}, 3) +end) + +RegisterNetEvent('esx_property:SetVehicleOut', function(PropertyId, VehIndex) + local source = source + local Property = Properties[PropertyId] + local xPlayer = ESX.GetPlayerFromId(source) + table.remove(Properties[PropertyId].garage.StoredVehicles, VehIndex) +end) + +AddEventHandler('playerDropped', function() + local source = source + for PropertyId = 1, #Properties do + for i = 1, #(Properties[PropertyId].plysinside) do + if Properties[PropertyId].plysinside[i] == source then + table.remove(Properties[PropertyId].plysinside, i) + break + end + end + end +end) + +ESX.RegisterServerCallback('esx_property:CanCreateProperty', function(source, cb, num) + local Re = false + local xPlayer = ESX.GetPlayerFromId(source) + + if xPlayer then + if IsPlayerAdmin(source, "CreateProperty") then + Re = true + end + end + + cb(Re) +end) + +ESX.RegisterServerCallback('esx_property:IsAdmin', function(source, cb, num) + cb(IsPlayerAdmin(source, "ViewProperties")) +end) + +RegisterNetEvent('esx_property:server:createProperty', function(Property) + local source = source + local xPlayer = ESX.GetPlayerFromId(source) + local Interior = GetInteriorValues(Property.interior) + print(ESX.DumpTable(Property)) + local garageData = + Property.garage.enabled and {enabled = true, pos = Property.garage.pos, Heading = Property.garage.heading, StoredVehicles = {}} or + {enabled = false} + if IsPlayerAdmin(source, "CreateProperty") then + local ActualProperty = {Name = Property.name, setName = "", Price = Property.price, furniture = {}, plysinside = {}, Interior = Property.interior, + Entrance = Property.entrance, Owner = "", Keys = {}, positions = Interior.positions, cctv = Property.cctv, + garage = garageData, Owned = false, Locked = false} + Properties[#Properties + 1] = ActualProperty + end + Log("Property Created", 65280, + {{name = "**Admin**", value = xPlayer.getName(), inline = true}, {name = "**Name**", value = Property.name, inline = true}, + {name = "**Price**", value = ESX.Math.GroupDigits(Property.price), inline = true}, + {name = "**Interior**", value = Interior.label, inline = true}, + {name = "**Garage Status**", value = Property.garage.enabled and "Enabled" or "Disabled", inline = true}, + {name = "**CCTV Status**", value = Property.cctv.enabled and "Enabled" or "Disabled", inline = true}, + {name = "**Entrance**", value = tostring(Property.entrance.x .. ", " .. Property.entrance.y .. ", " .. Property.entrance.z), inline = true}}, 1) + TriggerClientEvent("esx_property:syncProperties", -1, Properties) +end) + +-- Json File Saving + +--- Save Properties On Server Scheduled Restart +AddEventHandler('txAdmin:events:scheduledRestart', function(eventData) + if eventData.secondsRemaining == 60 then + CreateThread(function() + Wait(50000) + SaveResourceFile(GetCurrentResourceName(), 'properties.json', json.encode(Properties)) + Log("Properties Saving", 11141375, {{name = "**Reason**", value = "Scheduled Server Restart", inline = true}, + {name = "**Property Count**", value = tostring(#Properties), inline = true}}, 1) + end) + end +end) + +--- Save Properties On Server Stop/Restart +AddEventHandler('txAdmin:events:serverShuttingDown', function() + SaveResourceFile(GetCurrentResourceName(), 'properties.json', json.encode(Properties)) + Log("Properties Saving", 11141375, + {{name = "**Reason**", value = "Server Shutdown", inline = true}, {name = "**Property Count**", value = tostring(#Properties), inline = true}}, 1) +end) + +--- Save Properties On Resource Stop/Restart + +AddEventHandler('onResourceStop', function(ResourceName) + if ResourceName == GetCurrentResourceName() then + SaveResourceFile(GetCurrentResourceName(), 'properties.json', json.encode(Properties)) + Log("Properties Saving", 11141375, {{name = "**Reason**", value = "Resource Restart", inline = true}, + {name = "**Property Count**", value = tostring(#Properties), inline = true}}, 1) + end +end) +