diff --git a/.gitignore b/.gitignore index 6fd88e2e..2cf70cd0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,9 @@ .*.swp .* !.git* +/imgui* +/lua51* +savedir +log* +import +/bin diff --git a/LICENSE b/LICENSE index 244330b4..f288702d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,27 +1,674 @@ -The MIT License (MIT) + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Copyright (c) 2015-2016 USPGameDev + 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. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Preamble -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + 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. -Everything else here is under the Creative Commons BY SA 4.0 License: -https://creativecommons.org/licenses/by-sa/4.0/ + 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. + + + Copyright (C) + + 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: + + Copyright (C) + 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 +. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8351a90f --- /dev/null +++ b/Makefile @@ -0,0 +1,222 @@ + +GAME_DIR=game +BIN_DIR=bin +LIBS_DIR=$(GAME_DIR)/libs +GAME=$(BIN_DIR)/backdoor.love + +DEPLOY_SITE=https://uspgamedev.org +DEPLOY_PATH=downloads/projects/backdoor +DEPLOY_URL=$(DEPLOY_SITE)/$(DEPLOY_PATH) + +BIN_DIR_WIN32=$(BIN_DIR)/win32 +BIN_DIR_WIN32_PACKAGE=$(BIN_DIR_WIN32)/backdoor +BIN_DIR_WIN32_DEPS=$(BIN_DIR_WIN32)/deps +LOVE_WIN32=$(BIN_DIR_WIN32_DEPS)/love-11.1-win32.zip +GAME_WIN32=$(BIN_DIR_WIN32)/backdoor-win32.zip + +BIN_DIR_LINUX64=$(BIN_DIR)/linux64 +GAME_LINUX64=$(BIN_DIR_LINUX64)/backdoor-x86_64.AppImage +BIN_DIR_LINUX64_IMG=$(BIN_DIR_LINUX64)/image +GAME_LINUX64_TEMPLATE_NAME=backdoor-appimage-x86_64-template.tgz +GAME_LINUX64_TEMPLATE_URL=$(DEPLOY_URL)/$(GAME_LINUX64_TEMPLATE_NAME) +GAME_LINUX64_TEMPLATE=$(BIN_DIR_LINUX64_IMG)/$(GAME_LINUX64_TEMPLATE_NAME) +APPIMG_TOOL_NAME=appimage-x86_64.AppImage +APPIMG_TOOL=$(BIN_DIR_LINUX64_IMG)/$(APPIMG_TOOL_NAME) +APPIMG_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage + +BIN_DIR_OSX=$(BIN_DIR)/osx +GAME_OSX_APP=$(BIN_DIR_OSX)/backdoor.app +GAME_OSX_TEMPLATE_NAME=backdoor-osx-template.zip +GAME_OSX_TEMPLATE_URL=$(DEPLOY_URL)/$(GAME_OSX_TEMPLATE_NAME) +GAME_OSX_TEMPLATE=$(BIN_DIR_OSX)/$(GAME_OSX_TEMPLATE_NAME) +GAME_OSX=$(BIN_DIR_OSX)/backdoor-osx.zip + +LUX_LIB=$(LIBS_DIR)/lux +LUX_REPO=externals/luxproject + +STEAMING_LIB=$(LIBS_DIR)/steaming +STEAMING_REPO=externals/STEAMING +STEAMING_MODULES=$(STEAMING_REPO)/clean_template/font.lua \ + $(STEAMING_REPO)/clean_template/res_manager.lua \ + $(STEAMING_REPO)/clean_template/util.lua \ + $(STEAMING_REPO)/clean_template/classes \ + $(STEAMING_REPO)/clean_template/extra_libs + +INPUT_LIB=$(LIBS_DIR)/input +INPUT_REPO=externals/input + +IMGUI_LIB=imgui.so +IMGUI_DLL=$(BIN_DIR_WIN32_DEPS)/imgui.dll +LUAJIT_DLL=$(BIN_DIR_WIN32_DEPS)/lua51.dll +IMGUI_REPO=externals/love-imgui +IMGUI_BUILD_DIR=externals/love-imgui/build +IMGUI_BUILD_MAKEFILE=$(IMGUI_BUILD_DIR)/Makefile +IMGUI_BINARY=$(IMGUI_BUILD_DIR)/imgui.so + +CPML_LIB=$(LIBS_DIR)/cpml +CPML_REPO=externals/cpml + +DKJSON_LIB=$(LIBS_DIR)/dkjson.lua + +DEPENDENCIES=$(LUX_LIB) $(STEAMING_LIB) $(IMGUI_LIB) $(CPML_LIB) $(DKJSON_LIB) $(INPUT_LIB) + +BUILD_TYPE=nightly + +## MAIN TARGETS + +all: $(DEPENDENCIES) + love game $(FLAGS) + +update: + cd $(LUX_REPO); git pull + cd $(STEAMING_REPO); git pull + cd $(INPUT_REPO); git pull + +$(GAME): $(DEPENDENCIES) + mkdir -p $(BIN_DIR) + cd game; zip -r backdoor.love * + mv game/backdoor.love $(GAME) + +## LUX + +$(LUX_LIB): $(LUX_REPO) + cp -r $(LUX_REPO)/lib/lux $(LUX_LIB) + +$(LUX_REPO): + git clone https://github.com/Kazuo256/luxproject.git $(LUX_REPO) + +## STEAMING + +$(STEAMING_LIB): $(STEAMING_REPO) + mkdir $(STEAMING_LIB) + cp -r $(STEAMING_MODULES) $(STEAMING_LIB) + +$(STEAMING_REPO): + git clone https://github.com/uspgamedev/STEAMING.git $(STEAMING_REPO) + +## INPUT + +$(INPUT_LIB): $(INPUT_REPO) + cp -r $(INPUT_REPO) $(INPUT_LIB) + +$(INPUT_REPO): + git clone https://github.com/orenjiakira/input.git $(INPUT_REPO) + +## IMGUI + +$(IMGUI_LIB): $(IMGUI_BINARY) + cp -f $(IMGUI_BUILD_DIR)/imgui.so $(IMGUI_LIB) + +$(IMGUI_BINARY): $(IMGUI_BUILD_MAKEFILE) + $(MAKE) -C $(IMGUI_BUILD_DIR) + +$(IMGUI_BUILD_MAKEFILE): $(IMGUI_BUILD_DIR) + cd $(IMGUI_BUILD_DIR) && cmake .. + +$(IMGUI_BUILD_DIR): $(IMGUI_REPO) + mkdir -p $(IMGUI_BUILD_DIR) + +$(IMGUI_REPO): + git clone https://github.com/kazuo256/love-imgui.git $(IMGUI_REPO) + +## CPML + +$(CPML_LIB): $(CPML_REPO) + mkdir $(CPML_LIB) + cp -r $(CPML_REPO)/modules $(CPML_LIB) + cp -r $(CPML_REPO)/init.lua $(CPML_LIB) + +$(CPML_REPO): + git clone https://github.com/excessive/cpml.git $(CPML_REPO) + +## DKJSON + +$(DKJSON_LIB): + wget -O $(DKJSON_LIB) -- http://dkolf.de/src/dkjson-lua.fsl/raw/dkjson.lua?name=16cbc26080996d9da827df42cb0844a25518eeb3 + +## Linux build + +$(GAME_LINUX64_TEMPLATE): $(GAME) + mkdir -p $(BIN_DIR_LINUX64_IMG) + wget -O $(GAME_LINUX64_TEMPLATE) -- $(GAME_LINUX64_TEMPLATE_URL) + +$(APPIMG_TOOL): $(GAME) + mkdir -p $(BIN_DIR_LINUX64_IMG) + wget -O $(APPIMG_TOOL) -- $(APPIMG_TOOL_URL) + chmod +x $(APPIMG_TOOL) + +$(GAME_LINUX64): $(GAME) $(GAME_LINUX64_TEMPLATE) $(APPIMG_TOOL) + mkdir -p $(BIN_DIR_LINUX64_IMG) + cd $(BIN_DIR_LINUX64_IMG); tar -xf $(GAME_LINUX64_TEMPLATE_NAME) + cat $(BIN_DIR_LINUX64_IMG)/squashfs-root/usr/bin/love $(GAME) > $(BIN_DIR_LINUX64_IMG)/squashfs-root/usr/bin/backdoor + chmod +x $(BIN_DIR_LINUX64_IMG)/squashfs-root/usr/bin/backdoor + cp $(IMGUI_LIB) $(BIN_DIR_LINUX64_IMG)/squashfs-root/usr/bin + chmod +x $(BIN_DIR_LINUX64_IMG)/squashfs-root/AppRun + cd $(BIN_DIR_LINUX64_IMG); ./$(APPIMG_TOOL_NAME) squashfs-root + mv $(BIN_DIR_LINUX64_IMG)/backdoor-x86_64.AppImage $(BIN_DIR_LINUX64) + rm -rf $(BIN_DIR_LINUX64_IMG) + +## Windows build + +$(LOVE_WIN32): $(GAME) + mkdir -p $(BIN_DIR_WIN32_DEPS) + wget -O $(LOVE_WIN32) https://bitbucket.org/rude/love/downloads/love-11.1-win32.zip + +$(IMGUI_DLL): + mkdir -p $(BIN_DIR_WIN32_DEPS) + wget -O $(IMGUI_DLL) https://uspgamedev.org/downloads/libs/windows/x86/imgui.dll + +$(LUAJIT_DLL): + mkdir -p $(BIN_DIR_WIN32_DEPS) + wget -O $(LUAJIT_DLL) https://uspgamedev.org/downloads/libs/windows/x86/lua51.dll + +$(GAME_WIN32): $(GAME) $(IMGUI_DLL) $(LUAJIT_DLL) $(LOVE_WIN32) + unzip $(LOVE_WIN32) -d $(BIN_DIR_WIN32) + mv $(BIN_DIR_WIN32)/love-11.1.0-win32 $(BIN_DIR_WIN32_PACKAGE) + cp $(IMGUI_DLL) $(LUAJIT_DLL) $(BIN_DIR_WIN32_PACKAGE) + cat $(BIN_DIR_WIN32_PACKAGE)/love.exe $(GAME) > $(BIN_DIR_WIN32_PACKAGE)/backdoor.exe + rm $(BIN_DIR_WIN32_PACKAGE)/love.exe $(BIN_DIR_WIN32_PACKAGE)/lovec.exe + zip -r $(GAME_WIN32) $(BIN_DIR_WIN32_PACKAGE) + rm -rf $(BIN_DIR_WIN32_PACKAGE) + +## OSX + +$(GAME_OSX_TEMPLATE): + mkdir -p $(BIN_DIR_OSX) + wget -O $(GAME_OSX_TEMPLATE) $(GAME_OSX_TEMPLATE_URL) + +$(GAME_OSX): $(GAME) $(GAME_OSX_TEMPLATE) + cd $(BIN_DIR_OSX); unzip $(GAME_OSX_TEMPLATE_NAME) + cp $(GAME) $(IMGUI_LIB) $(GAME_OSX_APP)/Contents/Resources + zip -yr $(GAME_OSX) $(GAME_OSX_APP) + rm -rf $(GAME_OSX_TEMPLATE) + +## Deploy + +.PHONY: export +export: $(GAME) + +.PHONY: windows +windows: $(GAME_WIN32) + +.PHONY: linux +linux: $(GAME_LINUX64) + +.PHONY: osx +osx: $(GAME_OSX) + +.PHONY: deploy +deploy: $(GAME) $(GAME_WIN32) $(GAME_LINUX64) $(GAME_OSX) + scp $(GAME) $(GAME_WIN32) $(GAME_LINUX64) $(GAME_OSX) kazuo@uspgamedev.org:/var/docker-www/static/downloads/projects/backdoor/$(BUILD_TYPE)/ + +## CLEAN UP + +.PHONY: clean +clean: + rm -rf $(DEPENDENCIES) + +.PHONY: purge +purge: clean + rm -rf externals/* + rm -rf bin/* + diff --git a/README.md b/README.md index abffea3d..517d2e57 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,36 @@ -BACKDOOR -======== +At the *Front Stage*, from the *Fateful Dream* Endless saga: +The *Outermaze* Book of Rhapsodies' +# BACKDOOR ROUTE + +*Backdoor Route* is a card-collecting deck-building rogue-like cyberpunk +computer game developed in partnership with **project:v**. Abandoned in a +colossal planet-ship traveling across the rifts of the multiverse, the survivors +of a long-forgotten tragedy must venture through the ruins of a dead and +hopeless world in search of the Fruits of Vanth while drifting towards what they +believe to be their salvation. Play the role of one of these interdimensional +immigrants in a game that closely follows the format of classic rogue-likes save +for one particularity: your actions, your items, your skills, and even your +character progression are all represented by cards you assemble in decks while +you crawl, hack and slash your way through the world the Gods have left +behind. + +## Running the project + +Works only in Unix systems (Linux and MacOS) for now. Dependencies: + ++ git ++ CMake ++ Make ++ löve ++ wget ++ luajit (dev package) + +If all the above are properly installed, the command + +```bash +$ make +``` + +Should be enough to download, setup, and run the game. diff --git a/addons/card_dock/card_dock.gd b/addons/card_dock/card_dock.gd deleted file mode 100644 index 81ade915..00000000 --- a/addons/card_dock/card_dock.gd +++ /dev/null @@ -1,15 +0,0 @@ -tool -extends EditorPlugin - -const DockScene = preload("res://addons/card_dock/card_dock.tscn") - -var dock - -func _enter_tree(): - dock = DockScene.instance() - dock.plugin = self - add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE, dock) - -func _exit_tree(): - remove_control_from_docks(dock) - dock.free() diff --git a/addons/card_dock/card_dock.tscn b/addons/card_dock/card_dock.tscn deleted file mode 100644 index 17137f6c..00000000 --- a/addons/card_dock/card_dock.tscn +++ /dev/null @@ -1,69 +0,0 @@ -[gd_scene load_steps=2 format=1] - -[ext_resource path="res://addons/card_dock/main.gd" type="Script" id=1] - -[node name="Card" type="Container"] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 0.0 -margin/top = 0.0 -margin/right = 0.0 -margin/bottom = 0.0 -script/script = ExtResource( 1 ) - -[node name="Title" type="Label" parent="."] - -anchor/right = 1 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 7.0 -margin/top = 7.0 -margin/right = 7.0 -margin/bottom = 25.0 -text = "Bom dia" -align = 1 -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="Contents" type="Tree" parent="."] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 7.0 -margin/top = 32.0 -margin/right = 7.0 -margin/bottom = 7.0 - -[node name="Test" type="ToolButton" parent="Contents"] - -anchor/right = 1 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 0.0 -margin/top = 0.0 -margin/right = 0.0 -margin/bottom = 32.0 -toggle_mode = false -enabled_focus_mode = 2 -shortcut = null -text = "Sbrubles" -flat = true - -[connection signal="pressed" from="Contents/Test" to="." method="_on_pressed"] - - diff --git a/addons/card_dock/main.gd b/addons/card_dock/main.gd deleted file mode 100644 index b0797f4f..00000000 --- a/addons/card_dock/main.gd +++ /dev/null @@ -1,16 +0,0 @@ -tool -extends Container - -var plugin -var test - -func _on_enter_tree(): - test = get_node("Contents/Test") - test.connect("pressed", self, "_on_pressed") - -func _on_exit_tree(): - test.disconnect("pressed", self, "_on_pressed") - -func _on_pressed(): - var selected = plugin.get_selection().get_selected_nodes()[0] - printt("selected:", selected.get_card_id(selected.get_node("Arcane Bolt"))) diff --git a/addons/card_dock/plugin.cfg b/addons/card_dock/plugin.cfg deleted file mode 100644 index cf3fa95f..00000000 --- a/addons/card_dock/plugin.cfg +++ /dev/null @@ -1,9 +0,0 @@ - -[plugin] - -name="Card dock" -description="A dock for creating and editing game cards" -author="kazuo256" -version="0.0" -script="card_dock.gd" - diff --git a/assets/bodies/dummy/meta.json b/assets/bodies/dummy/meta.json new file mode 100644 index 00000000..dd0d0177 --- /dev/null +++ b/assets/bodies/dummy/meta.json @@ -0,0 +1,4 @@ +{ + "height": 1, + "offset": {"x": 0, "y": -32} +} diff --git a/assets/bodies/eye/meta.json b/assets/bodies/eye/meta.json new file mode 100644 index 00000000..dd0d0177 --- /dev/null +++ b/assets/bodies/eye/meta.json @@ -0,0 +1,4 @@ +{ + "height": 1, + "offset": {"x": 0, "y": -32} +} diff --git a/assets/bodies/froggy/meta.json b/assets/bodies/froggy/meta.json new file mode 100644 index 00000000..4431b652 --- /dev/null +++ b/assets/bodies/froggy/meta.json @@ -0,0 +1,4 @@ +{ + "height": 0, + "offset": {"x": 0, "y": -32} +} diff --git a/assets/bodies/hero/meta.json b/assets/bodies/hero/meta.json new file mode 100644 index 00000000..dd0d0177 --- /dev/null +++ b/assets/bodies/hero/meta.json @@ -0,0 +1,4 @@ +{ + "height": 1, + "offset": {"x": 0, "y": -32} +} diff --git a/assets/bodies/slime/meta.json b/assets/bodies/slime/meta.json new file mode 100644 index 00000000..4431b652 --- /dev/null +++ b/assets/bodies/slime/meta.json @@ -0,0 +1,4 @@ +{ + "height": 0, + "offset": {"x": 0, "y": -32} +} diff --git a/assets/bodies/thief/meta.json b/assets/bodies/thief/meta.json new file mode 100644 index 00000000..dd0d0177 --- /dev/null +++ b/assets/bodies/thief/meta.json @@ -0,0 +1,4 @@ +{ + "height": 1, + "offset": {"x": 0, "y": -32} +} diff --git a/assets/cursor_tile.ase b/assets/cursor_tile.ase new file mode 100644 index 00000000..c59f6eab Binary files /dev/null and b/assets/cursor_tile.ase differ diff --git a/assets/demobody.ase b/assets/demobody.ase new file mode 100644 index 00000000..d78b4fdc Binary files /dev/null and b/assets/demobody.ase differ diff --git a/assets/floating-eye-demon.ase b/assets/floating-eye-demon.ase new file mode 100644 index 00000000..cb405016 Binary files /dev/null and b/assets/floating-eye-demon.ase differ diff --git a/assets/hearthborn-idle.ase b/assets/hearthborn-idle.ase new file mode 100644 index 00000000..205c6e9b Binary files /dev/null and b/assets/hearthborn-idle.ase differ diff --git a/assets/imp.ase b/assets/imp.ase new file mode 100644 index 00000000..aff35439 Binary files /dev/null and b/assets/imp.ase differ diff --git a/assets/menu_cursor.ase b/assets/menu_cursor.ase new file mode 100644 index 00000000..90893f5c Binary files /dev/null and b/assets/menu_cursor.ase differ diff --git a/assets/menu_cursor.png b/assets/menu_cursor.png new file mode 100644 index 00000000..bdcd0309 Binary files /dev/null and b/assets/menu_cursor.png differ diff --git a/assets/slimy-bouncy.ase b/assets/slimy-bouncy.ase new file mode 100644 index 00000000..ea20f73d Binary files /dev/null and b/assets/slimy-bouncy.ase differ diff --git a/components/bodyview.gd b/components/bodyview.gd deleted file mode 100644 index e2359828..00000000 --- a/components/bodyview.gd +++ /dev/null @@ -1,44 +0,0 @@ - -extends Node2D - -const BodyViewScene = preload("res://components/bodyview.tscn") - -var body - -onready var sprite = get_node("Sprite") -onready var animation = get_node("Sprite/Animation") -onready var lifebar = get_node("Sprite/LifeBar") -onready var hl_indicator = get_node("Highlight") - -export(bool) var highlight = false - -static func create(body): - var bodyview = BodyViewScene.instance() - bodyview.body = body - return bodyview - -func _ready(): - sprite.set_texture(load("res://assets/bodies/" + body.type + "/idle.png")) - animation.play("idle") - set_process(true) - -func highlight(): - highlight = true - update_hl() - -func unhighlight(): - highlight = false - update_hl() - -func set_hl_color(color): - get_node("Highlight").set_modulate(color) - -func update_hl(): - if highlight: - get_node("Highlight").show() - else: - get_node("Highlight").hide() - -func _process(delta): - set_pos(get_parent().map_to_world(body.pos) + Vector2(0, 16 - 1)) - lifebar.set_value(body.get_hp_percent()) diff --git a/components/bodyview.tscn b/components/bodyview.tscn deleted file mode 100644 index 463d8850..00000000 --- a/components/bodyview.tscn +++ /dev/null @@ -1,85 +0,0 @@ -[gd_scene load_steps=6 format=1] - -[ext_resource path="res://components/bodyview.gd" type="Script" id=1] -[ext_resource path="res://assets/floor-base.tex" type="Texture" id=2] -[ext_resource path="res://assets/bodies/hero/idle.png" type="Texture" id=3] - -[sub_resource type="StyleBoxFlat" id=1] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 -bg_color = Color( 0.0884399, 0.539062, 0.391202, 1 ) -light_color = Color( 0.258514, 0.769531, 0.541969, 1 ) -dark_color = Color( 0.0714111, 0.40625, 0.257142, 1 ) -border_size = 0 -border_blend = true -draw_bg = true - -[sub_resource type="Animation" id=2] - -resource/name = "idle" -length = 0.9 -loop = true -step = 0.15 -tracks/0/type = "value" -tracks/0/path = NodePath(".:frame") -tracks/0/interp = 1 -tracks/0/imported = false -tracks/0/keys = { "times":FloatArray( 0, 0.3, 0.45, 0.75 ), "transitions":FloatArray( 1, 1, 1, 1 ), "update":1, "values":[ 1, 0, 2, 0 ] } - -[node name="BodyView" type="Node2D"] - -script/script = ExtResource( 1 ) -highlight = false - -[node name="Highlight" type="Sprite" parent="."] - -texture = ExtResource( 2 ) -modulate = Color( 0.922302, 1, 0.476562, 0.291683 ) - -[node name="Sprite" type="Sprite" parent="."] - -texture = ExtResource( 3 ) -offset = Vector2( 0, -32 ) -hframes = 3 -frame = 2 - -[node name="LifeBar" type="ProgressBar" parent="Sprite"] - -anchor/left = 3 -anchor/top = 2 -anchor/right = 3 -anchor/bottom = 3 -rect/scale = Vector2( 0.25, 0.25 ) -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 48.0 -margin/top = -0.84375 -margin/right = -80.0 -margin/bottom = 113.0 -custom_styles/fg = SubResource( 1 ) -range/min = 0.0 -range/max = 100.0 -range/step = 1.0 -range/page = 0.0 -range/value = 1.0 -range/exp_edit = false -range/rounded = false -percent/visible = false - -[node name="Animation" type="AnimationPlayer" parent="Sprite"] - -playback/process_mode = 1 -playback/default_blend_time = 0.0 -root/root = NodePath("..") -anims/idle = SubResource( 2 ) -playback/active = true -playback/speed = 1.0 -blend_times = [ ] -autoplay = "" - - diff --git a/components/controller/cooldownbar.gd b/components/controller/cooldownbar.gd deleted file mode 100644 index 00b1f992..00000000 --- a/components/controller/cooldownbar.gd +++ /dev/null @@ -1,24 +0,0 @@ - -extends ProgressBar - -var player - -func set_player(the_player): - player = the_player - player.connect("spent_action", self, "_on_player_action") - set_process(true) - show() - -func stop(): - player.disconnect("spent_action", self, "_on_player_action") - set_process(false) - player = null - hide() - -func _on_player_action(): - set_max(player.cooldown) - set_min(0) - set_value(0) - -func _process(delta): - set_value(get_max() - player.cooldown) diff --git a/components/controller/deck.gd b/components/controller/deck.gd deleted file mode 100644 index f1ebb0e5..00000000 --- a/components/controller/deck.gd +++ /dev/null @@ -1,35 +0,0 @@ - -extends Control - -const MAX_DECK_SIZE = 20 - -var deck_draw -var player = null - -func set_player(the_player): - player = the_player - deck_draw.set_min(0) - deck_draw.set_max(MAX_DECK_SIZE) - print("max: ", str(deck_draw.get_max())) - self.show() - player.connect("update_deck", self, "update_deck") - update_deck() - -func stop(): - hide() - set_process(false) - set_process_input(false) - player.disconnect("update_deck", self, "update_deck") - deck_draw.hide() - -func _ready(): - deck_draw = ProgressBar.new() - deck_draw.set_pos(Vector2(0,0)) - deck_draw.set_size(self.get_size()) - deck_draw.show() - add_child(deck_draw) - self.hide() - -func update_deck(): - deck_draw.set_value(player.deck.size()); - print("now: ", str(deck_draw.get_max())) \ No newline at end of file diff --git a/components/controller/default_controller.gd b/components/controller/default_controller.gd deleted file mode 100644 index 4794b55b..00000000 --- a/components/controller/default_controller.gd +++ /dev/null @@ -1,78 +0,0 @@ - -extends Node - -const GAME_RESOLUTION = Vector2(640,480) - -var enabled = true -var actions = {} - -func _init(): - if self extends Control: - set_process_input(true) - else: - set_process_unhandled_input(true) - build_action_dict() - -func enable(): - #print("enable ", get_path()) - enabled = true - -func disable(): - #print("disable ", get_path()) - enabled = false - -func _input_event(event): - if event.is_pressed() and event.type == InputEvent.KEY: - consume_input_key(event) - -func _input(event): - _input_event(event) - -func _unhandled_input(event): - _input_event(event) - -func get_event_name(action): - return "event_" + action.replace("ui_", "").replace("debug_", "") - -func build_action_dict(): - var index = 1 - var action = InputMap.get_action_from_id(index) - - while action != "": - index += 1 - action = InputMap.get_action_from_id(index) - var method_name = get_event_name(action) - if self.has_method(method_name): - actions[action] = funcref(self, method_name) - if self.get_tree() != null: - self.get_tree().set_input_as_handled() - -func consume_input_key(event): - if not enabled: - return - - for action in actions.keys(): - if event.is_action_pressed(action): - #print("calling method ", get_event_name(action), " action ", action) - actions[action].call_func() - return - -func event_cancel(): - get_node("/root/captains_log").finish() - get_tree().quit() - -func event_toggle_fullscreen(): - self.get_tree().set_input_as_handled() - if OS.is_window_fullscreen(): - OS.set_window_fullscreen(false) - get_viewport().set_size_override_stretch(false) - get_viewport().set_size_override(true, GAME_RESOLUTION, Vector2(0,0)) - else: - var screen_size = OS.get_screen_size() - var ratio = screen_size/GAME_RESOLUTION - ratio = ratio.floor() - var scaling = min(ratio.x, ratio.y) - var margin = screen_size/scaling - GAME_RESOLUTION - OS.set_window_fullscreen(true) - get_viewport().set_size_override_stretch(true) - get_viewport().set_size_override(true, GAME_RESOLUTION, margin/2.0) diff --git a/components/controller/hand.gd b/components/controller/hand.gd deleted file mode 100644 index 846eccf4..00000000 --- a/components/controller/hand.gd +++ /dev/null @@ -1,82 +0,0 @@ - -extends Node2D - -const CardSprite = preload("res://components/ui/card_sprite.gd") -const Action = preload("res://model/action.gd") - -var player = null -var focus - -func _ready(): - start() - -func start(): - set_process(true) - set_process_input(true) - show() - -func stop(): - hide() - set_process(false) - set_process_input(false) - player.disconnect("draw_card", self, "_on_player_draw") - player.disconnect("consumed_card", self, "_on_player_consume") - focus = null - for child in get_children(): - child.queue_free() - -func set_player(the_player): - start() - player = the_player - player.connect("draw_card", self, "_on_player_draw") - player.connect("consumed_card", self, "_on_player_consume") - for card in player.hand: - _on_player_draw(card) - -func _on_player_draw(card): - print("card added: ", card.get_name()) - var card_sprite = CardSprite.create(card) - add_child(card_sprite) - if focus == null: - focus = 0 - -func _on_player_consume(card): - focus = min(focus, get_child_count()-2) - if focus < 0: - focus = null - for card_sprite in get_children(): - if card_sprite.card == card: - card_sprite.queue_free() - -func next_card(): - if focus != null and get_child_count() > 0: - focus = (focus+1)%get_child_count() - -func prev_card(): - if focus != null and get_child_count() > 0: - focus = (focus-1+get_child_count())%get_child_count() - -func user_selected_card(): - var card = get_child(focus) - if not card.prepare_evocation(player): - return - -func _process(delta): - var n = get_child_count() - for i in range(n): - var card = get_child(i) - card.set_pos(Vector2(get_child_count()*40-40*i, 0)) - if i == focus: - card.select() - else: - card.deselect() - -func get_selected_cardsprite(): - if player.hand.size() <= 0: - return null; - return get_child(focus) - -func get_selected_card(): - if player.hand.size() <= 0: - return null; - return get_child(focus).card diff --git a/components/controller/main_controller.gd b/components/controller/main_controller.gd deleted file mode 100644 index 23f039dc..00000000 --- a/components/controller/main_controller.gd +++ /dev/null @@ -1,84 +0,0 @@ - -extends "res://components/controller/default_controller.gd" - -const Action = preload("res://model/action.gd") - -var player -var hand -var display_popup -var upgrades_popup - -func event_save(): - get_node("/root/captains_log/scene_manager").close_route() - -func event_idle(): - player.add_action(Action.Idle.new()) - -func event_up(): - move(Vector2(0, -1)) - -func event_down(): - move(Vector2(0, 1)) - -func event_right(): - move(Vector2(1, 0)) - -func event_left(): - move(Vector2(-1, 0)) - -func move(direction): - if player.is_ready(): - var map = get_node("../../map") - var target_pos = map.get_actor_body(player).pos + direction - var body = map.get_body_at(target_pos) - if body != null: - player.add_action(Action.MeleeAttack.new(body)) - else: - player.add_action(Action.Move.new(target_pos)) - -func set_player_map(player, hand): - self.player = player - self.hand = hand - display_popup = get_node("../CardDisplay") - upgrades_popup = get_node("../UpgradesDisplay") - -func event_next_sector(): - player.add_action(Action.ChangeSector.new(1)) - -func event_create_slime(): - var map = get_node("../../map") - var monsters = get_node("/root/captains_log/monsters").get_children() - monsters[randi()%monsters.size()].create(map, map.get_random_free_pos()) - -func event_display_card(): - if get_node("/root/sector/HUD/CardDisplay").is_hidden(): - self.disable() - self.display_popup.connect("close_popup", self, "restore_input") - self.display_popup.display(hand.get_selected_card()) - -func event_show_upgrades(): - if get_node("/root/sector/HUD/UpgradesDisplay").is_hidden(): - self.disable() - self.upgrades_popup.connect("close_popup", self, "restore_input") - self.upgrades_popup.display(player.upgrades) - -func event_focus_next(): - hand.next_card() - -func event_focus_prev(): - hand.prev_card() - -func event_select(): - if get_node("/root/sector/HUD/CardDisplay").is_hidden(): - hand.get_selected_cardsprite().connect("selecting_target", self, "block_input") - hand.get_selected_cardsprite().connect("target_selected", self, "restore_input") - hand.user_selected_card() - -func block_input(): - self.disable() - -func restore_input(): - # This is necessary in order to avoid a frame where multiple controllers - # become enabled at the same time. - yield(get_tree(), "fixed_frame") - self.enable() diff --git a/components/controller/popup_controller.gd b/components/controller/popup_controller.gd deleted file mode 100644 index ac6b78d6..00000000 --- a/components/controller/popup_controller.gd +++ /dev/null @@ -1,12 +0,0 @@ - -extends "res://components/controller/default_controller.gd" - -export(String) var path = ".." - -func consume_input_key(event): - if not enabled: - return - - if event.type == InputEvent.KEY: - get_node(path).hide() - self.disable() diff --git a/components/controller/select_controller.gd b/components/controller/select_controller.gd deleted file mode 100644 index 27e8b187..00000000 --- a/components/controller/select_controller.gd +++ /dev/null @@ -1,27 +0,0 @@ - -extends "res://components/controller/default_controller.gd" - -signal move_selection(direction) -signal confirm() -signal cancel() - -func event_up(): - emit_signal("move_selection", Vector2(0, -1)) - -func event_down(): - emit_signal("move_selection", Vector2(0, 1)) - -func event_right(): - emit_signal("move_selection", Vector2(1, 0)) - -func event_left(): - emit_signal("move_selection", Vector2(-1, 0)) - -func event_select(): - self.disable() - emit_signal("confirm") - -func event_cancel(): - self.disable() - emit_signal("cancel") - self.get_tree().set_input_as_handled() diff --git a/components/monster.tscn b/components/monster.tscn deleted file mode 100644 index 54dad0f3..00000000 --- a/components/monster.tscn +++ /dev/null @@ -1,15 +0,0 @@ -[gd_scene load_steps=2 format=1] - -[ext_resource path="res://model/monster/monster.gd" type="Script" id=1] - -[node name="monster" type="Node"] - -script/script = ExtResource( 1 ) -body_hp = 10 -body_type = "stone" - -[node name="cards" type="Node" parent="."] - -[node name="ai_modules" type="Node" parent="."] - - diff --git a/components/persist/profile_persist.gd b/components/persist/profile_persist.gd deleted file mode 100644 index d36c522e..00000000 --- a/components/persist/profile_persist.gd +++ /dev/null @@ -1,71 +0,0 @@ - -extends Node - -var profile_data - -func _init(): - profile_data = {} - var profile_stream = File.new() - if profile_stream.open("user://profile.meta", File.READ) == 0: - profile_data.parse_json(profile_stream.get_as_text()) - profile_stream.close() - else: - profile_data["saves"] = [] - -func find_free_route_id(): - var id = 1 - for route_id in profile_data["saves"]: - if route_id != id: - break - id += 1 - return id - -func get_journal_filename(route_id): - return "user://route-" + var2str(int(route_id)) + ".journal" - -func get_journal_file_reader(route_id): - var file = File.new() - var filename = get_journal_filename(route_id) - if file.open(filename, File.READ) == 0: - return file - else: - print("Could not open journal '", filename,"': ", file.get_error()) - -func get_journal_file_writer(route_id): - var file = File.new() - var filename = get_journal_filename(route_id) - if file.open(filename, File.WRITE) == 0: - return file - else: - print("Could not open journal '", filename,"': ", file.get_error()) - -func add_journal(route_id): - profile_data["saves"].append(route_id) - -func erase_journal(route_id): - var dir = Directory.new() - if dir.open("user://") == 0: - dir.remove(get_journal_filename(route_id)) - profile_data["saves"].erase(route_id) - -func get_journals(): - return profile_data["saves"] - -func get_player_name(route_id): - var file = get_journal_file_reader(route_id) - assert(file != null) - var data = {} - var text = file.get_as_text() - data.parse_json(text) - file.close() - var sector_data - for sector in data["sectors"]: - if sector["id"] == data["current_sector"]: - sector_data = sector - return sector_data["actors"][int(data["player_actor_id"])]["name"] - -func save(): - var file = File.new() - file.open("user://profile.meta", File.WRITE) - file.store_string(profile_data.to_json()) - file.close() \ No newline at end of file diff --git a/components/theme/pallete.txt b/components/theme/pallete.txt deleted file mode 100644 index f130f0fe..00000000 --- a/components/theme/pallete.txt +++ /dev/null @@ -1 +0,0 @@ -https://coolors.co/000000-2a1a1f-764134-ad8350-afa060 diff --git a/components/theme/wilokai.tres b/components/theme/wilokai.tres deleted file mode 100644 index 7c54d87e..00000000 --- a/components/theme/wilokai.tres +++ /dev/null @@ -1,53 +0,0 @@ -[gd_resource type="Theme" load_steps=7 format=1] - -[ext_resource path="res://assets/fonts/PressStart2P-8pt.fnt" type="BitmapFont" id=1] - -[sub_resource type="StyleBoxEmpty" id=1] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 - -[sub_resource type="StyleBoxEmpty" id=2] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 - -[sub_resource type="StyleBoxEmpty" id=3] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 - -[sub_resource type="StyleBoxEmpty" id=4] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 - -[sub_resource type="StyleBoxEmpty" id=5] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 - -[resource] - -Button/colors/font_color = Color( 0.678431, 0.513726, 0.313726, 1 ) -Button/colors/font_color_disabled = Color( 0.9, 0.9, 0.9, 0.2 ) -Button/colors/font_color_hover = Color( 0.941176, 0.941176, 0.941176, 1 ) -Button/colors/font_color_pressed = Color( 1, 1, 1, 1 ) -Button/constants/hseparation = 2 -Button/fonts/font = ExtResource( 1 ) -Button/styles/disabled = SubResource( 1 ) -Button/styles/focus = SubResource( 2 ) -Button/styles/hover = SubResource( 3 ) -Button/styles/normal = SubResource( 4 ) -Button/styles/pressed = SubResource( 5 ) - diff --git a/components/ui/card_display.gd b/components/ui/card_display.gd deleted file mode 100644 index 5a300aed..00000000 --- a/components/ui/card_display.gd +++ /dev/null @@ -1,29 +0,0 @@ -extends Control - -const CardSprite = preload("res://components/ui/card_sprite.gd") -const Actor = preload("res://model/actor.gd") - -onready var popup = get_node("CardPopup") - -signal close_popup - -func display(card): - self.get_node("CardPopup/CardName").set_text(card.get_name()) - var card_sprite = CardSprite.create(card) - - card_sprite.set_name("Card") - card_sprite.get_node("Name").hide() - self.get_node("CardPopup/SpriteHook/Card").free() - self.get_node("CardPopup/SpriteHook").add_child(card_sprite) - - self.get_node("CardPopup/DescriptionPanel/CardDescription").set_text(card.get_description()) - - popup.enable() - popup.show() - -func is_hidden(): - return popup.is_hidden() - -func hide(): - emit_signal("close_popup") - popup.hide() diff --git a/components/ui/card_display.tscn b/components/ui/card_display.tscn deleted file mode 100644 index 7cd634a9..00000000 --- a/components/ui/card_display.tscn +++ /dev/null @@ -1,110 +0,0 @@ -[gd_scene load_steps=6 format=1] - -[ext_resource path="res://components/ui/card_display.gd" type="Script" id=1] -[ext_resource path="res://components/controller/popup_controller.gd" type="Script" id=2] -[ext_resource path="res://components/ui/card_sprite.tscn" type="PackedScene" id=3] -[ext_resource path="res://assets/fonts/PressStart2P-16pt.fnt" type="Font" id=4] -[ext_resource path="res://assets/fonts/PressStart2P-8pt.fnt" type="Font" id=5] - -[node name="CardDisplay" type="Control"] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = true -focus/stop_mouse = false -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 0.0 -margin/top = 0.0 -margin/right = 0.0 -margin/bottom = 0.0 -script/script = ExtResource( 1 ) - -[node name="CardPopup" type="PopupDialog" parent="."] - -anchor/left = 3 -anchor/top = 3 -anchor/right = 3 -anchor/bottom = 3 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 200.0 -margin/top = 140.0 -margin/right = -200.0 -margin/bottom = -140.0 -popup/exclusive = true -script/script = ExtResource( 2 ) -path = ".." - -[node name="SpriteHook" type="Node2D" parent="CardPopup"] - -transform/pos = Vector2( 66.7894, 123.805 ) - -[node name="Card" parent="CardPopup/SpriteHook" instance=ExtResource( 3 )] - -transform/pos = Vector2( 12.0976, 25.8243 ) - -[node name="Name" parent="CardPopup/SpriteHook/Card"] - -visibility/visible = false -size_flags/vertical = 0 - -[node name="CardClass" parent="CardPopup/SpriteHook/Card"] - -size_flags/vertical = 0 - -[node name="CardName" type="Label" parent="CardPopup"] - -anchor/right = 1 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 16.0 -margin/top = 24.0 -margin/right = 16.0 -margin/bottom = 64.0 -custom_fonts/font = ExtResource( 4 ) -text = "Card Name" -align = 1 -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="DescriptionPanel" type="Panel" parent="CardPopup"] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 143.0 -margin/top = 80.0 -margin/right = 16.0 -margin/bottom = 16.0 - -[node name="CardDescription" type="Label" parent="CardPopup/DescriptionPanel"] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 5.0 -margin/top = 5.0 -margin/right = 5.0 -margin/bottom = 5.0 -custom_fonts/font = ExtResource( 5 ) -text = "Card Description" -autowrap = true -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - - -[editable path="CardPopup/SpriteHook/Card"] diff --git a/components/ui/card_list_item.tscn b/components/ui/card_list_item.tscn deleted file mode 100644 index be4b2a8b..00000000 --- a/components/ui/card_list_item.tscn +++ /dev/null @@ -1,61 +0,0 @@ -[gd_scene load_steps=3 format=1] - -[ext_resource path="res://assets/fonts/PressStart2P-8pt.fnt" type="Font" id=1] -[ext_resource path="res://components/ui/card_sprite.tscn" type="PackedScene" id=2] - -[node name="Item" type="Control"] - -margin/right = 294.0 -margin/bottom = 90.0 -rect/min_size = Vector2( 390, 90 ) -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 - -[node name="Panel" type="Panel" parent="."] - -anchor/right = 1 -anchor/bottom = 1 -margin/right = 50.0 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 - -[node name="CardName" type="Label" parent="."] - -anchor/right = 1 -margin/left = 83.0 -margin/top = 8.0 -margin/right = 10.0 -margin/bottom = 73.0 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -custom_fonts/font = ExtResource( 1 ) -text = "test" -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="CardHook" type="Control" parent="."] - -margin/left = 47.0 -margin/top = 44.0 -margin/right = 87.0 -margin/bottom = 84.0 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 - -[node name="Card" parent="CardHook" instance=ExtResource( 2 )] - -[node name="Name" parent="CardHook/Card"] - -visibility/visible = false - - -[editable path="CardHook/Card"] diff --git a/components/ui/card_sprite.gd b/components/ui/card_sprite.gd deleted file mode 100644 index 79be0759..00000000 --- a/components/ui/card_sprite.gd +++ /dev/null @@ -1,82 +0,0 @@ - -extends Node2D - -const CardScene = preload("res://components/ui/card_sprite.tscn") -const CARD_ATTRIBUTE = preload("res://model/cards/card_entity.gd").CARD_ATTRIBUTE -const Action = preload("res://model/action.gd") - -const Skill = preload("res://model/cards/card_skill.gd") -const Upgrade = preload("res://model/cards/card_upgrade.gd") -const Item = preload("res://model/cards/card_item.gd") - -const ANGLE = -atan2(1,2) -const BG_COLOR = "bg_color" -const FG_COLOR = "fg_color" - -const COLOR_DICT = { - CARD_ATTRIBUTE.ATHLETICS: { - BG_COLOR: Color("3d0b0b"), - FG_COLOR: Color("d95763") - }, - CARD_ATTRIBUTE.ARCANE: { - BG_COLOR: Color("000e36"), - FG_COLOR: Color("6277ff") - }, - CARD_ATTRIBUTE.TECH: { - BG_COLOR: Color("133b0b"), - FG_COLOR: Color("7fd95b") - } -} - -var card -var used = false - -signal selecting_target() -signal target_selected() - -static func create(card): - var card_sprite = CardScene.instance() - card_sprite.card = card - print("name=", card) - card_sprite.get_node("Name").set_text(card.get_name()) - card_sprite.get_node("Background").set_modulate(COLOR_DICT[card.card_ref.get_card_attribute()][BG_COLOR]) - card_sprite.get_node("Subborder").set_modulate(COLOR_DICT[card.card_ref.get_card_attribute()][FG_COLOR]) - card_sprite.get_node("CardClass").set_text(get_card_class(card.card_ref)) - card_sprite.used = false - return card_sprite - -func get_card_class(card): - if card extends Skill: - return "Skill" - elif card extends Upgrade: - return "Upgrade" - elif card extends Item: - return "Item" - return "" - -func prepare_evocation(player): - if used: - return - var action = Action.EvokeCard.new(card) - for option in self.card.get_ref().get_options(player): - if option["type"] == "TARGET": - var cursor = get_node("/root/sector/map/floors/cursor") - if cursor.select(option["check"], option["aoe"]): - emit_signal("selecting_target") - yield(cursor, "target_chosen") - emit_signal("target_selected") - if cursor.get_target() == null: - return false - action.add_option(cursor.get_target()) - else: - return false - player.add_action(action) - used = true - return true - -func select(): - set_pos(Vector2(get_pos().x, -32)) - get_node("Name").show() - -func deselect(): - get_node("Name").hide() diff --git a/components/ui/card_sprite.tscn b/components/ui/card_sprite.tscn deleted file mode 100644 index cd929fd2..00000000 --- a/components/ui/card_sprite.tscn +++ /dev/null @@ -1,91 +0,0 @@ -[gd_scene load_steps=8 format=1] - -[ext_resource path="res://components/ui/card_sprite.gd" type="Script" id=1] -[ext_resource path="res://assets/card.tex" type="Texture" id=2] -[ext_resource path="res://assets/fonts/PressStart2P-8pt.fnt" type="Font" id=3] - -[sub_resource type="AtlasTexture" id=1] - -atlas = ExtResource( 2 ) -region = Rect2( 0, 0, 64, 80 ) -margin = Rect2( 0, 0, 0, 0 ) - -[sub_resource type="AtlasTexture" id=2] - -atlas = ExtResource( 2 ) -region = Rect2( 96, 0, 96, 128 ) -margin = Rect2( 0, 0, 0, 0 ) - -[sub_resource type="AtlasTexture" id=3] - -atlas = ExtResource( 2 ) -region = Rect2( 64, 0, 64, 80 ) -margin = Rect2( 0, 0, 0, 0 ) - -[sub_resource type="AtlasTexture" id=4] - -atlas = ExtResource( 2 ) -region = Rect2( 128, 0, 64, 80 ) -margin = Rect2( 0, 0, 0, 0 ) - -[node name="Card" type="Node2D"] - -script/script = ExtResource( 1 ) -__meta__ = { "__editor_plugin_screen__":"Script" } - -[node name="Background" type="Sprite" parent="."] - -texture = SubResource( 1 ) -modulate = Color( 0, 0.054902, 0.211765, 1 ) - -[node name="Name" type="Label" parent="."] - -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = -64.0 -margin/top = -64.0 -margin/right = 64.0 -margin/bottom = -32.0 -custom_fonts/font = ExtResource( 3 ) -text = "Card Name" -align = 1 -valign = 1 -autowrap = true -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="Art" type="Sprite" parent="."] - -visibility/visible = false -texture = SubResource( 2 ) -modulate = Color( 0.11206, 0.113228, 0.0773466, 1 ) - -[node name="Subborder" type="Sprite" parent="."] - -texture = SubResource( 3 ) -modulate = Color( 0.388235, 0.470588, 1, 1 ) - -[node name="Border" type="Sprite" parent="."] - -texture = SubResource( 4 ) - -[node name="CardClass" type="Label" parent="."] - -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = -24.0 -margin/top = 22.0 -margin/right = 23.0 -margin/bottom = 35.0 -custom_fonts/font = ExtResource( 3 ) -align = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - - diff --git a/components/ui/cursor.gd b/components/ui/cursor.gd deleted file mode 100644 index dd695c5c..00000000 --- a/components/ui/cursor.gd +++ /dev/null @@ -1,144 +0,0 @@ - -extends Sprite - -const HighlightMap = preload("res://components/ui/highlight_map.gd") - -#FIXME: move to separate script -class Queue: - var values - var tail - var head - func _init(): - values = [] - tail = 0 - head = 0 - values.resize(512) - func is_empty(): - return tail == head - func push(value): - values[tail] = value - tail = (tail+1)%values.size() - func pop(): - var value = values[head] - head = (head+1)%values.size() - return value - -const DIRS = [ - Vector2(0,-1), #UP - Vector2(1,0), #RIGHT - Vector2(0,1), #DOWN - Vector2(-1,0) #LEFT -] - -var map_ -var origin -var target_ -var check_ -var aoe_ -var range_ - -signal target_chosen() - -func load_range_(): - range_ = {} - for tile in map_.get_node("floors").get_used_cells(): - if check_.call_func(map_.get_parent().player, tile): - range_[tile] = true - -func select(check, area): - #FIXME: keep reference in attribute, also rename it to lowercase - get_node("Controller").connect("move_selection", self, "move_to") - get_node("Controller").connect("confirm", self, "confirm") - get_node("Controller").connect("cancel", self, "cancel") - get_node("Controller").enable() - aoe_ = area - target_ = null - map_ = get_node("/root/sector/map") - var main = get_node("/root/sector") - check_ = check - load_range_() - origin = main.player.get_body().pos - for dir in DIRS: - if move_to(dir): - break - if target_ == null: - if check_.call_func(map_.get_parent().player, origin): - target_ = origin - else: - return false - set_process(true) - show() - return true - -func disable(): - var main = get_node("/root/sector") - hide() - get_node("Controller").disable() - map_.get_node("highlights").clear() - set_process(false) - emit_signal("target_chosen") - -func confirm(): - disable() - -func cancel(): - target_ = null - disable() - -func get_target(): - return target_ - -func inside(pos, dir): - var relative = pos - origin - var plus = relative.dot(dir) - var reach = abs(plus) - var p = relative.dot(Vector2(dir.y, dir.x)) - var q = relative.dot(dir) - return q >= 0 and q <= 16 and p < reach and p > -reach - -func move_to(dir): - # bfs - var found = false - var queue = Queue.new() - var checked = {} - checked[origin] = true - queue.push(origin+dir) - while not queue.is_empty(): - var next = queue.pop() - checked[next] = true - # Choose next as target if it is valid - if range_.has(next): - target_ = next - found = true - break - # If not, expand the search - for next_dir in DIRS: - var candidate = next + next_dir - if not checked.has(candidate) and inside(candidate, dir): - queue.push(candidate) - if target_ != null: - origin = target_ - return found - -func _process(delta): - if target_ != null: - # update cursor position - var floors = map_.get_node("floors") - set_pos(floors.map_to_world(target_)) - # update highlight - var hls = map_.get_node("highlights") - hls.clear() - for tile in range_: - hls.add_tile(tile, HighlightMap.RANGE) - if aoe_ != null: - var format - var center - if typeof(aoe_.format) != TYPE_ARRAY: - format = aoe_.format.call_func(map_.get_parent().player, target_) - else: - format = aoe_.format - if typeof(aoe_.center) != TYPE_VECTOR2: - center = aoe_.center.call_func(map_.get_parent().player, target_) - else: - center = aoe_.center - hls.add_area(target_, format, center, HighlightMap.AOE) diff --git a/components/ui/cursor.tscn b/components/ui/cursor.tscn deleted file mode 100644 index c3eb81f8..00000000 --- a/components/ui/cursor.tscn +++ /dev/null @@ -1,22 +0,0 @@ -[gd_scene load_steps=4 format=1] - -[ext_resource path="res://assets/cursor.tex" type="Texture" id=1] -[ext_resource path="res://components/ui/cursor.gd" type="Script" id=2] -[ext_resource path="res://components/controller/select_controller.gd" type="Script" id=3] - -[node name="cursor" type="Sprite"] - -visibility/visible = false -transform/pos = Vector2( 32, 416 ) -texture = ExtResource( 1 ) -script/script = ExtResource( 2 ) - -[node name="Controller" type="Control" parent="."] - -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -script/script = ExtResource( 3 ) - - diff --git a/components/ui/highlight_map.gd b/components/ui/highlight_map.gd deleted file mode 100644 index f0eb5840..00000000 --- a/components/ui/highlight_map.gd +++ /dev/null @@ -1,18 +0,0 @@ - -extends TileMap - -const RANGE = 0 -const AOE = 1 - -func _ready(): - pass - -func add_tile(pos, id): - set_cellv(pos, id) - -func add_area(pos, format, offset, id): - for i in range(format.size()): - for j in range(format[i].size()): - if format[i][j] > 0: - var p = pos - offset + Vector2(j,i) - set_cellv(p, id) diff --git a/components/ui/item_stats.gd b/components/ui/item_stats.gd deleted file mode 100644 index ca931b3b..00000000 --- a/components/ui/item_stats.gd +++ /dev/null @@ -1,18 +0,0 @@ - -extends Control - -const ItemCard = preload("res://model/cards/card_item.gd") - -onready var slots = [ - get_node("weapon"), - get_node("suit"), - get_node("accessory") -] - -func change_item(item, slot): - var item_name = "none" - if item != null: - item_name = item.get_name() - var slot_item = slots[slot] - slot_item.set_text(slot_item.get_name() + ": " + item_name) - #printt("##################### change item") diff --git a/components/ui/item_stats.tscn b/components/ui/item_stats.tscn deleted file mode 100644 index fc852811..00000000 --- a/components/ui/item_stats.tscn +++ /dev/null @@ -1,66 +0,0 @@ -[gd_scene load_steps=3 format=1] - -[ext_resource path="res://components/ui/item_stats.gd" type="Script" id=1] -[ext_resource path="res://assets/fonts/PressStart2P-8pt.fnt" type="Font" id=2] - -[node name="item_stats" type="Control"] - -anchor/left = 1 -anchor/right = 1 -margin/left = 192.0 -margin/top = 16.0 -margin/right = 16.0 -margin/bottom = 96.0 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -script/script = ExtResource( 1 ) - -[node name="weapon" type="Label" parent="."] - -anchor/right = 1 -margin/bottom = 32.0 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -custom_fonts/font = ExtResource( 2 ) -text = " weapon: none" -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="suit" type="Label" parent="."] - -anchor/right = 1 -margin/left = -1.0 -margin/top = 21.0 -margin/right = 1.0 -margin/bottom = 53.0 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -custom_fonts/font = ExtResource( 2 ) -text = " suit: none" -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="accessory" type="Label" parent="."] - -anchor/right = 1 -margin/top = 43.0 -margin/bottom = 75.0 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -custom_fonts/font = ExtResource( 2 ) -text = "accessory: none" -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - - diff --git a/components/ui/save-button.tscn b/components/ui/save-button.tscn deleted file mode 100644 index efa37d25..00000000 --- a/components/ui/save-button.tscn +++ /dev/null @@ -1,17 +0,0 @@ -[gd_scene load_steps=2 format=1] - -[ext_resource path="res://assets/fonts/PressStart2P-small.fnt" type="Font" id=1] - -[node name="menu_button" type="Button"] - -margin/right = 12.0 -margin/bottom = 19.0 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -custom_fonts/font = ExtResource( 1 ) -toggle_mode = false -flat = false - - diff --git a/components/ui/upgrade_display.gd b/components/ui/upgrade_display.gd deleted file mode 100644 index aeacb86d..00000000 --- a/components/ui/upgrade_display.gd +++ /dev/null @@ -1,38 +0,0 @@ -extends Control - -const CardSprite = preload("res://components/ui/card_sprite.gd") -const CardItem = preload("res://components/ui/card_list_item.tscn") - -signal close_popup - -func clear(): - for child in self.get_node("UpgradeDialog/Panel/UpgradeList").get_children(): - child.free() - -func display(upgrades): - self.clear() - - self.get_node("UpgradeDialog").show_modal(true) - - self.get_node("UpgradeDialog").enable() - - for upg in upgrades: - var item = CardItem.instance() - item.get_node("CardHook/Card").free() - - var card_sprite = CardSprite.create(upg) - card_sprite.set_name("Card") - - item.get_node("CardHook").add_child(card_sprite) - item.get_node("CardName").set_text(card_sprite.get_node("Name").get_text()) - card_sprite.get_node("Name").free() - - self.get_node("UpgradeDialog/Panel/UpgradeList").add_child(item) - - -func is_hidden(): - return self.get_node("UpgradeDialog").is_hidden() - -func hide(): - emit_signal("close_popup") - self.get_node("UpgradeDialog").hide() diff --git a/components/ui/upgrade_display.tscn b/components/ui/upgrade_display.tscn deleted file mode 100644 index bf13dfbb..00000000 --- a/components/ui/upgrade_display.tscn +++ /dev/null @@ -1,86 +0,0 @@ -[gd_scene load_steps=4 format=1] - -[ext_resource path="res://components/ui/upgrade_display.gd" type="Script" id=1] -[ext_resource path="res://components/controller/popup_controller.gd" type="Script" id=2] -[ext_resource path="res://assets/fonts/PressStart2P-16pt.fnt" type="Font" id=3] - -[node name="upgrade_display" type="Control"] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = true -focus/stop_mouse = false -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 0.0 -margin/top = 0.0 -margin/right = 0.0 -margin/bottom = 0.0 -script/script = ExtResource( 1 ) - -[node name="UpgradeDialog" type="PopupDialog" parent="."] - -anchor/left = 3 -anchor/top = 3 -anchor/right = 3 -anchor/bottom = 3 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 200.0 -margin/top = 160.0 -margin/right = -200.0 -margin/bottom = -160.0 -popup/exclusive = true -script/script = ExtResource( 2 ) -path = ".." - -[node name="Panel" type="ScrollContainer" parent="UpgradeDialog"] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 16.0 -margin/top = 90.0 -margin/right = 16.0 -margin/bottom = 16.0 -scroll/horizontal = false -scroll/vertical = true - -[node name="UpgradeList" type="VBoxContainer" parent="UpgradeDialog/Panel"] - -focus/ignore_mouse = false -focus/stop_mouse = false -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 0.0 -margin/top = 0.0 -margin/right = 0.0 -margin/bottom = 0.0 -custom_constants/separation = 10 -alignment = 0 - -[node name="Label" type="Label" parent="UpgradeDialog"] - -anchor/right = 1 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 16.0 -margin/top = 16.0 -margin/right = 16.0 -margin/bottom = 90.0 -custom_fonts/font = ExtResource( 3 ) -text = "Equiped Upgrade Cards" -align = 1 -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - - diff --git a/components/util/card_amount.gd b/components/util/card_amount.gd deleted file mode 100644 index 0d3efde0..00000000 --- a/components/util/card_amount.gd +++ /dev/null @@ -1,9 +0,0 @@ - -extends Node - -export(int, 1, 4) var amount - -func _ready(): - pass - - diff --git a/components/util/card_amount.tscn b/components/util/card_amount.tscn deleted file mode 100644 index f00f2259..00000000 --- a/components/util/card_amount.tscn +++ /dev/null @@ -1,10 +0,0 @@ -[gd_scene load_steps=2 format=1] - -[ext_resource path="res://components/util/card_amount.gd" type="Script" id=1] - -[node name="card_amount" type="Node"] - -script/script = ExtResource( 1 ) -amount = null - - diff --git a/components/util/card_list.gd b/components/util/card_list.gd deleted file mode 100644 index bdbfd7ad..00000000 --- a/components/util/card_list.gd +++ /dev/null @@ -1,11 +0,0 @@ -tool -extends Node - -const Card = preload("res://model/cards/card_entity.gd") - -func get_card_id(card): - assert(card extends Card) - for i in range(get_child_count()): - if card == get_child(i): - return i - assert(false) diff --git a/components/util/damage_formula.gd b/components/util/damage_formula.gd deleted file mode 100644 index 5a7055ef..00000000 --- a/components/util/damage_formula.gd +++ /dev/null @@ -1,5 +0,0 @@ -func dice(dcount, dfaces): - var value = 0 - for i in range(dcount): - value += 1 + int(rand_range(0, dfaces)) - return value diff --git a/components/util/highlight_tileset.tres b/components/util/highlight_tileset.tres deleted file mode 100644 index f1f41bd9..00000000 --- a/components/util/highlight_tileset.tres +++ /dev/null @@ -1,43 +0,0 @@ -[gd_resource type="TileSet" load_steps=6 format=1] - -[ext_resource path="res://assets/floor-base.tex" type="Texture" id=1] - -[sub_resource type="CanvasItemShader" id=3] - -_code = { "fragment":"\nCOLOR = tex(TEXTURE, UV)*color(0.3, 0.8, 0.3, 0.3);\n", "fragment_ofs":0, "light":"", "light_ofs":0, "vertex":"", "vertex_ofs":0 } - -[sub_resource type="CanvasItemMaterial" id=4] - -shader/shader = SubResource( 3 ) -shader/shading_mode = 0 - -[sub_resource type="CanvasItemShader" id=1] - -_code = { "fragment":"\nCOLOR = tex(TEXTURE, UV)*color(0.7, 0.125, 0.05, 0.3);\n", "fragment_ofs":0, "light":"", "light_ofs":0, "vertex":"", "vertex_ofs":0 } - -[sub_resource type="CanvasItemMaterial" id=2] - -shader/shader = SubResource( 1 ) -shader/shading_mode = 0 - -[resource] - -0/name = "Range" -0/texture = ExtResource( 1 ) -0/tex_offset = Vector2( 0, 0 ) -0/material = SubResource( 4 ) -0/region = Rect2( 0, 0, 0, 0 ) -0/occluder_offset = Vector2( 0, 0 ) -0/navigation_offset = Vector2( 0, 0 ) -0/shape_offset = Vector2( 0, 0 ) -0/shapes = [ ] -1/name = "AoE" -1/texture = ExtResource( 1 ) -1/tex_offset = Vector2( 0, 0 ) -1/material = SubResource( 2 ) -1/region = Rect2( 0, 0, 0, 0 ) -1/occluder_offset = Vector2( 0, 0 ) -1/navigation_offset = Vector2( 0, 0 ) -1/shape_offset = Vector2( 0, 0 ) -1/shapes = [ ] - diff --git a/components/util/highlight_tileset.tscn b/components/util/highlight_tileset.tscn deleted file mode 100644 index a263e392..00000000 --- a/components/util/highlight_tileset.tscn +++ /dev/null @@ -1,40 +0,0 @@ -[gd_scene load_steps=6 format=1] - -[ext_resource path="res://assets/floor-base.tex" type="Texture" id=1] - -[sub_resource type="CanvasItemShader" id=3] - -_code = { "fragment":"\nCOLOR = tex(TEXTURE, UV)*color(0.3, 0.8, 0.3, 0.3);\n", "fragment_ofs":0, "light":"", "light_ofs":0, "vertex":"", "vertex_ofs":0 } - -[sub_resource type="CanvasItemMaterial" id=4] - -shader/shader = SubResource( 3 ) -shader/shading_mode = 0 - -[sub_resource type="CanvasItemShader" id=1] - -_code = { "fragment":"\nCOLOR = tex(TEXTURE, UV)*color(0.7, 0.125, 0.05, 0.3);\n", "fragment_ofs":0, "light":"", "light_ofs":0, "vertex":"", "vertex_ofs":0 } - -[sub_resource type="CanvasItemMaterial" id=2] - -shader/shader = SubResource( 1 ) -shader/shading_mode = 0 - -[node name="sbrubles" type="Node2D"] - -[node name="Range" type="Sprite" parent="."] - -material/material = SubResource( 4 ) -transform/pos = Vector2( 85.9171, -2.16144 ) -texture = ExtResource( 1 ) -centered = false -modulate = Color( 1, 1, 1, 0.392157 ) - -[node name="AoE" type="Sprite" parent="."] - -material/material = SubResource( 2 ) -texture = ExtResource( 1 ) -centered = false -modulate = Color( 1, 1, 1, 0.392157 ) - - diff --git a/components/util/id_server.gd b/components/util/id_server.gd deleted file mode 100644 index b8c92245..00000000 --- a/components/util/id_server.gd +++ /dev/null @@ -1,11 +0,0 @@ - -extends Node - -var next = 0 - -func _ready(): - pass - -func generate_id(): - next += 1 - return next diff --git a/components/util/mapgen/map_generator.gd b/components/util/mapgen/map_generator.gd deleted file mode 100644 index 29f71f96..00000000 --- a/components/util/mapgen/map_generator.gd +++ /dev/null @@ -1,34 +0,0 @@ - -extends Node - -const MapGrid = preload("res://components/util/mapgen/map_grid.gd") -const Step = preload("res://components/util/mapgen/step.gd") -const RandomStep = preload("res://components/util/mapgen/steps/add_random_wall.gd") -const RoomStep = preload("res://components/util/mapgen/steps/carve_rooms.gd") -const PatternStep = preload("res://components/util/mapgen/steps/pattern_filter.gd") -const MarginStep = preload("res://components/util/mapgen/steps/add_margin.gd") - -var RANDOM_STEP = RandomStep.new() -var ROOM_STEP = RoomStep.new() -var GROW_STEP = PatternStep.load_from_file("growing") -var CLEAN_STEP = PatternStep.load_from_file("cleaning") -var BORDER_STEP = PatternStep.load_from_file("border") -var MARGIN_STEP = MarginStep.new() - -var PIPELINE = [ - ROOM_STEP, - #RANDOM_STEP, RANDOM_STEP, RANDOM_STEP, - #GROW_STEP, GROW_STEP, GROW_STEP, - MARGIN_STEP, - CLEAN_STEP, #CLEAN_STEP, CLEAN_STEP, - BORDER_STEP -] - -func _ready(): - pass - -func generate_map(id,w,h): - var map = MapGrid.new(w,h, Step.WALL) - for step in PIPELINE: - map = map.apply_step(step) - return map.export_scene(id) diff --git a/components/util/mapgen/map_grid.gd b/components/util/mapgen/map_grid.gd deleted file mode 100644 index bff2b881..00000000 --- a/components/util/mapgen/map_grid.gd +++ /dev/null @@ -1,42 +0,0 @@ - -const Step = preload("res://components/util/mapgen/step.gd") - -const MapScene = preload("res://scenes/map.gd") - -var map_ = [] -var width_ = 0 -var height_ = 0 - -func _init(w, h, value): - width_ = w - height_ = h - map_.resize(h) - for i in range(h): - map_[i] = [] - map_[i].resize(w) - for j in range(w): - map_[i][j] = value - -static func clone(map, w, h): - var map_grid = new(w, h, Step.EMPTY) - for i in range(h): - for j in range(w): - map_grid.set_tile(i, j, map[i][j]) - return map_grid - -func set_tile(i, j, value): - map_[i][j] = value - -func apply_step(step): - return step.apply(map_, width_, height_) - -func export_scene(id): - var map_node = MapScene.create(id, width_, height_) - var floors = map_node.get_node("floors") - var walls = map_node.get_node("walls") - for i in range(width_): - for j in range(height_): - walls.set_cell(j, i, map_[i][j]) - if map_[i][j] != Step.WALL: - floors.set_cell(j, i, 0) - return map_node diff --git a/components/util/mapgen/patterns/border.json b/components/util/mapgen/patterns/border.json deleted file mode 100644 index 2583d817..00000000 --- a/components/util/mapgen/patterns/border.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "top": { - "patterns": [ - [ - [ -3, -2, -3 ], - [ -3, -1, -3 ], - [ -2, 1, -2 ] - ] - ], - "value": 2 - }, - "top-right": { - "patterns": [ - [ - [ -2, -3, -2 ], - [ -3, -1, -3 ], - [ 1, -3, -2 ] - ] - ], - "value": 3 - }, - "right": { - "patterns": [ - [ - [ -2, -3, -3 ], - [ 1, -1, -2 ], - [ -2, -3, -3 ] - ] - ], - "value": 4 - }, - "bottom-right": { - "patterns": [ - [ - [ 1, -3, -2 ], - [ -3, -1, -3 ], - [ -2, -3, -2 ] - ] - ], - "value": 5 - }, - "bottom": { - "patterns": [ - [ - [ -2, 1, -2 ], - [ -3, -1, -3 ], - [ -3, -2, -3 ] - ] - ], - "value": 6 - }, - "bottom-left": { - "patterns": [ - [ - [ -2, -3, 1 ], - [ -3, -1, -3 ], - [ -2, -3, -2 ] - ] - ], - "value": 7 - }, - "left": { - "patterns": [ - [ - [ -3, -3, -2 ], - [ -2, -1, 1 ], - [ -3, -3, -2 ] - ] - ], - "value": 8 - }, - "top-left": { - "patterns": [ - [ - [ -2, -3, -2 ], - [ -3, -1, -3 ], - [ -2, -3, 1 ] - ] - ], - "value": 9 - }, - "corner-top-right": { - "patterns": [ - [ - [ -2, -2, -2 ], - [ 1, -1, -2 ], - [ -2, 1, -2 ] - ] - ], - "value": 10 - }, - "corner-bottom-right": { - "patterns": [ - [ - [ -2, 1, -2 ], - [ 1, -1, -2 ], - [ -2, -2, -2 ] - ] - ], - "value": 11 - }, - "corner-bottom-left": { - "patterns": [ - [ - [ -2, 1, -2 ], - [ -2, -1, 1 ], - [ -2, -2, -2 ] - ] - ], - "value": 12 - }, - "corner-top-left": { - "patterns": [ - [ - [ -2, -2, -2 ], - [ -2, -1, 1 ], - [ -2, 1, -2 ] - ] - ], - "value": 13 - } -} diff --git a/components/util/mapgen/patterns/cleaning.json b/components/util/mapgen/patterns/cleaning.json deleted file mode 100644 index b4091fdd..00000000 --- a/components/util/mapgen/patterns/cleaning.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "foo": { - "patterns": [ - [ - [ -2, 1, -2 ], - [ 1, -1, 1 ], - [ -2, -2, -2 ] - ], - [ - [ -2, -2, -2 ], - [ 1, -1, 1 ], - [ -2, 1, -2 ] - ], - [ - [ -2, 1, -2 ], - [ 1, -1, -2 ], - [ -2, 1, -2 ] - ], - [ - [ -2, 1, -2 ], - [ -2, -1, 1 ], - [ -2, 1, -2 ] - ], - [ - [ -2, 1, -2 ], - [ -2, -1, -2 ], - [ -2, 1, -2 ] - ], - [ - [ -2, -2, -2 ], - [ 1, -1, 1 ], - [ -2, -2, -2 ] - ], - [ - [ -2, -2, 1 ], - [ 1, -1, -2 ], - [ -2, 1, -2 ] - ], - [ - [ -2, 1, -2 ], - [ -2, -1, 1 ], - [ 1, -2, -2 ] - ], - [ - [ -2, 1, -2 ], - [ 1, -1, -2 ], - [ -2, -2, 1 ] - ], - [ - [ 1, -2, -2 ], - [ -2, -1, 1 ], - [ -2, 1, -2 ] - ] - ], - "value": 1 - } -} diff --git a/components/util/mapgen/patterns/growing.json b/components/util/mapgen/patterns/growing.json deleted file mode 100644 index 7fae91be..00000000 --- a/components/util/mapgen/patterns/growing.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "foo": { - "patterns": [ - [ - [ -1, -1, -1 ], - [ -1, -1, -1 ], - [ -2, 1, -2 ] - ], - [ - [ -2, -1, -1 ], - [ 1, -1, -1 ], - [ -2, -1, -1 ] - ], - [ - [ -2, 1, -2 ], - [ -1, -1, -1 ], - [ -1, -1, -1 ] - ], - [ - [ -1, -1, -2 ], - [ -1, -1, 1 ], - [ -1, -1, -2 ] - ], - [ - [ -1, -1, -2 ], - [ -1, -1, 1 ], - [ -2, 1, 1 ] - ], - [ - [ 1, 1, -2 ], - [ 1, -1, -1 ], - [ -2, -1, -1 ] - ], - [ - [ -2, -1, -1 ], - [ 1, -1, -1 ], - [ 1, 1, -2 ] - ], - [ - [ -2, 1, 1 ], - [ -1, -1, 1 ], - [ -1, -1, -2 ] - ] - ], - "value": 1 - } -} diff --git a/components/util/mapgen/step.gd b/components/util/mapgen/step.gd deleted file mode 100644 index 9aa3245a..00000000 --- a/components/util/mapgen/step.gd +++ /dev/null @@ -1,24 +0,0 @@ - -const MapGrid = preload("res://components/util/mapgen/map_grid.gd") - -const EMPTY = -1 -const FLOOR = 0 -const WALL = 1 -const WALL_TOP = 2 -const WALL_TOP_RIGHT = 3 -const WALL_RIGHT = 4 -const WALL_BOTTOM_RIGHT = 5 -const WALL_BOTTOM = 6 -const WALL_BOTTOM_LEFT = 7 -const WALL_LEFT = 8 -const WALL_TOP_LEFT = 9 -const WALL_CORNER_TOP_RIGHT = 10 -const WALL_CORNER_BOTTOM_RIGHT = 11 -const WALL_CORNER_BOTTOM_LEFT = 12 -const WALL_CORNER_TOP_LEFT = 13 -const ANY = -2 -const ANY_BUT_WALL = -3 - -# Override this method to produce a MapGrid from the given map -func apply(map, w, h): - assert(false) diff --git a/components/util/mapgen/steps/add_margin.gd b/components/util/mapgen/steps/add_margin.gd deleted file mode 100644 index 643a7a0f..00000000 --- a/components/util/mapgen/steps/add_margin.gd +++ /dev/null @@ -1,13 +0,0 @@ - -extends "res://components/util/mapgen/step.gd" - -const MARGIN = 16 - -func apply(map, w, h): - var nw = w + 2*MARGIN - var nh = h + 2*MARGIN - var map_grid = MapGrid.new(nw, nh, WALL) - for i in range(h): - for j in range(w): - map_grid.set_tile(MARGIN + 1 + i, MARGIN + 1 + j, map[i][j]) - return map_grid diff --git a/components/util/mapgen/steps/add_random_wall.gd b/components/util/mapgen/steps/add_random_wall.gd deleted file mode 100644 index 90e266b3..00000000 --- a/components/util/mapgen/steps/add_random_wall.gd +++ /dev/null @@ -1,11 +0,0 @@ - -extends "res://components/util/mapgen/step.gd" - -func apply(map, w, h): - var map_grid = MapGrid.clone(map, w, h) - var i = 1 + int(randf() * (h-1)) - var j = 1 + int(randf() * (w-1)) - map_grid.set_tile(i, j, WALL) - print("Adding random wall tile:") - print("[ " + str(i) + ", " + str(j) + " ]") - return map_grid diff --git a/components/util/mapgen/steps/carve_rooms.gd b/components/util/mapgen/steps/carve_rooms.gd deleted file mode 100644 index f0da950c..00000000 --- a/components/util/mapgen/steps/carve_rooms.gd +++ /dev/null @@ -1,65 +0,0 @@ - -extends "res://components/util/mapgen/step.gd" - -class Room: - var i_ - var j_ - var w_ - var h_ - func _init(i, j, w, h): - i_ = i - j_ = j - w_ = w - h_ = h - func left(): - return j_ - func top(): - return i_ - func right(): - return j_+w_ - func bottom(): - return i_+h_ - func intersects(other): - if right() < other.left(): - return false - if bottom() < other.top(): - return false - if left() > other.right(): - return false - if top() > other.bottom(): - return false - return true - func put_in(map_grid): - for i in range(h_): - for j in range(w_): - map_grid.set_tile(i_ + i, j_ + j, EMPTY) - -var rooms_ - -func new_room(w, h): - var rw = 6 + 2*(randi()%6) - var rh = 6 + 2*(randi()%6) - var i = 1 + 2*(randi()%((h-rh)/2)) - var j = 1 + 2*(randi()%((w-rw)/2)) - var room = Room.new(i, j, rw, rh) - #for other in rooms_: - # if room.intersects(other): - # return - rooms_.push_back(room) - -func place_rooms(map_grid): - for room in rooms_: - room.put_in(map_grid) - -# 1468518527 - -func apply(map, w, h): - var rngseed = OS.get_unix_time() - printt("RNG SEED:", rngseed) - seed(rngseed) - var map_grid = MapGrid.clone(map, w, h) - rooms_ = [] - for n in range(30): - new_room(w, h) - place_rooms(map_grid) - return map_grid diff --git a/components/util/mapgen/steps/pattern_filter.gd b/components/util/mapgen/steps/pattern_filter.gd deleted file mode 100644 index 6b70b95c..00000000 --- a/components/util/mapgen/steps/pattern_filter.gd +++ /dev/null @@ -1,76 +0,0 @@ - -extends "res://components/util/mapgen/step.gd" - -const THREAD_NUM = 4 - -const WINDOW = [ - Vector2(-1,-1), - Vector2(-1,0), - Vector2(-1,1), - Vector2(0,-1), - Vector2(0,0), - Vector2(0,1), - Vector2(1,-1), - Vector2(1,0), - Vector2(1,1) -] - -var rules_ -var sem_ - -func _init(dict): - rules_ = dict - -static func load_from_file(pattern_name): - var dict = {} - var file = File.new() - var text = "" - file.open("res://components/util/mapgen/patterns/" + pattern_name + ".json", File.READ) - text = file.get_as_text() - dict.parse_json(text) - file.close() - return new(dict) - -func apply_part(args): - var map = args[0] - var grid = args[1] - var j0 = args[2] - var i0 = args[3] - var w = args[4] - var h = args[5] - printt("thread", args[2], args[3], args[2] + args[4] - 1, args[3] + args[5] - 1) - for i in range(i0, i0+h): - for j in range(j0, j0+w): - #sem_.wait() - grid.set_tile(i, j, map[i][j]) - #sem_.post() - for key in rules_: - var patterns = rules_[key].patterns - var value = rules_[key].value - for pattern in patterns: - var change = true - for d in WINDOW: - var tile = map[i+d.y][j+d.x] - var pat = pattern[d.y+1][d.x+1] - if not ( pat == ANY or tile == pat or (pat == ANY_BUT_WALL and tile != WALL) ): - change = false - break - if change: - grid.set_tile(i, j, value) - break - -# override -func apply(map, w, h): - var map_grid = MapGrid.new(w, h, EMPTY) - var threads = range(THREAD_NUM) - for i in range(THREAD_NUM): - threads[i] = Thread.new() - sem_ = Semaphore.new() - sem_.post() - for i in range(THREAD_NUM-1): - threads[i].start(self, "apply_part", [map, map_grid, 1, 1+i*h/THREAD_NUM, w-2, h/THREAD_NUM]) - threads[THREAD_NUM-1].start(self, "apply_part", [map, map_grid, 1, 1+(THREAD_NUM-1)*h/THREAD_NUM, w-2, h/THREAD_NUM-2]) - for i in range(THREAD_NUM): - threads[i].wait_to_finish() - sem_ = null - return map_grid diff --git a/components/util/parallax_background.gd b/components/util/parallax_background.gd deleted file mode 100644 index 53cda6e2..00000000 --- a/components/util/parallax_background.gd +++ /dev/null @@ -1,20 +0,0 @@ - -extends ParallaxBackground - -var player_ -var layer0 - -func _ready(): - set_process(true) - -func load_parallax(): - pass - #player_ = get_node("/root/sector").map.find_body_view(get_node("/root/sector").player.get_body()) - #layer0 = get_node("ParallaxLayer") - -func _process(delta): - pass - #var player_pos = player_.get_pos() - #var pos = layer0.get_pos() - #var displacement = Vector2(player_pos.x - pos.x, player_pos.y - pos.y) - #layer0.set_pos(pos + delta * displacement) diff --git a/components/util/parallax_background.tscn b/components/util/parallax_background.tscn deleted file mode 100644 index 0082f478..00000000 --- a/components/util/parallax_background.tscn +++ /dev/null @@ -1,41 +0,0 @@ -[gd_scene load_steps=3 format=1] - -[ext_resource path="res://components/util/parallax_background.gd" type="Script" id=1] -[ext_resource path="res://assets/parallax-bg.png" type="Texture" id=2] - -[node name="ParallaxBackground" type="ParallaxBackground"] - -layer = -1 -offset = Vector2( 0, 0 ) -rotation = 0.0 -scale = Vector2( 1, 1 ) -scroll/offset = Vector2( 0, 0 ) -scroll/base_offset = Vector2( 0, 0 ) -scroll/base_scale = Vector2( 1, 1 ) -scroll/limit_begin = Vector2( 0, 0 ) -scroll/limit_end = Vector2( 0, 0 ) -scroll/ignore_camera_zoom = true -script/script = ExtResource( 1 ) - -[node name="ParallaxLayer" type="ParallaxLayer" parent="."] - -transform/scale = Vector2( 2, 2 ) -motion/scale = Vector2( 0.25, 0.25 ) -motion/offset = Vector2( -640, 0 ) -motion/mirroring = Vector2( 5120, 7680 ) - -[node name="Sprite" type="Sprite" parent="ParallaxLayer"] - -transform/scale = Vector2( 2, 1 ) -texture = ExtResource( 2 ) -centered = false - -[node name="Sprite1" type="Sprite" parent="ParallaxLayer"] - -transform/pos = Vector2( 0, 1920 ) -transform/scale = Vector2( 2, 1 ) -texture = ExtResource( 2 ) -centered = false -flip_v = true - - diff --git a/components/util/profiler.gd b/components/util/profiler.gd deleted file mode 100644 index 0af396af..00000000 --- a/components/util/profiler.gd +++ /dev/null @@ -1,33 +0,0 @@ - -const STACK_MAX = 16 - -var stack_ = IntArray() -var starts_ = IntArray() -var top_ = 0 - -func _init(): - stack_.resize(STACK_MAX) - starts_.resize(STACK_MAX) - -func time_(): - return OS.get_ticks_msec() - -func reset(): - for i in range(STACK_MAX): - stack_[i] = 0 - starts_[i] = 0 - -func push(): - starts_[top_] = time_() - top_ += 1 - -func pop(): - top_ -= 1 - stack_[top_] += time_() - starts_[top_] - -func report(): - print("[profiler report]") - for i in range(STACK_MAX): - if stack_[i] <= 0: - break - printt(i, stack_[i]) diff --git a/components/util/scene_manager.gd b/components/util/scene_manager.gd deleted file mode 100644 index a952ae52..00000000 --- a/components/util/scene_manager.gd +++ /dev/null @@ -1,31 +0,0 @@ - -extends Node - -onready var caplog = get_parent() -onready var menu = get_node("/root/menu") - -func replace_scene(parent, oldscene, newscene): - call_deferred("_deferred_replace_scene", parent, oldscene, newscene) - -func _deferred_replace_scene(parent, oldscene, newscene): - # Immediately free the current scene, - # there is no risk here. - oldscene.free() - # Add it to the active scene, as child of given parent - parent.add_child(newscene) - -func close_route(): - caplog.save_route() - var route = get_node("/root/route") - route.close_current_sector() - route.queue_free() - get_tree().set_current_scene(menu) - menu.start() - -func destroy_route(): - var route = get_node("/root/route") - route.close_current_sector() - route.queue_free() - caplog.erase_route(route.id) - get_tree().set_current_scene(menu) - menu.start() \ No newline at end of file diff --git a/components/util/smol_tileset.tres b/components/util/smol_tileset.tres deleted file mode 100644 index c4ff0669..00000000 --- a/components/util/smol_tileset.tres +++ /dev/null @@ -1,169 +0,0 @@ -[gd_resource type="TileSet" load_steps=4 format=1] - -[ext_resource path="res://assets/floor-cobblestone_v2.tex" type="Texture" id=1] -[ext_resource path="res://assets/wall-stone-bricks.tex" type="Texture" id=2] -[ext_resource path="res://assets/tiles-v2.tex" type="Texture" id=3] - -[resource] - -0/name = "Floor" -0/texture = ExtResource( 1 ) -0/tex_offset = Vector2( 0, 4 ) -0/region = Rect2( 0, 0, 64, 40 ) -0/occluder_offset = Vector2( 32, 20 ) -0/navigation_offset = Vector2( 32, 20 ) -0/shape_offset = Vector2( 0, 0 ) -0/shapes = [ ] -1/name = "Wall Stone Center" -1/texture = ExtResource( 2 ) -1/tex_offset = Vector2( 0, -32 ) -1/region = Rect2( 0, 96, 64, 96 ) -1/occluder_offset = Vector2( 32, 48 ) -1/navigation_offset = Vector2( 32, 48 ) -1/shape_offset = Vector2( 0, 0 ) -1/shapes = [ ] -2/name = "Wall Stone Top" -2/texture = ExtResource( 2 ) -2/tex_offset = Vector2( 0, -32 ) -2/region = Rect2( 192, 96, 64, 96 ) -2/occluder_offset = Vector2( 32, 48 ) -2/navigation_offset = Vector2( 32, 48 ) -2/shape_offset = Vector2( 0, 0 ) -2/shapes = [ ] -3/name = "Wall Stone Top Right" -3/texture = ExtResource( 2 ) -3/tex_offset = Vector2( 0, -32 ) -3/region = Rect2( 256, 0, 64, 96 ) -3/occluder_offset = Vector2( 32, 48 ) -3/navigation_offset = Vector2( 32, 48 ) -3/shape_offset = Vector2( 0, 0 ) -3/shapes = [ ] -4/name = "Wall Stone Right" -4/texture = ExtResource( 2 ) -4/tex_offset = Vector2( 0, -32 ) -4/region = Rect2( 192, 0, 64, 96 ) -4/occluder_offset = Vector2( 32, 48 ) -4/navigation_offset = Vector2( 32, 48 ) -4/shape_offset = Vector2( 0, 0 ) -4/shapes = [ ] -5/name = "Wall Stone Bottom Right" -5/texture = ExtResource( 2 ) -5/tex_offset = Vector2( 0, -32 ) -5/region = Rect2( 128, 0, 64, 96 ) -5/occluder_offset = Vector2( 32, 48 ) -5/navigation_offset = Vector2( 32, 48 ) -5/shape_offset = Vector2( 0, 0 ) -5/shapes = [ ] -6/name = "Wall Stone Bottom" -6/texture = ExtResource( 2 ) -6/tex_offset = Vector2( 0, -32 ) -6/region = Rect2( 64, 0, 64, 96 ) -6/occluder_offset = Vector2( 32, 48 ) -6/navigation_offset = Vector2( 32, 48 ) -6/shape_offset = Vector2( 0, 0 ) -6/shapes = [ ] -7/name = "Wall Stone Bottom Left" -7/texture = ExtResource( 2 ) -7/tex_offset = Vector2( 0, -32 ) -7/region = Rect2( 0, 0, 64, 96 ) -7/occluder_offset = Vector2( 32, 48 ) -7/navigation_offset = Vector2( 32, 48 ) -7/shape_offset = Vector2( 0, 0 ) -7/shapes = [ ] -8/name = "Wall Stone Left" -8/texture = ExtResource( 2 ) -8/tex_offset = Vector2( 0, -32 ) -8/region = Rect2( 64, 96, 64, 96 ) -8/occluder_offset = Vector2( 32, 48 ) -8/navigation_offset = Vector2( 32, 48 ) -8/shape_offset = Vector2( 0, 0 ) -8/shapes = [ ] -9/name = "Wall Stone Top Left" -9/texture = ExtResource( 2 ) -9/tex_offset = Vector2( 0, -32 ) -9/region = Rect2( 128, 96, 64, 96 ) -9/occluder_offset = Vector2( 32, 48 ) -9/navigation_offset = Vector2( 32, 48 ) -9/shape_offset = Vector2( 0, 0 ) -9/shapes = [ ] -10/name = "Wall Stone Corner Top Right" -10/texture = ExtResource( 2 ) -10/tex_offset = Vector2( 0, -32 ) -10/region = Rect2( 0, 192, 64, 96 ) -10/occluder_offset = Vector2( 32, 48 ) -10/navigation_offset = Vector2( 32, 48 ) -10/shape_offset = Vector2( 0, 0 ) -10/shapes = [ ] -11/name = "Wall Stone Corner Bottom Right" -11/texture = ExtResource( 2 ) -11/tex_offset = Vector2( 0, -32 ) -11/region = Rect2( 64, 192, 64, 96 ) -11/occluder_offset = Vector2( 32, 48 ) -11/navigation_offset = Vector2( 32, 48 ) -11/shape_offset = Vector2( 0, 0 ) -11/shapes = [ ] -12/name = "Wall Stone Corner Bottom Left" -12/texture = ExtResource( 2 ) -12/tex_offset = Vector2( 0, -32 ) -12/region = Rect2( 128, 192, 64, 96 ) -12/occluder_offset = Vector2( 32, 48 ) -12/navigation_offset = Vector2( 32, 48 ) -12/shape_offset = Vector2( 0, 0 ) -12/shapes = [ ] -13/name = "Wall Stone Corner Top Left" -13/texture = ExtResource( 2 ) -13/tex_offset = Vector2( 0, -32 ) -13/region = Rect2( 192, 192, 64, 96 ) -13/occluder_offset = Vector2( 32, 48 ) -13/navigation_offset = Vector2( 32, 48 ) -13/shape_offset = Vector2( 0, 0 ) -13/shapes = [ ] -14/name = "Wall Corner" -14/texture = ExtResource( 3 ) -14/tex_offset = Vector2( 0, -32 ) -14/region = Rect2( 64, 0, 64, 96 ) -14/occluder_offset = Vector2( 32, 48 ) -14/navigation_offset = Vector2( 32, 48 ) -14/shape_offset = Vector2( 0, 0 ) -14/shapes = [ ] -15/name = "Wall Corner T" -15/texture = ExtResource( 3 ) -15/tex_offset = Vector2( 0, -32 ) -15/region = Rect2( 128, 0, 64, 96 ) -15/occluder_offset = Vector2( 32, 48 ) -15/navigation_offset = Vector2( 32, 48 ) -15/shape_offset = Vector2( 0, 0 ) -15/shapes = [ ] -16/name = "Wall Left" -16/texture = ExtResource( 3 ) -16/tex_offset = Vector2( 0, -32 ) -16/region = Rect2( 192, 0, 64, 96 ) -16/occluder_offset = Vector2( 32, 48 ) -16/navigation_offset = Vector2( 32, 48 ) -16/shape_offset = Vector2( 0, 0 ) -16/shapes = [ ] -17/name = "Wall Left T" -17/texture = ExtResource( 3 ) -17/tex_offset = Vector2( 0, -32 ) -17/region = Rect2( 256, 0, 64, 96 ) -17/occluder_offset = Vector2( 32, 48 ) -17/navigation_offset = Vector2( 32, 48 ) -17/shape_offset = Vector2( 0, 0 ) -17/shapes = [ ] -18/name = "Wall Right " -18/texture = ExtResource( 3 ) -18/tex_offset = Vector2( 0, -32 ) -18/region = Rect2( 320, 0, 64, 96 ) -18/occluder_offset = Vector2( 32, 48 ) -18/navigation_offset = Vector2( 32, 48 ) -18/shape_offset = Vector2( 0, 0 ) -18/shapes = [ ] -19/name = "Wall Right T" -19/texture = ExtResource( 3 ) -19/tex_offset = Vector2( 0, -32 ) -19/region = Rect2( 384, 0, 64, 96 ) -19/occluder_offset = Vector2( 32, 48 ) -19/navigation_offset = Vector2( 32, 48 ) -19/shape_offset = Vector2( 0, 0 ) -19/shapes = [ ] - diff --git a/components/util/smol_tileset.tscn b/components/util/smol_tileset.tscn deleted file mode 100644 index 9c3d70a9..00000000 --- a/components/util/smol_tileset.tscn +++ /dev/null @@ -1,171 +0,0 @@ -[gd_scene load_steps=4 format=1] - -[ext_resource path="res://assets/floor-cobblestone_v2.tex" type="Texture" id=1] -[ext_resource path="res://assets/wall-stone-bricks.tex" type="Texture" id=2] -[ext_resource path="res://assets/tiles-v2.tex" type="Texture" id=3] - -[node name="Node2D" type="Node2D"] - -__meta__ = { "__editor_plugin_screen__":"2D" } - -[node name="Floor" type="Sprite" parent="."] - -transform/pos = Vector2( 32, 80 ) -texture = ExtResource( 1 ) -offset = Vector2( 0, 4 ) -region = true -region_rect = Rect2( 0, 0, 64, 40 ) - -[node name="Wall Stone Center" type="Sprite" parent="."] - -transform/pos = Vector2( 96, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 0, 96, 64, 96 ) - -[node name="Wall Stone Top" type="Sprite" parent="."] - -transform/pos = Vector2( 160, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 192, 96, 64, 96 ) - -[node name="Wall Stone Top Right" type="Sprite" parent="."] - -transform/pos = Vector2( 224, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 256, 0, 64, 96 ) - -[node name="Wall Stone Right" type="Sprite" parent="."] - -transform/pos = Vector2( 288, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 192, 0, 64, 96 ) - -[node name="Wall Stone Bottom Right" type="Sprite" parent="."] - -transform/pos = Vector2( 352, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 128, 0, 64, 96 ) - -[node name="Wall Stone Bottom" type="Sprite" parent="."] - -transform/pos = Vector2( 416, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 64, 0, 64, 96 ) - -[node name="Wall Stone Bottom Left" type="Sprite" parent="."] - -transform/pos = Vector2( 480, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 0, 0, 64, 96 ) - -[node name="Wall Stone Left" type="Sprite" parent="."] - -transform/pos = Vector2( 544, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 64, 96, 64, 96 ) - -[node name="Wall Stone Top Left" type="Sprite" parent="."] - -transform/pos = Vector2( 608, 80 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 128, 96, 64, 96 ) - -[node name="Wall Stone Corner Top Right" type="Sprite" parent="."] - -transform/pos = Vector2( 160, 176 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 0, 192, 64, 96 ) - -[node name="Wall Stone Corner Bottom Right" type="Sprite" parent="."] - -transform/pos = Vector2( 224, 176 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 64, 192, 64, 96 ) - -[node name="Wall Stone Corner Bottom Left" type="Sprite" parent="."] - -transform/pos = Vector2( 288, 176 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 128, 192, 64, 96 ) - -[node name="Wall Stone Corner Top Left" type="Sprite" parent="."] - -transform/pos = Vector2( 352, 176 ) -texture = ExtResource( 2 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 192, 192, 64, 96 ) - -[node name="Wall Corner" type="Sprite" parent="."] - -transform/pos = Vector2( 32, 465 ) -texture = ExtResource( 3 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 64, 0, 64, 96 ) - -[node name="Wall Corner T" type="Sprite" parent="."] - -transform/pos = Vector2( 96, 465 ) -texture = ExtResource( 3 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 128, 0, 64, 96 ) - -[node name="Wall Left" type="Sprite" parent="."] - -transform/pos = Vector2( 160, 464 ) -texture = ExtResource( 3 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 192, 0, 64, 96 ) - -[node name="Wall Left T" type="Sprite" parent="."] - -transform/pos = Vector2( 224, 464 ) -texture = ExtResource( 3 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 256, 0, 64, 96 ) - -[node name="Wall Right " type="Sprite" parent="."] - -transform/pos = Vector2( 288, 464 ) -texture = ExtResource( 3 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 320, 0, 64, 96 ) - -[node name="Wall Right T" type="Sprite" parent="."] - -transform/pos = Vector2( 352, 464 ) -texture = ExtResource( 3 ) -offset = Vector2( 0, -32 ) -region = true -region_rect = Rect2( 384, 0, 64, 96 ) - - diff --git a/components/util/tile-patterns.tscn b/components/util/tile-patterns.tscn deleted file mode 100644 index 0bc57b74..00000000 --- a/components/util/tile-patterns.tscn +++ /dev/null @@ -1,49 +0,0 @@ -[gd_scene load_steps=3 format=1] - -[ext_resource path="res://components/util/smol_tileset.tres" type="TileSet" id=1] -[ext_resource path="res://assets/bodies/hero/idle.tex" type="Texture" id=2] - -[node name="Node2D" type="Node2D"] - -[node name="ground" type="TileMap" parent="."] - -mode = 1 -tile_set = ExtResource( 1 ) -cell/size = Vector2( 64, 32 ) -cell/quadrant_size = 16 -cell/custom_transform = Matrix32( 1, 0, 0, 1, 0, 0 ) -cell/half_offset = 2 -cell/tile_origin = 1 -cell/y_sort = true -collision/use_kinematic = false -collision/friction = 1.0 -collision/bounce = 0.0 -collision/layers = 1 -collision/mask = 1 -occluder/light_mask = 1 -tile_data = IntArray( 2, 0, 4, 0, 6, 0, 8, 0, 65537, 0, 65538, 0, 65540, 0, 65541, 0, 65542, 0, 65544, 0, 65545, 0, 131072, 0, 131073, 0, 131074, 0, 131076, 0, 131077, 0, 131078, 0, 131080, 0, 131081, 0, 131082, 0, 262144, 0, 262145, 0, 262146, 0, 262152, 0, 262153, 0, 262154, 0, 262157, 0, 262158, 0, 262160, 0, 262161, 0, 327681, 0, 327682, 0, 327688, 0, 327689, 0, 327692, 0, 327693, 0, 327694, 0, 327696, 0, 327697, 0, 327698, 0, 393216, 0, 393217, 0, 393218, 0, 393224, 0, 393225, 0, 393226, 0, 393228, 0, 393229, 0, 393230, 0, 393232, 0, 393233, 0, 393234, 0, 524288, 0, 524289, 0, 524290, 0, 524292, 0, 524293, 0, 524294, 0, 524296, 0, 524297, 0, 524298, 0, 524300, 0, 524301, 0, 524302, 0, 524304, 0, 524305, 0, 524306, 0, 589825, 0, 589826, 0, 589828, 0, 589829, 0, 589830, 0, 589832, 0, 589833, 0, 589836, 0, 589837, 0, 589838, 0, 589840, 0, 589841, 0, 589842, 0, 655362, 0, 655364, 0, 655366, 0, 655368, 0, 655373, 0, 655374, 0, 655376, 0, 655377, 0 ) - -[node name="walls" type="TileMap" parent="."] - -mode = 1 -tile_set = ExtResource( 1 ) -cell/size = Vector2( 64, 32 ) -cell/quadrant_size = 16 -cell/custom_transform = Matrix32( 1, 0, 0, 1, 0, 0 ) -cell/half_offset = 2 -cell/tile_origin = 1 -cell/y_sort = true -collision/use_kinematic = false -collision/friction = 1.0 -collision/bounce = 0.0 -collision/layers = 1 -collision/mask = 1 -occluder/light_mask = 1 -tile_data = IntArray( 65537, 9, 65541, 2, 65545, 3, 131074, 1, 131077, 1, 131080, 1, 327681, 8, 327682, 1, 327688, 1, 327689, 4, 327693, 13, 327694, 1, 327696, 1, 327697, 10, 393229, 1, 393230, 1, 393232, 1, 393233, 1, 524290, 1, 524293, 1, 524296, 1, 524301, 1, 524302, 1, 524304, 1, 524305, 1, 589825, 7, 589829, 6, 589833, 5, 589837, 12, 589838, 1, 589840, 1, 589841, 11 ) - -[node name="Sprite" type="Sprite" parent="."] - -transform/pos = Vector2( 128, 240 ) -texture = ExtResource( 2 ) - - diff --git a/test.save b/docs/test.save similarity index 100% rename from test.save rename to docs/test.save diff --git a/test2.save b/docs/test2.save similarity index 100% rename from test2.save rename to docs/test2.save diff --git a/engine.cfg b/engine.cfg deleted file mode 100644 index e9a8c375..00000000 --- a/engine.cfg +++ /dev/null @@ -1,47 +0,0 @@ -[application] - -name="backdoor" -main_scene="res://scenes/menu.tscn" -target_fps=60 -icon="icon.png" - -[autoload] - -sector="*res://scenes/main.tscn" -captains_log="*res://model/captains_log.tscn" - -[display] - -stretch_mode="disabled" -resizable=true -fullscreen=false -width=640 -height=480 -use_2d_pixel_snap=true -stretch_aspect="keep" - -[image_loader] - -filter=false -gen_mipmaps=false - -[input] - -ui_select=[key(Space), jbutton(0, 3), key(D)] -ui_focus_next=[key(Tab), key(A)] -ui_focus_prev=[key(Tab, S), key(S)] -ui_left=[key(Left), jbutton(0, 14)] -ui_right=[key(Right), jbutton(0, 15)] -ui_up=[key(Up), jbutton(0, 12)] -ui_down=[key(Down), jbutton(0, 13)] -ui_toggle_fullscreen=[key(Return, A)] -ui_show_upgrades=[key(H)] -ui_display_card=[key(F)] -ui_save=[key(S, S)] -debug_create_slime=[key(I)] -debug_next_sector=[key(P)] -ui_idle=[key(Period)] - -[render] - -default_clear_color=#ff2a1a1f diff --git a/externals/.gitignore b/externals/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/externals/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/game/README.md b/game/README.md new file mode 100644 index 00000000..702ef0fb --- /dev/null +++ b/game/README.md @@ -0,0 +1,71 @@ + +# BACKDOOR ROUTE's source code + +This directory contains all the source code for Backdoor Route, organized as +follows: + ++ `database/` + Source and data files regarding the game contents, such as creature types and + card descriptions. ++ `debug/` + Source files for debug and development support in-game. ++ `domain/` + Source files implementing the rules of gameplay. ++ `gamestates/` + Source files managing the game interactions between player and gameplay. ++ `helpers/` + Assorted source files with auxiliary routines. + +Besides these, the following directories *should* also be here after the inital +setup but *are not* supposed to be versioned: + ++ `cpml/` ++ `lux/` ++ `steaming/` ++ `dkjson.lua` + +## Code style + +WIP + +### Naming conventions + +Name capitalization and format should make clear the **role** and **scope** of +the data they label. The conventions we use here are intended to reflect that +principle. + +#### Scope-related + +1. Any global names should be in `ALL_CAPS` +2. Names local in a source file should start with a `_` (underscore) + + Unless they are aliases used to name `require`d modules and classes, in + which case they should be in `ALL_CAPS` or `PascalCase`, respectively. +3. Names local in a function or method have no special markings + +#### Role-related + +1. Variable names should be in `snake_case` +2. Function and method names should be in `camelCase` +3. Class names should be in `PascalCase` + +### Formatting conventions + +This is all pretty much arbitrary. + +1. Indentation should be two spaces long (NO TABS) +2. Function declaration: +```lua +function foo() + return 42 +end +``` +3. When there are too many parameters in a function declaration or call, break + the line on the last parameter that fits the line and start a new one from + the opening parenthesis: +```lua +callFunctionWithTooManyParameters(parameter1, parameter2, parameter3, + parameter4, parameter5(didnt_expect_this, + did_you)) +``` + + diff --git a/game/assets/bgm/ruins.ogg b/game/assets/bgm/ruins.ogg new file mode 100644 index 00000000..deef4c42 Binary files /dev/null and b/game/assets/bgm/ruins.ogg differ diff --git a/game/assets/bgm/ruins_ac.ogg b/game/assets/bgm/ruins_ac.ogg new file mode 100644 index 00000000..18eb559e Binary files /dev/null and b/game/assets/bgm/ruins_ac.ogg differ diff --git a/game/assets/bgm/ruins_melody.ogg b/game/assets/bgm/ruins_melody.ogg new file mode 100644 index 00000000..7a0945c3 Binary files /dev/null and b/game/assets/bgm/ruins_melody.ogg differ diff --git a/game/assets/bgm/ruins_percussion.ogg b/game/assets/bgm/ruins_percussion.ogg new file mode 100644 index 00000000..c19c1c70 Binary files /dev/null and b/game/assets/bgm/ruins_percussion.ogg differ diff --git a/game/assets/font/Anton.ttf b/game/assets/font/Anton.ttf new file mode 100755 index 00000000..d8dad257 Binary files /dev/null and b/game/assets/font/Anton.ttf differ diff --git a/game/assets/font/OFL.txt b/game/assets/font/OFL.txt new file mode 100755 index 00000000..7bf7f048 --- /dev/null +++ b/game/assets/font/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2011, Vernon Adams (vern@newtypography.co.uk), +with Reserved Font Name Anton. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/game/assets/font/SIL Open Font License.txt b/game/assets/font/SIL Open Font License.txt new file mode 100755 index 00000000..49175907 --- /dev/null +++ b/game/assets/font/SIL Open Font License.txt @@ -0,0 +1,43 @@ +Copyright 2016 The Saira Project Authors (omnibus.type@gmail.com), with reserved font name "Saira". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the copyright statement(s). + +"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/game/assets/font/SairaCondensed-ExtraBold.ttf b/game/assets/font/SairaCondensed-ExtraBold.ttf new file mode 100755 index 00000000..74b10ce1 Binary files /dev/null and b/game/assets/font/SairaCondensed-ExtraBold.ttf differ diff --git a/game/assets/font/SairaCondensed-Medium.ttf b/game/assets/font/SairaCondensed-Medium.ttf new file mode 100755 index 00000000..2150ea60 Binary files /dev/null and b/game/assets/font/SairaCondensed-Medium.ttf differ diff --git a/game/assets/sfx/Footstep.wav b/game/assets/sfx/Footstep.wav new file mode 100644 index 00000000..85243d25 Binary files /dev/null and b/game/assets/sfx/Footstep.wav differ diff --git a/game/assets/sfx/Teleport.wav b/game/assets/sfx/Teleport.wav new file mode 100644 index 00000000..b7943354 Binary files /dev/null and b/game/assets/sfx/Teleport.wav differ diff --git a/game/assets/sfx/Upgrade2.wav b/game/assets/sfx/Upgrade2.wav new file mode 100644 index 00000000..c90053d6 Binary files /dev/null and b/game/assets/sfx/Upgrade2.wav differ diff --git a/game/assets/sfx/back_menu.wav b/game/assets/sfx/back_menu.wav new file mode 100644 index 00000000..e44d6884 Binary files /dev/null and b/game/assets/sfx/back_menu.wav differ diff --git a/game/assets/sfx/denied.wav b/game/assets/sfx/denied.wav new file mode 100644 index 00000000..71051c6f Binary files /dev/null and b/game/assets/sfx/denied.wav differ diff --git a/game/assets/sfx/example.wav b/game/assets/sfx/example.wav new file mode 100644 index 00000000..fb2ca76e Binary files /dev/null and b/game/assets/sfx/example.wav differ diff --git a/game/assets/sfx/get_item.wav b/game/assets/sfx/get_item.wav new file mode 100644 index 00000000..70e4bb54 Binary files /dev/null and b/game/assets/sfx/get_item.wav differ diff --git a/game/assets/sfx/going_down_stars.wav b/game/assets/sfx/going_down_stars.wav new file mode 100644 index 00000000..ec3b1991 Binary files /dev/null and b/game/assets/sfx/going_down_stars.wav differ diff --git a/game/assets/sfx/ok_menu.wav b/game/assets/sfx/ok_menu.wav new file mode 100644 index 00000000..77f7a9e4 Binary files /dev/null and b/game/assets/sfx/ok_menu.wav differ diff --git a/game/assets/sfx/open_menu.wav b/game/assets/sfx/open_menu.wav new file mode 100644 index 00000000..2766dcd1 Binary files /dev/null and b/game/assets/sfx/open_menu.wav differ diff --git a/game/assets/sfx/select_menu.wav b/game/assets/sfx/select_menu.wav new file mode 100644 index 00000000..a6fd668f Binary files /dev/null and b/game/assets/sfx/select_menu.wav differ diff --git a/game/assets/texture/.gitignore b/game/assets/texture/.gitignore new file mode 100644 index 00000000..756b22fa --- /dev/null +++ b/game/assets/texture/.gitignore @@ -0,0 +1 @@ +*.svg diff --git a/game/assets/texture/CREDITS.md b/game/assets/texture/CREDITS.md new file mode 100644 index 00000000..b54c639a --- /dev/null +++ b/game/assets/texture/CREDITS.md @@ -0,0 +1,46 @@ + +The following icon files were obtained from game-icons.net under the +Creative Commons BY 3.0 license, with credits to Lorc, Delapouite and +other contributors (as per game-icons.net/about.html#authors): + ++ auto-repair.png ++ boot-stomp.png ++ boots.png ++ brandy-bottle.png ++ burning-dot.png ++ burning-meteor.png ++ card-goggles.png ++ card-turret.png ++ chest-armor.png ++ emerald.png ++ fire-dash.png ++ fire-punch.png ++ fire-silhouette.png ++ fission.png ++ fluffly-flame.png ++ hospital-cross.png ++ icon-activate_widget.png ++ icon-draw_new_hand.png ++ icon-idle.png ++ icon-interact.png ++ icon-manage-buffer.png ++ icon-play_card.png ++ icon-receive_pack.png ++ icon-use_signature.png ++ land-mine.png ++ lunar-wand.png ++ magic-trident.png ++ meditation.png ++ metal-scales.png ++ monkey-wrench.png ++ orb-direction.png ++ ray-gun.png ++ robe.png ++ square-bottle.png ++ teleport.png ++ time-bomb.png ++ triorb.png ++ upgrade.png ++ warlock-eye.png ++ wide-arrow-dunk.png + diff --git a/game/assets/texture/auto-repair.png b/game/assets/texture/auto-repair.png new file mode 100644 index 00000000..d724fc9c Binary files /dev/null and b/game/assets/texture/auto-repair.png differ diff --git a/game/assets/texture/boot-stomp.png b/game/assets/texture/boot-stomp.png new file mode 100644 index 00000000..f10d027b Binary files /dev/null and b/game/assets/texture/boot-stomp.png differ diff --git a/game/assets/texture/boots.png b/game/assets/texture/boots.png new file mode 100644 index 00000000..336dea04 Binary files /dev/null and b/game/assets/texture/boots.png differ diff --git a/game/assets/texture/box-pack.png b/game/assets/texture/box-pack.png new file mode 100644 index 00000000..0053a026 Binary files /dev/null and b/game/assets/texture/box-pack.png differ diff --git a/game/assets/texture/box.png b/game/assets/texture/box.png new file mode 100644 index 00000000..098a64bd Binary files /dev/null and b/game/assets/texture/box.png differ diff --git a/game/assets/texture/brandy-bottle.png b/game/assets/texture/brandy-bottle.png new file mode 100644 index 00000000..58048eb2 Binary files /dev/null and b/game/assets/texture/brandy-bottle.png differ diff --git a/game/assets/texture/burning-dot.png b/game/assets/texture/burning-dot.png new file mode 100644 index 00000000..cd8fc306 Binary files /dev/null and b/game/assets/texture/burning-dot.png differ diff --git a/game/assets/texture/burning-meteor.png b/game/assets/texture/burning-meteor.png new file mode 100644 index 00000000..f4234c4d Binary files /dev/null and b/game/assets/texture/burning-meteor.png differ diff --git a/game/assets/texture/card-goggles.png b/game/assets/texture/card-goggles.png new file mode 100644 index 00000000..5ef7f994 Binary files /dev/null and b/game/assets/texture/card-goggles.png differ diff --git a/game/assets/texture/card-turret.png b/game/assets/texture/card-turret.png new file mode 100644 index 00000000..520d8cca Binary files /dev/null and b/game/assets/texture/card-turret.png differ diff --git a/game/assets/texture/card.png b/game/assets/texture/card.png new file mode 100644 index 00000000..8698eb03 Binary files /dev/null and b/game/assets/texture/card.png differ diff --git a/game/assets/texture/card_back_1.png b/game/assets/texture/card_back_1.png new file mode 100644 index 00000000..9f3897f0 Binary files /dev/null and b/game/assets/texture/card_back_1.png differ diff --git a/game/assets/texture/chest-armor.png b/game/assets/texture/chest-armor.png new file mode 100644 index 00000000..5e40b85c Binary files /dev/null and b/game/assets/texture/chest-armor.png differ diff --git a/game/assets/texture/cursor_tile.png b/game/assets/texture/cursor_tile.png new file mode 100644 index 00000000..e1eea924 Binary files /dev/null and b/game/assets/texture/cursor_tile.png differ diff --git a/game/assets/texture/demobody.png b/game/assets/texture/demobody.png new file mode 100644 index 00000000..8154dfb5 Binary files /dev/null and b/game/assets/texture/demobody.png differ diff --git a/game/assets/texture/emerald.png b/game/assets/texture/emerald.png new file mode 100644 index 00000000..0582122f Binary files /dev/null and b/game/assets/texture/emerald.png differ diff --git a/game/assets/texture/fakeminimap.png b/game/assets/texture/fakeminimap.png new file mode 100644 index 00000000..71c7b292 Binary files /dev/null and b/game/assets/texture/fakeminimap.png differ diff --git a/game/assets/texture/fire-dash.png b/game/assets/texture/fire-dash.png new file mode 100644 index 00000000..1b1677f7 Binary files /dev/null and b/game/assets/texture/fire-dash.png differ diff --git a/game/assets/texture/fire-punch.png b/game/assets/texture/fire-punch.png new file mode 100644 index 00000000..6274fd9c Binary files /dev/null and b/game/assets/texture/fire-punch.png differ diff --git a/game/assets/texture/fire-silhouette.png b/game/assets/texture/fire-silhouette.png new file mode 100644 index 00000000..61e48fb4 Binary files /dev/null and b/game/assets/texture/fire-silhouette.png differ diff --git a/game/assets/texture/fission.png b/game/assets/texture/fission.png new file mode 100644 index 00000000..524d1b32 Binary files /dev/null and b/game/assets/texture/fission.png differ diff --git a/game/assets/texture/floating-eye-demon.png b/game/assets/texture/floating-eye-demon.png new file mode 100644 index 00000000..be877ece Binary files /dev/null and b/game/assets/texture/floating-eye-demon.png differ diff --git a/game/assets/texture/fluffy-flame.png b/game/assets/texture/fluffy-flame.png new file mode 100644 index 00000000..3f2d631e Binary files /dev/null and b/game/assets/texture/fluffy-flame.png differ diff --git a/game/assets/texture/hearthborn-idle.png b/game/assets/texture/hearthborn-idle.png new file mode 100644 index 00000000..b927ec94 Binary files /dev/null and b/game/assets/texture/hearthborn-idle.png differ diff --git a/game/assets/texture/hillborn-idle.png b/game/assets/texture/hillborn-idle.png new file mode 100644 index 00000000..ef7f57d0 Binary files /dev/null and b/game/assets/texture/hillborn-idle.png differ diff --git a/game/assets/texture/hospital-cross.png b/game/assets/texture/hospital-cross.png new file mode 100644 index 00000000..d6aeda39 Binary files /dev/null and b/game/assets/texture/hospital-cross.png differ diff --git a/game/assets/texture/icon-activate_widget.png b/game/assets/texture/icon-activate_widget.png new file mode 100644 index 00000000..0d74e957 Binary files /dev/null and b/game/assets/texture/icon-activate_widget.png differ diff --git a/game/assets/texture/icon-draw_new_hand.png b/game/assets/texture/icon-draw_new_hand.png new file mode 100644 index 00000000..2a108b67 Binary files /dev/null and b/game/assets/texture/icon-draw_new_hand.png differ diff --git a/game/assets/texture/icon-idle.png b/game/assets/texture/icon-idle.png new file mode 100644 index 00000000..cd35e612 Binary files /dev/null and b/game/assets/texture/icon-idle.png differ diff --git a/game/assets/texture/icon-interact.png b/game/assets/texture/icon-interact.png new file mode 100644 index 00000000..c924c1b2 Binary files /dev/null and b/game/assets/texture/icon-interact.png differ diff --git a/game/assets/texture/icon-manage-buffer.png b/game/assets/texture/icon-manage-buffer.png new file mode 100644 index 00000000..c0a55519 Binary files /dev/null and b/game/assets/texture/icon-manage-buffer.png differ diff --git a/game/assets/texture/icon-play_card.png b/game/assets/texture/icon-play_card.png new file mode 100644 index 00000000..deee9bcc Binary files /dev/null and b/game/assets/texture/icon-play_card.png differ diff --git a/game/assets/texture/icon-receive_pack.png b/game/assets/texture/icon-receive_pack.png new file mode 100644 index 00000000..59e493d8 Binary files /dev/null and b/game/assets/texture/icon-receive_pack.png differ diff --git a/game/assets/texture/icon-use_signature.png b/game/assets/texture/icon-use_signature.png new file mode 100644 index 00000000..d08e9ac0 Binary files /dev/null and b/game/assets/texture/icon-use_signature.png differ diff --git a/game/assets/texture/imp.png b/game/assets/texture/imp.png new file mode 100644 index 00000000..6888e08e Binary files /dev/null and b/game/assets/texture/imp.png differ diff --git a/game/assets/texture/land-mine.png b/game/assets/texture/land-mine.png new file mode 100644 index 00000000..14657cf2 Binary files /dev/null and b/game/assets/texture/land-mine.png differ diff --git a/game/assets/texture/lunar-wand.png b/game/assets/texture/lunar-wand.png new file mode 100644 index 00000000..a19c276c Binary files /dev/null and b/game/assets/texture/lunar-wand.png differ diff --git a/game/assets/texture/magic-trident.png b/game/assets/texture/magic-trident.png new file mode 100644 index 00000000..cb54aa7c Binary files /dev/null and b/game/assets/texture/magic-trident.png differ diff --git a/game/assets/texture/meditation.png b/game/assets/texture/meditation.png new file mode 100644 index 00000000..9bb7d453 Binary files /dev/null and b/game/assets/texture/meditation.png differ diff --git a/game/assets/texture/metal-scales.png b/game/assets/texture/metal-scales.png new file mode 100644 index 00000000..30e56f94 Binary files /dev/null and b/game/assets/texture/metal-scales.png differ diff --git a/game/assets/texture/monkey-wrench.png b/game/assets/texture/monkey-wrench.png new file mode 100644 index 00000000..f5855763 Binary files /dev/null and b/game/assets/texture/monkey-wrench.png differ diff --git a/game/assets/texture/no-icon.png b/game/assets/texture/no-icon.png new file mode 100644 index 00000000..e832e918 Binary files /dev/null and b/game/assets/texture/no-icon.png differ diff --git a/game/assets/texture/notagoodboy.png b/game/assets/texture/notagoodboy.png new file mode 100644 index 00000000..dd2e4ece Binary files /dev/null and b/game/assets/texture/notagoodboy.png differ diff --git a/game/assets/texture/orb-direction.png b/game/assets/texture/orb-direction.png new file mode 100644 index 00000000..2d5af192 Binary files /dev/null and b/game/assets/texture/orb-direction.png differ diff --git a/game/assets/texture/pack.png b/game/assets/texture/pack.png new file mode 100644 index 00000000..eb691956 Binary files /dev/null and b/game/assets/texture/pack.png differ diff --git a/game/assets/texture/panel-theme.png b/game/assets/texture/panel-theme.png new file mode 100644 index 00000000..0845531b Binary files /dev/null and b/game/assets/texture/panel-theme.png differ diff --git a/game/assets/texture/pixel.png b/game/assets/texture/pixel.png new file mode 100644 index 00000000..44207a26 Binary files /dev/null and b/game/assets/texture/pixel.png differ diff --git a/game/assets/texture/ray-gun.png b/game/assets/texture/ray-gun.png new file mode 100644 index 00000000..1c00fec0 Binary files /dev/null and b/game/assets/texture/ray-gun.png differ diff --git a/game/assets/texture/robe.png b/game/assets/texture/robe.png new file mode 100644 index 00000000..270e9488 Binary files /dev/null and b/game/assets/texture/robe.png differ diff --git a/game/assets/texture/slimy-bouncy.png b/game/assets/texture/slimy-bouncy.png new file mode 100644 index 00000000..5a5ffb52 Binary files /dev/null and b/game/assets/texture/slimy-bouncy.png differ diff --git a/game/assets/texture/square-bottle.png b/game/assets/texture/square-bottle.png new file mode 100644 index 00000000..58da59bd Binary files /dev/null and b/game/assets/texture/square-bottle.png differ diff --git a/game/assets/texture/teleport.png b/game/assets/texture/teleport.png new file mode 100644 index 00000000..0979b0a4 Binary files /dev/null and b/game/assets/texture/teleport.png differ diff --git a/game/assets/texture/tile-testing.png b/game/assets/texture/tile-testing.png new file mode 100644 index 00000000..52d2f789 Binary files /dev/null and b/game/assets/texture/tile-testing.png differ diff --git a/game/assets/texture/tiles-purplr.png b/game/assets/texture/tiles-purplr.png new file mode 100644 index 00000000..141ecd96 Binary files /dev/null and b/game/assets/texture/tiles-purplr.png differ diff --git a/game/assets/texture/tiles.png b/game/assets/texture/tiles.png new file mode 100644 index 00000000..d96528f6 Binary files /dev/null and b/game/assets/texture/tiles.png differ diff --git a/game/assets/texture/time-bomb.png b/game/assets/texture/time-bomb.png new file mode 100644 index 00000000..2b27cadb Binary files /dev/null and b/game/assets/texture/time-bomb.png differ diff --git a/game/assets/texture/triorb.png b/game/assets/texture/triorb.png new file mode 100644 index 00000000..c0c1fe87 Binary files /dev/null and b/game/assets/texture/triorb.png differ diff --git a/game/assets/texture/upgrade.png b/game/assets/texture/upgrade.png new file mode 100644 index 00000000..79eaece2 Binary files /dev/null and b/game/assets/texture/upgrade.png differ diff --git a/game/assets/texture/warlock-eye.png b/game/assets/texture/warlock-eye.png new file mode 100644 index 00000000..e5dad1b0 Binary files /dev/null and b/game/assets/texture/warlock-eye.png differ diff --git a/game/assets/texture/wide-arrow-dunk.png b/game/assets/texture/wide-arrow-dunk.png new file mode 100644 index 00000000..5417b116 Binary files /dev/null and b/game/assets/texture/wide-arrow-dunk.png differ diff --git a/game/common/activity.lua b/game/common/activity.lua new file mode 100644 index 00000000..d637fc49 --- /dev/null +++ b/game/common/activity.lua @@ -0,0 +1,81 @@ + +-- local _activity = Activity() +-- function _activity:doSomething(x, y) +-- x = x + 1 +-- self.yield(tweenAndThen(self.resume)) +-- y = y + 1 +-- end +-- +-- _activity:doSomething(42, 1337) + +local Task = require 'lux.class' :new{} + +require 'lux.portable' + +local coroutine = coroutine +local debug = debug +local error = error +local table = table +local type = type +local setfenv = setfenv + +function Task:instance (obj, func, ...) + + setfenv(1, obj) + + local function _bootstrap(...) + return func(...) + end + + local task = coroutine.create(_bootstrap) + local onhold = false + local params = {} + + local function _hold() + onhold = true + end + + local function _release(...) + onhold = false + params = table.pack(...) + end + + function resume(...) + local check, result = coroutine.resume(task, obj, ...) + if not check then + error(debug.traceback(task, result)) + end + return result + end + + function yield() + return coroutine.yield(task) + end + + function __operator:newindex() + return error("Do not change the task object") + end + +end + +local Activity = require 'lux.class' :new{} + +function Activity:instance(obj) + + setfenv(1, obj) + + local _funcs = {} + + function __operator:newindex(name, func) + _funcs[name] = func + end + + function __operator:index(name) + local newtask = Task(_funcs[name]) + return function (_, ...) newtask.resume(...) end + end + +end + +return Activity + diff --git a/game/common/camera.lua b/game/common/camera.lua new file mode 100644 index 00000000..0547d92b --- /dev/null +++ b/game/common/camera.lua @@ -0,0 +1,68 @@ + +local math = require 'common.math' +local Camera = require "steaming.extra_libs.hump.camera" +local VIEWDEFS = require 'view.definitions' + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H +local _HALF_W = VIEWDEFS.HALF_W +local _HALF_H = VIEWDEFS.HALF_H + +local CAM = Camera(love.graphics.getWidth() / 2, + love.graphics.getHeight() / 2, + 1, 0, Camera.smooth.damped(5)) + +function CAM:attach(x, y, w, h, noclip) + local g = love.graphics + x,y = x or 0, y or 0 + w,h = w or g.getWidth(), h or g.getHeight() + + self._sx,self._sy,self._sw,self._sh = g.getScissor() + if not noclip then + g.setScissor(x,y,w,h) + end + + local cx,cy = x+w/2, y+h/2 + g.push() + g.translate(math.round(cx), math.round(cy)) + g.scale(self.scale) + g.rotate(self.rot) + g.translate(-math.round(self.x*self.scale)/self.scale, + -math.round(self.y*self.scale)/self.scale) +end + +function CAM:isTileInFrame(i, j) + local cx, cy = self:position() + cx = cx / _TILE_W + cy = cy / _TILE_H + return j >= cx - _HALF_W + and j <= cx + _HALF_W + and i >= cy - _HALF_H + and i <= cy + _HALF_H +end + +function CAM:tilesInRange() + local cx, cy = self:position() -- start point + local rx, ry + cx = math.floor(cx / _TILE_W - _HALF_W) + cy = math.floor(cy / _TILE_H - _HALF_H) + rx = math.ceil(cx + 2 * _HALF_W) + ry = math.ceil(cy + 2 * _HALF_H) + local init_s = { cy, cx - 1 } + return function(s) + s[2] = s[2] + 1 + + -- advance line + if s[2] > rx then + s[2] = cx + s[1] = s[1] + 1 + -- check for end of lines + if s[1] > ry then return end + end + + return s[1], s[2] + end, init_s +end + +return CAM + diff --git a/game/common/color.lua b/game/common/color.lua new file mode 100644 index 00000000..3db0cbb7 --- /dev/null +++ b/game/common/color.lua @@ -0,0 +1,95 @@ + +local math = require 'common.math' +local vec3 = require 'cpml'.vec3 + +local Color = require 'lux.prototype' :new { __type = 'color' } + +local function _err_type_mismatch(a, b, op) + return error(string.format("Invalid operation with color (%s %s %s)", + type(a), op, type(b))) +end + +function Color:__init() + for i = 1, 4 do + local ccode = self[i] + local ccode_type = type(ccode) + self[i] = ccode or 1 + assert(type(ccode == 'number'), "Cannot create color using non-number value!") + end +end + +function Color.__mul(a, b) + if type(a) == 'table' and a.__type == 'color' and type(b) == 'table' and b.__type == 'color' then + return Color:new { + a[1] * b[1], + a[2] * b[2], + a[3] * b[3], + a[4] * b[4], + } + elseif type(a) == 'table' and a.__type == 'color' and type(b) == 'number' then + return Color:new {a[1] * b, a[2] * b, a[3] * b, a[4] * b} + elseif type(a) == 'number' and type(b) == 'table' and b.__type == 'color' then + return Color:new {b[1] * a, b[2] * a, b[3] * a, b[4] * a} + end + return _err_type_mismatch(a, b, '*') +end + +function Color.__add(a, b) + if type(a) == 'table' and type(b) == 'table' + and a.__type == 'color' and b.__type == 'color' then + return Color:new { + math.min(1, a[1] + b[1]), + math.min(1, a[2] + b[2]), + math.min(1, a[3] + b[3]), + math.min(1, a[4] + b[4]), + } + end + return _err_type_mismatch(a, b, '+') +end + +function Color.__sub(a, b) + return Color.__add(a, -1*b) +end + +function Color:__tostring() + return string.format("Color(%03d, %03d, %03d, %03d)", + math.round(self[1] * 255), + math.round(self[2] * 255), + math.round(self[3] * 255), + math.round(self[4] * 255)) +end + +function Color:unpack() + return self[1], self[2], self[3], self[4] +end + +--- Converts HSV to RGB. (input and output range: 0 - 255) +-- From: https://love2d.org/wiki/HSV_color +function Color.fromHSV(h, s, v, a) + if s <= 0 then return v,v,v end + h, s, v = h/256*6, s/255, v/255 + local c = v*s + local x = (1-math.abs((h%2)-1))*c + local m,r,g,b = (v-c), 0,0,0 + if h < 1 then r,g,b = c,x,0 + elseif h < 2 then r,g,b = x,c,0 + elseif h < 3 then r,g,b = 0,c,x + elseif h < 4 then r,g,b = 0,x,c + elseif h < 5 then r,g,b = x,0,c + else r,g,b = c,0,x + end return Color:new {(r+m), (g+m), (b+m), a} +end + +function Color.fromInt(r, g, b, a) + local c = {} + if type(r) == "table" then + for i = 1, 4 do + c[i] = (r[i] or 1) / 255 + end + return Color:new(c) + end + return Color:new {r/255, g/255, b/255, (a or 1)/255} +end + +return Color + diff --git a/game/common/graph.lua b/game/common/graph.lua new file mode 100644 index 00000000..61c23fa1 --- /dev/null +++ b/game/common/graph.lua @@ -0,0 +1,78 @@ + +local Prototype = require 'lux.prototype' :new {} +local Graph = Prototype:new {} + +-- Nodes are basically sector states +local function Node(id, zone, symb) + local state = {} + state.id = id + state.zone = zone + state.specname = symb + state.exits = {} + return state +end + + +local function NodeInfo(node) + return string.format("%s(%s:%s)", node.id, node.zone, node.specname) +end + +-- Create Graph +function Graph:create(idgenerator) + local newgraph = Graph:new {} + newgraph.idgen = idgenerator + newgraph.nodes = {} + newgraph.edges = {} + return newgraph +end + +-- Create Node in Graph +function Graph:addNode(zone, symb) + local id = self.idgen.newID() + local node = Node(id, zone, symb) + self.nodes[id] = node + return id +end + +-- Remove Node from Graph +function Graph:removeNode(id) + local node = _nodes[id] + for other_id in pairs(node.exits) do + self:disconnect(id, other_id) + end + self.nodes[id] = nil +end + +-- Connect Nodes in Graph +function Graph:connect(id1, id2) + local nodes = self.nodes + assert(nodes[id1] and nodes[id2], "Invalid node id.") + nodes[id1].exits[id2] = {pos = false, target_pos = false} + nodes[id2].exits[id1] = {pos = false, target_pos = false} + --[[-- + printf("Connecting [%s] to [%s]", + NodeInfo(self:getNode(id1)), + NodeInfo(self:getNode(id2))) + --]]-- +end + +-- Disconnect Nodes in Graph +function Graph:disconnect(id1, id2) + local nodes = self.nodes + assert(nodes[id1] and nodes[id2], "Invalid node id.") + nodes[id1].exits[id2] = nil + nodes[id2].exits[id1] = nil + --[[-- + printf("Disconnecting [%s] to [%s]", + NodeInfo(self:getNode(id1)), + NodeInfo(self:getNode(id2))) + --]]-- +end + +-- Get Node in Graph +function Graph:getNode(id) + return self.nodes[id] +end + +return Graph + diff --git a/game/common/heap.lua b/game/common/heap.lua new file mode 100644 index 00000000..3a19459e --- /dev/null +++ b/game/common/heap.lua @@ -0,0 +1,71 @@ + +local Prototype = require 'lux.prototype' + +--HEAP-- +local Heap = Prototype:new() + +local function cmp(a, b) + return a[2] < b[2] +end + +local function maintain_up(array, i) + local parent = math.floor(i/2) + + if parent > 0 and cmp(array[i], array[parent]) then + local swap = array[i] + array[i] = array[parent] + array[parent] = swap + maintain_up(array, parent) + end +end + +local function maintain_down(array, i, limit) + local left = i*2 + local right = left+1 + local higher = false + + if left <= limit and cmp(array[left], array[i]) then + higher = left + else + higher = i + end + + if right <= limit and cmp(array[right], array[i]) then + higher = right + end + + if higher ~= i then + local swap = array[i] + array[i] = array[higher] + array[higher] = swap + maintain_down(array, higher, limit) + end +end + +function Heap:getNext() + local item = self.items[1] + self.items[1] = self.items[self.size] + self.items[self.size] = nil + self.size = self.size - 1 + maintain_down(self.items, 1, self.size) + return unpack(item) +end + +function Heap:add(e, rank) + rank = rank or 0 + self.size = self.size + 1 + self.items[self.size] = {e, rank} + maintain_up(self.items, self.size) +end + +function Heap:isEmpty() + return #self.items == 0 +end + +Heap.__init = { + items = {}, + size = 0, +} + +return Heap + diff --git a/game/common/idgenerator.lua b/game/common/idgenerator.lua new file mode 100644 index 00000000..f498bf6c --- /dev/null +++ b/game/common/idgenerator.lua @@ -0,0 +1,24 @@ + +local IDGenerator = require 'lux.class' :new{} + +local ID_BASE = 1000 +local ID_FORMAT = "#%d" + +function IDGenerator:instance(obj, first_id) + + local _next_id = first_id or 1 + + function obj.getNextID() + return _next_id + end + + function obj.newID() + local id = ID_FORMAT:format(ID_BASE + _next_id) + _next_id = _next_id + 1 + return id + end + +end + +return IDGenerator + diff --git a/game/common/math.lua b/game/common/math.lua new file mode 100644 index 00000000..0d89d527 --- /dev/null +++ b/game/common/math.lua @@ -0,0 +1,11 @@ + +local math = setmetatable({}, {__index=math}) + +function math.round(n,...) + if n then + return math.floor(n+.5), math.round(...) + end +end + +return math + diff --git a/game/common/random.lua b/game/common/random.lua new file mode 100644 index 00000000..89775d7c --- /dev/null +++ b/game/common/random.lua @@ -0,0 +1,88 @@ + +local RUNFLAGS = require 'infra.runflags' + +--HELPERS-- +local floor = math.floor + +--LOCALS-- +local RANDOM = {} +local _rng = love.math.newRandomGenerator() +local _safe = love.math.random + +--METHODS-- +function RANDOM.safeGenerate(e, d) + return _safe(e, d) +end + +function RANDOM.setSafeSeed(safeseed) + love.math.setRandomSeed(safeseed) +end + +function RANDOM.getSafeSeed() + return love.math.getRandomSeed() +end + +function RANDOM.generate(e, d) + return _rng:random(e, d) +end + +function RANDOM.generateOdd(e, d) + assert(d > e or not d and e > 0, "Invalid arguments for function `odd`.") + if not d then + d = e + e = 1 + end + if e % 2 == 0 then e = e + 1 end + if d % 2 == 0 and (d - e) % 2 == 0 then d = d - 1 end + return e + _rng:random(0, floor((d - e) / 2)) * 2 +end + +function RANDOM.generateEven(e, d) + assert(d > e or not d and e > 0, "Invalid arguments for function `even`.") + if not d then + d = e + e = 0 + end + if e % 2 == 1 then e = e + 1 end + if d % 2 == 1 and (d - e) % 2 == 1 then d = d - 1 end + return e + _rng:random(0, floor((d - e) / 2)) * 2 +end + +function RANDOM.rollDice(n, d) + local sum = 0 + for i = 1, n do + sum = sum + (d == 1 and 1 or RANDOM.generate(1, d)) + end + return sum +end + +function RANDOM.shuffle(t) + local n = #t + for i=1,n do + local j = RANDOM.generate(i,n) + t[i], t[j] = t[j], t[i] + end +end + +function RANDOM.generateSeed() + return tonumber(tostring(os.time()):sub(-7):reverse()) +end + +function RANDOM.setSeed(seed) + return _rng:setSeed(seed) +end + +function RANDOM.getSeed() + return _rng:getSeed() +end + +function RANDOM.getState() + return _rng:getState() +end + +function RANDOM.setState(state) + return _rng:setState(state) +end + +return RANDOM + diff --git a/game/common/rect.lua b/game/common/rect.lua new file mode 100644 index 00000000..ebed2999 --- /dev/null +++ b/game/common/rect.lua @@ -0,0 +1,47 @@ + +local Rectangle = require 'lux.class' :new{} + +-- dependencies +local Vector2 = require 'cpml.modules.vec2' + +function Rectangle:instance(obj, x, y, w, h) + local _pos = Vector2(x, y) + local _dim = Vector2(w, h) + + function obj.getPos () + return _pos + end + + function obj.getDim () + return _dim + end + + function obj.getMax() + return _pos + _dim + end + + obj.getMin = obj.getPos + + function obj.getParams() + return _pos.x, _pos.y, _dim.x, _dim.y + end + + function obj.intersect (obj2) + local a, b = obj, obj2 + local amin = a.getMin() + local bmin = b.getMin() + local amax = a.getMax() + local bmax = b.getMax() + if amin.x > bmax.x or + amin.y > bmax.y or + amax.x < bmin.x or + amax.y < bmin.y + then return false + end + return true + end + +end + +return Rectangle + diff --git a/game/common/tile.lua b/game/common/tile.lua new file mode 100644 index 00000000..413d193a --- /dev/null +++ b/game/common/tile.lua @@ -0,0 +1,12 @@ + +local TILE = {} + +local abs = math.abs +local max = math.max + +function TILE.dist(i1, j1, i2, j2) + return max(abs(i1 - i2), abs(j1 - j2)) +end + +return TILE + diff --git a/game/common/unionfind.lua b/game/common/unionfind.lua new file mode 100644 index 00000000..e15da49b --- /dev/null +++ b/game/common/unionfind.lua @@ -0,0 +1,53 @@ + +local UnionFind = require 'lux.class' :new{} + +function UnionFind:instance(obj, e) + local _parent = obj + local _rank = 1 + local _e = e + + function obj.getElement() + return _e + end + + function obj.getParent() + return _parent + end + + function obj.setParent(p) + p.addRank(_rank) + _parent = p + return _parent + end + + function obj.addRank(i) + _rank = _rank + i + end + + function obj.getRank() + return _rank + end + + function obj.find() + if _parent == obj then return obj end + local p = _parent + while p ~= p.getParent() do + p = p.getParent() + end + _parent = p + return _parent + end + +end + +function UnionFind:unite(obj1, obj2) + local p1 = obj1.find() + local p2 = obj2.find() + if p1.getRank() >= p2.getRank() then + return p2.setParent(p1) + else + return p1.setParent(p2) + end +end + +return UnionFind diff --git a/game/common/visibility.lua b/game/common/visibility.lua new file mode 100644 index 00000000..3ef65b5d --- /dev/null +++ b/game/common/visibility.lua @@ -0,0 +1,249 @@ +--Function that manipulate an actor's field of view +local SCHEMATICS = require 'domain.definitions.schematics' + +--LOCAL FUNCTIONS DECLARATIONS-- + +local updateOctant +local newShadowLine +local isShadowLineFull +local newShadow +local shadowContainsAnother +local visibilityOfShadow +local addProjection +local transformOctant +local projectTile + +local funcs = {} + + +function funcs.purgeFov(sector) + local w, h = sector:getDimensions() + local fov = {} + for i = 1, h do + fov[i] = fov[i] or {} + for j = 1, w do + fov[i][j] = false -- Invisible and not seen + end + end + return fov +end + +--- Reset actor field of view based on a given sector +function funcs.resetFov(fov, sector) + + local w, h = sector:getDimensions() + for i = 1, h do + fov[i] = fov[i] or {} + for j = 1, w do + if fov[i][j] then + fov[i][j] = 0 -- Invisible but seen + end + end + end + +end + +--Update actors field of view based on his position in a given sector +function funcs.updateFov(actor, sector) + funcs.resetFov(actor:getFov(sector), sector) + for octant = 1, 8 do + updateOctant(actor, sector, octant) + end +end + +------------------- +--LOCAL FUNCTIONS-- +------------------- + +function updateOctant(actor, sector, octant) + local line = newShadowLine() + local full_shadow = false + + --Actor current position + local actor_i, actor_j = actor:getPos() + local fov = actor:getFov(sector) + + local row = 0 + while true do + + local d_i, d_j = transformOctant(row, 0, octant) + local pos = {actor_i + d_i, actor_j + d_j} + + --Check if tile is inside sector + if not sector:isInside(pos[1],pos[2]) then break end + if row > actor:getFovRange() then + full_shadow = true + end + + for col = 0, row do + local d_i, d_j = transformOctant(row, col, octant) + local pos = {actor_i + d_i, actor_j + d_j} + + --Check if tile is inside sector + if not sector:isInside(pos[1],pos[2]) then break end + + if full_shadow then + if fov[pos[1]][pos[2]] then --Was seen once + fov[pos[1]][pos[2]] = 0 --Make it invisible + end + else + --Set visibility of tile + local projection = projectTile(row, col) + local visible = 1 - visibilityOfShadow(line, projection) + if fov[pos[1]][pos[2]] or visible == 1 then + fov[pos[1]][pos[2]] = visible + end + + --Add any wall tiles to the shadow line + if visible == 1 and + sector.tiles[pos[1]][pos[2]] and + sector.tiles[pos[1]][pos[2]].type == SCHEMATICS.WALL then + addProjection(line, projection) + fullShadow = fullShadow or isShadowLineFull(line) + end + end + end + row = row + 1 + end +end + +--Create an empty shadow line table +function newShadowLine() + return {shadow_list = {}} +end + +--Checks if a shadow line is complete from start to end +function isShadowLineFull(shadow_line) + local list = shadow_line.shadow_list + return (#list == 1 and list[1].start == 0 and list[1].finish == 1) +end + +local MAX = 80 + +function printShadowLine(shadow_line) + local string = {} + for i=1,MAX do + string[i] = '.' + end + for _,shadow in ipairs(shadow_line.shadow_list) do + local a,b = shadow.start*MAX,shadow.finish*MAX + a = math.floor(a + 0.5) + b = math.floor(b + 0.5) + for i=a,b do + string[i] = 'X' + end + end + print(table.concat(string)) +end + +--Create a shadow table +function newShadow(_start, _finish) + assert(_start, "start argument not valid for new shadow") + assert(_finish, "finish argument not valid for new shadow") + return { + start = _start, + finish = _finish + } +end + +--Checks if a shadow completly contains another and +-- returns the ratio other shadow is covered +function shadowContainsAnother(shadow, other_shadow) + + --Is completly contained + if other_shadow.finish <= shadow.finish and + other_shadow.start >= shadow.start then + return 1 + else + return 0 + end + +end + +--Returns how visible is a projection given a line of shadows +--From 0 (visible) to 1 (not visible) +function visibilityOfShadow(shadow_line, projection) + for _,shadow in ipairs(shadow_line.shadow_list) do + if shadowContainsAnother(shadow, projection) == 1 then + return 1 + end + end + + return 0 +end + +function addProjection(line, projection) + local list = line.shadow_list + local index = 1; + + --Figure out where to slot the new shadow in the list + while index <= #list do + --Stop when we hit the insertion point. + if list[index].start >= projection.start then break end + index = index + 1 + end + + --Check if projection overlaps the previous or next shadow + local overlappingPrevious + if index > 1 and list[index - 1].finish > projection.start then + overlappingPrevious = list[index - 1] + end + + local overlappingNext + if index <= #list and list[index].start < projection.finish then + overlappingNext = list[index] + end + + --Insert and unify with overlapping shadows. + if overlappingNext then + if overlappingPrevious then + --Overlaps both, so unify one and delete the other. + overlappingPrevious.finish = overlappingNext.finish + table.remove(list,index) + else + --Overlaps the next one, so unify it with that. + overlappingNext.start = projection.start + end + else + if overlappingPrevious then + --Overlaps the previous one, so unify it with that. + overlappingPrevious.finish = projection.finish + else + --Does not overlap anything, so insert. + table.insert(list, index, projection) + end + end + +end + +--Transforms row and column values to correspondent octant +function transformOctant(row, col, octant) + if octant == 1 then + return col, -row + elseif octant == 2 then + return row, -col + elseif octant == 3 then + return row, col + elseif octant == 4 then + return col, row + elseif octant == 5 then + return -col, row + elseif octant == 6 then + return -row, col + elseif octant == 7 then + return -row, -col + elseif octant == 8 then + return -col, -row + else + error("not a valid octant value:"..octant) + end +end + +--Create a shadow correspondent to the projected silhouette of given tile +function projectTile(row, col) + local top_left = col/(row+2) + local bottom_right = (col+1)/(row+1) + return newShadow(top_left, bottom_right) +end + +return funcs diff --git a/game/conf.lua b/game/conf.lua new file mode 100644 index 00000000..673e4c29 --- /dev/null +++ b/game/conf.lua @@ -0,0 +1,44 @@ +--MODULE FOR CONFIGURING STUFF-- + +function love.conf(t) + t.identity = "backdoor" -- The name of the save directory (string) + t.version = "11.0" -- The LÖVE version this game was made for (string) + t.console = false -- Attach a console (boolean, Windows only) + t.accelerometerjoystick = false -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean) + t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean) + t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean) + + t.window.title = "Backdoor Route" -- The window title (string) + t.window.icon = nil -- Filepath to an image to use as the window's icon (string) + t.window.width = 1280 -- The window width (number) + t.window.height = 720 -- The window height (number) + t.window.borderless = false -- Remove all border visuals from the window (boolean) + t.window.resizable = false -- Let the window be user-resizable (boolean) + t.window.minwidth = 1 -- Minimum window width if the window is resizable (number) + t.window.minheight = 1 -- Minimum window height if the window is resizable (number) + t.window.fullscreen = false -- Enable fullscreen (boolean) + t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string) + t.window.vsync = true -- Enable vertical sync (boolean) + t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number) + t.window.display = 1 -- Index of the monitor to show the window in (number) + t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean) + t.window.x = nil -- The x-coordinate of the window's position in the specified display (number) + t.window.y = nil -- The y-coordinate of the window's position in the specified display (number) + + t.modules.audio = true -- Enable the audio module (boolean) + t.modules.event = true -- Enable the event module (boolean) + t.modules.graphics = true -- Enable the graphics module (boolean) + t.modules.image = true -- Enable the image module (boolean) + t.modules.joystick = true -- Enable the joystick module (boolean) + t.modules.keyboard = true -- Enable the keyboard module (boolean) + t.modules.math = true -- Enable the math module (boolean) + t.modules.mouse = true -- Enable the mouse module (boolean) + t.modules.physics = true -- Enable the physics module (boolean) + t.modules.sound = true -- Enable the sound module (boolean) + t.modules.system = true -- Enable the system module (boolean) + t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update + t.modules.touch = true -- Enable the touch module (boolean) + t.modules.video = true -- Enable the video module (boolean) + t.modules.window = true -- Enable the window module (boolean) + t.modules.thread = true -- Enable the thread module (boolean) +end diff --git a/game/database/domains/action/bolting.json b/game/database/domains/action/bolting.json new file mode 100644 index 00000000..8d8b0aed --- /dev/null +++ b/game/database/domains/action/bolting.json @@ -0,0 +1,36 @@ +{ + "cost":20, + "playpoints":0, + "ability":{ + "inputs":[{ + "aoe-hint":1, + "name":"choose_target", + "body-only":{ + "body-type":false + }, + "empty-tile":false, + "type":"input", + "non-wall":false, + "output":"pos", + "max-range":5 + },{ + "type":"operator", + "pos":"=pos", + "name":"get_body_at", + "output":"target" + },{ + "which":"ARC", + "type":"operator", + "output":"arc", + "name":"get_attribute" + }], + "effects":[{ + "attr":"=arc", + "target":"=target", + "base":2, + "type":"effect", + "name":"damage_on_target", + "sfx":false + }] + } +} \ No newline at end of file diff --git a/game/database/domains/action/bullet.json b/game/database/domains/action/bullet.json new file mode 100644 index 00000000..829e3381 --- /dev/null +++ b/game/database/domains/action/bullet.json @@ -0,0 +1,53 @@ +{ + "ability":{ + "inputs":[{ + "type":"input", + "output":"dir", + "name":"choose_dir", + "body-block":true + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "output":"body_self", + "name":"get_actor_body", + "actor":"=self" + },{ + "type":"operator", + "body":"=body_self", + "name":"get_body_pos", + "output":"origin" + },{ + "maxrange":5, + "dir":"=dir", + "type":"operator", + "pos":"=origin", + "name":"hitscan", + "output":"hit_pos" + },{ + "pos":"=hit_pos", + "dir":"=dir", + "type":"operator", + "name":"translated", + "output":"target_pos" + },{ + "which":"COR", + "output":"cor", + "name":"get_attribute", + "type":"operator" + }], + "effects":[{ + "attr":"=cor", + "center":"=target_pos", + "size":1, + "base":5, + "ignore_owner":false, + "name":"damage_on_area", + "type":"effect" + }] + }, + "cost":20, + "playpoints":0 +} \ No newline at end of file diff --git a/game/database/domains/action/glare.json b/game/database/domains/action/glare.json new file mode 100644 index 00000000..18d1fe74 --- /dev/null +++ b/game/database/domains/action/glare.json @@ -0,0 +1,37 @@ +{ + "playpoints":0, + "ability":{ + "effects":[{ + "attr":"=arc", + "target":"=target", + "type":"effect", + "base":3, + "name":"damage_on_target", + "sfx":false + }], + "inputs":[{ + "empty-tile":false, + "output":"pos", + "aoe-hint":0, + "max-range":2, + "dif-fact":false, + "type":"input", + "non-wall":false, + "name":"choose_target", + "body-only":{ + "body-type":false + } + },{ + "type":"operator", + "pos":"=pos", + "output":"target", + "name":"get_body_at" + },{ + "type":"operator", + "name":"get_attribute", + "output":"arc", + "which":"ARC" + }] + }, + "cost":20 +} \ No newline at end of file diff --git a/game/database/domains/action/punch.json b/game/database/domains/action/punch.json new file mode 100644 index 00000000..1f657a92 --- /dev/null +++ b/game/database/domains/action/punch.json @@ -0,0 +1,32 @@ +{ + "cost":20, + "ability":{ + "effects":[{ + "attr":"=cor", + "target":"=target", + "type":"effect", + "base":6, + "name":"damage_on_target", + "sfx":"example" + }], + "inputs":[{ + "output":"pos", + "max-range":1, + "type":"input", + "body-only":{ + }, + "name":"choose_target" + },{ + "type":"operator", + "name":"get_attribute", + "output":"cor", + "which":"COR" + },{ + "type":"operator", + "pos":"=pos", + "output":"target", + "name":"get_body_at" + }] + }, + "playpoints":0 +} \ No newline at end of file diff --git a/game/database/domains/action/tackle.json b/game/database/domains/action/tackle.json new file mode 100644 index 00000000..b736a646 --- /dev/null +++ b/game/database/domains/action/tackle.json @@ -0,0 +1,36 @@ +{ + "playpoints":0, + "ability":{ + "effects":[{ + "attr":"=cor", + "target":"=target", + "type":"effect", + "base":2, + "name":"damage_on_target", + "sfx":false + }], + "inputs":[{ + "aoe-hint":0, + "output":"pos", + "body-only":{ + "body-type":false + }, + "non-wall":false, + "type":"input", + "empty-tile":false, + "name":"choose_target", + "max-range":1 + },{ + "type":"operator", + "name":"get_attribute", + "output":"cor", + "which":"COR" + },{ + "type":"operator", + "pos":"=pos", + "output":"target", + "name":"get_body_at" + }] + }, + "cost":20 +} \ No newline at end of file diff --git a/game/database/domains/actor/arcanist.json b/game/database/domains/actor/arcanist.json new file mode 100644 index 00000000..e081f6a0 --- /dev/null +++ b/game/database/domains/actor/arcanist.json @@ -0,0 +1,25 @@ +{ + "extends":"sentient", + "signature":"bolting", + "behavior":"player", + "traits":[], + "cor":-2, + "arc":1, + "collection":false, + "ani":1, + "name":"Arcanist", + "initial_buffer":[{ + "amount":1, + "card":"heal" + },{ + "amount":1, + "card":"fireball" + },{ + "amount":4, + "card":"firebolt" + },{ + "amount":4, + "card":"catalyst" + }], + "description":"After years of effort and study, you have\nbecome proficient in the arcane arts passed down\nsince time immemorial. You strongly believe there\nis nothing the power of your magic cannot achieve,\nso long as you have the time to prepare." +} \ No newline at end of file diff --git a/game/database/domains/actor/brawler.json b/game/database/domains/actor/brawler.json new file mode 100644 index 00000000..886efa7c --- /dev/null +++ b/game/database/domains/actor/brawler.json @@ -0,0 +1,23 @@ +{ + "extends":"sentient", + "behavior":"player", + "cor":1, + "arc":-2, + "collection":false, + "ani":1, + "name":"Brawler", + "initial_buffer":[{ + "amount":1, + "card":"calming_down" + },{ + "amount":1, + "card":"laser-spear" + },{ + "amount":4, + "card":"rage" + },{ + "amount":4, + "card":"trained-strike" + }], + "description":"You find that close-quarter skirmishes and martial\narts in general suit your tastes and abilities\nrather well. Anything that does not involve\nbreaking things is a test to your patience." +} \ No newline at end of file diff --git a/game/database/domains/actor/dumb.json b/game/database/domains/actor/dumb.json new file mode 100644 index 00000000..c69256cf --- /dev/null +++ b/game/database/domains/actor/dumb.json @@ -0,0 +1,12 @@ +{ + "ani":-2, + "signature":"tackle", + "behavior":"aggro", + "cor":-2, + "arc":-2, + "collection":"anything", + "extends":false, + "description":"Oh honey...", + "name":"Dumb", + "initial_buffer":[] +} diff --git a/game/database/domains/actor/fiendish.json b/game/database/domains/actor/fiendish.json new file mode 100644 index 00000000..6d3ad247 --- /dev/null +++ b/game/database/domains/actor/fiendish.json @@ -0,0 +1,12 @@ +{ + "extends":"grinful", + "signature":"glare", + "behavior":"aggro", + "cor":-1, + "arc":2, + "collection":"anything", + "description":"Stronger spellcaster monster, evil by nature.", + "name":"Fiendish", + "ani":-1, + "initial_buffer":[] +} diff --git a/game/database/domains/actor/gadgeteer.json b/game/database/domains/actor/gadgeteer.json new file mode 100644 index 00000000..61cfc48a --- /dev/null +++ b/game/database/domains/actor/gadgeteer.json @@ -0,0 +1,24 @@ +{ + "extends":"sentient", + "behavior":"player", + "traits":[], + "cor":1, + "arc":1, + "collection":false, + "ani":-2, + "initial_buffer":[{ + "amount":1, + "card":"impact-vest" + },{ + "amount":1, + "card":"turret" + },{ + "amount":4, + "card":"push" + },{ + "amount":4, + "card":"energy-beam" + }], + "name":"Gagdeteer", + "description":"There is nothing like the low buzzing of your\ntrinkets and drones floating around the workplace.\nTo you, every unexpected situation is but an\nopportunity to show off your newest gadgetry." +} \ No newline at end of file diff --git a/game/database/domains/actor/grinful.json b/game/database/domains/actor/grinful.json new file mode 100644 index 00000000..7b434371 --- /dev/null +++ b/game/database/domains/actor/grinful.json @@ -0,0 +1,12 @@ +{ + "extends":"strong", + "signature":"glare", + "behavior":"aggro", + "cor":-1, + "arc":1, + "collection":"anything", + "initial_buffer":[], + "ani":-1, + "name":"Grinning", + "description":"Spellcaster monster, evil by nature." +} diff --git a/game/database/domains/actor/newbieslayer.json b/game/database/domains/actor/newbieslayer.json new file mode 100644 index 00000000..350d8a94 --- /dev/null +++ b/game/database/domains/actor/newbieslayer.json @@ -0,0 +1,12 @@ +{ + "extends":"strong", + "signature":"punch", + "behavior":"aggro", + "cor":2, + "arc":1, + "collection":"anything", + "description":"The newbie's nightmares...", + "name":"Newbie Killer", + "ani":1, + "initial_buffer":[] +} diff --git a/game/database/domains/actor/sentient.json b/game/database/domains/actor/sentient.json new file mode 100644 index 00000000..7d56a57e --- /dev/null +++ b/game/database/domains/actor/sentient.json @@ -0,0 +1,12 @@ +{ + "extends":false, + "signature":"tackle", + "behavior":"player", + "cor":0, + "arc":0, + "collection":false, + "description":"", + "initial_buffer":[], + "ani":0, + "name":"Sentient" +} diff --git a/game/database/domains/actor/strong.json b/game/database/domains/actor/strong.json new file mode 100644 index 00000000..15693d4c --- /dev/null +++ b/game/database/domains/actor/strong.json @@ -0,0 +1,12 @@ +{ + "extends":"sentient", + "signature":"tackle", + "behavior":"aggro", + "cor":0, + "arc":0, + "collection":"anything", + "name":"Sentient", + "ani":0, + "initial_buffer":[], + "description":"A strong sentient individual,\nwho explores the ruins to kill\nand savage unnaware adventurers." +} diff --git a/game/database/domains/appearance/corgi.json b/game/database/domains/appearance/corgi.json new file mode 100644 index 00000000..556c4638 --- /dev/null +++ b/game/database/domains/appearance/corgi.json @@ -0,0 +1,3 @@ +{ + "idle":"corgi-idle" +} \ No newline at end of file diff --git a/game/database/domains/appearance/demobody.json b/game/database/domains/appearance/demobody.json new file mode 100644 index 00000000..8df5f697 --- /dev/null +++ b/game/database/domains/appearance/demobody.json @@ -0,0 +1,3 @@ +{ + "idle":"demobody-idle" +} \ No newline at end of file diff --git a/game/database/domains/appearance/eyedemon.json b/game/database/domains/appearance/eyedemon.json new file mode 100644 index 00000000..40637d60 --- /dev/null +++ b/game/database/domains/appearance/eyedemon.json @@ -0,0 +1,3 @@ +{ + "idle":"eyedemon-idle" +} \ No newline at end of file diff --git a/game/database/domains/appearance/hearthborn.json b/game/database/domains/appearance/hearthborn.json new file mode 100644 index 00000000..811c8e10 --- /dev/null +++ b/game/database/domains/appearance/hearthborn.json @@ -0,0 +1,3 @@ +{ + "idle":"hearthborn-idle" +} \ No newline at end of file diff --git a/game/database/domains/appearance/hillborn.json b/game/database/domains/appearance/hillborn.json new file mode 100644 index 00000000..147b27ee --- /dev/null +++ b/game/database/domains/appearance/hillborn.json @@ -0,0 +1,3 @@ +{ + "idle":"hillborn-idle" +} \ No newline at end of file diff --git a/game/database/domains/appearance/imp.json b/game/database/domains/appearance/imp.json new file mode 100644 index 00000000..1bc107be --- /dev/null +++ b/game/database/domains/appearance/imp.json @@ -0,0 +1,3 @@ +{ + "idle":"imp-idle" +} \ No newline at end of file diff --git a/game/database/domains/appearance/slime.json b/game/database/domains/appearance/slime.json new file mode 100644 index 00000000..3ddda67f --- /dev/null +++ b/game/database/domains/appearance/slime.json @@ -0,0 +1,3 @@ +{ + "idle":"slime-idle" +} \ No newline at end of file diff --git a/game/database/domains/body/corgi.json b/game/database/domains/body/corgi.json new file mode 100644 index 00000000..7baccf4b --- /dev/null +++ b/game/database/domains/body/corgi.json @@ -0,0 +1,28 @@ +{ + "extends":"demihuman", + "faction":"agressive", + "drops":{ + "2":{ + "droptype":"pack", + "droprate":10 + }, + "drops":[{ + "droptype":"pack", + "droprate":7 + }], + "1":{ + "droptype":"pack", + "droprate":100 + }, + "3":{ + "droptype":"food", + "droprate":50 + } + }, + "res":-1, + "con":-1, + "appearance":"corgi", + "fin":-1, + "name":"Corgiborn", + "description":"Stubby legs, a heart-shaped butt,\na happy goofy face. But don't be fooled!\nNot all of them are friendly..." +} \ No newline at end of file diff --git a/game/database/domains/body/demihuman.json b/game/database/domains/body/demihuman.json new file mode 100644 index 00000000..60add3c3 --- /dev/null +++ b/game/database/domains/body/demihuman.json @@ -0,0 +1,10 @@ +{ + "extends":false, + "faction":"allied", + "res":0, + "fin":0, + "appearance":"hearthborn", + "con":0, + "name":"Demihuman", + "description":"Humanoid people in general." +} diff --git a/game/database/domains/body/eye.json b/game/database/domains/body/eye.json new file mode 100644 index 00000000..c5cacb30 --- /dev/null +++ b/game/database/domains/body/eye.json @@ -0,0 +1,14 @@ +{ + "extends":"imp", + "faction":"agressive", + "drops":[{ + "droptype":"pack", + "droprate":50 + }], + "res":-2, + "con":-2, + "appearance":"eyedemon", + "fin":0, + "name":"Eye", + "description":"Floating demon, a hellish eye which stares\nits victims into shivers. A stink eye is not\nall it does, however: this fiend can cast powerful\narcane spells to strike down its foe." +} diff --git a/game/database/domains/body/hearthborn.json b/game/database/domains/body/hearthborn.json new file mode 100644 index 00000000..16712299 --- /dev/null +++ b/game/database/domains/body/hearthborn.json @@ -0,0 +1,10 @@ +{ + "extends":"demihuman", + "res":0, + "fin":-1, + "faction":"allied", + "appearance":"hearthborn", + "con":1, + "name":"Hearthborn", + "description":"With dark-orange skin, colorful hair and round\nnoses, these humanoids can boost their muscles for\na few bare seconds by pumping their blood faster\nand hotter, unleashing steam from their bodies." +} diff --git a/game/database/domains/body/hillborn.json b/game/database/domains/body/hillborn.json new file mode 100644 index 00000000..8b412a83 --- /dev/null +++ b/game/database/domains/body/hillborn.json @@ -0,0 +1,10 @@ +{ + "extends":"demihuman", + "res":-1, + "fin":1, + "faction":"allied", + "appearance":"hillborn", + "con":0, + "name":"Hillborn", + "description":"With greyish blue skin partially covered by\ndark tattoo-like lines and patterns, these\nhumanoids bear a variety of horns growing from\ntheir temples and are known for their quick legs." +} diff --git a/game/database/domains/body/imp.json b/game/database/domains/body/imp.json new file mode 100644 index 00000000..e4f3ae1f --- /dev/null +++ b/game/database/domains/body/imp.json @@ -0,0 +1,17 @@ +{ + "extends":"slime", + "faction":"agressive", + "drops":[{ + "droptype":"pack", + "droprate":70 + },{ + "droptype":"pack", + "droprate":20 + }], + "res":-2, + "con":-2, + "appearance":"imp", + "fin":-1, + "name":"Imp", + "description":"Lesser demons, mischievious little miscreations.\nThey are known for their trickster grin and their\naptitude with arcane spells. They are hella weak,\nthough: one strong blow and they are dead meat." +} diff --git a/game/database/domains/body/machine.json b/game/database/domains/body/machine.json new file mode 100644 index 00000000..a4e4a140 --- /dev/null +++ b/game/database/domains/body/machine.json @@ -0,0 +1,10 @@ +{ + "extends":false, + "name":"Machine", + "faction":"allied", + "res":2, + "appearance":"demobody", + "fin":0, + "con":0, + "description":"Heavy Metal." +} diff --git a/game/database/domains/body/rewasvat.json b/game/database/domains/body/rewasvat.json new file mode 100644 index 00000000..3781bc6d --- /dev/null +++ b/game/database/domains/body/rewasvat.json @@ -0,0 +1,28 @@ +{ + "extends":"corgi", + "faction":"agressive", + "drops":{ + "2":{ + "droptype":"pack", + "droprate":10 + }, + "drops":[{ + "droptype":"pack", + "droprate":7 + }], + "1":{ + "droptype":"pack", + "droprate":100 + }, + "3":{ + "droptype":"food", + "droprate":50 + } + }, + "res":0, + "con":0, + "appearance":"corgi", + "fin":0, + "name":"Rewasvat", + "description":"Rewaswat. Also known as the furry hater.\nOne day he befell the curse of the furries,\nand his fursona was given life. Now it roams\nthe ruins, in search of newbies to bark at." +} \ No newline at end of file diff --git a/game/database/domains/body/slime.json b/game/database/domains/body/slime.json new file mode 100644 index 00000000..4e7c67db --- /dev/null +++ b/game/database/domains/body/slime.json @@ -0,0 +1,20 @@ +{ + "extends":false, + "faction":"agressive", + "drops":[{ + "droptype":"pack", + "droprate":10 + },{ + "droptype":"food", + "droprate":80 + },{ + "droptype":"pack", + "droprate":10 + }], + "res":-1, + "con":-2, + "appearance":"slime", + "fin":-2, + "name":"Slime", + "description":"Delicious when fried." +} diff --git a/game/database/domains/card/ani+.json b/game/database/domains/card/ani+.json new file mode 100644 index 00000000..54f8e6b3 --- /dev/null +++ b/game/database/domains/card/ani+.json @@ -0,0 +1,14 @@ +{ + "attr":"ANI", + "widget":{ + "operators":[{ + "attr":"ANI", + "op":"+", + "val":1 + }], + "trigger":"on_act", + "charges":1 + }, + "one_time":true, + "name":"Anima Up" +} \ No newline at end of file diff --git a/game/database/domains/card/arc+.json b/game/database/domains/card/arc+.json new file mode 100644 index 00000000..866abffd --- /dev/null +++ b/game/database/domains/card/arc+.json @@ -0,0 +1,14 @@ +{ + "attr":"ARC", + "one_time":true, + "widget":{ + "operators":[{ + "attr":"ARC", + "op":"+", + "val":1 + }], + "trigger":"on_act", + "charges":1 + }, + "name":"Arcana Up" +} \ No newline at end of file diff --git a/game/database/domains/card/auto-fire.json b/game/database/domains/card/auto-fire.json new file mode 100644 index 00000000..45150789 --- /dev/null +++ b/game/database/domains/card/auto-fire.json @@ -0,0 +1,58 @@ +{ + "attr":"COR", + "icon":false, + "set":false, + "desc":"", + "type-description":"", + "one_time":true, + "pp":0, + "name":"Auto-Fire", + "widget":{ + "operators":[], + "auto_activation":{ + "ability":{ + "inputs":[{ + "type":"input", + "name":"pos", + "output":"pos_self" + },{ + "body-type":false, + "output":"count", + "ignore-owner":true, + "pos":"=pos_self", + "range":3, + "type":"input", + "count":1, + "name":"has_nearby_bodies", + "ignore-same-faction":true + },{ + "ignore-owner":true, + "pos":"=pos_self", + "range":3, + "type":"operator", + "name":"find_nearest_body", + "output":"target", + "ignore-same-faction":true + },{ + "which":"COR", + "type":"operator", + "name":"get_attribute", + "output":"cor" + }], + "effects":[{ + "attr":"=cor", + "target":"=target", + "type":"effect", + "base":4, + "name":"damage_on_target", + "sfx":false + }] + }, + "trigger":"on_cycle" + }, + "status-tags":[], + "trigger":false, + "charges":0, + "placement":false + } +} \ No newline at end of file diff --git a/game/database/domains/card/battery.json b/game/database/domains/card/battery.json new file mode 100644 index 00000000..d11b483a --- /dev/null +++ b/game/database/domains/card/battery.json @@ -0,0 +1,31 @@ +{ + "attr":false, + "icon":false, + "set":false, + "desc":"", + "type-description":"", + "widget":{ + "operators":[], + "trigger":"on_cycle", + "charges":20, + "status-tags":[], + "auto_activation":{ + "ability":{ + "effects":[{ + "type":"effect", + "target":"=body_self", + "name":"destroy_body" + }], + "inputs":[{ + "type":"input", + "name":"body", + "output":"body_self" + }] + }, + "trigger":"on_done" + }, + "placement":false + }, + "name":"Battery", + "one_time":false +} \ No newline at end of file diff --git a/game/database/domains/card/blind.json b/game/database/domains/card/blind.json new file mode 100644 index 00000000..ee0794ee --- /dev/null +++ b/game/database/domains/card/blind.json @@ -0,0 +1,20 @@ +{ + "attr":"ANI", + "icon":false, + "set":false, + "desc":"", + "type-description":"", + "widget":{ + "operators":[{ + "attr":"FOV", + "op":"-", + "val":3 + }], + "trigger":"on_turn", + "status-tags":[], + "charges":4, + "placement":false + }, + "name":"Blind", + "one_time":true +} \ No newline at end of file diff --git a/game/database/domains/card/bolting.json b/game/database/domains/card/bolting.json new file mode 100644 index 00000000..33d21779 --- /dev/null +++ b/game/database/domains/card/bolting.json @@ -0,0 +1,52 @@ +{ + "attr":"ARC", + "icon":false, + "one_time":false, + "desc":"", + "type-description":"", + "set":false, + "name":"Bolting", + "widget":{ + "operators":[], + "trigger":false, + "activation":{ + "cost":20, + "ability":{ + "inputs":[{ + "dif-fact":true, + "output":"target_pos", + "aoe-hint":0, + "type":"input", + "body-only":{ + "body-type":false + }, + "max-range":4, + "non-wall":true, + "name":"choose_target", + "empty-tile":false + },{ + "type":"operator", + "pos":"=target_pos", + "name":"get_body_at", + "output":"target" + },{ + "type":"operator", + "output":"arc", + "name":"get_attribute", + "which":"ARC" + }], + "effects":[{ + "attr":"=arc", + "target":"=target", + "base":2, + "type":"effect", + "name":"damage_on_target", + "sfx":"example" + }] + } + }, + "charges":0, + "status-tags":[], + "placement":false + } +} diff --git a/game/database/domains/card/bullet.json b/game/database/domains/card/bullet.json new file mode 100644 index 00000000..3aba7da3 --- /dev/null +++ b/game/database/domains/card/bullet.json @@ -0,0 +1,68 @@ +{ + "attr":"COR", + "icon":false, + "set":false, + "desc":"", + "type-description":"", + "one_time":false, + "name":"Bullet", + "widget":{ + "operators":[], + "trigger":false, + "activation":{ + "cost":20, + "ability":{ + "inputs":[{ + "type":"input", + "output":"dir", + "body-block":true, + "name":"choose_dir" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "name":"get_actor_body", + "output":"self_body", + "actor":"=self" + },{ + "type":"operator", + "body":"=self_body", + "name":"get_body_pos", + "output":"self_pos" + },{ + "pos":"=self_pos", + "dir":"=dir", + "type":"operator", + "output":"hitscan", + "name":"hitscan", + "maxrange":4 + },{ + "pos":"=hitscan", + "dir":"=dir", + "type":"operator", + "name":"translated", + "output":"target_pos" + },{ + "type":"operator", + "output":"cor", + "name":"get_attribute", + "which":"COR" + }], + "effects":[{ + "attr":"=cor", + "center":"=target_pos", + "size":1, + "type":"effect", + "ignore_owner":true, + "name":"damage_on_area", + "base":3 + }] + } + }, + "charges":0, + "status-tags":[], + "placement":false + } +} diff --git a/game/database/domains/card/calming_down.json b/game/database/domains/card/calming_down.json new file mode 100644 index 00000000..f033dcf5 --- /dev/null +++ b/game/database/domains/card/calming_down.json @@ -0,0 +1,46 @@ +{ + "attr":"ANI", + "icon":"card-meditation", + "set":"brawler", + "desc":"", + "type-description":"", + "widget":{ + "operators":[], + "auto_activation":{ + "trigger":"on_cycle", + "ability":{ + "effects":[{ + "attr":"=ani", + "target":"=actor_body", + "type":"effect", + "name":"heal", + "base":2 + }], + "inputs":[{ + "type":"operator", + "name":"self", + "output":"self_actor" + },{ + "type":"operator", + "output":"actor_body", + "name":"get_actor_body", + "actor":"=self_actor" + },{ + "type":"operator", + "which":"ANI", + "output":"ani", + "name":"get_attribute" + }] + } + }, + "status-tags":[{ + "tag":"stun" + }], + "trigger":"on_cycle", + "charges":6, + "placement":false + }, + "pp":4, + "name":"Meditation", + "one_time":false +} \ No newline at end of file diff --git a/game/database/domains/card/catalyst.json b/game/database/domains/card/catalyst.json new file mode 100644 index 00000000..b2550037 --- /dev/null +++ b/game/database/domains/card/catalyst.json @@ -0,0 +1,52 @@ +{ + "attr":"ARC", + "icon":"card-focus-gem", + "one_time":false, + "art":false, + "desc":"", + "type-description":"", + "widget":{ + "operators":[{ + "op":"+", + "attr":"ARC", + "val":2 + }], + "trigger":"on_play", + "status-tags":[], + "trigger-condition":{ + "effects":[], + "inputs":[{ + "type":"input", + "name":"card", + "output":"card" + },{ + "attribute":"ARC", + "card":"=card", + "type":"input", + "name":"card_has_attribute", + "output":"ok1" + },{ + "card":"=card", + "type":"input", + "cardtype":"ART", + "name":"card_is_type", + "output":"ok2" + },{ + "type":"input", + "name":"card", + "output":"widget_self" + },{ + "lhs":"=card", + "type":"input", + "rhs":"=widget_self", + "name":"different_card", + "output":"ok3" + }] + }, + "charges":2, + "placement":"offhand" + }, + "pp":0, + "name":"Focus Gem", + "set":"arcanist" +} diff --git a/game/database/domains/card/consume.json b/game/database/domains/card/consume.json new file mode 100644 index 00000000..e173aef9 --- /dev/null +++ b/game/database/domains/card/consume.json @@ -0,0 +1,25 @@ +{ + "attr":"COR", + "icon":false, + "one_time":false, + "art":{ + "cost":10, + "art_ability":{ + "inputs":[{ + "type":"input", + "output":"label", + "name":"choose_consume_list", + "max":3 + }], + "effects":[{ + "type":"effect", + "name":"consume_cards", + "card_list":"=label" + }] + } + }, + "desc":"", + "type-description":"", + "name":"Consume", + "set":false +} \ No newline at end of file diff --git a/game/database/domains/card/cor+.json b/game/database/domains/card/cor+.json new file mode 100644 index 00000000..c10b296b --- /dev/null +++ b/game/database/domains/card/cor+.json @@ -0,0 +1,14 @@ +{ + "attr":"COR", + "one_time":true, + "widget":{ + "operators":[{ + "attr":"COR", + "op":"+", + "val":1 + }], + "trigger":"on_act", + "charges":1 + }, + "name":"Corporis Up" +} \ No newline at end of file diff --git a/game/database/domains/card/divination.json b/game/database/domains/card/divination.json new file mode 100644 index 00000000..2bef4ad3 --- /dev/null +++ b/game/database/domains/card/divination.json @@ -0,0 +1,21 @@ +{ + "attr":"ARC", + "icon":"card-divination", + "one_time":false, + "art":{ + "cost":20, + "art_ability":{ + "inputs":[], + "effects":[{ + "amount":2, + "name":"draw_card", + "type":"effect" + }] + } + }, + "desc":"Close your eyes and see beyond.", + "type-description":"", + "pp":2, + "name":"Divination", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/dropkick.json b/game/database/domains/card/dropkick.json new file mode 100644 index 00000000..d938ec0f --- /dev/null +++ b/game/database/domains/card/dropkick.json @@ -0,0 +1,55 @@ +{ + "attr":"COR", + "icon":"card-dropkick", + "one_time":false, + "art":{ + "cost":20, + "art_ability":{ + "inputs":[{ + "aoe-hint":0, + "max-range":1, + "output":"pos", + "non-wall":false, + "type":"input", + "empty-tile":false, + "name":"choose_target", + "body-only":{ + "body-type":false + } + },{ + "type":"operator", + "output":"cor", + "name":"get_attribute", + "which":"COR" + },{ + "type":"operator", + "pos":"=pos", + "name":"get_body_at", + "output":"target" + }], + "effects":[{ + "attr":"=cor", + "target":"=target", + "base":10, + "type":"effect", + "name":"damage_on_target", + "sfx":false + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "target":"=self", + "type":"effect", + "stay_alive":false, + "percentage":25, + "name":"lose_life" + }] + } + }, + "desc":"They say 'no pain, no gain'. I'll give them what they want.", + "type-description":"", + "pp":0, + "name":"Unreserved Punch", + "set":"brawler" +} \ No newline at end of file diff --git a/game/database/domains/card/energy-beam.json b/game/database/domains/card/energy-beam.json new file mode 100644 index 00000000..c3702a11 --- /dev/null +++ b/game/database/domains/card/energy-beam.json @@ -0,0 +1,61 @@ +{ + "attr":"ARC", + "icon":false, + "set":"gadgeteer", + "art":{ + "art_ability":{ + "effects":[{ + "attr":"=arc", + "center":"=target_pos", + "size":1, + "type":"effect", + "ignore_owner":false, + "name":"damage_on_area", + "base":5 + }], + "inputs":[{ + "type":"input", + "body-block":true, + "name":"choose_dir", + "output":"dir" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "output":"self_body", + "name":"get_actor_body", + "actor":"=self" + },{ + "type":"operator", + "body":"=self_body", + "name":"get_body_pos", + "output":"self_pos" + },{ + "pos":"=self_pos", + "dir":"=dir", + "type":"operator", + "maxrange":4, + "name":"hitscan", + "output":"hitscan" + },{ + "pos":"=hitscan", + "dir":"=dir", + "type":"operator", + "name":"translated", + "output":"target_pos" + },{ + "type":"operator", + "which":"ARC", + "name":"get_attribute", + "output":"arc" + }] + }, + "cost":10 + }, + "desc":"", + "type-description":"", + "name":"Energy Beam", + "one_time":false +} \ No newline at end of file diff --git a/game/database/domains/card/fireball.json b/game/database/domains/card/fireball.json new file mode 100644 index 00000000..d4ab95b6 --- /dev/null +++ b/game/database/domains/card/fireball.json @@ -0,0 +1,38 @@ +{ + "attr":"ARC", + "icon":"card-fireball", + "one_time":false, + "art":{ + "art_ability":{ + "effects":[{ + "attr":"=arc", + "center":"=target", + "size":3, + "type":"effect", + "ignore_owner":false, + "name":"damage_on_area", + "base":4 + }], + "inputs":[{ + "aoe-hint":3, + "max-range":6, + "name":"choose_target", + "type":"input", + "empty-tile":false, + "output":"target", + "non-wall":true + },{ + "type":"operator", + "which":"ARC", + "name":"get_attribute", + "output":"arc" + }] + }, + "cost":40 + }, + "desc":"Ages go by, and the fireball spell remains the\nbread and butter of arcane artists.", + "type-description":"", + "pp":0, + "name":"Fireball", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/firebolt.json b/game/database/domains/card/firebolt.json new file mode 100644 index 00000000..6220a4ba --- /dev/null +++ b/game/database/domains/card/firebolt.json @@ -0,0 +1,42 @@ +{ + "attr":"ARC", + "icon":"card-firebolt", + "one_time":false, + "art":{ + "art_ability":{ + "effects":[{ + "attr":"=arc", + "target":"=body", + "type":"effect", + "base":2, + "name":"damage_on_target", + "sfx":"example" + }], + "inputs":[{ + "type":"input", + "name":"choose_target", + "max-range":7, + "empty-tile":false, + "body-only":{ + }, + "output":"tgt" + },{ + "type":"operator", + "pos":"=tgt", + "name":"get_body_at", + "output":"body" + },{ + "type":"operator", + "which":"ARC", + "name":"get_attribute", + "output":"arc" + }] + }, + "cost":10 + }, + "desc":"'Ouch that hurts! - Imp with his face on fire.", + "type-description":"", + "pp":4, + "name":"Firebolt", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/fix-turret.json b/game/database/domains/card/fix-turret.json new file mode 100644 index 00000000..4f01017e --- /dev/null +++ b/game/database/domains/card/fix-turret.json @@ -0,0 +1,44 @@ +{ + "attr":"ARC", + "icon":"box", + "set":"gadgeteer", + "art":{ + "art_ability":{ + "effects":[{ + "attr":"=arc", + "target":"=turret", + "type":"effect", + "name":"heal", + "base":6 + }], + "inputs":[{ + "dif-fact":false, + "output":"tgt", + "aoe-hint":0, + "body-only":{ + "body-type":"machine" + }, + "empty-tile":false, + "max-range":1, + "non-wall":false, + "name":"choose_target", + "type":"input" + },{ + "type":"operator", + "pos":"=tgt", + "name":"get_body_at", + "output":"turret" + },{ + "type":"operator", + "which":"ARC", + "output":"arc", + "name":"get_attribute" + }] + }, + "cost":20 + }, + "desc":"A love kick and it's ready to go.", + "type-description":"", + "name":"Fix Turret", + "one_time":false +} \ No newline at end of file diff --git a/game/database/domains/card/flash.json b/game/database/domains/card/flash.json new file mode 100644 index 00000000..809c3f09 --- /dev/null +++ b/game/database/domains/card/flash.json @@ -0,0 +1,40 @@ +{ + "attr":"ARC", + "icon":"card-flash", + "one_time":false, + "art":{ + "cost":40, + "art_ability":{ + "inputs":[{ + "aoe-hint":5, + "non-wall":false, + "output":"self_pos", + "type":"input", + "max-range":0, + "empty-tile":false, + "body-only":{ + "body-type":false + }, + "name":"choose_target" + },{ + "type":"operator", + "pos":"=self_pos", + "name":"get_body_at", + "output":"self" + }], + "effects":[{ + "center":"=self_pos", + "size":5, + "card":"stunned", + "type":"effect", + "ignore_owner":true, + "name":"place_widget_on_area" + }] + } + }, + "desc":"Aaaaaaahhh... Savior of the Universe!", + "type-description":"", + "pp":0, + "name":"Flash", + "set":"gadgeteer" +} \ No newline at end of file diff --git a/game/database/domains/card/goggles.json b/game/database/domains/card/goggles.json new file mode 100644 index 00000000..54463841 --- /dev/null +++ b/game/database/domains/card/goggles.json @@ -0,0 +1,27 @@ +{ + "attr":"NONE", + "icon":"card-goggles", + "one_time":false, + "desc":"\"This over the top technological spectacles combine infra-red rays, image intensifier and obfuscation protection to improve the wearer's Field of View!\n\nSee more than ever with the Throttle Goggles, the latest visual wonder from Tark Tech!\"", + "type-description":"", + "widget":{ + "operators":[{ + "op":"+", + "attr":"FOV", + "val":6 + }], + "auto_activation":false, + "activation":false, + "trigger":"on_turn", + "status-tags":[], + "trigger-condition":{ + "effects":[], + "inputs":[] + }, + "charges":25, + "placement":"accessory" + }, + "pp":4, + "name":"Throttle Goggles", + "set":"gadgeteer" +} \ No newline at end of file diff --git a/game/database/domains/card/haste.json b/game/database/domains/card/haste.json new file mode 100644 index 00000000..25b85b63 --- /dev/null +++ b/game/database/domains/card/haste.json @@ -0,0 +1,25 @@ +{ + "attr":"ANI", + "icon":"card-haste", + "one_time":false, + "desc":"'Gotta go fast!' - A mutated spiked beast motto.", + "type-description":"", + "widget":{ + "operators":[{ + "op":"+", + "attr":"SPD", + "val":2 + }], + "trigger":"on_turn", + "status-tags":[], + "trigger-condition":{ + "effects":[], + "inputs":[] + }, + "charges":10, + "placement":false + }, + "pp":4, + "name":"Haste", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/heal.json b/game/database/domains/card/heal.json new file mode 100644 index 00000000..d3db5cc7 --- /dev/null +++ b/game/database/domains/card/heal.json @@ -0,0 +1,45 @@ +{ + "attr":"ANI", + "icon":"card-heal", + "one_time":false, + "art":{ + "art_ability":{ + "effects":[{ + "attr":"=ANI", + "target":"=self", + "type":"effect", + "name":"heal", + "base":5 + }], + "inputs":[{ + "empty-tile":false, + "output":"body-pos", + "aoe-hint":1, + "dif-fact":false, + "body-only":{ + "body-type":false + }, + "max-range":0, + "non-wall":false, + "name":"choose_target", + "type":"input" + },{ + "type":"operator", + "which":"ANI", + "name":"get_attribute", + "output":"ANI" + },{ + "type":"operator", + "pos":"=body-pos", + "name":"get_body_at", + "output":"self" + }] + }, + "cost":20 + }, + "desc":"", + "type-description":"", + "pp":3, + "name":"Heal", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/healing_potion.json b/game/database/domains/card/healing_potion.json new file mode 100644 index 00000000..6a56181a --- /dev/null +++ b/game/database/domains/card/healing_potion.json @@ -0,0 +1,50 @@ +{ + "attr":"NONE", + "icon":"card-potion", + "one_time":true, + "art":{ + "art_ability":{ + "effects":[{ + "attr":"=vit", + "target":"=self_body", + "base":8, + "name":"heal", + "type":"effect" + }], + "inputs":[{ + "empty-tile":false, + "output":"self_pos", + "aoe-hint":0, + "max-range":0, + "dif-fact":false, + "type":"input", + "non-wall":false, + "body-only":{ + "body-type":false + }, + "name":"choose_target" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "name":"get_actor_body", + "output":"self_body", + "actor":"=self" + },{ + "which":"VIT", + "type":"operator", + "body":"=self_body", + "output":"vit", + "name":"get_body_attribite" + }] + }, + "cost":20 + }, + "desc":"Gulp. Down the hatch, foxy.", + "type-description":"", + "pp":4, + "name":"Healing Potion", + "set":"consumables_potions" +} \ No newline at end of file diff --git a/game/database/domains/card/illusion_of_darkness.json b/game/database/domains/card/illusion_of_darkness.json new file mode 100644 index 00000000..ca3ad9d1 --- /dev/null +++ b/game/database/domains/card/illusion_of_darkness.json @@ -0,0 +1,33 @@ +{ + "attr":"ARC", + "icon":"card-illusion-of-darkness", + "one_time":false, + "art":{ + "cost":20, + "art_ability":{ + "inputs":[{ + "output":"target_pos", + "max-range":6, + "type":"input", + "name":"choose_target", + "body-only":[] + },{ + "type":"operator", + "pos":"=target_pos", + "name":"get_body_at", + "output":"target_body" + }], + "effects":[{ + "type":"effect", + "body":"=target_body", + "name":"place_widget", + "card":"blind" + }] + } + }, + "desc":"Goodnight sweet prince.", + "type-description":"", + "pp":3, + "name":"Illusion of Darkness", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/impact-vest.json b/game/database/domains/card/impact-vest.json new file mode 100644 index 00000000..23d87fbe --- /dev/null +++ b/game/database/domains/card/impact-vest.json @@ -0,0 +1,20 @@ +{ + "attr":"NONE", + "icon":"card-power-suit", + "one_time":false, + "desc":"", + "type-description":"", + "set":"gadgeteer", + "name":"Impact Vest", + "widget":{ + "operators":[{ + "attr":"DEF", + "op":"+", + "val":1 + }], + "trigger":false, + "status-tags":[], + "charges":0, + "placement":"suit" + } +} \ No newline at end of file diff --git a/game/database/domains/card/jetboots.json b/game/database/domains/card/jetboots.json new file mode 100644 index 00000000..14d6143d --- /dev/null +++ b/game/database/domains/card/jetboots.json @@ -0,0 +1,57 @@ +{ + "attr":"NONE", + "icon":"card-jetboots", + "set":"gadgeteer", + "desc":"'Now with less Carbon Dioxide emission!' - Jetboots UltraSlim Model commercial slogan.", + "type-description":"", + "one_time":false, + "pp":0, + "name":"Jetboots", + "widget":{ + "operators":[], + "trigger":"on_use", + "activation":{ + "cost":10, + "ability":{ + "inputs":[{ + "type":"input", + "output":"dir", + "body-block":true, + "name":"choose_dir" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "name":"get_actor_body", + "output":"body", + "actor":"=self" + },{ + "type":"operator", + "body":"=body", + "name":"get_body_pos", + "output":"origin" + },{ + "maxrange":5, + "dir":"=dir", + "type":"operator", + "pos":"=origin", + "name":"hitscan", + "output":"target-pos" + }], + "effects":[{ + "vfx-spd":1, + "vfx":"SLIDE", + "type":"effect", + "body":"=body", + "name":"move_to", + "pos":"=target-pos" + }] + } + }, + "status-tags":[], + "charges":8, + "placement":"accessory" + } +} \ No newline at end of file diff --git a/game/database/domains/card/jump.json b/game/database/domains/card/jump.json new file mode 100644 index 00000000..ba4d2f09 --- /dev/null +++ b/game/database/domains/card/jump.json @@ -0,0 +1,54 @@ +{ + "attr":"COR", + "icon":"card-jump", + "one_time":false, + "art":{ + "art_ability":{ + "effects":[{ + "vfx-spd":1, + "vfx":"JUMP", + "type":"effect", + "body":"=body", + "name":"move_to", + "pos":"=pos" + },{ + "attr":"=cor", + "center":"=pos", + "size":2, + "type":"effect", + "ignore_owner":true, + "name":"damage_on_area", + "base":6 + }], + "inputs":[{ + "aoe-hint":2, + "max-range":4, + "name":"choose_target", + "type":"input", + "non-wall":true, + "output":"pos", + "empty-tile":true + },{ + "type":"operator", + "which":"COR", + "output":"cor", + "name":"get_attribute" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "output":"body", + "name":"get_actor_body", + "actor":"=self" + }] + }, + "cost":40 + }, + "desc":"Take THAT gravity!", + "type-description":"", + "pp":3, + "name":"Jump", + "set":"brawler" +} \ No newline at end of file diff --git a/game/database/domains/card/laser-spear.json b/game/database/domains/card/laser-spear.json new file mode 100644 index 00000000..f8619e2d --- /dev/null +++ b/game/database/domains/card/laser-spear.json @@ -0,0 +1,47 @@ +{ + "attr":"COR", + "icon":"card-laser-spear", + "set":"brawler", + "desc":"A mass-produced weapon.", + "type-description":"", + "one_time":false, + "pp":0, + "name":"Laser Spear", + "widget":{ + "operators":[], + "trigger":"on_focus_end", + "activation":{ + "cost":20, + "ability":{ + "inputs":[{ + "body-only":[], + "max-range":1, + "type":"input", + "name":"choose_target", + "output":"target" + },{ + "type":"operator", + "pos":"=target", + "name":"get_body_at", + "output":"body" + },{ + "type":"operator", + "output":"cor", + "name":"get_attribute", + "which":"COR" + }], + "effects":[{ + "attr":"=cor", + "target":"=body", + "base":5, + "type":"effect", + "name":"damage_on_target", + "sfx":false + }] + } + }, + "status-tags":[], + "charges":1, + "placement":"weapon" + } +} \ No newline at end of file diff --git a/game/database/domains/card/meteor_blast.json b/game/database/domains/card/meteor_blast.json new file mode 100644 index 00000000..b907bc8a --- /dev/null +++ b/game/database/domains/card/meteor_blast.json @@ -0,0 +1,66 @@ +{ + "attr":"ARC", + "icon":"card-fireball", + "one_time":false, + "art":{ + "cost":40, + "art_ability":{ + "inputs":[{ + "aoe-hint":0, + "non-wall":false, + "output":"victim_pos", + "type":"input", + "max-range":3, + "empty-tile":false, + "body-only":{ + "body-type":false + }, + "name":"choose_target" + },{ + "aoe-hint":0, + "empty-tile":true, + "output":"target_pos", + "type":"input", + "non-wall":false, + "name":"choose_target", + "max-range":6 + },{ + "type":"operator", + "pos":"=victim_pos", + "name":"get_body_at", + "output":"victim_body" + },{ + "type":"operator", + "name":"get_attribute", + "output":"arc", + "which":"ARC" + },{ + "op":"*", + "lhs":"=arc", + "type":"operator", + "rhs":3, + "name":"integer_binop", + "output":"arc*3" + }], + "effects":[{ + "vfx-spd":1.1000000238419, + "vfx":"JUMP", + "type":"effect", + "body":"=victim_body", + "name":"move_to", + "pos":"=target_pos" + },{ + "attr":3, + "target":"=victim_body", + "type":"effect", + "name":"damage_on_target", + "base":"=arc*3" + }] + } + }, + "desc":"Up you goooooooooo...", + "type-description":"", + "pp":0, + "name":"Meteor Blast", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/poison.json b/game/database/domains/card/poison.json new file mode 100644 index 00000000..41c61292 --- /dev/null +++ b/game/database/domains/card/poison.json @@ -0,0 +1,27 @@ +{ + "widget":{ + "operators":[], + "trigger":"on_cycle", + "auto_activation":{ + "trigger":"on_cycle", + "ability":{ + "inputs":[{ + "type":"input", + "name":"body", + "output":"body_self" + }], + "effects":[{ + "target":"=body_self", + "name":"damage_on_target", + "base":1, + "type":"effect", + "attr":1 + }] + } + }, + "charges":6 + }, + "name":"Poison", + "one_time":true, + "attr":"COR" +} \ No newline at end of file diff --git a/game/database/domains/card/power-suit.json b/game/database/domains/card/power-suit.json new file mode 100644 index 00000000..ea85f1e4 --- /dev/null +++ b/game/database/domains/card/power-suit.json @@ -0,0 +1,25 @@ +{ + "attr":"NONE", + "icon":"card-power-suit", + "widget":{ + "operators":[{ + "attr":"DEF", + "op":"+", + "val":5 + },{ + "attr":"SPD", + "op":"-", + "val":4 + }], + "trigger":false, + "charges":0, + "status-tags":[], + "placement":"suit" + }, + "desc":"While wearing, increase your DEF by 5, but decrease your SPD by 4.\n\n\"'You won't feel anything your enemies throw at you*!\n*Side-effects include not feeling your feet.'\n-Tark Tech slogan for Power Suit.\"\n", + "type-description":"", + "set":"brawler", + "pp":0, + "name":"Power Suit", + "one_time":true +} \ No newline at end of file diff --git a/game/database/domains/card/proximity-detector.json b/game/database/domains/card/proximity-detector.json new file mode 100644 index 00000000..37df7800 --- /dev/null +++ b/game/database/domains/card/proximity-detector.json @@ -0,0 +1,67 @@ +{ + "attr":"ARC", + "icon":false, + "one_time":true, + "desc":"Don't stand so close to me!", + "type-description":"", + "widget":{ + "operators":[], + "auto_activation":{ + "ability":{ + "effects":[{ + "type":"effect", + "target":"=body_self", + "name":"destroy_body" + },{ + "attr":"=arc", + "center":"=pos", + "size":2, + "type":"effect", + "ignore_owner":false, + "name":"damage_on_area", + "base":10 + }], + "inputs":[{ + "type":"input", + "name":"body", + "output":"body_self" + },{ + "type":"operator", + "body":"=body_self", + "name":"get_body_pos", + "output":"pos" + },{ + "type":"operator", + "which":"ARC", + "name":"get_attribute", + "output":"arc" + }] + }, + "trigger":"on_done" + }, + "trigger":"on_tick", + "status-tags":[], + "trigger-condition":{ + "effects":[], + "inputs":[{ + "type":"input", + "name":"pos", + "output":"pos_self" + },{ + "ignore-owner":true, + "body-type":false, + "range":1, + "name":"has_nearby_bodies", + "type":"input", + "count":1, + "output":"yup", + "pos":"=pos_self" + }] + }, + "charges":1, + "placement":false + }, + "pp":0, + "name":"Proximity Detector", + "set":false +} \ No newline at end of file diff --git a/game/database/domains/card/punch.json b/game/database/domains/card/punch.json new file mode 100644 index 00000000..59e0fadc --- /dev/null +++ b/game/database/domains/card/punch.json @@ -0,0 +1,68 @@ +{ + "attr":"COR", + "icon":false, + "one_time":false, + "desc":"", + "type-description":"", + "set":false, + "name":"Punch", + "widget":{ + "operators":[], + "trigger":false, + "activation":{ + "cost":20, + "ability":{ + "inputs":[{ + "empty-tile":false, + "output":"target_pos", + "aoe-hint":0, + "type":"input", + "body-only":{ + "body-type":false + }, + "max-range":1, + "non-wall":true, + "name":"choose_target", + "dif-fact":true + },{ + "type":"operator", + "pos":"=target_pos", + "name":"get_body_at", + "output":"target" + },{ + "type":"operator", + "output":"cor", + "name":"get_attribute", + "which":"COR" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "output":"self_body", + "name":"get_actor_body", + "actor":"=self" + }], + "effects":[{ + "attr":"=cor", + "target":"=target", + "base":3, + "type":"effect", + "name":"damage_on_target", + "sfx":"example" + },{ + "attr":"=cor", + "target":"=self_body", + "type":"effect", + "base":1, + "name":"armor_on_target", + "sfx":false + }] + } + }, + "charges":0, + "status-tags":[], + "placement":false + } +} diff --git a/game/database/domains/card/push.json b/game/database/domains/card/push.json new file mode 100644 index 00000000..4f26ba89 --- /dev/null +++ b/game/database/domains/card/push.json @@ -0,0 +1,68 @@ +{ + "attr":"COR", + "icon":"card-jump", + "set":"gadgeteer", + "art":{ + "art_ability":{ + "effects":[{ + "pos":"=push_pos", + "vfx":"SLIDE", + "vfx-spd":1, + "type":"effect", + "body":"=target_body", + "name":"move_to", + "sfx":false + }], + "inputs":[{ + "dif-fact":false, + "output":"target_pos", + "aoe-hint":0, + "body-only":{ + "body-type":false + }, + "empty-tile":false, + "max-range":1, + "non-wall":false, + "name":"choose_target", + "type":"input" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "output":"self_body", + "name":"get_actor_body", + "actor":"=self" + },{ + "type":"operator", + "body":"=self_body", + "name":"get_body_pos", + "output":"self_pos" + },{ + "to":"=target_pos", + "type":"operator", + "from":"=self_pos", + "name":"pos_difference", + "output":"dir" + },{ + "pos":"=target_pos", + "dir":"=dir", + "type":"operator", + "name":"hitscan", + "output":"push_pos", + "maxrange":5 + },{ + "type":"operator", + "pos":"=target_pos", + "name":"get_body_at", + "output":"target_body" + }] + }, + "cost":10 + }, + "desc":"Outta my way!", + "type-description":"", + "name":"Push", + "one_time":false +} \ No newline at end of file diff --git a/game/database/domains/card/rage.json b/game/database/domains/card/rage.json new file mode 100644 index 00000000..e9e14531 --- /dev/null +++ b/game/database/domains/card/rage.json @@ -0,0 +1,25 @@ +{ + "attr":"ANI", + "icon":"card-rage", + "set":"brawler", + "desc":"They say Rage is all the rage with the kids.", + "type-description":"", + "one_time":false, + "pp":0, + "name":"Rage", + "widget":{ + "operators":[{ + "attr":"COR", + "op":"+", + "val":2 + },{ + "attr":"DEF", + "op":"-", + "val":8 + }], + "trigger":"on_focus_end", + "charges":1, + "status-tags":[], + "placement":false + } +} \ No newline at end of file diff --git a/game/database/domains/card/reassemble.json b/game/database/domains/card/reassemble.json new file mode 100644 index 00000000..086d19bf --- /dev/null +++ b/game/database/domains/card/reassemble.json @@ -0,0 +1,51 @@ +{ + "attr":"COR", + "icon":"card-reassemble", + "one_time":false, + "art":{ + "art_ability":{ + "effects":[{ + "vfx-spd":0.5, + "vfx":"SLIDE", + "pos":"=to", + "type":"effect", + "body":"=body", + "name":"move_to", + "sfx":false + }], + "inputs":[{ + "empty-tile":false, + "output":"from", + "aoe-hint":0, + "max-range":4, + "body-only":{ + "body-type":"machine" + }, + "type":"input", + "non-wall":false, + "name":"choose_target", + "dif-fact":false + },{ + "aoe-hint":1, + "type":"input", + "name":"choose_target", + "non-wall":true, + "max-range":4, + "dif-fact":false, + "output":"to", + "empty-tile":true + },{ + "type":"operator", + "pos":"=from", + "name":"get_body_at", + "output":"body" + }] + }, + "cost":60 + }, + "desc":"Take your best friend with you!", + "type-description":"", + "pp":0, + "name":"Reassemble", + "set":"gadgeteer" +} \ No newline at end of file diff --git a/game/database/domains/card/reinforce.json b/game/database/domains/card/reinforce.json new file mode 100644 index 00000000..a198aa3c --- /dev/null +++ b/game/database/domains/card/reinforce.json @@ -0,0 +1,34 @@ +{ + "attr":"ARC", + "icon":"card-reinforce", + "one_time":false, + "art":{ + "cost":20, + "art_ability":{ + "inputs":[{ + "type":"input", + "name":"choose_target", + "output":"target", + "body-only":{ + "body-type":"machine" + } + },{ + "type":"operator", + "pos":"=target", + "name":"get_body_at", + "output":"machine" + }], + "effects":[{ + "type":"effect", + "body":"=machine", + "name":"place_widget", + "card":"reinforced-surface" + }] + } + }, + "desc":"", + "type-description":"", + "pp":0, + "name":"Reinforce", + "set":"gadgeteer" +} \ No newline at end of file diff --git a/game/database/domains/card/reinforced-surface.json b/game/database/domains/card/reinforced-surface.json new file mode 100644 index 00000000..ddfd58ae --- /dev/null +++ b/game/database/domains/card/reinforced-surface.json @@ -0,0 +1,20 @@ +{ + "attr":"ARC", + "icon":false, + "set":false, + "desc":"", + "type-description":"", + "widget":{ + "operators":[{ + "attr":"DEF", + "op":"+", + "val":4 + }], + "trigger":"on_hit", + "status-tags":[], + "charges":10, + "placement":false + }, + "name":"Reinforced Surface", + "one_time":true +} \ No newline at end of file diff --git a/game/database/domains/card/rush.json b/game/database/domains/card/rush.json new file mode 100644 index 00000000..766d4be5 --- /dev/null +++ b/game/database/domains/card/rush.json @@ -0,0 +1,69 @@ +{ + "attr":"COR", + "icon":"card-rush", + "one_time":false, + "art":{ + "cost":20, + "art_ability":{ + "inputs":[{ + "type":"input", + "output":"dir", + "body-block":true, + "name":"choose_dir" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "name":"get_actor_body", + "output":"body_self", + "actor":"=self" + },{ + "type":"operator", + "body":"=body_self", + "name":"get_body_pos", + "output":"origin" + },{ + "maxrange":6, + "dir":"=dir", + "type":"operator", + "pos":"=origin", + "name":"hitscan", + "output":"target_pos" + },{ + "pos":"=target_pos", + "dir":"=dir", + "type":"operator", + "name":"translated", + "output":"damage_pos" + },{ + "type":"operator", + "output":"cor", + "name":"get_attribute", + "which":"COR" + }], + "effects":[{ + "vfx-spd":1, + "vfx":"SLIDE", + "type":"effect", + "body":"=body_self", + "name":"move_to", + "pos":"=target_pos" + },{ + "attr":"=cor", + "center":"=damage_pos", + "size":1, + "type":"effect", + "ignore_owner":false, + "name":"damage_on_area", + "base":8 + }] + } + }, + "desc":"It's rush hour.And I'm not talking about kong-fu.", + "type-description":"", + "pp":10, + "name":"Rush", + "set":"brawler" +} \ No newline at end of file diff --git a/game/database/domains/card/rusty-blaster.json b/game/database/domains/card/rusty-blaster.json new file mode 100644 index 00000000..34b33ce0 --- /dev/null +++ b/game/database/domains/card/rusty-blaster.json @@ -0,0 +1,62 @@ +{ + "attr":"COR", + "icon":"card-blaster", + "set":"gadgeteer", + "desc":"An old but reliable arcane blaster.", + "type-description":"", + "widget":{ + "operators":[], + "trigger":false, + "activation":{ + "ability":{ + "effects":[{ + "attr":"=arc", + "target":"=body", + "base":2, + "type":"effect", + "name":"damage_on_target", + "sfx":false + },{ + "attr":"=arc", + "target":"=body", + "type":"effect", + "base":2, + "name":"damage_on_target", + "sfx":false + },{ + "attr":"=arc", + "target":"=body", + "type":"effect", + "base":2, + "name":"damage_on_target", + "sfx":false + }], + "inputs":[{ + "output":"target", + "max-range":5, + "type":"input", + "name":"choose_target", + "body-only":{ + } + },{ + "type":"operator", + "pos":"=target", + "name":"get_body_at", + "output":"body" + },{ + "type":"operator", + "which":"ARC", + "output":"arc", + "name":"get_attribute" + }] + }, + "cost":10 + }, + "status-tags":[], + "charges":0, + "placement":"weapon" + }, + "pp":0, + "name":"Rusty Blaster", + "one_time":true +} \ No newline at end of file diff --git a/game/database/domains/card/seedling-sap.json b/game/database/domains/card/seedling-sap.json new file mode 100644 index 00000000..b6cf1486 --- /dev/null +++ b/game/database/domains/card/seedling-sap.json @@ -0,0 +1,42 @@ +{ + "attr":"ANI", + "icon":"card-seedling-sap", + "set":"brawler", + "desc":"A bit too much slimy for our taste.", + "type-description":"", + "widget":{ + "operators":[], + "trigger":"on_use", + "activation":{ + "ability":{ + "effects":[{ + "amount":2, + "name":"draw_card", + "type":"effect" + },{ + "type":"effect", + "body":"=body_self", + "name":"place_widget", + "card":"tipsy" + }], + "inputs":[{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "output":"body_self", + "name":"get_actor_body", + "actor":"=self" + }] + }, + "cost":10 + }, + "status-tags":[], + "charges":3, + "placement":"offhand" + }, + "pp":0, + "name":"Seedling Sap", + "one_time":false +} \ No newline at end of file diff --git a/game/database/domains/card/self-destruct.json b/game/database/domains/card/self-destruct.json new file mode 100644 index 00000000..a17d3acd --- /dev/null +++ b/game/database/domains/card/self-destruct.json @@ -0,0 +1,53 @@ +{ + "attr":"ARC", + "icon":false, + "widget":{ + "operators":[], + "auto_activation":{ + "ability":{ + "inputs":[{ + "type":"input", + "name":"body", + "output":"body_self" + },{ + "type":"operator", + "name":"self", + "output":"actor" + },{ + "which":"ARC", + "type":"operator", + "output":"arc", + "name":"get_attribute" + },{ + "type":"operator", + "body":"=body_self", + "name":"get_body_pos", + "output":"pos" + }], + "effects":[{ + "attr":"=arc", + "center":"=pos", + "size":3, + "type":"effect", + "ignore_owner":false, + "name":"damage_on_area", + "base":8 + },{ + "type":"effect", + "target":"=body_self", + "name":"destroy_body" + }] + }, + "trigger":"on_done" + }, + "status-tags":[], + "trigger":"on_cycle", + "charges":8, + "placement":false + }, + "desc":"", + "type-description":"", + "set":false, + "name":"Self Destruction", + "one_time":true +} \ No newline at end of file diff --git a/game/database/domains/card/sensor-bomb.json b/game/database/domains/card/sensor-bomb.json new file mode 100644 index 00000000..b3708148 --- /dev/null +++ b/game/database/domains/card/sensor-bomb.json @@ -0,0 +1,35 @@ +{ + "attr":"COR", + "icon":"card-sensor-bomb", + "one_time":false, + "art":{ + "cost":40, + "art_ability":{ + "inputs":[{ + "aoe-hint":2, + "empty-tile":true, + "output":"pos", + "type":"input", + "non-wall":true, + "name":"choose_target", + "max-range":1 + }], + "effects":[{ + "widgets":[{ + "spec":"proximity-detector" + }], + "pos":"=pos", + "bodyspec":"machine", + "type":"effect", + "def":50, + "vit":50, + "name":"make_body" + }] + } + }, + "desc":"This is going to be a blast!", + "type-description":"", + "pp":0, + "name":"Sensor Bomb", + "set":"gadgeteer" +} \ No newline at end of file diff --git a/game/database/domains/card/soul_robe.json b/game/database/domains/card/soul_robe.json new file mode 100644 index 00000000..06f82f64 --- /dev/null +++ b/game/database/domains/card/soul_robe.json @@ -0,0 +1,25 @@ +{ + "attr":"ANI", + "icon":"card-soul-robe", + "one_time":false, + "desc":"", + "type-description":"", + "widget":{ + "operators":[{ + "op":"+", + "attr":"ANI", + "val":2 + }], + "trigger":false, + "status-tags":[], + "trigger-condition":{ + "effects":[], + "inputs":[] + }, + "charges":0, + "placement":"suit" + }, + "pp":3, + "name":"Soul Robe", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/spark_rod.json b/game/database/domains/card/spark_rod.json new file mode 100644 index 00000000..28867a7d --- /dev/null +++ b/game/database/domains/card/spark_rod.json @@ -0,0 +1,64 @@ +{ + "attr":"COR", + "icon":"card-spark-rod", + "one_time":true, + "desc":"'Why just ~hit~ your enemies when you can also create an eletric explosion around them? Well, Tark Tech answers this dillema with the new Spark Rod 2000*!!\n\n*Spark Rod is not recommended for children' - old and controversial commercial from Tark Tech.", + "type-description":"", + "set":"brawler", + "pp":4, + "name":"Spark Rod 2000", + "widget":{ + "operators":[], + "trigger":false, + "activation":{ + "cost":20, + "ability":{ + "inputs":[{ + "aoe-hint":3, + "body-only":{ + "body-type":false + }, + "output":"first_target", + "non-wall":false, + "type":"input", + "empty-tile":false, + "name":"choose_target", + "max-range":1 + },{ + "type":"operator", + "pos":"=first_target", + "name":"get_body_at", + "output":"target_body" + },{ + "type":"operator", + "output":"cor", + "name":"get_attribute", + "which":"COR" + }], + "effects":[{ + "attr":"=cor", + "target":"=target_body", + "base":6, + "type":"effect", + "name":"damage_on_target", + "sfx":false + },{ + "attr":"=cor", + "center":"=first_target", + "size":3, + "type":"effect", + "ignore_owner":true, + "name":"damage_on_area", + "base":4 + }] + } + }, + "status-tags":[], + "trigger-condition":{ + "inputs":[], + "effects":[] + }, + "charges":0, + "placement":"weapon" + } +} \ No newline at end of file diff --git a/game/database/domains/card/stick_flame.json b/game/database/domains/card/stick_flame.json new file mode 100644 index 00000000..ce04b7ae --- /dev/null +++ b/game/database/domains/card/stick_flame.json @@ -0,0 +1,34 @@ +{ + "attr":"ARC", + "icon":"card-sticky-flame", + "one_time":false, + "art":{ + "cost":40, + "art_ability":{ + "inputs":[{ + "aoe-hint":1, + "output":"target_pos", + "type":"input", + "body-only":[], + "name":"choose_target", + "max-range":4 + },{ + "type":"operator", + "pos":"=target_pos", + "name":"get_body_at", + "output":"target_body" + }], + "effects":[{ + "type":"effect", + "body":"=target_body", + "name":"place_widget", + "card":"sticky_flame" + }] + } + }, + "desc":"'Hey Danny, hold this for me, will ya?'.\n\nRobert was surprised Danny didn't sent him an invite for his birthday the week after.", + "type-description":"", + "pp":4, + "name":"Stick Flame", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/sticky_flame.json b/game/database/domains/card/sticky_flame.json new file mode 100644 index 00000000..50297478 --- /dev/null +++ b/game/database/domains/card/sticky_flame.json @@ -0,0 +1,40 @@ +{ + "attr":"ARC", + "icon":false, + "set":false, + "desc":"Oh man, this is gonna blow...", + "type-description":"", + "widget":{ + "operators":[], + "trigger":"on_cycle", + "activation":false, + "charges":8, + "status-tags":[], + "auto_activation":{ + "trigger":"on_cycle", + "ability":{ + "inputs":[{ + "type":"input", + "name":"body", + "output":"body_self" + },{ + "which":"ARC", + "type":"operator", + "name":"get_attribute", + "output":"ARC" + }], + "effects":[{ + "attr":"=ARC", + "target":"=body_self", + "type":"effect", + "base":1, + "name":"damage_on_target", + "sfx":false + }] + } + }, + "placement":false + }, + "name":"Sticky Flame", + "one_time":true +} \ No newline at end of file diff --git a/game/database/domains/card/stomp.json b/game/database/domains/card/stomp.json new file mode 100644 index 00000000..7dd7f5b9 --- /dev/null +++ b/game/database/domains/card/stomp.json @@ -0,0 +1,53 @@ +{ + "attr":"ANI", + "icon":"card-stomp", + "one_time":false, + "art":{ + "cost":60, + "art_ability":{ + "inputs":[{ + "aoe-hint":3, + "max-range":0, + "output":"self_pos", + "non-wall":false, + "type":"input", + "empty-tile":false, + "name":"choose_target", + "body-only":{ + "body-type":false + } + },{ + "type":"operator", + "output":"ani", + "name":"get_attribute", + "which":"ANI" + },{ + "type":"operator", + "pos":"=self_pos", + "name":"get_body_at", + "output":"body" + }], + "effects":[{ + "center":"=self_pos", + "size":3, + "card":"stunned", + "type":"effect", + "ignore_owner":true, + "name":"place_widget_on_area" + },{ + "attr":"=ani", + "center":"=self_pos", + "size":3, + "type":"effect", + "ignore_owner":true, + "name":"damage_on_area", + "base":4 + }] + } + }, + "desc":"", + "type-description":"", + "pp":0, + "name":"Stomp", + "set":"brawler" +} \ No newline at end of file diff --git a/game/database/domains/card/stunned.json b/game/database/domains/card/stunned.json new file mode 100644 index 00000000..faad45b0 --- /dev/null +++ b/game/database/domains/card/stunned.json @@ -0,0 +1,18 @@ +{ + "attr":"COR", + "icon":false, + "one_time":true, + "desc":"", + "type-description":"", + "widget":{ + "operators":[], + "trigger":"on_cycle", + "charges":8, + "status-tags":[{ + "tag":"stun" + }], + "placement":false + }, + "name":"Stunned", + "set":false +} \ No newline at end of file diff --git a/game/database/domains/card/teleport.json b/game/database/domains/card/teleport.json new file mode 100644 index 00000000..4bac6b52 --- /dev/null +++ b/game/database/domains/card/teleport.json @@ -0,0 +1,43 @@ +{ + "attr":"ARC", + "icon":"card-teleport", + "one_time":false, + "art":{ + "art_ability":{ + "effects":[{ + "vfx-spd":1, + "vfx":false, + "pos":"=target_pos", + "type":"effect", + "body":"=Body", + "name":"move_to", + "sfx":"teleport" + }], + "inputs":[{ + "aoe-hint":0, + "dif-fact":false, + "output":"target_pos", + "empty-tile":true, + "max-range":7, + "non-wall":true, + "name":"choose_target", + "type":"input" + },{ + "type":"operator", + "name":"self", + "output":"Actor" + },{ + "type":"operator", + "output":"Body", + "name":"get_actor_body", + "actor":"=Actor" + }] + }, + "cost":20 + }, + "desc":"", + "type-description":"", + "pp":4, + "name":"Teleport", + "set":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/card/timebomb-drone.json b/game/database/domains/card/timebomb-drone.json new file mode 100644 index 00000000..f79e8e56 --- /dev/null +++ b/game/database/domains/card/timebomb-drone.json @@ -0,0 +1,36 @@ +{ + "attr":"ARC", + "icon":"card-timer-bomb", + "one_time":false, + "art":{ + "cost":20, + "art_ability":{ + "inputs":[{ + "aoe-hint":3, + "name":"choose_target", + "non-wall":false, + "type":"input", + "empty-tile":true, + "output":"pos", + "max-range":1 + }], + "effects":[{ + "widgets":[{ + "spec":"self-destruct" + }], + "pos":"=pos", + "def":0, + "bodyspec":"machine", + "type":"effect", + "widgetspec":"self-destruct", + "vit":0, + "name":"make_body" + }] + } + }, + "desc":"", + "type-description":"", + "pp":10, + "name":"Timed Combustion Drone", + "set":"gadgeteer" +} \ No newline at end of file diff --git a/game/database/domains/card/tipsy.json b/game/database/domains/card/tipsy.json new file mode 100644 index 00000000..3f4079fa --- /dev/null +++ b/game/database/domains/card/tipsy.json @@ -0,0 +1,20 @@ +{ + "attr":"NONE", + "icon":false, + "set":false, + "desc":"", + "type-description":"", + "widget":{ + "operators":[{ + "attr":"SPD", + "op":"-", + "val":2 + }], + "trigger":"on_turn", + "status-tags":[], + "charges":3, + "placement":false + }, + "name":"Tipsy", + "one_time":true +} \ No newline at end of file diff --git a/game/database/domains/card/trained-strike.json b/game/database/domains/card/trained-strike.json new file mode 100644 index 00000000..2d808940 --- /dev/null +++ b/game/database/domains/card/trained-strike.json @@ -0,0 +1,61 @@ +{ + "attr":"COR", + "icon":"card-dropkick", + "set":"brawler", + "art":{ + "art_ability":{ + "effects":[{ + "attr":"=cor", + "target":"=target_body", + "type":"effect", + "base":3, + "name":"damage_on_target", + "sfx":"example" + },{ + "attr":"=cor", + "target":"=self_body", + "type":"effect", + "base":1, + "name":"armor_on_target", + "sfx":false + }], + "inputs":[{ + "dif-fact":true, + "output":"target_pos", + "aoe-hint":1, + "empty-tile":false, + "body-only":{ + "body-type":false + }, + "max-range":1, + "non-wall":false, + "name":"choose_target", + "type":"input" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "output":"self_body", + "name":"get_actor_body", + "actor":"=self" + },{ + "type":"operator", + "which":"COR", + "output":"cor", + "name":"get_attribute" + },{ + "type":"operator", + "pos":"=target_pos", + "name":"get_body_at", + "output":"target_body" + }] + }, + "cost":10 + }, + "desc":"", + "type-description":"", + "name":"Trained Strike", + "one_time":false +} diff --git a/game/database/domains/card/turret.json b/game/database/domains/card/turret.json new file mode 100644 index 00000000..47c7a085 --- /dev/null +++ b/game/database/domains/card/turret.json @@ -0,0 +1,35 @@ +{ + "attr":"ARC", + "icon":"card-turret", + "one_time":false, + "art":{ + "cost":100, + "art_ability":{ + "inputs":[{ + "aoe-hint":4, + "empty-tile":true, + "output":"pos", + "type":"input", + "non-wall":true, + "name":"choose_target", + "max-range":1 + }], + "effects":[{ + "widgets":[{ + "spec":"auto-fire" + }], + "pos":"=pos", + "bodyspec":"machine", + "type":"effect", + "def":50, + "vit":50, + "name":"make_body" + }] + } + }, + "desc":"A true friend never let's you down.\n\nAnd also shoots your enemies in the face.", + "type-description":"", + "pp":0, + "name":"Turret", + "set":false +} \ No newline at end of file diff --git a/game/database/domains/card/wrench.json b/game/database/domains/card/wrench.json new file mode 100644 index 00000000..3b3cf5dc --- /dev/null +++ b/game/database/domains/card/wrench.json @@ -0,0 +1,76 @@ +{ + "attr":"COR", + "icon":"card-wrench", + "set":"gadgeteer", + "desc":"", + "type-description":"", + "one_time":false, + "pp":0, + "name":"Wrench", + "widget":{ + "operators":[], + "trigger":false, + "activation":{ + "cost":20, + "ability":{ + "inputs":[{ + "body-only":[], + "max-range":1, + "type":"input", + "name":"choose_target", + "output":"pos" + },{ + "type":"operator", + "output":"cor", + "name":"get_attribute", + "which":"COR" + },{ + "type":"operator", + "name":"self", + "output":"self" + },{ + "type":"operator", + "name":"get_actor_body", + "output":"body", + "actor":"=self" + },{ + "type":"operator", + "body":"=body", + "name":"get_body_pos", + "output":"center" + },{ + "ignore-owner":true, + "body-type":"machine", + "pos":"=center", + "type":"operator", + "range":5, + "name":"count_nearby_bodies", + "output":"count" + },{ + "op":"+", + "lhs":"=count", + "type":"operator", + "rhs":"=cor", + "name":"integer_binop", + "output":"base" + },{ + "type":"operator", + "pos":"=pos", + "name":"get_body_at", + "output":"target" + }], + "effects":[{ + "attr":"=base", + "target":"=target", + "base":12, + "type":"effect", + "name":"damage_on_target", + "sfx":false + }] + } + }, + "status-tags":[], + "charges":0, + "placement":"weapon" + } +} \ No newline at end of file diff --git a/out.save b/game/database/domains/cardset/.gitkeep similarity index 100% rename from out.save rename to game/database/domains/cardset/.gitkeep diff --git a/game/database/domains/cardset/arcanist.json b/game/database/domains/cardset/arcanist.json new file mode 100644 index 00000000..15e68723 --- /dev/null +++ b/game/database/domains/cardset/arcanist.json @@ -0,0 +1,4 @@ +{ + "parent":"all", + "name":"arcanist" +} \ No newline at end of file diff --git a/game/database/domains/cardset/brawler.json b/game/database/domains/cardset/brawler.json new file mode 100644 index 00000000..e7595998 --- /dev/null +++ b/game/database/domains/cardset/brawler.json @@ -0,0 +1,4 @@ +{ + "parent":"all", + "name":"brawler" +} \ No newline at end of file diff --git a/game/database/domains/cardset/consumables.json b/game/database/domains/cardset/consumables.json new file mode 100644 index 00000000..1d4b24b2 --- /dev/null +++ b/game/database/domains/cardset/consumables.json @@ -0,0 +1,4 @@ +{ + "parent":false, + "name":"Consumables" +} \ No newline at end of file diff --git a/game/database/domains/cardset/consumables_potions.json b/game/database/domains/cardset/consumables_potions.json new file mode 100644 index 00000000..8b94dcf9 --- /dev/null +++ b/game/database/domains/cardset/consumables_potions.json @@ -0,0 +1,4 @@ +{ + "parent":"consumables", + "name":"Consumable Potions" +} \ No newline at end of file diff --git a/game/database/domains/cardset/gadgeteer.json b/game/database/domains/cardset/gadgeteer.json new file mode 100644 index 00000000..862baa8f --- /dev/null +++ b/game/database/domains/cardset/gadgeteer.json @@ -0,0 +1,4 @@ +{ + "parent":"all", + "name":"gadgeteer" +} \ No newline at end of file diff --git a/game/database/domains/collection/.gitkeep b/game/database/domains/collection/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/game/database/domains/collection/anything.json b/game/database/domains/collection/anything.json new file mode 100644 index 00000000..3ae83f1d --- /dev/null +++ b/game/database/domains/collection/anything.json @@ -0,0 +1,21 @@ +{ + "cards":[{ + "set":"arcanist", + "drop":50 + },{ + "set":"brawler", + "drop":50 + },{ + "set":"gadgeteer", + "drop":50 + },{ + "set":"consumables", + "drop":50 + },{ + "set":"consumables", + "drop":50 + }], + "name":"Anything", + "image":"card-upgrade", + "description":"" +} diff --git a/game/database/domains/drop/food.json b/game/database/domains/drop/food.json new file mode 100644 index 00000000..00981a17 --- /dev/null +++ b/game/database/domains/drop/food.json @@ -0,0 +1,17 @@ +{ + "sprite":"box", + "name":"Food", + "ability":{ + "effects":[{ + "type":"effect", + "name":"reward_pp", + "amount":10 + }], + "inputs":[{ + "type":"input", + "amount":100, + "name":"has_less_pp", + "output":"not_full" + }] + } +} diff --git a/game/database/domains/drop/pack.json b/game/database/domains/drop/pack.json new file mode 100644 index 00000000..667bc843 --- /dev/null +++ b/game/database/domains/drop/pack.json @@ -0,0 +1,15 @@ +{ + "sprite":"pack-drop", + "name":"Pack", + "ability":{ + "effects":[{ + "type":"effect", + "name":"give_pack", + "collection":"anything" + }], + "inputs":[{ + "type":"input", + "name":"is_controlled_actor" + }] + } +} \ No newline at end of file diff --git a/game/database/domains/faction/agressive.json b/game/database/domains/faction/agressive.json new file mode 100644 index 00000000..4db3ceae --- /dev/null +++ b/game/database/domains/faction/agressive.json @@ -0,0 +1,3 @@ +{ + "name":"agressive" +} \ No newline at end of file diff --git a/game/database/domains/faction/allied.json b/game/database/domains/faction/allied.json new file mode 100644 index 00000000..11a68293 --- /dev/null +++ b/game/database/domains/faction/allied.json @@ -0,0 +1,3 @@ +{ + "name":"allied" +} \ No newline at end of file diff --git a/game/database/domains/sector/initial.json b/game/database/domains/sector/initial.json new file mode 100644 index 00000000..b4f44987 --- /dev/null +++ b/game/database/domains/sector/initial.json @@ -0,0 +1,9 @@ +{ + "bootstrap":{ + "w":0, + "mh":0, + "tileset":"demo", + "h":0, + "mw":0 + } +} diff --git a/game/database/domains/sector/sector01.json b/game/database/domains/sector/sector01.json new file mode 100644 index 00000000..b0267628 --- /dev/null +++ b/game/database/domains/sector/sector01.json @@ -0,0 +1,68 @@ +{ + "fillings":[], + "connections":{ + "loops":24 + }, + "encounters":{ + "max":8, + "recipes":[{ + "bodyspec":"slime", + "actorspec":"dumb", + "upgrade_power":60 + },{ + "bodyspec":"imp", + "actorspec":"grinful", + "upgrade_power":80 + }], + "min":4 + }, + "drops":{ + "drops":[{ + "droptype":"food", + "droprate":4 + },{ + "droptype":"pack", + "droprate":2 + }] + }, + "rooms":{ + "minh":3, + "minw":3, + "rmar":1, + "tries":256, + "maxh":9, + "count":24, + "maxw":9 + }, + "holes":false, + "altars":{ + "max":6, + "min":2, + "threshold":1 + }, + "deadends":{ + "amount":128 + }, + "theme":false, + "exits":{ + "distance":5, + "exits":[{ + "target_specname":"sector01" + },{ + "target_specname":"sector02" + },{ + "target_specname":"sector02" + }], + "threshold":2 + }, + "bootstrap":{ + "w":48, + "mh":7, + "tileset":"demo", + "h":48, + "mw":9 + }, + "maze":{ + "double":false + } +} \ No newline at end of file diff --git a/game/database/domains/sector/sector02.json b/game/database/domains/sector/sector02.json new file mode 100644 index 00000000..543a4af8 --- /dev/null +++ b/game/database/domains/sector/sector02.json @@ -0,0 +1,72 @@ +{ + "fillings":[], + "connections":{ + "loops":16 + }, + "encounters":{ + "max":10, + "recipes":[{ + "actorspec":"dumb", + "bodyspec":"slime", + "upgrade_power":100 + },{ + "actorspec":"grinful", + "bodyspec":"imp", + "upgrade_power":120 + },{ + "actorspec":"fiendish", + "bodyspec":"eye", + "upgrade_power":150 + }], + "min":5 + }, + "drops":{ + "drops":[{ + "droptype":"food", + "droprate":4 + },{ + "droptype":"pack", + "droprate":1 + }] + }, + "rooms":{ + "minh":3, + "maxh":9, + "minw":3, + "tries":256, + "count":24, + "rmar":1, + "maxw":9 + }, + "holes":false, + "deadends":{ + "amount":12 + }, + "altars":{ + "max":6, + "min":2, + "threshold":1 + }, + "theme":false, + "maze":{ + "double":false + }, + "bootstrap":{ + "w":56, + "mh":7, + "tileset":"ruins", + "h":56, + "mw":9 + }, + "exits":{ + "distance":5, + "threshold":2, + "exits":[{ + "target_specname":"sector01" + },{ + "target_specname":"sector02" + },{ + "target_specname":"sector03" + }] + } +} \ No newline at end of file diff --git a/game/database/domains/sector/sector03.json b/game/database/domains/sector/sector03.json new file mode 100644 index 00000000..83a3e7b7 --- /dev/null +++ b/game/database/domains/sector/sector03.json @@ -0,0 +1,64 @@ +{ + "fillings":[], + "connections":{ + "loops":12 + }, + "encounters":{ + "max":12, + "recipes":[{ + "bodyspec":"eye", + "actorspec":"fiendish", + "upgrade_power":150 + },{ + "bodyspec":"imp", + "actorspec":"grinful", + "upgrade_power":180 + }], + "min":6 + }, + "drops":{ + "drops":[{ + "droptype":"food", + "droprate":1 + }] + }, + "rooms":{ + "minh":3, + "rmar":1, + "minw":3, + "tries":256, + "count":24, + "maxh":9, + "maxw":9 + }, + "altars":{ + "max":6, + "min":2, + "threshold":1 + }, + "exits":{ + "distance":5, + "threshold":2, + "exits":[{ + "target_specname":"sector02" + },{ + "target_specname":"sector03" + },{ + "target_specname":"sector_final" + }] + }, + "theme":false, + "deadends":{ + "amount":48 + }, + "bootstrap":{ + "w":56, + "mh":7, + "h":56, + "tileset":"otherdemo", + "mw":9 + }, + "maze":{ + "double":false + } +} \ No newline at end of file diff --git a/game/database/domains/sector/sector_final.json b/game/database/domains/sector/sector_final.json new file mode 100644 index 00000000..5cdda481 --- /dev/null +++ b/game/database/domains/sector/sector_final.json @@ -0,0 +1,67 @@ +{ + "fillings":[], + "connections":{ + "loops":24 + }, + "encounters":{ + "max":14, + "recipes":[{ + "actorspec":"newbieslayer", + "bodyspec":"rewasvat", + "upgrade_power":300 + },{ + "actorspec":"fiendish", + "bodyspec":"eye", + "upgrade_power":200 + },{ + "actorspec":"grinful", + "bodyspec":"imp", + "upgrade_power":200 + }], + "min":6 + }, + "drops":{ + "drops":[{ + "droptype":"food", + "droprate":4 + },{ + "droptype":"pack", + "droprate":3 + }] + }, + "rooms":{ + "minh":3, + "rmar":1, + "minw":3, + "tries":256, + "maxh":9, + "count":32, + "maxw":9 + }, + "exits":{ + "distance":5, + "exits":[{ + "target_specname":"sector03" + }], + "threshold":2 + }, + "altars":{ + "max":6, + "min":2, + "threshold":1 + }, + "theme":false, + "maze":{ + "double":false + }, + "bootstrap":{ + "w":48, + "mh":7, + "tileset":"otherdemo", + "h":48, + "mw":9 + }, + "deadends":{ + "amount":24 + } +} \ No newline at end of file diff --git a/game/database/domains/theme/ruins.json b/game/database/domains/theme/ruins.json new file mode 100644 index 00000000..076ae694 --- /dev/null +++ b/game/database/domains/theme/ruins.json @@ -0,0 +1,4 @@ +{ + "bgm":"ruins", + "tileset":"demo" +} diff --git a/game/database/domains/theme/seeds.json b/game/database/domains/theme/seeds.json new file mode 100644 index 00000000..4c03c5c1 --- /dev/null +++ b/game/database/domains/theme/seeds.json @@ -0,0 +1,4 @@ +{ + "bgm":"ruins", + "tileset":"otherdemo" +} diff --git a/game/database/domains/theme/zones.json b/game/database/domains/theme/zones.json new file mode 100644 index 00000000..7a3f2843 --- /dev/null +++ b/game/database/domains/theme/zones.json @@ -0,0 +1,4 @@ +{ + "bgm":"ruins", + "tileset":"ruins" +} diff --git a/game/database/domains/zone/ruins.json b/game/database/domains/zone/ruins.json new file mode 100644 index 00000000..e1ba36a8 --- /dev/null +++ b/game/database/domains/zone/ruins.json @@ -0,0 +1,5 @@ +{ + "theme":"ruins", + "difficulty":4, + "name":"Ruins" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed1.json b/game/database/domains/zone/seed1.json new file mode 100644 index 00000000..482e8697 --- /dev/null +++ b/game/database/domains/zone/seed1.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":16, + "name":"Seed 1" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed2.json b/game/database/domains/zone/seed2.json new file mode 100644 index 00000000..af5e293a --- /dev/null +++ b/game/database/domains/zone/seed2.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":8, + "name":"Seed 2" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed3.json b/game/database/domains/zone/seed3.json new file mode 100644 index 00000000..32dda203 --- /dev/null +++ b/game/database/domains/zone/seed3.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":16, + "name":"Seed 3" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed4.json b/game/database/domains/zone/seed4.json new file mode 100644 index 00000000..bbada72d --- /dev/null +++ b/game/database/domains/zone/seed4.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":16, + "name":"Seed 4" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed5.json b/game/database/domains/zone/seed5.json new file mode 100644 index 00000000..2e386de9 --- /dev/null +++ b/game/database/domains/zone/seed5.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":16, + "name":"Seed 5" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed6.json b/game/database/domains/zone/seed6.json new file mode 100644 index 00000000..e59b06ec --- /dev/null +++ b/game/database/domains/zone/seed6.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":16, + "name":"Seed 6" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed7.json b/game/database/domains/zone/seed7.json new file mode 100644 index 00000000..fd16cd6b --- /dev/null +++ b/game/database/domains/zone/seed7.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":16, + "name":"Seed 7" +} \ No newline at end of file diff --git a/game/database/domains/zone/seed8.json b/game/database/domains/zone/seed8.json new file mode 100644 index 00000000..5c7ef8a0 --- /dev/null +++ b/game/database/domains/zone/seed8.json @@ -0,0 +1,5 @@ +{ + "theme":"seeds", + "difficulty":16, + "name":"Seed 8" +} \ No newline at end of file diff --git a/game/database/domains/zone/zone1.json b/game/database/domains/zone/zone1.json new file mode 100644 index 00000000..909fb1f0 --- /dev/null +++ b/game/database/domains/zone/zone1.json @@ -0,0 +1,5 @@ +{ + "theme":"zones", + "difficulty":8, + "name":"Zone 1" +} \ No newline at end of file diff --git a/game/database/domains/zone/zone2.json b/game/database/domains/zone/zone2.json new file mode 100644 index 00000000..6ab9e131 --- /dev/null +++ b/game/database/domains/zone/zone2.json @@ -0,0 +1,5 @@ +{ + "theme":"zones", + "difficulty":8, + "name":"Zone 2" +} \ No newline at end of file diff --git a/game/database/domains/zone/zone3.json b/game/database/domains/zone/zone3.json new file mode 100644 index 00000000..1138d2a2 --- /dev/null +++ b/game/database/domains/zone/zone3.json @@ -0,0 +1,5 @@ +{ + "theme":"zones", + "difficulty":8, + "name":"Zone 3" +} \ No newline at end of file diff --git a/game/database/domains/zone/zone4.json b/game/database/domains/zone/zone4.json new file mode 100644 index 00000000..f0f08dbf --- /dev/null +++ b/game/database/domains/zone/zone4.json @@ -0,0 +1,5 @@ +{ + "theme":"zones", + "difficulty":8, + "name":"Zone 4" +} \ No newline at end of file diff --git a/game/database/init.lua b/game/database/init.lua new file mode 100644 index 00000000..2738cd31 --- /dev/null +++ b/game/database/init.lua @@ -0,0 +1,292 @@ + +local json = require 'dkjson' +local SCHEMA = require 'lux.pack' 'database.schema' +local DEFS = require 'domain.definitions' +local FS = love.filesystem + +local DB = {} + + +local _dbcache = {} +local _subschemas = {} + +local function _fullpath(relpath) + local srcpath = love.filesystem.getSource() + return ("%s/%s"):format(srcpath, relpath) +end + +function _loadSubschema(base) + local fs = love.filesystem + local sub = _subschemas[base] + if not sub then + sub = {} + for _,file in ipairs(fs.getDirectoryItems("domain/" .. base)) do + if file:match "^.+%.lua$" then + file = file:gsub("%.lua", "") + sub[file] = require('domain.' .. base .. '.' .. file).schema + table.insert(sub, file) + end + end + _subschemas[base] = sub + end + return sub +end + +function _subschemaFor(base, branch) + return _loadSubschema(base)[branch] +end + +local function _loadCategory(category) + return _dbcache[category] +end + +local function _loadGroup(category, group_name) + return _loadCategory(category)[group_name] +end + +local function _loadDomainGroup(group) + return _loadGroup('domains', group) +end + +local function _loadResourceGroup(group) + return _loadGroup('resources', group) +end + +local function _loadSetting(setting) + return _loadGroup('settings', setting) +end + +local function _listFilesIn(relpath) + local list = love.filesystem.getDirectoryItems(relpath) + local entries = {} + for i, filename in ipairs(list) do + local basename = filename:match("^(.+)%.json$") + if basename then + table.insert(entries, basename) + end + end + return ipairs(entries) +end + +local function _deleteFile(relpath) + -- We need os.remove and fullpath to write to files + -- This only works in development mode + if love.filesystem.getInfo(relpath, 'file') then + local path = _fullpath(relpath) + return os.remove(_fullpath(relpath)) + end +end + +local function _loadFile(relpath) + local file = assert(FS.newFile(relpath, 'r')) + local data, _, err = json.decode((file:read())) -- drop second value + file:close() + return assert(data, err) +end + +local function _writeFile(relpath, rawdata) + -- We need io.open and fullpath to write to files + -- This only works in development mode + local file = assert(io.open(_fullpath(relpath), 'w')) + local data = json.encode(rawdata, {indent = true}) + assert(file:write(data)) + return file:close() +end + +local function _save(cache, basepath) + -- check whether we are saving a group of files or a file + if not getmetatable(cache).is_leaf then + -- save group + for group, subcache in pairs(cache) do + local meta = getmetatable(subcache) or {} + local item = meta.group or group + local newbasepath = basepath.."/"..item + _save(subcache, newbasepath) + end + else + -- save file + local filepath = basepath..".json" + return assert(_writeFile(filepath, cache)) + end +end + +local function _refresh(cache, basepath) + -- check whether we are saving a group of files or a file + if getmetatable(cache).is_leaf then return end + -- save group + for group, subcache in pairs(cache) do + local meta = getmetatable(subcache) or {} + local item = meta.group or group + local newbasepath = basepath.."/"..item + if subcache == DEFS.DELETE then + cache[group] = nil + _deleteFile(newbasepath..".json") + else + _refresh(subcache, newbasepath) + end + end + -- HARD REFRESH -- + local group + repeat + group = next(cache) + if group then cache[group] = nil end + until next(cache) == nil +end + +function _listItemsIn(category, group_name) + local group = _loadGroup(category, group_name) + local relpath = getmetatable(group).relpath + local found = {} + for _,name in _listFilesIn(relpath) do + if group[name] and group[name] ~= DEFS.DELETE then + found[name] = true + end + end + for name,spec in pairs(group) do + if spec ~= DEFS.DELETE then + found[name] = true + end + end + return pairs(found) +end + +local function _metaSpec(container, name) + local path = ("%s/%s"):format(getmetatable(container).relpath, name) + return { + is_leaf = true, + relpath = path, + group = name, + __index = function(self, key) + local extends = rawget(self, "extends") + if extends then + return container[extends][key] + end + end + } +end + +local function _get(self, key) + local fs = love.filesystem + local path = ("%s/%s"):format(getmetatable(self).relpath, key) + local meta = {relpath = path, group = key} + local obj = setmetatable({}, meta) + + -- if directory + if fs.getInfo(path, 'directory') then + meta.__index = _get + self[key] = obj + return obj + end + + -- if json file + local filepath = path..".json" + if fs.getInfo(filepath, 'file') then + obj = _loadFile(filepath) + DB.initSpec(obj, self, meta.group) + return obj + end +end + +function DB.initSpec(spec, container, name) + -- inserts a leaf spec into the container + container[name] = spec + return setmetatable(spec, _metaSpec(container, name)) +end + +function DB.subschemaTypes(base) + return ipairs(_loadSubschema(base)) +end + +function DB.schemaFor(domain_name) + local base, branch = domain_name:match('^(.+)/(.+)$') + if base and branch then + return ipairs(_subschemaFor(base, branch)) + else + return ipairs(SCHEMA[domain_name]) + end +end + +function DB.loadCategory(category) + return _loadCategory(category) +end + +function DB.loadGroup(category, group_name) + return _loadGroup(category, group_name) +end + +function DB.loadDomain(domain_name) + return _loadDomainGroup(domain_name) +end + +function DB.listDomainItems(domain_name) + return _listItemsIn('domains', domain_name) +end + +function DB.loadSpec(domain_name, spec_name) + return DB.loadDomain(domain_name)[spec_name] +end + +function DB.loadSetting(setting_name) + return _loadSetting(setting_name) +end + +function DB.loadResourceGroup(res_type) + return _loadResourceGroup(res_type) +end + +function DB.loadResource(res_type, res_name) + return _loadResourceGroup(res_type)[res_name] +end + +function DB.loadResourcePath(res_type, res_name) + local path = "assets/%s/%s" + local filename = DB.loadResource(res_type, res_name).filename + return path:format(res_type, filename) +end + +function DB.listResourceItems(res_name) + return _listItemsIn('resources', res_name) +end + +function DB.listItemsIn(category, group) + return _listItemsIn(category, group) +end + +function DB.refresh(container) + container = container or _dbcache + local basepath = getmetatable(container).relpath + _refresh(container, basepath) +end + +function DB.save(container) + container = container or _dbcache + local basepath = getmetatable(container).relpath + _refresh(container, basepath) + _save(container, basepath) +end + +function DB.renameGroupItem(category, group_name, oldname, newname) + local group = _loadGroup(category, group_name) + local spec = DB.initSpec(group[oldname], group, newname) + DB.save(spec) + DB.deleteGroupItem(category, group_name, oldname) + return spec +end + +function DB.deleteGroupItem(category, group_name, spec_name) + local group = _loadGroup(category, group_name) + group[spec_name] = DEFS.DELETE + return DB.refresh(group) +end + +function DB.init() + local meta = { + relpath = "database", + group = "database", + __index = _get, + } + setmetatable(_dbcache, meta) +end + +return DB + diff --git a/game/database/resources/bgm/ruins.json b/game/database/resources/bgm/ruins.json new file mode 100644 index 00000000..cae2db1e --- /dev/null +++ b/game/database/resources/bgm/ruins.json @@ -0,0 +1,3 @@ +{ + "filename":"ruins.ogg" +} \ No newline at end of file diff --git a/game/database/resources/font/Text.json b/game/database/resources/font/Text.json new file mode 100644 index 00000000..9d344058 --- /dev/null +++ b/game/database/resources/font/Text.json @@ -0,0 +1,3 @@ +{ + "filename":"SairaCondensed-Medium.ttf" +} \ No newline at end of file diff --git a/game/database/resources/font/TextBold.json b/game/database/resources/font/TextBold.json new file mode 100644 index 00000000..4bd61f1c --- /dev/null +++ b/game/database/resources/font/TextBold.json @@ -0,0 +1 @@ +{ "filename": "SairaCondensed-ExtraBold.ttf" } diff --git a/game/database/resources/font/Title.json b/game/database/resources/font/Title.json new file mode 100644 index 00000000..b8006f84 --- /dev/null +++ b/game/database/resources/font/Title.json @@ -0,0 +1,3 @@ +{ + "filename":"Anton.ttf" +} \ No newline at end of file diff --git a/game/database/resources/sfx/back-menu.json b/game/database/resources/sfx/back-menu.json new file mode 100644 index 00000000..ee80087b --- /dev/null +++ b/game/database/resources/sfx/back-menu.json @@ -0,0 +1,3 @@ +{ + "filename":"back_menu.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/change-sector.json b/game/database/resources/sfx/change-sector.json new file mode 100644 index 00000000..d3e0d4c9 --- /dev/null +++ b/game/database/resources/sfx/change-sector.json @@ -0,0 +1,3 @@ +{ + "filename":"going_down_stars.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/denied.json b/game/database/resources/sfx/denied.json new file mode 100644 index 00000000..102c12c8 --- /dev/null +++ b/game/database/resources/sfx/denied.json @@ -0,0 +1,3 @@ +{ + "filename":"denied.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/example.json b/game/database/resources/sfx/example.json new file mode 100644 index 00000000..74c82855 --- /dev/null +++ b/game/database/resources/sfx/example.json @@ -0,0 +1,3 @@ +{ + "filename":"example.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/footstep.json b/game/database/resources/sfx/footstep.json new file mode 100644 index 00000000..2e1c7e62 --- /dev/null +++ b/game/database/resources/sfx/footstep.json @@ -0,0 +1,3 @@ +{ + "filename":"Footstep.wav" +} diff --git a/game/database/resources/sfx/get-item.json b/game/database/resources/sfx/get-item.json new file mode 100644 index 00000000..13866feb --- /dev/null +++ b/game/database/resources/sfx/get-item.json @@ -0,0 +1,3 @@ +{ + "filename":"get_item.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/ok-menu.json b/game/database/resources/sfx/ok-menu.json new file mode 100644 index 00000000..b4e367ef --- /dev/null +++ b/game/database/resources/sfx/ok-menu.json @@ -0,0 +1,3 @@ +{ + "filename":"ok_menu.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/open-menu.json b/game/database/resources/sfx/open-menu.json new file mode 100644 index 00000000..4bbd99e5 --- /dev/null +++ b/game/database/resources/sfx/open-menu.json @@ -0,0 +1,3 @@ +{ + "filename":"open_menu.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/select-menu.json b/game/database/resources/sfx/select-menu.json new file mode 100644 index 00000000..7d6d1f94 --- /dev/null +++ b/game/database/resources/sfx/select-menu.json @@ -0,0 +1,3 @@ +{ + "filename":"select_menu.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/teleport.json b/game/database/resources/sfx/teleport.json new file mode 100644 index 00000000..d4c4b52a --- /dev/null +++ b/game/database/resources/sfx/teleport.json @@ -0,0 +1,3 @@ +{ + "filename":"Teleport.wav" +} \ No newline at end of file diff --git a/game/database/resources/sfx/upgrade.json b/game/database/resources/sfx/upgrade.json new file mode 100644 index 00000000..c0fc76ff --- /dev/null +++ b/game/database/resources/sfx/upgrade.json @@ -0,0 +1,3 @@ +{ + "filename":"Upgrade2.wav" +} \ No newline at end of file diff --git a/game/database/resources/sprite/corgi-idle.json b/game/database/resources/sprite/corgi-idle.json new file mode 100644 index 00000000..41d94e8e --- /dev/null +++ b/game/database/resources/sprite/corgi-idle.json @@ -0,0 +1,25 @@ +{ + "animation":[{ + "time":100, + "quad_idx":1 + },{ + "time":100, + "quad_idx":2 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":2 + }], + "loop":true, + "texture":"corgi", + "offset":[0,70], + "quad_division":[4,1] +} diff --git a/game/database/resources/sprite/cursor.json b/game/database/resources/sprite/cursor.json new file mode 100644 index 00000000..e6432d74 --- /dev/null +++ b/game/database/resources/sprite/cursor.json @@ -0,0 +1,25 @@ +{ + "animation":[{ + "time":100, + "quad_idx":1 + },{ + "time":100, + "quad_idx":2 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":2 + }], + "loop":true, + "texture":"cursor", + "offset":[0,0], + "quad_division":[4,1] +} diff --git a/game/database/resources/sprite/demobody-idle.json b/game/database/resources/sprite/demobody-idle.json new file mode 100644 index 00000000..e1b0a50c --- /dev/null +++ b/game/database/resources/sprite/demobody-idle.json @@ -0,0 +1,25 @@ +{ + "animation":[{ + "time":100, + "quad_idx":1 + },{ + "time":100, + "quad_idx":2 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":2 + }], + "loop":true, + "texture":"demobody", + "offset":[0,64], + "quad_division":[4,1] +} diff --git a/game/database/resources/sprite/eyedemon-idle.json b/game/database/resources/sprite/eyedemon-idle.json new file mode 100644 index 00000000..b468981a --- /dev/null +++ b/game/database/resources/sprite/eyedemon-idle.json @@ -0,0 +1,25 @@ +{ + "animation":[{ + "time":100, + "quad_idx":1 + },{ + "time":100, + "quad_idx":2 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":2 + }], + "loop":true, + "texture":"eyedemon", + "offset":[0,48], + "quad_division":[4,1] +} diff --git a/game/database/resources/sprite/hearthborn-idle.json b/game/database/resources/sprite/hearthborn-idle.json new file mode 100644 index 00000000..d499436a --- /dev/null +++ b/game/database/resources/sprite/hearthborn-idle.json @@ -0,0 +1,31 @@ +{ + "quad_division":[5,1], + "offset":[0,80], + "texture":"hearthborn", + "animation":[{ + "quad_idx":1, + "time":100 + },{ + "quad_idx":2, + "time":100 + },{ + "quad_idx":3, + "time":100 + },{ + "quad_idx":4, + "time":100 + },{ + "quad_idx":5, + "time":100 + },{ + "quad_idx":4, + "time":100 + },{ + "quad_idx":3, + "time":100 + },{ + "quad_idx":2, + "time":100 + }], + "loop":true +} \ No newline at end of file diff --git a/game/database/resources/sprite/hillborn-idle.json b/game/database/resources/sprite/hillborn-idle.json new file mode 100644 index 00000000..2e8bfbcc --- /dev/null +++ b/game/database/resources/sprite/hillborn-idle.json @@ -0,0 +1,31 @@ +{ + "quad_division":[5,1], + "offset":[0,80], + "texture":"hillborn", + "animation":[{ + "quad_idx":1, + "time":100 + },{ + "quad_idx":2, + "time":100 + },{ + "quad_idx":3, + "time":100 + },{ + "quad_idx":4, + "time":100 + },{ + "quad_idx":5, + "time":100 + },{ + "quad_idx":4, + "time":100 + },{ + "quad_idx":3, + "time":100 + },{ + "quad_idx":2, + "time":100 + }], + "loop":true +} diff --git a/game/database/resources/sprite/imp-idle.json b/game/database/resources/sprite/imp-idle.json new file mode 100644 index 00000000..bf7ccb9d --- /dev/null +++ b/game/database/resources/sprite/imp-idle.json @@ -0,0 +1,31 @@ +{ + "animation":[{ + "time":100, + "quad_idx":1 + },{ + "time":100, + "quad_idx":2 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":5 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":2 + }], + "texture":"imp", + "loop":true, + "offset":[0,48], + "quad_division":[5,1] +} diff --git a/game/database/resources/sprite/slime-idle.json b/game/database/resources/sprite/slime-idle.json new file mode 100644 index 00000000..06ae08dd --- /dev/null +++ b/game/database/resources/sprite/slime-idle.json @@ -0,0 +1,28 @@ +{ + "animation":[{ + "time":100, + "quad_idx":1 + },{ + "time":100, + "quad_idx":2 + },{ + "time":100, + "quad_idx":3 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":5 + },{ + "time":100, + "quad_idx":4 + },{ + "time":100, + "quad_idx":3 + }], + "texture":"slime", + "loop":true, + "offset":[0,56], + "quad_division":[5,1] +} diff --git a/game/database/resources/texture/bare-tiles.json b/game/database/resources/texture/bare-tiles.json new file mode 100644 index 00000000..79359c44 --- /dev/null +++ b/game/database/resources/texture/bare-tiles.json @@ -0,0 +1,3 @@ +{ + "filename":"tiles.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/box.json b/game/database/resources/texture/box.json new file mode 100644 index 00000000..16ec9f77 --- /dev/null +++ b/game/database/resources/texture/box.json @@ -0,0 +1,3 @@ +{ + "filename":"box.png" +} diff --git a/game/database/resources/texture/buffer-card.json b/game/database/resources/texture/buffer-card.json new file mode 100644 index 00000000..3ac1edd6 --- /dev/null +++ b/game/database/resources/texture/buffer-card.json @@ -0,0 +1,3 @@ +{ + "filename":"card_back_1.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-base.json b/game/database/resources/texture/card-base.json new file mode 100644 index 00000000..51ee358e --- /dev/null +++ b/game/database/resources/texture/card-base.json @@ -0,0 +1,3 @@ +{ + "filename":"card.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-blaster.json b/game/database/resources/texture/card-blaster.json new file mode 100644 index 00000000..ac4b9b97 --- /dev/null +++ b/game/database/resources/texture/card-blaster.json @@ -0,0 +1,3 @@ +{ + "filename":"ray-gun.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-divination.json b/game/database/resources/texture/card-divination.json new file mode 100644 index 00000000..0e09929d --- /dev/null +++ b/game/database/resources/texture/card-divination.json @@ -0,0 +1,3 @@ +{ + "filename":"triorb.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-dropkick.json b/game/database/resources/texture/card-dropkick.json new file mode 100644 index 00000000..f0490045 --- /dev/null +++ b/game/database/resources/texture/card-dropkick.json @@ -0,0 +1,3 @@ +{ + "filename":"fire-punch.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-fireball.json b/game/database/resources/texture/card-fireball.json new file mode 100644 index 00000000..7b7aabff --- /dev/null +++ b/game/database/resources/texture/card-fireball.json @@ -0,0 +1,3 @@ +{ + "filename":"burning-meteor.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-firebolt.json b/game/database/resources/texture/card-firebolt.json new file mode 100644 index 00000000..9dee1eb7 --- /dev/null +++ b/game/database/resources/texture/card-firebolt.json @@ -0,0 +1,3 @@ +{ + "filename":"burning-dot.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-flash.json b/game/database/resources/texture/card-flash.json new file mode 100644 index 00000000..49dad3a3 --- /dev/null +++ b/game/database/resources/texture/card-flash.json @@ -0,0 +1,3 @@ +{ + "filename":"fission.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-focus-gem.json b/game/database/resources/texture/card-focus-gem.json new file mode 100644 index 00000000..c2d56764 --- /dev/null +++ b/game/database/resources/texture/card-focus-gem.json @@ -0,0 +1,3 @@ +{ + "filename":"emerald.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-goggles.json b/game/database/resources/texture/card-goggles.json new file mode 100644 index 00000000..c9579d7d --- /dev/null +++ b/game/database/resources/texture/card-goggles.json @@ -0,0 +1,3 @@ +{ + "filename":"card-goggles.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-haste.json b/game/database/resources/texture/card-haste.json new file mode 100644 index 00000000..05db64d5 --- /dev/null +++ b/game/database/resources/texture/card-haste.json @@ -0,0 +1,3 @@ +{ + "filename":"upgrade.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-heal.json b/game/database/resources/texture/card-heal.json new file mode 100644 index 00000000..2a6b481d --- /dev/null +++ b/game/database/resources/texture/card-heal.json @@ -0,0 +1,3 @@ +{ + "filename":"hospital-cross.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-illusion-of-darkness.json b/game/database/resources/texture/card-illusion-of-darkness.json new file mode 100644 index 00000000..d6cd948d --- /dev/null +++ b/game/database/resources/texture/card-illusion-of-darkness.json @@ -0,0 +1,3 @@ +{ + "filename":"warlock-eye.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-jetboots.json b/game/database/resources/texture/card-jetboots.json new file mode 100644 index 00000000..2c709fc6 --- /dev/null +++ b/game/database/resources/texture/card-jetboots.json @@ -0,0 +1,3 @@ +{ + "filename":"boots.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-jump.json b/game/database/resources/texture/card-jump.json new file mode 100644 index 00000000..6bb4ebae --- /dev/null +++ b/game/database/resources/texture/card-jump.json @@ -0,0 +1,3 @@ +{ + "filename":"wide-arrow-dunk.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-laser-spear.json b/game/database/resources/texture/card-laser-spear.json new file mode 100644 index 00000000..58e599b6 --- /dev/null +++ b/game/database/resources/texture/card-laser-spear.json @@ -0,0 +1,3 @@ +{ + "filename":"magic-trident.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-meditation.json b/game/database/resources/texture/card-meditation.json new file mode 100644 index 00000000..41a1b81a --- /dev/null +++ b/game/database/resources/texture/card-meditation.json @@ -0,0 +1,3 @@ +{ + "filename":"meditation.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-potion.json b/game/database/resources/texture/card-potion.json new file mode 100644 index 00000000..97901591 --- /dev/null +++ b/game/database/resources/texture/card-potion.json @@ -0,0 +1,3 @@ +{ + "filename":"square-bottle.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-power-suit.json b/game/database/resources/texture/card-power-suit.json new file mode 100644 index 00000000..b7c53c42 --- /dev/null +++ b/game/database/resources/texture/card-power-suit.json @@ -0,0 +1,3 @@ +{ + "filename":"chest-armor.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-rage.json b/game/database/resources/texture/card-rage.json new file mode 100644 index 00000000..40890091 --- /dev/null +++ b/game/database/resources/texture/card-rage.json @@ -0,0 +1,3 @@ +{ + "filename":"fire-silhouette.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-reassemble.json b/game/database/resources/texture/card-reassemble.json new file mode 100644 index 00000000..f45be2b5 --- /dev/null +++ b/game/database/resources/texture/card-reassemble.json @@ -0,0 +1,3 @@ +{ + "filename":"auto-repair.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-reinforce.json b/game/database/resources/texture/card-reinforce.json new file mode 100644 index 00000000..ba74a143 --- /dev/null +++ b/game/database/resources/texture/card-reinforce.json @@ -0,0 +1,3 @@ +{ + "filename":"metal-scales.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-rush.json b/game/database/resources/texture/card-rush.json new file mode 100644 index 00000000..12d6fd2b --- /dev/null +++ b/game/database/resources/texture/card-rush.json @@ -0,0 +1,3 @@ +{ + "filename":"fire-dash.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-seedling-sap.json b/game/database/resources/texture/card-seedling-sap.json new file mode 100644 index 00000000..1e68b0df --- /dev/null +++ b/game/database/resources/texture/card-seedling-sap.json @@ -0,0 +1,3 @@ +{ + "filename":"brandy-bottle.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-sensor-bomb.json b/game/database/resources/texture/card-sensor-bomb.json new file mode 100644 index 00000000..fb6254a4 --- /dev/null +++ b/game/database/resources/texture/card-sensor-bomb.json @@ -0,0 +1,3 @@ +{ + "filename":"land-mine.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-soul-robe.json b/game/database/resources/texture/card-soul-robe.json new file mode 100644 index 00000000..e981f52f --- /dev/null +++ b/game/database/resources/texture/card-soul-robe.json @@ -0,0 +1,3 @@ +{ + "filename":"robe.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-spark-rod.json b/game/database/resources/texture/card-spark-rod.json new file mode 100644 index 00000000..1863a166 --- /dev/null +++ b/game/database/resources/texture/card-spark-rod.json @@ -0,0 +1,3 @@ +{ + "filename":"lunar-wand.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-sticky-flame.json b/game/database/resources/texture/card-sticky-flame.json new file mode 100644 index 00000000..d62f9f02 --- /dev/null +++ b/game/database/resources/texture/card-sticky-flame.json @@ -0,0 +1,3 @@ +{ + "filename":"fluffy-flame.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-stomp.json b/game/database/resources/texture/card-stomp.json new file mode 100644 index 00000000..e91f6bcc --- /dev/null +++ b/game/database/resources/texture/card-stomp.json @@ -0,0 +1,3 @@ +{ + "filename":"boot-stomp.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-teleport.json b/game/database/resources/texture/card-teleport.json new file mode 100644 index 00000000..9dca472c --- /dev/null +++ b/game/database/resources/texture/card-teleport.json @@ -0,0 +1,3 @@ +{ + "filename":"teleport.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-timer-bomb.json b/game/database/resources/texture/card-timer-bomb.json new file mode 100644 index 00000000..16b26d43 --- /dev/null +++ b/game/database/resources/texture/card-timer-bomb.json @@ -0,0 +1,3 @@ +{ + "filename":"time-bomb.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-turret.json b/game/database/resources/texture/card-turret.json new file mode 100644 index 00000000..a0926315 --- /dev/null +++ b/game/database/resources/texture/card-turret.json @@ -0,0 +1,3 @@ +{ + "filename":"card-turret.png" +} diff --git a/game/database/resources/texture/card-upgrade.json b/game/database/resources/texture/card-upgrade.json new file mode 100644 index 00000000..eb20e7a4 --- /dev/null +++ b/game/database/resources/texture/card-upgrade.json @@ -0,0 +1,3 @@ +{ + "filename":"orb-direction.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/card-wrench.json b/game/database/resources/texture/card-wrench.json new file mode 100644 index 00000000..33e029b0 --- /dev/null +++ b/game/database/resources/texture/card-wrench.json @@ -0,0 +1,3 @@ +{ + "filename":"monkey-wrench.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/corgi.json b/game/database/resources/texture/corgi.json new file mode 100644 index 00000000..3ec41f0f --- /dev/null +++ b/game/database/resources/texture/corgi.json @@ -0,0 +1,3 @@ +{ + "filename":"notagoodboy.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/cursor.json b/game/database/resources/texture/cursor.json new file mode 100644 index 00000000..e4ad7ef3 --- /dev/null +++ b/game/database/resources/texture/cursor.json @@ -0,0 +1,3 @@ +{ + "filename":"cursor_tile.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/demobody.json b/game/database/resources/texture/demobody.json new file mode 100644 index 00000000..f26e5e36 --- /dev/null +++ b/game/database/resources/texture/demobody.json @@ -0,0 +1,3 @@ +{ + "filename":"demobody.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/eyedemon.json b/game/database/resources/texture/eyedemon.json new file mode 100644 index 00000000..1fa342f3 --- /dev/null +++ b/game/database/resources/texture/eyedemon.json @@ -0,0 +1,3 @@ +{ + "filename":"floating-eye-demon.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/hearthborn.json b/game/database/resources/texture/hearthborn.json new file mode 100644 index 00000000..864b0642 --- /dev/null +++ b/game/database/resources/texture/hearthborn.json @@ -0,0 +1,3 @@ +{ + "filename":"hearthborn-idle.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/hillborn.json b/game/database/resources/texture/hillborn.json new file mode 100644 index 00000000..21fb1698 --- /dev/null +++ b/game/database/resources/texture/hillborn.json @@ -0,0 +1,3 @@ +{ + "filename":"hillborn-idle.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/icon-activate_widget.json b/game/database/resources/texture/icon-activate_widget.json new file mode 100644 index 00000000..ee288d7c --- /dev/null +++ b/game/database/resources/texture/icon-activate_widget.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-activate_widget.png" +} diff --git a/game/database/resources/texture/icon-consume_cards_from_buffer.json b/game/database/resources/texture/icon-consume_cards_from_buffer.json new file mode 100644 index 00000000..9c1a5661 --- /dev/null +++ b/game/database/resources/texture/icon-consume_cards_from_buffer.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-manage-buffer.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/icon-draw_new_hand.json b/game/database/resources/texture/icon-draw_new_hand.json new file mode 100644 index 00000000..b2aba2a5 --- /dev/null +++ b/game/database/resources/texture/icon-draw_new_hand.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-draw_new_hand.png" +} diff --git a/game/database/resources/texture/icon-idle.json b/game/database/resources/texture/icon-idle.json new file mode 100644 index 00000000..992c6663 --- /dev/null +++ b/game/database/resources/texture/icon-idle.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-idle.png" +} diff --git a/game/database/resources/texture/icon-interact.json b/game/database/resources/texture/icon-interact.json new file mode 100644 index 00000000..98f54861 --- /dev/null +++ b/game/database/resources/texture/icon-interact.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-interact.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/icon-none.json b/game/database/resources/texture/icon-none.json new file mode 100644 index 00000000..5222fe6b --- /dev/null +++ b/game/database/resources/texture/icon-none.json @@ -0,0 +1,3 @@ +{ + "filename":"no-icon.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/icon-play_card.json b/game/database/resources/texture/icon-play_card.json new file mode 100644 index 00000000..823078e4 --- /dev/null +++ b/game/database/resources/texture/icon-play_card.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-play_card.png" +} diff --git a/game/database/resources/texture/icon-receive_pack.json b/game/database/resources/texture/icon-receive_pack.json new file mode 100644 index 00000000..b9c2a3f4 --- /dev/null +++ b/game/database/resources/texture/icon-receive_pack.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-receive_pack.png" +} diff --git a/game/database/resources/texture/icon-use_signature.json b/game/database/resources/texture/icon-use_signature.json new file mode 100644 index 00000000..eef4c1c9 --- /dev/null +++ b/game/database/resources/texture/icon-use_signature.json @@ -0,0 +1,3 @@ +{ + "filename":"icon-use_signature.png" +} diff --git a/game/database/resources/texture/imp.json b/game/database/resources/texture/imp.json new file mode 100644 index 00000000..8888e4ea --- /dev/null +++ b/game/database/resources/texture/imp.json @@ -0,0 +1,3 @@ +{ + "filename":"imp.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/pack-drop.json b/game/database/resources/texture/pack-drop.json new file mode 100644 index 00000000..ac808499 --- /dev/null +++ b/game/database/resources/texture/pack-drop.json @@ -0,0 +1,3 @@ +{ + "filename":"box-pack.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/pack.json b/game/database/resources/texture/pack.json new file mode 100644 index 00000000..d13ac2c7 --- /dev/null +++ b/game/database/resources/texture/pack.json @@ -0,0 +1,3 @@ +{ + "filename":"pack.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/panel-theme.json b/game/database/resources/texture/panel-theme.json new file mode 100644 index 00000000..40ba461c --- /dev/null +++ b/game/database/resources/texture/panel-theme.json @@ -0,0 +1,3 @@ +{ + "filename":"panel-theme.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/pixel.json b/game/database/resources/texture/pixel.json new file mode 100644 index 00000000..5e98bd54 --- /dev/null +++ b/game/database/resources/texture/pixel.json @@ -0,0 +1,3 @@ +{ + "filename":"pixel.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/purple-tiles.json b/game/database/resources/texture/purple-tiles.json new file mode 100644 index 00000000..990f652a --- /dev/null +++ b/game/database/resources/texture/purple-tiles.json @@ -0,0 +1,3 @@ +{ + "filename":"tiles-purplr.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/slime.json b/game/database/resources/texture/slime.json new file mode 100644 index 00000000..f649c09a --- /dev/null +++ b/game/database/resources/texture/slime.json @@ -0,0 +1,3 @@ +{ + "filename":"slimy-bouncy.png" +} \ No newline at end of file diff --git a/game/database/resources/texture/tiles-alt.json b/game/database/resources/texture/tiles-alt.json new file mode 100644 index 00000000..d58a3ce7 --- /dev/null +++ b/game/database/resources/texture/tiles-alt.json @@ -0,0 +1,3 @@ +{ + "filename":"tile-testing.png" +} \ No newline at end of file diff --git a/game/database/resources/tileset/demo.json b/game/database/resources/tileset/demo.json new file mode 100644 index 00000000..8974d774 --- /dev/null +++ b/game/database/resources/tileset/demo.json @@ -0,0 +1,9 @@ +{ + "mapping":{ + "EXIT":[80,0,80,160,0,80], + "WALL":[160,0,80,160,0,80], + "FLOOR":[0,0,80,160,0,80], + "ALTAR":[240,0,80,160,0,80] + }, + "texture":"bare-tiles" +} \ No newline at end of file diff --git a/game/database/resources/tileset/otherdemo.json b/game/database/resources/tileset/otherdemo.json new file mode 100644 index 00000000..91411078 --- /dev/null +++ b/game/database/resources/tileset/otherdemo.json @@ -0,0 +1,9 @@ +{ + "mapping":{ + "EXIT":[80,0,80,160,0,80], + "WALL":[160,0,80,160,0,80], + "FLOOR":[0,0,80,160,0,80], + "ALTAR":[240,0,80,160,0,80] + }, + "texture":"purple-tiles" +} \ No newline at end of file diff --git a/game/database/resources/tileset/ruins.json b/game/database/resources/tileset/ruins.json new file mode 100644 index 00000000..44e5e9b4 --- /dev/null +++ b/game/database/resources/tileset/ruins.json @@ -0,0 +1,9 @@ +{ + "mapping":{ + "EXIT":[80,160,80,160,0,80], + "WALL":[0,320,80,160,0,80], + "FLOOR":[0,0,80,160,0,80], + "ALTAR":[240,0,80,160,0,80] + }, + "texture":"tiles-alt" +} \ No newline at end of file diff --git a/game/database/schema/action.lua b/game/database/schema/action.lua new file mode 100644 index 00000000..643f1e08 --- /dev/null +++ b/game/database/schema/action.lua @@ -0,0 +1,6 @@ + +return { + { id = 'cost', name = "Time cost", type = 'integer', range = {0,128} }, + { id = 'ability', name = "Activated Ability", type = 'ability' }, +} + diff --git a/game/database/schema/actor.lua b/game/database/schema/actor.lua new file mode 100644 index 00000000..297e28ce --- /dev/null +++ b/game/database/schema/actor.lua @@ -0,0 +1,35 @@ + +local behaviors = love.filesystem.getDirectoryItems("domain/behaviors/") +do + for i=1, #behaviors do + behaviors[i] = behaviors[i]:gsub("[.]lua", "") + end +end + +return { + { id = 'extends', name = "Prototype", type = 'enum', + options = 'domains.actor', optional = true }, + { id = 'name', name = "Full Name", type = 'string' }, + { id = 'description', name = "Description", type = 'text' }, + { id = 'behavior', name = "Behavior", type = 'enum', + options = behaviors }, + { id = 'signature', name = "Signature Ability", type = 'enum', + options = 'domains.action' }, + { id = 'traits', name = "Traits", type = 'array', + schema = { + { id = 'specname', name = "Trait", type = 'enum', + options = "domains.card" }, + } + }, + { id = 'cor', name = "COR Aptitude", type = 'range', min = -2, max = 2 }, + { id = 'arc', name = "ARC Aptitude", type = 'range', min = -2, max = 2 }, + { id = 'ani', name = "ANI Aptitude", type = 'range', min = -2, max = 2 }, + { id = 'collection', name = "Drops", type = 'enum', + options = 'domains.collection' }, + { id = 'initial_buffer', name = "Buffer Card", type = 'array', + schema = { + { id = 'card', name = "Card", type = 'enum', options = "domains.card" }, + { id = 'amount', name = "Amount", type = 'integer', range = {1,16} }, + } + }, +} diff --git a/game/database/schema/appearance.lua b/game/database/schema/appearance.lua new file mode 100644 index 00000000..97768878 --- /dev/null +++ b/game/database/schema/appearance.lua @@ -0,0 +1,5 @@ + +return { + { id = "idle", name = "Idle", type = 'enum', options = 'resources.sprite' }, +} + diff --git a/game/database/schema/bgm.lua b/game/database/schema/bgm.lua new file mode 100644 index 00000000..f1d2ebdb --- /dev/null +++ b/game/database/schema/bgm.lua @@ -0,0 +1,12 @@ + +local filelist = love.filesystem.getDirectoryItems("assets/bgm") +local files = {} +for _,file in ipairs(filelist) do + if file:match("^.+%.ogg$") then + table.insert(files, file) + end +end + +return { + { id = 'filename', name = "Filename", type = 'enum', options = files } +} diff --git a/game/database/schema/body.lua b/game/database/schema/body.lua new file mode 100644 index 00000000..b9325264 --- /dev/null +++ b/game/database/schema/body.lua @@ -0,0 +1,23 @@ + +return { + { id = 'extends', name = "Prototype", type = "enum", options = 'domains.body', + optional = true}, + { id = 'name', name = "Full Name", type = "string" }, + { id = 'appearance', name = "Appearance", type = "enum", + options = 'domains.appearance' }, + { id = 'faction', name = "Faction", type = "enum", + options = 'domains.faction'}, + { id = 'res', name = "Resistance", type = "integer", range = {-2,2} }, + { id = 'fin', name = "Finesse", type = "integer", range = {-2,2} }, + { id = 'con', name = "Constitution", type = "integer", range = {-2,2} }, + { id = 'drops', name = 'Drops', type = 'array', + schema = { + { id = 'droptype', name = "Drop Type", type = 'enum', + options = 'domains.drop' }, + { id = 'droprate', name = "Drop Rate", type = 'integer', + range = {0, 100} }, + }, + }, + { id = 'description', name = "Description", type = 'text' }, +} + diff --git a/game/database/schema/card.lua b/game/database/schema/card.lua new file mode 100644 index 00000000..07991610 --- /dev/null +++ b/game/database/schema/card.lua @@ -0,0 +1,74 @@ + +local DEFS_PACK = require 'lux.pack' 'domain.definitions' +local DEFS = require 'domain.definitions' + +return { + { id = 'one_time', name = "Is One Time Usage", type = 'boolean' }, + { id = 'name', name = "Name", type = 'string' }, + { id = 'icon', name = "Icon", type = 'enum', + options = 'resources.texture', + optional = true }, + { id = 'set', name = "Card Set", type = 'enum', options = 'domains.cardset' }, + { id = 'desc', name = "Description", type = 'text' }, + { id = 'attr', name = "Type (attr)", type = 'enum', + options = DEFS.CARD_ATTRIBUTES }, + { id = 'type-description', type = 'description', + info = "Cards can be either Arts or Widgets" }, + { + id = 'art', name = "Art", + type = 'section', + schema = { + { id = 'cost', name = "Cost", type = 'integer', range = {0} }, + { id = 'art_ability', name = "Art Ability", type = 'ability', + hint = "Happens when card is played from hand" }, + } + }, + { + id = 'widget', name = "Widget", + type = 'section', + schema = { + { id = 'charges', name = "Charges", type = 'integer', range = {0}, }, + { id = 'trigger', name = "Spend Trigger", type = 'enum', + options = DEFS.TRIGGERS }, + { id = 'trigger-condition', name = "Spend Trigger Condition", + type = 'ability', optional = true }, + { id = 'placement', name = "Placement", type = 'enum', + options = DEFS_PACK.placements, optional = true }, + { + id = 'operators', name = "Static Attribute Operator", + type = 'array', schema = { + { id = 'attr', name = "Attribute", type = 'enum', + options = DEFS.ALL_ATTRIBUTES }, + { id = 'op', name = "Operator", type = 'enum', + options = { '+', '-', '*', '/' } }, + { id = 'val', name = "Value", type = 'integer' }, + } + }, + { + id = 'status-tags', name = "Status Tag", type = 'array', + schema = { + { id = 'tag', name = "Tag", type = 'enum', + options = DEFS.STATUS_TAGS } + } + }, + { + id = 'activation', name = "Activated Ability", type = 'section', + schema = { + { id = 'cost', name = "Time Cost", type = 'integer', + range = {0} }, + { id = 'ability', name = "Ability", type = 'ability', + hint = "Happens when widget is activated" } + } + }, + { + id = 'auto_activation', name = "Triggered Ability", type = 'section', + schema = { + { id = 'trigger', name = "Trigger", type = 'enum', + options = DEFS.TRIGGERS }, + { id = 'ability', name = "Ability", type = 'ability', + hint = "Happens when trigger is detected" } + } + }, + }, + } +} diff --git a/game/database/schema/cardset.lua b/game/database/schema/cardset.lua new file mode 100644 index 00000000..e6b4f11f --- /dev/null +++ b/game/database/schema/cardset.lua @@ -0,0 +1,7 @@ + +return { + { id = 'name', name = "Unique Name", type = 'string' }, + { id = 'parent', name = "Belongs To", type = 'enum', + options = 'domains.cardset' } +} + diff --git a/game/database/schema/collection.lua b/game/database/schema/collection.lua new file mode 100644 index 00000000..35183a1f --- /dev/null +++ b/game/database/schema/collection.lua @@ -0,0 +1,15 @@ + +return { + { id = 'name', name = "Full Name", type = "string" }, + { id = 'image', name = "Image", type = 'enum', options = 'resources.texture' }, + { id = 'description', name = "Description", type = 'text' }, + { + id = 'cards', name = "Cards", type = 'array', + schema = { + { id = 'set', name = "Card Set", type = 'enum', + options = 'domains.cardset' }, + { id = 'drop', name = "Drop Rate", type = 'integer', + range = {1, 100} }, + } + } +} diff --git a/game/database/schema/drop.lua b/game/database/schema/drop.lua new file mode 100644 index 00000000..9fa7ee9b --- /dev/null +++ b/game/database/schema/drop.lua @@ -0,0 +1,8 @@ + +return { + { id = 'name', name = "Name", type = 'string' }, + { id = "sprite", name = "Sprite", type = 'enum', + options = 'resources.texture' }, + { id = 'ability', name = "Triggered Ability", type = 'ability' } +} + diff --git a/game/database/schema/faction.lua b/game/database/schema/faction.lua new file mode 100644 index 00000000..160bd5c2 --- /dev/null +++ b/game/database/schema/faction.lua @@ -0,0 +1,4 @@ + +return { + { id = 'name', name = "Faction name", type = 'string' } +} diff --git a/game/database/schema/font.lua b/game/database/schema/font.lua new file mode 100644 index 00000000..0cbb5fb9 --- /dev/null +++ b/game/database/schema/font.lua @@ -0,0 +1,13 @@ + +local filelist = love.filesystem.getDirectoryItems("assets/font") +local files = {} +for _,file in ipairs(filelist) do + if file:match("^.+%.ttf$") then + table.insert(files, file) + end +end + +return { + { id = 'filename', name = "Filename", type = 'enum', options = files } +} + diff --git a/game/database/schema/sector.lua b/game/database/schema/sector.lua new file mode 100644 index 00000000..e70e533a --- /dev/null +++ b/game/database/schema/sector.lua @@ -0,0 +1,27 @@ + +return { + { id = 'theme', name = "Theme", type = 'enum', options = 'domains.theme' }, + { id = 'bootstrap', name = "Base Settings", type = 'section', + schema = 'transformers.bootstrap', required = true }, + { id = 'holes', name = "Holes Settings", type = 'section', + schema = 'transformers.holes' }, + { id = 'rooms', name = "Room Settings", type = 'section', + schema = 'transformers.rooms' }, + { id = 'maze', name = "Maze Settings", type = 'section', + schema = 'transformers.maze' }, + { id = 'connections', name = "Connection Settings", type = 'section', + schema = 'transformers.connections' }, + { id = 'deadends', name = "Deadend Settings", type = 'section', + schema = 'transformers.deadends' }, + { id = 'fillings', name = "Fillings Settings", type = 'section', + schema = 'transformers.fillings' }, + { id = 'exits', name = "Exit Settings", type = 'section', + schema = 'transformers.exits' }, + { id = 'drops', name = "Drops Settings", type = 'section', + schema = 'transformers.drops' }, + { id = 'altars', name = "Altar Settings", type = 'section', + schema = 'transformers.altars' }, + { id = 'encounters', name = "Encounter Settings", type = 'section', + schema = 'transformers.encounters' }, +} + diff --git a/game/database/schema/sfx.lua b/game/database/schema/sfx.lua new file mode 100644 index 00000000..99c39a56 --- /dev/null +++ b/game/database/schema/sfx.lua @@ -0,0 +1,12 @@ + +local filelist = love.filesystem.getDirectoryItems("assets/sfx") +local files = {} +for _,file in ipairs(filelist) do + if file:match("^.+%.wav$") then + table.insert(files, file) + end +end + +return { + { id = 'filename', name = "Filename", type = 'enum', options = files } +} diff --git a/game/database/schema/sprite.lua b/game/database/schema/sprite.lua new file mode 100644 index 00000000..4624b1d3 --- /dev/null +++ b/game/database/schema/sprite.lua @@ -0,0 +1,18 @@ + +return { + { id = 'texture', name = "Texture", type = 'enum', + options = 'resources.texture' }, + { id = 'loop', name = "Loop", type = 'boolean' }, + { id = 'offset', name = "Offset", type = 'vector', size = 2 }, + { id = 'quad_division', name = "Quad Division", type = 'vector', size = 2, + signature = {'cols', 'rows'} }, + { id = 'animation', name = "Frame", type = 'array', + schema = { + { id = 'quad_idx', name = "Quad Index", type = 'integer', + range = {1, 999} }, + { id = 'time', name = "Miliseconds", type = 'integer', + range = {1, 999} }, + } + } +} + diff --git a/game/database/schema/texture.lua b/game/database/schema/texture.lua new file mode 100644 index 00000000..4cbfdfea --- /dev/null +++ b/game/database/schema/texture.lua @@ -0,0 +1,13 @@ + +local filelist = love.filesystem.getDirectoryItems("assets/texture") +local files = {} +for _,file in ipairs(filelist) do + if file:match("^.+%.png$") then + table.insert(files, file) + end +end + +return { + { id = 'filename', name = "Filename", type = 'enum', options = files } +} + diff --git a/game/database/schema/theme.lua b/game/database/schema/theme.lua new file mode 100644 index 00000000..67153821 --- /dev/null +++ b/game/database/schema/theme.lua @@ -0,0 +1,6 @@ + +return { + { id = "bgm", name = "BGM", type = 'enum', options = 'resources.bgm' }, + { id = "tileset", name = "TileSet", type = 'enum', options = 'resources.tileset' }, +} + diff --git a/game/database/schema/tileset.lua b/game/database/schema/tileset.lua new file mode 100644 index 00000000..617e46f0 --- /dev/null +++ b/game/database/schema/tileset.lua @@ -0,0 +1,23 @@ + +return { + { id = 'texture', name = "Texture", type = "enum", + options = 'resources.texture' }, + { id = 'mapping', name = "Mapping", type = 'section', + required = true, + schema = { + { id = 'FLOOR', name = "Floor Quad #", type = 'vector', + size = 6, range = {0}, + signature = {'ix', 'iy', 'qw', 'qh', 'ox', 'oy'} }, + { id = 'WALL', name = "Wall Quad #", type = 'vector', + size = 6, range = {0}, + signature = {'ix', 'iy', 'qw', 'qh', 'ox', 'oy'} }, + { id = 'ALTAR', name = "Altar Quad #", type = 'vector', + size = 6, range = {0}, + signature = {'ix', 'iy', 'qw', 'qh', 'ox', 'oy'} }, + { id = 'EXIT', name = "Exit Quad #", type = 'vector', + size = 6, range = {0}, + signature = {'ix', 'iy', 'qw', 'qh', 'ox', 'oy'} }, + }, + } +} + diff --git a/game/database/schema/zone.lua b/game/database/schema/zone.lua new file mode 100644 index 00000000..b1b56965 --- /dev/null +++ b/game/database/schema/zone.lua @@ -0,0 +1,9 @@ + +return { + { id = 'name', name = "Zone Name", type = 'string' }, + { id = 'theme', name = "Zone Theme", type = 'enum', + options = "domains.theme"}, + { id = 'difficulty', name = "Difficulty Level", type = 'integer', + range = {0,100} }, +} + diff --git a/game/database/settings/controls.json b/game/database/settings/controls.json new file mode 100644 index 00000000..d85b387c --- /dev/null +++ b/game/database/settings/controls.json @@ -0,0 +1,30 @@ +{ + "digital":{ + "ACTION_1":["r"], + "ACTION_2":["e"], + "ACTION_3":["w"], + "ACTION_4":["q"], + "CONFIRM":["f", true, "return"], + "CANCEL":["d"], + "SPECIAL":["s"], + "EXTRA":["a"], + "UP":["up","k"], + "RIGHT":["right","l"], + "DOWN":["down","j"], + "LEFT":["left","h"], + "UPLEFT":["home","y"], + "UPRIGHT":["pageup","u"], + "DOWNRIGHT":["pagedown","n"], + "DOWNLEFT":["end","b"], + "QUIT":["f8"], + "PAUSE":["escape"], + "MODIFIER":["lshift"] + }, + "analog":{ + "AXIS_X":1, + "AXIS_Y":2 + }, + "hat":{ + "HAT_DIRECTIONALS":1 + } +} diff --git a/game/database/settings/init_tiledata.json b/game/database/settings/init_tiledata.json new file mode 100644 index 00000000..959bdf79 --- /dev/null +++ b/game/database/settings/init_tiledata.json @@ -0,0 +1,84 @@ +{ + "tiles":[[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":">","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },{ + "type":".","drops":[] + },false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false],[false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false]], + "exit":[10, 12] +} diff --git a/game/database/settings/playable.json b/game/database/settings/playable.json new file mode 100644 index 00000000..a21528bf --- /dev/null +++ b/game/database/settings/playable.json @@ -0,0 +1,4 @@ +{ + "species":["hearthborn","hillborn"], + "background":["brawler","arcanist","gadgeteer"] +} diff --git a/game/database/settings/user-preferences.json b/game/database/settings/user-preferences.json new file mode 100644 index 00000000..a9f9f393 --- /dev/null +++ b/game/database/settings/user-preferences.json @@ -0,0 +1,12 @@ +{ + "bgm-volume": { + "default": 100, + "range": [0, 100], + "step": 10 + }, + "sfx-volume": { + "default": 100, + "range": [0, 100], + "step": 10 + } +} diff --git a/game/debug/gui.lua b/game/debug/gui.lua new file mode 100644 index 00000000..3a02cc44 --- /dev/null +++ b/game/debug/gui.lua @@ -0,0 +1,208 @@ + +local IMGUI = require 'imgui' +local DB = require 'database' +local tween = require 'helpers.tween' + +local MENU_WIDTH = 240 +local MENU_MAX_HEIGHT = 600 + +local GUI = Class { + __includes = { ELEMENT } +} + +local DOMAINS = { + 'body', 'actor', 'appearance', 'sector', + 'card', 'cardset', 'collection', 'action', + 'drop', 'faction', 'zone', 'theme', + body = "Body Type", + actor = "Actor Type", + appearance = "Appearance", + sector = "Sector Type", + card = "Card", + cardset = "Card Set", + collection = "Collection", + action = "Signature", + drop = "Drop", + faction = "Faction", + theme = "Theme", + zone = "Zone", +} + +local RESOURCES = { + 'font', 'texture', 'sprite', 'tileset', 'sfx', 'bgm', + font = "Font", + texture = "Texture", + sfx = "Sound Effect", + bgm = "Background Music", + sprite = "Animated Sprite", + tileset = "TileSet", +} + + +local view = {} + +-- This automatically loads all debug menus in debug/view +for _,file in ipairs(love.filesystem.getDirectoryItems "debug/view") do + if file:match "^.+%.lua$" then + file = file:gsub("%.lua", "") + view[file] = require('debug.view.' .. file) + end +end + +function GUI:init(sector_view) + + ELEMENT.init(self) + self.stack = {} + self.active = false + self.current_level = 1 + self.sector_view = sector_view + self.demo_window = false + + IMGUI.StyleColorsDark() + +end + +function GUI:length() + local length = 0 + for _,view in ipairs(self.stack) do + length = length + view.size + end + return length +end + +--- Pushes a menu. Menus in debug mode appear from left to right and behave like +-- a stack. It's easier to understand if you play and check it out. +function GUI:push(viewname, ...) + local level = self.current_level+1 + self:pop(level) + local title, size, render = view[viewname](...) + local length = self:length() + local width = MENU_WIDTH * size + local x = tween.start( + (length-size)*MENU_WIDTH, + 8 + length*MENU_WIDTH + #self.stack * 8, + 5 + ) + self.stack[level] = { + size = size, + draw = function (self) + IMGUI.SetNextWindowPos(x(), 40, "Always") + IMGUI.SetNextWindowSizeConstraints(width, 80, width, MENU_MAX_HEIGHT) + IMGUI.PushStyleVar("WindowPadding", 16, 16) + local open = IMGUI.Begin(title, true, + { "NoCollapse", "AlwaysAutoResize", + "AlwaysUseWindowPadding" }) + if open then + open = not render(self) + end + IMGUI.End() + IMGUI.PopStyleVar() + return open + end + } +end + +function GUI:pop(level) + for i=level,#self.stack do + self.stack[i] = nil + end +end + +function GUI:draw() + if DEBUG and not self.active then + self.active = true + elseif not DEBUG then + self.stack = {} + self.active = false + return + end + + self.current_level = 0 + + local g = love.graphics + + IMGUI.NewFrame() + IMGUI.PushStyleVar('FramePadding', 8, 4) + + if IMGUI.BeginMainMenuBar() then + if IMGUI.BeginMenu("Game") then + IMGUI.Text("WIP") + if IMGUI.MenuItem("New Game") then + end + if IMGUI.MenuItem("Save & Quit") then + end + if IMGUI.MenuItem("Load") then + end + IMGUI.EndMenu() + end + if IMGUI.BeginMenu("Current Route") then + if IMGUI.MenuItem("Actors") then + self:push('actors_menu') + end + if IMGUI.MenuItem("Bodies") then + self:push('bodies_menu') + end + IMGUI.EndMenu() + end + if IMGUI.BeginMenu("Domains") then + for _,name in ipairs(DOMAINS) do + local title = DOMAINS[name] + if IMGUI.MenuItem(title.."s") then + self:push("category_list", 'domains', name, title) + end + end + IMGUI.Separator() + if IMGUI.MenuItem("Refresh") then + DB.refresh(DB.loadCategory('domains')) + end + if IMGUI.MenuItem("Save") then + DB.save(DB.loadCategory('domains')) + end + IMGUI.EndMenu() + end + if IMGUI.BeginMenu("Resources") then + for _,name in ipairs(RESOURCES) do + local title = RESOURCES[name] + if IMGUI.MenuItem(title.."s") then + self:push("category_list", 'resources', name, title) + end + end + IMGUI.Separator() + if IMGUI.MenuItem("Refresh") then + DB.refresh(DB.loadCategory('resources')) + end + if IMGUI.MenuItem("Save") then + DB.save(DB.loadCategory('resources')) + end + IMGUI.EndMenu() + end + if IMGUI.BeginMenu("IMGUI") then + if IMGUI.MenuItem("Demo Window") then + self.demo_window = true + end + IMGUI.EndMenu() + end + IMGUI.EndMainMenuBar() + end + + for level,view in ipairs(self.stack) do + self.current_level = level + if not view.draw(self) then + if level > 0 then + self:pop(level) + break + end + end + end + + if self.demo_window then + self.demo_window = IMGUI.ShowDemoWindow(self.demo_window) + end + + g.setBackgroundColor(50/255, 80/255, 80/255, 1) + g.setColor(1, 1, 1) + IMGUI.PopStyleVar(1) + IMGUI.Render() +end + +return GUI diff --git a/game/debug/view/actor_inspector.lua b/game/debug/view/actor_inspector.lua new file mode 100644 index 00000000..892fb07e --- /dev/null +++ b/game/debug/view/actor_inspector.lua @@ -0,0 +1,40 @@ + +local IMGUI = require 'imgui' + +return function (actor) + + local player = actor:getBody():getSector():getRoute().getControlledActor() + return "Actor Inspector", 2, function(gui) + local hp = actor:getBody():getHP() + gui.sector_view:lookAt(actor) + IMGUI.Text(("ID: %s"):format(actor:getId())) + IMGUI.Text(("Title: %s"):format(actor:getTitle())) + IMGUI.Separator() + IMGUI.PushItemWidth(100) + local newhp, changed = IMGUI.SliderInt("Hit Points", hp, 1, + actor:getBody():getMaxHP()) + IMGUI.PopItemWidth() + if changed then + actor:getBody():setHP(newhp) + end + IMGUI.Text(("PWRLVL: %d"):format(actor:getPowerLevel())) + IMGUI.Separator() + IMGUI.Text(("COR: %d"):format(actor:getCOR())) + IMGUI.Text(("ARC: %d"):format(actor:getARC())) + IMGUI.Text(("ANI: %d"):format(actor:getANI())) + IMGUI.Separator() + IMGUI.Text(("SPD: %d"):format(actor:getSPD())) + IMGUI.Separator() + IMGUI.Text(("DEF: %.2f"):format(actor:getBody():getDEF())) + IMGUI.Text(("EFC: %.2f"):format(actor:getBody():getEFC())) + IMGUI.Text(("VIT: %.2f"):format(actor:getBody():getVIT())) + IMGUI.Separator() + IMGUI.Text(("RES: %d"):format(actor:getBody():getRES())) + IMGUI.Text(("FIN: %d"):format(actor:getBody():getFIN())) + IMGUI.Text(("CON: %d"):format(actor:getBody():getCON())) + IMGUI.Separator() + IMGUI.Text(("Armor bonus: %d"):format(actor:getBody():getArmorBonus())) + IMGUI.Text(("Consumption: %d"):format(actor:getBody():getConsumption())) + end + +end diff --git a/game/debug/view/actors_menu.lua b/game/debug/view/actors_menu.lua new file mode 100644 index 00000000..74d908cb --- /dev/null +++ b/game/debug/view/actors_menu.lua @@ -0,0 +1,19 @@ + +local IMGUI = require 'imgui' + +return function() + + local selected = nil + + return "Current Route", 1, function(gui) + for actor,_ in pairs(Util.findSubtype 'actor') do + local identity = ("%s: %s"):format(actor:getId(), actor:getTitle()) + if IMGUI.Selectable(identity, actor == selected) then + selected = actor + gui:push("actor_inspector", actor) + end + end + end + +end + diff --git a/game/debug/view/bodies_menu.lua b/game/debug/view/bodies_menu.lua new file mode 100644 index 00000000..62b3a2a0 --- /dev/null +++ b/game/debug/view/bodies_menu.lua @@ -0,0 +1,19 @@ + +local IMGUI = require 'imgui' + +return function() + + local selected = nil + + return "Current Route", 1, function(gui) + for body in pairs(Util.findSubtype 'body') do + local identity = ("%s: %s"):format(body:getId(), body:getSpec('name')) + if IMGUI.Selectable(identity, body == selected) then + selected = body + gui:push("body_inspector", body) + end + end + end + +end + diff --git a/game/debug/view/body_inspector.lua b/game/debug/view/body_inspector.lua new file mode 100644 index 00000000..f33cb536 --- /dev/null +++ b/game/debug/view/body_inspector.lua @@ -0,0 +1,42 @@ + +local IMGUI = require 'imgui' + +return function (body) + + return "Body Inspector", 2, function(gui) + local hp = body:getHP() + gui.sector_view:lookAt(body) + IMGUI.Text(("ID: %s"):format(body:getId())) + IMGUI.Text(("Species: %s"):format(body:getSpec('name'))) + IMGUI.Separator() + IMGUI.PushItemWidth(100) + local newhp, changed = IMGUI.SliderInt("Hit Points", hp, 1, + body:getMaxHP()) + IMGUI.PopItemWidth() + if changed then + body:setHP(newhp) + end + IMGUI.Separator() + IMGUI.Text(("DEF: %d"):format(body:getDEF())) + IMGUI.Text(("EFC: %d"):format(body:getEFC())) + IMGUI.Text(("VIT: %d"):format(body:getVIT())) + IMGUI.Separator() + IMGUI.Text(("RES: %d"):format(body:getRES())) + IMGUI.Text(("FIN: %d"):format(body:getFIN())) + IMGUI.Text(("CON: %d"):format(body:getCON())) + IMGUI.Separator() + IMGUI.Text(("Armor bonus: %d"):format(body:getArmorBonus())) + IMGUI.Text(("Consumption: %d"):format(body:getConsumption())) + IMGUI.Separator() + IMGUI.Text("Widgets:") + IMGUI.Indent(20) + for _,widget in body:eachWidget() do + local txt = ("%s [%d]"):format( + widget:getName(), widget:getWidgetCharges() - widget:getUsages() + ) + IMGUI.Text(txt) + end + IMGUI.Unindent(20) + end + +end diff --git a/game/debug/view/category_list.lua b/game/debug/view/category_list.lua new file mode 100644 index 00000000..bd2a586c --- /dev/null +++ b/game/debug/view/category_list.lua @@ -0,0 +1,71 @@ + +local IMGUI = require 'imgui' +local DB = require 'database' +local DEFS = require 'domain.definitions' + +local function add(list, specname) + table.insert(list, specname) + list.n = list.n + 1 +end + +local function sort(list) + table.sort(list) +end + +return function(category_name, group_name, title) + local group = DB.loadGroup(category_name, group_name) + local list = { n = 0 } + local selected = 0 + for name in DB.listItemsIn(category_name, group_name) do + add(list, name) + end + sort(list) + + local function delete() + local specname = list[selected] + DB.deleteGroupItem(category_name, group_name, specname) + table.remove(list, selected) + list.n = list.n - 1 + end + + local function newvalue(specname, spec) + local new = spec or DB.initSpec({}, group, specname) + for _,key in DB.schemaFor(group_name) do + if key.type == 'list' then + new[key.id] = new[key.id] or {} + end + end + add(list, specname) + sort(list) + for i,name in DB.listItemsIn(category_name, group_name) do + if name == specname then + selected = i + end + end + end + + local function rename(specname) + local oldspecname = list[selected] + local spec = DB.renameGroupItem(category_name, group_name, + oldspecname, specname) + if spec then + delete() + newvalue(specname, spec) + end + end + + return title .. " List", 1, function(gui) + if IMGUI.Button("New "..title) then + gui:push('name_input', title, newvalue) + end + IMGUI.Text(("All %ss:"):format(title)) + local changed + selected, changed = IMGUI.ListBox("", selected, list, list.n, 15) + if changed and selected then + gui:push('specification_editor', group[list[selected]], group_name, + title, delete, rename) + end + end + +end + diff --git a/game/debug/view/helpers/integer.lua b/game/debug/view/helpers/integer.lua new file mode 100644 index 00000000..84a4a095 --- /dev/null +++ b/game/debug/view/helpers/integer.lua @@ -0,0 +1,12 @@ + +local IMGUI = require 'imgui' + +return function (value, name, range) + local newvalue, changed = IMGUI.InputInt(name, value, 1, 10) + if range then + newvalue = math.max(range[1], + range[2] and math.min(range[2], newvalue) or newvalue) + end + return newvalue, changed +end + diff --git a/game/debug/view/helpers/string.lua b/game/debug/view/helpers/string.lua new file mode 100644 index 00000000..3bf2fed8 --- /dev/null +++ b/game/debug/view/helpers/string.lua @@ -0,0 +1,6 @@ + +local IMGUI = require 'imgui' + +return function (value, name, range) + return IMGUI.InputText(name, value, 64) +end diff --git a/game/debug/view/input/ability.lua b/game/debug/view/input/ability.lua new file mode 100644 index 00000000..97fb6327 --- /dev/null +++ b/game/debug/view/input/ability.lua @@ -0,0 +1,114 @@ + +local IMGUI = require 'imgui' +local DB = require 'database' +local IDGenerator = require 'common.idgenerator' + +local _CMDTYPES = { + 'inputs', 'effects', + inputs = "Input", + effects = "Effect" +} + +local _idgen = IDGenerator() +local inputs = {} + +local function _commandList(gui, ability, cmdtype, selected, delete) + + local result + local cmdoptions = {} + local cmdtypes = {} + + local list = ability[cmdtype] or {} + ability[cmdtype] = list + + for _,option in DB.subschemaTypes(cmdtype) do + table.insert(cmdoptions, option) + table.insert(cmdtypes, cmdtype:sub(1,-2)) + end + + for _,option in DB.subschemaTypes('operators') do + table.insert(cmdoptions, option) + table.insert(cmdtypes, 'operator') + end + + IMGUI.Text(("%ss:"):format(_CMDTYPES[cmdtype])) + IMGUI.Indent(20) + for i,command in ipairs(list) do + local view + if command.output then + view = ("%s -> [%s]"):format(command.name, command.output) + else + view = ("%2d: %s"):format(i, command.name) + end + IMGUI.PushID(("%s/%s:%d"):format(ability, cmdtype, i)) + if IMGUI.Selectable(view, + selected and selected.cmdtype == cmdtype + and selected.idx == i) then + selected = selected or {} + selected.cmdtype = cmdtype + selected.idx = i + gui:push('specification_editor', command, + command.type .. 's/' .. command.name, _CMDTYPES[cmdtype], delete, + nil, ability) + end + IMGUI.PopID() + end + if IMGUI.Button("New " .. _CMDTYPES[cmdtype]) then + gui:push( + 'list_picker', _CMDTYPES[cmdtype], cmdoptions, + function (value) + if value then + local new = { type = cmdtypes[value], name = cmdoptions[value] } + local schema = require('domain.'..new.type..'s.'..new.name).schema + for _,subfield in ipairs(schema) do + if subfield.type == 'output' then + new[subfield.id] = 'label'.._idgen.newID() + end + end + table.insert(list, new) + return true + end + return 0 + end + ) + end + IMGUI.Unindent(20) + return selected +end + +function inputs.ability(spec, field) + + local ability = spec[field.id] + or { inputs = {}, effects = {} } + local selected = nil + + local function delete() + table.remove(ability[selected.cmdtype], selected.idx) + end + + local _active = not (not spec[field.id] and field.optional) + + return function(gui) + if field.optional then + IMGUI.PushID(field.id .. ".check") + _active = IMGUI.Checkbox("", _active) + IMGUI.PopID() + IMGUI.SameLine() + end + IMGUI.Text(("%s"):format(field.name)) + if _active then + IMGUI.Indent(20) + if field.hint then + IMGUI.Text(field.hint) + end + for _,cmdtype in ipairs(_CMDTYPES) do + selected = _commandList(gui, ability, cmdtype, selected, delete) + end + spec[field.id] = ability + IMGUI.Unindent(20) + end + end +end + +return inputs + diff --git a/game/debug/view/input/array.lua b/game/debug/view/input/array.lua new file mode 100644 index 00000000..384d1673 --- /dev/null +++ b/game/debug/view/input/array.lua @@ -0,0 +1,40 @@ + +local IMGUI = require 'imgui' +local INPUT = require 'debug.view.input' +local DB = require 'database' + +local inputs = {} + +function inputs.array(spec, field) + + local array = spec[field.id] or {} + local selected = nil + + spec[field.id] = array + + return function(gui) + local removed + for i,element in ipairs(array) do + IMGUI.Text(("%s #%d"):format(field.name, i)) + IMGUI.Indent(20) + for j,subfield in ipairs(field.schema) do + IMGUI.PushID(i) + INPUT(subfield.type, element, subfield)(gui) + IMGUI.PopID() + end + if IMGUI.Button("Delete##array-button-"..i) then + removed = i + end + IMGUI.Unindent(20) + end + if removed then + table.remove(array,removed) + end + if IMGUI.Button("New " .. field.name) then + table.insert(array, {}) + end + end +end + +return inputs + diff --git a/game/debug/view/input/common.lua b/game/debug/view/input/common.lua new file mode 100644 index 00000000..0ec5e333 --- /dev/null +++ b/game/debug/view/input/common.lua @@ -0,0 +1,98 @@ + +local IMGUI = require 'imgui' + +local max = math.max +local min = math.min + +local inputs = {} + +local function _makeCommon(default, call) + return function(spec, field) + return function(gui) + if field.name then + IMGUI.Text(field.name) + end + local value = spec[field.id] or default + spec[field.id] = value + IMGUI.PushID(field.id) + local newvalue, changed = call(value, field) + IMGUI.PopID() + if changed then + spec[field.id] = newvalue + end + end + end +end + +inputs.boolean = _makeCommon( + false, + function(value, field) + return IMGUI.Checkbox("", value) + end +) + +inputs.float = _makeCommon( + nil, + function(value, field) + value = value or field.default or (field.range or {0})[1] + local range = field.range + local newvalue, changed = IMGUI.InputFloat("", value, 0.1, 0.5) + if range then + newvalue = max(range[1], + range[2] and min(range[2], newvalue) or newvalue) + end + return newvalue, changed + end +) + +inputs.integer = _makeCommon( + 0, + function(value, field) + value = value or (field.range or {0})[1] + local range = field.range + local newvalue, changed = IMGUI.InputInt("", value, 1, 10) + if range then + newvalue = max(range[1], + range[2] and min(range[2], newvalue) or newvalue) + end + return newvalue, changed + end +) + +inputs.string = _makeCommon( + "", + function(value, field) + return IMGUI.InputText("", value, 64) + end +) + +inputs.text = _makeCommon( + "", + function(value, field) + IMGUI.PushItemWidth(360) + local newvalue, changed = IMGUI.InputTextMultiline("", value, 1024) + IMGUI.PopItemWidth() + return newvalue, changed + end +) + +inputs.description = _makeCommon( + "", + function(value, field) + IMGUI.Text(field.info) + return "", false + end +) + +inputs.range = _makeCommon( + 0, + function(value, field) + assert(field.max, "No 'max' field in range input.") + assert(field.min, "No 'min' field in range input.") + value = max(field.min, min(field.max, value or field.min)) + local newvalue, changed = IMGUI.SliderInt("", value, field.min, field.max) + return newvalue, changed + end +) + +return inputs diff --git a/game/debug/view/input/enum.lua b/game/debug/view/input/enum.lua new file mode 100644 index 00000000..03c09a47 --- /dev/null +++ b/game/debug/view/input/enum.lua @@ -0,0 +1,70 @@ + +local IMGUI = require 'imgui' +local DB = require 'database' + +local _NONE = "" + +local inputs = {} + +function inputs.enum(spec, field) + + -- Build option list from given array or from a database domain + local _options = field.options + if type(_options) == 'string' then + local group_name = _options + local category, group = group_name:match("(.-)[%./](.+)") + _options = { _NONE } + for k,v in DB.listItemsIn(category, group) do + table.insert(_options, k) + end + table.sort(_options) + else + _options = { _NONE } + for k,v in ipairs(field.options) do + table.insert(_options, v) + end + end + + -- Find the index of the currently assigned option + local _current = 1 + for i,option in ipairs(_options) do + if option == spec[field.id] then + _current = i + break + end + end + + local _active = not (not spec[field.id] and field.optional) + + if _active and _options[_current] ~= _NONE then + spec[field.id] = spec[field.id] or _options[_current] + else + spec[field.id] = false + end + + return function(gui) + if field.optional then + IMGUI.PushID(field.id .. ".check") + _active = IMGUI.Checkbox("", _active) + IMGUI.PopID() + IMGUI.SameLine() + end + IMGUI.Text(field.name) + if _active then + IMGUI.PushID(field.id) + local value, changed = IMGUI.Combo("", _current, _options, #_options, 15) + IMGUI.PopID() + if changed then + _current = value + if _options[value] == _NONE then + spec[field.id] = false + else + spec[field.id] = _options[value] + end + end + end + end +end + +return inputs + diff --git a/game/debug/view/input/init.lua b/game/debug/view/input/init.lua new file mode 100644 index 00000000..44b01a27 --- /dev/null +++ b/game/debug/view/input/init.lua @@ -0,0 +1,32 @@ + +local IMGUI = require 'imgui' +local INPUT = {} +package.loaded['debug.view.input'] = INPUT + +for _,file in ipairs(love.filesystem.getDirectoryItems "debug/view/input") do + if file:match "^.+%.lua$" then + file = file:gsub("%.lua", "") + if file ~= 'init' then + for k,input in pairs(require('debug.view.input.' .. file)) do + INPUT[k] = input + end + end + end +end + +local function _invalid(spec, field) + return function (gui) + IMGUI.PushStyleColor("Text", 0.8, 0.6, 0, 1) + IMGUI.Text(("Unknown field type: %s"):format(field.type)) + IMGUI.PopStyleColor(1) + end +end + +local meta = {} + +function meta:__call(input_typename, ...) + return (INPUT[input_typename] or _invalid)(...) or _invalid() +end + +return setmetatable(INPUT, meta) + diff --git a/game/debug/view/input/list.lua b/game/debug/view/input/list.lua new file mode 100644 index 00000000..6c165c19 --- /dev/null +++ b/game/debug/view/input/list.lua @@ -0,0 +1,66 @@ + +local IMGUI = require 'imgui' +local DB = require 'database' +local IDGenerator = require 'common.idgenerator' + +local _idgen = IDGenerator() + +local inputs = {} + +function inputs.list(spec, field) + + local list = spec[field.id] or {} + local selected = nil + local typeoptions = {} + + for _,option in DB.subschemaTypes(field.id) do + table.insert(typeoptions, option) + end + + local function delete() + table.remove(list, selected) + end + + return function(gui) + IMGUI.Text(("%ss:"):format(field.name)) + IMGUI.Indent(20) + for i,element in ipairs(list) do + local view + if element.output then + view = ("%s -> [%s]"):format(element.typename, element.output) + else + view = ("%2d: %s"):format(i, element.typename) + end + if IMGUI.Selectable(view, selected == i) then + selected = i + gui:push('specification_editor', element, + field.id .. '/' .. element.typename, field.name, delete, nil, + spec) + end + end + if IMGUI.Button("New " .. field.name) then + gui:push( + 'list_picker', field.name, typeoptions, + function (value) + if value then + local new = { typename = typeoptions[value] } + local schema = + require('domain.'..field.id..'.'..new.typename).schema + for _,subfield in ipairs(schema) do + if subfield.type == 'output' then + new[subfield.id] = 'label'.._idgen.newID() + end + end + table.insert(list, new) + return true + end + return 0 + end + ) + end + IMGUI.Unindent(20) + end +end + +return inputs + diff --git a/game/debug/view/input/reference.lua b/game/debug/view/input/reference.lua new file mode 100644 index 00000000..86c8244c --- /dev/null +++ b/game/debug/view/input/reference.lua @@ -0,0 +1,91 @@ + +local IMGUI = require 'imgui' +local DB = require 'database' + +local inputs = {} + +local function _appendRefs(from, to, spec, match) + for k,item in pairs(from) do + if item == spec then return false end + local t = require(('domain.%ss.%s'):format(item.type, item.name)).type + if not t or t == match then + table.insert(to, "=" .. item.output) + end + end + return true +end + +local function _getRefs(spec, field, parent) + local refs = {} + _appendRefs(parent.inputs, refs, spec, field.match) + local idx = 0 + for i,ref in ipairs(refs) do + if ref == spec[field.id] then + idx = i + break + end + end + return refs, idx +end + +inputs['output'] = function(spec, field, parent) + return function(gui) + IMGUI.PushID(field.id) + IMGUI.Text(field.name) + local value, changed = IMGUI.InputText("", spec[field.id] or field.id, 64) + if changed then + spec[field.id] = value + end + IMGUI.PopID() + end +end + +inputs['value'] = function(spec, field, parent) + + local inputInt = require 'debug.view.helpers.integer' + local inputStr = require 'debug.view.helpers.string' + + + local value = 0 + local refs, idx = _getRefs(spec, field, parent) + + local use_ref = true + + if field.match == 'integer' and type(spec[field.id]) == 'number' then + value = spec[field.id] + use_ref = false + elseif field.match == 'string' and type(spec[field.id]) == 'string' then + value = spec[field.id] + use_ref = false + end + + return function(gui) + IMGUI.PushID(field.id) + IMGUI.Text(field.name) + local changed + if use_ref then + idx, changed = IMGUI.Combo(field.name, idx, refs, #refs, 15) + if changed then + spec[field.id] = refs[idx] + end + else + if field.match == "integer" then + value, changed = inputInt(value, "", field.range) + elseif field.match == 'string' then + value, changed = inputStr(value, "") + end + if changed then + spec[field.id] = value + end + end + + if field.match == 'integer' or field.match == 'string' then + IMGUI.SameLine() + use_ref, changed = IMGUI.Checkbox("Ref##"..field.id, use_ref) + end + IMGUI.PopID() + + end +end + +return inputs diff --git a/game/debug/view/input/section.lua b/game/debug/view/input/section.lua new file mode 100644 index 00000000..e618e5ea --- /dev/null +++ b/game/debug/view/input/section.lua @@ -0,0 +1,54 @@ + +local IMGUI = require 'imgui' +local INPUT = require 'debug.view.input' +local DB = require 'database' + +local _inputs = {} + +function _inputs.section(spec, field, parent) + + local schema = field.schema + local backup = {} + local inside_schema = {} + + if type(schema) == "string" then + schema = require ('domain.'..schema).schema + end + + for i, subfield in ipairs(schema) do + local input_lambda + inside_schema[i] = { + id = ("%s:%s"):format(field.id, subfield.id), + input = function(section_spec) + input_lambda = input_lambda or INPUT(subfield.type, section_spec, + subfield, parent) + return input_lambda + end + } + end + + return function(gui) + local element = spec[field.id] + local enabled + enabled = IMGUI.Checkbox(field.name, not not element) + if not element and (enabled or field.required) then + element = backup + elseif element and not enabled and not field.required then + backup = element + element = false + end + if element then + IMGUI.Indent(20) + for i, subfield in ipairs(schema) do + IMGUI.PushID(inside_schema[i].id) + inside_schema[i].input(element)(gui) + IMGUI.PopID() + end + IMGUI.Unindent(20) + end + spec[field.id] = element + end +end + +return _inputs + diff --git a/game/debug/view/input/vector.lua b/game/debug/view/input/vector.lua new file mode 100644 index 00000000..6848b86c --- /dev/null +++ b/game/debug/view/input/vector.lua @@ -0,0 +1,62 @@ + +local IMGUI = require 'imgui' +local INPUT = require 'debug.view.input' +local DB = require 'database' + +local inputs = {} +local signature_mt = { + __index = function(gui, k) + return tonumber(k) + end +} + +function inputs.vector(spec, field) + + local vector = spec[field.id] or {} + spec[field.id] = vector + local selected = nil + local size = field.size + local range = field.range + local default = field.default or 0 + local signature = setmetatable(field.signature or + {'x','y','z','w'}, + signature_mt) + local subschemas = {} + for i=1, size do + vector[i] = vector[i] or default + local subfield = { + id = i, + name = signature[i], + } + subschemas[i] = subfield + end + + local function check_range(new) + if range then + new = math.max(range[1], + range[2] and math.min(range[2], new) or new) + end + return new + end + + return function(gui) + IMGUI.Text(("%s"):format(field.name)) + IMGUI.Columns(2, field.id, false) + for i, subfield in ipairs(subschemas) do + IMGUI.PushID(("%s#%d"):format(field.name, subfield.id)) + IMGUI.Text(("%s"):format(subfield.name)) + IMGUI.SameLine() + local new, changed = IMGUI.InputInt("", vector[i]) + IMGUI.PopID() + if changed then vector[i] = check_range(new) end + if i % 2 == 0 then IMGUI.NextColumn() end + if i % 4 == 0 then IMGUI.Spacing() end + end + IMGUI.Columns(1) + IMGUI.Spacing() + IMGUI.Spacing() + end +end + +return inputs + diff --git a/game/debug/view/list_picker.lua b/game/debug/view/list_picker.lua new file mode 100644 index 00000000..4da59050 --- /dev/null +++ b/game/debug/view/list_picker.lua @@ -0,0 +1,19 @@ + +local IMGUI = require 'imgui' + +return function(name, list, value) + + return "Choose a " .. name, 1, function(gui) + IMGUI.Text("Options:") + IMGUI.PushItemWidth(160) + local newvalue, changed = IMGUI.ListBox("", value(), list, #list, 15) + local confirmed + if changed then + confirmed = value(newvalue) + end + IMGUI.PopItemWidth() + if confirmed then return true end + end + +end + diff --git a/game/debug/view/name_input.lua b/game/debug/view/name_input.lua new file mode 100644 index 00000000..fed1e72a --- /dev/null +++ b/game/debug/view/name_input.lua @@ -0,0 +1,19 @@ + +local IMGUI = require 'imgui' + +return function(title, validator) + + local name = "" + + return "Name for " .. title, 1, function(gui) + local changed + name, changed = IMGUI.InputText("", name, 64) + if (IMGUI.Button("Confirm") or IMGUI.IsKeyPressed(12)) + and name ~= "" then + validator(name) + return true + end + end + +end + diff --git a/game/debug/view/specification_editor.lua b/game/debug/view/specification_editor.lua new file mode 100644 index 00000000..a61d460e --- /dev/null +++ b/game/debug/view/specification_editor.lua @@ -0,0 +1,73 @@ + +local IMGUI = require 'imgui' +local DB = require 'database' +local INPUT = require 'debug.view.input' + +return function(spec, group_name, title, delete, rename, parent) + + local inputs = {} + local keys = {} + for _,key in DB.schemaFor(group_name) do + table.insert(keys, key) + table.insert(inputs, INPUT(key.type, spec, key, parent)) + end + + return title .. " Editor", 2, function(gui) + + -- meta actions + local spec_meta = getmetatable(spec) + if spec_meta and spec_meta.is_leaf and IMGUI.Button("Save##1") then + DB.save(spec) + end + IMGUI.SameLine() + if rename and IMGUI.Button("Rename##1") then + gui:push('name_input', title, rename) + end + IMGUI.SameLine() + if IMGUI.Button("Delete##1") then + delete() + return true + end + IMGUI.Spacing() + IMGUI.Separator() + IMGUI.Spacing() + + -- editor inputs + for i,input in ipairs(inputs) do + local pop = 0 + local keyid = keys[i].id + local extended = rawget(spec, 'extends') + if extended and not rawget(spec, keyid) then + IMGUI.PushStyleColor("FrameBg", 0.8, 1, 0.9, 0.1) + pop = 1 + end + IMGUI.Spacing() + input(gui) + if pop > 0 then + IMGUI.PopStyleColor(pop) + elseif extended and keyid ~= 'extends' then + IMGUI.SameLine() + if IMGUI.Button("Reset##"..keyid) then + rawset(spec, keyid, nil) + end + end + end + IMGUI.Spacing() + IMGUI.Separator() + IMGUI.Spacing() + if spec_meta and spec_meta.is_leaf and IMGUI.Button("Save##2") then + DB.save(spec) + end + IMGUI.SameLine() + if rename and IMGUI.Button("Rename##2") then + gui:push('name_input', title, rename) + end + IMGUI.SameLine() + if IMGUI.Button("Delete##2") then + delete() + return true + end + end + +end + diff --git a/game/domain/ability.lua b/game/domain/ability.lua new file mode 100644 index 00000000..d750761c --- /dev/null +++ b/game/domain/ability.lua @@ -0,0 +1,132 @@ + +local FX = require 'lux.pack' 'domain.effects' +local OP = require 'lux.pack' 'domain.operators' +local IN = require 'lux.pack' 'domain.inputs' +local DB = require 'database' + +local _CMDTYPES = { + effect = FX, + inputs = IN, + operators = OP +} + +local function _unref(ref, values) + if type(ref) == 'string' then + local n = ref:match '=(.+)' + if n then + return values[n] + end + end + return ref +end + +local ABILITY = {} + +function ABILITY.inputsOf(ability) + return ipairs(ability.inputs) +end + +function ABILITY.input(input_name) + return IN[input_name] +end + +function ABILITY.validate(input_name, actor, input_fields, value) + return IN[input_name].isValid(actor, input_fields, value) +end + +local function _fields(cmd) + return DB.schemaFor(cmd.type .. 's/' .. cmd.name) +end + +local function _unrefFieldValues(cmd, values) + local unrefd_field_values = {} + for _,field in _fields(cmd) do + unrefd_field_values[field.id] = _unref(cmd[field.id], values) + end + return unrefd_field_values +end + +function ABILITY.checkInputs(ability, actor, inputvalues) + local values = {} + for _,cmd in ipairs(ability.inputs) do + if cmd.type == 'input' then + local unrefd_field_values = _unrefFieldValues(cmd, values) + local inputspec = IN[cmd.name] + if inputspec.isValid(actor, unrefd_field_values, + inputvalues[cmd.output]) then + if cmd.output then + values[cmd.output] = inputvalues[cmd.output] + end + else + return false + end + end + end + return true +end + +local _CMDLISTS = { 'inputs', 'effects' } + +function ABILITY.execute(ability, actor, inputvalues) + local values = {} + for _,cmdlist in ipairs(_CMDLISTS) do + for _,cmd in ipairs(ability[cmdlist]) do + local value + local type, name = cmd.type, cmd.name + if type == 'input' then + value = inputvalues[cmd.output] + else + local unrefd_field_values = _unrefFieldValues(cmd, values) + if type == 'operator' then + value = OP[name].process(actor, unrefd_field_values) + elseif type == 'effect' then + value = FX[name].process(actor, unrefd_field_values) + else + return error("Invalid command type") + end + end + if cmd.output then + values[cmd.output] = value + end + end + end +end + +function _NOPREVIEW() + return nil +end + +function ABILITY.preview(ability, actor, inputvalues) + local values = {} + local prevs = {} + for _,cmdlist in ipairs(_CMDLISTS) do + for _,cmd in ipairs(ability[cmdlist]) do + local prev, value + local type, name = cmd.type, cmd.name + local unrefd_field_values = _unrefFieldValues(cmd, values) + if type == 'input' then + value = IN[name].preview or function() + return inputvalues[cmd.output] + end + else + if type == 'operator' then + value = OP[name].preview + elseif type == 'effect' then + prev = FX[name].preview + else + return error("Invalid command type") + end + end + if cmd.output and value then + values[cmd.output] = value(actor, unrefd_field_values) + end + local text = (prev or _NOPREVIEW)(actor, unrefd_field_values) + if text then + table.insert(values, text) + end + end + end + return table.concat(values, ". ") .. "." +end + +return ABILITY diff --git a/game/domain/action.lua b/game/domain/action.lua new file mode 100644 index 00000000..065e4979 --- /dev/null +++ b/game/domain/action.lua @@ -0,0 +1,47 @@ + +local MANEUVERS = require 'lux.pack' 'domain.maneuver' +local ABILITY = require 'domain.ability' +local DB = require 'database' + +local ACTION = {} + +function ACTION.exists(action_name) + return not not MANEUVERS[action_name] +end + +function ACTION.exhaustionCost(action_name, actor, inputvalues) + return MANEUVERS[action_name].exhaustionCost(actor, inputvalues) +end + +function ACTION.card(action_name, actor, inputvalues) + return MANEUVERS[action_name].card(actor, inputvalues) +end + +function ACTION.pendingInput(action_name, actor, inputvalues) + local maneuver = MANEUVERS[action_name] + for _,input_spec in ipairs(maneuver.input_specs) do + if not inputvalues[input_spec.output] then + return input_spec + end + end + local activated_ability = maneuver.activatedAbility(actor, inputvalues) + if activated_ability then + for _,input_spec in ABILITY.inputsOf(activated_ability) do + if input_spec.type == 'input' and not inputvalues[input_spec.output] then + return input_spec + end + end + end +end + +function ACTION.execute(action_slot, actor, inputvalues) + local maneuver = MANEUVERS[action_slot] + if not maneuver or not maneuver.validate(actor, inputvalues) then + return false + end + maneuver.perform(actor, inputvalues) + return true +end + +return ACTION + diff --git a/game/domain/actor.lua b/game/domain/actor.lua new file mode 100644 index 00000000..3fe924d9 --- /dev/null +++ b/game/domain/actor.lua @@ -0,0 +1,598 @@ + +local GameElement = require 'domain.gameelement' +local DB = require 'database' +local Card = require 'domain.card' +local ACTION = require 'domain.action' +local ABILITY = require 'domain.ability' +local RANDOM = require 'common.random' +local DEFS = require 'domain.definitions' + +local PLACEMENTS = require 'domain.definitions.placements' +local PACK = require 'domain.pack' +local Visibility = require 'common.visibility' + +local math = require 'common.math' + +local Actor = Class{ + __includes = { GameElement } +} + +--[[ Setup methods ]]-- + +function Actor:init(spec_name) + + GameElement.init(self, 'actor', spec_name) + + self.behavior = require('domain.behaviors.' .. self:getSpec 'behavior') + + self.body_id = nil + self.cooldown = DEFS.ACTION.EXHAUSTION_UNIT + + self.hand = {} + self.focus = 0 + self.upgrades = { + COR = DEFS.ATTR.INITIAL_UPGRADE, + ARC = DEFS.ATTR.INITIAL_UPGRADE, + ANI = DEFS.ATTR.INITIAL_UPGRADE, + } + self.training = { + COR = 1, + ARC = 1, + ANI = 1 + } + self.attr_lv = { + COR = 0, + ARC = 0, + ANI = 0, + } + self.exp = 0 + self.playpoints = DEFS.MAX_PP + + self.fov = {} + self.fov_range = 4 + + self.buffer = {} + self.prizes = {} + + self:updateAttr('COR') + self:updateAttr('ARC') + self:updateAttr('ANI') +end + +function Actor:loadState(state) + self:setId(state.id or self.id) + self:setSubtype(self.spectype) + self.cooldown = state.cooldown or self.cooldown + self.body_id = state.body_id or self.body_id + self.exp = state.exp or self.exp + self.playpoints = state.playpoints or self.playpoints + self.upgrades = state.upgrades or self.upgrades + self.training = state.training or self.training + self.attr_lv = {} + self.prizes = state.prizes or self.prizes + self.focus = state.focus or self.focus + self.hand = state.hand and {} or self.hand + for _,card_state in ipairs(state.hand) do + local card = Card(card_state.specname) + card:loadState(card_state) + if not card.owner_id then + card:setOwner(self) + end + table.insert(self.hand, card) + end + self.buffer = state.buffer and {} or self.buffer + for i,card_state in ipairs(state.buffer) do + local card = DEFS.DONE + if card_state ~= card then + card = Card(state.buffer.specname) + card:loadState(card_state) + if not card.owner_id then + card:setOwner(self) + end + end + self.buffer[i] = card + end + self.fov = state.fov or self.fov + self:updateAttr('COR') + self:updateAttr('ARC') + self:updateAttr('ANI') +end + +function Actor:saveState() + local state = {} + state.id = self:getId() + state.specname = self.specname + state.cooldown = self.cooldown + state.body_id = self.body_id + state.exp = self.exp + state.playpoints = self.playpoints + state.upgrades = self.upgrades + state.training = self.training + state.prizes = self.prizes + state.focus = self.focus + state.hand = {} + for _,card in ipairs(self.hand) do + local card_state = card:saveState() + table.insert(state.hand, card_state) + end + state.buffer = {} + for i,card in ipairs(self.buffer) do + local card_state = DEFS.DONE + if card ~= card_state then + card_state = card:saveState() + end + state.buffer[i] = card_state + end + state.fov = self.fov + return state +end + +--[[ Spec methods ]]-- + +function Actor:getTitle() + return ("%s %s"):format(self:getSpec('name'), self:getBody():getSpec('name')) +end + +function Actor:isPlayer() + return self:getSpec('behavior') == 'player' +end + +function Actor:getBasicCollection() + return self:getSpec('collection') +end + +function Actor:getSignatureAbilityName() + return self:getSpec('signature') +end + +function Actor:getExp() + return self.exp +end + +function Actor:getCooldown() + return self.cooldown +end + +function Actor:modifyExpBy(n) + self.exp = math.max(0, self.exp + n) +end + +function Actor:getAptitude(which) + return self:getSpec(which:lower()) +end + +--[[ Attribute and co methods ]]-- + +function Actor:getAttrLevel(which) + return self.attr_lv[which] +end + +function Actor:getWithMod(which, value) + return self:getBody():getWithMod(which, value) +end + +function Actor:getAttribute(which) + return self:getWithMod(which, self:getAttrLevel(which)) +end + +function Actor:getAttrUpgrade(which) + return self.upgrades[which] +end + +function Actor:updateAttr(which) + self.attr_lv[which] = DEFS.APT.ATTR_LEVEL(self, which) +end + +function Actor:trainingDitribution() + local cor, arc, ani = self.training.COR, self.training.ARC, self.training.ANI + local total = 1.0 * (cor + arc + ani) + return cor/total, arc/total, ani/total +end + +function Actor:upgradeAttr(which, amount) + self.upgrades[which] = self.upgrades[which] + amount + self:updateAttr(which) +end + +function Actor:getCOR() + return self:getAttribute('COR') +end + +function Actor:upgradeCOR(n) + self:upgradeAttr('COR', n) +end + +function Actor:getARC() + return self:getAttribute('ARC') +end + +function Actor:upgradeARC(n) + self:upgradeAttr('ARC', n) +end + +function Actor:getANI() + return self:getAttribute('ANI') +end + +function Actor:upgradeANI(n) + self:upgradeAttr('ANI', n) +end + +function Actor:getSPD() + return self:getWithMod('SPD', DEFS.ATTR.BASE_SPD) +end + +--[[ Body methods ]]-- + +function Actor:setBody(body_id) + self.body_id = body_id +end + +function Actor:getBody() + return Util.findId(self.body_id) +end + +function Actor:getPos() + return self:getBody():getPos() +end + +function Actor:getSector() + return self:getBody():getSector() +end + +--[[ Action methods ]]-- + +function Actor:isWidget(slot) + return type(slot) == 'string' + and slot:match("^WIDGET/%d+$") +end + +function Actor:isCard(slot) + return type(slot) == 'string' + and slot:match("^CARD/%d+$") +end + +function Actor:getSignature() + return DB.loadSpec("action", self:getSignatureAbilityName()) +end + +function Actor:setAction(name, id) + self.actions[name] = id +end + +--[[ Card methods ]]-- + +function Actor:getHand() + return self.hand +end + +function Actor:getHandSize() + return #self.hand +end + +function Actor:isHandEmpty() + return #self.hand == 0 +end + +function Actor:isHandFull() + return #self.hand >= DEFS.HAND_LIMIT +end + +function Actor:getFocus() + return self.focus +end + +function Actor:isFocused() + return self.focus > 0 +end + +function Actor:getBufferSize() + for i,card in ipairs(self.buffer) do + if card == DEFS.DONE then + return i-1 + end + end +end + +function Actor:getBackBufferSize() + for i,card in ipairs(self.buffer) do + if card == DEFS.DONE then + return #self.buffer - i + end + end +end + +function Actor:getBackBufferCard(i) + return self.buffer[self:getBufferSize()+1+i] +end + +function Actor:removeBufferCard(i) + assert(self.buffer[i] and self.buffer[i] ~= DEFS.DONE, + "Invalid card index to remove") + return table.remove(self.buffer, i) +end + +function Actor:copyBackBuffer() + local copy = {} + for i = self:getBufferSize()+2, #self.buffer do + table.insert(copy, self.buffer[i]) + end + return copy +end + +function Actor:countCardInBuffer(specname) + local count = 0 + for i,card in ipairs(self.buffer) do + if card:getSpecName() == specname then + count = count + 1 + end + end + return count +end + +function Actor:isBufferEmpty() + return #self.buffer == 1 +end + +function Actor:copyBuffer() + local copy = {} + for i = 1, #self.buffer do + if self.buffer[i] ~= DEFS.DONE then + table.insert(copy, self.buffer[i]) + end + end + return copy +end + +--- Draw a card from actor's buffer +function Actor:drawCard() + if #self.hand >= DEFS.HAND_LIMIT then return end + -- Empty buffer + if self:isBufferEmpty() then return end + + local card = table.remove(self.buffer, 1) + if card == DEFS.DONE then + RANDOM.shuffle(self.buffer) + table.insert(self.buffer, DEFS.DONE) + card = table.remove(self.buffer, 1) + end + table.insert(self.hand, card) + Signal.emit("actor_draw", self, card) +end + +function Actor:getHandCard(index) + return index and self.hand[index] +end + +function Actor:removeHandCard(index) + assert(index >= 1 and index <= #self.hand) + return table.remove(self.hand, index) +end + +function Actor:addCardToBackbuffer(card) + table.insert(self.buffer, card) +end + +function Actor:consumeCard(card) + --FIXME: add card rarity modifier! + local cor, arc, ani = self:trainingDitribution() + local xp = DEFS.CONSUME_EXP + local round = math.round + self:upgradeCOR(round(cor*xp)) + self:upgradeARC(round(arc*xp)) + self:upgradeANI(round(ani*xp)) + self.exp = self.exp + xp + coroutine.yield('report', { + body = self:getBody(), + sfx = 'upgrade' + }) +end + +function Actor:addPrizePack(collection) + table.insert(self.prizes, collection) +end + +function Actor:getPrizePacks() + return self.prizes +end + +function Actor:getNextPrizePack() + return #self.prizes > 0 and table.remove(self.prizes, 1) +end + +function Actor:removePrizePack(index) + if self.prizes[index] then table.remove(self.prizes,index) end +end + +function Actor:getPrizePackCount() + return #self.prizes +end + +-- Visibility Methods -- + +function Actor:getVisibleBodies() + local seen = {} + local sector = self:getSector() + local w, h = sector:getDimensions() + + local range = self:getFovRange() + local pi, pj = self:getPos() + for i = pi-range, pi+range do + for j = pj-range, pj+range do + if sector:isInside(i, j) then + local body = sector:getBodyAt(i, j) + local fov = self:getFov(sector) + local visible = fov and fov[i] and fov[i][j] + if body and body ~= self:getBody() and visible and visible ~= 0 then + seen[body:getId()] = true + end + end + end + end + + return seen +end + +function Actor:canSee(target) + local sector = self:getSector() + local target_sector = target:getSector() + if sector ~= target_sector then + return false + end + local fov = self:getFov(sector) + local i, j = target:getPos() + local visible = fov[i][j] + return visible and visible > 0 +end + +function Actor:getHostileBodies() + local visible_bodies = self:getVisibleBodies() + local hostile_bodies = {} + local actor_body_faction = self:getBody():getFaction() + for body_id in pairs(visible_bodies) do + local body = Util.findId(body_id) + if body:getFaction() ~= actor_body_faction then + table.insert(hostile_bodies, body) + end + end + return hostile_bodies +end + +function Actor:purgeFov(sector) + local fov = self:getFov(sector) + if not fov then + self.fov[sector:getId()] = Visibility.purgeFov(sector) + end +end + +function Actor:resetFov(sector) + Visibility.resetFov(self:getFov(sector), sector) +end + +function Actor:updateFov(sector) + Visibility.updateFov(self, sector) +end + +function Actor:getFov(sector) + sector = sector or self:getBody():getSector() + return self.fov[sector:getId()] +end + +function Actor:getFovRange() + return math.max(0,self:getBody() + :applyStaticOperators("FOV", self.fov_range)) +end + +--[[ Turn methods ]]-- + +function Actor:grabDrops(tile) + local drops = tile.drops + local inputvalues = {} + local n = #drops + local i = 1 + while i <= n do + local dropname = drops[i] + local dropspec = DB.loadSpec('drop', dropname) + if ABILITY.checkInputs(dropspec.ability, self, inputvalues) then + table.remove(drops, i) + n = n-1 + coroutine.yield('report', { + sfx = 'get-item' + }) + ABILITY.execute(dropspec.ability, self, inputvalues) + else + i = i+1 + end + end +end + +function Actor:tick() + self.cooldown = math.max(0, self.cooldown - self:getSPD()) +end + +function Actor:resetFocus() + self.focus = DEFS.ACTION.FOCUS_DURATION +end + +function Actor:ready() + return self:getBody():isAlive() and self.cooldown <= 0 +end + +function Actor:playCard(card_index) + local card = table.remove(self.hand, card_index) + local attr = card:getRelatedAttr() + if attr ~= DEFS.CARD_ATTRIBUTES.NONE then + self.training[attr] = self.training[attr] + 1 + end + if not card:isOneTimeOnly() and not card:isWidget() then + self:addCardToBackbuffer(card) + end + return card +end + +local function _removeEquipment(body, slot) + local equipment = body:getEquipmentAt(slot) + local widget_index = equipment and body:findWidget(equipment) + if widget_index then + body:removeWidget(widget_index) + end +end + +function Actor:turn() + local body = self:getBody() + body:triggerWidgets(DEFS.TRIGGERS.ON_TURN) + self.focus = math.max(0, self.focus - 1) + if self.focus == 0 then + while not self:isHandEmpty() do + local card = self:removeHandCard(1) + self:addCardToBackbuffer(card) + end + body:triggerWidgets(DEFS.TRIGGERS.ON_FOCUS_END) + _removeEquipment(body, 'weapon') + _removeEquipment(body, 'offhand') + body:removeAllArmor() + end +end + +function Actor:makeAction() + local success = false + repeat + local action_slot, params + if self:getBody():hasStatusTag(DEFS.STATUS_TAGS.STUN) then + action_slot, params = DEFS.ACTION.IDLE, {} + else + action_slot, params = self:behavior() + end + if ACTION.exists(action_slot) then + success = ACTION.execute(action_slot, self, params) + end + until success + self:updateFov(self:getBody():getSector()) + return true +end + +function Actor:exhaust(n) + self.cooldown = self.cooldown + n +end + +function Actor:rewardPP(n) + self.playpoints = math.min(self.playpoints + n, DEFS.MAX_PP) +end + +function Actor:spendPP(n) + self.playpoints = math.max(self.playpoints - n, 0) +end + +function Actor:getPP() + return self.playpoints +end + +function Actor:getPowerLevel() + local lvl = 0 + for attr,value in pairs(self.upgrades) do + lvl = value + lvl + end + return lvl +end + +return Actor diff --git a/game/domain/behaviors/aggro.lua b/game/domain/behaviors/aggro.lua new file mode 100644 index 00000000..8fbad778 --- /dev/null +++ b/game/domain/behaviors/aggro.lua @@ -0,0 +1,67 @@ + +local MANEUVERS = require 'lux.pack' 'domain.maneuver' +local ACTIONDEFS = require 'domain.definitions.action' +local FindTarget = require 'domain.behaviors.helpers.findtarget' +local FindPath = require 'domain.behaviors.helpers.findpath' +local RandomWalk = require 'domain.behaviors.helpers.randomwalk' + +local _USE_SIGNATURE = ACTIONDEFS.USE_SIGNATURE +local _MOVE = ACTIONDEFS.MOVE + +return function (actor) + local sector = actor:getSector() + local behaviors = sector:getRoute().getBehaviors() + local ai = behaviors.getAI(actor) or behaviors.newAI(actor) + local i, j = actor:getPos() + + local target = ai.target + local target_pos = ai.target_pos + + -- if i don't have a target or it is dead, i'll look for one + if not target or target:isDead() then + target = FindTarget.getTarget(actor) + end + + if target then + -- if i have a target, can i see it? + if actor:canSee(target) then + -- if so, lock on to it + local k, l = target:getPos() + target_pos = { k, l } + else + -- if not, lose the target, but not its position + target = false + end + elseif target_pos then + -- if i don't have a target, but i have its last position... + -- ...am i in the target's position? + local k, l = unpack(target_pos) + if i == k and l == j then + -- if so, then i lost them completely + target_pos = false + end + end + + -- update AI state + ai.target = target + ai.target_pos = target_pos + + -- if i have a position targetted + if target_pos then + -- ...if i have a target, then try to attack! + local inputs = { pos = target_pos } + if target and MANEUVERS[_USE_SIGNATURE].validate(actor, inputs) then + return _USE_SIGNATURE, inputs + end + -- ...if i can't see or reach them, then at least chase it! + local next_step = FindPath.getNextStep({i, j}, target_pos, sector) + inputs.pos = next_step + if next_step and MANEUVERS[_MOVE].validate(actor, inputs) then + return _MOVE, inputs + end + end + + -- i don't have targets or any clue to my last target's position + return RandomWalk.execute(actor) +end + diff --git a/game/domain/behaviors/helpers/findpath.lua b/game/domain/behaviors/helpers/findpath.lua new file mode 100644 index 00000000..37577713 --- /dev/null +++ b/game/domain/behaviors/helpers/findpath.lua @@ -0,0 +1,93 @@ + +local DIR = require 'domain.definitions.dir' +local Heap = require 'common.heap' +local TILE = require 'common.tile' + + +-- CONSTANTS -- + +local _DEDICATION = 32 +local _LIMIT = 64 + + +-- LOCAL FUNCTIONS -- + +local function _hash(pos, width) + if not pos then return "none" end + local i, j = unpack(pos) + return i * width + j +end + +local function _heuristic(pos1, pos2) + local i1, j1 = unpack(pos1) + local i2, j2 = unpack(pos2) + return TILE.dist(i1, j1, i2, j2) +end + + +-- MODULE METHODS -- + +local FindPath = {} + +function FindPath.getNextStep(start, goal, sector) + local frontier = Heap:new() + local came_from = {} + local cost_so_far = {} + local path = {} + local found = false + local iterations = 0 + local swidth = sector:getDimensions() + + frontier:add(start, 0) + came_from[_hash(start, swidth)] = true + cost_so_far[_hash(start, swidth)] = 0 + + while not frontier:isEmpty() do + iterations = iterations + 1 + local current, rank = frontier:getNext() + + -- if you found your goal, quit loop + if _heuristic(goal, current) == 1 then + found = true + goal = current + break + end + + if iterations >= _LIMIT then break end + + -- look at neighbors + for _,dir in ipairs(DIR) do + local di, dj = unpack(DIR[dir]) + local i, j = unpack(current) + local ti, tj = unpack(goal) + local next_pos = { i+di, j+dj } + local distance = _heuristic(goal, next_pos) + + if sector:isValid(unpack(next_pos)) and distance < _DEDICATION then + local new_cost = cost_so_far[_hash(current, swidth)] + 1 + + -- is it a valid and not yet checked neighbor? + if not cost_so_far[_hash(next_pos, swidth)] + or new_cost < cost_so_far[_hash(next_pos, swidth)] then + local new_rank = new_cost + 2 * distance + cost_so_far[_hash(next_pos, swidth)] = new_cost + came_from[_hash(next_pos, swidth)] = current + frontier:add(next_pos, new_rank) + end + end + end + end + + local current = goal + if found then + while _hash(start, swidth) ~= _hash(current, swidth) do + table.insert(path, current) + current = came_from[_hash(current, swidth)] + end + return path[#path] + end + return false +end + +return FindPath + diff --git a/game/domain/behaviors/helpers/findtarget.lua b/game/domain/behaviors/helpers/findtarget.lua new file mode 100644 index 00000000..29b00885 --- /dev/null +++ b/game/domain/behaviors/helpers/findtarget.lua @@ -0,0 +1,24 @@ + +local TILE = require 'common.tile' + +local FindTarget = {} + +function FindTarget.getTarget(actor) + local target, dist + local visible_bodies = actor:getVisibleBodies() + for body_id in pairs(visible_bodies) do + local opponent = Util.findId(body_id) + if opponent and opponent:getFaction() ~= actor:getBody():getFaction() then + local k, l = opponent:getPos() + local d = TILE.dist(i, j, k, l) + if not target or not dist or d < dist then + target = opponent + dist = d + end + end + end + return target +end + +return FindTarget + diff --git a/game/domain/behaviors/helpers/randomwalk.lua b/game/domain/behaviors/helpers/randomwalk.lua new file mode 100644 index 00000000..11888659 --- /dev/null +++ b/game/domain/behaviors/helpers/randomwalk.lua @@ -0,0 +1,26 @@ + +local MANEUVERS = require 'lux.pack' 'domain.maneuver' +local ACTIONDEFS = require 'domain.definitions.action' +local RANDOM = require 'common.random' +local DIR = require 'domain.definitions.dir' + +local _IDLE = 0 + +local RandomWalk = {} + +function RandomWalk.execute(actor) + local i, j = actor:getPos() + local input = {} + repeat + local idx = RANDOM.generate(0, 8) + if idx == 0 then + return ACTIONDEFS.IDLE, {} + end + local di, dj = unpack(DIR[DIR[idx]]) + input.pos = {i+di, j+dj} + until MANEUVERS[ACTIONDEFS.MOVE].validate(actor, input) + return ACTIONDEFS.MOVE, input +end + +return RandomWalk + diff --git a/game/domain/behaviors/init.lua b/game/domain/behaviors/init.lua new file mode 100644 index 00000000..dac4102b --- /dev/null +++ b/game/domain/behaviors/init.lua @@ -0,0 +1,55 @@ + +local Behaviors = require 'lux.class' :new{} + +function Behaviors:instance(obj) + + local _ai = {} + + function obj.load(state) + local actor_ai_state = state.ai + local ai = {} + for actor_id, actor_ai_state in pairs(_ai) do + local actor_ai = {} + local target_id = actor_ai_state.target + actor_ai.target = target_id and Util.findId(target_id) + actor_ai.target_pos = actor_ai_state.target_pos + ai[actor_id] = actor_ai + end + _ai = ai + end + + function obj.save() + local state = {} + local ai_states = {} + for actor_id, actor_ai in pairs(_ai) do + local actor_ai_state = {} + local target = actor_ai.target + actor_ai_state.target = target and target:getId() + actor_ai_state.target_pos = actor_ai.target_pos + ai_states[actor_id] = actor_ai_state + end + state.ai = ai_states + return state + end + + function obj.newAI(actor) + local actor_ai = { + target = false, + target_pos = false, + } + _ai[actor:getId()] = actor_ai + return actor_ai + end + + function obj.getAI(actor) + return _ai[actor:getId()] + end + + function obj.removeAI(actor) + _ai[actor:getId()] = nil + end + +end + +return Behaviors + diff --git a/game/domain/behaviors/player.lua b/game/domain/behaviors/player.lua new file mode 100644 index 00000000..ea3db715 --- /dev/null +++ b/game/domain/behaviors/player.lua @@ -0,0 +1,6 @@ + +return function (actor) + -- This returns the action chosen by the user. See gamestate.play and + -- gamestate.user_turn + return select(2,coroutine.yield("userTurn", actor)) +end diff --git a/game/domain/behaviors/random_walk.lua b/game/domain/behaviors/random_walk.lua new file mode 100644 index 00000000..ff513263 --- /dev/null +++ b/game/domain/behaviors/random_walk.lua @@ -0,0 +1,20 @@ + +local DIR = require 'domain.definitions.dir' +local Action = require 'domain.action' + +return function (actor, sector) + local dir = DIR[DIR[love.math.random(4)]] + local i, j = actor:getPos() + local di, dj = unpack(dir) + i, j = i+di, j+dj + if sector:isValid(i, j) then + return 'MOVE', { pos = {i,j} } + else + local body = sector:getBodyAt(i,j) + if body then + return 'PRIMARY', { target = {i,j} } + else + return 'IDLE', {} + end + end +end diff --git a/game/domain/body.lua b/game/domain/body.lua new file mode 100644 index 00000000..669ccc51 --- /dev/null +++ b/game/domain/body.lua @@ -0,0 +1,438 @@ + +local RANDOM = require 'common.random' +local ABILITY = require 'domain.ability' +local TRIGGERS = require 'domain.definitions.triggers' +local PLACEMENTS = require 'domain.definitions.placements' +local APT = require 'domain.definitions.aptitude' +local ATTR = require 'domain.definitions.attribute' +local DB = require 'database' +local GameElement = require 'domain.gameelement' + +local Card = require 'domain.card' + +local _EMPTY = {} + +local Body = Class{ + __includes = { GameElement } +} + +--[[ Setup methods ]]-- + +function Body:init(specname) + + GameElement.init(self, 'body', specname) + + self.killer = false + self.damage = 0 + self.armor = 0 + self.widgets = {} + self.equipped = {} + for placement in ipairs(PLACEMENTS) do + self.equipped[placement] = false + end + self.sector_id = nil +end + +function Body:loadState(state) + self:setId(state.id or self.id) + self:setSubtype(self.spectype) + self.damage = state.damage or self.damage + self.armor = state.armor or self.armor + self.killer = state.killer or false + self.attr_lv = {} + self.sector_id = state.sector_id or self.sector_id + self.widgets = state.widgets and {} or self.widgets + for index, card_state in pairs(state.widgets) do + if card_state then + local card = Card(card_state.specname) + card:loadState(card_state) + self.widgets[index] = card + end + end + local equipped = self.equipped + if state.equipped then + equipped = {} + for _,placement in ipairs(PLACEMENTS) do + equipped[placement] = self.widgets[state.equipped[placement]] + end + end + self.equipped = equipped +end + +function Body:saveState() + local state = {} + state.id = self:getId() + state.specname = self.specname + state.damage = self.damage + state.armor = self.armor + state.killer = self.killer + state.sector_id = self.sector_id + local equipped = {} + for _,placement in ipairs(PLACEMENTS) do + local equip = self:getEquipmentAt(placement) + if equip then + local index = self:findWidget(equip) + equipped[placement] = index + end + end + state.equipped = equipped + state.widgets = {} + for index, card in pairs(self.widgets) do + if card then + local card_state = card:saveState() + state.widgets[index] = card_state + end + end + return state +end + +--[[ Spec-related methods ]]-- + +function Body:isSpec(specname) + if not specname then + return true + end + local actual_specname = self:getSpecName() + local ok = false + repeat + local parent = DB.loadSpec('body', actual_specname)['extends'] + if actual_specname == specname then + ok = true + break + end + actual_specname = parent + until not parent + return ok +end + +--[[ Sector-related methods ]]-- + +function Body:setSector(sector_id) + self.sector_id = sector_id +end + +function Body:getSector() + return Util.findId(self.sector_id) +end + +function Body:getActor() + return self:getSector():getActorFromBody(self) +end + +function Body:getPos() + return self:getSector():getBodyPos(self) +end + +--[[ Attribute getters ]]-- + +function Body:getWithMod(which, value) + return math.max(1, self:applyStaticOperators(which, value)) +end + +function Body:getAptitude(which) + return self:getSpec(which:lower()) +end + +function Body:getAttribute(which) + local actor = self:getActor() + local inf = ATTR.INFLUENCE[which] + local base = actor and (2 * actor:getAttribute(inf[1]) + + 1 * actor:getAttribute(inf[2])) / 3 + or 1 + return self:getWithMod(which, base) +end + +function Body:getDEF() + return self:getAttribute('DEF') +end + +function Body:getEFC() + return self:getAttribute('EFC') +end + +function Body:getVIT() + return self:getAttribute('VIT') +end + +function Body:getRES() + return self:getAptitude('RES') +end + +function Body:getFIN() + return self:getAptitude('FIN') +end + +function Body:getCON() + return self:getAptitude('CON') +end + +function Body:getArmorBonus() + return APT.ARMORBONUS(self:getDEF(), self:getRES()) +end + +function Body:getConsumption() + return APT.STAMINA(self:getEFC(), self:getFIN()) +end + +function Body:getMaxHP() + return APT.HP(self:getVIT(), self:getCON()) +end + +--[[ Appearance methods ]]-- + +function Body:getAppearance() + return self:getSpec('appearance') +end + +--[[ Faction methods ]]-- + +function Body:getFaction() + return self:getSpec('faction') +end + +--[[ Drops methods ]]-- + +function Body:getDrops() + return self:getSpec('drops') or {} +end + + +--[[ HP methods ]]-- + +function Body:getHP() + return self:getMaxHP() - self.damage +end + +function Body:isDead() + return self:getHP() <= 0 +end + +function Body:isAlive() + return not self:isDead() +end + +function Body:setHP(hp) + self.damage = math.max(0, math.min(self:getMaxHP() - hp, self:getMaxHP())) +end + +--[[ Widget methods ]]-- + +function Body:getEquipmentAt(place) + return place and self.equipped[place] +end + +function Body:equip(place, card) + if not place then return end + -- check if placement is being used + -- if it is, then remove card from that slot + if self:getEquipmentAt(place) then + local index + for i,widget in ipairs(self.widgets) do + if widget == self.equipped[place] then + index = i + break + end + end + local card = self:removeWidget(index) + end + -- equip new thing on index + self.equipped[place] = card +end + +function Body:unequip(place) + if not place then return end + self.equipped[place] = false +end + +function Body:hasWidgetAt(index) + return not not self.widgets[index] +end + +function Body:removeWidget(index) + local card = self.widgets[index] + local placement = card:getWidgetPlacement() + local owner = card:getOwner() + self:triggerOneWidget(index, TRIGGERS.ON_LEAVE) + self:unequip(placement) + table.remove(self.widgets, index) + if owner and not card:isOneTimeOnly() then + card:resetUsages() + owner:addCardToBackbuffer(card) + end + return card +end + +function Body:placeWidget(card) + local placement = card:getWidgetPlacement() + self:equip(placement, card) + table.insert(self.widgets, card) + card:resetTicks() + return self:triggerOneWidget(#self.widgets, TRIGGERS.ON_PLACE) +end + +function Body:getWidget(index) + return index and self.widgets[index] +end + +function Body:getWidgetNameAt(index) + local card = self.widgets[index] + if card then return card:getName() end +end + +function Body:findWidget(target) + for index, widget in self:eachWidget() do + if widget == target then + return index + end + end + return -1 +end + +function Body:spendWidget(index) + local card = self.widgets[index] + if card then + card:addUsages() + end +end + +function Body:eachWidget() + return ipairs(self.widgets) +end + +function Body:getWidgetCount() + return #self.widgets +end + +local floor = math.floor +local _OPS = { + ['+'] = function (a,b) return a+b end, + ['-'] = function (a,b) return a-b end, + ['*'] = function (a,b) return a*b end, + ['/'] = function (a,b) return a/b end, +} + +function Body:applyStaticOperators(attr, value) + for _,widget in ipairs(self.widgets) do + for _,operator in widget:getStaticOperators() do + if operator.attr == attr then + value = floor(_OPS[operator.op](value, operator.val)) + end + end + end + return value +end + +function Body:hasStatusTag(tag) + for _,widget in ipairs(self.widgets) do + if widget:hasStatusTag(tag) then + return true + end + end +end + +function Body:tick() + self:triggerWidgets(TRIGGERS.ON_TICK) + local spent = {} + for i,widget in ipairs(self.widgets) do + if widget:tick() then + self:triggerOneWidget(i, TRIGGERS.ON_CYCLE) + end + if widget:isSpent() then + table.insert(spent, i) + end + end + for n,i in ipairs(spent) do + local index = i - n + 1 + self:triggerOneWidget(index, TRIGGERS.ON_DONE) + self:removeWidget(index) + end +end + +function Body:triggerWidgets(trigger, params) + for index in self:eachWidget() do + self:triggerOneWidget(index, trigger, params) + end +end + +function Body:triggerOneWidget(index, trigger, inputs) + local widget = self:getWidget(index) + local owner = widget:getOwner() + inputs = inputs or {} + inputs.widget_self = widget + inputs.body_self = self + inputs.pos_self = {self:getPos()} + if widget:getWidgetTrigger() == trigger then + local condition = widget:getWidgetTriggerCondition() + if not condition + or ABILITY.checkInputs(condition, owner, inputs) then + self:spendWidget(index) + end + end + local triggered_ability = widget:getWidgetTriggeredAbility() or _EMPTY + if triggered_ability.trigger == trigger then + local ability = triggered_ability.ability + if ability then + if ABILITY.checkInputs(ability, owner, inputs) then + ABILITY.execute(ability, owner, inputs) + end + end + end +end + +--[[ Combat methods ]]-- + +function Body:getArmor() + return self.armor +end + +function Body:gainArmor(amount) + amount = math.max(0, amount) + self:getArmorBonus() + self.armor = self.armor + amount + return amount +end + +function Body:removeAllArmor() + self.armor = 0 +end + +function Body:takeDamageFrom(amount, source) + local blocked = math.min(amount, self.armor) + self.armor = self.armor - blocked -- should never go negative + local dmg = math.max(0, amount - blocked) + self.damage = math.min(self:getMaxHP(), self.damage + dmg) + self.killer = source:getId() + self:triggerWidgets(TRIGGERS.ON_HIT) + return dmg + -- print damage formula info (uncomment for debugging) + --[[ + local str = "%s is being attacked with %d damage!\n" + .. "> %s rolls %dd%d for %d defense points!\n" + .. "> %s takes %d in damage!\n" + local name = self:getSpec('name') + print(str:format(name, amount, name, self:getDEF(), self:getBaseDEF(), + defroll, name, dmg)) + --]]-- +end + +function Body:loseLifeFrom(amount, source) + self.damage = math.min(self:getMaxHP(), self.damage + amount) + self.killer = source:getId() + return amount +end + +function Body:exterminate() + self.damage = self:getMaxHP() +end + +function Body:heal(amount) + local olddamage = self.damage + self.damage = math.max(0, self.damage - amount) + return olddamage - self.damage +end + +function Body:getKiller() + return self.killer +end + +return Body + diff --git a/game/domain/builders/actor.lua b/game/domain/builders/actor.lua new file mode 100644 index 00000000..a902b26a --- /dev/null +++ b/game/domain/builders/actor.lua @@ -0,0 +1,56 @@ + +local DB = require 'database' +local CARD_BUILDER = require 'domain.builders.card' +local BUFFER_BUILDER = require 'domain.builders.buffers' +local DEFS = require 'domain.definitions' +local Actor = require 'domain.actor' + +local BUILDER = {} + +function BUILDER.buildState(idgenerator, background, body_state) + -- WARNING: Do not instantiate body before building the actor! + -- Build only bodystate instead. If not possible (body already exists), + -- you will have to reload its state after calling this function. If so, + -- do not lose the 'body_state' reference, as it is edited in-place here. + local traits_specs = DB.loadSpec('actor', background)['traits'] + local id = idgenerator.newID() + if traits_specs then + for _,trait_spec in ipairs(traits_specs) do + local trait = CARD_BUILDER.buildState(idgenerator, trait_spec.specname, + id) + table.insert(body_state.widgets, trait) + end + end + return { + id = id, + body_id = body_state.id, + specname = background, + cooldown = 10, + exp = 0, + playpoints = DEFS.MAX_PP, + upgrades = { + COR = 100, + ARC = 100, + ANI = 100, + }, + training = { + COR = 1, + ARC = 1, + ANI = 1 + }, + buffer = BUFFER_BUILDER.build(idgenerator, background), + hand_limit = 5, + hand = {}, + prizes = {}, + } +end + +function BUILDER.buildElement(idgenerator, background, body_state) + local state = BUILDER.buildState(idgenerator, background, body_state) + local actor = Actor(background) + actor:loadState(state) + return actor +end + +return BUILDER + diff --git a/game/domain/builders/body.lua b/game/domain/builders/body.lua new file mode 100644 index 00000000..2d45493d --- /dev/null +++ b/game/domain/builders/body.lua @@ -0,0 +1,32 @@ + +local Body = require 'domain.body' + +local BUILDER = {} + +function BUILDER.buildState(idgenerator, species, i, j) + return { + id = idgenerator.newID(), + specname = species, + damage = 0, + i = i, + j = j, + equipped = { + weapon = false, + offhand = false, + suit = false, + tool = false, + accessory = false, + }, + widgets = {}, + } +end + +function BUILDER.buildElement(idgenerator, species, i, j) + local state = BUILDER.buildState(idgenerator, species, i, j) + local body = Body(species) + body:loadState(state) + return body +end + +return BUILDER + diff --git a/game/domain/builders/buffers.lua b/game/domain/builders/buffers.lua new file mode 100644 index 00000000..63438c43 --- /dev/null +++ b/game/domain/builders/buffers.lua @@ -0,0 +1,23 @@ + +local DB = require 'database' +local RANDOM = require 'common.random' +local DEFS = require 'domain.definitions' + +local CARD_BUILDER = require 'domain.builders.card' + +local BUILDER = {} + +function BUILDER.build(idgenerator, background) + local buffer = {} + for _,cardinfo in ipairs(DB.loadSpec('actor', background).initial_buffer) do + for i = 1, cardinfo.amount do + table.insert(buffer, CARD_BUILDER.buildState(idgenerator, cardinfo.card)) + end + end + RANDOM.shuffle(buffer) + table.insert(buffer, DEFS.DONE) + return buffer +end + +return BUILDER + diff --git a/game/domain/builders/card.lua b/game/domain/builders/card.lua new file mode 100644 index 00000000..947256b2 --- /dev/null +++ b/game/domain/builders/card.lua @@ -0,0 +1,23 @@ + +local Card = require 'domain.card' + +local BUILDER = {} + +function BUILDER.buildState(idgenerator, specname, owner_id) + return { + id = idgenerator.newID(), + owner_id = owner_id, + specname = specname, + usages = 0, + } +end + +function BUILDER.buildElement(idgenerator, specname, owner_id) + local state = BUILDER.buildState(idgenerator, specname, owner_id) + local card = Card(specname) + card:loadState(state) + return card +end + +return BUILDER + diff --git a/game/domain/builders/route.lua b/game/domain/builders/route.lua new file mode 100644 index 00000000..ef6ad7af --- /dev/null +++ b/game/domain/builders/route.lua @@ -0,0 +1,71 @@ + +local RUNFLAGS = require 'infra.runflags' +local RANDOM = require 'common.random' +local IDGenerator = require 'common.idgenerator' +local SECTORS_BUILDER = require 'domain.builders.sectors' + +local BUILDER = {} + +local _ROUTE_NAMES = { + "Banana", + "Kiwi", + "Omar", + "Orange", + "Front Door", + "Longsword", + "Jennifer", + "Evil Dragon", + "Jacekt", + "Pants", + "Green", + "Boots", + "Pudding", + "Cake", + "Fox", + "OwO", + "Nope", + "Hector", + "Black", + "Glass", + "January", + "Hallow", + "Hollow", + "Skeleton", + "Ghost", + "Sord", + "Juniper", + "Corgi", + "Dog", + "Cat" +} + +function BUILDER.build(route_id, player_info) + local idgenerator = IDGenerator() + local data = {} + if RUNFLAGS.SAFESEED then + -- if we have to debug rng corner cases that are not from route's rng + RANDOM.setSafeSeed(RUNFLAGS.SAFESEED) + end + RANDOM.setSeed(RUNFLAGS.SEED or RANDOM.generateSeed()) + player_info.name = _ROUTE_NAMES[RANDOM.safeGenerate(#_ROUTE_NAMES)] + data.version = VERSION + data.id = route_id + data.rng_seed = RANDOM.getSeed() + data.rng_state = RANDOM.getState() + local sectors, first_sector = SECTORS_BUILDER.build(idgenerator, player_info) + assert(first_sector, "No initial sector.") + data.sectors = sectors + data.next_id = idgenerator.getNextID() + data.current_sector_id = first_sector.id + data.player_name = player_info.name + data.player_id = first_sector.actors[1].id + data.behaviors = { ai = {} } + printf("Generated %s...", route_id) + printf("GLOBAL RNG seed: %d", RANDOM.getSafeSeed()) + printf("ROUTE RNG seed: %d", data.rng_seed) + printf("ROUTE RNG state: %d", data.rng_state) + return data +end + +return BUILDER + diff --git a/game/domain/builders/sectors.lua b/game/domain/builders/sectors.lua new file mode 100644 index 00000000..710730b5 --- /dev/null +++ b/game/domain/builders/sectors.lua @@ -0,0 +1,59 @@ + +local DB = require 'database' +local Graph = require 'common.graph' +local ROUTEMAPDEFS = require 'domain.definitions.routemap' +local BODY_BUILDER = require 'domain.builders.body' +local ACTOR_BUILDER = require 'domain.builders.actor' + +local BUILDER = {} + +function BUILDER.build(idgenerator, player_data) + local route_map = Graph:create(idgenerator) + local sectors = {} + + -- create nodes + for i, node_info in ipairs(ROUTEMAPDEFS.initial_nodes) do + local id = route_map:addNode(unpack(node_info)) + sectors[i] = route_map:getNode(id) + end + + -- connect nodes + for i, connection_info in ipairs(ROUTEMAPDEFS.initial_connections) do + local idx, jdx = unpack(connection_info) + local id1 = sectors[idx].id + local id2 = sectors[jdx].id + route_map:connect(id1, id2) + end + + -- generate player + local species = player_data.species + local background = player_data.background + local pbody = BODY_BUILDER.buildState(idgenerator, species, 16, 12) + local pactor = ACTOR_BUILDER.buildState(idgenerator, background, pbody) + + -- generate first sector + local tiledata = DB.loadSetting('init_tiledata') + local first_sector + for _,sector in ipairs(sectors) do + if sector.specname == "initial" then + first_sector = sector + break + end + end + + assert(first_sector, "No first sector???") + + first_sector.tiles = tiledata.tiles + first_sector.h = #tiledata.tiles + first_sector.w = #tiledata.tiles[1] + first_sector.bodies = { pbody } + first_sector.actors = { pactor } + first_sector.generated = true + local _,exit = next(first_sector.exits) + exit.pos = tiledata.exit + + return sectors, first_sector +end + +return BUILDER + diff --git a/game/domain/card.lua b/game/domain/card.lua new file mode 100644 index 00000000..3febd244 --- /dev/null +++ b/game/domain/card.lua @@ -0,0 +1,212 @@ + +local ABILITY = require 'domain.ability' +local ACTIONSDEFS = require 'domain.definitions.action' +local GameElement = require 'domain.gameelement' + +local Card = Class{ + __includes = { GameElement } +} + +function Card:init(specname) + + GameElement.init(self, 'card', specname) + self.usages = 0 + self.owner_id = nil + self.ticks = 0 + +end + +function Card:loadState(state) + self:setId(state.id or self.id) + self:setSubtype(self.spectype) + self.specname = state.specname or self.specname + self.usages = state.usages or self.usages + self.owner_id = state.owner_id or self.owner_id + self.ticks = state.ticks or self.ticks +end + +function Card:saveState() + local state = {} + state.id = self:getId() + state.specname = self.specname + state.usages = self.usages + state.owner_id = self.owner_id + state.ticks = self.ticks + return state +end + +function Card:getName() + return self:getSpec('name') +end + +function Card:getDescription() + return self:getSpec('desc') +end + +function Card:getIconTexture() + return self:getSpec('icon') +end + +function Card:getPPReward() + return self:getSpec('pp') or 0 +end + +function Card:getRelatedAttr() + return self:getSpec('attr') +end + +function Card:getOwner() + return Util.findId(self.owner_id) +end + +function Card:setOwner(owner) + self.owner_id = owner.id +end + +function Card:isOneTimeOnly() + return self:getSpec('one_time') +end + +function Card:isArt() + return not not self:getSpec('art') +end + +function Card:isWidget() + return not not self:getSpec('widget') +end + +function Card:getType() + if self:isArt() then return 'art' + elseif self:isWidget() then return 'widget' + end +end + +function Card:getArtAbility() + return self:getSpec('art').art_ability +end + +function Card:getArtCost() + return self:getSpec('art').cost +end + +function Card:getWidgetTrigger() + return self:getSpec('widget')['trigger'] +end + +function Card:getWidgetTriggerCondition() + return self:getSpec('widget')['trigger-condition'] +end + +function Card:getStaticOperators() + return ipairs(self:getSpec('widget')['operators'] or {}) +end + +function Card:hasStatusTag(tag) + local status_list = self:getSpec('widget')['status-tags'] or {} + for _,status in ipairs(status_list) do + if status['tag'] == tag then + return true + end + end + return false +end + +function Card:getWidgetActivation() + return self:getSpec('widget')['activation'] +end + +function Card:getWidgetTriggeredAbility() + return self:getSpec('widget')['auto_activation'] +end + +function Card:getWidgetAbility() + local activation = self:getWidgetActivation() + return activation and activation.ability +end + +function Card:getWidgetActivationCost() + local activation = self:getWidgetActivation() + return activation and activation.cost +end + +function Card:getWidgetPlacement() + return self:getSpec('widget').placement +end + +function Card:getWidgetCharges() + return self:getSpec('widget').charges +end + +function Card:isWidgetPermanent() + return self:getWidgetCharges() == 0 +end + +function Card:resetUsages() + self.usages = 0 +end + +function Card:addUsages(n) + self.usages = self.usages + (n or 1) +end + +function Card:getUsages() + return self.usages +end + +function Card:isSpent() + local max = self:getWidgetCharges() + return max > 0 and self:getUsages() >= max +end + +function Card:resetTicks() + self.ticks = 0 +end + +function Card:tick() + self.ticks = self.ticks + 1 + if self.ticks >= ACTIONSDEFS.CYCLE_UNIT then + self.ticks = 0 + return true + end + return false +end + +function Card:getEffect() + local effect + local inputs = { self = self:getOwner() } + if self:isArt() then + effect = ("Art [%d exhaustion]\n\n"):format(self:getArtCost()) + .. ABILITY.preview(self:getArtAbility(), self:getOwner(), inputs) + elseif self:isWidget() then + effect = "Widget" + local place = self:getWidgetPlacement() if place then + effect = effect .. " [" .. place .. "]" + end + local charges = self:getWidgetCharges() if charges > 0 then + local trigger = self:getWidgetTrigger() + effect = effect .. (" [%d/%s charges]"):format(charges, trigger) + end + local activation = self:getWidgetActivation() if activation then + local ability, cost = activation.ability, activation.cost + effect = effect .. ("\n\nActivate [%d exhaustion]: "):format(cost) + .. ABILITY.preview(ability, self:getOwner(), inputs) + end + local auto = self:getWidgetTriggeredAbility() if auto then + local ability, trigger = auto.ability, auto.trigger + effect = effect .. ("\n\nTrigger [%s]: "):format(trigger) + .. ABILITY.preview(ability, self:getOwner(), inputs) + end + local ops, n = {}, 0 + for _,op in self:getStaticOperators() do + n = n + 1 + ops[n] = ("%s %s%d"):format(op.attr, op.op, op.val) + end + if n > 0 then + effect = effect .. "\n\n" .. table.concat(ops, ", ") .. "." + end + end + return effect +end + +return Card + diff --git a/game/domain/cardset.lua b/game/domain/cardset.lua new file mode 100644 index 00000000..866eb7a1 --- /dev/null +++ b/game/domain/cardset.lua @@ -0,0 +1,41 @@ + +local DB = require 'database' +local RANDOM = require 'common.random' + +local _EMPTY = {} +local CARDSET = {} + +local function _getParent(setname) + return (DB.loadSpec('cardset', setname) or _EMPTY).parent +end + +local function _belongs(setname, from) + if not setname then return false end + if setname == from then return true end + local parentset = _getParent(setname) + return _belongs(parentset, from) +end + +local function _getCardsFrom(setname) + local cardlist = {} + local n = 0 + for cardname in DB.listDomainItems("card") do + if _belongs(DB.loadSpec('card', cardname).set, setname) then + n = n + 1 + cardlist[n] = cardname + end + end + return cardlist +end + +function CARDSET.getRandomCardFrom(setname) + local cardlist = _getCardsFrom(setname) + local parent = _getParent(setname) + if parent and RANDOM.generate(1, 10) == 1 then + return CARDSET.getRandomCardFrom(parent) + end + return cardlist[RANDOM.generate(1, #cardlist)] +end + +return CARDSET + diff --git a/game/domain/definitions/action.lua b/game/domain/definitions/action.lua new file mode 100644 index 00000000..5087bb13 --- /dev/null +++ b/game/domain/definitions/action.lua @@ -0,0 +1,26 @@ + +local ACTION = {} + +ACTION.IDLE = 'idle' +ACTION.MOVE = 'move' +ACTION.INTERACT = 'interact' +ACTION.USE_SIGNATURE = 'use_signature' +ACTION.DRAW_NEW_HAND = 'draw_new_hand' +ACTION.PLAY_CARD = 'play_card' +ACTION.ACTIVATE_WIDGET = 'activate_widget' +ACTION.STASH_CARD = 'stash_card' +ACTION.CONSUME_CARDS = 'consume_cards_from_buffer' +ACTION.RECEIVE_PACK = 'receive_pack' + +ACTION.EXHAUSTION_UNIT = 10 +ACTION.IDLE_COST = 1 * ACTION.EXHAUSTION_UNIT +ACTION.PLAY_WIDGET_COST = 1 * ACTION.EXHAUSTION_UNIT +ACTION.PLAY_UPGRADE_COST = 1 * ACTION.EXHAUSTION_UNIT +ACTION.MOVE_COST = 2 * ACTION.EXHAUSTION_UNIT + +ACTION.CYCLE_UNIT = 5 + +ACTION.NEW_HAND_COST = 2*10 +ACTION.FOCUS_DURATION = 8 + +return ACTION diff --git a/game/domain/definitions/aptitude.lua b/game/domain/definitions/aptitude.lua new file mode 100644 index 00000000..e30cc7aa --- /dev/null +++ b/game/domain/definitions/aptitude.lua @@ -0,0 +1,46 @@ + +local APT = {} + +--- Calculate required upgrade points for a certain level given an aptitude. +-- For the resulting tables run +-- ```bash +-- $ make FLAGS=--test=aptitude +-- ``` +function APT.REQUIRED_ATTR_UPGRADE(apt, lv) + return math.ceil((15 - 3*apt) ^ (1 + lv/10)) +end + +function APT.CUMULATIVE_REQUIRED_ATTR_UPGRADE(apt, lv) + local required = 0 + for i = 1, lv do + required = required + APT.REQUIRED_ATTR_UPGRADE(apt, i) + end + return required +end + +function APT.ATTR_LEVEL(owner, which) + local lv = 0 + local required = 0 + repeat + lv = lv + 1 + required = required + + APT.REQUIRED_ATTR_UPGRADE(owner:getAptitude(which), lv) + until owner.upgrades[which] < required + return lv-1 +end + +function APT.HP(vit, con) + return math.floor(20 + (4+con)*vit*vit - (7+con)*vit) +end + +function APT.STAMINA(efc, fin) + local min, max = 7 - 2.5*fin, 25 - fin + local food = max - (max-min)*efc/12 + return math.floor(food) +end + +function APT.ARMORBONUS(def, res) + return math.floor(0.5 * def * (6 + res) / 4) +end + +return APT diff --git a/game/domain/definitions/attribute.lua b/game/domain/definitions/attribute.lua new file mode 100644 index 00000000..0528246f --- /dev/null +++ b/game/domain/definitions/attribute.lua @@ -0,0 +1,20 @@ + +local round = require 'common.math' .round + +local ATTR = {} + +ATTR.BASE_SPD = 3 +ATTR.INITIAL_UPGRADE = 100 + +ATTR.INFLUENCE = { + DEF = {'COR', 'ARC'}, + EFC = {'ARC', 'ANI'}, + VIT = {'ANI', 'COR'} +} + +function ATTR.EFFECTIVE_POWER(base, mod) + return round(base * mod / 2) +end + +return ATTR + diff --git a/game/domain/definitions/colors.lua b/game/domain/definitions/colors.lua new file mode 100644 index 00000000..a916b2fa --- /dev/null +++ b/game/domain/definitions/colors.lua @@ -0,0 +1,35 @@ + +local Color = require 'common.color' + +local COLORS = {} + +COLORS.NEUTRAL = Color.fromInt {0xff, 0xff, 0xff, 0xff} +COLORS.TRANSP = Color.fromInt {0xff, 0xff, 0xff, 0x00} +COLORS.SEMITRANSP = Color.fromInt {0xff, 0xff, 0xff, 0x80} +COLORS.BLACK = Color.fromInt {0x00, 0x00, 0x00, 0xff} +COLORS.VOID = Color.fromInt {0, 0, 0, 0} +COLORS.HALF_VISIBLE = Color.fromInt {0x80, 0x80, 0x80, 0xff} +COLORS.DARK = Color.fromInt {0x1f, 0x1f, 0x1f, 0xff} +COLORS.DARKER = Color.fromInt {12, 12, 12, 255} +COLORS.BACKGROUND = Color.fromInt {50, 80, 80, 255} +COLORS.EXIT = Color.fromInt {0x77, 0xba, 0x99, 0xff} +COLORS.FLOOR1 = Color.fromInt {25, 73, 95, 0xff} +COLORS.FLOOR2 = Color.fromInt {25, 73, 95 + 20, 0xff} + +COLORS.EMPTY = Color:new {0.2, .15, 0.05, 1} +COLORS.WARNING = Color:new {1, 0.8, 0.2, 1} +COLORS.VALID = Color:new {0, 0.7, 1, 1} +COLORS.NOTIFICATION = Color.fromInt {0xD9, 0x53, 0x4F, 0xff} +COLORS.SUCCESS = Color.fromInt {0x33, 0xAA, 0x3F, 0xff} + +COLORS.STASH = Color.fromInt {0x41, 0x6e, 0x8e, 0xff} +COLORS.PLAY = Color.fromInt {0xa3, 0x78, 0x71, 0xff} + +COLORS.COR = Color.fromInt {0xbe, 0x76, 0x3a, 0xff} +COLORS.ARC = Color.fromInt {0x6e, 0x60, 0xaa, 0xff} +COLORS.ANI = Color.fromInt {0x77, 0xb9, 0x55, 0xff} +COLORS.NONE = Color.fromInt {0x77, 0x77, 0x77, 0xff} +COLORS.PP = Color.fromInt {153, 51, 153, 255} + +return COLORS + diff --git a/game/domain/definitions/dir.lua b/game/domain/definitions/dir.lua new file mode 100644 index 00000000..12b40aa1 --- /dev/null +++ b/game/domain/definitions/dir.lua @@ -0,0 +1,13 @@ + +return { + 'UP', 'RIGHT', 'DOWN', 'LEFT', 'UPLEFT', 'UPRIGHT', 'DOWNRIGHT', 'DOWNLEFT', + UP = {-1,0}, + RIGHT = {0,1}, + DOWN = {1,0}, + LEFT = {0,-1}, + UPLEFT = {-1,-1}, + UPRIGHT = {-1,1}, + DOWNRIGHT = {1,1}, + DOWNLEFT = {1,-1}, +} + diff --git a/game/domain/definitions/init.lua b/game/domain/definitions/init.lua new file mode 100644 index 00000000..15180832 --- /dev/null +++ b/game/domain/definitions/init.lua @@ -0,0 +1,43 @@ + +local DEFS = {} + +DEFS.NULL_METHOD = function() end +DEFS.DONE = "__DONE_VALUE__" +DEFS.DELETE = "__DELETE_VALUE__" +DEFS.CONSUME_EXP = 10 +DEFS.MAX_PP = 100 +DEFS.CARD_TYPES = { 'ART', 'WIDGET' } +DEFS.PRIMARY_ATTRIBUTES = { + "COR", "ARC", "ANI", + COR = "COR", + ARC = "ARC", + ANI = "ANI", +} +DEFS.ALL_ATTRIBUTES = { + "COR", "ARC", "ANI", + "SPD", "FOV", + "DEF", "EFC", "VIT", + "RES", "FIN", "CON" +} +DEFS.BODY_ATTRIBUTES = { + "DEF", "EFC", "VIT", + "RES", "FIN", "CON" +} +DEFS.CARD_ATTRIBUTES = { + "COR", "ARC", "ANI", "NONE", + COR = "COR", + ARC = "ARC", + ANI = "ANI", + NONE = "NONE", +} + +DEFS.HAND_LIMIT = 5 + +DEFS.ACTION = require 'domain.definitions.action' +DEFS.TRIGGERS = require 'domain.definitions.triggers' +DEFS.STASH_CARDS = require 'domain.definitions.stash_cards' +DEFS.APT = require 'domain.definitions.aptitude' +DEFS.ATTR = require 'domain.definitions.attribute' +DEFS.STATUS_TAGS = require 'domain.definitions.status_tags' + +return DEFS diff --git a/game/domain/definitions/placements.lua b/game/domain/definitions/placements.lua new file mode 100644 index 00000000..b5a8be65 --- /dev/null +++ b/game/domain/definitions/placements.lua @@ -0,0 +1,14 @@ + +return { + "weapon", + "offhand", + "suit", + "tool", + "accessory", + weapon = "Wpn", + offhand = "Off", + suit = "Suit", + tool = "Tool", + accessory = "Acc", +} + diff --git a/game/domain/definitions/regions.lua b/game/domain/definitions/regions.lua new file mode 100644 index 00000000..96a9a61a --- /dev/null +++ b/game/domain/definitions/regions.lua @@ -0,0 +1,50 @@ + +local REGIONDEFS = {} + + +-- Node Zones + +REGIONDEFS.ZONES = { + A = 'ruins', + B = 'zone1', + C = 'zone2', + D = 'zone3', + E = 'zone4', + F = 'seed1', + G = 'seed2', + H = 'seed3', + I = 'seed4', + J = 'seed5', + K = 'seed6', + L = 'seed7', + M = 'seed8', +} + + +-- Node Symbols + +REGIONDEFS.SYMBOLS = { + O = 'sector01', + V = 'sector02', + v = 'sector03', + e = 'sector_final', +} + + +-- Subgraph Rules + +REGIONDEFS.RULES = { + { + pattern = { + v = {'(.):O', '(.):O'}, + e = { {1,2} }, + }, + result = { + v = {'%1:V', '%1:V', '%2:v'}, + e = { {1,2}, {1,3}, {2,3}, }, + }, + }, +} + +return REGIONDEFS + diff --git a/game/domain/definitions/routemap.lua b/game/domain/definitions/routemap.lua new file mode 100644 index 00000000..1fa0dea2 --- /dev/null +++ b/game/domain/definitions/routemap.lua @@ -0,0 +1,104 @@ + +local REGIONDEFS = require 'domain.definitions.regions' +local ZONES = REGIONDEFS.ZONES +local SYMBOLS = REGIONDEFS.SYMBOLS + +local ROUTEMAP = {} + +ROUTEMAP.initial_nodes = { + { ZONES.A, "initial" }, + { ZONES.A, SYMBOLS.O }, + { ZONES.A, SYMBOLS.O }, + { ZONES.A, SYMBOLS.O }, + { ZONES.A, SYMBOLS.O }, + { ZONES.A, SYMBOLS.O }, + { ZONES.B, SYMBOLS.V }, + { ZONES.B, SYMBOLS.V }, + { ZONES.B, SYMBOLS.V }, + { ZONES.B, SYMBOLS.V }, + { ZONES.B, SYMBOLS.V }, + { ZONES.C, SYMBOLS.V }, + { ZONES.C, SYMBOLS.V }, + { ZONES.C, SYMBOLS.V }, + { ZONES.C, SYMBOLS.V }, + { ZONES.C, SYMBOLS.V }, + { ZONES.D, SYMBOLS.v }, + { ZONES.D, SYMBOLS.v }, + { ZONES.D, SYMBOLS.v }, + { ZONES.D, SYMBOLS.v }, + { ZONES.D, SYMBOLS.v }, + { ZONES.E, SYMBOLS.v }, + { ZONES.E, SYMBOLS.v }, + { ZONES.E, SYMBOLS.v }, + { ZONES.E, SYMBOLS.v }, + { ZONES.E, SYMBOLS.v }, + { ZONES.F, SYMBOLS.e }, + { ZONES.G, SYMBOLS.e }, + { ZONES.H, SYMBOLS.e }, + { ZONES.I, SYMBOLS.e }, + { ZONES.J, SYMBOLS.e }, + { ZONES.K, SYMBOLS.e }, + { ZONES.L, SYMBOLS.e }, + { ZONES.M, SYMBOLS.e }, +} + +ROUTEMAP.initial_connections = { + {1, 2}, + {2, 3}, + {2, 4}, + {2, 5}, + {2, 6}, + {3, 4}, + {4, 5}, + {5, 6}, + {6, 3}, + -- connecting ruins to zones + {3, 7}, + {4, 12}, + {5, 17}, + {6, 22}, + -- building zone 1 + {7, 8}, + {7, 9}, + {8, 10}, + {9, 11}, + {10, 11}, + -- connect zone 1 to zone 2 + {9, 13}, + -- building zone 2 + {12, 13}, + {12, 14}, + {13, 15}, + {14, 16}, + {15, 16}, + -- connect zone 2 to zone 3 + {14, 18}, + -- building zone 3 + {17, 18}, + {17, 19}, + {18, 20}, + {19, 21}, + {20, 21}, + -- connect zone 3 to zone 4 + {19, 23}, + -- building zone 4 + {22, 23}, + {22, 24}, + {23, 25}, + {24, 26}, + {25, 26}, + -- connect zone 4 to zone 1 + {24, 8}, + -- connect zones to seed regions + {10, 27}, + {11, 28}, + {15, 29}, + {16, 30}, + {20, 31}, + {21, 32}, + {25, 33}, + {26, 34}, +} + +return ROUTEMAP + diff --git a/game/domain/definitions/schematics.lua b/game/domain/definitions/schematics.lua new file mode 100644 index 00000000..fe12fa06 --- /dev/null +++ b/game/domain/definitions/schematics.lua @@ -0,0 +1,9 @@ + +return { + NAUGHT = " ", + FLOOR = ".", + WALL = "#", + EXIT = ">", + ALTAR = "&", +} + diff --git a/game/domain/definitions/stash_cards.lua b/game/domain/definitions/stash_cards.lua new file mode 100644 index 00000000..85fb5d6c --- /dev/null +++ b/game/domain/definitions/stash_cards.lua @@ -0,0 +1,8 @@ + +return { + 'cor+', 'arc+', 'ani+', + COR = 'cor+', + ARC = 'arc+', + ANI = 'ani+' +} + diff --git a/game/domain/definitions/status_tags.lua b/game/domain/definitions/status_tags.lua new file mode 100644 index 00000000..a98892af --- /dev/null +++ b/game/domain/definitions/status_tags.lua @@ -0,0 +1,6 @@ + +return { + 'stun', + STUN = 'stun' +} + diff --git a/game/domain/definitions/triggers.lua b/game/domain/definitions/triggers.lua new file mode 100644 index 00000000..dadd39b5 --- /dev/null +++ b/game/domain/definitions/triggers.lua @@ -0,0 +1,29 @@ + +return { + "on_tick", + "on_cycle", + "on_turn", + "on_use", + "on_any_use", + "on_hit", + "on_done", + "on_place", + "on_play", + "on_leave", + "on_act", + "on_focus_end", + ON_TICK = 'on_tick', + ON_CYCLE = 'on_cycle', + ON_TURN = 'on_turn', + ON_USE = 'on_use', + ON_ANY_USE = 'on_any_use', + ON_HIT = 'on_hit', + ON_DONE = 'on_done', + ON_PLACE = 'on_place', + ON_PLAY = 'on_play', + ON_LEAVE = 'on_leave', + ON_ACT = 'on_act', + ON_FOCUS_END = 'on_focus_end', +} + + diff --git a/game/domain/effects/add_card_to_backbuffer.lua b/game/domain/effects/add_card_to_backbuffer.lua new file mode 100644 index 00000000..11b41ba3 --- /dev/null +++ b/game/domain/effects/add_card_to_backbuffer.lua @@ -0,0 +1,15 @@ + +local DEFS = require 'domain.definitions' + +local FX = {} + +FX.schema = { + { id = 'card', name = "Card", type = 'value', match = 'card' }, +} + +function FX.process (actor, fieldvalues) + actor:addCardToBackbuffer(fieldvalues['card']) +end + +return FX + diff --git a/game/domain/effects/armor_on_target.lua b/game/domain/effects/armor_on_target.lua new file mode 100644 index 00000000..ad1e67ba --- /dev/null +++ b/game/domain/effects/armor_on_target.lua @@ -0,0 +1,35 @@ + +local ATTR = require 'domain.definitions.attribute' +local FX = {} + +FX.schema = { + { id = 'target', name = "Target", type = 'value', match = 'body' }, + { id = 'base', name = "Base Power", type = 'integer', + range = {1} }, + { id = 'attr', name = "Mod Power", type = 'value', + match = 'integer', range = {1} }, + { id = 'sfx', name = "SFX", type = 'enum', + options = 'resources.sfx', + optional = true }, +} + +function FX.preview (actor, fieldvalues) + local attr, base = fieldvalues.attr, fieldvalues.base + local amount = ATTR.EFFECTIVE_POWER(base, attr) + return ("Give %s armor to target"):format(amount) +end + +function FX.process (actor, fieldvalues) + local attr, base = fieldvalues.attr, fieldvalues.base + local amount = ATTR.EFFECTIVE_POWER(base, attr) + amount = fieldvalues.target:gainArmor(amount) + + coroutine.yield('report', { + type = 'text_rise', + text_type = 'armor', + body = fieldvalues['target'], + amount = amount, + }) +end + +return FX diff --git a/game/domain/effects/change_sector.lua b/game/domain/effects/change_sector.lua new file mode 100644 index 00000000..a9e3c1a5 --- /dev/null +++ b/game/domain/effects/change_sector.lua @@ -0,0 +1,16 @@ + +local FX = {} + +FX.schema = { + { id = 'target_sector', name = "Target Sector", type = 'value', + match = 'sector' }, + { id = 'target_pos', name = "Target Position", type = 'value', match = 'pos' } +} + +function FX.process(actor, fieldvalues) + local target_sector = Util.findId(fieldvalues.target_sector) + target_sector:putActor(actor, unpack(fieldvalues.target_pos)) +end + +return FX + diff --git a/game/domain/effects/consume_cards.lua b/game/domain/effects/consume_cards.lua new file mode 100644 index 00000000..f05494a6 --- /dev/null +++ b/game/domain/effects/consume_cards.lua @@ -0,0 +1,24 @@ + +local FX = {} + +FX.schema = { + { id = 'card_list', name = "Consumed card list", type = 'value', + match = 'consume_list' }, +} + +function FX.preview(actor, fieldvalues) + return ("Consume up to %s cards"):format(fieldvalues['card_list']) +end + +function FX.process (actor, fieldvalues) + local consume_list = fieldvalues['card_list'] + local bufsize = actor:getBufferSize() + local n = #consume_list + for i=n,1,-1 do + local card = actor:removeBufferCard(consume_list[i]) + actor:consumeCard(card) + end +end + +return FX + diff --git a/game/domain/effects/damage_on_area.lua b/game/domain/effects/damage_on_area.lua new file mode 100644 index 00000000..0fc9726b --- /dev/null +++ b/game/domain/effects/damage_on_area.lua @@ -0,0 +1,49 @@ + +local RANDOM = require 'common.random' +local ATTR = require 'domain.definitions.attribute' +local FX = {} + +FX.schema = { + { id = 'center', name = "Target position", type = 'value', match = 'pos' }, + { id = 'size', name = "Area Size", type = 'value', match = 'integer', + range = {1} }, + { id = 'base', name = "Base Power", type = 'integer', + range = {1} }, + { id = 'attr', name = "Mod Power", type = 'value', match = 'integer', + range = {1} }, + { id = 'ignore_owner', name = "Ignore Owner", type = 'boolean'}, +} + +function FX.preview (actor, fieldvalues) + local attr, base = fieldvalues.attr, fieldvalues.base + local amount = ATTR.EFFECTIVE_POWER(base, attr) + local size = fieldvalues['size'] * 2 - 1 + return ("Deal %s damage on %sx%s area"):format(amount, size, size) +end + +function FX.process (actor, fieldvalues) + local sector = actor:getBody():getSector() + local ci, cj = unpack(fieldvalues['center']) + local size = fieldvalues['size'] + local attr = fieldvalues['attr'] + local base = fieldvalues['base'] + local ignore_owner = fieldvalues['ignore_owner'] + local amount = ATTR.EFFECTIVE_POWER(base, attr) + for i=ci-size+1,ci+size-1 do + for j=cj-size+1,cj+size-1 do + local body = sector:getBodyAt(i, j) if body then + if not ignore_owner or body ~= actor:getBody() then + local dmg = body:takeDamageFrom(amount, actor) + coroutine.yield('report', { + type = 'text_rise', + text_type = 'damage', + body = body, + amount = dmg, + }) + end + end + end + end +end + +return FX diff --git a/game/domain/effects/damage_on_target.lua b/game/domain/effects/damage_on_target.lua new file mode 100644 index 00000000..7254acee --- /dev/null +++ b/game/domain/effects/damage_on_target.lua @@ -0,0 +1,37 @@ + +local RANDOM = require 'common.random' +local ATTR = require 'domain.definitions.attribute' +local FX = {} + +FX.schema = { + { id = 'target', name = "Target", type = 'value', match = 'body' }, + { id = 'base', name = "Base Power", type = 'integer', + range = {1} }, + { id = 'attr', name = "Mod Power", type = 'value', + match = 'integer', range = {1} }, + { id = 'sfx', name = "SFX", type = 'enum', + options = 'resources.sfx', + optional = true }, +} + +function FX.preview (actor, fieldvalues) + local attr, base = fieldvalues.attr, fieldvalues.base + local amount = ATTR.EFFECTIVE_POWER(base, attr) + return ("Deal %s damage to target"):format(amount) +end + +function FX.process (actor, fieldvalues) + local attr, base = fieldvalues.attr, fieldvalues.base + local amount = ATTR.EFFECTIVE_POWER(base, attr) + local dmg = fieldvalues.target:takeDamageFrom(amount, actor) + + coroutine.yield('report', { + type = 'text_rise', + text_type = 'damage', + body = fieldvalues['target'], + amount = dmg, + sfx = fieldvalues.sfx, + }) +end + +return FX diff --git a/game/domain/effects/destroy_body.lua b/game/domain/effects/destroy_body.lua new file mode 100644 index 00000000..a46acb77 --- /dev/null +++ b/game/domain/effects/destroy_body.lua @@ -0,0 +1,18 @@ + +local RANDOM = require 'common.random' +local FX = {} + +FX.schema = { + { id = 'target', name = "Target", type = 'value', match = 'body' }, +} + +function FX.preview() + return "Destroy target body" +end + +function FX.process (actor, fieldvalues) + fieldvalues.target:exterminate() +end + +return FX + diff --git a/game/domain/effects/draw_card.lua b/game/domain/effects/draw_card.lua new file mode 100644 index 00000000..5a4374a2 --- /dev/null +++ b/game/domain/effects/draw_card.lua @@ -0,0 +1,19 @@ + +local FX = {} + +FX.schema = { + { id = 'amount', name = "Amount of cards", type = 'value', match = 'integer', + range = {1} }, +} + +function FX.preview(actor, fieldvalues) + return ("Draw %s cards"):format(fieldvalues['amount']) +end + +function FX.process (actor, fieldvalues) + for i = 1, fieldvalues.amount do + actor:drawCard() + end +end + +return FX diff --git a/game/domain/effects/give_pack.lua b/game/domain/effects/give_pack.lua new file mode 100644 index 00000000..bb96ac18 --- /dev/null +++ b/game/domain/effects/give_pack.lua @@ -0,0 +1,25 @@ + +local FX = {} + +FX.schema = { + { id = 'collection', name = "Pack's Collection", + type = 'enum', options = 'domains.collection' }, +} + +function FX.preview() + return "Get card pack" +end + +function FX.process (actor, fieldvalues) + actor:addPrizePack(fieldvalues['collection']) + coroutine.yield('report', { + type = 'text_rise', + text_type = 'status', + body = actor:getBody(), + string = "+Pack", + sfx = fieldvalues.sfx, + }) +end + +return FX + diff --git a/game/domain/effects/heal.lua b/game/domain/effects/heal.lua new file mode 100644 index 00000000..bdfc0941 --- /dev/null +++ b/game/domain/effects/heal.lua @@ -0,0 +1,35 @@ + +local RANDOM = require 'common.random' +local ATTR = require 'domain.definitions.attribute' + +local FX = {} + +FX.schema = { + { id = 'target', name = "Target", type = 'value', match = 'body' }, + { id = 'base', name = "Heal power base", type = 'integer', + range = {1} }, + { id = 'attr', name = "Heal power mod", type = 'value', match = 'integer', + range = {1} }, +} + +function FX.preview(actor, fieldvalues) + local attr, base = fieldvalues.attr, fieldvalues.base + local amount = ATTR.EFFECTIVE_POWER(base, attr) + return ("Heal %s hit points"):format(amount) +end + +function FX.process(actor, fieldvalues) + local attr, base = fieldvalues.attr, fieldvalues.base + local target = fieldvalues['target'] + local amount = ATTR.EFFECTIVE_POWER(base, attr) + local effective_amount = target:heal(amount) + coroutine.yield('report', { + type = 'text_rise', + text_type = 'heal', + body = target, + amount = effective_amount, + }) +end + +return FX + diff --git a/game/domain/effects/lose_life.lua b/game/domain/effects/lose_life.lua new file mode 100644 index 00000000..14115995 --- /dev/null +++ b/game/domain/effects/lose_life.lua @@ -0,0 +1,42 @@ + +local math = require 'common.math' + +local FX = {} + +FX.schema = { + { id = 'percentage', name = "HP Percentage", type = 'integer', + range = {1, 90} + }, + { id = 'target', name = "Target", type = 'value', match = 'body' }, + { id = 'stay_alive', name = "Stay Alive?", type = 'boolean' }, +} + +-- can be for self-damaging attacks, as well as effects like poison + +function FX.preview(actor, fieldvalues) + local str = ("Lose %s%% hit points"):format(fieldvalues['percentage']) + if fieldvalues['stay_alive'] then + str = str .. " (non-lethal)" + end + return str +end + +function FX.process (actor, fieldvalues) + local body = actor:getBody() + local current_hp = body:getHP() + local max_hp = body:getMaxHP() + local amount = math.round(max_hp*fieldvalues.percentage/100) + if fieldvalues.stay_alive then + amount = math.min(current_hp - 1, amount) + end + local dmg = body:loseLifeFrom(amount, actor) + coroutine.yield('report', { + type = 'text_rise', + text_type = 'damage', + body = body, + amount = amount, + }) +end + +return FX + diff --git a/game/domain/effects/make_body.lua b/game/domain/effects/make_body.lua new file mode 100644 index 00000000..0778ed92 --- /dev/null +++ b/game/domain/effects/make_body.lua @@ -0,0 +1,59 @@ + +local Card = require 'domain.card' +local DB = require 'database' +local FX = {} + +FX.schema = { + { id = 'bodyspec', name = "Body Type", type = 'enum', + options = 'domains.body' }, + { id = 'pos', name = "Position", type = 'value', match = 'pos' }, + { id = 'vit', name = "VIT upgrades", type = 'value', match = 'integer', + range = {0} }, + { id = 'def', name = "DEF upgrades", type = 'value', match = 'integer', + range = {0} }, + { + id = 'widgets', name = "Starting Widget", type = 'array', + schema = { + { id = 'spec', name = "Which", type = 'enum', + options = 'domains.card' }, + } + } +} + +function FX.preview(_, fieldvalues) + local name = DB.loadSpec('body', fieldvalues['bodyspec'])['name'] + local str = ("Create %s"):format(name) + local widgets = fieldvalues['widgets'] + if widgets and #widgets > 0 then + str = str .. " with " + local list, n = {}, 0 + for _,widget in ipairs(widgets) do + n = n + 1 + list[n] = DB.loadSpec('card', widget.spec)['name'] + end + str = str .. table.concat(list, ', ') + end + return str +end + +function FX.process (actor, fieldvalues) + local sector = actor:getBody():getSector() + local bodyspec = fieldvalues['bodyspec'] + local i,j = unpack(fieldvalues['pos']) + local body = sector:getRoute().makeBody(sector, bodyspec, i, j) + local state = body:saveState() + state.widgets = {} + for _,widget_fieldvalues in ipairs(fieldvalues['widgets']) do + local widgetspec = widget_fieldvalues['spec'] + if widgetspec then + local widget = Card(widgetspec) + assert(widget:isWidget()) + widget:setOwner(actor) + table.insert(state.widgets, widget) + end + end + body:loadState(state) +end + +return FX + diff --git a/game/domain/effects/modify_exp.lua b/game/domain/effects/modify_exp.lua new file mode 100644 index 00000000..b85033a1 --- /dev/null +++ b/game/domain/effects/modify_exp.lua @@ -0,0 +1,13 @@ + +local FX = {} + +FX.schema = { + { id = 'value', name = "Modify Exp By", type = 'value', match = 'integer' }, +} + +function FX.process(actor, fieldvalues) + actor:modifyExpBy(fieldvalues.value) +end + +return FX + diff --git a/game/domain/effects/move_to.lua b/game/domain/effects/move_to.lua new file mode 100644 index 00000000..4b34d197 --- /dev/null +++ b/game/domain/effects/move_to.lua @@ -0,0 +1,54 @@ + +local FX = {} + +FX.schema = { + { id = 'body', name = "Body", type = 'value', match = 'body' }, + { id = 'pos', name = "Position", type = 'value', match = 'pos' }, + { id = 'vfx', name = "Visual Effect", type = 'enum', + options = { 'SLIDE', 'JUMP' } }, + { id = 'vfx-spd', name ="Animation Speed", type = 'float', + range = {0.1, 10.0}, default = 1.0 }, + { id = 'sfx', name = "SFX", type = 'enum', + options = 'resources.sfx', + optional = true }, +} + +function FX.preview(_, fieldvalues) + return "Movement effect" +end + +function FX.process (actor, fieldvalues) + local pos = {actor:getPos()} + local body = fieldvalues['body'] + local target_pos = fieldvalues['pos'] + local sfx = fieldvalues['sfx'] + if pos[1] == target_pos[1] and pos[2] == target_pos[2] then + return + end + local sector = body:getSector() + sector:putBody(body, unpack(target_pos)) + if fieldvalues['vfx'] == 'SLIDE' then + coroutine.yield('report', { + type = 'body_moved', + body = body, + origin = pos, + sfx = sfx, + speed_factor = fieldvalues['vfx-spd'] + }) + elseif fieldvalues['vfx'] == 'JUMP' then + coroutine.yield('report', { + type = 'body_jumped', + body = body, + origin = pos, + sfx = sfx, + speed_factor = fieldvalues['vfx-spd'] + }) + else + coroutine.yield('report', { + sfx = sfx, + }) + end +end + +return FX + diff --git a/game/domain/effects/new_hand.lua b/game/domain/effects/new_hand.lua new file mode 100644 index 00000000..87126db0 --- /dev/null +++ b/game/domain/effects/new_hand.lua @@ -0,0 +1,19 @@ + +local DEFS = require 'domain.definitions' + +local FX = {} + +FX.schema = { + { id='nothing', type='none', name = "NO PARAM" } +} + +function FX.process (actor, fieldvalues) + if actor:isHandEmpty() then + for i = 1, DEFS.HAND_LIMIT do + actor:drawCard() + end + end +end + +return FX + diff --git a/game/domain/effects/place_widget.lua b/game/domain/effects/place_widget.lua new file mode 100644 index 00000000..6e94e5c1 --- /dev/null +++ b/game/domain/effects/place_widget.lua @@ -0,0 +1,32 @@ + +local Card = require 'domain.card' +local DB = require 'database' + +local FX = {} + +FX.schema = { + { id = 'body', name = "Target Body", type = 'value', match = "body" }, + { id = 'card', name = "Card Specname", type = 'enum', + options = "domains.card" }, +} + +function FX.preview(_, fieldvalues) + local name = DB.loadSpec('card', fieldvalues['card'])['name'] + return ("Cause %s"):format(name) +end + +function FX.process(actor, fieldvalues) + local card = Card(fieldvalues['card']) + local body = fieldvalues['body'] + card:setOwner(actor) + body:placeWidget(card) + coroutine.yield('report', { + type = 'text_rise', + text_type = 'status', + body = body, + string = card:getName(), + sfx = fieldvalues.sfx, + }) +end + +return FX diff --git a/game/domain/effects/place_widget_on_area.lua b/game/domain/effects/place_widget_on_area.lua new file mode 100644 index 00000000..19f2b1cc --- /dev/null +++ b/game/domain/effects/place_widget_on_area.lua @@ -0,0 +1,48 @@ + +local RANDOM = require 'common.random' +local DB = require 'database' +local Card = require 'domain.card' +local FX = {} + +FX.schema = { + { id = 'center', name = "Target position", type = 'value', match = 'pos' }, + { id = 'ignore_owner', name = "Ignore Owner", type = 'boolean'}, + { id = 'size', name = "Area Size", type = 'value', match = 'integer', + range = {1} }, + { id = 'card', name = "Card Specname", type = 'enum', + options = "domains.card" }, +} + +function FX.preview(_, fieldvalues) + local name = DB.loadSpec('card', fieldvalues['card'])['name'] + local size = fieldvalues['size'] * 2 - 1 + return ("Cause %s on %sx%s area"):format(name, size, size) +end + +function FX.process (actor, fieldvalues) + local sector = actor:getBody():getSector() + local ci, cj = unpack(fieldvalues['center']) + local size = fieldvalues['size'] + local cardspec = fieldvalues['card'] + local ignore_owner = fieldvalues['ignore_owner'] + for i=ci-size+1,ci+size-1 do + for j=cj-size+1,cj+size-1 do + local body = sector:getBodyAt(i, j) if body then + if not ignore_owner or body ~= actor:getBody() then + local card = Card(cardspec) + card:setOwner(actor) + body:placeWidget(card) + coroutine.yield('report', { + type = 'text_rise', + text_type = 'status', + body = body, + string = card:getName(), + sfx = fieldvalues.sfx, + }) + end + end + end + end +end + +return FX diff --git a/game/domain/effects/print.lua b/game/domain/effects/print.lua new file mode 100644 index 00000000..ca5716df --- /dev/null +++ b/game/domain/effects/print.lua @@ -0,0 +1,17 @@ + +local FX = {} + +FX.schema = { + { id = 'text', name = "Text", type = 'value', match = 'integer', + range = {0} } +} + +function FX.preview() + return "Debug" +end + +function FX.process (actor,fieldvalues) + print(fieldvalues.text) +end + +return FX diff --git a/game/domain/effects/remove_actor_card.lua b/game/domain/effects/remove_actor_card.lua new file mode 100644 index 00000000..056033d5 --- /dev/null +++ b/game/domain/effects/remove_actor_card.lua @@ -0,0 +1,25 @@ + +local FX = {} + +FX.schema = { + { id = 'actor', name = "Actor", type = "value", match = 'actor' }, + { id = 'source', name = "Card source", type = 'enum', + options = { 'HAND', 'PACK' } }, + { id = 'card-index', name = "Card position in pack", type = 'value', + match = 'integer', range = {1} }, +} + +function FX.process (actor, fieldvalues) + local self = fieldvalues['actor'] + local source = fieldvalues['source'] + if source == 'HAND' then + self:removeHandCard(fieldvalues['card-index']) + elseif source == 'PACK' then + self:removePackCard(fieldvalues['card-index']) + else + return error("Unknown card source") + end +end + +return FX + diff --git a/game/domain/effects/reward_pp.lua b/game/domain/effects/reward_pp.lua new file mode 100644 index 00000000..f88a99aa --- /dev/null +++ b/game/domain/effects/reward_pp.lua @@ -0,0 +1,34 @@ + +local DEFS = require 'domain.definitions' +local FX = {} + +FX.schema = { + { id = 'amount', name = "PP Amount", type = 'value', + match = 'integer', range = {0} }, + { id = 'target', name = "Target", optional = true, type = 'value', + match = 'body' }, +} + +function FX.preview() + return ("Give %s PP"):format(fieldvalues['amount']) +end + +function FX.process (actor, fieldvalues) + local target = fieldvalues['target'] + local amount = fieldvalues['amount'] + if target then + actor = target:getSector():getActorFromBody(target) + end + actor:rewardPP(amount) + + coroutine.yield('report', { + type = 'text_rise', + text_type = 'food', + body = target or actor:getBody(), + amount = amount, + }) + return amount +end + +return FX + diff --git a/game/domain/effects/spend_pp.lua b/game/domain/effects/spend_pp.lua new file mode 100644 index 00000000..672767d8 --- /dev/null +++ b/game/domain/effects/spend_pp.lua @@ -0,0 +1,12 @@ + +local DEFS = require 'domain.definitions' +local FX = {} + +FX.schema = {} + +function FX.process (actor, fieldvalues) + actor:spendPP(DEFS.NEW_HAND_COST) +end + +return FX + diff --git a/game/domain/gameelement.lua b/game/domain/gameelement.lua new file mode 100644 index 00000000..d460f85f --- /dev/null +++ b/game/domain/gameelement.lua @@ -0,0 +1,46 @@ + + +local DB = require 'database' + +local GameElement = Class{ + __includes = { ELEMENT } +} + +function GameElement:init(spectype, specname) + + ELEMENT.init(self) + + self.spectype = spectype + self.specname = specname + +end + +function GameElement:kill() + self.death = true +end + +function GameElement:getId() + return self.id +end + +function GameElement:loadState(state) +end + +function GameElement:saveState() + local state = {} + state.specname = self.specname + return state +end + +function GameElement:getSpecName() + return self.specname +end + +function GameElement:getSpec(key) + local spec = DB.loadSpec(self.spectype, self.specname) + assert(spec, ("Spec %s/%s not found"):format(self.spectype, self.specname)) + return spec[key] +end + +return GameElement + diff --git a/game/domain/inputs/body.lua b/game/domain/inputs/body.lua new file mode 100644 index 00000000..ba9c94ed --- /dev/null +++ b/game/domain/inputs/body.lua @@ -0,0 +1,17 @@ + +local DB = require 'database' + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'body' + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/inputs/card.lua b/game/domain/inputs/card.lua new file mode 100644 index 00000000..3608d23c --- /dev/null +++ b/game/domain/inputs/card.lua @@ -0,0 +1,17 @@ + +local DB = require 'database' + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'card' + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/inputs/card_has_attribute.lua b/game/domain/inputs/card_has_attribute.lua new file mode 100644 index 00000000..5cc03c7a --- /dev/null +++ b/game/domain/inputs/card_has_attribute.lua @@ -0,0 +1,19 @@ + +local DEFS = require 'domain.definitions' +local INPUT = {} + +INPUT.schema = { + { id = 'card', name = "Card", type = 'value', match = 'card' }, + { id = 'attribute', name = "Attribute", type = 'enum', + options = DEFS.PRIMARY_ATTRIBUTES }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'boolean' + +function INPUT.isValid(actor, fieldvalues, value) + return fieldvalues['card']:getRelatedAttr() == fieldvalues['attribute'] +end + +return INPUT + diff --git a/game/domain/inputs/card_index.lua b/game/domain/inputs/card_index.lua new file mode 100644 index 00000000..09baf3ac --- /dev/null +++ b/game/domain/inputs/card_index.lua @@ -0,0 +1,17 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'source', name = "Card source", type = 'enum', + options = { 'HAND', 'PACK' } }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'integer' + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/inputs/card_is_type.lua b/game/domain/inputs/card_is_type.lua new file mode 100644 index 00000000..76892e4f --- /dev/null +++ b/game/domain/inputs/card_is_type.lua @@ -0,0 +1,26 @@ + +local DEFS = require 'domain.definitions' +local INPUT = {} + +INPUT.schema = { + { id = 'card', name = "Card", type = 'value', match = 'card' }, + { id = 'cardtype', name = "Type", type = 'enum', + options = DEFS.CARD_TYPES }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'boolean' + +function INPUT.isValid(actor, fieldvalues, value) + local cardtype = fieldvalues['cardtype'] + local card = fieldvalues['card'] + if cardtype == 'ART' then + return card:isArt() + elseif cardtype == 'WIDGET' then + return card:isWidget() + end + return false +end + +return INPUT + diff --git a/game/domain/inputs/choose_consume_list.lua b/game/domain/inputs/choose_consume_list.lua new file mode 100644 index 00000000..fa784814 --- /dev/null +++ b/game/domain/inputs/choose_consume_list.lua @@ -0,0 +1,21 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "choose_consume_list", type = 'output' }, + { id = 'max', name = "Max consumable cards", type = 'value', + match = 'integer', range = {1} } +} + +INPUT.type = 'consume_list' + +function INPUT.preview(actor, fieldvalues) + return fieldvalues['max'] +end + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/inputs/choose_dir.lua b/game/domain/inputs/choose_dir.lua new file mode 100644 index 00000000..95ef4386 --- /dev/null +++ b/game/domain/inputs/choose_dir.lua @@ -0,0 +1,23 @@ + +local DIR = require 'domain.definitions.dir' + +local INPUT = {} + +INPUT.schema = { + { id = 'body-block', name = "Stop on bodies", type = 'boolean' }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'dir' + +function INPUT.isValid(actor, fieldvalues, value) + for _,dir in ipairs(DIR) do + dir = DIR[dir] + if dir[1] == value[1] and dir[2] == value[2] then + return true + end + end + return false +end + +return INPUT diff --git a/game/domain/inputs/choose_target.lua b/game/domain/inputs/choose_target.lua new file mode 100644 index 00000000..b73fa73a --- /dev/null +++ b/game/domain/inputs/choose_target.lua @@ -0,0 +1,76 @@ + +local SCHEMATICS = require 'domain.definitions.schematics' +local TILE = require 'common.tile' +local DB = require 'database' + +local INPUT = {} + +INPUT.schema = { + { id = 'max-range', name = "Maximum range", type = 'value', match = 'integer', + range = {0} }, + { + id = 'body-only', name = "Only position with body", type = 'section', + schema = { + { id = 'body-type', name = "Body Type", type = 'enum', + options = 'domains.body' } + } + }, + { id = 'empty-tile', name = "Only empty position", type = 'boolean' }, + { id = 'dif-fact', name = "Only different faction", type = 'boolean' }, + { id = 'non-wall', name = "Only without wall", type = 'boolean' }, + { id = 'aoe-hint', name = "Size of previewed AoE", type = 'integer', + range = {1} }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'pos' + +function INPUT.isWithinRange(actor, fieldvalues, value) + local max = fieldvalues['max-range'] if max then + local i,j = actor:getPos() + local dist = TILE.dist(i,j,unpack(value)) + if dist > max then + return false + end + end + return true +end + +function INPUT.isValid(actor, fieldvalues, value) + local sector = actor:getBody():getSector() + local i, j = unpack(value) + if not sector:isInside(i, j) then + return false + end + if fieldvalues['body-only'] then + local body = sector:getBodyAt(i, j) + if not body then + return false + end + local typename = fieldvalues['body-only']['body-type'] + if typename and not body:isSpec(typename) then + return false + end + end + if fieldvalues['empty-tile'] and sector:getBodyAt(i, j) then + return false + end + if fieldvalues['dif-fact'] and sector:getBodyAt(i, j):getFaction() == actor:getBody():getFaction() then + return false + end + local tile = sector:getTile(i, j) + if fieldvalues['non-wall'] and tile and tile.type == SCHEMATICS.WALL then + return false + end + if not INPUT.isWithinRange(actor, fieldvalues, value) then + return false + end + local fov = actor:getFov(sector) + if not fov[i][j] or fov[i][j] == 0 then + return false + end + return true +end + +return INPUT + diff --git a/game/domain/inputs/choose_widget_slot.lua b/game/domain/inputs/choose_widget_slot.lua new file mode 100644 index 00000000..4844e991 --- /dev/null +++ b/game/domain/inputs/choose_widget_slot.lua @@ -0,0 +1,18 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'widget_slot' + +function INPUT.isValid(actor, fieldvalues, value) + if not actor:getBody():hasWidgetAt(value) then + return false + end + return not not actor:getBody():getWidget(value):getWidgetActivation() +end + +return INPUT + diff --git a/game/domain/inputs/different_card.lua b/game/domain/inputs/different_card.lua new file mode 100644 index 00000000..426d6380 --- /dev/null +++ b/game/domain/inputs/different_card.lua @@ -0,0 +1,19 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'lhs', name = "Operand 1", type = 'value', match = 'card' }, + { id = 'rhs', name = "Operand 2", type = 'value', match = 'card' }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'boolean' + +function INPUT.isValid(actor, fieldvalues, value) + local lhs, rhs = fieldvalues['lhs'], fieldvalues['rhs'] + local op = fieldvalues['op'] + return lhs ~= rhs +end + +return INPUT + diff --git a/game/domain/inputs/direction.lua b/game/domain/inputs/direction.lua new file mode 100644 index 00000000..cf8fdde5 --- /dev/null +++ b/game/domain/inputs/direction.lua @@ -0,0 +1,15 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'pos' + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/inputs/empty_hand.lua b/game/domain/inputs/empty_hand.lua new file mode 100644 index 00000000..210831d4 --- /dev/null +++ b/game/domain/inputs/empty_hand.lua @@ -0,0 +1,15 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'boolean' + +function INPUT.isValid(actor, fieldvalues, value) + return actor:isHandEmpty() +end + +return INPUT + diff --git a/game/domain/inputs/has_enough_pp.lua b/game/domain/inputs/has_enough_pp.lua new file mode 100644 index 00000000..6a8af9c6 --- /dev/null +++ b/game/domain/inputs/has_enough_pp.lua @@ -0,0 +1,17 @@ + +local DEFS = require 'domain.definitions' +local INPUT = {} + +INPUT.schema = { + { id = 'amount', name = "Amount", type = 'value', match = 'integer' }, + { id = 'output', name = "Label", type = 'output' }, +} + +INPUT.type = 'none' + +function INPUT.isValid(actor, fieldvalues, value) + return actor:getPP() >= fieldvalues['amount'] +end + +return INPUT + diff --git a/game/domain/inputs/has_less_pp.lua b/game/domain/inputs/has_less_pp.lua new file mode 100644 index 00000000..0f609924 --- /dev/null +++ b/game/domain/inputs/has_less_pp.lua @@ -0,0 +1,17 @@ + +local DEFS = require 'domain.definitions' +local INPUT = {} + +INPUT.schema = { + { id = 'amount', name = "Amount", type = 'value', match = 'integer' }, + { id = 'output', name = "Label", type = 'output' }, +} + +INPUT.type = 'none' + +function INPUT.isValid(actor, fieldvalues, value) + return actor:getPP() < fieldvalues['amount'] +end + +return INPUT + diff --git a/game/domain/inputs/has_nearby_bodies.lua b/game/domain/inputs/has_nearby_bodies.lua new file mode 100644 index 00000000..9be0c54f --- /dev/null +++ b/game/domain/inputs/has_nearby_bodies.lua @@ -0,0 +1,53 @@ + +local DEFS = require 'domain.definitions' +local INPUT = {} + +INPUT.schema = { + { id = 'pos', name = "Position", type = 'value', match = 'pos' }, + { id = 'count', name = "At least", type = 'value', match = 'integer', + range = {1} }, + { id = 'range', name = "Range", type = 'value', match = 'integer', + range = {0} }, + { id = 'body-type', name = "Body Type", type = 'enum', + options = 'domains.body' }, + { id = 'ignore-owner', name = "Ignore Owner", type = 'boolean' }, + { id = 'ignore-same-faction', name = "Ignore Same Faction", + type = 'boolean' }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'none' + +local function _checkOwner(actor, body, ignore) + return not ignore or actor:getBody() ~= body +end + +local function _checkFaction(actor, body, ignore) + return not ignore or actor:getBody():getFaction() ~= body:getFaction() +end + +function INPUT.isValid(actor, fieldvalues, value) + local sector = actor:getBody():getSector() + local i, j = unpack(fieldvalues['pos']) + local range = fieldvalues['range'] + local specname = fieldvalues['body-type'] + local notowner = fieldvalues['ignore-owner'] + local notfaction = fieldvalues['ignore-same-faction'] + local count = 0 + for di=i-range,i+range do + for dj=j-range,j+range do + if di ~= i or dj ~= j then + local body = sector:getBodyAt(di, dj) + if body and body:isSpec(specname) + and _checkOwner(actor, body, notowner) + and _checkFaction(actor, body, notfaction) then + count = count + 1 + end + end + end + end + return count >= fieldvalues['count'] +end + +return INPUT + diff --git a/game/domain/inputs/is_controlled_actor.lua b/game/domain/inputs/is_controlled_actor.lua new file mode 100644 index 00000000..0965483e --- /dev/null +++ b/game/domain/inputs/is_controlled_actor.lua @@ -0,0 +1,13 @@ + +local INPUT = {} + +INPUT.schema = {} + +INPUT.type = 'boolean' + +function INPUT.isValid(actor, fieldvalues, value) + return actor == actor:getSector():getRoute().getControlledActor() +end + +return INPUT + diff --git a/game/domain/inputs/not_full_hand.lua b/game/domain/inputs/not_full_hand.lua new file mode 100644 index 00000000..cc399e89 --- /dev/null +++ b/game/domain/inputs/not_full_hand.lua @@ -0,0 +1,15 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'boolean' + +function INPUT.isValid(actor, fieldvalues, value) + return not actor:isHandFull() +end + +return INPUT + diff --git a/game/domain/inputs/pack_card.lua b/game/domain/inputs/pack_card.lua new file mode 100644 index 00000000..ba9fca35 --- /dev/null +++ b/game/domain/inputs/pack_card.lua @@ -0,0 +1,15 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'card' + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/inputs/pos.lua b/game/domain/inputs/pos.lua new file mode 100644 index 00000000..7992c956 --- /dev/null +++ b/game/domain/inputs/pos.lua @@ -0,0 +1,17 @@ + +local DB = require 'database' + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'pos' + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/inputs/same_card.lua b/game/domain/inputs/same_card.lua new file mode 100644 index 00000000..680f5096 --- /dev/null +++ b/game/domain/inputs/same_card.lua @@ -0,0 +1,19 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'lhs', name = "Operand 1", type = 'value', match = 'card' }, + { id = 'rhs', name = "Operand 2", type = 'value', match = 'card' }, + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'boolean' + +function INPUT.isValid(actor, fieldvalues, value) + local lhs, rhs = fieldvalues['lhs'], fieldvalues['rhs'] + local op = fieldvalues['op'] + return lhs == rhs +end + +return INPUT + diff --git a/game/domain/inputs/sector.lua b/game/domain/inputs/sector.lua new file mode 100644 index 00000000..b106ea35 --- /dev/null +++ b/game/domain/inputs/sector.lua @@ -0,0 +1,15 @@ + +local INPUT = {} + +INPUT.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +INPUT.type = 'sector' + +function INPUT.isValid(actor, fieldvalues, value) + return true +end + +return INPUT + diff --git a/game/domain/maneuver/activate_widget.lua b/game/domain/maneuver/activate_widget.lua new file mode 100644 index 00000000..40e93112 --- /dev/null +++ b/game/domain/maneuver/activate_widget.lua @@ -0,0 +1,48 @@ + +local ACTIONDEFS = require 'domain.definitions.action' +local ABILITY = require 'domain.ability' +local DEFS = require 'domain.definitions' +local ACTIVATE = {} + +ACTIVATE.input_specs = { + { output = 'widget_slot', name = 'choose_widget_slot' } +} + +function ACTIVATE.card(actor, inputvalues) + return actor:getBody():getWidget(inputvalues.widget_slot) +end + +function ACTIVATE.activatedAbility(actor, inputvalues) + return actor:getBody():getWidget(inputvalues.widget_slot):getWidgetAbility() +end + +function ACTIVATE.exhaustionCost(actor, inputvalues) + local widget = actor:getBody():getWidget(inputvalues.widget_slot) + return widget and widget:getWidgetActivationCost() or 0 +end + +function ACTIVATE.validate(actor, inputvalues) + if not inputvalues.widget_slot then return false end + local widget = actor:getBody():getWidget(inputvalues.widget_slot) + if not widget then return false end + local ability = widget:getWidgetAbility() + return ability and ABILITY.checkInputs(ability, actor, inputvalues) +end + +function ACTIVATE.perform(actor, inputvalues) + local body = actor:getBody() + local widget = body:getWidget(inputvalues.widget_slot) + local ability = widget:getWidgetAbility() + coroutine.yield('report', { + type = 'body_acted', + body = body, + }) + actor:exhaust(widget:getWidgetActivationCost()) + ABILITY.execute(ability, actor, inputvalues) + body:triggerWidgets(DEFS.TRIGGERS.ON_ANY_USE, { activated_widget = widget }) + body:triggerWidgets(DEFS.TRIGGERS.ON_ACT) + body:triggerOneWidget(inputvalues.widget_slot, DEFS.TRIGGERS.ON_USE) +end + +return ACTIVATE + diff --git a/game/domain/maneuver/consume_cards_from_buffer.lua b/game/domain/maneuver/consume_cards_from_buffer.lua new file mode 100644 index 00000000..c8db9f02 --- /dev/null +++ b/game/domain/maneuver/consume_cards_from_buffer.lua @@ -0,0 +1,34 @@ + +local CONSUME = {} + +CONSUME.input_specs = { + { output = 'consumed', name = 'consume_list' }, +} + +function CONSUME.card(actor, inputvalues) + return nil +end + +function CONSUME.activatedAbility(actor, inputvalues) + return nil +end + +function CONSUME.exhaustionCost(actor, inputvalues) + return 0 +end + +function CONSUME.validate(actor, inputvalues) + return inputvalues.consumed +end + +function CONSUME.perform(actor, inputvalues) + for _,idx in ipairs(inputvalues.consumed) do + local index = idx + actor:getBufferSize()+1 + local card = actor:getBackBufferCard(index) + actor:removeBufferCard(index) + actor:consumeCard(card) + end +end + +return CONSUME + diff --git a/game/domain/maneuver/draw_new_hand.lua b/game/domain/maneuver/draw_new_hand.lua new file mode 100644 index 00000000..d203bd92 --- /dev/null +++ b/game/domain/maneuver/draw_new_hand.lua @@ -0,0 +1,38 @@ + +local DEFS = require 'domain.definitions' + +local DRAWHAND = {} + +DRAWHAND.input_specs = {} + +function DRAWHAND.card(actor, inputvalues) + return nil +end + +function DRAWHAND.activatedAbility(actor, inputvalues) + return nil +end + +function DRAWHAND.exhaustionCost(actor, inputvalues) + return 0 +end + +function DRAWHAND.validate(actor, inputvalues) + return not actor:isBufferEmpty() + and actor:getPP() >= actor:getBody():getConsumption() +end + +function DRAWHAND.perform(actor, inputvalues) + actor:spendPP(actor:getBody():getConsumption()) + while not actor:isHandEmpty() do + local card = actor:removeHandCard(1) + actor:addCardToBackbuffer(card) + end + for i = 1, DEFS.HAND_LIMIT do + actor:drawCard() + end + actor:resetFocus() +end + +return DRAWHAND + diff --git a/game/domain/maneuver/idle.lua b/game/domain/maneuver/idle.lua new file mode 100644 index 00000000..50d6c880 --- /dev/null +++ b/game/domain/maneuver/idle.lua @@ -0,0 +1,28 @@ + +local ACTIONDEFS = require 'domain.definitions.action' +local IDLE = {} + +IDLE.input_specs = {} + +function IDLE.card(actor, inputvalues) + return nil +end + +function IDLE.activatedAbility(actor, inputvalues) + return nil +end + +function IDLE.exhaustionCost(actor, inputvalues) + return ACTIONDEFS.IDLE_COST +end + +function IDLE.validate(actor, inputvalues) + return true +end + +function IDLE.perform(actor, inputvalues) + actor:exhaust(ACTIONDEFS.IDLE_COST) +end + +return IDLE + diff --git a/game/domain/maneuver/interact.lua b/game/domain/maneuver/interact.lua new file mode 100644 index 00000000..358870ba --- /dev/null +++ b/game/domain/maneuver/interact.lua @@ -0,0 +1,79 @@ + +local ABILITY = require 'domain.ability' +local ACTIONDEFS = require 'domain.definitions.action' +local SCHEMATICS = require 'domain.definitions.schematics' +local INTERACT = {} + +local CONSUME_ABILITY = { + inputs = { + { type = "input", + output = "label", + name = "choose_consume_list", + max = 2 } + }, + effects = { + { type = "effect", + name = "consume_cards", + card_list = "=label" } + } +} + +INTERACT.input_specs = { +} + +-- FIXME: CHANGE_SECTOR should be an activated ability of interaction with +-- stairs, portals, etc. + +local function _seek(actor, inputvalues) + local sector = actor:getBody():getSector() + if not inputvalues.interaction then + -- Try to go through exit + local i, j = actor:getPos() + local id, exit = sector:findExit(i, j, true) + if id then + inputvalues.interaction = 'CHANGE_SECTOR' + inputvalues.sector = id + inputvalues.pos = exit.target_pos + elseif sector:getTile(i, j).type == SCHEMATICS.ALTAR + and actor:isHandEmpty() then + inputvalues.interaction = 'CONSUME_CARDS' + end + end + return inputvalues.interaction +end + +function INTERACT.card(actor, inputvalues) + return nil +end + +function INTERACT.activatedAbility(actor, inputvalues) + _seek(actor, inputvalues) + if inputvalues.interaction == 'CONSUME_CARDS' then + return CONSUME_ABILITY + else + return nil + end +end + +function INTERACT.exhaustionCost(actor, inputvalues) + return ACTIONDEFS.MOVE_COST +end + +function INTERACT.validate(actor, inputvalues) + return not actor:isFocused() and not not _seek(actor, inputvalues) +end + +function INTERACT.perform(actor, inputvalues) + _seek(actor, inputvalues) + if inputvalues.interaction == 'CHANGE_SECTOR' then + actor:exhaust(ACTIONDEFS.MOVE_COST) + local target_sector = Util.findId(inputvalues.sector) + target_sector:putActor(actor, unpack(inputvalues.pos)) + elseif inputvalues.interaction == 'CONSUME_CARDS' then + ABILITY.execute(CONSUME_ABILITY, actor, inputvalues) + actor:getSector():getTile(actor:getPos()).type = SCHEMATICS.FLOOR + end +end + +return INTERACT + diff --git a/game/domain/maneuver/move.lua b/game/domain/maneuver/move.lua new file mode 100644 index 00000000..f58d47df --- /dev/null +++ b/game/domain/maneuver/move.lua @@ -0,0 +1,43 @@ + +local ACTIONDEFS = require 'domain.definitions.action' +local ABILITY = require 'domain.ability' +local DB = require 'database' +local MOVE = {} + +MOVE.input_specs = { + { output = 'pos', name = 'direction' }, +} + +function MOVE.card(actor, inputvalues) + return nil +end + +function MOVE.activatedAbility(actor, inputvalues) + return nil +end + +function MOVE.exhaustionCost(actor, inputvalues) + return ACTIONDEFS.MOVE_COST +end + +function MOVE.validate(actor, inputvalues) + local sector = actor:getBody():getSector() + return sector:isValid(unpack(inputvalues.pos)) +end + +function MOVE.perform(actor, inputvalues) + local sector = actor:getBody():getSector() + actor:exhaust(ACTIONDEFS.MOVE_COST) + local pos = {actor:getPos()} + sector:putBody(actor:getBody(), unpack(inputvalues.pos)) + coroutine.yield('report', { + type = 'body_moved', + body = actor:getBody(), + origin = pos, + sfx = 'footstep', + speed_factor = 1.0 + }) +end + +return MOVE + diff --git a/game/domain/maneuver/play_card.lua b/game/domain/maneuver/play_card.lua new file mode 100644 index 00000000..9cef7e41 --- /dev/null +++ b/game/domain/maneuver/play_card.lua @@ -0,0 +1,68 @@ + +local ACTIONDEFS = require 'domain.definitions.action' +local TRIGGERS = require 'domain.definitions.triggers' +local ABILITY = require 'domain.ability' + +local PLAYCARD = {} + +PLAYCARD.input_specs = { + { output = 'card_index', name = 'card_index' } +} + +local function _card(actor, inputvalues) + return actor:getHandCard(inputvalues.card_index) +end + +function PLAYCARD.card(actor, inputvalues) + return _card(actor, inputvalues) +end + +function PLAYCARD.activatedAbility(actor, inputvalues) + local card = _card(actor, inputvalues) + return card:isArt() and card:getArtAbility() +end + +function PLAYCARD.exhaustionCost(actor, inputvalues) + local card = _card(actor, inputvalues) + if card:isArt() then + return card:getArtCost() + elseif card:isWidget() then + return ACTIONDEFS.PLAY_WIDGET_COST + end + return 0 +end + +function PLAYCARD.validate(actor, inputvalues) + local card = _card(actor, inputvalues) + local valid = false + if card:isArt() then + valid = ABILITY.checkInputs(card:getArtAbility(), actor, inputvalues) + elseif card:isWidget() then + valid = true + end + return valid +end + +function PLAYCARD.perform(actor, inputvalues) + local card = _card(actor, inputvalues) + local body = actor:getBody() + actor:playCard(inputvalues.card_index) + + if card:isArt() then + coroutine.yield('report', { + type = 'body_acted', + body = body, + }) + actor:exhaust(card:getArtCost()) + ABILITY.execute(card:getArtAbility(), actor, inputvalues) + body:triggerWidgets(TRIGGERS.ON_ACT) + elseif card:isWidget() then + actor:exhaust(ACTIONDEFS.PLAY_WIDGET_COST) + body:placeWidget(card) + end + + body:triggerWidgets(TRIGGERS.ON_PLAY, { card = card }) +end + +return PLAYCARD + diff --git a/game/domain/maneuver/receive_pack.lua b/game/domain/maneuver/receive_pack.lua new file mode 100644 index 00000000..80a7831d --- /dev/null +++ b/game/domain/maneuver/receive_pack.lua @@ -0,0 +1,36 @@ + +local RECEIVEPACK = {} + +RECEIVEPACK.input_specs = { + { output = 'consumed', name = 'consume_list' }, + { output = 'pack', name = 'pack_list'} +} + +function RECEIVEPACK.card(actor, inputvalues) + return nil +end + +function RECEIVEPACK.activatedAbility(actor, inputvalues) + return nil +end + +function RECEIVEPACK.exhaustionCost(actor, inputvalues) + return 0 +end + +function RECEIVEPACK.validate(actor, inputvalues) + return inputvalues.consumed and inputvalues.pack +end + +function RECEIVEPACK.perform(actor, inputvalues) + for _,card in ipairs(inputvalues.consumed) do + actor:consumeCard(card) + end + for _,card in ipairs(inputvalues.pack) do + card:setOwner(actor) + actor:addCardToBackbuffer(card) + end +end + +return RECEIVEPACK + diff --git a/game/domain/maneuver/use_signature.lua b/game/domain/maneuver/use_signature.lua new file mode 100644 index 00000000..ba3d3933 --- /dev/null +++ b/game/domain/maneuver/use_signature.lua @@ -0,0 +1,37 @@ + +local DEFS = require 'domain.definitions' +local ACTIONDEFS = require 'domain.definitions.action' +local ABILITY = require 'domain.ability' +local SIGNATURE = {} + +SIGNATURE.input_specs = {} + +function SIGNATURE.card(actor, inputvalues) + return nil +end + +function SIGNATURE.activatedAbility(actor, inputvalues) + return actor:getSignature().ability +end + +function SIGNATURE.exhaustionCost(actor, inputvalues) + return actor:getSignature().cost +end + +function SIGNATURE.validate(actor, inputvalues) + return ABILITY.checkInputs(actor:getSignature().ability, actor, inputvalues) +end + +function SIGNATURE.perform(actor, inputvalues) + local signature = actor:getSignature() + coroutine.yield('report', { + type = 'body_acted', + body = actor:getBody(), + }) + actor:exhaust(signature.cost) + actor:getBody():triggerWidgets(DEFS.TRIGGERS.ON_ACT) + ABILITY.execute(signature.ability, actor, inputvalues) +end + +return SIGNATURE + diff --git a/game/domain/operators/count_nearby_bodies.lua b/game/domain/operators/count_nearby_bodies.lua new file mode 100644 index 00000000..5049a7e9 --- /dev/null +++ b/game/domain/operators/count_nearby_bodies.lua @@ -0,0 +1,44 @@ + +local OP = {} + +OP.schema = { + { id = 'pos', name = "Position", type = 'value', match = 'pos' }, + { id = 'range', name = "Range", type = 'value', match = 'integer', + range = {0} }, + { id = 'body-type', name = "Body Type", type = 'enum', + options = 'domains.body' }, + { id = 'ignore-owner', name = "Ignore Owner", type = 'boolean' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'integer' + +local function _checkOwner(actor, body, ignore) + return not ignore or actor:getBody() ~= body +end + +function OP.process(actor, fieldvalues) + local sector = actor:getBody():getSector() + local i, j = unpack(fieldvalues['pos']) + local range = fieldvalues['range'] + local specname = fieldvalues['body-type'] + local notowner = fieldvalues['ignore-owner'] + local count = 0 + for di=i-range,i+range do + for dj=j-range,j+range do + if di ~= i or dj ~= j then + local body = sector:getBodyAt(di, dj) + if body and body:isSpec(specname) + and _checkOwner(actor, body, notowner) then + count = count + 1 + end + end + end + end + return count +end + +OP.preview = OP.process + +return OP + diff --git a/game/domain/operators/dice_throw.lua b/game/domain/operators/dice_throw.lua new file mode 100644 index 00000000..a161f325 --- /dev/null +++ b/game/domain/operators/dice_throw.lua @@ -0,0 +1,24 @@ + +--- Get an attribute value of user + +local RANDOM = require 'common.random' +local OP = {} + +OP.schema = { + { id = 'rolls', name = "Rolls", type = 'value', match = 'integer', range = {1} }, + { id = 'sides', name = "Sides", type = 'value', match = 'integer', range = {1} }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'integer' + +function OP.preview(actor, fieldvalues) + return ("%sd%s"):format(fieldvalues['rolls'], fieldvalues['sides']) +end + +function OP.process(actor, fieldvalues) + return RANDOM.rollDice(fieldvalues.rolls, fieldvalues.sides) +end + +return OP + diff --git a/game/domain/operators/find_nearest_body.lua b/game/domain/operators/find_nearest_body.lua new file mode 100644 index 00000000..d57a3b66 --- /dev/null +++ b/game/domain/operators/find_nearest_body.lua @@ -0,0 +1,49 @@ + +local OP = {} + +OP.schema = { + { id = 'pos', name = "Position", type = 'value', match = 'pos' }, + { id = 'range', name = "Range", type = 'value', match = 'integer', + range = {0} }, + { id = 'ignore-owner', name = "Ignore Owner", type = 'boolean' }, + { id = 'ignore-same-faction', name = "Ignore Same Faction", + type = 'boolean' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'body' + +local function _checkOwner(actor, body, ignore) + return not ignore or actor:getBody() ~= body +end + +local function _checkFaction(actor, body, ignore) + return not ignore or actor:getBody():getFaction() ~= body:getFaction() +end + +function OP.process(actor, fieldvalues) + local sector = actor:getBody():getSector() + local i, j = unpack(fieldvalues['pos']) + local range = fieldvalues['range'] + local notowner = fieldvalues['ignore-owner'] + local notfaction = fieldvalues['ignore-same-faction'] + local nearest + local mindist = range+1 + for di=i-range,i+range do + for dj=j-range,j+range do + if di ~= i or dj ~= j then + local body = sector:getBodyAt(di, dj) + local dist = math.max(math.abs(di-i), math.abs(dj-j)) + if body and dist < mindist and _checkOwner(actor, body, notowner) + and _checkFaction(actor, body, notfaction) then + nearest = body + mindist = dist + end + end + end + end + return nearest +end + +return OP + diff --git a/game/domain/operators/get_actor_body.lua b/game/domain/operators/get_actor_body.lua new file mode 100644 index 00000000..8919730c --- /dev/null +++ b/game/domain/operators/get_actor_body.lua @@ -0,0 +1,20 @@ + +--- Get an attribute value of user + +local OP = {} + +OP.schema = { + { id = 'actor', name = "Actor", type = "value", match = 'actor' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'body' + +function OP.process(actor, fieldvalues) + return actor:getBody() +end + +OP.preview = OP.process + +return OP + diff --git a/game/domain/operators/get_actor_card.lua b/game/domain/operators/get_actor_card.lua new file mode 100644 index 00000000..506b24a8 --- /dev/null +++ b/game/domain/operators/get_actor_card.lua @@ -0,0 +1,31 @@ + +--- Get actor's card from pack + +local OP = {} + +OP.schema = { + { id = 'output', name = "Label", type = 'output' }, + { id = 'actor', name = "Actor", type = "value", match = 'actor' }, + { id = 'source', name = "Card source", type = 'enum', + options = { 'HAND', 'PACK' } }, + { id = 'card-index', name = "Position in Pack", type = "value", + match = 'integer', range = {1} } +} + +OP.type = 'card' + +function OP.process(actor, fieldvalues) + local self = fieldvalues['actor'] + local index = fieldvalues['card-index'] + local source = fieldvalues['source'] + if source == 'HAND' then + return self:getHandCard(index) + elseif source == 'PACK' then + return self:getPackCard(index) + else + return error("Unknown card source") + end +end + +return OP + diff --git a/game/domain/operators/get_attribute.lua b/game/domain/operators/get_attribute.lua new file mode 100644 index 00000000..65f96e2a --- /dev/null +++ b/game/domain/operators/get_attribute.lua @@ -0,0 +1,23 @@ + +--- Get an attribute value of user + +local DEFS = require 'domain.definitions' + +local OP = {} + +OP.schema = { + { id = 'which', name = "Attribute", type = 'enum', + options = DEFS.PRIMARY_ATTRIBUTES }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'integer' + +function OP.process(actor, fieldvalues) + return actor["get"..fieldvalues.which](actor) +end + +OP.preview = OP.process + +return OP + diff --git a/game/domain/operators/get_body_at.lua b/game/domain/operators/get_body_at.lua new file mode 100644 index 00000000..df0aa416 --- /dev/null +++ b/game/domain/operators/get_body_at.lua @@ -0,0 +1,18 @@ + +--- Get body at given position + +local OP = {} + +OP.schema = { + { id = 'pos', name = "Position", type = 'value', match = 'pos' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'body' + +function OP.process(actor, fieldvalues) + return actor:getBody():getSector():getBodyAt(unpack(fieldvalues.pos)) +end + +return OP + diff --git a/game/domain/operators/get_body_attribite.lua b/game/domain/operators/get_body_attribite.lua new file mode 100644 index 00000000..4660aaee --- /dev/null +++ b/game/domain/operators/get_body_attribite.lua @@ -0,0 +1,25 @@ + +--- Get an attribute value of body + +local DEFS = require 'domain.definitions' + +local OP = {} + +OP.schema = { + { id = 'body', name = "Body", type = 'value', match = 'body' }, + { id = 'which', name = "Attribute", type = 'enum', + options = DEFS.BODY_ATTRIBUTES }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'integer' + +function OP.process(actor, fieldvalues) + local body = fieldvalues['body'] + return body["get"..fieldvalues.which](body) +end + +OP.preview = OP.process + +return OP + diff --git a/game/domain/operators/get_body_pos.lua b/game/domain/operators/get_body_pos.lua new file mode 100644 index 00000000..0f0d0578 --- /dev/null +++ b/game/domain/operators/get_body_pos.lua @@ -0,0 +1,19 @@ + +--- Get body at given position + +local OP = {} + +OP.schema = { + { id = 'body', name = "Body", type = 'value', match = 'body' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'pos' + +function OP.process(actor, fieldvalues) + return { fieldvalues['body']:getPos() } +end + +OP.preview = OP.process + +return OP diff --git a/game/domain/operators/hitscan.lua b/game/domain/operators/hitscan.lua new file mode 100644 index 00000000..4c19f7a2 --- /dev/null +++ b/game/domain/operators/hitscan.lua @@ -0,0 +1,32 @@ + +--- Find last valid position in given direction + +local OP = {} + +OP.schema = { + { id = 'pos', name = "Origin", type = 'value', match = 'pos' }, + { id = 'dir', name = "Raycast direction", type = 'value', match = 'dir' }, + { id = 'maxrange', name = "Maximum range", type = 'value', match = 'integer', + range = {1} }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'pos' + +function OP.process(actor, fieldvalues) + local sector = actor:getBody():getSector() + local pos = {} + local next_pos = { unpack(fieldvalues['pos']) } -- Clone it! + local dir = fieldvalues['dir'] + local maxrange = fieldvalues['maxrange'] + local i = 0 + repeat + pos[1], pos[2] = unpack(next_pos) + next_pos[1], next_pos[2] = pos[1]+dir[1], pos[2]+dir[2] + i = i + 1 + until i > maxrange or not sector:isValid(unpack(next_pos)) + return pos +end + +return OP + diff --git a/game/domain/operators/integer_binop.lua b/game/domain/operators/integer_binop.lua new file mode 100644 index 00000000..20f2b413 --- /dev/null +++ b/game/domain/operators/integer_binop.lua @@ -0,0 +1,32 @@ +--Make a integer operation between two values + +local OP = {} + +OP.schema = { + { id = 'lhs', name = "Operand 1", type = 'value', match = 'integer' }, + { id = 'op', name = "Operator", type = 'enum', + options = {'+', '-', '*', '/'} }, + { id = 'rhs', name = "Operand 2", type = 'value', match = 'integer' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'integer' + +function OP.process(actor, fieldvalues) + if fieldvalues.op == "+" then + return fieldvalues.lhs + fieldvalues.rhs + elseif fieldvalues.op == "-" then + return fieldvalues.lhs - fieldvalues.rhs + elseif fieldvalues.op == "*" then + return fieldvalues.lhs * fieldvalues.rhs + elseif fieldvalues.op == "/" then + --Handle division by zero + assert(fieldvalues.rhs ~= 0, "Tried to divide by zero") + return math.floor(fieldvalues.lhs / fieldvalues.rhs) + end + +end + +OP.preview = OP.process + +return OP diff --git a/game/domain/operators/pos_difference.lua b/game/domain/operators/pos_difference.lua new file mode 100644 index 00000000..7eda1cca --- /dev/null +++ b/game/domain/operators/pos_difference.lua @@ -0,0 +1,21 @@ + +--- Get translated position from pos+dir + +local OP = {} + +OP.schema = { + { id = 'from', name = "Position From", type = 'value', match = 'pos' }, + { id = 'to', name = "Position To", type = 'value', match = 'pos' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'dir' + +function OP.process(actor, fieldvalues) + local from = fieldvalues['from'] + local to = fieldvalues['to'] + return { to[1]-from[1], to[2]-from[2] } +end + +return OP + diff --git a/game/domain/operators/self.lua b/game/domain/operators/self.lua new file mode 100644 index 00000000..19f24127 --- /dev/null +++ b/game/domain/operators/self.lua @@ -0,0 +1,19 @@ + +--- Get controlled actor + +local OP = {} + +OP.schema = { + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'actor' + +function OP.process(actor, fieldvalues) + return actor +end + +OP.preview = OP.process + +return OP + diff --git a/game/domain/operators/translated.lua b/game/domain/operators/translated.lua new file mode 100644 index 00000000..774b9fe2 --- /dev/null +++ b/game/domain/operators/translated.lua @@ -0,0 +1,21 @@ + +--- Get translated position from pos+dir + +local OP = {} + +OP.schema = { + { id = 'pos', name = "Position", type = 'value', match = 'pos' }, + { id = 'dir', name = "Translation", type = 'value', match = 'dir' }, + { id = 'output', name = "Label", type = 'output' } +} + +OP.type = 'pos' + +function OP.process(actor, fieldvalues) + local pos = fieldvalues['pos'] + local dir = fieldvalues['dir'] + return { pos[1]+dir[1], pos[2]+dir[2] } +end + +return OP + diff --git a/game/domain/pack.lua b/game/domain/pack.lua new file mode 100644 index 00000000..2d9dc90e --- /dev/null +++ b/game/domain/pack.lua @@ -0,0 +1,28 @@ + +local DB = require 'database' +local RANDOM = require 'common.random' +local CARDSET = require 'domain.cardset' + +local _EMPTY = {} + +local PACK = {} + +function PACK.generatePackFrom(collection_name) + if not collection_name then return _EMPTY end + local collection = DB.loadSpec('collection', collection_name) + local pack = {} + local n = 0 + while n == 0 do + for _,card_drop in pairs(collection.cards) do + local p = RANDOM.generate(1, 100) + if p <= card_drop.drop then + n = n + 1 + pack[n] = CARDSET.getRandomCardFrom(card_drop.set) + end + end + end + return pack +end + +return PACK + diff --git a/game/domain/route.lua b/game/domain/route.lua new file mode 100644 index 00000000..e3fa5f5b --- /dev/null +++ b/game/domain/route.lua @@ -0,0 +1,175 @@ + +local Route = require 'lux.class' :new{} + +local IDGenerator = require 'common.idgenerator' +local RANDOM = require 'common.random' +local PROFILE = require 'infra.profile' + +local BUILDERS = require 'lux.pack' 'domain.builders' +local PACK = require 'domain.pack' +local Body = require 'domain.body' +local Actor = require 'domain.actor' +local Sector = require 'domain.sector' +local Behaviors = require 'domain.behaviors' + +function Route:instance(obj) + + -- Saved data + local _id + local _id_generator = IDGenerator() + local _player_name = "Unknown" + local _player_id + local _sectors = {} + local _behaviors = Behaviors() + local _current_sector = nil + local _controlled_actor = nil + + Util.destroyAll 'true_force' + + function obj.loadState(state) + -- id + _id = state.id + _id_generator = IDGenerator(state.next_id) + + -- player + _player_name = state.player_name + _player_id = state.player_id + + -- rng + -- setState is theoretically enough to reproduce seed as well + RANDOM.setState(state.rng_state) + + -- sectors + _sectors = {} + for _,sector_state in ipairs(state.sectors) do + local sector = Sector(sector_state.specname, obj) + sector:loadState(sector_state) + table.insert(_sectors, sector) + end + + -- behaviors + _behaviors.load(state.behaviors) + + -- current sector + obj.setCurrentSector(state.current_sector_id) + end + + function obj.saveState() + local state = {} + -- id + state.id = _id + state.next_id = _id_generator.getNextID() + -- id + state.player_name = _player_name + state.player_id = _player_id + -- rng + state.rng_state = RANDOM.getState() + state.rng_seed = RANDOM.getSeed() + -- sectors + state.sectors = {} + for _,sector in ipairs(_sectors) do + local sector_state = sector:saveState() + table.insert(state.sectors, sector_state) + end + -- behaviors + state.behaviors = _behaviors.save() + -- current sector + state.current_sector_id = _current_sector.id + return state + end + + function obj.getPlayerName() + return _player_name + end + + function obj.getBehaviors() + return _behaviors + end + + function obj.setCurrentSector(id) + _current_sector = Util.findId(id) + end + + function obj.getCurrentSector() + return _current_sector + end + + function obj.getControlledActor() + return _controlled_actor + end + + --- Links an exit with the next sector over, generating it + -- @param from_sector The sector where to exit from + -- @param idx The exit index + -- @param exit The exit data + function obj.linkSectorExit(from_sector, target_sector_id, exit) + if not exit.target_pos then + local to_sector = Util.findId(target_sector_id) + if not to_sector:isGenerated() then + to_sector:generate() + end + local entry = to_sector:getExit(from_sector.id) + to_sector:link(from_sector.id, unpack(exit.pos)) + from_sector:link(target_sector_id, unpack(entry.pos)) + end + end + + function obj.makeCard(cardspec, owner_id) + local card = BUILDERS.card.buildElement(_id_generator, cardspec, owner_id) + return card + end + + function obj.makePack(collection, owner) + local speclist = PACK.generatePackFrom(collection) + local pack = {} + for i,cardspec in ipairs(speclist) do + pack[i] = obj.makeCard(cardspec, owner:getId()) + end + return pack + end + + function obj.makeBody(sector, bodyspec, i, j) + local body = BUILDERS.body.buildElement(_id_generator, bodyspec, i, j) + sector:putBody(body, i, j) + return body + end + + function obj.makeActor(sector, actorspec, bodyspec, i, j) + local b_state = BUILDERS.body.buildState(_id_generator, bodyspec, i, j) + local actor = BUILDERS.actor.buildElement(_id_generator, actorspec, b_state) + local body = Body(bodyspec) + body:loadState(b_state) + sector:putActor(actor, i, j) + return actor, body + end + + function obj.getPlayerActor() + return Util.findId(_player_id) + end + + function obj.checkSector() + local player_sector = obj.getPlayerActor():getBody():getSector() + if player_sector ~= _current_sector then + obj.setCurrentSector(player_sector.id) + return true + end + return false + end + + function obj.playTurns(...) + local request, extra = _current_sector:playTurns(...) + if request == 'userTurn' then + _controlled_actor = extra + end + return request, extra + end + + function obj.destroyAll() + Util.destroySubtype("actor", "force") + Util.destroySubtype("body", "force") + Util.destroySubtype("sector", "force") + end + +end + +return Route diff --git a/game/domain/sector.lua b/game/domain/sector.lua new file mode 100644 index 00000000..b1ca6dbc --- /dev/null +++ b/game/domain/sector.lua @@ -0,0 +1,515 @@ + +local DB = require 'database' +local DEFS = require 'domain.definitions' +local SCHEMATICS = require 'domain.definitions.schematics' +local TRANSFORMERS = require 'lux.pack' 'domain.transformers' +local COLORS = require 'domain.definitions.colors' +local RANDOM = require 'common.random' + +local Actor = require 'domain.actor' +local Body = require 'domain.body' + +local SectorGrid = require 'domain.transformers.helpers.sectorgrid' +local GameElement = require 'domain.gameelement' + +local Sector = Class { + __includes = { GameElement } +} + +local _turnLoop + +local function _initBodies(w, h) + local t = {} + for i = 0, h do + t[i] = {} + for j = 0, w do + t[i][j] = false + end + end + return t +end + +function Sector:init(spec_name, route) + + GameElement.init(self, 'sector', spec_name) + + self.w = 1 + self.h = 1 + + self.route = route + self.tiles = {{ false }} + self.generated = false + self.bodies = _initBodies(1,1) + self.actors = {} + self.exits = {} + self.actors_queue = {} + + self.turnLoop = coroutine.create(_turnLoop) + +end + +function Sector:loadState(state) + self:setId(state.id or self.id) + self:setSubtype(self.spectype) + self.exits = state.exits + self.zone = GameElement('zone', state.zone) + self.generated = state.generated + if state.generated then + self.tiles = state.tiles + self.w = state.w or self.w + self.h = state.h or self.h + self.bodies = _initBodies(self.w, self.h) + local bodies = {} + for _,body_state in ipairs(state.bodies) do + local body = Body(body_state.specname) + body:loadState(body_state) + bodies[body.id] = body_state + end + for _,actor_state in ipairs(state.actors) do + local actor = Actor(actor_state.specname) + actor:loadState(actor_state) + local body_id = actor.body_id + local body_state = bodies[body_id] + local i, j = body_state.i, body_state.j + bodies[body_id] = nil + self:putActor(actor, i, j) + end + for id, body_state in pairs(bodies) do + local i, j = body_state.i, body_state.j + local body = Util.findId(id) + self:putBody(body, i, j) + end + end +end + +function Sector:saveState() + local state = {} + state.id = self:getId() + state.specname = self.specname + state.zone = self.zone:getSpecName() + state.exits = self.exits + state.generated = self.generated + state.tiles = self.tiles + state.w = self.w + state.h = self.h + state.actors = {} + state.bodies = {} + for _,actor in ipairs(self.actors) do + local actor_state = actor:saveState() + table.insert(state.actors, actor_state) + end + for body, body_pos in pairs(self.bodies) do + if not tonumber(body) then + local i, j = body_pos[1], body_pos[2] + local body_state = body:saveState() + body_state.i = i + body_state.j = j + table.insert(state.bodies, body_state) + end + end + return state +end + +function Sector:getRoute() + return self.route +end + +function Sector:getDimensions() + return self.w, self.h +end + +function Sector:getZone() + return self.zone +end + +function Sector:getTheme() + return DB.loadSpec('theme', self:getZone():getSpec('theme')) +end + +function Sector:getTileSet() + return self:getTheme()['tileset'] +end + +function Sector:getZoneName() + return self:getZone():getSpec('name') +end + +function Sector:getDifficulty() + return self:getZone():getSpec('difficulty') +end + +function Sector:isGenerated() + return self.generated +end + +function Sector:generate() + + -- load sector's specs + local base = { + exits = self.exits + } + + -- sector grid generation + for _,transformer in DB.schemaFor('sector') do + local spec = self:getSpec(transformer.id) + if spec then + base = TRANSFORMERS[transformer.id].process(base, spec) + end + end + + self:makeTiles(base.grid, base.drops) + self:makeEncounters(base.encounters) + + self.generated = true +end + +function Sector:getTile(i, j) + return self.tiles[i][j] +end + +function Sector:makeTiles(grid, drops) + self.w, self.h = grid.getDim() + for i = 1, self.h do + self.tiles[i] = {} + self.bodies[i] = {} + for j = 1, self.w do + local tile = false + local tile_type = grid.get(j, i) + if tile_type and tile_type ~= SCHEMATICS.NAUGHT then + tile = { type = tile_type, drops = {} } + for _,drop in ipairs(drops[i][j]) do + table.insert(tile.drops, drop) + end + end + self.tiles[i][j] = tile + self.bodies[i][j] = false + end + end +end + +function Sector:makeEncounters(encounters) + for _,encounter in ipairs(encounters) do + local actorspec, bodyspec = unpack(encounter.monster) + local i, j = unpack(encounter.pos) + local actor, body = self.route.makeActor(self, actorspec, bodyspec, i, j) + local difficulty_multiplier = 1 + self:getDifficulty() + local upgradexp = encounter.upgrade_power + + upgradexp = math.floor(upgradexp * difficulty_multiplier) + + -- allocating exp + if upgradexp > 0 then + local unit, total = 0, 0 + local aptitudes = {} + for _,attr in ipairs(DEFS.PRIMARY_ATTRIBUTES) do + aptitudes[attr] = actor:getSpec(attr:lower()) + 3 -- min of 1 + total = total + aptitudes[attr] + end + unit = upgradexp / total + for attr,priority in pairs(aptitudes) do + local award = math.floor(unit * priority) + if DEFS.PRIMARY_ATTRIBUTES[attr] then + actor:upgradeAttr(attr, award) + end + end + end + end +end + +--- Returns the exit with the given index +-- @param idx The exit index (must be valid) +-- @param generate Flag indicating whether to generate the next sector over +-- or not. +function Sector:getExit(id, generate) + local exit = self.exits[id] + assert(exit, + ("No such exit: %s"):format(id)) + local result = { + pos = exit.pos, + target_pos = exit.target_pos + } + if not exit.target_pos and generate then + self.route.linkSectorExit(self, id, result) + result.target_pos = exit.target_pos + end + return result +end + +--- Finds the exit at [i,j], if any +-- @param i The i-position of the possible exit +-- @param j The j-position of the possible exit +-- @param generate A flag passed on to Sector:getExit() +-- @return[1] The target sector's id +-- @return[2] The corresponding result of Sector:getExit +function Sector:findExit(i, j, generate) + for id, exit in pairs(self.exits) do + local di, dj = unpack(exit.pos) + if di == i and dj == j then + return id, self:getExit(id, generate) + end + end + return false +end + +function Sector:link(id, i, j) + local exit = self.exits[id] + exit.target_pos = {i, j} +end + +--- Puts body at position (i.j), removing it from where it was before, wherever +-- that is! +function Sector:putBody(body, i, j) + assert(self:isValid(i,j), + ("Invalid position (%d,%d):"):format(i,j)) + -- Remove body from where it was vefore + local oldsector = body:getSector() or self + local oldbodies = oldsector.bodies + local pos = oldsector.bodies[body] or {0,0} + oldbodies[pos[1]][pos[2]] = false + if self ~= oldsector then + oldbodies[body] = nil + end + -- Actually put body at (i,j) in this sector + local bodies = self.bodies + body:setSector(self.id) + bodies[body] = pos + bodies[i][j] = body + pos[1], pos[2] = i, j +end + +function Sector:getBodyAt(i, j) + return self:isInside(i,j) and self.bodies[i][j] or nil +end + +--- Removes the body at given position if it exists. +-- @return The associated actor if any +function Sector:removeBodyAt(i, j, body) + + local removed_actor + + --Checks if this body has an actor + for i, actor in ipairs(self.actors) do + if actor:getBody() == body then + + if actor:isPlayer() then + coroutine.yield("playerDead") + end + + removed_actor = table.remove(self.actors, i) + + break + end + end + + --Remove body from the sector + self.bodies[i][j] = false + self.bodies[body] = nil + body:kill() + + return removed_actor + +end + +--- Remove all bodies with <=0 hp on the map +-- @return A table containing all removed actors +function Sector:removeDeadBodies() + local dead_actor_list = {} + local drop_points = {} + + for i = 1, self.h do + for j = 1, self.w do + + local body = self:getBodyAt(i,j) + + if body and body:getHP() <= 0 then + local drops_table = {} + local drops = body:getDrops() + for _,drop in ipairs(drops) do + if RANDOM.generate(101)-1 < drop["droprate"] then + table.insert(drops_table, drop["droptype"]) + end + end + + local actor = self:removeBodyAt(i,j, body) + if actor then + table.insert(dead_actor_list, actor) + end + table.insert(drop_points, {i, j, drops_table}) + end + + end + end + + return dead_actor_list, drop_points +end + +function Sector:putActor(actor, i, j) + local body = actor:getBody() + local oldsector = body:getSector() + if oldsector and oldsector ~= self then + oldsector:removeActor(actor) + end + self:putBody(body, i, j) + actor:purgeFov(self) --Update sector fov map to current sector dimensions + return table.insert(self.actors, actor) +end + +function Sector:removeActor(removed_actor) + local idx + for i, actor in ipairs(self.actors) do + if actor == removed_actor then idx = i break end + end + table.remove(self.actors, idx) + for i, actor in ipairs(self.actors_queue) do + if actor == removed_actor then idx = i break end + end + table.remove(self.actors_queue, idx) +end + +function Sector:getBodyPos(body) + return unpack(self.bodies[body]) +end + +function Sector:iterateActors() + return ipairs(self.actors) +end + +function Sector:getActorFromBody(body) + for _,actor in self:iterateActors() do + if actor:getBody() == body then + return actor + end + end +end + +function Sector:getActorPos(actor) + return self:getBodyPos(actor:getBody()) +end + +function Sector:isInside(i, j) + return (i >= 1 and i <= self.h) and + (j >= 1 and j <= self.w) +end + +function Sector:isWalkable(i, j) + return self:isInside(i,j) and + (self.tiles[i][j] and self.tiles[i][j].type ~= SCHEMATICS.WALL) +end + +function Sector:isValid(i, j) + return self:isWalkable(i, j) and not self.bodies[i][j] +end + +function Sector:randomValidTile() + local rand = RANDOM.generate + local i, j + repeat + i, j = rand(self.h), rand(self.w) + until self:isValid(i, j) + return i, j +end + +function Sector:randomNeighbor(i, j, allow_bodies) + local rand = RANDOM.generate + repeat + local di, dj = rand(-1, 1), rand(-1, 1) + i = math.max(1, math.min(self.h, i+di)) + j = math.max(1, math.min(self.w, j+dj)) + until not (di == dj and di == 0) and + allow_bodies or not self.bodies[i][j] + return i, j +end + +--- Check for dead bodies if any, and remove associated actors from the queue. +local function manageDeadBodiesAndUpdateActorsQueue(sector, actors_queue) + local dead_actor_list, drop_points = sector:removeDeadBodies() + for _, dead_actor in ipairs(dead_actor_list) do + for i, act in ipairs(actors_queue) do + if dead_actor == act then + table.remove(actors_queue, i) + break + end + end + sector:getRoute().getBehaviors().removeAI(dead_actor) + dead_actor:kill() + end + for _,drop_point in ipairs(drop_points) do + sector:spreadDrops(unpack(drop_point)) + end +end + +function Sector:spreadDrops(i, j, drops) + local ti, tj + local tile + local new_drops = {} + for _,drop in ipairs(drops) do + repeat + ti, tj = self:randomNeighbor(i, j, true) + until self:isWalkable(ti, tj) + tile = self.tiles[ti][tj] + table.insert(tile.drops, drop) + table.insert(new_drops, {ti, tj, #tile.drops}) + end + coroutine.yield('report', { + type = 'drop_spread', + drops = new_drops, + origin = {i, j} + }) + +end + +function _turnLoop(self, ...) + local actors_queue = self.actors_queue + while true do + + for body in pairs(self.bodies) do + if type(body) == 'table' then + body:tick() + end + end + + --Initialize actor queue + for _,actor in ipairs(self.actors) do + actor:tick() + actor:updateFov(self) + table.insert(actors_queue,actor) + end + + manageDeadBodiesAndUpdateActorsQueue(self, actors_queue) + + while not Util.tableEmpty(actors_queue) do + local actor = table.remove(actors_queue) + + if actor:ready() then + while actor:ready() do + actor:grabDrops(self:getTile(actor:getPos())) + actor:makeAction() + manageDeadBodiesAndUpdateActorsQueue(self, actors_queue) + end + actor:turn() + end + + if actor:isPlayer() and actor:getBody():getSector() ~= self then + coroutine.yield('changeSector') + break + end + end + + end +end + +--- Plays turn coroutine. +-- Any erros in it are propagated with the appropriate stacktrace. +function Sector:playTurns(...) + local result = table.pack(coroutine.resume(self.turnLoop, self, ...)) + local ok, err = unpack(result) + if not ok then + return error(debug.traceback(self.turnLoop, err)) + else + return unpack(result, 2) + end +end + + + +return Sector diff --git a/game/domain/transformers/altars.lua b/game/domain/transformers/altars.lua new file mode 100644 index 00000000..b7904d64 --- /dev/null +++ b/game/domain/transformers/altars.lua @@ -0,0 +1,62 @@ + +local RANDOM = require 'common.random' +local SCHEMATICS = require 'domain.definitions.schematics' + +local transformer = {} + +transformer.schema = { + { id = 'threshold', name = "Threshold spacing", type = 'integer', + range = {0} }, + { id = 'min', name = "Minimum number of altars", type = 'integer', + range = {0} }, + { id = 'max', name = "Maximum number of altars", type = 'integer', + range = {0} }, +} + +local FLOOR_THRESHOLD = 1 + +local function _canPlaceAltar(grid, x, y) + local f = SCHEMATICS.FLOOR + for dx = -FLOOR_THRESHOLD, FLOOR_THRESHOLD do + for dy = -FLOOR_THRESHOLD, FLOOR_THRESHOLD do + local tx, ty = dx + x, dy + y + local tile = grid.get(tx, ty) + -- verify it's a position surrounded by floors + if tile ~= f then return false end + end + end + return true +end + +function transformer.process(sectorinfo, params) + local sectorgrid = sectorinfo.grid + local altars_min, altars_max = params.min, params.max + + local possible_altars = {} + + FLOOR_THRESHOLD = params.threshold or FLOOR_THRESHOLD + + -- construct list of possible altars + do + for x, y, tile in sectorgrid.iterate() do + if _canPlaceAltar(sectorgrid, x, y) then + table.insert(possible_altars, {y, x}) + end + end + end + + local number_altars = RANDOM.generate(altars_min, altars_max) + + for i = 1, number_altars do + local size = #possible_altars + if size <= 0 then break end + local idx = RANDOM.generate(1,size) + local pos = table.remove(possible_altars, idx) + sectorgrid.set(pos[2], pos[1], SCHEMATICS.ALTAR) + end + + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/bootstrap.lua b/game/domain/transformers/bootstrap.lua new file mode 100644 index 00000000..418f38f5 --- /dev/null +++ b/game/domain/transformers/bootstrap.lua @@ -0,0 +1,24 @@ + +local SectorGrid = require 'domain.transformers.helpers.sectorgrid' + +local transformer = {} + +transformer.schema = { + { id = 'w', name = "Width", type = 'integer', range = {1} }, + { id = 'h', name = "Height", type = 'integer', range = {1} }, + { id = 'mw', name = "Horizontal Margin", type = 'integer', range = {1} }, + { id = 'mh', name = "Vertical Margin", type = 'integer', range = {1} }, +} + +function transformer.process(sectorinfo, params) + local _w = params.w + local _h = params.h + local _mw = params.mw + local _mh = params.mh + + sectorinfo.grid = SectorGrid(_w, _h, _mw, _mh) + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/connections.lua b/game/domain/transformers/connections.lua new file mode 100644 index 00000000..5250f185 --- /dev/null +++ b/game/domain/transformers/connections.lua @@ -0,0 +1,173 @@ + +-- dependencies +local SCHEMATICS = require 'domain.definitions.schematics' +local RANDOM = require 'common.random' +local Rectangle = require 'common.rect' +local UnionFind = require 'common.unionfind' +local Vector2 = require 'cpml.modules.vec2' + +local transformer = {} + +transformer.schema = { + { id = 'loops', name = "Num Connections", type = 'integer', + range = { 1, 1024 } } +} + +function transformer.process(sectorinfo, params) + local _sectorgrid = sectorinfo.grid + local _width, _height = _sectorgrid.getDim() + local _mw, _mh = _sectorgrid.getMargins() + + -- valid positions + local _minx = _mw + 1 + local _miny = _mh + 1 + local _maxx = _width - _mw + local _maxy = _height - _mh + + -- lists and sets + local _loops = params.loops + local _flooded = {} + local _connectors = {} + local _cardinals = { + Vector2( 1, 0), + Vector2( 0, 1), + Vector2(-1, 0), + Vector2( 0, -1) + } + + + local function getId(x, y) + return _width * (y - 1) + (x - 1) + end + + local function connectTwoRegions(r1, r2, p) + local union = UnionFind(getId(p.x, p.y)) + local id1 = r1.getElement() + local id2 = r2.getElement() + union = UnionFind:unite(union, r1) + union = UnionFind:unite(union, r2) + _flooded[union.getElement()] = union + _sectorgrid.set(p.x, p.y, SCHEMATICS.FLOOR) + end + + local function getFloorNeighbours(x, y) + local FLOOR = SCHEMATICS.FLOOR + local insert = table.insert + local neighbours = {} + local pos = Vector2(x, y) + for i, dir in ipairs(_cardinals) do + local p = pos + dir + if _sectorgrid.get(p.x, p.y) == FLOOR then + insert(neighbours, p) + end + end + return neighbours + end + + local function floodOneRegion(x, y) + local id = getId(x, y) + local region + + -- recursion base + if _flooded[id] then + return _flooded[id] + end + + -- general case + region = UnionFind(id) + _flooded[id] = region + local sides = getFloorNeighbours(x, y) + for _, side in ipairs(sides) do + local sx, sy = side.x, side.y + region = UnionFind:unite(region, floodOneRegion(sx, sy)) + end + + return region + end + + local function checkIfConnection(x, y) + local insert = table.insert + local sides = getFloorNeighbours(x, y) + if #sides == 2 then + if sides[1]:dist2(sides[2]) == 4 then + local id1 = getId(sides[1].x, sides[1].y) + local id2 = getId(sides[2].x, sides[2].y) + local c = { Vector2(x, y), { id1, id2 } } + insert(_connectors, c) + end + end + end + + local function floodRegions() + local FLOOR = SCHEMATICS.FLOOR + for x = _minx, _maxx do + for y = _miny, _maxy do + local id = getId(x, y) + if _sectorgrid.get(x, y) == FLOOR then + if not _flooded[id] then + floodOneRegion(x, y) + end + else + checkIfConnection(x, y) + end + end + end + end + + local function countRegions() + local counted = {} + local count = 0 + for id, region in pairs(_flooded) do + local k = region.find() + if not counted[k] then + count = count + 1 + counted[k] = true + end + end + return count + end + + local function connectAllRegions() + local insert = table.insert + local copy_connectors = {} + local N = #_connectors + while countRegions() > 1 and N > 0 do + local k, c, r1, r2 + repeat + k = N > 1 and RANDOM.generate(1, N) or 1 + c = _connectors[k] + r1 = _flooded[c[2][1]].find() + r2 = _flooded[c[2][2]].find() + _connectors[k] = _connectors[N] + _connectors[N] = nil + N = N - 1 + if r1 == r2 then insert(copy_connectors, c) end + until r1 ~= r2 or N == 0 + connectTwoRegions(r1, r2, c[1]) + end + return copy_connectors + end + + local function makeLoops(connectors) + local N = #connectors + while N > 0 do + if _loops <= 0 then break end + local k = N > 1 and RANDOM.generate(1, N) or 1 + local c = connectors[k] + local r1 = _flooded[c[2][1]].find() + local r2 = _flooded[c[2][2]].find() + connectors[k] = connectors[N] + connectors[N] = nil + _loops = _loops - 1 + N = N - 1 + connectTwoRegions(r1, r2, c[1]) + end + end + + floodRegions() + makeLoops(connectAllRegions()) + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/deadends.lua b/game/domain/transformers/deadends.lua new file mode 100644 index 00000000..34b7c0da --- /dev/null +++ b/game/domain/transformers/deadends.lua @@ -0,0 +1,103 @@ + +-- dependencies +local SCHEMATICS = require 'domain.definitions.schematics' +local RANDOM = require 'common.random' +local Vector2 = require 'cpml.modules.vec2' + +local transformer = {} + +transformer.schema = { + { id = 'amount', name = "Total deadends", type = 'integer', + range = { 0, 1024 } } +} + +function transformer.process(sectorinfo, params) + local _sectorgrid = sectorinfo.grid + local _width, _height = _sectorgrid.getDim() + local _mw, _mh = _sectorgrid.getMargins() + + local _minx = _mw + 1 + local _miny = _mh + 1 + local _maxx = _width - _mw + local _maxy = _height - _mh + + local _corners = {} + local _n = params.amount + local _cardinals = { + Vector2( 1, 0), + Vector2( 0, 1), + Vector2(-1, 0), + Vector2( 0, -1) + } + + local function isCorner(point) + local FLOOR = SCHEMATICS.FLOOR + local notwall = false + local count = 0 + for i, dir in ipairs(_cardinals) do + local pos = point + dir + if _sectorgrid.get(pos.x, pos.y) ~= FLOOR then + count = count + 1 + else + notwall = pos + end + end + if count == 3 then + return notwall + else + return false + end + end + + local function cleanCorner(corner) + local WALL = SCHEMATICS.WALL + while corner and isCorner(corner) do + _sectorgrid.set(corner.x, corner.y, WALL) + corner = isCorner(corner) + end + end + + local function getDeadEnds() + local FLOOR = SCHEMATICS.FLOOR + local insert = table.insert + for x = _minx, _maxx do + for y = _miny, _maxy do + local p = Vector2(x, y) + if _sectorgrid.get(x, y) == FLOOR then + if isCorner(p) then + insert(_corners, p) + end + end + end + end + end + + local function removeDeadEnds() + local FLOOR = SCHEMATICS.FLOOR + local corner + local len = #_corners + if len == 0 then return false end + while len > 0 do + local k = RANDOM.generate(1, len) + + corner = _corners[k] + _corners[k] = _corners[len] + _corners[len] = nil + len = #_corners + if isCorner(corner) then + cleanCorner(corner) + end + end + return true + end + + for i = 1, _n do + getDeadEnds() + if not removeDeadEnds() then break end + end + + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/drops.lua b/game/domain/transformers/drops.lua new file mode 100644 index 00000000..ae39d451 --- /dev/null +++ b/game/domain/transformers/drops.lua @@ -0,0 +1,47 @@ + +local DB = require 'database' +local RANDOM = require 'common.random' +local SCHEMATICS = require 'domain.definitions.schematics' + +local _RATE_MAX = 256 + +local transformer = {} + +transformer.schema = { + { id = 'drop-info', type = 'description', + info = ("Chance of drop in a given " + .. "tile is [Drop Rate] / %d"):format(_RATE_MAX) + }, + { id = 'drops', name = 'Drop Spec', type = 'array', + schema = { + { id = 'droptype', name = "Drop Type", type = 'enum', + options = 'domains.drop' }, + { id = 'droprate', name = "Drop Rate", type = 'integer', + range = {0, _RATE_MAX}, }, + }, + }, +} + +function transformer.process(sectorinfo, params) + local grid = sectorinfo.grid + + local drops = {} + for _,dropspec in ipairs(params.drops) do + local droprate = dropspec.droprate + local droptype = dropspec.droptype + for j, i, tile in grid.iterate() do + drops[i] = drops[i] or {} + drops[i][j] = drops[i][j] or {} + if tile == SCHEMATICS.FLOOR and + RANDOM.generate(_RATE_MAX) <= droprate then + table.insert(drops[i][j], droptype) + end + end + end + + sectorinfo.drops = drops + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/encounters.lua b/game/domain/transformers/encounters.lua new file mode 100644 index 00000000..10af503a --- /dev/null +++ b/game/domain/transformers/encounters.lua @@ -0,0 +1,56 @@ + +local RANDOM = require 'common.random' +local SCHEMATICS = require 'domain.definitions.schematics' + +local transformer = {} + +transformer.schema = { + { id = 'min', name = "Minimum number of encounters", type = 'integer', + range = {1} }, + { id = 'max', name = "Maximum number of encounters", type = 'integer', + range = {1} }, + { id = 'recipes', name = "Encounter recipe", type = 'array', + schema = { + { id = 'actorspec', name = "Actor Specification", type = 'enum', + options = 'domains.actor' }, + { id = 'bodyspec', name = "Body Specification", type = 'enum', + options = 'domains.body' }, + { id = 'upgrade_power', name = "Upgrade Power", type = 'integer', + range = {10,1000} }, + } } +} + +local function _hash(i,j) + return ("%s:%s"):format(i,j) +end + +function transformer.process(sectorinfo, params) + local grid = sectorinfo.grid + local recipes = params.recipes + local encounters = {} + local total = RANDOM.generate(params.min, params.max) + local used = {} + + for i=1,total do + local encounter = {} + local recipe = recipes[RANDOM.generate(1,#recipes)] + local upgrade_power = math.floor(0.8 + 0.4*RANDOM.generate() + * recipe.upgrade_power) + encounter.upgrade_power = upgrade_power + encounter.monster = { recipe.actorspec, recipe.bodyspec } + local minj, maxj, mini, maxi = grid.getRange() + local i, j + repeat + i = RANDOM.generate(mini, maxi) + j = RANDOM.generate(minj, maxj) + until grid.get(j,i) == SCHEMATICS.FLOOR and not used[_hash(i,j)] + encounter.pos = {i,j} + used[_hash(i,j)] = true + table.insert(encounters, encounter) + end + sectorinfo.encounters = encounters + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/exits.lua b/game/domain/transformers/exits.lua new file mode 100644 index 00000000..e5666756 --- /dev/null +++ b/game/domain/transformers/exits.lua @@ -0,0 +1,99 @@ + +local RANDOM = require 'common.random' +local SCHEMATICS = require 'domain.definitions.schematics' + +local transformer = {} + +transformer.schema = { + { id = 'threshold', name = "Threshold spacing", type = 'integer', + range = {0} }, + { id = 'distance', name = "Distance between exits", type = 'integer', + range = {1} }, +} + +local FLOOR_THRESHOLD = 1 +local EXIT_THRESHOLD = 1 + +local function _hasSpaceForExit(grid, x, y) + local f = SCHEMATICS.FLOOR + for dx = -FLOOR_THRESHOLD, FLOOR_THRESHOLD do + for dy = -FLOOR_THRESHOLD, FLOOR_THRESHOLD do + local tx, ty = dx + x, dy + y + local tile = grid.get(tx, ty) + -- verify it's a position surrounded by floors and not a single exit + if tile ~= f then return false end + end + end + return true +end + +local function _hasNoExitNearby(grid, x, y) + local e = SCHEMATICS.EXIT + for dx = -EXIT_THRESHOLD, EXIT_THRESHOLD, 1 do + for dy = -EXIT_THRESHOLD, EXIT_THRESHOLD, 1 do + local tx, ty = dx + x, dy + y + local tile = grid.get(tx, ty) + -- verify it's a position surrounded by floors and not a single exit + if tile == e then return false end + end + end + return true +end + +local function _isPossibleExit(grid, x, y) + return _hasSpaceForExit(grid, x, y) and _hasNoExitNearby(grid, x, y) +end + +function transformer.process(sectorinfo, params) + local sectorgrid = sectorinfo.grid + local exits_specs = sectorinfo.exits + + local possible_exits = {} + + FLOOR_THRESHOLD = params.threshold or FLOOR_THRESHOLD + EXIT_THRESHOLD = params.distance or EXIT_THRESHOLD + + -- construct list of possible exits + do + for x, y, tile in sectorgrid.iterate() do + if _isPossibleExit(sectorgrid, x, y) then + table.insert(possible_exits, {y, x}) + end + end + end + + -- get a number of random possible exits from that list + do + for id, exit in pairs(exits_specs) do + local i, j + repeat + local COUNT = #possible_exits + if COUNT == 1 then + -- if there is only one last possible exit, check it: + i, j = unpack(possible_exits[1]) + -- if it's not a good position, tough luck, break it up + if not _isPossibleExit(sectorgrid, j, i) then + return error("Not enough possible exits. Invalid sector.") + end + else + -- if there are many possible exits, get a random one: + local idx = RANDOM.generate(1, COUNT) + i, j = unpack(possible_exits[idx]) + -- remove found position from list of possible exits + table.remove(possible_exits, idx) + end + -- repeat until you find a position that: + -- > is not an exit or around another exit + until _isPossibleExit(sectorgrid, j, i) + exit.pos = {i, j} + -- add exit info to sectorinfo + -- and set and exit tile on the sectorgrid + sectorgrid.set(j, i, SCHEMATICS.EXIT) + end + end + + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/fillings.lua b/game/domain/transformers/fillings.lua new file mode 100644 index 00000000..3f38645a --- /dev/null +++ b/game/domain/transformers/fillings.lua @@ -0,0 +1,51 @@ + +local SCHEMATICS = require 'domain.definitions.schematics' + +local transformer = {} + +transformer.schema = {} + +function transformer.process(sectorinfo, params) + local sectorgrid = sectorinfo.grid + local potentials, n + + local function _findPotentials(tile_type) + potentials = potentials or {} + n = 0 + for x, y, tile in sectorgrid.iterate() do + if sectorgrid.isInsideMargins(x, y) + and sectorgrid.get(x, y) == tile_type then + local count = 0 + for j = x-1, x+1 do + for i = y-1, y+1 do + if not (j == x and y == i) then + if sectorgrid.get(j, i) == tile_type then + count = count + 1 + end + end + end + end + if count <= 1 then + n = n + 1 + potentials[n] = {x, y} + end + end + end + end + + repeat + _findPotentials(SCHEMATICS.NAUGHT) + _findPotentials(SCHEMATICS.WALL) + if n > 0 then + for idx = 1, n do + local x, y = unpack(potentials[idx]) + sectorgrid.set(x, y, SCHEMATICS.FLOOR) + end + end + until n == 0 + + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/helpers/sectorgrid.lua b/game/domain/transformers/helpers/sectorgrid.lua new file mode 100644 index 00000000..73beb309 --- /dev/null +++ b/game/domain/transformers/helpers/sectorgrid.lua @@ -0,0 +1,100 @@ + +local SCHEMATICS = require 'domain.definitions.schematics' + +local SectorGrid = require 'lux.class' :new{} + +function SectorGrid:instance(obj, w, h, mw, mh) + local _w, _h = w, h + local _mw, _mh = mw, mh + local _grid = {} + + -- fill sector + for i = 1, h do + _grid[i] = {} + for j = 1, w do + if (j > mw and j <= w - mw) and + (i > mh and i <= h - mh) then + _grid[i][j] = SCHEMATICS.WALL + else + _grid[i][j] = SCHEMATICS.NAUGHT + end + end + end + + function obj.getDim() + return _w, _h + end + + function obj.getRange() + return _mw, _w - _mw, _mh, _h - _mh + end + + function obj.isInside(x, y) + return x >= 1 and x <= _w and y >= 1 and y <= _h + end + + function obj.getMargins() + return _mw, _mh + end + + function obj.isInsideMargins(x, y) + return y > _mh and y <= _h - _mh and x > _mw and x <= _w - _mw + end + + function obj.set(x, y, fill) + local e_str = "("..tostring(x)..", "..tostring(y)..")" + assert(obj.get(x, y), "Out of range: " .. e_str) + _grid[y][x] = fill + end + + function obj.get(x, y) + return _grid[y] and _grid[y][x] + end + + function obj.iterate () + local init_s = { 1, 0, tbl = _grid } + return function(s, value) + local m = s.tbl + + s[2] = s[2] + 1 + i, j = s[1], s[2] + value = m[i] and m[i][j] + + if not value then + s[1] = s[1] + 1 + s[2] = 1 + i, j = s[1], s[2] + value = m[i] and m[i][j] + end + + return value and j, i, value + end, + init_s, + 0 + end + + function obj.__operator:tostring() + local s = "" + for y = 1, _h do + for x = 1, _w do + s = s .. " " .. obj.get(x, y) + end + s = s .. "\n" + end + return s + end + +end + +function SectorGrid:copy(from) + local w, h = from.getDim() + local mw, mh = from.getMargins() + local newgrid = SectorGrid(w, h, mw, mh) + for x, y, tile in from.iterate() do + newgrid.set(x, y, tile) + end + return newgrid +end + +return SectorGrid + diff --git a/game/domain/transformers/holes.lua b/game/domain/transformers/holes.lua new file mode 100644 index 00000000..95db7fef --- /dev/null +++ b/game/domain/transformers/holes.lua @@ -0,0 +1,66 @@ + +local RANDOM = require 'common.random' +local SectorGrid = require 'domain.transformers.helpers.sectorgrid' +local SCHEMATICS = require 'domain.definitions.schematics' +local DIR = require 'domain.definitions.dir' + +local transformer = {} + +transformer.schema = { + { id = 'count', name = "Initial", type = 'integer', range = {1} }, + { id = 'iter', name = "Iterations", type = 'integer', range = {1} }, +} + +function transformer.process(sectorinfo, params) + local from_grid = sectorinfo.grid + local to_grid = SectorGrid:copy(from_grid) + + local w, h = from_grid.getDim() + local mw, mh = from_grid.getMargins() + local min_x, min_y = mw + 1, mh + 1 + local max_x, max_y = w - min_x, h - min_y + + do + for n = 1, params.count do + local x, y + repeat + x = RANDOM.generate(min_x, max_x) + y = RANDOM.generate(min_y, max_y) + until from_grid.get(x, y) == SCHEMATICS.WALL + to_grid.set(x, y, SCHEMATICS.NAUGHT) + end + end + from_grid, to_grid = to_grid, from_grid + + -- iterations + do + for n = 1, params.iter do + for x, y, tile in from_grid.iterate() do + local count = 0 + for _,dir in ipairs(DIR) do + local dx, dy = unpack(DIR[dir]) + local nx, ny = x+dx, y+dy + if (x ~= ny or y ~= ny) then + if from_grid.get(nx, ny) == SCHEMATICS.NAUGHT then + count = count + 1 + end + end + end + if count > 3 and tile == SCHEMATICS.WALL then + if RANDOM.generate() > .5 then + to_grid.set(x, y, SCHEMATICS.NAUGHT) + end + else + to_grid.set(x, y, tile) + end + end + to_grid, from_grid = from_grid, to_grid + end + end + + sectorinfo.grid = to_grid + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/maze.lua b/game/domain/transformers/maze.lua new file mode 100644 index 00000000..836543a8 --- /dev/null +++ b/game/domain/transformers/maze.lua @@ -0,0 +1,166 @@ + +-- dependencies +local SCHEMATICS = require 'domain.definitions.schematics' +local RANDOM = require 'common.random' +local Vector2 = require 'cpml.modules.vec2' + +local transformer = {} + +transformer.schema = { + { id = 'double', name = "Double Step", type = "boolean" } +} + +function transformer.process(sectorinfo, params) + local _sectorgrid = sectorinfo.grid + local _width, _height = _sectorgrid.getDim() + local _mw, _mh = _sectorgrid.getMargins() + + -- corridor positions + local _minx = _mw + 1 + local _miny = _mh + 1 + local _maxx = _width - _mw + local _maxy = _height - _mh + + -- params + local _dist = 2 * (params.double and 2 or 1) + local _cardinals = { + Vector2( 1, 0) * _dist, + Vector2( 0, 1) * _dist, + Vector2(-1, 0) * _dist, + Vector2( 0, -1) * _dist, + } + + local _potentials = {} -- { point, direction } + local _maze_scheme = {} -- { point, direction } + local _possible_starts = {} + local _start + + local function isPointInScheme(point) + for _, p in ipairs(_maze_scheme) do + if p[1] + p[2] == point then return true end + end + return false + end + + local function isValidPoint(point) + local FLOOR = SCHEMATICS.FLOOR + local x, y = point.x, point.y + return _sectorgrid.isInsideMargins(x, y) + and _sectorgrid.get(x, y) ~= SCHEMATICS.FLOOR + and not isPointInScheme(point) + end + + local function getAllPossibleStartPoints() + local insert = table.insert + local mx = _minx % 2 == 1 and _minx or _minx + 1 + local my = _miny % 2 == 1 and _miny or _miny + 1 + for x = mx, _maxx, 2 do + for y = my, _maxy, 2 do + local p = Vector2(x, y) + if isValidPoint(p) then + insert(_possible_starts, p) + end + end + end + end + + local function removePossibleStart(k) + local N = #_possible_starts + local possible_start = _possible_starts[k] + _possible_starts[k] = _possible_starts[N] + _possible_starts[N] = nil + return possible_start + end + + local function getPossibleStart(p) + local N = #_possible_starts + for k = 1, N do + if _possible_starts[k] == p then + return k + end + end + end + + local function setStartPoint() + local N = #_possible_starts + local k = RANDOM.generate(1, N) + _start = removePossibleStart(k) + end + + local function addPotentialMovements() + local insert = table.insert + for _, dir in ipairs(_cardinals) do + local newpoint = _start + dir + if isValidPoint(newpoint) then + insert(_potentials, { _start, dir }) + end + end + end + + local function getPotentialMovement() + local N = #_potentials + local movement + local k + + if N == 0 then return false end + + local function removeFromPotentials(i) + _potentials[i] = _potentials[N] + _potentials[N] = nil + N = #_potentials + end + + repeat + k = N > 1 and RANDOM.generate(N) or 1 + movement = _potentials[k] + if not isValidPoint(movement[1] + movement[2]) then + movement = false + end + removeFromPotentials(k) + until movement or N <= 0 + + return movement + end + + local function generateMaze() + local insert = table.insert + local moveable + repeat + addPotentialMovements() + moveable = getPotentialMovement() + if moveable then + _start = moveable[1] + moveable[2] + insert(_maze_scheme, moveable) + local k = getPossibleStart(_start) + if k then removePossibleStart(k) end + end + until not moveable or #_potentials == 0 + end + + local function caveMaze() + local FLOOR = SCHEMATICS.FLOOR + local abs = math.abs + for _, movement in ipairs(_maze_scheme) do + local pos1, pos2 = movement[1], movement[1] + movement[2] + local dx = (pos2.x - pos1.x) / abs(pos2.x - pos1.x) + local dy = (pos2.y - pos1.y) / abs(pos2.y - pos1.y) + for x = pos1.x, pos2.x, dx do + _sectorgrid.set(x, pos1.y, FLOOR) + end + for y = pos1.y, pos2.y, dy do + _sectorgrid.set(pos1.x, y, FLOOR) + end + end + end + + getAllPossibleStartPoints() + while #_possible_starts > 0 do + setStartPoint() + generateMaze() + end + caveMaze() + return sectorinfo +end + +return transformer + diff --git a/game/domain/transformers/rooms.lua b/game/domain/transformers/rooms.lua new file mode 100644 index 00000000..da9a4d71 --- /dev/null +++ b/game/domain/transformers/rooms.lua @@ -0,0 +1,108 @@ + +-- dependencies +local SCHEMATICS = require 'domain.definitions.schematics' +local RANDOM = require 'common.random' +local Rectangle = require 'common.rect' + +local transformer = {} + +transformer.schema = { + { id = 'minw', name = "Minimum Width", type = 'integer', range = {2,32} }, + { id = 'minh', name = "Minimum Height", type = 'integer', range = {2,32} }, + { id = 'maxw', name = "Maximum Width", type = 'integer', range = {2,32} }, + { id = 'maxh', name = "Maximum Height", type = 'integer', range = {2,32} }, + { id = 'rmar', name = "Room Margin", type = 'integer', range = {1,32}}, + { id = 'count', name = "Room Count", type = 'integer', range = {1,32} }, + { id = 'tries', name = "Maximum Tries", type = 'integer', range = {1,1024} }, +} + +function transformer.process(sectorinfo, params) + local _sectorgrid = sectorinfo.grid + local _width, _height = _sectorgrid.getDim() + local _mw, _mh = _sectorgrid.getMargins() + + -- room dimensions + local _minw = params.minw + local _minh = params.minh + local _maxw = params.maxw + local _maxh = params.maxh + local _rmargin = params.rmar + + -- room quantities + local _count = params.count + local _tries = params.tries + + -- room positions + local _minx = _mw + 1 + local _miny = _mh + 1 + local _maxx = _width - _mw + local _maxy = _height - _mh + + local _rooms = {} + + local function makeOneRoom() + return Rectangle( + RANDOM.generateOdd(_minx, _maxx), RANDOM.generateOdd(_miny, _maxy), + RANDOM.generateEven(_minw, _maxw), RANDOM.generateEven(_minh, _maxh) + ) + end + + local function isRoomIntersecting(room) + local try = 0 + local N = #_rooms + local cpos = room.getPos() + local cdim = room.getDim() + local copy = Rectangle( + cpos.x - _rmargin - 1, + cpos.y - _rmargin - 1, + cdim.x + _rmargin * 2 + 1, + cdim.y + _rmargin * 2 + 1) + for i = 1, N do + if copy.intersect(_rooms[i]) then return true end + end + return false + end + + local function isRoomInsideSector(room) + local max = room.getMax() + return _sectorgrid.isInsideMargins(max.x, max.y) + end + + local function generateRooms () + local insert = table.insert + for i = 1, _count do + local room = (function () + local room + repeat + _tries = _tries - 1 + room = makeOneRoom() + until isRoomInsideSector(room, _sectorgrid) + and not isRoomIntersecting(room) + or _tries <= 0 + if _tries <= 0 then room = false end + return room + end)() + if room then + insert(_rooms, room) + end + end + end + + local function caveRooms() + for _, room in ipairs(_rooms) do + local min, max = room.getMin(), room.getMax() + for x = min.x, max.x do + for y = min.y, max.y do + _sectorgrid.set(x, y, SCHEMATICS.FLOOR) + end + end + end + end + + generateRooms() + caveRooms() + return sectorinfo +end + +return transformer + diff --git a/game/draw.lua b/game/draw.lua new file mode 100644 index 00000000..9df9b7b5 --- /dev/null +++ b/game/draw.lua @@ -0,0 +1,73 @@ +--MODULE FOR DRAWING STUFF-- + +local CAM = require 'common.camera' +local tween = require 'helpers.tween' +local first_time = false + +local _GAMEFRAMEWIDTH = 960 + +local _fade + +local draw = {} + +---------------------- +--BASIC DRAW FUNCTIONS +---------------------- + +--Update every drawable object +function draw.update(dt) + for _,layer in pairs(DRAW_TABLE) do + for o in pairs(layer) do + if not o.death and not o.invisible and o.update then + o:update(dt) + end + end + end +end + +--Draws every drawable object from all tables +function draw.allTables() + + DrawTable(DRAW_TABLE.BG) + + CAM:attach(nil, nil, _GAMEFRAMEWIDTH) --Start tracking camera + + DrawTable(DRAW_TABLE.L1) + + DrawTable(DRAW_TABLE.L2) + + CAM:detach() --Stop tracking camera + + DrawTable(DRAW_TABLE.HUD_BG) + + DrawTable(DRAW_TABLE.HUD) + + if DEBUG and first_time then + _fade = tween.start(0, 50, 5) + first_time = false + elseif not DEBUG and not first_time then + _fade = tween.start(50, 0, 5) + first_time = true + end + local g = love.graphics + g.setColor(1, 1, 1, _fade()/255) + g.rectangle('fill', 0, 0, g.getDimensions()) + + DrawTable(DRAW_TABLE.GUI) + + SWITCHER.handleChangedState() +end + +--Draw all the elements in a table +function DrawTable(t) + + for o in pairs(t) do + if not o.invisible then + o:draw() --Call the object respective draw function + end + end + +end + +--Return functions +return draw diff --git a/game/gamestates/animation.lua b/game/gamestates/animation.lua new file mode 100644 index 00000000..91322bb0 --- /dev/null +++ b/game/gamestates/animation.lua @@ -0,0 +1,55 @@ + +local INPUT = require 'input' + +local state = {} + +--[[ LOCAL VARIABLES ]]-- + +local _sector_view +local _alert + +--[[ LOCAL FUNCTIONS ]]-- + +--[[ STATE FUNCTIONS ]]-- + +function state:init() + -- dunno +end + +function state:enter(_, sector_view, animation) + + _sector_view = sector_view + _alert = false + +end + +function state:leave() + + Util.destroyAll() + +end + +function state:update(dt) + + if INPUT.wasAnyPressed(0.5) then + _alert = true + end + + if not _sector_view:hasPendingVFX() then + SWITCHER.pop(_alert) + else + _sector_view:updateVFX(dt) + end + + Util.destroyAll() + +end + +function state:draw() + + Draw.allTables() + +end + +return state + diff --git a/game/gamestates/card_select.lua b/game/gamestates/card_select.lua new file mode 100644 index 00000000..9310320d --- /dev/null +++ b/game/gamestates/card_select.lua @@ -0,0 +1,95 @@ +--MODULE FOR THE GAMESTATE: SELECTING A CARD IN HAND-- +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local PLAYSFX = require 'helpers.playsfx' + +local state = {} + + +--LOCAL VARIABLES-- + +local _route +local _actor_view +local _hand_view + +--LOCAL FUNCTIONS-- + +local function _moveFocus(dir) + _hand_view:moveFocus(dir) +end + +local function _changeActionType(dir) + _hand_view:changeActionType(dir) +end + +local function _confirmCard() + local args = { + chose_a_card = true, + action_type = _hand_view:getActionType(), + card_index = _hand_view:getFocus(), + } + SWITCHER.pop(args) +end + +local function _cancel() + local args = { + chose_a_card = false, + } + PLAYSFX 'back-menu' + SWITCHER.pop(args) +end + +--STATE FUNCTIONS-- + +function state:init() +end + +function state:enter(_, route, _view) + + _route = route + _hand_view = _view.hand + _hand_view:activate() + _actor_view = _view.actor + _actor_view.onhandview = true + + --Make cool animation for cards showing up + +end + +function state:leave() + + _hand_view:deactivate() + _actor_view.onhandview = false + +end + +function state:update(dt) + + if DEBUG then return end + + if DIRECTIONALS.wasDirectionTriggered('RIGHT') then + _moveFocus("RIGHT") + elseif DIRECTIONALS.wasDirectionTriggered('LEFT') then + _moveFocus("LEFT") + elseif DIRECTIONALS.wasDirectionTriggered('UP') then + _changeActionType("UP") + elseif DIRECTIONALS.wasDirectionTriggered('DOWN') then + _changeActionType("DOWN") + elseif INPUT.wasActionPressed('CONFIRM') then + _confirmCard() + elseif INPUT.wasActionPressed('CANCEL') or + INPUT.wasActionPressed('ACTION_1') or + INPUT.wasActionPressed('SPECIAL') then + _cancel() + end + +end + +function state:draw() + + Draw.allTables() + +end + +--Return state functions +return state diff --git a/game/gamestates/character_build.lua b/game/gamestates/character_build.lua new file mode 100644 index 00000000..e166bf78 --- /dev/null +++ b/game/gamestates/character_build.lua @@ -0,0 +1,93 @@ +--MODULE FOR THE GAMESTATE: CHARACTER BUILDER-- +local DB = require 'database' +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local CharaBuildView = require 'view.charabuild' + + +local state = {} + +--CONSTS-- +local _CONFIRM = 'CONFIRM' +local _CANCEL = 'CANCEL' +local _NEXT = 'RIGHT' +local _PREV = 'LEFT' + +--LOCAL VARIABLES-- + +local _playerinfo +local _view +local _menus +local _leave + +--LOCAL FUNCTIONS-- + +local function _resetState() + _playerinfo.species = false + _playerinfo.background = false + _playerinfo.confirm = false +end + +--STATE FUNCTIONS-- + +function state:init() + _playerinfo = { + species = false, + background = false, + confirm = false, + } +end + +function state:enter() + _resetState() + _view = CharaBuildView() + _view:addElement("GUI", nil, "character_builder_view") + _view:open(_playerinfo) + _leave = false +end + +function state:leave() +end + +function state:update(dt) + + if _leave then return end + + -- if you confirm or cancel, all it does is change the current menu context + if INPUT.wasActionPressed(_CONFIRM) then + _view:confirm() + elseif INPUT.wasActionPressed(_CANCEL) then + _view:cancel() + elseif DIRECTIONALS.wasDirectionTriggered(_NEXT) then + _view:selectPrev() + elseif DIRECTIONALS.wasDirectionTriggered(_PREV) then + _view:selectNext() + end + + -- exit gamestate if either everything or nothing is done + if _view.leave then + _leave = true + _view:close(function() + _view:destroy() + SWITCHER.pop() + end) + elseif _view:getContext() > 3 then + if _playerinfo.confirm then + _leave = true + _view:close(function() + _view:destroy() + SWITCHER.pop(_playerinfo) + end) + else + _resetState() + _view:reset() + end + end +end + +function state:draw() + Draw.allTables() +end + +return state + diff --git a/game/gamestates/consume_cards.lua b/game/gamestates/consume_cards.lua new file mode 100644 index 00000000..32fc16a9 --- /dev/null +++ b/game/gamestates/consume_cards.lua @@ -0,0 +1,87 @@ + +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DEFS = require 'domain.definitions' +local PLAYSFX = require 'helpers.playsfx' +local CardView = require 'view.consumelist' + +local state = {} + +local _actor +local _card_list_view +local _leave +local _status + +function state:init() +end + +local function _prev() + _card_list_view:selectPrev() +end + +local function _next() + _card_list_view:selectNext() +end + +local function _toggle() + _card_list_view:toggleSelected() +end + +local function _cancel() + _leave = true +end + +function state:enter(from, actor, maxconsume) + _card_list_view = CardView({"CONFIRM"}) + local buffer = actor:copyBuffer() + _actor = actor + _card_list_view:open(buffer, maxconsume) + _card_list_view:addElement("HUD") + if #buffer == 0 then + _leave = true + end +end + +function state:leave() + _leave = false + _card_list_view:close() + _card_list_view:destroy() + _card_list_view = nil +end + +function state:update(dt) + if DEBUG then return end + + if _leave then + PLAYSFX 'back-menu' + SWITCHER.pop({}) + elseif _card_list_view:isReadyToLeave() then + local consume_log = _card_list_view:getConsumeLog() + local bufsize = _actor:getBufferSize() + for i,v in ipairs(consume_log) do + if v > bufsize then + consume_log[i] = v + 1 + end + end + SWITCHER.pop({ + consumed = consume_log, + }) + else + if DIRECTIONALS.wasDirectionTriggered('LEFT') then + _prev() + elseif DIRECTIONALS.wasDirectionTriggered('RIGHT') then + _next() + elseif (DIRECTIONALS.wasDirectionTriggered('UP') or + DIRECTIONALS.wasDirectionTriggered('DOWN')) then + _toggle() + elseif INPUT.wasActionPressed('CANCEL') then + _cancel() + end + end +end + +function state:draw() + Draw.allTables() +end + +return state diff --git a/game/gamestates/devmode.lua b/game/gamestates/devmode.lua new file mode 100644 index 00000000..f501fd41 --- /dev/null +++ b/game/gamestates/devmode.lua @@ -0,0 +1,46 @@ + +local state = {} + +function state:update(dt) + if not DEBUG then + SWITCHER.pop() + end +end + +function state:draw() + Draw.allTables() +end + +function state:keypressed(key) + imgui.KeyPressed(key) + if not imgui.GetWantCaptureKeyboard() and key == 'f1' then + DEBUG = false + end +end + +function state:textinput(t) + imgui.TextInput(t) +end + +function state:keyreleased(key) + imgui.KeyReleased(key) +end + +function state:mousemoved(x, y) + imgui.MouseMoved(x, y) +end + +function state:mousepressed(x, y, button) + imgui.MousePressed(button) +end + +function state:mousereleased(x, y, button) + imgui.MouseReleased(button) +end + +function state:wheelmoved(x, y) + imgui.WheelMoved(y) +end + +return state + diff --git a/game/gamestates/init.lua b/game/gamestates/init.lua new file mode 100644 index 00000000..62106da8 --- /dev/null +++ b/game/gamestates/init.lua @@ -0,0 +1,15 @@ + +local GAMESTATES = {} + +local fs = love.filesystem + +for _,filename in ipairs(fs.getDirectoryItems("gamestates")) do + local basename = filename:match("^(.-)[.]lua$") + if basename and basename ~= "init" then + local requirepath = "gamestates." .. basename + GAMESTATES[basename:upper()] = require(requirepath) + end +end + +return GAMESTATES + diff --git a/game/gamestates/manage_buffer.lua b/game/gamestates/manage_buffer.lua new file mode 100644 index 00000000..1fc9bff1 --- /dev/null +++ b/game/gamestates/manage_buffer.lua @@ -0,0 +1,74 @@ + +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DEFS = require 'domain.definitions' +local PLAYSFX = require 'helpers.playsfx' +local ManageBufferView = require 'view.cardlist' + +local state = {} + +local _view +local _leave + +local function _prev() + _view:selectPrev() +end + +local function _next() + _view:selectNext() +end + +local function _confirm() + _leave = true +end + +local function _cancel() + _leave = true +end + +function state:enter(from, actor) + _view = ManageBufferView() + if actor:getBackBufferSize() > 0 then + _leave = false + _view:addElement("HUD") + _view:open(actor:copyBackBuffer()) + else + _leave = true + end +end + +function state:leave() + _view:close() + _view = nil +end + +function state:update(dt) + if DEBUG then return end + + if _leave or _view:isCardListEmpty() then + PLAYSFX 'back-menu' + SWITCHER.pop() + else + + if DIRECTIONALS.wasDirectionTriggered('LEFT') then + _prev() + elseif DIRECTIONALS.wasDirectionTriggered('RIGHT') then + _next() + elseif INPUT.wasActionPressed('CONFIRM') then + _confirm() + elseif INPUT.wasActionPressed('CANCEL') then + _cancel() + end + + end + +end + +function state:draw() + Draw.allTables() +end + +return state + + + diff --git a/game/gamestates/open_pack.lua b/game/gamestates/open_pack.lua new file mode 100644 index 00000000..8949ae54 --- /dev/null +++ b/game/gamestates/open_pack.lua @@ -0,0 +1,121 @@ + +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DEFS = require 'domain.definitions' +local PLAYSFX = require 'helpers.playsfx' +local PackView = require 'view.packlist' +local CardView = require 'view.consumelist' + +local state = {} + +local _route +local _card_list_view +local _pack +local _leave +local _status +local _pack_index + +function state:init() +end + +local function _prev() + _card_list_view:selectPrev() +end + +local function _next() + _card_list_view:selectNext() +end + +local function _toggle() + _card_list_view:toggleSelected() +end + +local function _confirm() + if _status == "choosing_pack" then + _status = "choosing_card" + local collection = _card_list_view:getChosenPack() + _pack = _route.makePack(collection, _route.getControlledActor()) + _pack_index = _card_list_view:getSelection() + _card_list_view:close() + _card_list_view = CardView({"CONFIRM"}) + _card_list_view:open(_pack) + _card_list_view:addElement("HUD") + end +end + +local function _cancel() + if _status == "choosing_pack" then + _leave = true + end +end + +local function _consumeCards(consumed) + local count = 0 + for _,i in ipairs(consumed) do + table.remove(_pack, i-count) + count = count + 1 + end +end + +function state:enter(from, route, packlist) + _status = "choosing_pack" + _route = route + _pack = nil + _card_list_view = PackView({"UP", "CONFIRM"}, packlist) + if #packlist > 0 then + _card_list_view:addElement("HUD") + else + _leave = true + end +end + +function state:leave() + _leave = false + _card_list_view:close() + _card_list_view = nil +end + +function state:update(dt) + if DEBUG then return end + + if _status == "choosing_pack" and + (_leave or _card_list_view:isPackListEmpty()) then + PLAYSFX 'back-menu' + SWITCHER.pop({ + consumed = {}, + pack = nil, + pack_index = nil, + }) + elseif _status == "choosing_card" and + (_leave or _card_list_view:isReadyToLeave()) then + PLAYSFX 'back-menu' + local consume_log = _card_list_view:getConsumeLog() + _consumeCards(consume_log) + SWITCHER.pop({ + consumed = consume_log, + pack = _pack, + pack_index = _pack_index + }) + else + if _status == "choosing_pack" and _card_list_view:usedHoldbar() then + _confirm() + elseif DIRECTIONALS.wasDirectionTriggered('LEFT') then + _prev() + elseif DIRECTIONALS.wasDirectionTriggered('RIGHT') then + _next() + elseif _status == "choosing_card" and + (DIRECTIONALS.wasDirectionTriggered('UP') or + DIRECTIONALS.wasDirectionTriggered('DOWN')) then + _toggle() + elseif INPUT.wasActionPressed('CANCEL') then + _cancel() + end + + end +end + +function state:draw() + Draw.allTables() +end + +return state diff --git a/game/gamestates/pick_dir.lua b/game/gamestates/pick_dir.lua new file mode 100644 index 00000000..0e653f91 --- /dev/null +++ b/game/gamestates/pick_dir.lua @@ -0,0 +1,62 @@ + +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DIR = require 'domain.definitions.dir' +local PLAYSFX = require 'helpers.playsfx' +local CardInfo = require 'view.cardinfo' +local vec2 = require 'cpml' .vec2 + +local state = {} + +local _sector_view +local _card_info_view +local _current_dir +local _body_block +local _card + +local function _updateDir(dir) + _current_dir = DIR[dir] + _sector_view:setRayDir(_current_dir, _body_block) +end + +function state:enter(prev, sector_view, body_block, card) + _sector_view = sector_view + _body_block = body_block + if card then + _card_info_view = CardInfo() + _card_info_view:addElement('HUD') + _card_info_view:set(card) + end + _updateDir(DIR[1]) +end + +function state:leave() + if _card_info_view then + _card_info_view:destroy() + _card_info_view = nil + end +end + +function state:update(dt) + if INPUT.wasActionPressed('CONFIRM') then + _sector_view:setRayDir() + SWITCHER.pop(_current_dir) + elseif INPUT.wasActionPressed('CANCEL') then + _sector_view:setRayDir() + PLAYSFX 'back-menu' + SWITCHER.pop() + else + for _,dir in ipairs(DIR) do + if DIRECTIONALS.wasDirectionTriggered(dir) then + _updateDir(dir) + end + end + end +end + +function state:draw() + Draw.allTables() +end + +return state + diff --git a/game/gamestates/pick_target.lua b/game/gamestates/pick_target.lua new file mode 100644 index 00000000..963aae2a --- /dev/null +++ b/game/gamestates/pick_target.lua @@ -0,0 +1,103 @@ +--MODULE FOR THE GAMESTATE: PICKING A TARGET-- +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DIR = require 'domain.definitions.dir' +local PLAYSFX = require 'helpers.playsfx' +local CardInfo = require 'view.cardinfo' +local vec2 = require 'cpml' .vec2 + +--STATE-- +local state = {} + +--LOCAL VARIABLES-- + +local _is_valid_position + +local _sector_view +local _card_info_view +local _cursor + +--LOCAL FUNCTIONS' FORWARD DECLARATION-- +local _moveCursor +local _confirm +local _cancel + +--STATE FUNCTIONS-- + +function state:enter(_, sector_view, target_opt) + + _sector_view = sector_view + local i, j = unpack(target_opt.pos) + _sector_view:newCursor(i, j, target_opt.aoe_hint, target_opt.validator, + target_opt.range_checker) + + if target_opt.card then + _card_info_view = CardInfo() + _card_info_view:addElement('HUD') + _card_info_view:set(target_opt.card) + end + + _moveCursor = function (dir) + _sector_view:moveCursor(unpack(DIR[dir])) + end + + _confirm = function () + if _sector_view.cursor.validator(_sector_view:getCursorPos()) then + local args = { + target_is_valid = true, + pos = {_sector_view:getCursorPos()} + } + SWITCHER.pop(args) + end + end + + _cancel = function () + local args = { + target_is_valid = false, + } + PLAYSFX 'back-menu' + SWITCHER.pop(args) + end + +end + +function state:leave() + _moveCursor = nil + _confirm = nil + _cancel = nil + if _card_info_view then + _card_info_view:destroy() + _card_info_view = nil + end + + _sector_view:removeCursor() +end + +function state:update(dt) + if DEBUG then return end + + _sector_view:lookAtCursor() + + if INPUT.wasActionPressed('CONFIRM') then + _confirm() + elseif INPUT.wasActionPressed('CANCEL') then + _cancel() + else + for _,dir in ipairs(DIR) do + if DIRECTIONALS.wasDirectionTriggered(dir) then + return _moveCursor(dir) + end + end + end + +end + +function state:draw() + + Draw.allTables() + +end + +--Return state functions +return state + diff --git a/game/gamestates/pick_widget_slot.lua b/game/gamestates/pick_widget_slot.lua new file mode 100644 index 00000000..053ddcb9 --- /dev/null +++ b/game/gamestates/pick_widget_slot.lua @@ -0,0 +1,76 @@ + +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DEFS = require 'domain.definitions' +local PLAYSFX = require 'helpers.playsfx' +local PickWidgetView = require 'view.pickwidget' + +local state = {} + +local _view +local _selection +local _validate +local _target +local _leave + + +local function _prev() + _selection = (_selection - 2) % _target:getBody():getWidgetCount() + 1 +end + +local function _next() + _selection = _selection % _target:getBody():getWidgetCount() + 1 +end + +local function _confirm() + if _validate(_selection) then + _view:fadeOut() + SWITCHER.pop({ picked_slot = _selection }) + end +end + +local function _cancel() + _view:fadeOut() + PLAYSFX 'back-menu' + SWITCHER.pop({}) +end + +function state:enter(from, actor, validator) + _target = actor + _validate = validator + _selection = 1 + _leave = actor:getBody():getWidgetCount() <= 0 + + if not _leave then + _view = PickWidgetView(actor) + _view:addElement("HUD") + _view:fadeIn() + end +end + +function state:update(dt) + if not DEBUG then + if _leave then + (_view and _view.fadeOut or DEFS.NULL_METHOD)(_view) + SWITCHER.pop({}) + else + if DIRECTIONALS.wasDirectionTriggered('UP') then + _prev() + elseif DIRECTIONALS.wasDirectionTriggered('DOWN') then + _next() + elseif INPUT.wasActionPressed('CONFIRM') then + _confirm() + elseif INPUT.wasActionPressed('CANCEL') then + _cancel() + end + _view:setSelection(_selection) + end + end +end + +function state:draw() + Draw.allTables() +end + +return state + diff --git a/game/gamestates/play.lua b/game/gamestates/play.lua new file mode 100644 index 00000000..171d25ee --- /dev/null +++ b/game/gamestates/play.lua @@ -0,0 +1,214 @@ + +--MODULE FOR THE GAMESTATE: GAME-- + +local INPUT = require 'input' +local GUI = require 'debug.gui' +local PROFILE = require 'infra.profile' +local PLAYSFX = require 'helpers.playsfx' + +local Route = require 'domain.route' +local SectorView = require 'view.sector' +local HandView = require 'view.hand' +local ActorView = require 'view.actor' +local FadeView = require 'view.fade' +local SoundTrack = require 'view.soundtrack' + +local Activity = require 'common.activity' + +local state = {} + +--LOCAL VARIABLES-- + +local _activity = Activity() + +local _route +local _player +local _next_action + +local _view +local _gui +local _soundtrack + +local _switch_to +local _alert + +--LOCAL FUNCTION-- + +local function _playTurns(...) + local request,extra = _route.playTurns(...) + + if request == "playerDead" then + SWITCHER.switch(GS.START_MENU) + elseif request == "userTurn" then + SWITCHER.push(GS.USER_TURN, _route, _view, _alert) + _alert = false + elseif request == "changeSector" then + _activity:changeSector(...) + elseif request == "report" then + _view.sector:startVFX(extra) + _alert = _alert or (extra.type == 'text_rise') + and (extra.body == _player:getBody()) + SWITCHER.push(GS.ANIMATION, _view.sector) + end + _next_action = nil +end + +function _activity:saveAndQuit() + local fade_view = FadeView(FadeView.STATE_UNFADED) + local route_data = _route.saveState() + PROFILE.saveRoute(route_data) + fade_view:addElement("GUI") + fade_view:fadeOutAndThen(self.resume) + self.yield() + SWITCHER.switch(GS.START_MENU) + fade_view:fadeInAndThen(self.resume) + self.yield() + fade_view:destroy() +end + +function _activity:changeSector() + local fade_view = FadeView(FadeView.STATE_UNFADED) + PLAYSFX 'change-sector' + fade_view:addElement("GUI") + fade_view:fadeOutAndThen(self.resume) + self.yield() + local change_sector_ok = _route.checkSector() + assert(change_sector_ok, "Sector Change fuck up") + _view.sector:sectorChanged() + _soundtrack.playTheme(_route.getCurrentSector():getTheme()) + MAIN_TIMER:after(FadeView.FADE_TIME, self.resume) + self.yield() + fade_view:fadeInAndThen(self.resume) + self.yield() + fade_view:destroy() + return _playTurns() +end + +function _activity:fadeInGUI() + local fade_view = FadeView(FadeView.STATE_FADED) + fade_view:addElement("GUI") + MAIN_TIMER:after(FadeView.FADE_TIME, self.resume) + self.yield() + fade_view:fadeInAndThen(self.resume) + self.yield() + fade_view:destroy() +end + +--STATE FUNCTIONS-- + +function state:init() + _alert = false +end + +function state:enter(pre, route_data) + + -- load route + _route = Route() + _route.loadState(route_data) + + -- View table + _view = {} + + -- sector view + local sector = _route.getCurrentSector() + + _view.sector = SectorView(_route) + _view.sector:addElement("L1", nil, "sector_view") + _view.sector:lookAt(_player) + + -- hand view + _view.hand = HandView(_route) + _view.hand:addElement("HUD_BG", nil, "hand_view") + Signal.register( + "actor_draw", + function(actor, card) + _view.hand:addCard(actor,card) + end + ) + Signal.register( + "actor_used_card", + function(actor, card_index) + _view.hand:removeCard(actor,card_index) + end + ) + + -- Actor view + _view.actor = ActorView(_route) + _view.actor:addElement("HUD_BG") + + -- GUI + _gui = GUI(_view.sector) + _gui:addElement("GUI") + + -- Sound Track + _soundtrack = SoundTrack() + _soundtrack.playTheme(sector:getTheme()) + + -- start gamestate + _playTurns() + + -- set player + _player = _route.getControlledActor() + + _activity:fadeInGUI() + +end + +function state:leave() + + _route.destroyAll() + for _,view in pairs(_view) do + view:destroy() + end + _gui:destroy() + _soundtrack.playTheme(nil) + Util.destroyAll() + +end + +function state:update(dt) + if not DEBUG then + + --FIXME:this doesn't need to happen every update (I think) + if _route.getControlledActor() or _player then + _view.sector:updateFov(_route.getControlledActor() or _player) + else + print("oops") + end + + if INPUT.wasAnyPressed(0.5) then + _alert = true + end + + if _next_action then + _playTurns(unpack(_next_action)) + end + _view.sector:lookAt(_route.getControlledActor() or _player) + end + + Util.destroyAll() + +end + +function state:resume(state, args) + + if state == GS.USER_TURN then + if args == "SAVE_AND_QUIT" then return _activity:saveAndQuit() end + _next_action = args.next_action + elseif state == GS.ANIMATION then + _alert = _alert or args + _playTurns() + end + +end + +function state:draw() + Draw.allTables() +end + +function state:keypressed(key) + if key == 'f1' then DEBUG = true end +end + +--Return state functions +return state diff --git a/game/gamestates/ready_ability.lua b/game/gamestates/ready_ability.lua new file mode 100644 index 00000000..6635e386 --- /dev/null +++ b/game/gamestates/ready_ability.lua @@ -0,0 +1,78 @@ + +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DEFS = require 'domain.definitions' +local PLAYSFX = require 'helpers.playsfx' +local ReadyAbilityView = require 'view.readyability' + +local max = math.max +local min = math.min + +local _HOLDTIME = 0.25 + + +local _view +local _abilities +local _quick_toggle + + +local function _prev() + _view:selectPrev() + PLAYSFX 'select-menu' +end + +local function _next() + _view:selectNext() + PLAYSFX 'select-menu' +end + +local function _confirm() + PLAYSFX 'ok-menu' + _view:exitList() + _abilities.ready = _abilities.list[_view:getSelection()]:getId() + SWITCHER.pop() +end + +local function _cancel() + _view:exitList() + PLAYSFX 'back-menu' + SWITCHER.pop({}) +end + + +local state = {} + +function state:enter(from, abilities, view) + _abilities = abilities + _quick_toggle = 0 + _view = view + _view:enterList() +end + +function state:update(dt) + if DEBUG then return end + _quick_toggle = min(_HOLDTIME, _quick_toggle + dt) + if INPUT.wasActionReleased('ACTION_2') then + if _quick_toggle < _HOLDTIME then + _next() + _confirm() + else + _confirm() + end + elseif DIRECTIONALS.wasDirectionTriggered('UP') then + _quick_toggle = _HOLDTIME + _next() + elseif DIRECTIONALS.wasDirectionTriggered('DOWN') then + _quick_toggle = _HOLDTIME + _prev() + elseif INPUT.wasActionPressed('CONFIRM') then + _confirm() + end +end + +function state:draw() + Draw.allTables() +end + +return state + diff --git a/game/gamestates/settings.lua b/game/gamestates/settings.lua new file mode 100644 index 00000000..5f9faded --- /dev/null +++ b/game/gamestates/settings.lua @@ -0,0 +1,100 @@ + +local DB = require 'database' +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local PROFILE = require 'infra.profile' +local PLAYSFX = require 'helpers.playsfx' +local SettingsView = require 'view.settings' + +local state = {} + +local insert = table.insert +local sort = table.sort +local max = math.max +local min = math.min +local unpack = unpack + +local _selection +local _schema +local _fields +local _fieldcount +local _original +local _view +local _save + +local function _changeField(field, offset) + local low, high = unpack(_schema[field]["range"]) + local step = _schema[field]["step"] + local value = (_changes[field] or _original[field]) + offset * step + _changes[field] = min(high, max(low, value)) + PROFILE.setPreference(field, _changes[field]) +end + +function state:init() + _schema = DB.loadSetting("user-preferences") + _fields = {} + for field in pairs(_schema) do + insert(_fields, field) + end + sort(_fields) -- show them in alphabetical order! + _fieldcount = #_fields + _selection = 1 +end + +function state:enter(from, ...) + _selection = 1 + _original = {} + for _,field in ipairs(_fields) do + _original[field] = PROFILE.getPreference(field) or _schema[field].default + PROFILE.setPreference(field, _original[field]) + end + _changes = setmetatable({}, { __index = original }) + _view = SettingsView(_fields) + _view:addElement("GUI") +end + +function state:update(dt) + if INPUT.wasActionPressed("CONFIRM") then + PLAYSFX 'ok-menu' + _save = true + SWITCHER.pop() + elseif INPUT.wasActionPressed("CANCEL") then + PLAYSFX 'back-menu' + _save = false + SWITCHER.pop() + elseif DIRECTIONALS.wasDirectionTriggered("UP") then + PLAYSFX 'select-menu' + _selection = (_selection - 2 + _fieldcount) % _fieldcount + 1 + _view:setFocus(_selection) + elseif DIRECTIONALS.wasDirectionTriggered("DOWN") then + PLAYSFX 'select-menu' + _selection = (_selection % _fieldcount) + 1 + _view:setFocus(_selection) + elseif DIRECTIONALS.wasDirectionTriggered("LEFT") then + PLAYSFX 'ok-menu' + _changeField(_fields[_selection], -1) + elseif DIRECTIONALS.wasDirectionTriggered("RIGHT") then + PLAYSFX 'ok-menu' + _changeField(_fields[_selection], 1) + end +end + +function state:leave() + -- when you leave, it will either save or restore the changes + if _save then + PROFILE.save() + PROFILE.init() + else + for field, value in ipairs(_original) do + PROFILE.setPreference(field, value) + end + end + _view:destroy() +end + +function state:draw() + return Draw.allTables() +end + +return state + diff --git a/game/gamestates/start_menu.lua b/game/gamestates/start_menu.lua new file mode 100644 index 00000000..447da8ca --- /dev/null +++ b/game/gamestates/start_menu.lua @@ -0,0 +1,168 @@ +--MODULE FOR THE GAMESTATE: MAIN MENU-- +local DB = require 'database' +local MENU = require 'infra.menu' +local DIRECTIONALS = require 'infra.dir' +local INPUT = require 'input' +local CONFIGURE_INPUT = require 'input.configure' +local PROFILE = require 'infra.profile' +local Activity = require 'common.activity' +local StartMenuView = require 'view.startmenu' +local FadeView = require 'view.fade' + +local state = {} + +--LOCAL VARIABLES-- + +local _menu_view +local _menu_context +local _locked +local _activity = Activity() + +-- LOCAL METHODS -- + +function _activity:quit() + _locked = true + local fade_view = FadeView(FadeView.STATE_UNFADED) + fade_view:addElement("GUI") + fade_view:fadeOutAndThen(self.resume) + self.yield() + love.event.quit() +end + +function _activity:enterMenu() + _locked = true + local fade_view = FadeView(FadeView.STATE_FADED) + fade_view:addElement("GUI") + fade_view:fadeInAndThen(self.resume) + self.yield() + _locked = false + return fade_view:destroy() +end + +function _activity:changeState(mode, to, ...) + _locked = true + local fade_view = FadeView(FadeView.STATE_UNFADED) + fade_view:addElement("GUI") + fade_view:fadeOutAndThen(self.resume) + self.yield() + fade_view:destroy() + _menu_view.invisible = true + return SWITCHER[mode](to, ...) +end + + +--STATE FUNCTIONS-- +function state:enter() + _menu_context = "START_MENU" + + _menu_view = StartMenuView() + _menu_view:addElement("HUD") + + _activity:enterMenu() +end + +function state:leave() + _menu_view:destroy() + _menu_view = nil +end + +function state:resume(from, player_info) + if player_info then + _locked = true + print(("%s %s"):format(player_info.species, player_info.background)) + SWITCHER.switch(GS.PLAY, PROFILE.newRoute(player_info)) + else + _menu_context = "START_MENU" + _menu_view.invisible = false + _activity:enterMenu() + end +end + +function state:update(dt) + + if not _locked then + if INPUT.wasActionPressed('CONFIRM') then + MENU.confirm() + elseif INPUT.wasActionPressed('SPECIAL') or + INPUT.wasActionPressed('CANCEL') or + INPUT.wasActionPressed('QUIT') then + MENU.cancel() + elseif DIRECTIONALS.wasDirectionTriggered('UP') then + MENU.prev() + elseif DIRECTIONALS.wasDirectionTriggered('DOWN') then + MENU.next() + end + end + + if _menu_context == "START_MENU" then + _menu_view:setItem("New route") + _menu_view:setItem("Load route") + _menu_view:setItem("Settings") + if DEV then + _menu_view:setItem("Controls") + end + _menu_view:setItem("Quit") + elseif _menu_context == "LOAD_LIST" then + local savelist = PROFILE.getSaveList() + if next(savelist) then + for route_id, route_header in pairs(savelist) do + local savename = ("%s %s"):format(route_id, route_header.player_name) + _menu_view:setItem(savename) + end + else + _menu_view:setItem("[ NO DATA ]") + end + end + + if MENU.begin(_menu_context) then + if _menu_context == "START_MENU" then + if MENU.item("New route") then + _locked = true + _activity:changeState('push', GS.CHARACTER_BUILD) + end + if MENU.item("Load route") then + _menu_context = "LOAD_LIST" + end + if MENU.item("Settings") then + _activity:changeState('push', GS.SETTINGS) + end + if DEV and MENU.item("Controls") then + CONFIGURE_INPUT(INPUT, INPUT.getMap()) + end + if MENU.item("Quit") then + _activity:quit() + end + elseif _menu_context == "LOAD_LIST" then + local savelist = PROFILE.getSaveList() + if next(savelist) then + for route_id, route_header in pairs(savelist) do + local savename = ("%s %s"):format(route_id, route_header.player_name) + if MENU.item(savename) then + local route_data = PROFILE.loadRoute(route_id) + _activity:changeState('switch', GS.PLAY, route_data) + end + end + else + if MENU.item("[ NO DATA ]") then + print("Cannot load no data.") + end + end + end + else + if _menu_context == "START_MENU" then + _activity:quit() + elseif _menu_context == "LOAD_LIST" then + _menu_context = "START_MENU" + end + end + MENU.finish() + _menu_view:setSelection(MENU.getSelection()) +end + +function state:draw() + Draw.allTables() +end + +--Return state functions +return state + diff --git a/game/gamestates/user_turn.lua b/game/gamestates/user_turn.lua new file mode 100644 index 00000000..6a30bec1 --- /dev/null +++ b/game/gamestates/user_turn.lua @@ -0,0 +1,480 @@ + +--- MODULE FOR THE GAMESTATE: PLAYER TURN +-- This gamestate rolls out when the player's turn arrives. It pops the action +-- the player chose to do. + +local DEFS = require 'domain.definitions' +local DIR = require 'domain.definitions.dir' +local SCHEMATICS = require 'domain.definitions.schematics' +local ACTION = require 'domain.action' +local ABILITY = require 'domain.ability' +local MANEUVERS = require 'lux.pack' 'domain.maneuver' +local DIRECTIONALS = require 'infra.dir' +local INPUT = require 'input' +local PLAYSFX = require 'helpers.playsfx' + +local ReadyAbilityView = require 'view.readyability' + +local state = {} + +-- [[ Constant Variables ]]-- +local _INSPECT_MENU = "INSPECT_MENU" +local _SAVE_QUIT = "SAVE_QUIT" +local _USE_READY_ABILITY = "USE_READY_ABILITY" +local _READY_ABILITY_ACTION = "READY_ABILITY" + +--[[ Local Variables ]]-- + +local _task +local _route +local _next_action +local _view + +local _widget_abilities = { + ready = false, + list = {}, +} +local _long_walk +local _adjacency = {} +local _alert +local _save_and_quit +local _was_on_menu + +local _ACTION = {} + +--[[ Task Functions ]]-- + +local function _resumeTask(...) + if _task then + local _ + _, _task = assert(coroutine.resume(_task, ...)) + end +end + +local function _startTask(action, ...) + local controlled_actor = _route.getControlledActor() + local callback = _ACTION[action] + if controlled_actor and not _next_action then + _task = coroutine.create(callback) + return _resumeTask(...) + end +end + +--[[ Adjacency function ]]-- + +local function _updateAdjacency(dir) + local i, j = _route.getControlledActor():getPos() + local sector = _route.getCurrentSector() + local changed = false + local side1, side2 + if dir[1] ~= 0 and dir[2] ~= 0 then + side1 = {0, dir[2]} + side2 = {dir[1], 0} + elseif dir[1] == 0 then + side1 = {-1, dir[2]} + side2 = { 1, dir[2]} + elseif dir[2] == 0 then + side1 = {dir[1], -1} + side2 = {dir[1], 1} + end + local range = {dir, side1, side2} + + for idx, adj_move in ipairs(range) do + local ti = adj_move[1] + i + local tj = adj_move[2] + j + local tile = sector:isInside(ti, tj) and sector:getTile(ti, tj) + local tile_type = tile and tile.type + local current = _adjacency[idx] + _adjacency[idx] = tile_type + if current ~= -1 then + if tile_type ~= current then + changed = true + end + end + end + + return changed +end + +local function _unsetAdjacency() + _adjacency_changes = 0 + for i = 1, 3 do + _adjacency[i] = -1 + end +end + +--[[ Long walk functionf ]]-- + +local function _canLongWalk() + local hostile_bodies = _route.getControlledActor():getHostileBodies() + return (not _long_walk) and #hostile_bodies == 0 +end + +local function _startLongWalk(dir) + _unsetAdjacency() + _long_walk = dir + _alert = false +end + +local function _continueLongWalk() + local dir = _long_walk + dir = DIR[dir] + local i, j = _route.getControlledActor():getPos() + i, j = i+dir[1], j+dir[2] + if not _route.getCurrentSector():isValid(i,j) then + return false + end + if _alert then + _alert = false + return false + end + + local hostile_bodies = _route.getControlledActor():getHostileBodies() + if #hostile_bodies > 0 or _updateAdjacency(dir) then + return false + end + + return true +end + +--[[ Abilities ]]-- + +local function _updateAbilityList() + local n = 0 + local list = _widget_abilities.list + local ready = _widget_abilities.ready + for _,widget in _route.getControlledActor():getBody():eachWidget() do + if widget:getWidgetAbility() then + n = n + 1 + list[n] = widget + end + end + if n > 0 then + local is_ready + for i = 1, n do + -- if there is a ready ability, keep it ready + is_ready = is_ready or list[i]:getId() == ready + end + -- if there isn't, select the first one + ready = is_ready and ready or list[1]:getId() + else + -- no ability to select + ready = false + end + _widget_abilities.ready = ready + _widget_abilities.list = list +end + +function _selectedAbilitySlot() + local ready = _widget_abilities.ready + if not ready then return false end + local widget = Util.findId(ready) + return _route.getControlledActor():getBody():findWidget(widget) +end + +--[[ State Methods ]]-- + +function state:init() + _long_walk = false + return _unsetAdjacency() +end + +function state:enter(_, route, view, alert) + + _route = route + _save_and_quit = false + _alert = alert + + _updateAbilityList() + + _view = view + _view.hand:reset() + local ability_idx = 1 + for i, widget in ipairs(_widget_abilities.list) do + if widget:getId() == _widget_abilities.ready then + ability_idx = i + break + end + end + local ability_view = ReadyAbilityView(_widget_abilities.list, ability_idx) + ability_view:addElement("HUD") + ability_view:enter() + _view.ability = ability_view + + _was_on_menu = false + +end + +function state:leave() + for i = #_widget_abilities.list, 1, -1 do + _widget_abilities.list[i] = nil + end + _view.ability:exit() + _view.ability = nil +end + +function state:resume(from, args) + _view.sector.setCooldownPreview(0) + _resumeTask(args) + if INPUT.wasAnyPressed() then + _alert = true + end +end + +function state:update(dt) + + if DEBUG then + return SWITCHER.push(GS.DEVMODE) + end + + if _save_and_quit then return SWITCHER.pop("SAVE_AND_QUIT") end + + _view.sector:lookAt(_route.getControlledActor()) + + if INPUT.wasAnyPressed() then + _alert = true + end + + if _next_action then + SWITCHER.pop({next_action = _next_action}) + _next_action = nil + return + end + + + local action_request + local dir = DIRECTIONALS.hasDirectionTriggered() + if dir then + if INPUT.isActionDown('ACTION_4') and _canLongWalk() then + _startLongWalk(dir) + else + action_request = {DEFS.ACTION.MOVE, dir} + end + end + + if INPUT.wasActionPressed('CONFIRM') then + action_request = {DEFS.ACTION.INTERACT} + elseif INPUT.wasActionPressed('CANCEL') then + action_request = {DEFS.ACTION.IDLE} + elseif INPUT.wasActionPressed('SPECIAL') then + action_request = {_USE_READY_ABILITY} + elseif INPUT.wasActionPressed('ACTION_1') then + action_request = {DEFS.ACTION.PLAY_CARD} + elseif INPUT.wasActionPressed('ACTION_2') then + action_request = {_READY_ABILITY_ACTION} + elseif INPUT.wasActionPressed('ACTION_3') then + action_request = {DEFS.ACTION.RECEIVE_PACK} + elseif INPUT.wasActionPressed('EXTRA') then + action_request = _INSPECT_MENU + elseif INPUT.wasActionPressed('PAUSE') then + action_request = _SAVE_QUIT + end + + -- execute action + if _long_walk then + if not action_request and _continueLongWalk() then + _startTask(DEFS.ACTION.MOVE, _long_walk) + else + _long_walk = false + end + elseif action_request == _INSPECT_MENU then + -- + elseif action_request == _SAVE_QUIT then + _save_and_quit = true + return + elseif action_request then + _startTask(unpack(action_request)) + end + + + Util.destroyAll() + +end + +function state:draw() + Draw.allTables() +end + +function state:keypressed(key) + if key == 'f1' then DEBUG = true end +end + +--[[ Action functions ]]-- + +local function _useAction(action_slot, params) + if not ACTION.exists(action_slot) then return false end + local current_sector = _route.getCurrentSector() + local controlled_actor = _route.getControlledActor() + params = params or {} + local param = ACTION.pendingInput(action_slot, controlled_actor, params) + while param do + _view.sector:setCooldownPreview( + ACTION.exhaustionCost(action_slot, controlled_actor, params) + ) + if param.name == 'choose_dir' then + SWITCHER.push(GS.PICK_DIR, _view.sector, param['body-block'], + ACTION.card(action_slot, controlled_actor, params)) + local dir = coroutine.yield(_task) + if dir then + params[param.output] = dir + else + return false + end + elseif param.name == 'choose_target' then + SWITCHER.push( + GS.PICK_TARGET, _view.sector, + { + card = ACTION.card(action_slot, controlled_actor, params), + pos = { controlled_actor:getPos() }, + aoe_hint = param['aoe-hint'], + range_checker = function(i, j) + return ABILITY.input('choose_target') + .isWithinRange(controlled_actor, param, {i,j}) + end, + validator = function(i, j) + return ABILITY.validate('choose_target', controlled_actor, param, + {i,j}) + end + } + ) + local args = coroutine.yield(_task) + if args.target_is_valid then + params[param.output] = args.pos + else + return false + end + elseif param.name == "choose_widget_slot" then + SWITCHER.push( + GS.PICK_WIDGET_SLOT, controlled_actor, + function (which_slot) + return ABILITY.validate('choose_widget_slot', controlled_actor, param, + which_slot) + end + ) + local args = coroutine.yield(_task) + if args.picked_slot then + params[param.output] = args.picked_slot + else + return false + end + elseif param.name == "choose_consume_list" then + SWITCHER.push(GS.CONSUME_CARDS, controlled_actor, param.max) + local args = coroutine.yield(_task) + if args.consumed then + params[param.output] = args.consumed + else + return false + end + end + param = ACTION.pendingInput(action_slot, controlled_actor, params) + end + _next_action = {action_slot, params} + return true +end + +_ACTION[DEFS.ACTION.MOVE] = function (dir) + local current_sector = _route.getCurrentSector() + local controlled_actor = _route.getControlledActor() + local i, j = controlled_actor:getPos() + + dir = DIR[dir] + i, j = i+dir[1], j+dir[2] + if current_sector:isValid(i,j) then + _useAction(DEFS.ACTION.MOVE, { pos = {i,j} }) + else + PLAYSFX 'denied' + end +end + + +_ACTION[DEFS.ACTION.INTERACT] = function() + _useAction(DEFS.ACTION.INTERACT) +end + +_ACTION[DEFS.ACTION.ACTIVATE_WIDGET] = function() + local has_widget = _route.getControlledActor():getBody():hasWidgetAt(1) + if has_widget then + PLAYSFX 'ok-menu' + _useAction(DEFS.ACTION.ACTIVATE_WIDGET) + end +end + +_ACTION[DEFS.ACTION.DRAW_NEW_HAND] = function() + if MANEUVERS['draw_new_hand'].validate(_route.getControlledActor(), {}) then + PLAYSFX 'ok-menu' + _useAction(DEFS.ACTION.DRAW_NEW_HAND) + else + PLAYSFX 'denied' + end +end + +_ACTION[DEFS.ACTION.PLAY_CARD] = function() + PLAYSFX 'ok-menu' + SWITCHER.push(GS.CARD_SELECT, _route, _view) + local args = coroutine.yield(_task) + if args.chose_a_card then + if args.action_type == 'play' then + PLAYSFX 'ok-menu' + if args.card_index > #_view.hand.hand then + _useAction(DEFS.ACTION.DRAW_NEW_HAND) + else + if _useAction(DEFS.ACTION.PLAY_CARD, + { card_index = args.card_index }) then + Signal.emit("actor_used_card", _route.getControlledActor(), index) + end + end + end + end +end + +_ACTION[DEFS.ACTION.CONSUME_CARDS] = function() + local actor = _route.getControlledActor() + if actor:getBackBufferSize() > 0 then + PLAYSFX 'ok-menu' + SWITCHER.push(GS.MANAGE_BUFFER, actor) + local args = coroutine.yield(_task) + else + PLAYSFX 'denied' + end +end + +_ACTION[DEFS.ACTION.RECEIVE_PACK] = function() + local actor = _route.getControlledActor() + if actor:getPrizePackCount() > 0 then + PLAYSFX 'ok-menu' + SWITCHER.push(GS.OPEN_PACK, _route, actor:getPrizePacks()) + local args = coroutine.yield(_task) + if args.pack == nil then return end + _route.getControlledActor():removePrizePack(args.pack_index) + _useAction(DEFS.ACTION.RECEIVE_PACK, + { consumed = args.consumed, pack = args.pack }) + else + PLAYSFX 'denied' + end +end + +_ACTION[DEFS.ACTION.IDLE] = function() + _useAction(DEFS.ACTION.IDLE) +end + +_ACTION[_READY_ABILITY_ACTION] = function() + if _widget_abilities.list[2] then + PLAYSFX 'open-menu' + SWITCHER.push(GS.READY_ABILITY, _widget_abilities, _view.ability) + else + PLAYSFX 'denied' + end +end + +_ACTION[_USE_READY_ABILITY] = function() + local slot = _selectedAbilitySlot() + if slot then + PLAYSFX 'ok-menu' + _useAction(DEFS.ACTION.ACTIVATE_WIDGET, { widget_slot = slot }) + else + PLAYSFX 'denied' + end +end + +return state + diff --git a/game/helpers/playsfx.lua b/game/helpers/playsfx.lua new file mode 100644 index 00000000..fe4218ba --- /dev/null +++ b/game/helpers/playsfx.lua @@ -0,0 +1,11 @@ + +local PROFILE = require 'infra.profile' +local RES = require 'resources' + +return function (sfxname) + local sfx = RES.loadSFX(sfxname) + sfx:setVolume(PROFILE.getPreference("sfx-volume") / 100) + sfx:stop() + return sfx:play() +end + diff --git a/game/helpers/tween.lua b/game/helpers/tween.lua new file mode 100644 index 00000000..73106b22 --- /dev/null +++ b/game/helpers/tween.lua @@ -0,0 +1,17 @@ + +local tween = {} + +function tween.start(from, to, pace) + return function (dt) + dt = dt or 1 + local step = (to - from)/pace + if step*step > 0.01 then + from = from + step * dt + else + from = to + end + return from + end +end + +return tween diff --git a/game/infra/dir.lua b/game/infra/dir.lua new file mode 100644 index 00000000..bf90b3b7 --- /dev/null +++ b/game/infra/dir.lua @@ -0,0 +1,161 @@ + +local INPUT = require 'input' +local DIR = require 'domain.definitions.dir' + +local pi = math.pi +local abs = math.abs +local atan2 = math.atan2 + +local DIRECTIONALS = {} + +local _DEADZONE = .5 +local _DEADZONE_SQR = _DEADZONE * _DEADZONE +local _SIXTEENTH = math.pi / 8 + +local _DIR_ENUM = { + c = false, + u = 1, + r = 2, + d = 3, + l = 4, + lu = 5, + ru = 6, + rd = 7, + ld = 8, +} + +local _DIR_TRANSLATE = { + UP = 'u', + RIGHT = 'r', + DOWN = 'd', + LEFT = 'l', + UPLEFT = 'lu', + UPRIGHT = 'ru', + DOWNRIGHT = 'rd', + DOWNLEFT = 'ld', +} + +local _OCTANTS = {} + +function _OCTANTS.UP(x, y) + return y/3 < x and x < -y/3 +end + +function _OCTANTS.RIGHT(x, y) + return -x/3 < y and y < x/3 +end + +function _OCTANTS.DOWN(x, y) + return -y/3 < x and x < y/3 +end + +function _OCTANTS.LEFT(x, y) + return x/3 < y and y < -x/3 +end + +function _OCTANTS.UPLEFT(x, y) + return 3*x <= y and y <= x/3 +end + +function _OCTANTS.UPRIGHT(x, y) + return -3*x <= y and y <= -x/3 +end + +function _OCTANTS.DOWNRIGHT(x, y) + return x/3 <= y and y <= 3*x +end + +function _OCTANTS.DOWNLEFT(x, y) + return -x/3 <= y and y <= -3*x +end + +DIRECTIONALS.DEADZONE = _DEADZONE + +function DIRECTIONALS.getFromAxes() + local x, y = INPUT.getAxis('AXIS_X'), INPUT.getAxis('AXIS_Y') + if x*x+y*y < _DEADZONE_SQR then + return 'c' + else + for dir, enum in pairs(_DIR_TRANSLATE) do + if _OCTANTS[dir](x, y) then + return enum + end + end + end + return false +end + +function DIRECTIONALS.getFromHat() + return INPUT.getHat('HAT_DIRECTIONALS') +end + +local _last_axis +local _last_hat + +function DIRECTIONALS.wasDirectionTriggered(direction) + local dir = _DIR_ENUM[_DIR_TRANSLATE[direction]] + local hat = _DIR_ENUM[DIRECTIONALS.getFromHat()] + local axis = _DIR_ENUM[DIRECTIONALS.getFromAxes()] + + if not hat then _last_hat = false end + if not axis then _last_axis = false end + + if _last_hat then hat = false end + if _last_axis then axis = false end + + local is_hat = hat == dir + local is_axis = axis == dir + + if is_hat then _last_hat = hat end + if is_axis then _last_axis = axis end + + return is_hat or is_axis or INPUT.wasActionPressed(direction) +end + +function DIRECTIONALS.isDirectionDown(direction) + local dir = _DIR_ENUM[_DIR_TRANSLATE[direction]] + local hat = _DIR_ENUM[DIRECTIONALS.getFromHat()] + local axis = _DIR_ENUM[DIRECTIONALS.getFromAxes()] + + _last_hat = hat + _last_axis = axis + + return dir == hat or dir == axis or INPUT.isActionDown(direction) +end + +--Check the if any direction is triggered, including diagonals +function DIRECTIONALS.hasDirectionTriggered() + if DIRECTIONALS.wasDirectionTriggered('UPLEFT') then + return 'UPLEFT' + elseif DIRECTIONALS.wasDirectionTriggered('UPRIGHT') then + return 'UPRIGHT' + elseif DIRECTIONALS.wasDirectionTriggered('DOWNLEFT') then + return 'DOWNLEFT' + elseif DIRECTIONALS.wasDirectionTriggered('DOWNRIGHT') then + return 'DOWNRIGHT' + elseif DIRECTIONALS.wasDirectionTriggered('UP') then + if DIRECTIONALS.wasDirectionTriggered('LEFT') then + return 'UPLEFT' + elseif DIRECTIONALS.wasDirectionTriggered('RIGHT') then + return 'UPRIGHT' + else + return 'UP' + end + elseif DIRECTIONALS.wasDirectionTriggered('DOWN') then + if DIRECTIONALS.wasDirectionTriggered('LEFT') then + return 'DOWNLEFT' + elseif DIRECTIONALS.wasDirectionTriggered('RIGHT') then + return 'DOWNRIGHT' + else + return 'DOWN' + end + elseif DIRECTIONALS.wasDirectionTriggered('LEFT') then + return 'LEFT' + elseif DIRECTIONALS.wasDirectionTriggered('RIGHT') then + return 'RIGHT' + end + + return nil +end + +return DIRECTIONALS diff --git a/game/infra/menu.lua b/game/infra/menu.lua new file mode 100644 index 00000000..86110ed5 --- /dev/null +++ b/game/infra/menu.lua @@ -0,0 +1,97 @@ +-- PRIVATE VARIABLES -- +local Menu = {} +local _registered = {} +local _count +local _current +local _size +local _actions = { + confirm = false, + cancel = false, + next = false, + prev = false, +} + +-- PRIVATE METHODS -- +-- get/set selection +local function _selection(n) + _registered[_current] = n or _registered[_current] + return _registered[_current] +end + +-- check cancel +local function _checkCancel() + return not _actions.cancel +end + +-- check movement +local function _checkMovement() + if _actions.next then _registered[_current] = _registered[_current] + 1 end + if _actions.prev then _registered[_current] = _registered[_current] - 1 end + return true +end + +-- check if item was selected +local function _isSelected() + return _count == _selection() +end + +-- check if item was confirmed +local function _isConfirmed() + return _isSelected() and _actions.confirm +end + +-- start menu +function Menu.begin(name) + -- register new menu + if _current ~= name then + _registered[name] = 1 + _current = name + _count = 0 + _size = 0 + end + + -- check menu input and set menu box minimal dimensions + return _checkCancel() and _checkMovement() +end + +-- check menuitem +function Menu.item(item) + -- increment item count + _count = _count + 1 + return _isConfirmed() +end + + +function Menu.finish() + -- update menu size + _size = _count + _count = 0 + + -- reposition selection if out of bounds + if _selection() > _size then _selection(1) + elseif _selection() < 1 then _selection(_size) end + + -- reset actions' states + for k in pairs(_actions) do _actions[k] = false end + return _selection() +end + +-- MENU ACTIONS -- +function Menu.confirm() _actions.confirm = true end +function Menu.cancel() _actions.cancel = true end +function Menu.next() _actions.next = true end +function Menu.prev() _actions.prev = true end + +-- GETTERS -- +function Menu.getSelection () + -- currently selected index + return _selection() +end + +function Menu.getSize () + -- total number of items in menu + return _size +end + +return Menu + diff --git a/game/infra/profile.lua b/game/infra/profile.lua new file mode 100644 index 00000000..e58cbfe4 --- /dev/null +++ b/game/infra/profile.lua @@ -0,0 +1,170 @@ + +local JSON = require 'dkjson' +local INPUT = require 'input' + +local IDGenerator = require 'common.idgenerator' +local RUNFLAGS = require 'infra.runflags' +local ROUTEBUILDER = require 'domain.builders.route' +local DB = require 'database' +local ZIP = love.math + +-- CONSTANTS -- +local SAVEDIR = "_savedata/" +local PROFILE_FILENAME = "profile" +local CONTROL_FILENAME = "controls" +local PROFILE_PATH = SAVEDIR..PROFILE_FILENAME +local CONTROL_PATH = SAVEDIR..CONTROL_FILENAME +local METABASE = { next_id = 1, save_list = {}, preferences = {} } + +-- HELPERS +local filesystem = love.filesystem + +-- LOCALS -- +local PROFILE = {} +local __COMPRESS__ = false +local _id_generator +local _metadata + +local function _compress(str) --> str + if not __COMPRESS__ then return str end + return assert(ZIP.compress(str:gsub(" ", ""), "lz4", 9)) +end + +local function _decompress(str) --> str + if not __COMPRESS__ then return str end + return assert(ZIP.decompress(str, "lz4")) +end + +local function _encode(t) --> str + return _compress(JSON.encode(t, {indent = true})) +end + +local function _decode(str) --> table + return JSON.decode(_decompress(str)) +end + +local function _deleteInput() + INPUT.delete(CONTROL_PATH) +end + +local function _loadInput() + -- setup input + local loaded_input = INPUT.load(CONTROL_PATH, _decode) + if not loaded_input then + local inputmap = DB.loadSetting('controls') + INPUT.setup(inputmap) + end +end + +local function _saveInput() + return INPUT.save(CONTROL_PATH, _encode) +end + +local function _cleanSlate () + print("CLEAR FLAG SET. DELETING ALL SAVE DATA.") + print("Removing custom controls") + _deleteInput() + for _,filename in ipairs(filesystem.getDirectoryItems(SAVEDIR)) do + print(("Removing: %s"):format(filename)) + filesystem.remove(SAVEDIR..filename) + end +end + +local function _saveProfile(base) + local profile_data = base or _metadata + local file = assert(filesystem.newFile(PROFILE_PATH, "w")) + file:write(_encode(profile_data)) + return file:close() +end + +local function _newProfile() + filesystem.createDirectory(SAVEDIR) + _saveProfile(METABASE) +end + +local function _loadProfile() + local filedata = assert(filesystem.newFileData(PROFILE_PATH)) + _metadata = _decode(filedata:getString()) + _id_generator = IDGenerator(_metadata.next_id) + for field, default in pairs(METABASE) do + -- protection against version update (SHALLOW COPY ONLY) + _metadata[field] = _metadata[field] or default + end +end + +-- METHODS -- +function PROFILE.init() + -- set version + METABASE.version = VERSION + -- clean all save history if CLEAR runflag is set + __COMPRESS__ = __COMPRESS__ or RUNFLAGS.COMPRESS + if RUNFLAGS.CLEAR then _cleanSlate() end + -- check if profile exists and generate one if not + if not filesystem.getInfo(PROFILE_PATH, 'file') then _newProfile() end + -- load profile from disk + _loadProfile() + _loadInput() +end + +function PROFILE.loadRoute(route_id) + local filedata = assert(filesystem.newFileData(SAVEDIR..route_id)) + local route_data = _decode(filedata:getString()) + -- delete save from profile list + _metadata.save_list[route_data.id] = nil + return route_data +end + +function PROFILE.saveRoute(route_data) + local file = assert(filesystem.newFile(SAVEDIR..route_data.id, "w")) + -- add save to profile list + _metadata.save_list[route_data.id] = { + player_name = route_data.player_name + } + file:write(_encode(route_data)) + return file:close() +end + +function PROFILE.newRoute(player_info) + local route_id = ("route%s"):format(_id_generator.newID()) + local route_data = ROUTEBUILDER.build(route_id, player_info) + _metadata.next_id = _id_generator.getNextID() + return route_data +end + +function PROFILE.getSaveList() + return _metadata.save_list +end + +function PROFILE.getPreference(field) + return _metadata.preferences[field] +end + +function PROFILE.setPreference(field, value) + _metadata.preferences[field] = value +end + +function PROFILE.save() + _saveProfile() +end + +-- NOTE TO SELF: +-- Add a quit method that deletes saves that are not on the profile's save_list. +-- This means that when you load a file, and you don't save it, it is lost +-- forever once the program quits. This would be an easy way to implement +-- permadeath. Also if you quit without saving, you lose your savefle. +-- BUT! If the game crashes, you keep your last save. +function PROFILE.quit() + _saveInput() + _saveProfile() + local save_list = _metadata.save_list + for _,filename in ipairs(filesystem.getDirectoryItems(SAVEDIR)) do + if filename ~= PROFILE_FILENAME and filename ~= CONTROL_FILENAME + and not save_list[filename] then + print(("Removing unsaved file: %s"):format(filename)) + filesystem.remove(SAVEDIR..filename) + end + end +end + + +return PROFILE diff --git a/game/infra/runflags.lua b/game/infra/runflags.lua new file mode 100644 index 00000000..ae0e1b63 --- /dev/null +++ b/game/infra/runflags.lua @@ -0,0 +1,21 @@ + +local RUNFLAGS = {} +local _flags = {} + +function RUNFLAGS.init(arg) + for _, runflag in ipairs(arg) do + if runflag:match("^%-") then + local flagname = runflag:match("%-(%w+)") + local value = runflag:match("=(.+)") + if flagname then + value = tonumber(value) or value or true + _flags[flagname:upper()] = value + end + end + end + RUNFLAGS.init = function () end +end + +return setmetatable(RUNFLAGS, { + __index = function (t, k) return _flags[k] end +}) diff --git a/game/infra/switcher.lua b/game/infra/switcher.lua new file mode 100644 index 00000000..893b0368 --- /dev/null +++ b/game/infra/switcher.lua @@ -0,0 +1,72 @@ + +local Gamestate = require "steaming.extra_libs.hump.gamestate" +local Queue = require 'lux.common.Queue' +local INPUT = require 'input' + +local SWITCHER = {} + +local _INPUT_HANDLES = { + "keypressed", + "keyreleased", + "textinput", + "mousemoved", + "mousepressed", + "mousereleased", + "wheelmoved", +} + +local _stack_size = 0 +local _pushed = false +local _popped = false +local _switched = false + +function SWITCHER.init() + for _,handle in ipairs(_INPUT_HANDLES) do + love[handle] = function (...) + return Gamestate[handle](...) + end + end +end + +function SWITCHER.start(to, ...) + -- call right after init, with the initial gamestate + -- call it only once! + _stack_size = 1 + Gamestate.switch(to, ...) +end + +function SWITCHER.switch(to, ...) + INPUT.flush() + _switched = { to, ... } +end + +function SWITCHER.push(to, ...) + _stack_size = _stack_size + 1 + INPUT.flush() + _pushed = { to, ... } +end + +function SWITCHER.pop(...) + _stack_size = _stack_size - 1 + INPUT.flush() + _popped = { ... } +end + +function SWITCHER.handleChangedState() + if _popped then + Gamestate.pop(unpack(_popped)) + _popped = false + end + if _pushed then + Gamestate.push(unpack(_pushed)) + _pushed = false + end + if _switched then + Gamestate.switch(unpack(_switched)) + _switched = false + end +end + +setmetatable(SWITCHER, { __index = Gamestate }) + +return SWITCHER diff --git a/game/libs/.gitignore b/game/libs/.gitignore new file mode 100644 index 00000000..c70a9295 --- /dev/null +++ b/game/libs/.gitignore @@ -0,0 +1,3 @@ +* +!init.lua +!.gitignore diff --git a/game/libs/init.lua b/game/libs/init.lua new file mode 100644 index 00000000..87f04a4c --- /dev/null +++ b/game/libs/init.lua @@ -0,0 +1,5 @@ + +local fs = love.filesystem +fs.setRequirePath("libs/?/init.lua;libs/?.lua;"..fs.getRequirePath()) +package.cpath = fs.getSourceBaseDirectory().."/?.so;" .. package.cpath + diff --git a/game/main.lua b/game/main.lua new file mode 100644 index 00000000..4fbfb965 --- /dev/null +++ b/game/main.lua @@ -0,0 +1,93 @@ + +-- set libs dir to path +require 'libs' + +-- LUX portability +require 'lux.portable' + +local common = require 'lux.common' + +printf = common.printf +identityp = common.identityp + +-- CPML +cpml = require 'cpml' + +-- HUMP +Timer = require "steaming.extra_libs.hump.timer" +Class = require "steaming.extra_libs.hump.class" +Camera = require "steaming.extra_libs.hump.camera" +Vector = require "steaming.extra_libs.hump.vector" +Signal = require "steaming.extra_libs.hump.signal" + + +-- CLASSES +require "steaming.classes.primitive" + +-- STEAMING MODULES +Util = require "steaming.util" +Font = require "steaming.font" +Res = require "steaming.res_manager" + +Draw = require "draw" +Setup = require "setup" + +-- GAMESTATES +GS = require 'gamestates' + +-- GAMESTATE SWITCHER +SWITCHER = require 'infra.switcher' + +local PROFILE = require 'infra.profile' +local RUNFLAGS = require 'infra.runflags' +local INPUT = require 'input' +local DB = require 'database' + +local JSON = require 'dkjson' + +------------------ +--LÖVE FUNCTIONS-- +------------------ + +function love.load(arg) + + Setup.config() --Configure your game + + RUNFLAGS.init(arg) + + SWITCHER.init() --Overwrites love callbacks to call Gamestate as well + + -- Setup support for multiple resolutions. Res.init() Must be called after + -- Gamestate.registerEvents() so it will properly call the draw function + -- applying translations. + Res.init() + love.graphics.setDefaultFilter("nearest", "nearest") + + -- init DB + DB.init() + + -- initializes save & load system + PROFILE.init() + + require 'tests' + + SWITCHER.start(GS.START_MENU) --Jump to the inicial state +end + +function love.update(dt) + MAIN_TIMER:update(dt) + if INPUT.wasActionReleased('QUIT') then love.event.quit() end + SWITCHER.update(dt) + INPUT.flush() -- must be called afterwards + Draw.update(dt) +end + +function love.draw() + SWITCHER.draw() +end + +function love.quit() + imgui.ShutDown(); + PROFILE.quit() +end + diff --git a/game/resources/init.lua b/game/resources/init.lua new file mode 100644 index 00000000..9653c854 --- /dev/null +++ b/game/resources/init.lua @@ -0,0 +1,79 @@ + +local DB = require 'database' +local COMPOUND_RES = require 'lux.pack' 'resources' + +local RES = {} + +local _rescache = { + font = {}, + texture = {}, + sfx = {}, + bgm = {}, + frames = {}, + tileset = {}, +} + +local _initResource = { + font = function(path, size) + local font = love.graphics.newFont(path, size) + font:setFilter('linear', 'linear', 1) + return font + end, + texture = function(path) + return love.graphics.newImage(path) + end, + sfx = function(path) + return love.audio.newSource(path, "static") + end, + bgm = function(path) + local src = love.audio.newSource(path, "stream") + src:setLooping(true) + return src + end, +} + +function _updateResource(rtype, name, data) + _rescache[rtype][name] = data +end + +function _loadResource(rtype, name, ...) + local sufix = table.concat({...}, "_") + local res = _rescache[rtype][name..sufix] if not res then + local path = DB.loadResourcePath(rtype, name) + res = _initResource[rtype](path, ...) + _updateResource(rtype, name..sufix, res) + _rescache[rtype][name..sufix] = res + end + return res +end + +function RES.loadFont(name, size) + return _loadResource('font', name, size) +end + +function RES.loadTexture(name) + return _loadResource('texture', name) +end + +function RES.loadSFX(name) + return _loadResource('sfx', name) +end + +function RES.loadBGM(name) + return _loadResource('bgm', name) +end + +function RES.loadSprite(name) + local info = DB.loadResource('sprite', name) + local texture = RES.loadTexture(info.texture) + return COMPOUND_RES.sprite.load(name, info, texture) +end + +function RES.loadTileSet(name) + local info = DB.loadResource('tileset', name) + local texture = RES.loadTexture(info.texture) + return COMPOUND_RES.tileset.new(name, info, texture) +end + +return RES + diff --git a/game/resources/sprite.lua b/game/resources/sprite.lua new file mode 100644 index 00000000..4913cba6 --- /dev/null +++ b/game/resources/sprite.lua @@ -0,0 +1,114 @@ + +--DEPENDENCIES-- +local DB = require 'database' + + +--?????-- +local floor = math.floor +local min = math.min +local delta = love.timer.getDelta +local g = love.graphics + + +--LOCAL VARS-- +local _animcache = {} + + +--LOCAL FUNCTIONS-- +local function _newAnimationMeta(name, info, texture) + local cols, rows = unpack(info.quad_division) + local ox, oy = unpack(info.offset) + local iw, ih = texture:getDimensions() + local qw, qh = floor(iw/cols), floor(ih/rows) + local quads = {} + local frames = {} + + for i = 0, rows-1 do + for j = 0, cols-1 do + table.insert(quads, g.newQuad(j*qw, i*qh, qw, qh, iw, ih)) + end + end + + for i, frame in ipairs(info.animation) do + frames[i] = { + quad = quads[frame.quad_idx], + time = frame.time, + } + end + + local anim = { + frames = frames, + framecount = #frames, + loop = info.loop, + offset = {ox, oy}, + } + + _animcache[name] = anim + + return anim +end + +local function _updateAndGetCurrentQuadOfSprite(sprite) + local animation = sprite.animation + local dt = floor(delta()*1000) + sprite.timecount = sprite.timecount + dt + if sprite.timecount >= animation.frames[sprite.frame].time then + sprite.timecount = 0 + if animation.loop then + sprite.frame = sprite.frame % animation.framecount + 1 + else + sprite.frame = min(sprite.frame + 1, animation.framecount) + end + end + return animation.frames[sprite.frame].quad +end + + +--SPRITE MODULE-- +local Sprite = Class({ + __includes = ELEMENT +}) + +function Sprite:init(texture, animation) + ELEMENT.init(self) + self.animation = animation + self.texture = texture + self.frame = 1 + self.decor = false + self.timecount = 0 +end + +function Sprite:setDecorator(f) + self.decor = f +end + +function Sprite:clearDecorator() + self.decor = false +end + +function Sprite:draw(...) + if self.decor then + self:decor(...) + else + self:render(...) + end +end + +function Sprite:render(x, y) + local quad = _updateAndGetCurrentQuadOfSprite(self) + local tex = self.texture + local ox, oy = unpack(self.animation.offset) + g.draw(tex, quad, x, y, 0, 1, 1, ox, oy) +end + + +--SPRITE LOADER MODULE-- +local SpriteLoader = {} + +function SpriteLoader.load(name, info, texture) + local anim = _animcache[name] or _newAnimationMeta(name, info, texture) + return Sprite(texture, anim) +end + +return SpriteLoader + diff --git a/game/resources/tileset.lua b/game/resources/tileset.lua new file mode 100644 index 00000000..54bc9472 --- /dev/null +++ b/game/resources/tileset.lua @@ -0,0 +1,34 @@ + +local SCHEMATICS = require 'domain.definitions.schematics' +local TileSet = {} +local _cache = {} + +local function _initTileset(info, texture) + local g = love.graphics + local w, h = texture:getDimensions() + local mapping = info.mapping + local quads = {} + local offsets = {} + if mapping then + for name, dim in pairs(mapping) do + local tile = SCHEMATICS[name] + quads[tile] = g.newQuad(dim[1], dim[2], dim[3], dim[4], w, h) + offsets[tile] = { dim[5], dim[6] } + end + end + return { + texture = info.texture, + quads = quads, + offsets = offsets, + } +end + +function TileSet.new(name, info, texture) + local tileset = _cache[name] if not tileset then + tileset = _initTileset(info, texture) + end + return tileset +end + +return TileSet + diff --git a/game/setup.lua b/game/setup.lua new file mode 100644 index 00000000..2355cf8c --- /dev/null +++ b/game/setup.lua @@ -0,0 +1,56 @@ +--MODULE FOR SETUP STUFF-- + +local setup = {} + +-------------------- +--SETUP FUNCTIONS +-------------------- + +--Set game's global variables, random seed, window configuration and anything else needed +function setup.config() + + VERSION = "0.0.4" + DEV = true + + --RANDOM SEED-- + love.math.setRandomSeed( os.time() ) + + --TIMERS-- + MAIN_TIMER = Timer.new() --General Timer + + + --GLOBAL VARIABLES-- + DEBUG = false --DEBUG mode status + + O_WIN_W = 1280 --The original width of your game. Work with this value when using res_manager multiple resolutions support + O_WIN_H = 720 --The original height of your game. Work with this value when using res_manager multiple resolutions support + + --INITIALIZING TABLES-- + --Drawing Tables + DRAW_TABLE = { + BG = {}, --Background (bottom layer, first to draw) + L1 = {}, --Layer 1 + L2 = {}, --Layer 2 + HUD_BG = {}, --HUD lower (drawn before HUD) + HUD = {}, --HUD (top layer, second to last to draw) + GUI = {}, --Graphic User Interface (top layer, last to draw) + } + --Other Tables + SUBTP_TABLE = {} --Table with tables for each subtype (for fast lookup) + ID_TABLE = {} --Table with elements with Ids (for fast lookup) + + --AUDIO-- + SFX = { --Table containing all the sound fx + } + + BGM = { --Table containing all the background music tracks + } + + + --SHADERS-- + -- + +end + +--Return functions +return setup diff --git a/game/tests/README.md b/game/tests/README.md new file mode 100644 index 00000000..bdbae57e --- /dev/null +++ b/game/tests/README.md @@ -0,0 +1,13 @@ + +# Tests + +## About + +Tests are important for us. When we develop a big new module, we want to make sure it is working by itself before shoving it into the other parts of the code. +The files on this folder must be required from the `game/` folder. Why? Because that is where the game runs from! This is implemented so tests can run by themselves as well as while `ingame`. + +## List of Tests + +1. random +2. sector1 +3. bulksectorgenerator diff --git a/game/tests/aptitude.lua b/game/tests/aptitude.lua new file mode 100644 index 00000000..e4434cdd --- /dev/null +++ b/game/tests/aptitude.lua @@ -0,0 +1,28 @@ + +local ANSICOLOR = require 'lux.term.ansicolors' +local APT = require 'domain.definitions.aptitude' + +return function() + + print[[ +| lv | -2 | -1 | 0 | +1 | +2 | +|----|-----|-----|-----|-----|-----|]] + + local acc = {} + for lv=1,20 do + local line = ("| %2d |"):format(lv) + local line2 = "| |" + for apt=-2,2 do + local xp = APT.REQUIRED_ATTR_UPGRADE(apt, lv) + local i = apt+5 + line = line .. ("%s%+5d%s|"):format(ANSICOLOR.green, xp, ANSICOLOR.white) + xp = (acc[i] or 0) + xp + line2 = line2 .. ("%5d|"):format(xp) + acc[i] = xp + end + print(line) + print(line2) + end + +end + diff --git a/game/tests/bulk_sector_gen.lua b/game/tests/bulk_sector_gen.lua new file mode 100644 index 00000000..f9f7e86a --- /dev/null +++ b/game/tests/bulk_sector_gen.lua @@ -0,0 +1,13 @@ + +local RANDOM = require 'common.RANDOM' +local generator = require 'tests.sector01' +local seed = RANDOM.generateSeed() + +return function () + for i = 1, 10 do + local s = RANDOM.generate(0, seed * 2) + seed = s + generator(s) + end +end + diff --git a/game/tests/food.lua b/game/tests/food.lua new file mode 100644 index 00000000..d97c22ad --- /dev/null +++ b/game/tests/food.lua @@ -0,0 +1,14 @@ + +local APT = require 'domain.definitions.aptitude' + +local function food(efc, mtb) + return APT.STAMINA(efc, mtb-3) +end + +return function() + for efc=1,12,0.5 do + print(efc, food(efc, 1), food(efc, 2), food(efc, 3), food(efc, 4), + food(efc, 5)) + end +end + diff --git a/game/tests/hp.lua b/game/tests/hp.lua new file mode 100644 index 00000000..9a8e588a --- /dev/null +++ b/game/tests/hp.lua @@ -0,0 +1,13 @@ + +local APT = require 'domain.definitions.aptitude' + +local function hp(vit, con) + return APT.HP(vit, con-3) +end + +return function() + for vit=1,12,0.5 do + print(vit, hp(vit, 1), hp(vit, 2), hp(vit, 3), hp(vit, 4), hp(vit, 5)) + end +end + diff --git a/game/tests/init.lua b/game/tests/init.lua new file mode 100644 index 00000000..cfc07d26 --- /dev/null +++ b/game/tests/init.lua @@ -0,0 +1,40 @@ +-- +local RUNFLAGS = require 'infra.runflags' + +--CONTSTANTS-- +local TESTSDIR = "tests/" + +--LOCALS-- +local _module_name = RUNFLAGS.TEST +local _require_error_msg = [==[ + +Testing requested, but an error occurred. +Make sure the test file exists in game/test/.lua +Please specify test to run by running: +>> love game --test= + +If you're running `make`, the command should be: +>> make FLAGS="--test=" + +This error can also have been triggered by +syntax errors in the test file. Please refer +to the error message above for more information. + +]==] + + +--TESTING-- +if _module_name then + local module_path = ("%s%s"):format(TESTSDIR, _module_name) + local require_success, module_test = pcall(require, module_path) + if require_success then + print(("Running test: %s"):format(_module_name)) + local test_successful, err = pcall(module_test) + assert(test_successful, ("TEST FAILED!\n\n%s\n"):format(err)) + print("Test Successful!") + else + print(module_test) + print(_require_error_msg) + end +end + diff --git a/game/tests/interface.lua b/game/tests/interface.lua new file mode 100644 index 00000000..22e0692e --- /dev/null +++ b/game/tests/interface.lua @@ -0,0 +1,288 @@ + +local _GAUSSIAN_CODE = [=[ + +extern vec2 tex_size; +const int range = 5; + +vec4 effect(vec4 color, Image tex, vec2 uv, vec2 screen_coords) { + vec2 pixel_size = 1.0 / tex_size; + float alpha = 0.0; + float ponder = 0.0; + for (int i = -range; i < range; i++) { + for (int j = -range; j < range; j++) { + float weight = -(distance(vec2(j, i), vec2(0.0, 0.0)) - 2.0 * range); + alpha += weight * (Texel(tex, uv + vec2(j, i) * pixel_size)).w; + ponder += weight; + } + } + alpha = alpha / ponder; + return vec4(1, 1, 1, alpha) * color; +} + +]=] + +local WIDTH = 1280 +local HEIGHT = 720 + +local COLORS = { + WHITE = {1, 1, 1}, + BLACK = {0, 0, 0}, + DARK = {12/255, 12/255, 12/255}, + NEUTRAL = {0, 0, 0, 0}, + GREEN = {0.1, 0.9, 0.3}, + PP = {0.6, 0.2, 0.6}, + LIGHT = {0.75, 0.75, 0.75}, + EMPTY = {0.2, .15, 0.05}, + COR = {0xbe/255, 0x76/255, 0x3a/255}, + ARC = {0x6e/255, 0x60/255, 0xaa/255}, + ANI = {0x77/255, 0xb9/255, 0x55/255}, +} + +local QUIT_KEY = { + escape = true, + f8 = true, +} + +return function () + local _fontH1 + local _fontBold + local _fontText + local _font_cache = {} + + local _fake_minimap + local _pixel + local _gaussian_shader + + function love.load() + local window = love.window + local graphics = love.graphics + + -- resolution + window.setMode(WIDTH, HEIGHT) + + -- fonts + _font_cache.h1 = {} + _font_cache.bold = {} + _font_cache.text = {} + function _fontH1(size) + local font = _font_cache.h1[size] if not font then + font = graphics.setFont("assets/font/Anton.ttf", size) + _font_cache.h1[size] = font + end + graphics.setFont(font) + return font + end + function _fontBold(size) + local font = _font_cache.h1[size] if not font then + font = graphics.newFont("assets/font/SairaCondensed-ExtraBold.ttf", size) + _font_cache.bold[size] = font + end + graphics.setFont(font) + return font + end + function _fontText(size) + local font = _font_cache.h1[size] if not font then + font = graphics.newFont("assets/font/SairaCondensed-Medium.ttf", size) + _font_cache.text[size] = font + end + graphics.setFont(font) + return font + end + + -- textures + _fake_minimap = graphics.newImage("assets/texture/fakeminimap.png") + _pixel = graphics.newImage("assets/texture/pixel.png") + + -- shaders + _gaussian_shader = graphics.newShader(_GAUSSIAN_CODE) + end + + function love.keypressed(key) + if QUIT_KEY[key] then + love.event.quit() + end + end + + function love.update(dt) + end + + function love.draw() + local graphics = love.graphics + + -- margin & other values + local mg = 24 + local width = 320 + local length = width-mg*2 + local attr_width = length/4 + local shape = graphics.newMesh({ + {width, 0, + 1, 0}, + {-mg, 0, + 0, 0}, + {-mg, mg+HEIGHT/2, + 0, 1}, + {0, 2*mg+HEIGHT/2, + 0, 0}, + {0, HEIGHT, + 0, 1}, + {width, HEIGHT, + 1, 1}, + }, 'fan', 'dynamic') + shape:setTexture(_pixel) + local countour = { + -mg/2, 0, + -mg/2, mg/2+HEIGHT/2, + mg/2, 2*mg-mg/2+HEIGHT/2, + mg/2, HEIGHT, + } + + + graphics.setBackgroundColor(COLORS.NEUTRAL) + graphics.setColor(COLORS.WHITE) + graphics.draw(_fake_minimap, 0, 0, 0, + WIDTH/_fake_minimap:getWidth(), + 960/_fake_minimap:getHeight()) + graphics.push() + graphics.translate(960, 0) + --[[-- left + --]]-- or right? + + -- panel + graphics.push() + graphics.setColor(0, 0, 0, 0.2) + --_gaussian_shader:send("tex_size", {1, 1}) + --graphics.setShader(_gaussian_shader) + graphics.draw(shape, -8, 0) + graphics.setColor(COLORS.DARK) + graphics.setShader() + graphics.draw(shape, 0, 0) + graphics.setColor(COLORS.WHITE) + graphics.setLineWidth(2) + graphics.line(countour) + graphics.translate(8, 0) + graphics.line(countour) + graphics.pop() + + + + -- character name + _fontBold(24) + graphics.translate(mg, mg) + graphics.setColor(COLORS.WHITE) + graphics.printf("Charname the Background", 0, -8, length, "left") + + -- lifebar + _fontText(20) + graphics.translate(0, 48) + graphics.push() + graphics.setColor(COLORS.EMPTY) + graphics.rectangle("fill", 0, 0, length, 12) + graphics.translate(-1, -1) + graphics.setColor(COLORS.GREEN) + graphics.rectangle("fill", 0, 0, 28/32*length, 12) + graphics.translate(8, -16) + graphics.setColor(COLORS.BLACK) + graphics.printf("HP 28/032", 0, 0, length-8, "left") + graphics.translate(-2, -2) + graphics.setColor(COLORS.WHITE) + graphics.printf("HP 28/032", 0, 0, length-8, "left") + graphics.pop() + + -- cooldown bar + graphics.translate(0, 32) + graphics.push() + graphics.setColor(COLORS.EMPTY) + graphics.setLineWidth(1) + graphics.rectangle("fill", 0, 0, length, 12) + graphics.translate(-1, -1) + graphics.setColor(COLORS.PP) + graphics.rectangle("fill", 0, 0, 0.6*length, 12) + graphics.translate(8, -16) + graphics.setColor(COLORS.BLACK) + graphics.printf("PP 60/100", 0, 0, length-8, "left") + graphics.translate(-2, -2) + graphics.setColor(COLORS.WHITE) + graphics.printf("PP 60/100", 0, 0, length-8, "left") + graphics.pop() + + -- minimap + graphics.translate(0, 48) + graphics.setColor(COLORS.EMPTY) + graphics.rectangle("fill", 0, 0, length, 192) + graphics.setColor(COLORS.WHITE) + graphics.draw(_fake_minimap, 0, 0, 0, length/_fake_minimap:getWidth(), 1) + + -- attributes + _fontText(24) + graphics.translate(mg*4/3, 192+2*mg) + graphics.push() + graphics.push() + -- COR + graphics.setColor(COLORS.WHITE) + graphics.printf("COR: 5", 0, 0, attr_width, "left") + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.EMPTY) + graphics.rectangle("fill", 0, 0, attr_width, 16) + graphics.setColor(COLORS.COR) + graphics.rectangle("fill", 0, 0, 0.4*attr_width, 16) + graphics.pop() + -- ARC + graphics.translate(attr_width + mg/2, 0) + graphics.push() + graphics.setColor(COLORS.WHITE) + graphics.printf("ARC: 7", 0, 0, attr_width, "left") + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.EMPTY) + graphics.rectangle("fill", 0, 0, attr_width, 16) + graphics.setColor(COLORS.ARC) + graphics.rectangle("fill", 0, 0, 0.8*attr_width, 16) + graphics.pop() + -- ANI + graphics.translate(attr_width + mg/2, 0) + graphics.setColor(COLORS.WHITE) + graphics.printf("ANI: 3", 0, 0, attr_width, "left") + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.EMPTY) + graphics.rectangle("fill", 0, 0, attr_width, 16) + graphics.setColor(COLORS.ANI) + graphics.rectangle("fill", 0, 0, 0.2*attr_width, 16) + graphics.pop() + + -- PLACEMENTS + graphics.translate(0, mg*3) + graphics.setColor(COLORS.WHITE) + graphics.printf("PLACEMENTS", 0, 0, 128, "left") + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.EMPTY) + local len = 32 + for i = 1, 6 do + graphics.rectangle("fill", (i-1)*(len+4), 0, len, len) + end + + -- TRAITS + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.WHITE) + graphics.printf("TRAITS", 0, 0, 128, "left") + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.EMPTY) + local len = 32 + for i = 1, 6 do + graphics.rectangle("fill", (i-1)*(len+4), 0, len, len) + end + + -- CONDITIONS + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.WHITE) + graphics.printf("CONDITIONS", 0, 0, 128, "left") + graphics.translate(0, mg*1.5) + graphics.setColor(COLORS.EMPTY) + local len = 32 + for i = 1, 6 do + graphics.rectangle("fill", (i-1)*(len+4), 0, len, len) + end + + graphics.pop() + end + + love.load() +end diff --git a/game/tests/random.lua b/game/tests/random.lua new file mode 100644 index 00000000..f1789e6d --- /dev/null +++ b/game/tests/random.lua @@ -0,0 +1,41 @@ + +local RANDOM = require 'common.random' +local _seed = RANDOM.generateSeed() +local _setSeed = love and love.math.setRandomSeed or math.randomseed + +return function () + _setSeed(_seed) + + for i = 1, 16 do + print("odd 0-20", RANDOM.generateOdd(0, 20)) + end + + for i = 1, 16 do + print("odd 1-20", RANDOM.generateOdd(1, 20)) + end + + for i = 1, 16 do + print("odd 0-19", RANDOM.generateOdd(0, 19)) + end + + for i = 1, 16 do + print("odd 1-19", RANDOM.generateOdd(1, 19)) + end + + for i = 1, 16 do + print("even 0-20", RANDOM.generateEven(0, 20)) + end + + for i = 1, 16 do + print("even 1-20", RANDOM.generateEven(1, 20)) + end + + for i = 1, 16 do + print("even 0-19", RANDOM.generateEven(0, 19)) + end + + for i = 1, 16 do + print("even 1-19", RANDOM.generateEven(1, 19)) + end +end + diff --git a/game/tests/routegen.lua b/game/tests/routegen.lua new file mode 100644 index 00000000..61490bae --- /dev/null +++ b/game/tests/routegen.lua @@ -0,0 +1,16 @@ + +local ROUTE_BUILDER = require 'domain.builders.route' +local IDGenerator = require 'common.idgenerator' + + +return function() + local idgen = IDGenerator() + local route_data = ROUTE_BUILDER.build(idgen.newID(), + { + species = 'hearthborn', + background = 'brawler', + } + ) + printf("Generated route: %s", route_data.id) +end + diff --git a/game/tests/sector01.lua b/game/tests/sector01.lua new file mode 100644 index 00000000..9b2f074f --- /dev/null +++ b/game/tests/sector01.lua @@ -0,0 +1,34 @@ + +-- dependencies +local DB = require 'database' +local RANDOM = require 'common.random' +local TRANSFORMERS = require 'lux.pack' 'domain.transformers' +local SectorGrid = require 'domain.transformers.helpers.sectorgrid' + +-- seed value +local _seed = RANDOM.generateSeed() +RANDOM.setSeed(_seed) + +-- generation of sector, pretty straightforward +local function generate() + local sectorinfo = {} + + for _,transformer in DB.schemaFor('sector') do + local spec = DB.loadSpec("sector", "sector01")[transformer.id] + if spec then + sectorinfo = TRANSFORMERS[transformer.id].process(sectorinfo, spec) + end + end + + print(sectorinfo.grid, sectorinfo.grid:getDim()) + return sectorinfo +end + +-- generation of sector with controlled seeds +return function(s) + local seed = s or RANDOM.generateSeed() + RANDOM.setSeed(seed) + print("SEED:", seed) + return generate() +end + diff --git a/game/view/actor.lua b/game/view/actor.lua new file mode 100644 index 00000000..288eb3ef --- /dev/null +++ b/game/view/actor.lua @@ -0,0 +1,164 @@ + +local RES = require 'resources' +local FONT = require 'view.helpers.font' +local ACTIONDEFS = require 'domain.definitions.action' +local COLORS = require 'domain.definitions.colors' +local DEFS = require 'domain.definitions' + +local ACTOR_PANEL = require 'view.actor.panel' +local ACTOR_HEADER = require 'view.actor.header' +local ACTOR_MINIMAP = require 'view.actor.minimap' +local ACTOR_ATTR = require 'view.actor.attr' +local ACTOR_WIDGETS = require 'view.actor.widgets' + +local math = require 'common.math' + +local ActorView = Class{ + __includes = { ELEMENT } +} + +local _PANEL_MG = 24 +local _PANEL_PD = 8 +local _PANEL_WIDTH +local _PANEL_HEIGHT +local _PANEL_INNERWIDTH + +local _WIDTH, _HEIGHT + + +local function _initGraphicValues() + local g = love.graphics + _WIDTH, _HEIGHT = g.getDimensions() + -- panel + _PANEL_WIDTH = g.getWidth()/4 + _PANEL_HEIGHT = g.getHeight() + _PANEL_INNERWIDTH = _PANEL_WIDTH - 2*_PANEL_PD - 2*_PANEL_MG + ACTOR_PANEL.init(_PANEL_WIDTH, _PANEL_HEIGHT, _PANEL_MG) + -- header + ACTOR_HEADER.init(_PANEL_WIDTH, _PANEL_MG, _PANEL_PD) + -- minimap + ACTOR_MINIMAP.init(_PANEL_INNERWIDTH, 192) + -- attributes + ACTOR_ATTR.init(_PANEL_INNERWIDTH) + -- widgets + ACTOR_WIDGETS.init() +end + +function ActorView:init(route) + + ELEMENT.init(self) + + self.route = route + self.actor = false + + _initGraphicValues() + +end + +function ActorView:loadActor() + local newactor = self.route.getControlledActor() + if self.actor ~= newactor and newactor then + self.actor = newactor + end + return self.actor +end + +function ActorView:draw() + local g = love.graphics + local actor = self:loadActor() + if not actor then return end + local cr,cg,cb = unpack(COLORS.NEUTRAL) + + -- always visible + g.push() + self:drawPanel(g) + self:drawHP(g, actor) + self:drawMiniMap(g, actor) + self:drawAttributes(g, actor) + self:drawWidgets(g, actor) + g.pop() + + -- only visible when holding button + if DEV then + local font = FONT.get("Text", 20) + local fps_str = ("fps: %d"):format(love.timer.getFPS()) + font:set() + g.setColor(1, 1, 1, 1) + g.print(fps_str, g.getWidth()- 40 - font:getWidth(fps_str), + g.getHeight() - 24) + end +end + +function ActorView:drawPanel(g) + g.setColor(COLORS.NEUTRAL) + g.translate(3/4*g.getWidth(), 0) + ACTOR_PANEL.draw(g, -_PANEL_MG*2, -_PANEL_MG) +end + +function ActorView:drawHP(g, actor) + local body = actor:getBody() + local hp, pp = body:getHP(), actor:getPP() + local max_hp, max_pp = body:getMaxHP(), DEFS.MAX_PP + local armor = body:getArmor() + -- character name + FONT.set("TextBold", 22) + g.translate(_PANEL_MG, _PANEL_MG) + g.setColor(COLORS.NEUTRAL) + g.print(("%s the %s"):format(self.route.getPlayerName(), actor:getTitle()), + 0, -8) + -- character hp & pp + g.translate(0, 48) + ACTOR_HEADER.drawBar(g, "HP", hp, max_hp, COLORS.SUCCESS, COLORS.NOTIFICATION, + armor) + g.translate(0, 32) + ACTOR_HEADER.drawBar(g, "PP", pp, max_pp, COLORS.PP, COLORS.PP) +end + +function ActorView:drawMiniMap(g, actor) + local sector = self.route.getCurrentSector() + ACTOR_MINIMAP.draw(g, actor, sector) +end + +function ActorView:drawAttributes(g, actor) + FONT.set("Text", 20) + g.translate(_PANEL_MG*4/3, 2*_PANEL_MG + 192) + + -- exp + g.push() + g.translate(0, -32) + g.setColor(COLORS.NEUTRAL) + g.print(("EXP: %04d"):format(actor:getExp()), 0, 0) + + -- packs + local packcount = actor:getPrizePackCount() + local packcolor = packcount > 0 and COLORS.VALID or COLORS.NEUTRAL + local packstr = { + COLORS.NEUTRAL, "PACKS: ", + packcolor, ("%02d"):format(packcount) + } + g.translate(_PANEL_INNERWIDTH/2 - 24, 0) + g.setColor(COLORS.NEUTRAL) + g.print(packstr, 0, 0) + g.pop() + + -- attributes + g.push() + ACTOR_ATTR.draw(g, actor, 'COR') + g.translate(_PANEL_INNERWIDTH/4 + _PANEL_MG/2, 0) + ACTOR_ATTR.draw(g, actor, 'ARC') + g.translate(_PANEL_INNERWIDTH/4 + _PANEL_MG/2, 0) + ACTOR_ATTR.draw(g, actor, 'ANI') + g.pop() +end + +function ActorView:drawWidgets(g, actor) + g.push() + g.translate(0, 16) + for wtype = 1, 3 do + ACTOR_WIDGETS.draw(g, actor, wtype) + end + g.pop() +end + +return ActorView + diff --git a/game/view/actor/attr.lua b/game/view/actor/attr.lua new file mode 100644 index 00000000..36011f75 --- /dev/null +++ b/game/view/actor/attr.lua @@ -0,0 +1,145 @@ + +local RES = require 'resources' +local RANDOM = require 'common.random' +local APT = require 'domain.definitions.aptitude' +local COLORS = require 'domain.definitions.colors' +local FONT = require 'view.helpers.font' +local PLAYSFX = require 'helpers.playsfx' + +local abs = math.abs +local min = math.min +local max = math.max +local pi = math.pi +local fmod = math.fmod + +local coresume = coroutine.resume +local cocreate = coroutine.create +local coyield = coroutine.yield + +local delta = love.timer.getDelta + +local _dt +local _barwidth +local _font +local _states = {} + +local function _newParticleSource() + local pixel = RES.loadTexture('pixel') + local particles = love.graphics.newParticleSystem(pixel, 128) + particles:setParticleLifetime(.75) + particles:setSizeVariation(0) + particles:setLinearDamping(6) + particles:setSpeed(256) + particles:setSpread(2*pi) + particles:setColors(COLORS.NEUTRAL, COLORS.TRANSP) + particles:setSizes(4) + particles:setEmissionArea('ellipse', 0, 0, 0, false) + particles:setTangentialAcceleration(-512) + return particles +end + +local function _renderAttribute(actor, attrname, particles) + local g = love.graphics + local progress = 0 + local lvl = actor:getAttrLevel(attrname) + while true do + -- update bar progress + local upgrade = actor:getAttrUpgrade(attrname) + local aptitude = actor:getAptitude(attrname) + local total_prev = APT.CUMULATIVE_REQUIRED_ATTR_UPGRADE(aptitude, lvl) + local total_next = APT.CUMULATIVE_REQUIRED_ATTR_UPGRADE(aptitude, lvl+1) + local target = min(1 , + max(0, (upgrade - total_prev) / (total_next - total_prev)) + ) + progress = min( + 1, max(0, progress + 8*(target-progress)*_dt) + ) + if abs(target - progress) < 0.01 then + progress = target + end + + -- check if level up + local newlvl = actor:getAttrLevel(attrname) + local offset = newlvl - lvl + local step = offset / abs(offset) + if newlvl ~= lvl and progress == 1 then + lvl = lvl + step + offset = offset - step + progress = 0 + particles:emit(48) + PLAYSFX('get-item') + local start = 0 + local rand = RANDOM.safeGenerate + while start <= 0.5 do + g.translate(2*(rand()*2-1), 2*(rand()*2-1)) + start = start + _dt + coyield(lvl, offset, progress, true) + end + end + + -- yield + coyield(lvl, offset, progress) + end +end + +local ATTR = {} + +function ATTR.init(width) + _barwidth = width/4 + _font = FONT.get("Text", 20) +end + +function ATTR.draw(g, actor, attrname) + g.push() + _dt = delta() + local actorstate = _states[actor] or {} + local attrstate = actorstate[attrname] or { + co = cocreate(_renderAttribute), + particles = _newParticleSource() + } + local particles = attrstate.particles + local _, rawlvl, offset, percent, rise = assert( + coresume(attrstate.co, actor, attrname, particles) + ) + actorstate[attrname] = attrstate + _states[actor] = actorstate + + + -- check if attribute is modified + local lvl = actor:getAttribute(attrname) - offset + local diff = lvl - rawlvl + local color = (diff > 0 and {0, 0.7, 1}) or + (diff < 0 and {1, 0.8, 0.2}) or + COLORS.NEUTRAL + + -- render bar + particles:update(_dt) + _font:set() + g.setColor(COLORS.NEUTRAL) + g.printf( + { + COLORS.NEUTRAL, + ("%s: "):format(attrname), + rise and COLORS.NEUTRAL or color, + ("%02d"):format(lvl) + }, + 0, 0, _barwidth, "left" + ) + g.translate(0, 32) + g.push() + if not rise then + g.setColor(COLORS.EMPTY) + end + g.rectangle("fill", 0, 0, _barwidth, 16) + if not rise then + g.setColor(COLORS[attrname]) + end + g.rectangle("fill", 0, 0, percent*_barwidth, 16) + g.setColor(COLORS.NEUTRAL) + g.pop() + g.draw(particles, 48, -_font:getHeight()/2) + g.pop() +end + +return ATTR + diff --git a/game/view/actor/header.lua b/game/view/actor/header.lua new file mode 100644 index 00000000..2d2dfd7f --- /dev/null +++ b/game/view/actor/header.lua @@ -0,0 +1,51 @@ + +local COLORS = require 'domain.definitions.colors' +local FONT = require 'view.helpers.font' + +local math = require 'common.math' +local abs = math.abs + +local HEADER = {} + +local _SIGNATURE = "%s %d/%d" +local _LENGTH + +local _states = {} + +function HEADER.init(width, mg, pd) + _LENGTH = width - 2*mg - 2*pd +end + +function HEADER.drawBar(g, attrname, current, max, color_full, color_empty, + extra) + max = max + (extra or 0) + local state = _states[attrname] or 0 + local percent = current/max + state = state + (percent - state) * 1/8 + if abs(percent - state) <= 0.001 then state = percent end + _states[attrname] = state + + FONT.set("Text", 20) + g.push() + g.setColor(COLORS.EMPTY) + g.rectangle("fill", 0, 0, _LENGTH, 12) + g.translate(-1, -1) + g.setColor(state*color_full + (1-state)*color_empty) + g.rectangle("fill", 0, 0, state*_LENGTH, 12) + if extra then + g.setColor(COLORS.NEUTRAL) + g.rectangle("fill", state*_LENGTH, 0, extra/max*_LENGTH, 12) + end + g.translate(8, -16) + g.setColor(COLORS.BLACK) + local text = _SIGNATURE:format(attrname, current, max - (extra or 0)) + if extra and extra > 0 then text = text .. " +" .. extra end + g.printf(text, 0, 0, _LENGTH-8, "left") + g.translate(-2, -2) + g.setColor(COLORS.NEUTRAL) + g.printf(text, 0, 0, _LENGTH-8, "left") + g.pop() +end + +return HEADER + diff --git a/game/view/actor/minimap.lua b/game/view/actor/minimap.lua new file mode 100644 index 00000000..4da13da5 --- /dev/null +++ b/game/view/actor/minimap.lua @@ -0,0 +1,109 @@ + +local Color = require 'common.color' +local SCHEMATICS = require 'domain.definitions.schematics' +local COLORS = require 'domain.definitions.colors' +local FONT = require 'view.helpers.font' + +local min = math.min +local max = math.max + +local _WIDTH, _HEIGHT +local _TILE_W = 8 +local _TILE_H = 8 +local _RAD_W = _TILE_W/2 +local _RAD_H = _TILE_H/2 +local _TILE_POLYGON = { + 0, 0, + 1, 0, + 1, 1, + 0, 1, +} +local _BODY_POLYGON = { + 0.5, 0, + 1, 0.5, + 0.5, 1, + 0, 0.5, +} + +local _TILE_COLORS = { + [SCHEMATICS.WALL] = Color:new {0.3, 0.5, 0.9, 1}, + [SCHEMATICS.FLOOR] = Color:new {0.1, 0.3, 0.7, 1}, + [SCHEMATICS.EXIT] = Color.fromInt {200, 200, 40, 255}, + [SCHEMATICS.ALTAR] = Color.fromInt {30, 100, 240, 255}, +} + +local _map + +local MINIMAP = {} + +function MINIMAP.init(width, height) + _WIDTH, _HEIGHT = width, height + _map = love.graphics.newCanvas(width, height) + _font = FONT.get("Text", 20) +end + +function MINIMAP.draw(g, actor, sector) + local w, h = sector:getDimensions() + local ai, aj = actor:getPos() + local tiles = sector.tiles + local zonename = sector:getZoneName() + local nr, ng, nb = unpack(COLORS.NEUTRAL) + local fov = actor:getFov(sector) + + _font:set() + g.translate(0, 48) + do + g.setCanvas(_map) + g.push() + g.origin() + g.clear() + g.setColor(COLORS.EMPTY) + g.rectangle("fill", 0, 0, _WIDTH, _HEIGHT) + g.setColor(COLORS.NEUTRAL) + local translation_x = -aj*_TILE_W + _WIDTH/2 + local translation_y = -ai*_TILE_H + _HEIGHT/2 + g.translate( + translation_x, + translation_y + ) + --]]-- + g.scale(_TILE_W, _TILE_H) + for i = 0, h-1 do + for j = 0, w-1 do + local ti, tj = i+1, j+1 + local tile = tiles[ti][tj] + local seen = fov[ti][tj] + if tile and seen then + local x, y = j, i + g.push() + g.setColor(_TILE_COLORS[tile.type]) + g.translate(x, y) + g.polygon("fill", _TILE_POLYGON) + if ai == ti and aj == tj then + g.setColor(COLORS.NEUTRAL) + g.polygon("fill", _BODY_POLYGON) + elseif seen > 0 and sector:getBodyAt(ti, tj) then + g.setColor(1, 0.4, 0.1) + g.polygon("fill", _BODY_POLYGON) + end + g.pop() + end + end + end + g.pop() + g.setCanvas() + end + g.setColor(COLORS.NEUTRAL) + g.draw(_map, 0, 0) + g.push() + g.translate(2, _HEIGHT-20) + g.setColor(COLORS.BLACK) + g.printf(zonename:upper(), 0, 0, _WIDTH-8, "right") + g.translate(-2, -2) + g.setColor(COLORS.NEUTRAL) + g.printf(zonename:upper(), 0, 0, _WIDTH-8, "right") + g.pop() +end + +return MINIMAP + diff --git a/game/view/actor/panel.lua b/game/view/actor/panel.lua new file mode 100644 index 00000000..008f0983 --- /dev/null +++ b/game/view/actor/panel.lua @@ -0,0 +1,76 @@ + +local RES = require 'resources' +local COLORS = require 'domain.definitions.colors' +local SHADERS = require 'view.shaders' + +local PANEL = {} + +local _panel + +function PANEL.init(width, height, mg) + local g = love.graphics + local points = { + {width+mg, 0}, + {0, 0}, + {0, mg+height/2}, + {mg, 2*mg+height/2}, + {mg, height}, + {width+mg, height}, + } + local contour = { + mg/2, -2*mg, + mg/2, mg+height/2, + 3/2*mg, 2*mg+height/2, + 3/2*mg, height+2*mg, + } + for _,point in ipairs(points) do + point[3] = point[1]/width + point[4] = point[2]/height + end + local mesh = g.newMesh(points, 'fan', 'static') + local canvas = g.newCanvas(width+3*mg, height+2*mg) + local theme = RES.loadTexture("panel-theme") + theme:setWrap('repeat') + mesh:setTexture(theme) + canvas:setFilter('linear', 'linear') + canvas:renderTo(function() + g.push() + g.setBackgroundColor(COLORS.VOID) + g.clear() + g.origin() + g.translate(mg, mg) + g.setColor(1, 1, 1) + g.draw(mesh, 0, 0) + g.pop() + end) + local dropshadow = g.newImage(canvas:newImageData()) + canvas:renderTo(function () + g.push() + g.setBackgroundColor(COLORS.VOID) + g.clear() + g.origin() + g.translate(mg, mg) + SHADERS.gaussian:send('tex_size', { canvas:getDimensions() }) + SHADERS.gaussian:send('range', 12) + g.setShader(SHADERS.gaussian) + g.setColor(0, 0, 0, 1) + g.draw(dropshadow, -1.5*mg, -mg) + g.setShader() + g.setColor(1, 1, 1) + g.draw(mesh, 0, 0) + g.setColor(COLORS.NEUTRAL) + g.setLineWidth(2) + g.line(contour) + g.translate(8, 0) + g.line(contour) + g.pop() + end) + _panel = canvas +end + +function PANEL.draw(g, ...) + g.draw(_panel, ...) +end + +return PANEL + diff --git a/game/view/actor/widgets.lua b/game/view/actor/widgets.lua new file mode 100644 index 00000000..35f3f2ea --- /dev/null +++ b/game/view/actor/widgets.lua @@ -0,0 +1,107 @@ + +local RES = require 'resources' +local PLACEMENTS = require 'domain.definitions.placements' +local COLORS = require 'domain.definitions.colors' +local FONT = require 'view.helpers.font' + +local _font +local _MG = 24 +local _PD = 4 +local _SQRSIZE = 36 + +local _WIDGETGETTER +local _WIDGETSTRING = { + "PLACEMENTS", + "TRAITS", + "CONDITIONS", +} + +local function _getPlacements(body) + local placements, n = {}, 0 + for _,slot in ipairs(PLACEMENTS) do + local placement = body:getEquipmentAt(slot) + n = n + 1 + placements[n] = placement + end + return placements +end + +local function _getTraits(body) + local traits, n = {}, 0 + for i,widget in body:eachWidget() do + if not widget:getWidgetPlacement() and widget:isWidgetPermanent() then + n = n + 1 + traits[n] = widget + end + end + return traits +end + +local function _getConditions(body) + local conditions, n = {}, 0 + for i,widget in body:eachWidget() do + if not widget:getWidgetPlacement() and not widget:isWidgetPermanent() then + n = n + 1 + conditions[n] = widget + end + end + return conditions +end + + +local WIDGETS = {} + +function WIDGETS.init() + _font = FONT.get("Text", 20) + _WIDGETGETTER = { + _getPlacements, + _getTraits, + _getConditions + } +end + +function WIDGETS.draw(g, actor, wtype) + local widgets = _WIDGETGETTER[wtype](actor:getBody()) + + -- set position + g.translate(0, _MG*2) + g.setColor(COLORS.NEUTRAL) + g.print(_WIDGETSTRING[wtype], 0, 0) + g.translate(0, _font:getHeight()) + + for i = 1, 5 do + -- draw the first 5 widgets + g.push() + g.translate((i - 1) * (_SQRSIZE + _PD), 0) + g.setColor(COLORS.EMPTY) + g.rectangle("fill", 0, 0, _SQRSIZE, _SQRSIZE) + local widget = widgets[i] + if widget then + local icon = RES.loadTexture(widget:getIconTexture() or 'icon-none') + local iw, ih = icon:getDimensions() + icon:setFilter('linear', 'linear') + g.setColor(COLORS[widget:getRelatedAttr()]) + g.rectangle("fill", 0, 0, _SQRSIZE, _SQRSIZE) + g.setColor(COLORS.BLACK) + g.draw(icon, 0, 0, 0, _SQRSIZE/iw, _SQRSIZE/ih) + elseif wtype == 1 then + g.setColor(COLORS.BLACK) + g.printf(PLACEMENTS[PLACEMENTS[i]]:lower(), 0, 0, _SQRSIZE, "center") + end + g.pop() + end + + if widgets[6] then + -- if there are more than 5 widgets + g.push() + g.translate(5 * (_SQRSIZE + _PD), 0) + g.setColor(COLORS.DARK) + g.rectangle("fill", 0, 0, _SQRSIZE, _SQRSIZE) + g.setColor(COLORS.NEUTRAL) + g.printf("...", 0, 0, _SQRSIZE, "center") + g.pop() + end +end + +return WIDGETS + diff --git a/game/view/cardinfo.lua b/game/view/cardinfo.lua new file mode 100644 index 00000000..84439c57 --- /dev/null +++ b/game/view/cardinfo.lua @@ -0,0 +1,30 @@ + +local CARD = require 'view.helpers.card' +local vec2 = require 'cpml' .vec2 + +local _W + +local CardInfo = Class{ + __includes = { ELEMENT } +} + +function CardInfo:init() + + ELEMENT.init(self) + + self.card = nil + + _W = love.graphics.getDimensions()/3 + +end + +function CardInfo:set(card) + self.card = card +end + +function CardInfo:draw() + CARD.drawInfo(self.card, 20, 40, _W, 1, nil, true) +end + +return CardInfo + diff --git a/game/view/cardlist.lua b/game/view/cardlist.lua new file mode 100644 index 00000000..d54f7ba1 --- /dev/null +++ b/game/view/cardlist.lua @@ -0,0 +1,183 @@ + +local math = require 'common.math' +local HoldBar = require 'view.helpers.holdbar' +local CARD = require 'view.helpers.card' +local FONT = require 'view.helpers.font' +local COLORS = require 'domain.definitions.colors' + +-- MODULE ----------------------------------- +local View = Class({ + __includes = ELEMENT +}) + +-- CONSTS ----------------------------------- +local _EMPTY = {} +local _ENTER_TIMER = "manage_card_list_enter" +local _TEXT_TIMER = "manage_card_list_text" +local _CONSUMED_TIMER = "consumed_card:" +local _ENTER_SPEED = .2 +local _MOVE_SMOOTH = 1/5 +local _EPSILON = 2e-5 +local _SIN_INTERVAL = 1/2^5 +local _PD = 40 +local _ARRSIZE = 20 +local _MAX_Y_OFFSET = 768 +local _PI = math.pi +local _CONSUME_TEXT = "consume (+1 EXP)" +local _WIDTH, _HEIGHT +local _CW, _CH + +-- LOCAL VARS +local _font + +-- LOCAL METHODS ---------------------------- +local function _initGraphicValues() + local g = love.graphics + _WIDTH, _HEIGHT = g.getDimensions() + _font = FONT.get("TextBold", 20) + _CW = CARD.getWidth() + _CH = CARD.getHeight() +end + +local function _next_circular(i, len, n) + if n == 0 then return i end + return _next_circular(i % len + 1, len, n - 1) +end + +local function _prev_circular(i, len, n) + if n == 0 then return i end + return _prev_circular((i - 2) % len + 1, len, n - 1) +end + +-- PUBLIC METHODS --------------------------- +function View:init() + ELEMENT.init(self) + + self.enter = 0 + self.text = 0 + self.selection = 1 + self.cursor = 0 + self.move = self.selection + self.offsets = {} + self.card_list = _EMPTY + + _initGraphicValues() +end + +function View:open(card_list) + self.card_list = card_list + self.selection = math.ceil(#card_list/2) + self:removeTimer(_ENTER_TIMER, MAIN_TIMER) + self:addTimer(_ENTER_TIMER, MAIN_TIMER, "tween", + _ENTER_SPEED, self, { enter=1, text=1 }, "out-quad") +end + +function View:close() + self:removeTimer(_ENTER_TIMER, MAIN_TIMER) + self:addTimer(_ENTER_TIMER, MAIN_TIMER, "tween", + _ENTER_SPEED, self, { enter=0, text=0 }, "out-quad", + function () + self.card_list = _EMPTY + self:destroy() + end) +end + +function View:selectPrev(n) + n = n or 1 + self.selection = _prev_circular(self.selection, #self.card_list, n) +end + +function View:selectNext() + n = n or 1 + self.selection = _next_circular(self.selection, #self.card_list, n) +end + +function View:setSelection(n) + self.selection = n +end + +function View:updateSelection() + local selection = self.selection + local card_list_size = #self.card_list + for i = selection, card_list_size do + self.offsets[i] = 1 + end + self.selection = math.min(selection, card_list_size) +end + +function View:isCardListEmpty() + return #self.card_list == 0 +end + +function View:draw() + local g = love.graphics + local enter = self.enter + g.push() + + if enter > 0 then + self:drawBG(g, enter) + self:drawCards(g, enter) + end + + g.pop() +end + +function View:drawBG(g, enter) + g.setColor(0, 0, 0, enter*0.5) + g.rectangle("fill", 0, 0, _WIDTH, _HEIGHT) +end + +function View:drawCards(g, enter) + local selection = self.selection + local card_list = self.card_list + local card_list_size = #card_list + + g.push() + + -- smooth enter! + g.translate(math.round((_WIDTH/2)*(1-enter)+_WIDTH/2-_CW/2), + math.round(3*_HEIGHT/7-_CH/2)) + + -- smooth movement! + self.move = self.move + (selection - self.move)*_MOVE_SMOOTH + if (self.move-selection)^2 <= _EPSILON then self.move = selection end + g.translate(math.round(-(_CW+_PD)*(self.move-1)), 0) + + -- draw each card + for i = 1, card_list_size do + g.push() + local focus = selection == i + local dist = math.abs(selection-i) + local offset = self.offsets[i] or 0 + + -- smooth offset when consuming cards + offset = offset > _EPSILON and offset - offset * _MOVE_SMOOTH or 0 + self.offsets[i] = offset + g.translate((_CW+_PD)*(i-1+offset), 0) + CARD.draw(card_list[i], 0, 0, focus, + dist>0 and enter/dist or enter) + g.pop() + end + g.pop() + + -- draw selection + g.push() + g.translate(math.round(_WIDTH/2), + math.round(3*_HEIGHT/7-_CH/2)) + enter = self.text + if enter > 0 then + if card_list[selection] then + self:drawCardDesc(g, card_list[selection], enter) + end + end + g.pop() +end + +function View:drawCardDesc(g, card, enter) + g.push() + g.translate(-1.5*_CW, _CH+_PD) + CARD.drawInfo(card, 0, 0, 4*_CW, enter) + g.pop() +end + +return View diff --git a/game/view/charabuild.lua b/game/view/charabuild.lua new file mode 100644 index 00000000..a66ce16f --- /dev/null +++ b/game/view/charabuild.lua @@ -0,0 +1,243 @@ +--CHARACTER BUILDER VIEW-- +local DB = require 'database' +local RES = require 'resources' +local FONT = require 'view.helpers.font' +local COLORS = require 'domain.definitions.colors' + + +--CONSTANTS-- +local _CONTEXTS = { + "Species", + "Background", + "Are you sure?", +} + +local _PLAYER_FIELDS = { + 'species', + 'background', + 'confirm', +} + +local _SPECS = { + species = 'body', + background = 'actor', +} + +local _FADE_TIME = .4 +local _PD = 16 +local _LH = 1.5 +local _WIDTH +local _HEIGHT + + +--LOCALS-- +local _header_font +local _content_font +local _menu_values + + +local sin = math.sin +local pi = math.pi +local delta = love.timer.getDelta + +--MODULE-- +local View = Class{ + __includes = { ELEMENT } +} + + +--LOCAL FUNCTIONS-- + +local function _initValues() + local playables = DB.loadSetting("playable") + local g = love.graphics + _menu_values = { + species = playables.species, + background = playables.background, + confirm = {true, false}, + } + _WIDTH, _HEIGHT = g.getDimensions() + _header_font = FONT.get("Text", 32) + _content_font = FONT.get("Text", 24) +end + +local function _getSpec(field, specname) + return DB.loadSpec(_SPECS[field], specname) +end + +function View:init() + ELEMENT.init(self) + + self.enter = 0 + self.context = 1 + self.selection = 1 + self.arrow = 0 + self.leave = false + self.sprite = false + + _initValues() +end + +function View:open(player_info) + self.player_info = player_info + self:removeTimer('charabuild_fade', MAIN_TIMER) + self:addTimer('charabuild_fade', MAIN_TIMER, "tween", _FADE_TIME, + self, { enter = 1 }, "linear" + ) +end + +function View:close(after) + self:removeTimer('charabuild_fade', MAIN_TIMER) + self:addTimer('charabuild_fade', MAIN_TIMER, "tween", _FADE_TIME, + self, { enter = 0 }, "linear", after + ) +end + +function View:reset() + self.context = 1 + self.selection = 1 + self.sprite = false +end + +function View:selectPrev() + local context = _PLAYER_FIELDS[self.context] + local menu_size = #_menu_values[context] + self.selection = (self.selection + menu_size - 2) % menu_size + 1 + if self.context == 1 then self.sprite = false end +end + +function View:selectNext() + local context = _PLAYER_FIELDS[self.context] + local menu_size = #_menu_values[context] + self.selection = self.selection % menu_size + 1 + if self.context == 1 then self.sprite = false end +end + +function View:confirm() + local context = _PLAYER_FIELDS[self.context] + self.player_info[context] = _menu_values[context][self.selection] + self.context = self.context + 1 + self.selection = 1 +end + +function View:cancel() + local context = _PLAYER_FIELDS[self.context] + self.player_info[context] = false + self.context = self.context - 1 + if self.context < 1 then + self.leave = true + self.context = 1 + end + self.selection = 1 +end + +function View:getContext() + return self.context +end + +function View:draw() + local g = love.graphics + local enter = self.enter + local player_info = self.player_info + local context = _PLAYER_FIELDS[self.context] + local selection = self.selection + + g.setBackgroundColor(0, 0, 0) + g.setColor(1, 1, 1, enter) + + self:drawSaved(g, player_info) + self:drawContext(g) + self:drawSpecies(g, player_info) + if self.context > #_CONTEXTS then return end + self:drawSelection(g, context, selection) + +end + +function View:drawSpecies(g, player_info) + g.push() + g.translate(_WIDTH/2, _HEIGHT/2) + local species = player_info.species or _menu_values.species[self.selection] + if not self.sprite and species then + local appearance_specname = _getSpec('species', species)['appearance'] + local appearance = DB.loadSpec('appearance', appearance_specname) + self.sprite = RES.loadSprite(appearance.idle) + end + if self.sprite then self.sprite:draw(-40, 0) end + g.pop() +end + +function View:drawSelection(g, context, selection) + g.push() + g.translate(_WIDTH/2, _HEIGHT/2 + _header_font:getHeight()) + + local text = _menu_values[context][selection] + local spec + if type(text):match('boolean') then text = text and "Yes" or "No" + else + spec = _getSpec(context, text) + text = spec['name'] + end + g.printf(text, -256, 0, 512, "center") + + local w = _header_font:getWidth(text) / 2 + _PD * 2 + self.arrow = (self.arrow + 2 * pi * delta()) % pi + + g.push() + g.translate(-sin(self.arrow)*_PD/2, _PD*1.8) + g.polygon("fill", -w, 0, -w+_PD, -_PD/2, -w+_PD, _PD/2) + g.pop() + + g.push() + g.translate(sin(self.arrow)*_PD/2, _PD*1.8) + g.polygon("fill", w, 0, w-_PD, _PD/2, w-_PD, -_PD/2) + g.pop() + + g.pop() + + if spec then + g.push() + g.translate(2*_WIDTH/3-40, + _HEIGHT/2 + 40) + local specname = _menu_values[context][selection] + local name = _getSpec(context, specname)['name'] + local desc = _getSpec(context, specname)['description'] + _header_font:set() + _header_font:setLineHeight(1) + g.printf(name, 0, -_header_font:getHeight(), 400, "left") + _content_font:set() + _content_font:setLineHeight(1) + g.printf(desc:gsub("([^\n])\n([^\n])", "%1 %2"), 0, 0, 400, "left") + g.pop() + end + +end + +function View:drawContext(g) + -- draw current options in context + _header_font:set() + g.push() + g.translate(_WIDTH/2, _HEIGHT/2 - 160) + g.printf(_CONTEXTS[self.context] or "ROUTE START!", -256, 0, 512, "center") + g.pop() +end + +function View:drawSaved(g, player_info) + -- draw current state + _header_font:setLineHeight(1) + _header_font:set() + g.push() + g.translate(_WIDTH/3-80, _HEIGHT/2 - 2*_header_font:getHeight() + 40) + g.setColor(0x38/255, 0xe4/255, 1, self.enter) + for i = 1, self.context - 1 do + local field = _PLAYER_FIELDS[i] + local specname = player_info[field] + if specname == true or specname == false then break end + + g.print(_getSpec(field, specname)['name'], 0, 0) + g.translate(0, _header_font:getHeight() - 16) + end + g.setColor(1, 1, 1, self.enter) + g.pop() +end + +return View diff --git a/game/view/consumelist.lua b/game/view/consumelist.lua new file mode 100644 index 00000000..eb6ca7b2 --- /dev/null +++ b/game/view/consumelist.lua @@ -0,0 +1,363 @@ + +local math = require 'common.math' +local HoldBar = require 'view.helpers.holdbar' +local CARD = require 'view.helpers.card' +local FONT = require 'view.helpers.font' +local COLORS = require 'domain.definitions.colors' +local DEFS = require 'domain.definitions' + +-- MODULE ----------------------------------- +local View = Class({ + __includes = ELEMENT +}) + +-- CONSTS ----------------------------------- +local _EMPTY = {} +local _ENTER_TIMER = "manage_card_list_enter" +local _TEXT_TIMER = "manage_card_list_text" +local _CONSUMED_TIMER = "consumed_card:" +local _ENTER_SPEED = .2 +local _MOVE_SMOOTH = 1/5 +local _EPSILON = 2e-5 +local _SIN_INTERVAL = 1/2^5 +local _PD = 40 +local _ARRSIZE = 20 +local _PI = math.pi +local _CONSUME_TEXT = "consume (+%d EXP)" +local _FULL_WIDTH, _WIDTH, _HEIGHT +local _LIST_VALIGN +local _CW, _CH + +-- LOCAL VARS +local _font +local _otherfont + +-- LOCAL METHODS ---------------------------- +local function _initGraphicValues() + local g = love.graphics + _FULL_WIDTH, _HEIGHT = g.getDimensions() + _WIDTH = _FULL_WIDTH*3/4 + _LIST_VALIGN = 0.5*_HEIGHT + _font = FONT.get("TextBold", 20) + _otherfont = FONT.get("Text", 20) + _CW = CARD.getWidth() + _CH = CARD.getHeight() +end + +local function _next_circular(i, len, n) + if n == 0 then return i end + return _next_circular(i % len + 1, len, n - 1) +end + +local function _prev_circular(i, len, n) + if n == 0 then return i end + return _prev_circular((i - 2) % len + 1, len, n - 1) +end + +-- PUBLIC METHODS --------------------------- +function View:init(hold_actions) + ELEMENT.init(self) + + self.enter = 0 + self.text = 0 + self.selection = 1 + self.cursor = 0 + self.buffered_offset = {} + self.consumed_offset = {} + self.card_alpha = {} + self.move = self.selection + self.offsets = {} + self.card_list = _EMPTY + self.consumed = {} + self.consumed_count = 0 + self.consume_log = false + self.holdbar = HoldBar(hold_actions) + self.exp_gained = 0 + self.exp_gained_offset = 0 + self.exp_gained_alpha = 1 + self.ready_to_leave = false + self.is_leaving = false + + _initGraphicValues() +end + +function View:isLocked() + return self.holdbar:isLocked() +end + +function View:open(card_list, maxconsume) + self.card_list = card_list + self.consume_log = {} + self.holdbar:unlock() + self.selection = math.ceil(#card_list/2) + self.maxconsume = maxconsume + for i=1,#card_list do + self.buffered_offset[i] = 0 + self.consumed_offset[i] = 0 + self.card_alpha[i] = 1 + self.consumed[i] = false + end + self:removeTimer(_ENTER_TIMER, MAIN_TIMER) + self:addTimer(_ENTER_TIMER, MAIN_TIMER, "tween", + _ENTER_SPEED, self, { enter=1, text=1 }, "out-quad") +end + +function View:close() + self.holdbar:lock() + self.consume_log = _EMPTY + self:removeTimer(_ENTER_TIMER, MAIN_TIMER) + self:addTimer(_ENTER_TIMER, MAIN_TIMER, "tween", + _ENTER_SPEED, self, { enter=0, text=0, exp_gained_alpha = 0}, "out-quad", + function () + self.card_list = _EMPTY + self:destroy() + end) +end + +function View:selectPrev(n) + if self:isLocked() then return end + n = n or 1 + self.selection = _prev_circular(self.selection, #self.card_list, n) + self.holdbar:reset() +end + +function View:selectNext() + if self:isLocked() then return end + n = n or 1 + self.selection = _next_circular(self.selection, #self.card_list, n) + self.holdbar:reset() +end + +function View:setSelection(n) + self.selection = n +end + +function View:startLeaving() + self.holdbar:lock() + self.is_leaving = true + self:addTimer(_TEXT_TIMER, MAIN_TIMER, "tween", _ENTER_SPEED, + self, {text=0}, "in-quad") + for i = 1, #self.card_list do + self:addTimer("collect_card_"..i, MAIN_TIMER, "after", + i*3/60 + .05, + function() + if not self.consumed[i] then + self:addTimer("getting_card_"..i, MAIN_TIMER, + "tween", .3, self.buffered_offset, + {[i] = _HEIGHT}, "in-back") + else + self:addTimer("consuming_card_off"..i, MAIN_TIMER, + "tween", .3, self.consumed_offset, + {[i] = 30}, "in-quad") + self:addTimer("consuming_card_alpha"..i, MAIN_TIMER, + "tween", .3, self.card_alpha, + {[i] = 0}, "in-quad") + end + end) + end + self:addTimer("finish_collection", MAIN_TIMER, "after", + 0.65, function() self.ready_to_leave = true end) + +end + +function View:isReadyToLeave() + return self.ready_to_leave +end + +function View:toggleSelected() + local changed = false + if self.consumed[self.selection] then + changed = true + self:removeConsume() + elseif not self.maxconsume or self.consumed_count < self.maxconsume then + changed = true + self:addConsume() + end + if changed then + self.consumed[self.selection] = not self.consumed[self.selection] + end +end + +function View:getConsumeLog() + local t = {} + for i, consumed in ipairs(self.consumed) do + if consumed then + table.insert(t,i) + end + end + return t +end + +function View:addConsume() + self.exp_gained = self.exp_gained + 1 + self.exp_gained_offset = -10 + self.consumed_count = self.consumed_count + 1 +end + +function View:removeConsume() + self.exp_gained = self.exp_gained - 1 + self.exp_gained_offset = -10 + self.consumed_count = self.consumed_count - 1 +end + +function View:draw() + local g = love.graphics + local enter = self.enter + g.push() + + if enter > 0 then + self:drawBG(g, enter) + self:drawCards(g, enter) + self:drawGainedEXP(g, enter) + end + + g.pop() +end + +function View:drawBG(g, enter) + g.setColor(0, 0, 0, enter*0.5) + g.rectangle("fill", 0, 0, _FULL_WIDTH, _HEIGHT) +end + +function View:drawCards(g, enter) + local selection = self.selection + local card_list = self.card_list + local card_list_size = #card_list + + g.push() + + -- smooth enter! + g.translate(math.round((_WIDTH/2)*(1-enter)+_WIDTH/2-_CW/2), + math.round(2*_HEIGHT/7-_CH/2)) + + -- smooth movement! + self.move = self.move + (selection - self.move)*_MOVE_SMOOTH + if (self.move-selection)^2 <= _EPSILON then self.move = selection end + g.translate(math.round(-(_CW+_PD)*(self.move-1)), 0) + + -- draw each card + for i = 1, card_list_size do + g.push() + local focus = selection == i + local dist = math.abs(selection-i) + local offset = self.offsets[i] or 0 + local consumed = self.consumed[i] and _LIST_VALIGN or 0 + + -- smooth offset when consuming cards + offset = offset > _EPSILON and offset - offset * _MOVE_SMOOTH or 0 + self.offsets[i] = offset + g.translate((_CW+_PD)*(i-1+offset), _LIST_VALIGN - consumed) + g.translate(0, self.buffered_offset[i] + -self.consumed_offset[i]) + CARD.draw(card_list[i], 0, 0, focus and not self.is_leaving, + dist > 0 and enter/dist*self.card_alpha[i] + or enter*self.card_alpha[i], 0.9) + g.pop() + end + g.pop() + + -- draw selection + g.push() + g.translate(math.round(_WIDTH/2), + math.round(_HEIGHT/2)) + enter = self.text + if enter > 0 then + if card_list[selection] then + self:drawCardDesc(g, card_list[selection], enter) + end + end + g.pop() +end + +function View:drawArrow(g, enter) + local text_width = _font:getWidth(_CONSUME_TEXT) + local lh = 1.25 + local text_height + local senoid + + g.push() + + -- move arrow in senoid + self.cursor = self.cursor + _SIN_INTERVAL + while self.cursor > 1 do self.cursor = self.cursor - 1 end + senoid = (_ARRSIZE/2)*math.sin(self.cursor*_PI) + + _font:setLineHeight(lh) + _font.set() + text_height = _font:getHeight()*lh + + g.translate(0, -text_height*.5) + g.setColor(1, 1, 1, enter) + self:drawHoldBar(g) + + g.pop() +end + +function View:drawCardDesc(g, card, enter) + g.push() + + g.setLineWidth(2) + local maxw = 2*_CW + g.setColor(COLORS.NEUTRAL) + g.line(-0.45*_WIDTH, 0, -maxw - _PD, 0) + g.line(maxw + _PD, 0, 0.45*_WIDTH, 0) + _otherfont.set() + g.print("Keep", maxw + _PD, 0.5 * _otherfont:getHeight()) + local consume, extra = "Consume\n", 0 + if self.maxconsume then + consume = ("Consume [%d/%d]\n"):format(self.consumed_count, self.maxconsume) + extra = _CW/2 + 10 + end + local cor, arc, ani = card:getOwner():trainingDitribution() + local cor_t = ("COR %.1f%% "):format(cor*100) + local arc_t = ("ARC %.1f%% "):format(arc*100) + local ani_t = ("ANI %.1f%%"):format(ani*100) + local table = {COLORS.NEUTRAL,consume, + COLORS.COR, cor_t, + COLORS.ARC, arc_t, + COLORS.ANI, ani_t + } + g.print(table, maxw + _PD, -2.5 * _otherfont:getHeight()) + + g.push() + g.translate(maxw + _PD + 1.5*_CW + extra, -1.6 * _otherfont:getHeight()) + self:drawArrow(g, enter) + g.pop() + + g.push() + g.translate(-maxw, -CARD.getInfoHeight(3)/2) + CARD.drawInfo(card, 0, 0, 2*maxw, enter, nil, true) + g.pop() + + g.pop() +end + + +function View:drawHoldBar(g) + if self.holdbar:update() then + self:startLeaving() + end + self.holdbar:draw(0, 0) +end + + +function View:drawGainedEXP(g, enter) + local offset_speed = 120 + if self.exp_gained > 0 then + local str = ("+%4d"):format(self.exp_gained * DEFS.CONSUME_EXP) + local x, y = 3/4*g.getWidth()+98, g.getHeight()/2 - _otherfont:getHeight()/2 + + _otherfont:set() + g.setColor(COLORS.DARK[1], COLORS.DARK[2], COLORS.DARK[3], enter) + g.print(str, x, y - 1 + self.exp_gained_offset) + g.setColor(COLORS.VALID[1], COLORS.VALID[2], COLORS.VALID[3], enter) + g.print(str, x, y - 3 + self.exp_gained_offset) + + if self.exp_gained_offset < 0 then + self.exp_gained_offset = math.min(0, self.exp_gained_offset + + offset_speed*love.timer.getDelta()) + end + end +end + + +return View diff --git a/game/view/definitions/init.lua b/game/view/definitions/init.lua new file mode 100644 index 00000000..28c3cfb6 --- /dev/null +++ b/game/view/definitions/init.lua @@ -0,0 +1,10 @@ + +local VIEWDEFS = {} + +VIEWDEFS.TILE_W = 80 +VIEWDEFS.TILE_H = 60 +VIEWDEFS.HALF_W = 9 +VIEWDEFS.HALF_H = 7 + +return VIEWDEFS + diff --git a/game/view/fade.lua b/game/view/fade.lua new file mode 100644 index 00000000..894d2b79 --- /dev/null +++ b/game/view/fade.lua @@ -0,0 +1,57 @@ + +local FadeView = Class{ + __includes = { ELEMENT } +} + +local _FADE_TIME = 0.25 +local _FADE_TWEEN = "FADING_TWEEN" +local _FADE_CONFLICT_ERR = [=[ +Fade animations conflicting. +Please wait until one of them is finished]=] + +--[[ PUBLIC METHODS ]]-- + +FadeView.STATE_FADED = 1 +FadeView.STATE_UNFADED = 0 +FadeView.FADE_TIME = _FADE_TIME + +function FadeView:init(fade_state) + ELEMENT.init(self) + self.fading_in = false + self.fading_out = false + self.exception = true + self.alpha = fade_state or FadeView.STATE_UNFADED +end + +function FadeView:fadeOutAndThen(do_a_thing) + assert(not self.fading_out and not self.fading_in, _FADE_CONFLICT_ERR) + self.fading_out = true + self:removeTimer(_FADE_TWEEN, MAIN_TIMER) + self:addTimer(_FADE_TWEEN, MAIN_TIMER, "tween", _FADE_TIME, + self, { alpha = 1 }, "linear", + function() + self.fading_out = false + if do_a_thing then do_a_thing() end + end) +end + +function FadeView:fadeInAndThen(do_a_thing) + assert(not self.fading_out and not self.fading_in, _FADE_CONFLICT_ERR) + self.fading_in = true + self:removeTimer(_FADE_TWEEN, MAIN_TIMER) + self:addTimer(_FADE_TWEEN, MAIN_TIMER, "tween", _FADE_TIME, + self, { alpha = 0 }, "linear", + function() + self.fading_in = false + if do_a_thing then do_a_thing() end + end) +end + +function FadeView:draw() + local g = love.graphics + g.setColor(0, 0, 0, self.alpha) + g.rectangle("fill", 0, 0, g.getDimensions()) +end + +return FadeView + diff --git a/game/view/hand.lua b/game/view/hand.lua new file mode 100644 index 00000000..09813abe --- /dev/null +++ b/game/view/hand.lua @@ -0,0 +1,258 @@ + +local FONT = require 'view.helpers.font' +local CARD = require 'view.helpers.card' +local COLORS = require 'domain.definitions.colors' +local ACTIONDEFS = require 'domain.definitions.action' + +local math = require 'common.math' + +--LOCAL FUNCTIONS DECLARATIONS-- + +local _drawCard + +--CONSTS-- +local _WIDTH, _HEIGHT +local _F_NAME = "Title" --Font name +local _F_SIZE = 24 --Font size +local _GAP = 20 +local _GAP_SCALE = { MIN = -0.5, MAX = 1 } +local _BG = {12/256, 12/256, 12/256, 1} +local _ACTION_TYPES = { + 'play', +} +local _FOCUS_ICON = { + -6, 0, 0, -9, 6, 0, 0, 9 +} + +local _font + +--HandView Class-- + +local HandView = Class{ + __includes = { ELEMENT } +} + +--CLASS FUNCTIONS-- + +function HandView:init(route) + + ELEMENT.init(self) + + _WIDTH, _HEIGHT = love.graphics.getDimensions() + + self.focus_index = -1 --What card is focused. -1 if none + self.action_type = -1 + self.x, self.y = (3*_WIDTH/4)/2, _HEIGHT - 50 + self.initial_x, self.initial_y = self.x, self.y + self.route = route + self.gap_scale = _GAP_SCALE.MIN + + --Emergency effect + self.emer_fx_alpha = 0 + self.emer_fx_max = math.pi + self.emer_fx_speed = 3.5 + self.emer_fx_v = math.sin(self.emer_fx_alpha) + self:reset() + + _font = _font or FONT.get(_F_NAME, _F_SIZE) + +end + +function HandView:getFocus() + return self.focus_index +end + +function HandView:moveFocus(dir) + if dir == "LEFT" then + self.focus_index = (self.focus_index + #self.hand - 1) % (#self.hand+1) + 1 + elseif dir == "RIGHT" then + self.focus_index = self.focus_index % (#self.hand+1) + 1 + end +end + +function HandView:getActionType() + return _ACTION_TYPES[self.action_type] +end + +function HandView:changeActionType(dir) + if dir == 'UP' then + self.action_type = (self.action_type - 2) % #_ACTION_TYPES + 1 + elseif dir == 'DOWN' then + self.action_type = self.action_type % #_ACTION_TYPES + 1 + else + error(("Unknown dir %s"):format(dir)) + end +end + +function HandView:activate() + self.focus_index = 1 + self.action_type = 1 + self:removeTimer("start", MAIN_TIMER) + self:removeTimer("end", MAIN_TIMER) + self:addTimer("start", MAIN_TIMER, "tween", 0.2, self, + { y = self.initial_y - CARD.getHeight(), + gap_scale = _GAP_SCALE.MAX }, 'out-back') +end + +function HandView:deactivate() + self.focus_index = -1 + self.action_type = -1 + + self:removeTimer("start", MAIN_TIMER) + self:removeTimer("end", MAIN_TIMER) + + self:addTimer("end", MAIN_TIMER, "tween", 0.2, self, + { y = self.initial_y, gap_scale = _GAP_SCALE.MIN }, + 'out-back') +end + +function HandView:draw() + local hand = { unpack(self.hand) } + table.insert(hand, "draw") + local size = #hand + local gap = _GAP * self.gap_scale + local step = CARD.getWidth() + gap + local x, y = self.x + (size*CARD.getWidth() + (size-1)*gap)/2, self.y + local enter = math.abs(y - self.initial_y) / (CARD.getHeight()) + local boxwidth = 128 + local g = love.graphics + + --update emergency effect + local dt = love.timer.getDelta() + self.emer_fx_alpha = self.emer_fx_alpha + self.emer_fx_speed*dt + self.emer_fx_v = math.sin(self.emer_fx_alpha) + while self.emer_fx_alpha >= self.emer_fx_max do + self.emer_fx_alpha = self.emer_fx_alpha - self.emer_fx_max + end + + + -- draw action type + _font.set() + local colorname = (self:getActionType() or "BACKGROUND"):upper() + local poly = { + -20, _HEIGHT/2, + self.x + boxwidth, _HEIGHT/2, + self.x + boxwidth, _HEIGHT/2 + 40, + self.x + boxwidth - 20, _HEIGHT/2 + 60, + -20, _HEIGHT/2 + 60, + } + local offset = self.x+boxwidth + + -- draw each card + local infoy = 40 + for i=size,1,-1 do + local card = hand[i] + local dx = (size-i+1)*step + CARD.draw(card, x - dx + gap, + y - 50 + (0.2+enter*0.4)*(i - (size+1)/2)^2*_GAP, + i == self.focus_index) + if self.focus_index == i then + local infox = _GAP + CARD.drawInfo(card, infox, infoy, _WIDTH/3 - infox, enter, + self.route:getPlayerActor()) + end + end + + self:drawFocusBar(g, self.route.getControlledActor()) +end + +function HandView:drawFocusBar(g, actor) + if not actor then return end + -- draw hand countdown + local maxfocus = ACTIONDEFS.FOCUS_DURATION + local focuscountdown = math.min(actor:getFocus(), maxfocus) + local current = self.hand_count_down or 0 + local y = 144 + current = current + (focuscountdown - current) * 0.2 + if math.abs(current - focuscountdown) < 1 then + current = focuscountdown + end + self.hand_count_down = current + local handbar_percent = current / maxfocus + local emergency_percent = .33 + local handbar_width = 492/2 + local handbar_height = 12 + local handbar_gap = handbar_width / (maxfocus-1) + local font = FONT.get("Text", 18) + local fh = font:getHeight()*font:getLineHeight() + local mx, my = 60, 20 + local slope = handbar_height + 2*my + font:set() + g.push() + g.origin() + g.translate(self.x - handbar_width/2, _HEIGHT - handbar_height - my) + + --Drawing background + g.setColor(_BG) + g.polygon('fill', -mx, handbar_height+my, + -mx + slope, -my, + handbar_width + mx - slope, -my, + handbar_width + mx, handbar_height + my) + --Drawing focus bar + g.setLineWidth(1) + local red, gre, blu, a = unpack(COLORS.NOTIFICATION) + if handbar_percent <= emergency_percent then + red, gre, blu = red + (1-red)*self.emer_fx_v, + gre + (1-gre)*self.emer_fx_v, + blu + (1-blu)*self.emer_fx_v + end + g.push() + g.translate(0, 0.3*(handbar_height + 2*my)) + for i=0,maxfocus-1 do + g.push() + g.translate(i * handbar_gap, 0) + g.setColor(COLORS.EMPTY) + g.polygon('fill', _FOCUS_ICON) + if current >= i then + g.setColor(red, gre, blu, a * math.min(1, (current-i))) + g.polygon('fill', _FOCUS_ICON) + end + g.pop() + end + g.pop() + + --Drawing contour lines + g.setColor(COLORS.NEUTRAL) + g.setLineWidth(2) + g.line(-mx, handbar_height+my, + -mx + slope, -my, + handbar_width + mx - slope, -my, + handbar_width + mx, handbar_height + my) + + + --Draw text + g.translate(0, -20) + g.setColor(COLORS.BLACK) + g.printf("Focus Duration", 0, 0, handbar_width, 'center') + g.translate(-1, -1) + g.setColor(COLORS.NEUTRAL) + g.printf("Focus Duration", 0, 0, handbar_width, 'center') + g.pop() +end + +function HandView:addCard(actor, card) + if self.route.getControlledActor() == actor then + table.insert(self.hand, card) + end +end + +--Remove card given by index (must be valid) +function HandView:removeCard(actor, card_index) + if self.route.getControlledActor() == actor then + table.remove(self.hand, card_index) + end +end + +function HandView:reset() + self.hand = {} + + local controlled_actor = self.route.getControlledActor() + if controlled_actor then + for i,card in ipairs(controlled_actor:getHand()) do + self.hand[i] = card + end + end + +end + +return HandView diff --git a/game/view/helpers/card.lua b/game/view/helpers/card.lua new file mode 100644 index 00000000..8723b858 --- /dev/null +++ b/game/view/helpers/card.lua @@ -0,0 +1,180 @@ + +local FONT = require 'view.helpers.font' +local TEXTURE = require 'view.helpers.texture' +local RES = require 'resources' +local APT = require 'domain.definitions.aptitude' +local COLORS = require 'domain.definitions.colors' +local round = require 'common.math' .round + +--CARDVIEW PROPERTIES-- + +local _title_font = FONT.get("TextBold", 20) +local _text_font = FONT.get("Text", 20) +local _info_font = FONT.get("Text", 18) +local _card_font = FONT.get("Text", 12) +local _card_base +local _neutral_icon + +local CARD = {} + + +local _is_init = false +local function _init() + _card_base = TEXTURE.get("card-base") + _card_base:setFilter("linear", "linear", 1) + + _is_init = true +end + +local _DRAW = {} + +function _DRAW:getRelatedAttr() + return 'NONE' +end + +function _DRAW:getType() + return '' +end + +function _DRAW:getName() + return "New Hand" +end + +function _DRAW:getEffect(player_actor) + local pp + if player_actor then + pp = player_actor:getBody():getConsumption() + end + return ("Action [-%s PP]\n\nDiscard your hand, draw five cards."):format(pp) +end + +function _DRAW:getDescription() + return "" +end + +function _DRAW:getIconTexture() +end + +function _DRAW:isWidget() +end + +--Draw a card starting its upper left corner on given x,y values +--Alpha is a float value between [0,1] applied to all graphics +function CARD.draw(card, x, y, focused, alpha, scale) + if not _is_init then _init() end + if card == 'draw' then + card = _DRAW + end + alpha = alpha or 1 + scale = scale or 1 + --Draw card background + local g = love.graphics + local cr, cg, cb = unpack(COLORS[card:getRelatedAttr()]) + local w, h = _card_base:getDimensions() + local typewidth = _info_font:getWidth(card:getType()) + local pd = 12 + g.push() + g.scale(scale, scale) + + if focused then + -- shine! + local shine = 50/255 + local cardname = card:getName() + local namewidth = _title_font:getWidth(cardname) + g.translate(0, -10) + cr = cr + shine + cg = cg + shine + cb = cb + shine + _title_font:set() + g.setColor(COLORS.NEUTRAL) + g.printf(cardname, x + round((w - namewidth)/2), + round(y-pd-_title_font:getHeight()), + namewidth, "center") + end + + _card_font.set() + + --shadow + g.setColor(0, 0, 0, alpha) + _card_base:draw(x+2, y+2) + + --card + g.setColor(cr, cg, cb, alpha) + _card_base:draw(x, y) + + --card icon + local br, bg, bb = unpack(COLORS.DARK) + local icon_texture = TEXTURE.get(card:getIconTexture() or 'icon-none') + g.setColor(br, bg, bb, alpha) + icon_texture:setFilter('linear', 'linear') + icon_texture:draw(x+w/2, y+h/2, 0, 72/120, 72/120, + icon_texture:getWidth()/2, + icon_texture:getHeight()/2 + ) + g.translate(x, y) + --Draw card info + g.setColor(0x20/255, 0x20/255, 0x20/255, alpha) + g.printf(card:getType(), w-pd-typewidth, 0, typewidth, "right") + if card:isWidget() then + g.printf(("[%d]"):format(card:getWidgetCharges()-card:getUsages()), + pd, h-pd-_card_font:getHeight(), w-pd*2, "left" + ) + end + + g.pop() +end + +--Draw the description of a card. +function CARD.drawInfo(card, x, y, width, alpha, player_actor, no_desc) + alpha = alpha or 1 + if card == 'draw' then + card = _DRAW + end + local g = love.graphics + local cr, cg, cb = unpack(COLORS.NEUTRAL) + + g.push() + + g.translate(x, y) + g.setColor(cr, cg, cb, alpha) + + _title_font:setLineHeight(1.5) + _title_font.set() + g.printf(card:getName(), 0, 0, width) + + g.translate(0, _title_font:getHeight()) + + _text_font.set() + local desc = card:getEffect(player_actor) + if not no_desc then + desc = desc .. "\n\n---" + desc = desc .. '\n\n' .. (card:getDescription() or "[No description]") + end + desc = desc:gsub("([^\n])[\n]([^\n])", "%1 %2") + desc = desc:gsub("\n\n", "\n") + g.printf(desc, 0, 0, width) + + g.pop() +end + +function CARD.getInfoHeight(lines) + _title_font:setLineHeight(1.5) + return _text_font:getHeight() * _text_font:getLineHeight() * lines +end + +function CARD.getInfoWidth(card, width) + return _text_font:getWrap(card:getDescription(), width) +end + +function CARD.getWidth() + if not _is_init then _init() end + return _card_base:getWidth() +end + +function CARD.getHeight() + if not _is_init then _init() end + return _card_base:getHeight() +end + + +return CARD diff --git a/game/view/helpers/font.lua b/game/view/helpers/font.lua new file mode 100644 index 00000000..ce6fcffb --- /dev/null +++ b/game/view/helpers/font.lua @@ -0,0 +1,43 @@ + +local RES = require 'resources' + +local _getFont = RES.loadFont + +local FONT = {} + +function FONT.set(name_or_font, size) + local g = love.graphics + if type(name_or_font) == "string" then + local font = _getFont(name_or_font, size) + g.setFont(font) + return font + else + name_or_font:set() + return name_or_font + end +end + +function FONT.get(name, size) + return setmetatable( + { + set = function() + FONT.set(name, size) + end + }, { + __index = function(t, key) + local self = _getFont(name, size) + local method = self[key] + if type(method) == 'function' then + return function (_, ...) + return method(self, ...) + end + else + return method + end + end + } + ) +end + +return FONT + diff --git a/game/view/helpers/holdbar.lua b/game/view/helpers/holdbar.lua new file mode 100644 index 00000000..4724e631 --- /dev/null +++ b/game/view/helpers/holdbar.lua @@ -0,0 +1,133 @@ + +local INPUT = require 'input' +local DIRECTIONALS = require 'infra.dir' +local DIR = require 'domain.definitions.dir' +local COLORS = require 'domain.definitions.colors' + +local _TOTAL = 1 +local _TIME = .8 +local _ENTER_SPEED = 8 +local _EPSILON = 0.01 -- 1% +local _WIDTH = 64 +local _HEIGHT = 16 + +local _dt = love.timer.getDelta + +local function _tween(from, to, smooth) + local step = (to - from) * smooth * _dt() + local target = from + step + if (to - target)^2 <= _EPSILON^2 then target = to end + return target +end + +local function _linear(from, to, time) + local step = (to - from > 0 and 1 or -1) * _TOTAL/time * _dt() + return from + step +end + +local function _render(enter, progress, x, y) + local g = love.graphics + local alpha = enter + local cr, cg, cb = unpack(COLORS.NEUTRAL) + g.push() + g.translate(x - _WIDTH/2, y) + g.setColor(cr/4, cg/4, cb/4, alpha) + g.rectangle("fill", 0, 0, _WIDTH, _HEIGHT) + g.setColor(cr, cg, cb, alpha) + g.rectangle("fill", 0, 0, _WIDTH*(progress/_TOTAL), _HEIGHT) + g.pop() +end + + +local HoldBar = Class({ + __includes = ELEMENT +}) + +function HoldBar:init(hold_actions) + ELEMENT.init(self) + assert(type(hold_actions) == 'table', + "HoldBar object receives a list (table) of possible actions to hold! " + .. ("Not a '%s'"):format(type(hold_actions))) + self.enter = 0 + self.progress = 0 + self.hold_actions = hold_actions + self.timers = {} +end + +function HoldBar:lock() + self.locked = true +end + +function HoldBar:unlock() + self.locked = false + self.progress = 0 +end + +function HoldBar:isLocked() + return self.locked +end + +function HoldBar:reset() + self.progress = 0 +end + +function HoldBar:fadeIn() + self.enter = _tween(self.enter, _TOTAL, _ENTER_SPEED) +end + +function HoldBar:fadeOut() + self.enter = _tween(self.enter, 0, _ENTER_SPEED) +end + +function HoldBar:advance() + self.progress = math.min(_linear(self.progress, _TOTAL, _TIME), _TOTAL) +end + +function HoldBar:rewind() + self.progress = math.max(_linear(self.progress, 0, _TIME), 0) +end + +function HoldBar:update() + local is_down = false + local actions = self.hold_actions + + for _,action in ipairs(actions) do + if is_down then break end + is_down = (DIR[action] and DIRECTIONALS.isDirectionDown(action)) + or INPUT.isActionDown(action) + end + + -- enter fade in + if self.locked or not is_down then + self:fadeOut() + else + self:fadeIn() + end + + -- advance or rewind progress + if self.enter <= 0 then self.progress = 0 end + if not self.locked then + if is_down then + self:advance() + else + self:rewind() + end + end + + -- check progress + if not self.locked and self.progress >= _TOTAL then + self:removeTimer("advance") + return true + end + return false +end + +function HoldBar:draw(x, y) + -- render bar + if self.enter > 0 and self.progress > 0 then + _render(self.enter, self.progress, x, y) + end +end + +return HoldBar + diff --git a/game/view/helpers/texture.lua b/game/view/helpers/texture.lua new file mode 100644 index 00000000..30f9e3cf --- /dev/null +++ b/game/view/helpers/texture.lua @@ -0,0 +1,32 @@ + +local RES = require 'resources' + +local _loadTexture = RES.loadTexture +local TEX = {} + +function TEX.get(name) + local g = love.graphics + return setmetatable( + { + draw = function (t, ...) + g.draw(_loadTexture(name), ...) + end + }, + { + __index = function(t, key) + local self = _loadTexture(name) + local method = self[key] + if type(method) == 'function' then + return function (_, ...) + return method(self, ...) + end + else + return method + end + end + } + ) +end + +return TEX + diff --git a/game/view/helpers/widget.lua b/game/view/helpers/widget.lua new file mode 100644 index 00000000..09f14318 --- /dev/null +++ b/game/view/helpers/widget.lua @@ -0,0 +1,68 @@ + +local TRIGGERS = require 'domain.definitions.triggers' +local FONT = require 'view.helpers.font' + +local WIDGET = {} + +local _WIDTH = 160 +local _HEIGHT +local _PD = 12 +local _LH = .8 + +local _font +local _fmt + +local function _init() + _font = FONT.get("Text", 20) + _fmt = string.format +end + +function WIDGET.draw(widget, x, y, alpha) + if not _font then _init() end + local g = love.graphics + + g.push() + g.setColor(0x1f/255, 0x1f/255, 0x1f/255, alpha) + g.rectangle("fill", x, y, WIDGET.getWidth(), WIDGET.getHeight()) + g.translate(_PD, _PD) + _font:setLineHeight(_LH) + _font.set() + g.setColor(1, 1, 1, alpha) + if not widget:isWidgetPermanent() then + g.printf(_fmt("%s\n[%s]\n%02d/%02d", widget:getName():sub(1,16), + widget:getWidgetTrigger():gsub("_", " "), + widget:getWidgetCharges() - widget:getUsages(), + widget:getWidgetCharges() + ), x, y, _WIDTH - _PD*2, 'left' + ) + elseif not not widget:getWidgetPlacement() then + local lh2 = _font:getLineHeight() * _font:getHeight() / 2 + g.printf(_fmt("%s\n[%s]", widget:getName():sub(1,16), + widget:getWidgetPlacement():gsub("^%l", string.upper) + ), x, y+lh2, _WIDTH - _PD*2, 'left' + ) + else + local lh2 = _font:getLineHeight() * _font:getHeight() + g.printf(_fmt("%s", widget:getName():sub(1,16) + ), x, y+lh2, _WIDTH - _PD*2, 'left' + ) + end + g.pop() + +end + +function WIDGET.getWidth() + return _WIDTH +end + +function WIDGET.getHeight(widget) + if not _HEIGHT then + if not _font then _init() end + _font:setLineHeight(_LH) + _HEIGHT = _font:getHeight()*3 + 2*_PD - 4 + end + return _HEIGHT +end + +return WIDGET + diff --git a/game/view/packlist.lua b/game/view/packlist.lua new file mode 100644 index 00000000..7efa371b --- /dev/null +++ b/game/view/packlist.lua @@ -0,0 +1,279 @@ + +local math = require 'common.math' +local HoldBar = require 'view.helpers.holdbar' +local CARD = require 'view.helpers.card' +local FONT = require 'view.helpers.font' +local RES = require 'resources' +local DB = require 'database' + + +-- MODULE ----------------------------------- +local View = Class({ + __includes = ELEMENT +}) + +-- CONSTS ----------------------------------- +local _EMPTY = {} +local _ENTER_TIMER = "manage_card_list_enter" +local _TEXT_TIMER = "manage_card_list_text" +local _CONSUMED_TIMER = "consumed_card:" +local _ENTER_SPEED = .2 +local _MOVE_SMOOTH = 1/5 +local _EPSILON = 2e-5 +local _SIN_INTERVAL = 1/2^5 +local _PD = 40 +local _ARRSIZE = 20 +local _MAX_Y_OFFSET = 768 +local _PI = math.pi +local _HOLDBAR_TEXT = "open pack" +local _FULL_WIDTH, _WIDTH, _HEIGHT +local _CW, _CH + +-- LOCAL VARS +local _font + +-- LOCAL METHODS ---------------------------- +local function _initGraphicValues() + local g = love.graphics + _FULL_WIDTH, _HEIGHT = g.getDimensions() + + _WIDTH = 3*_FULL_WIDTH/4 + _font = FONT.get("TextBold", 20) + _CW = CARD.getWidth() + 20 + _CH = CARD.getHeight() + 20 +end + +local function _stencilFunction(g) + g.polygon("fill", + { + 0, 0, + _WIDTH - 24, 0, + _WIDTH - 24, _HEIGHT/2 + 24, + _WIDTH, _HEIGHT/2 + 48, + _WIDTH, _HEIGHT, + 0, _HEIGHT, + } + ) +end + + +local function _next_circular(i, len, n) + if n == 0 then return i end + return _next_circular(i % len + 1, len, n - 1) +end + +local function _prev_circular(i, len, n) + if n == 0 then return i end + return _prev_circular((i - 2) % len + 1, len, n - 1) +end + +-- PUBLIC METHODS --------------------------- +function View:init(hold_actions, packlist) + ELEMENT.init(self) + + self.enter = 0 + self.text = 0 + self.selection = math.ceil(#packlist/2) + self.cursor = 0 + + self.y_offset = {} + for i=1,#packlist do self.y_offset[i] = 0 end + + self.move = self.selection + self.offsets = {} + self.pack_list = packlist + + self.holdbar = HoldBar(hold_actions) + self.holdbar:unlock() + self.holdbar_activated = false + + self:removeTimer(_ENTER_TIMER, MAIN_TIMER) + self:addTimer(_ENTER_TIMER, MAIN_TIMER, "tween", + _ENTER_SPEED, self, { enter=1, text=1 }, "out-quad") + + _initGraphicValues() +end + +function View:isLocked() + return self.holdbar:isLocked() +end + +function View:getChosenPack() + return self.pack_list[self.selection] +end + +function View:close() + self.holdbar:lock() + self:removeTimer(_ENTER_TIMER, MAIN_TIMER) + self:addTimer(_ENTER_TIMER, MAIN_TIMER, "tween", + _ENTER_SPEED, self, { enter=0, text=0 }, "out-quad", + function () + self.pack_list = _EMPTY + self:destroy() + end) +end + +function View:selectPrev(n) + if self:isLocked() then return end + n = n or 1 + self.selection = _prev_circular(self.selection, #self.pack_list, n) + self.holdbar:reset() +end + +function View:selectNext() + if self:isLocked() then return end + n = n or 1 + self.selection = _next_circular(self.selection, #self.pack_list, n) + self.holdbar:reset() +end + +function View:setSelection(n) + self.selection = n +end + +function View:getSelection() + return self.selection +end + +function View:isPackListEmpty() + return #self.pack_list == 0 +end + +function View:draw() + local g = love.graphics + local enter = self.enter + g.push() + + if enter > 0 then + self:drawBG(g, enter) + self:drawPacks(g, enter) + end + + g.pop() +end + +function View:drawBG(g, enter) + g.setColor(0, 0, 0, enter*0.5) + g.rectangle("fill", 0, 0, _FULL_WIDTH, _HEIGHT) +end + +function View:drawPacks(g, enter) + local selection = self.selection + local pack_list = self.pack_list + local pack_list_size = #pack_list + + g.push() + + g.stencil(function() _stencilFunction(g) end, "replace", 1) + g.setStencilTest("greater", 0) + + -- smooth enter! + g.translate(math.round((_WIDTH/2)*(1-enter)+_WIDTH/2-_CW/2), + math.round(3*_HEIGHT/7-_CH/2)) + + -- smooth movement! + self.move = self.move + (selection - self.move)*_MOVE_SMOOTH + if (self.move-selection)^2 <= _EPSILON then self.move = selection end + g.translate(math.round(-(_CW+_PD)*(self.move-1)), 0) + + -- draw each pack + for i = 1, pack_list_size do + g.push() + local focus = selection == i + local dist = math.abs(selection-i) + local offset = self.offsets[i] or 0 + + -- smooth offset when consuming pack + offset = offset > _EPSILON and offset - offset * _MOVE_SMOOTH or 0 + self.offsets[i] = offset + g.translate((_CW+_PD)*(i-1+offset), 0) + g.translate(0, self.y_offset[i]) + packbg = RES.loadTexture("pack") + + local shiny = 1/255 + if focus then + shiny = 1.5/255 + end + + --shadow + g.setColor(0, 0, 0, 200/255) + g.draw(packbg, 5, 5) + + --pack + g.setColor(85*shiny, 178*shiny, 127*shiny) + g.draw(packbg, 0, 0) + + --draw icon + local collection = DB.loadSpec("collection", pack_list[selection]) + local icon = RES.loadTexture(collection.image) + g.setColor(1, 1, 1) + g.draw(icon,15,55, nil, .5) + g.pop() + end + g.pop() + + -- draw selection + g.push() + g.translate(math.round(_WIDTH/2), + math.round(3*_HEIGHT/7-_CH/2)) + enter = self.text + if enter > 0 then + self:drawArrow(g, enter) + if pack_list[selection] then + self:drawPackDesc(g, pack_list[selection], enter) + end + end + g.setStencilTest() + g.pop() +end + +function View:drawArrow(g, enter) + local text_width = _font:getWidth(_HOLDBAR_TEXT) + local lh = 1.25 + local text_height + local senoid + + g.push() + + -- move arrow in senoid + self.cursor = self.cursor + _SIN_INTERVAL + while self.cursor > 1 do self.cursor = self.cursor - 1 end + senoid = (_ARRSIZE/2)*math.sin(self.cursor*_PI) + + _font:setLineHeight(lh) + _font.set() + text_height = _font:getHeight()*lh + + g.translate(0, -_PD - text_height*2.5) + self:drawHoldBar(g) + + g.translate(0, text_height*.5) + g.setColor(1, 1, 1, enter) + g.printf(_HOLDBAR_TEXT, -text_width/2, 0, text_width, "center") + + g.translate(-_ARRSIZE/2, _PD + text_height - _ARRSIZE - senoid) + g.polygon("fill", 0, 0, _ARRSIZE/2, -_ARRSIZE, _ARRSIZE, 0) + + g.pop() +end + +function View:drawPackDesc(g, pack, enter) + g.push() + g.translate(-1.5*_CW, _CH+_PD) + --CARD.drawInfo(card, 0, 0, 4*_CW, enter) + g.pop() +end + +function View:usedHoldbar() + return self.holdbar_activated +end + +function View:drawHoldBar(g) + if self.holdbar:update() then + self.holdbar_activated = true + end + self.holdbar:draw(0, 0) +end + + +return View diff --git a/game/view/pickwidget.lua b/game/view/pickwidget.lua new file mode 100644 index 00000000..824322e4 --- /dev/null +++ b/game/view/pickwidget.lua @@ -0,0 +1,100 @@ + +local RES = require 'resources' +local DEFS = require 'domain.definitions' +local Color = require 'common.color' +local FONT = require 'view.helpers.font' + +local PickWidgetView = Class{ + __includes = { ELEMENT } +} + +local _FONT_NAME = "Text" +local _FONT_SIZE = 20 +local _BLOCK_HEIGHT = 48 +local _MARGIN = 16 +local _PADDING = 4 +local _FMT = "%s [%d/%d%s]" +local _TRIGGERS = { + [DEFS.TRIGGERS.ON_USE] = " uses", + [DEFS.TRIGGERS.ON_TURN] = " turns", + [DEFS.TRIGGERS.ON_TICK] = " ticks", +} + +local _width, _height +local _font +local _alpha + +local function _initGraphicValues() + local g = love.graphics + _width, _height = g.getDimensions() + _font = _font or FONT.get(_FONT_NAME, _FONT_SIZE) +end + +function PickWidgetView:init(target_actor) + ELEMENT.init(self) + + self.target = target_actor + self.selection = 1 + self.alpha = 0 + + _initGraphicValues() +end + +function PickWidgetView:setSelection(n) + self.selection = n +end + +function PickWidgetView:draw() + local g = love.graphics + g.setColor(0, 0, 0, self.alpha*0.5) + g.rectangle("fill", 0, 0, _width, _height) + g.push() + + g.translate(_width/8, _height/2-2*(_BLOCK_HEIGHT+_MARGIN)) + -- draw stuff + local strs = {} + local width = 0 + for index, widget in self.target:getBody():eachWidget() do + strs[index] = _FMT:format(widget:getName(), + widget:getWidgetCharges() - widget:getUsages(), + widget:getWidgetCharges(), + _TRIGGERS[widget:getWidgetTrigger()] or "" + ) + width = math.max(width, _font:getWidth(strs[index])) + end + for index, info_str in ipairs(strs) do + local selected = self.selection == index + if selected then + g.setColor(Color.fromInt(0xff, 0xff, 0xff, self.alpha*0xff)) + else + g.setColor(Color.fromInt(0x16, 0x16, 0x16, self.alpha*0xff)) + end + g.rectangle("fill", 0, 0, width+8*_PADDING, _BLOCK_HEIGHT) + _font:set() + if selected then + g.setColor(Color.fromInt(0x00, 0x00, 0x00, self.alpha*0xff)) + else + g.setColor(Color.fromInt(0xff, 0xff, 0xff, self.alpha*0xff)) + end + g.printf(info_str, 4*_PADDING, _PADDING, width) + g.translate(0, _BLOCK_HEIGHT + _MARGIN) + end + + g.pop() +end + +function PickWidgetView:fadeOut() + self:removeTimer("widget_picker_fade", MAIN_TIMER) + self:addTimer("widget_picker_fade", MAIN_TIMER, "tween", + .2, self, { alpha = 0 }, "out-quad", + function () self:destroy() end) +end + +function PickWidgetView:fadeIn() + self:removeTimer("widget_picker_fade", MAIN_TIMER) + self:addTimer("widget_picker_fade", MAIN_TIMER, "tween", + .25, self, { alpha = 1 }, "out-quad") +end + +return PickWidgetView + diff --git a/game/view/readyability.lua b/game/view/readyability.lua new file mode 100644 index 00000000..c6c926a8 --- /dev/null +++ b/game/view/readyability.lua @@ -0,0 +1,231 @@ + +local RES = require 'resources' +local Color = require 'common.color' +local DEFS = require 'domain.definitions' +local COLORS = require 'domain.definitions.colors' +local FONT = require 'view.helpers.font' + +local min = math.min +local abs = math.abs +local floor = math.floor + +local ReadyAbilityView = Class{ + __includes = { ELEMENT } +} + +local _FADE_TIMER = "FADE_TIMER" +local _LIST_TIMER = "LIST_TIMER" +local _OFFSET_TIMER = "READY_ABILITY_OFFSET_TIMER" +local _MOVE_SPEED = 0.25 + +local _FONT_NAME = "Text" +local _FONT_SIZE = 20 +local _MARGIN = 8 +local _PADDING = 8 +local _FMT = "%s (%d)" +local _TRIGGERS = { + [DEFS.TRIGGERS.ON_USE] = " uses", + [DEFS.TRIGGERS.ON_TURN] = " turns", + [DEFS.TRIGGERS.ON_TICK] = " ticks", +} + +local _WIDTH, _HEIGHT +local _font +local _alpha + +local function _initGraphicValues() + local g = love.graphics + _WIDTH, _HEIGHT = g.getDimensions() + _font = _font or FONT.get(_FONT_NAME, _FONT_SIZE) +end + +local function _next(i, len, n) + if n == 0 then return i end + return _next(i % len + 1, len, n - 1) +end + +local function _prev(i, len, n) + if n == 0 then return i end + return _prev((i - 2) % len + 1, len, n - 1) +end + +local function _dist(i, j, len) + local d = i - j + if d > len/2 then + d = d - len + elseif d < -len/2 then + d = d + len + end + return d +end + +local function _drawContainer(g, width) + local h = _font:getHeight() + _PADDING*2 + 2*_MARGIN + 4 + g.rectangle("fill", + -_MARGIN*4, + -h - _MARGIN, + width + 4*_PADDING + _MARGIN*8, + h*3 + ) +end + +function ReadyAbilityView:init(widgets, selection) + ELEMENT.init(self) + + self.widgets = widgets + self.widget_count = #widgets + self.selection = selection + self.offset = 0 + self.alpha = 0 + self.list_alpha = 0 + + _initGraphicValues() +end + +function ReadyAbilityView:setSelection(n) + self.selection = n +end + +function ReadyAbilityView:getSelection() + return self.selection +end + +function ReadyAbilityView:selectNext() + local previous = self.selection + self.selection = _next(self.selection, self.widget_count, 1) + self.offset = 1 + self:removeTimer(_OFFSET_TIMER, MAIN_TIMER) + self:addTimer(_OFFSET_TIMER, MAIN_TIMER, "tween", _MOVE_SPEED, + self, {offset = 0}, "in-out-back") +end + +function ReadyAbilityView:selectPrev() + local previous = self.selection + self.selection = _prev(self.selection, self.widget_count, 1) + self.offset = -1 + self:removeTimer(_OFFSET_TIMER, MAIN_TIMER) + self:addTimer(_OFFSET_TIMER, MAIN_TIMER, "tween", _MOVE_SPEED, + self, {offset = 0}, "in-out-back") +end + +function ReadyAbilityView:draw() + local g = love.graphics + local alpha = self.alpha + local list_alpha = self.list_alpha + local widgets = self.widgets + local widget_count = #widgets + local selection = self.selection + local offset = self.offset + local fh = _font:getHeight() + _font:set() + + -- draw stuff + local names = {} + local width = 0 + local block_height = fh + 2*_PADDING + 2*_MARGIN + 4 + + for index, widget in ipairs(widgets) do + local str = widget:getName() + if not widget:isWidgetPermanent() then + str = _FMT:format(str, widget:getWidgetCharges() - widget:getUsages()) + end + names[index] = str + width = math.max(width, _font:getWidth(names[index])) + end + + g.push() + g.translate( + 3/4*_WIDTH - width - 64, + _HEIGHT - _MARGIN - block_height) + g.stencil(function() + _drawContainer(g, width) + end, "replace", 1) + + g.setStencilTest("gequal", 1) + + local box = { + _MARGIN, 0, + width + 2*_PADDING, 0, + width + 2*_PADDING, fh + 2*_PADDING - _MARGIN, + width + 2*_PADDING - _MARGIN, fh + 2*_PADDING, + 0, fh + 2*_PADDING, + 0, _MARGIN + } + local idx = _prev(selection, widget_count, 2) + local count = 0 + while widget_count > 0 and count < 5 do + local name = names[floor(idx)] + local dist = _dist(selection, idx, widget_count) + local a + if (widget_count > 1 and count == 2) + or (dist == 0 and widget_count == 1) then a = alpha + else + a = alpha * list_alpha * min(1, (1-abs((count - 2)/7*2))) + end + local transp = Color:new {1, 1, 1, a} + local bgcolor = COLORS.DARKER * transp + local fgcolor + if (widget_count > 1 and count == 2) + or (dist == 0 and widget_count == 1) then + fgcolor = COLORS.NEUTRAL * transp + else + fgcolor = COLORS.HALF_VISIBLE * transp + end + g.push() + if widget_count > 1 then + g.translate(0, - block_height * (count - 2 + offset)) + else + g.translate(0, - block_height * (-dist + offset)) + end + if (widget_count > 1 and dist == 0 and count == 2) + or (dist == 0 and widget_count <= 1) then + g.translate( + -0.1*(width + 2*(_PADDING + 2)), + -0.1*(block_height) + ) + g.scale(1.1, 1.1) + end + g.setColor(bgcolor) + g.polygon("fill", box) + g.setLineWidth(2) + g.setColor(COLORS.HALF_VISIBLE * transp) + g.polygon("line", box) + g.setColor(fgcolor) + g.printf(name, _PADDING, _PADDING, width) + g.pop() + idx = _next(idx, widget_count, 1) + count = count + 1 + end + + g.setStencilTest() + + g.pop() +end + +function ReadyAbilityView:enter() + self:removeTimer(_FADE_TIMER, MAIN_TIMER) + self:addTimer(_FADE_TIMER, MAIN_TIMER, "tween", + .1, self, { alpha = 1 }, "linear") +end + +function ReadyAbilityView:exit() + self:removeTimer(_FADE_TIMER, MAIN_TIMER) + self:addTimer(_FADE_TIMER, MAIN_TIMER, "tween", + .1, self, { alpha = 0 }, "linear", + function() self:destroy() end) +end + +function ReadyAbilityView:enterList() + self:removeTimer(_LIST_TIMER, MAIN_TIMER) + self:addTimer(_LIST_TIMER, MAIN_TIMER, "tween", + .25, self, { list_alpha = 1 }, "linear") +end + +function ReadyAbilityView:exitList() + self:removeTimer(_LIST_TIMER, MAIN_TIMER) + self:addTimer(_LIST_TIMER, MAIN_TIMER, "tween", + .2, self, { list_alpha = 0 }, "linear") +end + +return ReadyAbilityView + diff --git a/game/view/sector.lua b/game/view/sector.lua new file mode 100644 index 00000000..55b21c03 --- /dev/null +++ b/game/view/sector.lua @@ -0,0 +1,590 @@ + +local DB = require 'database' +local RES = require 'resources' +local Color = require 'common.color' +local math = require 'common.math' +local CAM = require 'common.camera' +local SCHEMATICS = require 'domain.definitions.schematics' +local COLORS = require 'domain.definitions.colors' +local DIR = require 'domain.definitions.dir' +local ACTION = require 'domain.definitions.action' +local FONT = require 'view.helpers.font' +local Queue = require "lux.common.Queue" +local VIEWDEFS = require 'view.definitions' +local SPRITEFX = require 'lux.pack' 'view.spritefx' +local PLAYSFX = require 'helpers.playsfx' +local vec2 = require 'cpml'.vec2 + +local SECTOR_TILEMAP = require 'view.sector.tilemap' +local SECTOR_COOLDOWNBAR = require 'view.sector.cooldownbar' +local SECTOR_LIFEBAR = require 'view.sector.lifebar' +local SECTOR_WALL = require 'view.sector.wall' + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H +local _HALF_W = VIEWDEFS.HALF_W +local _HALF_H = VIEWDEFS.HALF_H + +local _HEALTHBAR_WIDTH = 56 +local _HEALTHBAR_HEIGHT = 4 + +local _texture +local _tile_offset +local _tile_quads +local _tileset +local _cursor_sprite +local _font +local _sparkles + +local _isInCone + +local Cursor + +local SectorView = Class{ + __includes = { ELEMENT } +} + +local function _moveCamera(target, force) + local i, j = target:getPos() + local tx, ty = (j-0.5)*_TILE_W, (i-0.5)*_TILE_H + if force then + CAM:lookAt(tx, ty) + else + CAM:lockPosition(tx, ty) + end +end + +local function _dropId(i, j, k) + return ("%d:%d:%d"):format(i, j, k) +end + +function SectorView:init(route) + + ELEMENT.init(self) + + self.target = nil + self.cursor = nil + self.ray_dir = nil + self.ray_body_block = false + self.vfx = nil + + self.fov = nil --Fov to apply on the sector + + self.route = route + self.body_sprites = {} + self.drop_offsets = {} + self.sector = false + self.sector_changed = false + + _font = _font or FONT.get("Text", 16) + +end + +function SectorView:getTarget() + return self.target +end + +function SectorView:setCooldownPreview(value) + return SECTOR_COOLDOWNBAR.setCooldownPreview(value) +end + +function SectorView:initSector(sector) + if sector and sector ~= self.sector then + local g = love.graphics + self.sector = sector + + _tileset = RES.loadTileSet(sector:getTileSet()) + _texture = RES.loadTexture(_tileset.texture) + + _tile_offset = _tileset.offsets + _tile_quads = _tileset.quads + + SECTOR_TILEMAP.init(sector, _tileset) + SECTOR_COOLDOWNBAR.init() + SECTOR_WALL.load(sector) + + local pixel = RES.loadTexture('pixel') + _sparkles = g.newParticleSystem(pixel, 256) + _sparkles:setParticleLifetime(1, 2) + _sparkles:setEmissionRate(5) + _sparkles:setSizeVariation(1) + _sparkles:setLinearAcceleration(0, -40, 0, -5) + _sparkles:setColors(COLORS.TRANSP, + COLORS.NEUTRAL, + COLORS.TRANSP) + _sparkles:setEmissionArea("uniform", 16, 16, 0, false) + _sparkles:setSizes(2, 4) + + _tall_batch = g.newSpriteBatch(_texture, 512, "stream") + --FIXME: Get tile info from resource cache or something + end +end + +function SectorView:hasPendingVFX() + return not not self.vfx +end + +function SectorView:lookAt(target) + self.target = target + if target and target.fov then + self:updateFov(target) + end +end + +function SectorView:updateVFX(dt) + +end + +function SectorView:setDropOffset(i, j, k, offset) + self.drop_offsets[_dropId(i, j, k)] = offset +end + +function SectorView:startVFX(extra) + --Play SFX if any + if extra.sfx then + local target = self.target + if not target or not target.fov or not extra.body then + PLAYSFX(extra.sfx) + else + if target:canSee(extra.body) then + PLAYSFX(extra.sfx) + end + end + end + if extra.type then + local spritefx = SPRITEFX[extra.type] + self.vfx = spritefx + spritefx.apply(self, extra) + end +end + +function SectorView:finishVFX() + self.vfx = nil +end + +function SectorView:updateFov(actor) + local sector = self.route.getCurrentSector() + self.fov = actor:getFov(sector) +end + +function SectorView:setRayDir(dir, body_block) + self.ray_dir = dir + self.ray_body_block = body_block +end + +function SectorView:getBodySprite(body) + local id = body:getId() + local body_sprite = self.body_sprites[id] + if not body_sprite then + local idle = DB.loadSpec('appearance', body:getAppearance()).idle + body_sprite = RES.loadSprite(idle) + self.body_sprites[id] = body_sprite + end + return body_sprite +end + +function SectorView:setBodySprite(body, draw) + self.body_sprites[body:getId()] = draw +end + +function SectorView:sectorChanged() + self.sector_changed = true +end + +function SectorView:draw() + local g = love.graphics + local dt = love.timer.getDelta() + local sector = self.route.getCurrentSector() + self:initSector(sector) + if not self.sector then return end + if self.target then + _moveCamera(self.target, self.sector_changed) + self.sector_changed = false + end + + -- update particles + _sparkles:update(dt) + + + -- draw background + g.setBackgroundColor(COLORS.BACKGROUND) + g.setColor(COLORS.NEUTRAL) + + -- reset color + g.push() + + local fov = self.fov + local fovmask = SECTOR_TILEMAP.calculateFOVMask(g, fov) + SECTOR_TILEMAP.drawAbyss(g) + SECTOR_TILEMAP.drawFloor(g) + + -- setting up rays + local rays = {} + for i=1,sector.h do + rays[i] = {} + for j=1,sector.w do + rays[i][j] = false + end + end + + if self.ray_dir and self.target then + local dir = self.ray_dir + local i, j = self.target:getPos() + local check + if self.ray_body_block then + check = sector.isValid + else + check = sector.isWalkable + end + repeat + rays[i][j] = true + i = i + dir[1] + j = j + dir[2] + until not check(sector, i, j) or not self.fov[i][j] + end + + -- draw tall things + g.push() + local all_bodies = {} + local named + for i = 0, sector.h-1 do + local draw_bodies = {} + local draw_drops = {} + local highlights = {} + for j = 0, sector.w-1 do + local tile = sector.tiles[i+1][j+1] + if CAM:isTileInFrame(i, j) and tile then + -- Add tiles to spritebatch + local body = sector.bodies[i+1][j+1] + local x = j*_TILE_W + if self.cursor then + local current_body = self.route.getControlledActor():getBody() + if not self.fov or (self.fov[i+1][j+1] and + self.fov[i+1][j+1] > 0) + or body == current_body then + if self.cursor.range_checker(i+1, j+1) then + table.insert(highlights, { x, 0, _TILE_W, _TILE_H, + Color.fromInt {100, 200, 200, 100} }) + end + if self.cursor.validator(i+1, j+1) then + table.insert(highlights, { x, 0, _TILE_W, _TILE_H, + Color.fromInt {100, 200, 100, 100} }) + end + local ci, cj = self.cursor:getPos() + local size = self.cursor.aoe_hint or 1 + local abs = math.abs + if size and tile.type == SCHEMATICS.FLOOR + and abs(i+1 - ci) < size and abs(j+1 - cj) < size then + table.insert(highlights, { x, 0, _TILE_W, _TILE_H, + Color.fromInt {200, 100, 100, 100} }) + end + end + elseif self.ray_dir and rays[i+1][j+1] then + table.insert(highlights, { x, 0, _TILE_W, _TILE_H, + Color.fromInt {200, 100, 100, 100} }) + end + if body then + table.insert(draw_bodies, {body, x, 0}) + table.insert(all_bodies, body) + end + local dropcount = #tile.drops + local angle = math.pi*2/dropcount + local phase = 3*math.pi/4 + for k,drop in ipairs(tile.drops) do + if not self.fov or (self.fov[i+1][j+1] and + self.fov[i+1][j+1] ~= 0) then + local offset = vec2(0,0) + local radius = _TILE_W/4 + local t = k-1 + local alpha = t*angle + phase + local frequency = 3.5 + local amplitude = _TILE_H/8 + local seed = love.timer.getTime() + i*2 + j*3 + k*2 + local oscilation = (math.sin(seed * frequency) - 1) * amplitude + if dropcount > 1 then + offset = vec2(math.cos(alpha), -math.sin(alpha)) * radius + end + local spread_off = self.drop_offsets[_dropId(i+1, j+1, k)] + local dx, dy, t = 0, 0, 0 + if spread_off then + dx = (spread_off.j - (j+1))*_TILE_W + dy = (spread_off.i - (i+1))*_TILE_H + t = spread_off.t + end + table.insert(draw_drops, { + drop, x + offset.x + (1-t)*dx, 0 + offset.y + (1-t)*dy, + 2*_TILE_H*(0.25 - (t - 0.5)^2), oscilation + }) + end + end + end + end + + -- Actually Draw tiles + g.setColor(COLORS.NEUTRAL) + SECTOR_WALL.drawRow(i+1, fovmask) + + -- Draw highlights + for _, highlight in ipairs(highlights) do + local x,y,w,h,color = unpack(highlight) + g.setColor(color) + g.rectangle('fill', x, y, w, h) + end + + --Draw Cursor, if it exists + if self.cursor then + local c_i, c_j = self:getCursorPos() + if c_i == i+1 then + local x = (c_j-1)*_TILE_W + _cursor_sprite = _cursor_sprite or RES.loadSprite("cursor") + g.push() + g.translate(x, 0) + if self.cursor.validator(c_i, c_j) then + -- NAME + local body = sector:getBodyAt(c_i, c_j) + if body then + named = body + end + g.setColor(COLORS.NEUTRAL) + else + g.setColor(1, 50/255, 50/255) + end + _cursor_sprite:draw(0, 0) + g.pop() + end + end + + -- Draw dem bodies + for _, bodyinfo in ipairs(draw_bodies) do + local body, x, y = unpack(bodyinfo) + local i,j = body:getPos() + + --Draw only bodies if player is seeing them + if not self.fov or (self.fov[i][j] and self.fov[i][j] ~= 0) then + + local body_sprite = self:getBodySprite(body) + g.setColor(COLORS.NEUTRAL) + body_sprite:draw(x, y) + + end + end + + -- Draw drop shadows + for _,drop in ipairs(draw_drops) do + local specname, x, y, z, oscilation = unpack(drop) + local decrease = 1 + math.abs(oscilation)/18 + g.setColor(0, 0, 0, 0.4) + g.ellipse('fill', x + _TILE_W/2, y + _TILE_H/2, 16/decrease, 6/decrease, + 16) + end + + -- Draw drop sprites + for _,drop in ipairs(draw_drops) do + local specname, x, y, z, oscilation = unpack(drop) + local offset = vec2(0,0) + local sprite = RES.loadTexture(DB.loadSpec('drop', specname).sprite) + local rx = x + _TILE_W/2 + offset.x + local ry = y - _TILE_H*.25 + offset.y - z + oscilation + local iw, ih = sprite:getDimensions() + g.setColor(COLORS.NEUTRAL) + g.draw(sprite, rx, ry, 0, 1, 1, 32, 24) + g.draw(_sparkles, x + _TILE_W/2, y + _TILE_H/2-ih/2, 0, 1, 1, 0, 0) + end + + + g.translate(0, _TILE_H) + end + g.pop() + + -- Draw cooldown bars & HP + for _, body in ipairs(all_bodies) do + local i,j = body:getPos() + --Draw only if player is seeing them + x, y = (j-0.5)*_TILE_W, (i-0.5)*_TILE_H + if not self.fov or (self.fov[i][j] and self.fov[i][j] ~= 0) then + SECTOR_LIFEBAR.draw(body, x, y) + local actor = body:getActor() if actor then + local is_controlled = (actor == sector:getRoute().getControlledActor()) + SECTOR_COOLDOWNBAR.draw(actor, x, y, is_controlled) + end + end + end + + -- name, above everything + for _,body in ipairs(all_bodies) do + local i, j = body:getPos() + if not self.fov or (self.fov[i][j] and self.fov[i][j] ~= 0) then + local x, y = (j-1)*_TILE_W, (i-1)*_TILE_H + g.push() + g.translate(x, y) + -- NAME + if named == body then + local name + local actor = sector:getActorFromBody(body) + if actor then + name = actor:getTitle() + else + name = body:getSpec('name') + end + _font.set() + _font:setLineHeight(.8) + g.setColor(COLORS.NEUTRAL) + g.printf(name, -0.5*_TILE_W, _TILE_H-5, 2*_TILE_W, "center") + end + g.pop() + end + end + + g.pop() + +end + +--CURSOR FUNCTIONS + +function SectorView:newCursor(i, j, aoe_hint, validator, range_checker) + i, j = i or 1, j or 1 + self.cursor = Cursor(i, j, aoe_hint, validator, range_checker) +end + +function SectorView:removeCursor() + self.cursor = nil +end + +function SectorView:getCursorPos() + if not self.cursor then return end + + return self.cursor:getPos() +end + +function SectorView:setCursorPos(i,j) + if not self.cursor then return end + + self.cursor.i = i + self.cursor.j = j +end + +--Function checks if a target position is inside "target cone" given desired direction {di,dj} +function _isInCone(origin_i, origin_j, target_i, target_j, dir) + local i = target_i - origin_i + local j = target_j - origin_j + + if dir == "UP" then --UP + return j >= i and j <= -i + elseif dir == "RIGHT" then --RIGHT + return i >= -j and i <= j + elseif dir == "DOWN" then --DOWN + return j <= i and j >= -i + elseif dir == "LEFT" then --LEFT + return i <= -j and i >= j + elseif dir == "UPRIGHT" then --UPRIGHT + return i <= 0 and j >= 0 + elseif dir == "DOWNRIGHT" then --DOWNRIGHT + return i >= 0 and j >= 0 + elseif dir == "DOWNLEFT" then --DOWNLEFT + return i >= 0 and j <= 0 + elseif dir == "UPLEFT" then --UPLEFT + return i <= 0 and j <= 0 + else + return error(("Not valid direction for cone function: %s"):format(dir)) + end + +end + +function SectorView:moveCursor(di, dj) + if not self.cursor then return end + + local sector = self.route.getCurrentSector() + local queue = Queue(128) + local chosen = false + local sector_map = {} + + if not sector:isInside(self.cursor.i + di, self.cursor.j + dj) then + return + end + + -- get direction's name + local dirname + for _,dir in ipairs(DIR) do + local i, j = unpack(DIR[dir]) + dirname = dir + if di == i and dj == j then break end + end + + --Reset all sector position to "not-seen" + for i = 1, sector.h do + sector_map[i] = {} + for j = 1, sector.w do + sector_map[i][j] = false + end + end + + --Initialize queue with first valid position + sector_map[self.cursor.i][self.cursor.j] = true + queue.push({self.cursor.i, self.cursor.j}) + + --Start "custom-bfs" + while not queue.isEmpty() do + if chosen then break end + local pos = queue.pop() + + --Else add all other valid positions to the queue + for _,dir in ipairs(DIR) do + local i, j = unpack(DIR[dir]) + local target_pos = {pos[1] + i, pos[2] + j} + --Check if position is inside sector + if sector:isInside(unpack(target_pos)) + --Check if position hasn't been "seen" + and not sector_map[target_pos[1]][target_pos[2]] + --Check if position is inside desired cone + and _isInCone(self.cursor.i, self.cursor.j, + target_pos[1], target_pos[2], dirname) + -- Check if position is within range + and self.cursor.range_checker(unpack(target_pos)) then + + -- if it's a valid target use it! + if self.cursor.validator(unpack(target_pos)) then + chosen = target_pos + break + end + + --Mark position as "seen" + sector_map[target_pos[1]][target_pos[2]] = true + queue.push(target_pos) + end + end + pos = nil + end + + if chosen then + self.cursor.i = chosen[1] + self.cursor.j = chosen[2] + end + +end + +function SectorView:lookAtCursor() + if self.cursor then + self:lookAt(self.cursor) + end +end + +--CURSOR CLASS-- + +Cursor = Class{ + __includes = { ELEMENT } +} + +function Cursor:init(i, j, aoe_hint, validator, range_checker) + self.i = i + self.j = j + self.aoe_hint = aoe_hint + + self.validator = validator + self.range_checker = range_checker +end + +function Cursor:getPos() + return self.i, self.j +end + +return SectorView diff --git a/game/view/sector/cooldownbar.lua b/game/view/sector/cooldownbar.lua new file mode 100644 index 00000000..16d73d82 --- /dev/null +++ b/game/view/sector/cooldownbar.lua @@ -0,0 +1,71 @@ + +local ACTIONDEFS = require 'domain.definitions.action' + +local COOLDOWNBAR = {} + +local _BARSCALE = 5 +local _SMOOTH_FACTOR = 0.2 + +local _barstates +local _preview +local _glow + +function COOLDOWNBAR.init() + _barstates = {} + _preview = 0 + _glow = {} +end + +function COOLDOWNBAR.setCooldownPreview(value) + _preview = value or 0 +end + +function COOLDOWNBAR.draw(actor, x, y, is_controlled) + local g = love.graphics + local cooldown = actor:getCooldown() + local last = _barstates[actor:getId()] or 0 + local value = last + (cooldown - last)*_SMOOTH_FACTOR + if math.abs(value) < 1 then + value = 0 + end + _barstates[actor:getId()] = value + local unit = actor:getSPD()*ACTIONDEFS.CYCLE_UNIT*_BARSCALE + local percent = math.fmod(value, unit)/unit + local pi = math.pi + local start = pi/2 + 2*pi/36 + local length = 2*pi/3 + g.push() + g.translate(x, y) + g.scale(1, 1/2) + g.setLineWidth(8) + g.setColor(1, 1, 1, 0.2) + g.arc('line', 'open', 0, 0, 36, start, start + length, 32) + g.setColor(1, 1, 1, 0.8) + g.arc('line', 'open', 0, 0, 36, start, start + length * percent, 32) + + local glow = _glow[actor:getId()] or 0 + glow = glow + love.timer.getDelta() + local alpha = 0.5 + 0.3*math.sin(2 * glow * 2 * math.pi) + _glow[actor:getId()] = glow + + g.setColor(.8, .2, 0, alpha) + if is_controlled then + g.arc('line', 'open', 0, 0, 36, start, start + (_preview/unit) * length, 32) + elseif _preview > 0 then + local controlled = actor:getSector():getRoute().getControlledActor() + local turns = math.ceil(_preview / controlled:getSPD()) + local recovered = math.min(actor:getSPD() * turns, value) + g.arc('line', 'open', 0, 0, 36, start + (value - recovered) / unit * length, + start + percent * length, 32) + end + for i=1,4 do + g.setColor(0, 0, 0.4, 0.5) + local r = start + length * i/_BARSCALE + local w = length/100 + g.arc('line', 'open', 0, 0, 36, r-w, r+w, 32) + end + g.pop() +end + +return COOLDOWNBAR + diff --git a/game/view/sector/lifebar.lua b/game/view/sector/lifebar.lua new file mode 100644 index 00000000..5f74a98e --- /dev/null +++ b/game/view/sector/lifebar.lua @@ -0,0 +1,46 @@ + +local Color = require 'common.color' +local VIEWDEFS = require 'view.definitions' + +local _SMOOTH_FACTOR = 0.2 + +local _lifestates = {} + +local LIFEBAR = {} + +function LIFEBAR.draw(body, x, y) + local g = love.graphics + local id = body:getId() + local current = _lifestates[id] or 0 + local hp = body:getHP() + local max_hp = body:getMaxHP() + local armor = body:getArmor() + current = current + (hp - current) * _SMOOTH_FACTOR + if math.abs(hp - current) < 1 then + current = hp + end + _lifestates[id] = current + local hppercent = current / (max_hp + armor) + local armorpercent = armor / (max_hp + armor) + local hsvcol = { 0 + 100*hppercent, 240, 255 - 50*hppercent } + local cr, cg, cb = Color.fromHSV(unpack(hsvcol)):unpack() + local pi = math.pi + local start = pi/2 - 2*pi/36 + local length = -2*pi/3 + g.push() + g.translate(x, y) + g.scale(1, 1/2) + g.setLineWidth(8) + g.setColor(0, 0, 0, 0.2) + g.arc('line', 'open', 0, 0, 36, start, start + length, 32) + g.setColor(cr, cg, cb, 0.5) + g.arc('line', 'open', 0, 0, 36, start, start + length * hppercent, 32) + g.setColor(1, 1, 1, 0.5) + g.arc('line', 'open', 0, 0, 36, start + length * hppercent, + start + length * (hppercent + armorpercent), + 32) + g.pop() +end + +return LIFEBAR + diff --git a/game/view/sector/mesh/wall.lua b/game/view/sector/mesh/wall.lua new file mode 100644 index 00000000..b9a6e9bf --- /dev/null +++ b/game/view/sector/mesh/wall.lua @@ -0,0 +1,76 @@ + +local VIEWDEFS = require 'view.definitions' +local vec3 = require 'cpml' .vec3 + +local _WALL_H = 80 +local _QUADFACES = {1, 2, 3, 2, 4, 3} + +local WallMesh = require 'lux.prototype' :new {} + +WallMesh.__init = { + pos = vec3(), + count = 0, + vertices = {}, + faces = {}, + border_color = {.5, .5, .5, 1} +} + +local function _tov3(v) + return vec3(v.x, v.y, 0) +end + +local function _appendVertices(mesh, color, ...) + local vtx_seq = { ... } + for _,vtx in ipairs(vtx_seq) do + mesh.count = mesh.count + 1 + mesh.vertices[mesh.count] = { vtx.x, vtx.y, vtx.z, unpack(color) } + end +end + +local function _appendFace(mesh, base) + local n = #mesh.faces + for i,idx in ipairs(_QUADFACES) do + mesh.faces[n+i] = base + idx + end +end + +local function _appendQuad(mesh, color, v1, v2, v3, v4) + local base = mesh.count + local pos = _tov3(mesh.pos) + _appendVertices(mesh, color, pos+v1, pos+v2, pos+v3, pos+v4) + _appendFace(mesh, base) +end + +function WallMesh:map() + return ipairs(self.faces) +end + +function WallMesh:getVertex(i) + return { unpack(self.vertices[i]) } +end + +function WallMesh:addSide(color, v1, v2, b1, b2) + local height = vec3(0,0,-1)*_WALL_H + b2 = b2 or b1 + v1, v2, b1, b2 = _tov3(v1), _tov3(v2), _tov3(b1), _tov3(b2) + if color then + _appendQuad(self, color, v1 + height, v2 + height, v1, v2) + end + if b1 then + _appendQuad(self, self.border_color, v1 + height + b1, v2 + height + b2, + v1 + height, v2 + height) + end +end + +function WallMesh:addTop(color, v1, v2, v3, v4, extra, ...) + local height = vec3(0,0,-1)*_WALL_H + v1, v2, v3, v4 = _tov3(v1), _tov3(v2), _tov3(v3), _tov3(v4 or v3) + _appendQuad(self, color, v1 + height, v2 + height, v3 + height, + v4 + height) + if extra then + return self:addTop(color, v3, v4, extra, ...) + end +end + +return WallMesh + diff --git a/game/view/sector/tilemap.lua b/game/view/sector/tilemap.lua new file mode 100644 index 00000000..96f1ec6d --- /dev/null +++ b/game/view/sector/tilemap.lua @@ -0,0 +1,131 @@ + +local RES = require 'resources' +local CAM = require 'common.camera' +local SCHEMATICS = require 'domain.definitions.schematics' +local COLORS = require 'domain.definitions.colors' +local VIEWDEFS = require 'view.definitions' + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H +local _VIEW_W = VIEWDEFS.HALF_W*2 - 2 +local _VIEW_H = VIEWDEFS.HALF_H*2 - 2 +local _SEEN_ABYSS = COLORS.BACKGROUND * COLORS.HALF_VISIBLE + +local _FXCODE = [[ +uniform Image mask; + +vec4 effect(vec4 color, Image tex, vec2 uv, vec2 pos) { + vec2 p = pos/love_ScreenSize.xy; + return color * Texel(tex, uv) * Texel(mask, p); +} +]] +local _FXSHADER + +local TILEMAP = {} + +local _sector +local _tile_batch +local _fovmask +local _tilemask + +function TILEMAP.init(sector, tileset) + local pixel_texture = RES.loadTexture("pixel") + local texture = RES.loadTexture(tileset.texture) + _tile_batch = love.graphics.newSpriteBatch(texture, 512, "stream") + _tile_offset = tileset.offsets + _tile_quads = tileset.quads + _sector = sector + _fovmask = love.graphics.newCanvas(_VIEW_W * _TILE_W, _VIEW_H * _TILE_H) + _FXSHADER = _FXSHADER or love.graphics.newShader(_FXCODE) + if not _tilemask then + local data = love.image.newImageData(_TILE_W*3, _TILE_H*3) + data:mapPixel( + function (x, y) + y = y*_TILE_W/_TILE_H + local px = math.max(_TILE_W, math.min(2*_TILE_W, x)) + local py = math.max(_TILE_W, math.min(2*_TILE_W, y)) + local d = math.sqrt((x - px)^2 + (y - py)^2)/_TILE_W*2 + local c = math.max(0, (1-d^2)) + return c, c, c, 1 + end + ) + _tilemask = love.graphics.newImage(data) + end +end + +function TILEMAP.calculateFOVMask(g, fov) + g.setCanvas(_fovmask) + g.clear() + g.setColor(COLORS.BLACK) + + g.push() + g.origin() + g.rectangle('fill', 0, 0, _fovmask:getWidth(), _fovmask:getHeight()) + g.pop() + + g.setBlendMode('lighten', 'premultiplied') + for i, j in CAM:tilesInRange() do + local ti, tj = i+1, j+1 -- logic coordinates + local x, y = j*_TILE_W, i*_TILE_H + local color = COLORS.NEUTRAL + if fov and fov[ti] then + local visibility = fov[ti][tj] + if not visibility then + color = COLORS.BLACK + elseif visibility == 0 then + color = COLORS.HALF_VISIBLE + else + color = COLORS.NEUTRAL + end + end + g.setColor(color) + g.draw(_tilemask, x - _TILE_W, y - _TILE_H) + end + g.setBlendMode('alpha', 'alphamultiply') + g.setCanvas() + + return _fovmask +end + +function TILEMAP.drawAbyss(g, fov) + g.push() + + g.origin() + _FXSHADER:send('mask', _fovmask) + g.setShader(_FXSHADER) + g.setColor(COLORS.BACKGROUND) + g.rectangle('fill', 0, 0, _VIEW_W*_TILE_W, _VIEW_H*_TILE_H) + g.setShader() + + g.pop() +end + +function TILEMAP.drawFloor(g) + _tile_batch:clear() + for i, j in CAM:tilesInRange() do + local ti, tj = i+1, j+1 -- logic coordinates + local tile = _sector.tiles[ti] and _sector.tiles[ti][tj] + if tile then + local tile_type = (tile.type == SCHEMATICS.WALL) + and SCHEMATICS.FLOOR or tile.type + local x, y = j*_TILE_W, i*_TILE_H + _tile_batch:add(_tile_quads[tile_type], x, y, + 0, 1, 1, unpack(_tile_offset[tile.type])) + end + end + + _FXSHADER:send('mask', _fovmask) + g.setShader(_FXSHADER) + g.setColor(COLORS.NEUTRAL) + g.draw(_tile_batch, 0, 0) + g.setShader() + + _tile_batch:clear() +end + +function TILEMAP.drawWallInLine(g, i, fov) + -- to be implemented in v11.0 +end + +return TILEMAP + diff --git a/game/view/sector/wall.lua b/game/view/sector/wall.lua new file mode 100644 index 00000000..1af327d7 --- /dev/null +++ b/game/view/sector/wall.lua @@ -0,0 +1,333 @@ + +local WALL = {} + +local SCHEMATICS = require 'domain.definitions.schematics' +local VIEWDEFS = require 'view.definitions' + +local vec2 = require 'cpml' .vec2 +local WallMesh = require 'view.sector.mesh.wall' + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H +local _WALL_H = 80 +local _MARGIN_W = 12 +local _MARGIN_H = 8 +local _BORDER_W = 8 +local _BORDER_H = 6 +local _GRID_W = 24 +local _GRID_H = 18 + +local _TOPLEFT = vec2(0,0) +local _TOPRIGHT = vec2(_TILE_W, 0) +local _BOTLEFT = vec2(0, _TILE_H) +local _BOTRIGHT = vec2(_TILE_W, _TILE_H) + +local _BACK_COLOR = {31/256, 44/256, 38/256, 0.4} +local _FRONT_COLOR = {31/256, 44/256, 38/256, 1} +local _BORDER_COLOR = {43/256, 100/256, 112/256, 1} +local _TOP_COLOR = {0.2, 0.2, 0.2, 0.6} + +local _W, _H +local _MAX_VTX = 4096 + +local _VTX_FORMAT = { + {'VertexPosition', 'float', 2}, + {'Height', 'float', 1}, + {'VertexColor', 'float', 4}, +} + +local _VTXCODE = [[ +uniform Image mask; +attribute number Height; + +vec4 position(mat4 transform_projection, vec4 vertex_position) { + vec4 h = Height * vec4(0, 1, 0, 0); + vec4 pos = ProjectionMatrix * TransformMatrix * vertex_position; + pos.y = -pos.y; + VaryingColor *= Texel(mask, pos.xy/2 + 0.5); + return transform_projection * (vertex_position+h); +} +]] +local _VTXSHADER + +local _mesh +local _rowmeshes +local _vertexcount = 0 + +local function _wallidx(i, j) + return (i-1)*_W + j +end + +local function _neighbors(sector, i, j) + local neighbors = {} + for r=1,3 do + local di = r - 2 + neighbors[r] = {} + for s=1,3 do + local dj = s - 2 + neighbors[r][s] = sector:isInside(i + di, j + dj) + and sector:getTile(i + di, j + dj) + or false + end + end + return neighbors +end + +local function _empty(neighbors, r, s) + return not neighbors[r][s] or neighbors[r][s].type ~= SCHEMATICS.WALL +end + +local function _walled(neighbors, r, s) + return neighbors[r][s] and neighbors[r][s].type == SCHEMATICS.WALL +end + +local function _rect(left, right, top, bottom) + return vec2(left, top), vec2(right, top), vec2(left, bottom), + vec2(right, bottom) +end + +local function _xform(off, sx, sy, v1, ...) + if not v1 then return end + return off + vec2(v1.x * sx, v1.y * sy), _xform(off, sx, sy, ...) +end + +local function _pack(t) + return function () return unpack(t) end +end + +local _CORNER_HOR = _pack{ _rect(0, _GRID_W, _MARGIN_H + _BORDER_H, _GRID_H) } +local _CORNER_VER = _pack{ _rect(_MARGIN_W + _BORDER_W, _GRID_W, 0, _GRID_H) } +local _CORNER_OUT = _pack{ vec2(_MARGIN_W + _BORDER_W, _GRID_H), + vec2(_GRID_W, _MARGIN_H + _BORDER_H), + vec2(_GRID_W, _GRID_H) } +local _CORNER_INN = _pack{ vec2(0, _MARGIN_H + _BORDER_H), + vec2(_MARGIN_W + _BORDER_W, 0), vec2(0, _GRID_H), + vec2(_GRID_W, 0), vec2(_GRID_W, _GRID_H) } +local _CORNER_ALL = _pack{ _rect(0, _GRID_W, 0, _GRID_H) } + +function WALL.load(sector) + local count = 0 + _W, _H = sector:getDimensions() + _rowmeshes = {} + if not _VTXSHADER then _VTXSHADER = love.graphics.newShader(_VTXCODE) end + for i=1,_H do + local vertices = {} + local map = {} + for j=1,_W do + local neighbors = _neighbors(sector, i, j) + local tile = sector:getTile(i,j) + local wall = false + if tile and tile.type == SCHEMATICS.WALL then + local x0 = (j-1)*_TILE_W + local y0 = 0 + wall = WallMesh:new { pos = vec2(x0,y0), border_color = _BORDER_COLOR } + + -- top + if _empty(neighbors, 1, 2) then + wall:addSide(_BACK_COLOR, vec2(_GRID_W, _MARGIN_H), + vec2(_TILE_W - _GRID_W, _MARGIN_H), + vec2(0, _BORDER_H)) + wall:addTop(_TOP_COLOR, _rect(_GRID_W, _TILE_W - _GRID_W, + _MARGIN_H + _BORDER_H, _GRID_H)) + else + wall:addTop(_TOP_COLOR, _rect(_GRID_W, _TILE_W - _GRID_W, 0, _GRID_H)) + end + + -- topleft + if _walled(neighbors, 2, 1) and _empty(neighbors, 1, 2) then + -- straight left + wall:addSide(_BACK_COLOR, vec2(0, _MARGIN_H), + vec2(_GRID_W, _MARGIN_H), + vec2(0, _BORDER_H)) + wall:addTop(_TOP_COLOR, _CORNER_HOR()) + elseif _walled(neighbors, 1, 2) and _empty(neighbors, 2, 1) then + -- straight up + wall:addSide(nil, vec2(_MARGIN_W, 0), vec2(_MARGIN_W, _GRID_H), + vec2(_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _CORNER_VER()) + elseif _empty(neighbors, 2, 1) and _empty(neighbors, 1, 2) then + -- outer corner + wall:addSide(_BACK_COLOR, vec2(_MARGIN_W, _GRID_H), + vec2(_GRID_W, _MARGIN_H), + vec2(_BORDER_W, 0), vec2(0, _BORDER_H)) + wall:addTop(_TOP_COLOR, _CORNER_OUT()) + elseif _walled(neighbors, 2, 1) and _walled(neighbors, 1, 2) and + _empty(neighbors, 1, 1) then + -- inner corner + wall:addSide(_BACK_COLOR, vec2(0, _MARGIN_H), vec2(_MARGIN_W, 0), + vec2(0, _BORDER_H), vec2(_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _CORNER_INN()) + else + wall:addTop(_TOP_COLOR, _CORNER_ALL()) + end + + -- topright + if _walled(neighbors, 2, 3) and _empty(neighbors, 1, 2) then + -- straight right + wall:addSide(_BACK_COLOR, vec2(_TILE_W - _GRID_W, _MARGIN_H), + vec2(_TILE_W, _MARGIN_H), + vec2(0, _BORDER_H)) + wall:addTop(_TOP_COLOR, _xform(_TOPRIGHT, -1, 1, _CORNER_HOR())) + elseif _walled(neighbors, 1, 2) and _empty(neighbors, 2, 3) then + -- straight up + wall:addSide(nil, vec2(_TILE_W - _MARGIN_W, 0), + vec2(_TILE_W - _MARGIN_W, _GRID_H), + vec2(-_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _xform(_TOPRIGHT, -1, 1, _CORNER_VER())) + elseif _empty(neighbors, 1, 2) and _empty(neighbors, 2, 3) then + -- outer corner + wall:addSide(_BACK_COLOR, vec2(_TILE_W - _MARGIN_W, _GRID_H), + vec2(_TILE_W - _GRID_W, _MARGIN_H), + vec2(-_BORDER_W, 0), vec2(0, _BORDER_H)) + wall:addTop(_TOP_COLOR, _xform(_TOPRIGHT, -1, 1, _CORNER_OUT())) + elseif _walled(neighbors, 1, 2) and _walled(neighbors, 2, 3) and + _empty(neighbors, 1, 3) then + -- inner corner + wall:addSide(_BACK_COLOR, vec2(_TILE_W, _MARGIN_H), + vec2(_TILE_W - _MARGIN_W, 0), + vec2(0, _BORDER_H), vec2(-_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _xform(_TOPRIGHT, -1, 1, _CORNER_INN())) + else + wall:addTop(_TOP_COLOR, _xform(_TOPRIGHT, -1, 1, _CORNER_ALL())) + end + + -- left + if _empty(neighbors, 2, 1) then + local border = vec2(_BORDER_W,0) + wall:addSide(nil, vec2(_MARGIN_W, _GRID_H), + vec2(_MARGIN_W, _TILE_H - _GRID_H), border) + wall:addTop(_TOP_COLOR, _rect(_MARGIN_W + _BORDER_W, _GRID_W, + _GRID_H, _TILE_H - _GRID_H)) + else + wall:addTop(_TOP_COLOR, _rect(0, _GRID_W, _GRID_H, _TILE_H - _GRID_H)) + end + + -- middle + do + wall:addTop(_TOP_COLOR, _rect(_GRID_W, _TILE_W - _GRID_W, _GRID_H, + _TILE_H - _GRID_H)) + end + + -- right + if _empty(neighbors, 2, 3) then + local border = vec2(-_BORDER_W,0) + wall:addSide(nil, vec2(_TILE_W - _MARGIN_W, _GRID_H), + vec2(_TILE_W - _MARGIN_W, _TILE_H - _GRID_H), + border) + wall:addTop(_TOP_COLOR, _rect(_TILE_W - _GRID_W, + _TILE_W - (_MARGIN_W + _BORDER_W), + _GRID_H, _TILE_H - _GRID_H)) + else + wall:addTop(_TOP_COLOR, _rect(_TILE_W - _GRID_W, _TILE_W, _GRID_H, + _TILE_H - _GRID_H)) + end + + -- front + if _empty(neighbors, 3, 2) then + wall:addSide(_FRONT_COLOR, _BOTLEFT + vec2(_GRID_W, -_MARGIN_H), + _BOTRIGHT - vec2(_GRID_W, _MARGIN_H), + vec2(0, -_BORDER_H)) + wall:addTop(_TOP_COLOR, _rect(_GRID_W, _TILE_W - _GRID_W, + _TILE_H - _GRID_H, + _TILE_H - (_MARGIN_H + _BORDER_H))) + else + wall:addTop(_TOP_COLOR, _rect(_GRID_W, _TILE_W - _GRID_W, + _TILE_H - _GRID_H , _TILE_H)) + end + + -- bottomleft + if _walled(neighbors, 2, 1) and _empty(neighbors, 3, 2) then + -- straight left + wall:addSide(_FRONT_COLOR, _BOTLEFT - vec2(0, _MARGIN_H), + _BOTLEFT + vec2(_GRID_W, -_MARGIN_H), + vec2(0, -_BORDER_H)) + wall:addTop(_TOP_COLOR, _xform(_BOTLEFT, 1, -1, _CORNER_HOR())) + elseif _walled(neighbors, 3, 2) and _empty(neighbors, 2, 1) then + -- straight down + wall:addSide(nil, _BOTLEFT + vec2(_MARGIN_W, -_GRID_H), + _BOTLEFT + vec2(_MARGIN_W, 0), + vec2(_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _xform(_BOTLEFT, 1, -1, _CORNER_VER())) + elseif _empty(neighbors, 2, 1) and _empty(neighbors, 3, 2) then + -- outer corner + wall:addSide(_FRONT_COLOR, _BOTLEFT + vec2(_MARGIN_W, -_GRID_H), + _BOTLEFT + vec2(_GRID_W, -_MARGIN_H), + vec2(_BORDER_W, 0), vec2(0, -_BORDER_H)) + wall:addTop(_TOP_COLOR, _xform(_BOTLEFT, 1, -1, _CORNER_OUT())) + elseif _walled(neighbors, 2, 1) and _walled(neighbors, 3, 2) and + _empty(neighbors, 3, 1) then + -- inner corner + wall:addSide(_FRONT_COLOR, _BOTLEFT + vec2(0, -_MARGIN_H), + _BOTLEFT + vec2(_MARGIN_W, 0), + vec2(0, -_BORDER_H), vec2(_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _xform(_BOTLEFT, 1, -1, _CORNER_INN())) + else + wall:addTop(_TOP_COLOR, _xform(_BOTLEFT, 1, -1, _CORNER_ALL())) + end + + -- bottomright + if _walled(neighbors, 2, 3) and _empty(neighbors, 3, 2) then + -- straight right + wall:addSide(_FRONT_COLOR, _BOTRIGHT - vec2(0, _MARGIN_H), + _BOTRIGHT - vec2(_GRID_W, _MARGIN_H), + vec2(0, -_BORDER_H)) + wall:addTop(_TOP_COLOR, _xform(_BOTRIGHT, -1, -1, _CORNER_HOR())) + elseif _walled(neighbors, 3, 2) and _empty(neighbors, 2, 3) then + -- straight down + wall:addSide(nil, _BOTRIGHT - vec2(_MARGIN_W, _GRID_H), + _BOTRIGHT - vec2(_MARGIN_W, 0), + vec2(-_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _xform(_BOTRIGHT, -1, -1, _CORNER_VER())) + elseif _empty(neighbors, 2, 3) and _empty(neighbors, 3, 2) then + -- outer corner + wall:addSide(_FRONT_COLOR, _BOTRIGHT - vec2(_MARGIN_W, _GRID_H), + _BOTRIGHT - vec2(_GRID_W, _MARGIN_H), + vec2(-_BORDER_W, 0), vec2(0, -_BORDER_H)) + wall:addTop(_TOP_COLOR, _xform(_BOTRIGHT, -1, -1, _CORNER_OUT())) + elseif _walled(neighbors, 2, 3) and _walled(neighbors, 3, 2) and + _empty(neighbors, 3, 3) then + -- inner corner + wall:addSide(_FRONT_COLOR, _BOTRIGHT - vec2(0, _MARGIN_H), + _BOTRIGHT - vec2(_MARGIN_W, 0), + vec2(0, -_BORDER_H), vec2(-_BORDER_W, 0)) + wall:addTop(_TOP_COLOR, _xform(_BOTRIGHT, -1, -1, _CORNER_INN())) + else + wall:addTop(_TOP_COLOR, _xform(_BOTRIGHT, -1, -1, _CORNER_ALL())) + end + end + if wall then + local n, m = #vertices, #map + for k,vtx in ipairs(wall.vertices) do + vertices[n+k] = vtx + end + for k,idx in ipairs(wall.faces) do + map[m+k] = n+idx + end + end + end + if #vertices > 0 then + local rowmesh = love.graphics.newMesh(_VTX_FORMAT, vertices, 'triangles', + 'static') + rowmesh:setVertexMap(map) + _rowmeshes[i] = rowmesh + else + _rowmeshes[i] = false + end + end + _mesh = love.graphics.newMesh(_MAX_VTX, 'triangles', 'stream') +end + +local _NULL_VTX = {0, 0, 0, 0, 0, 0, 0, 0} + +function WALL.drawRow(i, mask) + local g = love.graphics + _VTXSHADER:send('mask', mask) + if _rowmeshes[i] then + g.setShader(_VTXSHADER) + g.draw(_rowmeshes[i]) + g.setShader() + end +end + +return WALL + diff --git a/game/view/settings.lua b/game/view/settings.lua new file mode 100644 index 00000000..2c0b87c1 --- /dev/null +++ b/game/view/settings.lua @@ -0,0 +1,75 @@ + +local COLORS = require 'domain.definitions.colors' +local PROFILE = require 'infra.profile' +local FONT = require 'view.helpers.font' + +local fmod = math.fmod +local sin = math.sin + +local _PI = math.pi + +local _font + +local SettingsView = Class{ + __includes = { ELEMENT } +} + +local _getPreference = PROFILE.getPreference + +function SettingsView:init(fields) + ELEMENT.init(self) + self.fields = fields + self.focus = 1 + _font = FONT.get("Text", 20) +end + +function SettingsView:setFocus(idx) + self.focus = idx +end + +function SettingsView:draw() + local g = love.graphics + local focus = self.focus + local width = 256 + local height = 10 + local mx, my = 8, 8 + local font_height = 20 + local lw = 4 + local base_y = 256 + local x = 320 + -- draw one settings input type + FONT.set("Title", 32) + g.setColor(COLORS.NEUTRAL) + g.print("SETTINGS", x, base_y - 80) + _font:set() + for i, field in ipairs(self.fields) do + local y = base_y + (i - 1) * (height + 4*my + font_height) + local percentage = _getPreference(field) / 100 + local is_focused = (i == focus) + g.push() + g.translate(x, y) + + -- field name + g.setColor(is_focused and COLORS.NEUTRAL or COLORS.HALF_VISIBLE) + g.print(field:gsub("[-]", " "):upper(), 0, -font_height) + g.translate(0, font_height) + + -- line width offset + g.translate(0, lw) + + -- trail + g.setLineWidth(lw) + g.setColor(is_focused and COLORS.EMPTY or COLORS.DARKER) + g.line(0, 0, width, 0) + + -- value progression + g.setColor(is_focused and COLORS.NEUTRAL or COLORS.HALF_VISIBLE) + g.line(0, 0, percentage * width, 0) + g.ellipse("fill", percentage * width, 0, height, height) + + g.pop() + end +end + +return SettingsView + diff --git a/game/view/shaders/gaussian.lua b/game/view/shaders/gaussian.lua new file mode 100644 index 00000000..98e88d6f --- /dev/null +++ b/game/view/shaders/gaussian.lua @@ -0,0 +1,23 @@ + +return [=[ + +extern vec2 tex_size; +extern int range = 5; + +vec4 effect(vec4 color, Image tex, vec2 uv, vec2 screen_coords) { + vec2 pixel_size = 1.0 / tex_size; + float alpha = 0.0; + float ponder = 0.0; + for (int i = -range; i < range; i++) { + for (int j = -range; j < range; j++) { + float weight = -(distance(vec2(j, i), vec2(0.0, 0.0)) - 2.0 * range); + alpha += weight * (Texel(tex, uv + vec2(j, i) * pixel_size)).w; + ponder += weight; + } + } + alpha = alpha / ponder; + return vec4(1, 1, 1, alpha) * color; +} + +]=] + diff --git a/game/view/shaders/init.lua b/game/view/shaders/init.lua new file mode 100644 index 00000000..5083e883 --- /dev/null +++ b/game/view/shaders/init.lua @@ -0,0 +1,12 @@ + +local SHADERLOADER = require 'lux.pack' 'view.shaders' + +function SHADERLOADER:__index(k) + local value = getmetatable(self)[k] + local shader = love.graphics.newShader(value) + rawset(self, k, shader) + return shader +end + +return setmetatable({}, SHADERLOADER) + diff --git a/game/view/soundtrack/init.lua b/game/view/soundtrack/init.lua new file mode 100644 index 00000000..ad1bba9e --- /dev/null +++ b/game/view/soundtrack/init.lua @@ -0,0 +1,38 @@ + +local RES = require 'resources' +local PROFILE = require 'infra.profile' + +local SoundTrack = require 'lux.class' :new{} + +local source = love.source +local setfenv = setfenv + +function SoundTrack:instance(obj) + + setfenv(1, obj) + + local bgm = {} + + function playTheme(theme) + if theme and bgm.id ~= theme.bgm then + if bgm.stream then bgm.stream:stop() end + bgm.stream = RES.loadBGM(theme.bgm) + bgm.stream:play() + bgm.id = theme.bgm + updateVolume() + elseif not theme and bgm.stream then + bgm.stream:stop() + bgm.stream = nil + end + end + + function updateVolume() + if bgm.stream then + bgm.stream:setVolume(PROFILE.getPreference("bgm-volume") / 100) + end + end + +end + +return SoundTrack + diff --git a/game/view/spritefx/body_acted.lua b/game/view/spritefx/body_acted.lua new file mode 100644 index 00000000..bfb08f91 --- /dev/null +++ b/game/view/spritefx/body_acted.lua @@ -0,0 +1,30 @@ + +local VIEWDEFS = require 'view.definitions' +local SPRITEFX = {} + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H + +function SPRITEFX.apply(sectorview, args) + local body = args.body + local t = {0} + local body_sprite = sectorview:getBodySprite(body) + body_sprite:setDecorator( + function (self, x, y, ...) + local s = (t[1] - 0.5)*2 + offset = (s*s) * math.sin(math.pi*2*100*s) + body_sprite:render(x + _TILE_W/4*s, y, ...) + end + ) + sectorview:addTimer( + nil, MAIN_TIMER, "tween", 0.1, t, {1}, + "in-linear", + function() + body_sprite:clearDecorator() + sectorview:finishVFX() + end + ) +end + +return SPRITEFX + diff --git a/game/view/spritefx/body_jumped.lua b/game/view/spritefx/body_jumped.lua new file mode 100644 index 00000000..f764129c --- /dev/null +++ b/game/view/spritefx/body_jumped.lua @@ -0,0 +1,41 @@ + +local VIEWDEFS = require 'view.definitions' +local SPRITEFX = {} + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H + +function SPRITEFX.apply(sectorview, args) + local body, i, j = args.body, unpack(args.origin) + local i0, j0 = body:getPos() + local offset = {i - i0, j - j0} + local jump_offset = {0} + local di, dj = unpack(offset) + local body_sprite = sectorview:getBodySprite(body) + body_sprite:setDecorator( + function (self, x, y, ...) + local di, dj = unpack(offset) + local dx, dy = dj*_TILE_W, di*_TILE_H + x, y = x+dx, y+dy + self:render(x, y - jump_offset[1], ...) + end + ) + sectorview:addTimer( + nil, MAIN_TIMER, "tween", 0.2/args.speed_factor, jump_offset, {O_WIN_H}, + "in-out-quad", + function() + offset = {0, 0} + sectorview:addTimer( + nil, MAIN_TIMER, "tween", 0.2/args.speed_factor, jump_offset, {0}, + "in-quad", + function() + body_sprite:clearDecorator() + sectorview:finishVFX() + end + ) + end + ) +end + +return SPRITEFX + diff --git a/game/view/spritefx/body_moved.lua b/game/view/spritefx/body_moved.lua new file mode 100644 index 00000000..ce6ade25 --- /dev/null +++ b/game/view/spritefx/body_moved.lua @@ -0,0 +1,42 @@ + +local VIEWDEFS = require 'view.definitions' +local SPRITEFX = {} + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H + +function SPRITEFX.apply(sectorview, args) + local body, i, j = args.body, unpack(args.origin) + local i0, j0 = body:getPos() + local offset = {i - i0, j - j0} + local di, dj = unpack(offset) + local dist = (di*di + dj*dj)^0.5 + local body_sprite = sectorview:getBodySprite(body) + local current_observer = sectorview:getTarget() + if current_observer and current_observer.fov then + local fov = current_observer:getFov() + if (not fov[i0][j0] or fov[i0][j0] == 0) and + (not fov[i][j] or fov[i][j] == 0) then + return sectorview:finishVFX() + end + end + body_sprite:setDecorator( + function (self, x, y, ...) + local di, dj = unpack(offset) + local dx, dy = dj*_TILE_W, di*_TILE_H + x, y = x+dx, y+dy + body_sprite:render(x, y, ...) + end + ) + sectorview:addTimer( + nil, MAIN_TIMER, "tween", dist/20/args.speed_factor, offset, {0, 0}, + "in-out-quad", + function() + body_sprite:clearDecorator() + sectorview:finishVFX() + end + ) +end + +return SPRITEFX + diff --git a/game/view/spritefx/drop_spread.lua b/game/view/spritefx/drop_spread.lua new file mode 100644 index 00000000..628b7853 --- /dev/null +++ b/game/view/spritefx/drop_spread.lua @@ -0,0 +1,52 @@ + +local VIEWDEFS = require 'view.definitions' +local RNG = require 'common.random' +local SPRITEFX = {} + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H + +local _DELAY = 0.05 +local _DURATION = 0.2 + +function SPRITEFX.apply(sectorview, args) + local i, j = unpack(args.origin) + local drops = args.drops + local finished = 0 + local total = #drops + local count = 1 + + if total <= 0 then + return sectorview:finishVFX() + end + + local offsets = {} + for t,drop in ipairs(drops) do + local ti, tj, k = unpack(drop) + local offset = {t=0, i=i, j=j} + sectorview:setDropOffset(ti, tj, k, offset) + offsets[t] = offset + end + + local function launch() + local drop = drops[count] + local offset = offsets[count] + count = count + 1 + local ti, tj, k = unpack(drop) + sectorview:addTimer( + nil, MAIN_TIMER, "tween", _DURATION, offset, {t=1}, 'in-out-linear', + function() + finished = finished + 1 + if finished >= total then + sectorview:finishVFX() + end + end + ) + end + + launch() + sectorview:addTimer(nil, MAIN_TIMER, "every", _DELAY, launch, #drops-1) +end + +return SPRITEFX + diff --git a/game/view/spritefx/text_rise.lua b/game/view/spritefx/text_rise.lua new file mode 100644 index 00000000..aa8c7451 --- /dev/null +++ b/game/view/spritefx/text_rise.lua @@ -0,0 +1,68 @@ + +local VIEWDEFS = require 'view.definitions' +local FONT = require 'view.helpers.font' +local COLORS = require 'domain.definitions.colors' +local Color = require 'common.color' +local SPRITEFX = {} + +local _TILE_W = VIEWDEFS.TILE_W +local _TILE_H = VIEWDEFS.TILE_H + +local _NUMBER_COLOR = { + damage = 'NOTIFICATION', + heal = 'SUCCESS', + food = 'WARNING', + armor = 'HALF_VISIBLE', + status = 'WARNING' +} + +local _SIGNALS = { + damage = '-', + heal = '+', + food = '+', + armor = '+', +} + +local _font + +function SPRITEFX.apply(sectorview, args) + local body, amount = args.body, args.amount + local text_type = args.text_type + local signal = _SIGNALS[text_type] + local i, j = body:getPos() + local body_sprite = sectorview:getBodySprite(body) + local animation_info = { y = 0, a = 0.5} + local text + if args.string then + text = args.string + else + text = ("%s%d"):format(signal, amount) + end + _font = _font or FONT.get('Text', 32) + body_sprite:setDecorator( + function (self, x, y, ...) + local g = love.graphics + body_sprite:render(x, y, ...) + x = x + _TILE_W/2 - _font:getWidth(text)/2 + y = y - _TILE_H/2 - animation_info.y + _font:set() + local transparency = COLORS.NEUTRAL + * Color:new {1, 1, 1, animation_info.a} + g.setColor(COLORS.DARK * transparency) + g.print(text, x + 2, y + 2) + g.setColor(COLORS[_NUMBER_COLOR[text_type]] * transparency) + g.print(text, x, y) + end + ) + sectorview:addTimer(nil, MAIN_TIMER, "tween", 0.2, + animation_info, { y = 96, a = 1 }, "out-cubic", + function() + sectorview:addTimer(nil, MAIN_TIMER, "after", 0.3, + function () + body_sprite:clearDecorator() + sectorview:finishVFX() + end) + end) +end + +return SPRITEFX diff --git a/game/view/startmenu.lua b/game/view/startmenu.lua new file mode 100644 index 00000000..02c0f0f8 --- /dev/null +++ b/game/view/startmenu.lua @@ -0,0 +1,113 @@ +--DEPENDENCIES-- +local RES = require 'resources' +local FONT = require 'view.helpers.font' +local COLORS = require 'domain.definitions.colors' +local Queue = require 'lux.common.Queue' + +--CLASS VIEW-- +local StartMenuView = Class{ + __includes = { ELEMENT } +} + +local _TITLE_TEXT = "backdoor" +local _LH = 1.5 +local _TILE_W, _TILE_H = 80, 80 + +local _SCROLL_THRESHOLD = 4 +local _TITLE_FONT_SIZE = 48 +local _MENU_FONT_SIZE = 24 +local _FADE_TIME = .5 + + +local _menu_font, _title_font +local _width, _height + +local function _initFontValues() + local g = love.graphics + _title_font = _title_font or FONT.get("Title", _TITLE_FONT_SIZE) + _menu_font = _menu_font or FONT.get("Text", _MENU_FONT_SIZE) + _width, _height = g.getDimensions() +end + + +local function _renderTitle(g) + g.push() + g.translate(0, _height/4) + _title_font:set() + _title_font:setLineHeight(_LH) + g.setColor(COLORS.NEUTRAL) + g.print(_TITLE_TEXT, 0, 0) + g.pop() +end + + +local function _renderOptions(g, q, selection, scrolltop) + g.push() + g.translate(0, _height/2) + _menu_font:set() + _menu_font:setLineHeight(_LH) + local count = 0 + while not q.isEmpty() do + local item_text = q.pop() + local text_color = COLORS.BACKGROUND + count = count + 1 + if count >= scrolltop and count < scrolltop + _SCROLL_THRESHOLD then + if selection == count then + text_color = COLORS.NEUTRAL + end + g.setColor(text_color) + g.print(item_text, 0, 0) + g.translate(0, _menu_font:getHeight()) + end + end + g.pop() +end + + +function StartMenuView:init() + + ELEMENT.init(self) + + self.queue = Queue(128) + self.title = "backdoor" + self.selection = 1 + self.scrolltop = 1 + + _initFontValues() + +end + +function StartMenuView:setItem(item_text) + self.queue.push(item_text) +end + + +function StartMenuView:setSelection(n) + if n < self.scrolltop then + self.scrolltop = n + end + if n >= self.scrolltop + _SCROLL_THRESHOLD then + self.scrolltop = n - _SCROLL_THRESHOLD + 1 + end + self.selection = n +end + + +function StartMenuView:draw() + + local g = love.graphics + local q = self.queue + + g.push() + g.setBackgroundColor(0, 0, 0) + g.translate(4*_TILE_W, 0) + + _renderTitle(g) + _renderOptions(g, q, self.selection, self.scrolltop) + + g.pop() + +end + + +return StartMenuView diff --git a/icon.png b/icon.png deleted file mode 100644 index c019ef64..00000000 Binary files a/icon.png and /dev/null differ diff --git a/icon.png.flags b/icon.png.flags deleted file mode 100644 index 5130fd1a..00000000 --- a/icon.png.flags +++ /dev/null @@ -1 +0,0 @@ -gen_mipmaps=false diff --git a/model/action.gd b/model/action.gd deleted file mode 100644 index 9b146d8a..00000000 --- a/model/action.gd +++ /dev/null @@ -1,78 +0,0 @@ - -extends Object - -class BaseAction: - var type_ - func _init(type): - type_ = type - func can_be_used(actor): - return false - func get_cost(actor): - return 0 - func use(actor): - pass - -class Idle: - extends BaseAction - func _init().("idle"): - pass - func get_cost(actor): - return 100 - func can_be_used(actor): - return true - -class Move: - extends BaseAction - var target_ - func _init(target).("move"): - target_ = target - func get_map(actor): - return actor.get_node("/root/sector/map") - func can_be_used(actor): - return get_map(actor).is_empty_space(target_) && get_map(actor).get_body_at(target_) == null - func get_cost(actor): - return 50 - func use(actor): - get_map(actor).move_actor(actor, target_) - pass - -class MeleeAttack: - extends BaseAction - var body_ - func _init(body).("melee_attack"): - body_ = body - func can_be_used(actor): - return true - func get_cost(actor): - return 100 - func use(actor): - body_.take_damage(actor.get_melee_damage(), actor) - -class ChangeSector: - extends BaseAction - var target_ - func _init(target).("change_sector"): - target_ = target - func can_be_used(actor): - return true - func get_cost(actor): - return 50 - func use(actor): - actor.get_node("/root/sector").set_next_sector(target_) - -class EvokeCard: - extends BaseAction - var card_ - var options_ - func _init(card).("use_card"): - card_ = card - options_ = [] - func add_option(option): - options_.append(option) - func get_cost(actor): - return card_.card_ref.get_time_cost() - func can_be_used(actor): - return (card_.card_ref.can_be_evoked(actor)) - func use(actor): - actor.consume_card(card_) - card_.card_ref.evoke(actor,options_) diff --git a/model/actor.gd b/model/actor.gd deleted file mode 100644 index 376944e1..00000000 --- a/model/actor.gd +++ /dev/null @@ -1,305 +0,0 @@ - -extends Node - -const SlotItem = preload("res://model/cards/card_item.gd").SlotItem - -const UPGRADE_SLOT_MAX = 3 - -const ATTR_ATHLETICS = 0 -const ATTR_ARCANA = 1 -const ATTR_TECH = 2 -const ATTR_MAX = 3 - -const DRAW_TIME = 120 -const HAND_MAX = 5 -const ACC_CONSUME = 10 - -class Card: - var card_ref - func _init(ref): - card_ref = ref - func get_name(): - return card_ref.get_name() - func get_description(): - return card_ref.get_description() - func get_ref(): - return card_ref - - -# Stats -var char_name -var base_attributes_ -var speed = 10 -var draw_rate = 5 -var draw_rate_bonus_multiplier = 1 - -# Play state -var cooldown -var draw_cooldown -var action - -# Play zones -var hand -var deck -var upgrades -# Item slots -var weapon -var suit -var accessory - -signal has_action -signal spent_action -signal draw_card(card) -signal consumed_card(card) -signal update_deck -signal equipped_item(item, slot_type) - -func _init(name): - hand = [] - deck = [] - upgrades = [] - char_name = name - base_attributes_ = [] - base_attributes_.resize(ATTR_MAX) - for attr in range(ATTR_MAX): - base_attributes_[attr] = 0 - -func _ready(): - cooldown = 100/speed - draw_cooldown = DRAW_TIME - if weapon != null: - emit_signal("equipped_item", weapon.get_ref(), SlotItem.WEAPON) - if suit != null: - emit_signal("equipped_item", suit.get_ref(), SlotItem.SUIT) - if accessory != null: - emit_signal("equipped_item", accessory.get_ref(), SlotItem.ACCESSORY) - -func get_attribute(which): - assert(which >= 0 and which < ATTR_MAX) - var value = base_attributes_[which] - for upgrade in upgrades: - var card = upgrade.get_ref() - if card.get_card_attribute() == which: - value += card.get_bonus_amount() - return value - -func get_static_effect(which): - var value = 0 - for upgrade in upgrades: - var card = upgrade.get_ref() - if card.has_static_effect(which): - value += card.get_static_effect() - return value - -func check_triggers(which, params): - for upgrade in upgrades: - var card = upgrade.get_ref() - if card.has_trigger_effect(which): - card.trigger(self, params) - -func get_athletics(): - return get_attribute(ATTR_ATHLETICS) - -func get_arcana(): - return get_attribute(ATTR_ARCANA) - -func get_tech(): - return get_attribute(ATTR_TECH) - -func get_speed(): - return speed + get_static_effect("speed_bonus") - -func get_melee_damage(): - if weapon != null: - weapon.get_ref().consume_item() - var damage = weapon.get_ref().calculate_damage(self) - #printt("Hit with", weapon.get_ref().get_name(), "damage done", damage) - if weapon.get_ref().get_durability() < 0: - weapon = null - emit_signal("equipped_item", null, SlotItem.WEAPON) - return damage - return get_athletics() + 1 + randi()%6 - -func get_body(): - return get_node("/root/sector/map").get_actor_body(self) - -func get_body_pos(): - return get_body().pos - -func can_draw(): - return hand.size() < HAND_MAX and deck.size() > 0 - -func consume_card(card): - hand.erase(card) - emit_signal("consumed_card", card) - -func step_time(): - cooldown = max(0, cooldown - 1) - while draw_cooldown <= 0 and can_draw(): - hand.append(deck[0]) - deck.remove(0) - draw_cooldown += DRAW_TIME - emit_signal("draw_card", hand[hand.size() - 1]) - emit_signal("update_deck") - if accessory != null: - if accessory.get_ref().get_durability() > 0: - accessory.get_ref().consume_item() - else: - accessory.get_ref().finish_effect(self) - accessory = null - emit_signal("equipped_item", self.accessory, SlotItem.ACCESSORY) - if can_draw(): - draw_cooldown -= draw_rate() - -func draw_rate(): - return draw_rate * draw_rate_bonus_multiplier - -func set_draw_rate_bonus_multiplier(bonus_multiplier): - self.draw_rate_bonus_multiplier = bonus_multiplier - -func set_upgrade(upgrade): - if upgrades.size() == UPGRADE_SLOT_MAX: - return - if upgrade extends Card: - upgrades.push_back(upgrade) - else: - upgrades.push_back(Card.new(upgrade)) - -func equip_item(card): - if card.get_slot() == SlotItem.WEAPON: - self.weapon = Card.new(card) - elif card.get_slot() == SlotItem.SUIT: - self.suit = Card.new(card) - get_body().set_absorption(card.get_absorption()) - get_body().connect("damage_taken", self, "consume_armory") - get_body().connect("damage_taken", self, "_on_damage_taken") - elif card.get_slot() == SlotItem.ACCESSORY: - self.accessory = Card.new(card) - card.init_effect(self) - emit_signal("equipped_item", card, card.get_slot()) - -func _on_damage_taken(amount, source): - check_triggers("damage_taken", { "amount": amount, "source": source }) - -func consume_armory(): - if self.suit == null: - return - self.suit.get_ref().consume_item() - printt("consume armor durability=", self.suit.get_ref().get_durability()) - if self.suit.get_ref().get_durability() < 0: - get_body().set_absorption(0) - get_body().disconnect("damage_taken", self, "consume_armory") - self.suit = null - emit_signal("equipped_item", self.suit, SlotItem.SUIT) - -func is_ready(): - return cooldown == 0 - -func has_action(): - return action != null - -func add_action(the_action): - if !has_action() and the_action.can_be_used(self): - action = the_action - #print(get_name(), ": added action ", action.get_type()) - emit_signal("has_action") - -func use_action(): - #print(get_name(), ": used action ", action.get_type()) - if self.suit != null and !get_body().is_connected("damage_taken", self, "consume_armory"): - get_body().connect("damage_taken", self, "consume_armory") - cooldown += action.get_cost(self)/get_speed() - action.use(self) - action = null - emit_signal("spent_action") - -func pick_ai_module(): - var total = 0 - for module in get_children(): - total += module.chance - var roll = total*randf() - var acc = 0 - for module in get_children(): - acc += module.chance - if acc >= roll: - return module - return get_child(0) - -static func get_card_id(cards_db, card): - return cards_db.get_card_id(card.get_ref()) - -static func serialize_card_array(cards_db, card_array): - var array_data = [] - for card in card_array: - array_data.append(get_card_id(cards_db, card)) - return array_data - -func serialize(): - var sector = get_parent().get_parent() - var actor_data = {} - actor_data["name"] = char_name - actor_data["cooldown"] = cooldown - actor_data["drawcooldown"] = draw_cooldown - - var cards_db = get_node("/root/captains_log/cards") - - if weapon != null: - actor_data["weapon"] = get_card_id(cards_db, weapon) - if suit != null: - actor_data["suit"] = get_card_id(cards_db, suit) - if accessory != null: - actor_data["accessory"] = get_card_id(cards_db, accessory) - - actor_data["hand"] = serialize_card_array(cards_db, hand) - actor_data["deck"] = serialize_card_array(cards_db, deck) - actor_data["upgrades"] = serialize_card_array(cards_db, upgrades) - - actor_data["body_id"] = sector.get_actor_body(self).get_id() - var ai_modules_data = [] - for module in get_children(): - var module_data = {} - module_data["name"] = module.get_name() - module_data["chance"] = module.chance - ai_modules_data.append(module_data) - actor_data["ai_modules"] = ai_modules_data - return actor_data - -static func load_card(cards_db, card_id): - return Card.new(cards_db.get_child(card_id)) - -static func unserialize_card_array(cards_db, actor_property, card_array): - for card_id in card_array: - actor_property.append(load_card(cards_db, card_id)) - -static func unserialize(data, root): - var actor = new(data["name"]) - actor.cooldown = data["cooldown"] - actor.draw_cooldown = data["drawcooldown"] - - var cards_db = root.get_node("captains_log/cards") - - print("data=", data) - - if data.has("weapon"): - actor.weapon = load_card(cards_db, data["weapon"]) - if data.has("suit"): - actor.suit = load_card(cards_db, data["suit"]) - if data.has("accessory"): - actor.accessory = load_card(cards_db, data["accessory"]) - - unserialize_card_array(cards_db, actor.hand, data["hand"]) - unserialize_card_array(cards_db, actor.deck, data["deck"]) - - if data.has("upgrades"): - for card_id in data["upgrades"]: - var upg = load_card(cards_db, card_id) - actor.set_upgrade(upg) - - var ai_modules = data["ai_modules"] - for module in ai_modules: - var ai = Node.new() - ai.set_script(load("res://model/ai/" + module["name"] + ".gd")) - ai.set_name(module["name"]) - ai.chance = module["chance"] - actor.add_child(ai) - return actor diff --git a/model/ai/defensive.gd b/model/ai/defensive.gd deleted file mode 100644 index f8b0ed08..00000000 --- a/model/ai/defensive.gd +++ /dev/null @@ -1,25 +0,0 @@ - -extends Node - -const Action = preload("res://model/action.gd") - -export(float, 0.0, 1.0, 0.01) var chance = 1 - -onready var actor = get_parent() - -const moves = [ - Vector2(0,1), - Vector2(1,0), - Vector2(0,-1), - Vector2(-1,0) -] - -func think(): - for move in moves: - var target = actor.get_body_pos() + move - var body = get_node("/root/sector/map").get_body_at(target) - if body != null: - actor.add_action(Action.MeleeAttack.new(body)) - break - if !actor.has_action(): - actor.add_action(Action.Idle.new()) diff --git a/model/ai/wander.gd b/model/ai/wander.gd deleted file mode 100644 index 23029174..00000000 --- a/model/ai/wander.gd +++ /dev/null @@ -1,24 +0,0 @@ - -extends Node - -const Action = preload("res://model/action.gd") - -export(float, 0.0, 1.0, 0.01) var chance = 1 - -onready var actor = get_parent() - -const moves = [ - Vector2(0,1), - Vector2(1,0), - Vector2(0,-1), - Vector2(-1,0) -] - -func _ready(): - print("HAHAHAHA") - -func think(): - var move = moves[randi()%moves.size()] - actor.add_action(Action.Move.new(actor.get_body_pos() + move)) - if !actor.has_action(): - actor.add_action(Action.Idle.new()) diff --git a/model/body.gd b/model/body.gd deleted file mode 100644 index 53b4f4e7..00000000 --- a/model/body.gd +++ /dev/null @@ -1,56 +0,0 @@ - -extends "identifiable.gd" - -var type -var pos -var hp -var damage - -var absorption = 0 - -signal damage_taken(amount, source) - -func _init(id, the_type, the_pos, the_hp).(id): - type = the_type - pos = the_pos - hp = the_hp - damage = 0 - -func set_absorption(absorption): - printt("Set dmg reduction") - self.absorption = absorption - -func get_absorption(): - return self.absorption - -func take_damage(amount, source_actor): - damage += max(amount - absorption, 0) - printt("Damage done", amount, "reduction", absorption) - if is_dead(): - pass - emit_signal("damage_taken", amount, source_actor) - -func heal(amount): - damage = max(damage - amount, 0) - -func get_hp_percent(): - return 100*(hp - damage)/hp - -func is_dead(): - return damage >= hp - -func serialize(): - var body_data = {} - body_data["id"] = get_id() - body_data["type"] = type - body_data["pos"] = [pos.x, pos.y] - body_data["hp"] = hp - body_data["damage"] = damage - body_data["absorption"] = absorption - return body_data - -static func unserialize(data): - var body = new(data["id"], data["type"], Vector2(data["pos"][0], data["pos"][1]), data["hp"]) - body.damage = data["damage"] - body.absorption = data["absorption"] - return body diff --git a/model/captains_log.gd b/model/captains_log.gd deleted file mode 100644 index 1a5a7613..00000000 --- a/model/captains_log.gd +++ /dev/null @@ -1,61 +0,0 @@ - -extends Node - -const RouteScene = preload("res://model/route.tscn") - -const Route = preload("res://model/route.gd") -const Actor = preload("res://model/actor.gd") -const Body = preload("res://model/body.gd") - -onready var map_generator = get_node("map_generator") -onready var profile = get_node("profile") - -func get_profile(): - return profile - -func erase_route(id): - profile.erase_journal(id) - -func create_route(): - var route = RouteScene.instance() - route.id = profile.find_free_route_id() - profile.add_journal(route.id) - var w = 64 - var h = 64 - var map_node = map_generator.generate_map(1,w,h) - route.get_node("sectors").add_child(map_node) - route.current_sector = map_node - map_node.show() - var player = Actor.new("hero") - var cards_db = get_node("/root/captains_log/cards") - for i in range(20): - var aux = i % cards_db.get_child_count() - var card = Actor.Card.new(cards_db.get_child(aux)) - player.deck.append(card) - route.player = player - get_node("/root/sector").set_player(player) - var player_body = Body.new(1, "hero", map_node.get_random_free_pos(), 10) - map_node.add_body(player_body) - map_node.add_actor(player_body, player) - get_parent().add_child(route) - -func load_route(id): - var file = profile.get_journal_file_reader(id) - assert(file != null) - var route = Route.load_from_file(id, file, get_tree().get_root()) - file.close() - get_node("/root/sector").set_player(route.player) - get_parent().call_deferred("add_child", route) - -func save_route(): - var route = get_node("/root/route") - var file = profile.get_journal_file_writer(route.id) - assert(file != null) - route.save_to_file(file) - file.close() - -func finish(): - if get_node("/root/").has_node("route"): - save_route() - profile.save() - get_tree().quit() diff --git a/model/captains_log.tscn b/model/captains_log.tscn deleted file mode 100644 index b584bd47..00000000 --- a/model/captains_log.tscn +++ /dev/null @@ -1,214 +0,0 @@ -[gd_scene load_steps=20 format=1] - -[ext_resource path="res://model/captains_log.gd" type="Script" id=1] -[ext_resource path="res://components/util/id_server.gd" type="Script" id=2] -[ext_resource path="res://components/monster.tscn" type="PackedScene" id=3] -[ext_resource path="res://model/ai/wander.gd" type="Script" id=4] -[ext_resource path="res://model/ai/defensive.gd" type="Script" id=5] -[ext_resource path="res://components/util/card_list.gd" type="Script" id=6] -[ext_resource path="res://model/cards/heal.gd" type="Script" id=7] -[ext_resource path="res://model/cards/teleport.gd" type="Script" id=8] -[ext_resource path="res://model/cards/arcane_bolt.gd" type="Script" id=9] -[ext_resource path="res://model/cards/card_item_weapon.gd" type="Script" id=10] -[ext_resource path="res://model/cards/card_item_armory.gd" type="Script" id=11] -[ext_resource path="res://model/cards/card_item_accessory.gd" type="Script" id=12] -[ext_resource path="res://model/cards/card_upgrade.gd" type="Script" id=13] -[ext_resource path="res://model/cards/counter.gd" type="Script" id=14] -[ext_resource path="res://model/cards/fireball.gd" type="Script" id=15] -[ext_resource path="res://model/cards/laser_beam.gd" type="Script" id=16] -[ext_resource path="res://components/util/mapgen/map_generator.gd" type="Script" id=17] -[ext_resource path="res://components/util/scene_manager.gd" type="Script" id=18] -[ext_resource path="res://components/persist/profile_persist.gd" type="Script" id=19] - -[node name="captains_log" type="Node"] - -script/script = ExtResource( 1 ) - -[node name="id_server" type="Node" parent="."] - -script/script = ExtResource( 2 ) - -[node name="monsters" type="Node" parent="."] - -[node name="Slime" parent="monsters" instance=ExtResource( 3 )] - -body_type = "slime" - -[node name="wander" type="Node" parent="monsters/Slime/ai_modules"] - -script/script = ExtResource( 4 ) -chance = 0.3 - -[node name="defensive" type="Node" parent="monsters/Slime/ai_modules"] - -script/script = ExtResource( 5 ) -chance = 1.0 - -[node name="Floating Eye" parent="monsters" instance=ExtResource( 3 )] - -body_hp = 15 -body_type = "eye" - -[node name="defensive" type="Node" parent="monsters/Floating Eye/ai_modules"] - -script/script = ExtResource( 5 ) -chance = 1 - -[node name="Thief" parent="monsters" instance=ExtResource( 3 )] - -body_hp = 25 -body_type = "thief" - -[node name="wander" type="Node" parent="monsters/Thief/ai_modules"] - -script/script = ExtResource( 4 ) -chance = 1 - -[node name="Giant Toad" parent="monsters" instance=ExtResource( 3 )] - -body_hp = 20 -body_type = "froggy" - -[node name="wander" type="Node" parent="monsters/Giant Toad/ai_modules"] - -script/script = ExtResource( 4 ) -chance = 1 - -[node name="cards" type="Node" parent="."] - -editor/display_folded = true -script/script = ExtResource( 6 ) - -[node name="Heal" type="Node" parent="cards"] - -script/script = ExtResource( 7 ) -card_attribute = 0 -description = "This skill heals the player" -time_cost = 20 - -[node name="Teleport" type="Node" parent="cards"] - -script/script = ExtResource( 8 ) -card_attribute = 0 -description = "This skill teleport the player" -time_cost = 50 - -[node name="Arcane Bolt" type="Node" parent="cards"] - -script/script = ExtResource( 9 ) -card_attribute = 1 -description = "This skill unleashes a bolt of arcane power" -time_cost = 50 - -[node name="Iron Pipe" type="Node" parent="cards"] - -script/script = ExtResource( 10 ) -card_attribute = 0 -description = "Weapon Iron Pipe" -time_cost = 10 -slot = 0 -initial_durability = 2 -damage_expression = "$2d6 + $ATH" - -[node name="Combat Suit" type="Node" parent="cards"] - -script/script = ExtResource( 11 ) -card_attribute = 0 -description = "a card" -time_cost = 50 -slot = 1 -initial_durability = 2 -absorption = 3 - -[node name="Concentration Ring" type="Node" parent="cards"] - -script/script = ExtResource( 12 ) -card_attribute = 1 -description = "Double the draw rate" -time_cost = 50 -slot = 2 -initial_durability = 100 -init_effect_expression = "actor.set_draw_rate_bonus_multiplier(2)" -finish_effect_expression = "actor.set_draw_rate_bonus_multiplier(1)" - -[node name="Strong Grip" type="Node" parent="cards"] - -script/script = ExtResource( 13 ) -card_attribute = 0 -description = "+3 ATH" -time_cost = 50 -bonus_amount = 3 -static_kind = "none" -static_value = 0 - -[node name="Mind Focus" type="Node" parent="cards"] - -script/script = ExtResource( 13 ) -card_attribute = 1 -description = "+3 ARC" -time_cost = 50 -bonus_amount = 3 -static_kind = "none" -static_value = 0 - -[node name="Guidorizzi Tome" type="Node" parent="cards"] - -script/script = ExtResource( 13 ) -card_attribute = 2 -description = "+3 TECH" -time_cost = 50 -bonus_amount = 3 -static_kind = "none" -static_value = 0 - -[node name="Adrenaline Dose" type="Node" parent="cards"] - -script/script = ExtResource( 13 ) -card_attribute = 0 -description = "+1 ATH, +5 SPD" -time_cost = 50 -bonus_amount = 1 -static_kind = "bonus_speed" -static_value = 5 - -[node name="Counter" type="Node" parent="cards"] - -script/script = ExtResource( 14 ) -card_attribute = 2 -description = "Whenever you are hit, counter with 2d4 damage." -time_cost = 50 -bonus_amount = 1 -static_kind = "none" -static_value = 0 - -[node name="Fireball" type="Node" parent="cards"] - -script/script = ExtResource( 15 ) -card_attribute = 0 -description = "a card" -time_cost = 50 - -[node name="Laser Beam" type="Node" parent="cards"] - -script/script = ExtResource( 16 ) -card_attribute = 0 -description = "Shoots a powerful laser beam" -time_cost = 50 - -[node name="map_generator" type="Node" parent="."] - -script/script = ExtResource( 17 ) - -[node name="scene_manager" type="Node" parent="."] - -script/script = ExtResource( 18 ) - -[node name="profile" type="Node" parent="."] - -script/script = ExtResource( 19 ) - - -[editable path="monsters/Slime"] -[editable path="monsters/Floating Eye"] -[editable path="monsters/Thief"] -[editable path="monsters/Giant Toad"] diff --git a/model/cards/arcane_bolt.gd b/model/cards/arcane_bolt.gd deleted file mode 100644 index 37420f64..00000000 --- a/model/cards/arcane_bolt.gd +++ /dev/null @@ -1,17 +0,0 @@ - -extends "res://model/cards/card_skill.gd" - -func valid_target(actor, target): - var map = get_node("/root/sector/map") - return dist(actor,target) <= 10 and map.get_body_at(target) != null - -func get_options(actor): - return [ - { "type": "TARGET", "check": funcref(self, "valid_target"), "aoe":null } - ] - -func evoke(actor, options): - var target = options[0] - var map = get_node("/root/sector/map") - var body = map.get_body_at(target) - body.take_damage(5, actor) diff --git a/model/cards/card_entity.gd b/model/cards/card_entity.gd deleted file mode 100644 index b2de7439..00000000 --- a/model/cards/card_entity.gd +++ /dev/null @@ -1,37 +0,0 @@ - -extends Node - -const DamageFormula = preload("res://components/util/damage_formula.gd") - -class CARD_ATTRIBUTE: - const ATHLETICS = 0 - const ARCANE = 1 - const TECH = 2 - -export(int, "ATHLETICS", "ARCANE", "TECH") var card_attribute = 0 -export(String) var description = "a card" -export(int) var time_cost = 50 - -func get_time_cost(): - return time_cost - -func get_card_attribute(): - return card_attribute - -func can_be_evoked(actor): - return true - -func get_options(actor): - return [] - -func get_description(): - return description - -func evoke(actor, options): - print("Card evoked") - -## Utility functions - -func dist(actor, target): - var d = target - actor.get_body_pos() - return abs(d.x) + abs(d.y) diff --git a/model/cards/card_item.gd b/model/cards/card_item.gd deleted file mode 100644 index 7c984049..00000000 --- a/model/cards/card_item.gd +++ /dev/null @@ -1,27 +0,0 @@ - -extends "res://model/cards/card_entity.gd" - -class SlotItem: - const WEAPON = 0 - const SUIT = 1 - const ACCESSORY = 2 - -export(int, "WEAPON", "SUIT", "ACCESSORY") var slot = 0 -export(int) var initial_durability = 5 - -var consumption = 0 - -func get_initial_durability(): - return initial_durability - -func get_durability(): - return initial_durability - consumption - -func get_slot(): - return slot - -func evoke(actor, options): - actor.equip_item(self) - -func consume_item(): - consumption += 1 diff --git a/model/cards/card_item_accessory.gd b/model/cards/card_item_accessory.gd deleted file mode 100644 index 8574e74e..00000000 --- a/model/cards/card_item_accessory.gd +++ /dev/null @@ -1,36 +0,0 @@ - -extends "res://model/cards/card_item.gd" - -export(String, MULTILINE) var init_effect_expression = "" -export(String, MULTILINE) var finish_effect_expression = "" - -func init_effect(actor): - var src = "func init_effect(actor):\n" +\ - "\t" + init_effect_expression.replace("\n", "\n\t") - - #print("src=\n\n########\n" + src + "\n#######\n") - - var script = GDScript.new() - script.set_source_code(src) - script.reload() - - var obj = Reference.new() - obj.set_script(script) - - obj.init_effect(actor) - - -func finish_effect(actor): - var src = "func finish_effect(actor):\n" +\ - "\t" + finish_effect_expression.replace("\n", "\n\t") - - #print("src=\n\n########\n" + src + "\n#######\n") - - var script = GDScript.new() - script.set_source_code(src) - script.reload() - - var obj = Reference.new() - obj.set_script(script) - - obj.finish_effect(actor) diff --git a/model/cards/card_item_armory.gd b/model/cards/card_item_armory.gd deleted file mode 100644 index a5946bb2..00000000 --- a/model/cards/card_item_armory.gd +++ /dev/null @@ -1,6 +0,0 @@ -extends "res://model/cards/card_item.gd" - -export(int) var absorption = 5 - -func get_absorption(): - return absorption diff --git a/model/cards/card_item_weapon.gd b/model/cards/card_item_weapon.gd deleted file mode 100644 index da6be314..00000000 --- a/model/cards/card_item_weapon.gd +++ /dev/null @@ -1,46 +0,0 @@ - -extends "res://model/cards/card_item.gd" - -export(String) var damage_expression = "$2d6" - - -func calc_dice_value(dice): - var regex = RegEx.new() - regex.compile("\\$(\\d{1,2})d(\\d{1,2})") - regex.find(dice) - var dcount = regex.get_capture(1).to_int() - var dfaces = regex.get_capture(2).to_int() - return "dice(" + var2str(dcount) + ", " + var2str(dfaces) + ")" - -func get_damage_expression(dice_str): - var regex = RegEx.new() - regex.compile("(\\$[\\d]{1,2}d[\\d]{1,2})") - var found = regex.find(dice_str) - while found >= 0: - var dice_expr = regex.get_captures()[0] - var dice_func = calc_dice_value(dice_expr) - dice_str = dice_str.replace(dice_expr, dice_func) - found = regex.find(dice_str) - return dice_str - -func calculate_damage(player): - var exp_aux = damage_expression.replace("$ATH", var2str(player.get_athletics()))\ - .replace("$ARC", var2str(player.get_arcana()))\ - .replace("$TEC", var2str(player.get_tech())) - var damage_exp = get_damage_expression(exp_aux) - var src = "extends \"res://components/util/damage_formula.gd\"\n\n" +\ - "func eval():\n" +\ - "\treturn " + damage_exp - - #print("src=\n\n########\n" + src + "\n#######\n") - - var script = GDScript.new() - script.set_source_code(src) - script.reload() - - var obj = Reference.new() - obj.set_script(script) - - consume_item() - - return obj.eval() diff --git a/model/cards/card_skill.gd b/model/cards/card_skill.gd deleted file mode 100644 index 1d68d98a..00000000 --- a/model/cards/card_skill.gd +++ /dev/null @@ -1,3 +0,0 @@ - -extends "res://model/cards/card_entity.gd" - diff --git a/model/cards/card_upgrade.gd b/model/cards/card_upgrade.gd deleted file mode 100644 index 14eec4ef..00000000 --- a/model/cards/card_upgrade.gd +++ /dev/null @@ -1,35 +0,0 @@ - -extends "res://model/cards/card_entity.gd" - -export(int) var bonus_amount = 2 -export(String) var static_kind = "none" -export(int) var static_value = 0 - -class StaticEffect: - var kind_ - var value_ - func _init(kind, value): - kind_ = kind - value_ = value - func get_kind(): - return kind_ - func get_value(): - return value_ - -func get_bonus_amount(): - return bonus_amount - -func has_static_effect(kind): - return static_kind == kind - -func has_trigger_effect(kind): - return false - -func get_static_effect(): - return static_value - -func evoke(actor, options): - actor.set_upgrade(self) - -func trigger(actor, params): - pass #abstract diff --git a/model/cards/counter.gd b/model/cards/counter.gd deleted file mode 100644 index dbd038f9..00000000 --- a/model/cards/counter.gd +++ /dev/null @@ -1,10 +0,0 @@ - - -extends "res://model/cards/card_upgrade.gd" - -func has_trigger_effect(kind): - return kind == "damage_taken" - -func trigger(actor, params): - var source = params.source - source.get_body().take_damage(DamageFormula.new().dice(2, 4), actor) diff --git a/model/cards/fireball.gd b/model/cards/fireball.gd deleted file mode 100644 index 3b38a1ec..00000000 --- a/model/cards/fireball.gd +++ /dev/null @@ -1,29 +0,0 @@ - -extends "res://model/cards/card_skill.gd" - -const AREA = [[0,1,0], - [1,1,1], - [0,1,0]] -const CENTER = Vector2(1,1) - -func valid_target(actor, target): - var map = get_node("/root/sector/map") - return map.is_empty_space(target) - -func get_options(actor): - return [ - { "type": "TARGET", "check": funcref(self, "valid_target"), - "aoe":{"format":AREA, "center":CENTER}} - ] - -func evoke(actor, options): - var map = get_node("/root/sector/map") - var pos = options[0] - for i in range(AREA.size()): - for j in range(AREA[i].size()): - var target = AREA[i][j] - if target == 1: - var body = map.get_body_at(pos - CENTER + Vector2(j,i)) - printt("FIREBALL HIT", i, j) - if body != null: - body.take_damage(20, actor) diff --git a/model/cards/heal.gd b/model/cards/heal.gd deleted file mode 100644 index a1fbd708..00000000 --- a/model/cards/heal.gd +++ /dev/null @@ -1,8 +0,0 @@ - -extends "res://model/cards/card_skill.gd" - -func can_be_evoked(actor): - return true - -func evoke(actor, options): - actor.get_body().heal(5) diff --git a/model/cards/laser_beam.gd b/model/cards/laser_beam.gd deleted file mode 100644 index 12e547c7..00000000 --- a/model/cards/laser_beam.gd +++ /dev/null @@ -1,55 +0,0 @@ - -extends "res://model/cards/card_skill.gd" - -const RANGE = 5 - -var horizontal = true - -func valid_target(actor, target): - var map = get_node("/root/sector/map") - var pos = actor.get_body_pos() - return map.is_empty_space(target) and \ - (pos.x == target.x and pos.y != target.y and abs(pos.y - target.y) < RANGE) or \ - (pos.y == target.y and pos.x != target.x and abs(pos.x - target.x) < RANGE) - -func get_area(actor, target): - if actor.get_body_pos().x == target.x: - horizontal = true - return [[1],[1],[1],[1]] - else: - horizontal = false - return [[1,1,1,1]] - -func get_center(actor, target): - var dist - var pos = actor.get_body_pos() - if horizontal: - dist = target.y - pos.y - if dist < 0: - dist = RANGE + dist - return Vector2(0, dist - 1) - else: - dist = target.x - pos.x - if dist < 0: - dist = RANGE + dist - return Vector2(dist - 1, 0) - -func get_options(actor): - return [ - { "type": "TARGET", "check": funcref(self, "valid_target"), - "aoe":{"format": funcref(self, "get_area"), "center":funcref(self, "get_center")}} - ] - -func evoke(actor, options): - var map = get_node("/root/sector/map") - var pos = options[0] - var area = get_area(actor, pos) - var center = get_center(actor, pos) - for i in range(area.size()): - for j in range(area[i].size()): - var target = area[i][j] - if target == 1: - var body = map.get_body_at(pos - center + Vector2(j,i)) - if body != null: - printt("LASERBEAM SHOT", pos - center + Vector2(j,i)) - body.take_damage(15, actor) diff --git a/model/cards/teleport.gd b/model/cards/teleport.gd deleted file mode 100644 index 8bca1727..00000000 --- a/model/cards/teleport.gd +++ /dev/null @@ -1,18 +0,0 @@ - -extends "res://model/cards/card_skill.gd" - -func valid_target(actor, target): - var map = get_node("/root/sector/map") - return dist(actor,target) <= 10 and map.is_empty_space(target) and map.get_body_at(target) == null - -func can_be_evoked(actor): - return true - -func get_options(actor): - return [ - { "type": "TARGET", "check": funcref(self, "valid_target"), "aoe":null } - ] - -func evoke(actor, options): - var target = options[0] - get_node("/root/sector/map").move_body(actor.get_body(), target) diff --git a/model/identifiable.gd b/model/identifiable.gd deleted file mode 100644 index 91ac1a3d..00000000 --- a/model/identifiable.gd +++ /dev/null @@ -1,15 +0,0 @@ - -extends Object - -var id_ - -static func find(identifiable_list, id): - for identifiable in identifiable_list: - if identifiable.get_id() == id: - return identifiable - -func _init(id): - id_ = id - -func get_id(): - return id_ diff --git a/model/monster/monster.gd b/model/monster/monster.gd deleted file mode 100644 index c462aa86..00000000 --- a/model/monster/monster.gd +++ /dev/null @@ -1,23 +0,0 @@ - -extends Node - -const Body = preload("res://model/body.gd") -const Actor = preload("res://model/actor.gd") -const Wander = preload("res://model/ai/wander.gd") - -export var body_hp = 10 -export(String) var body_type = "dummy" -onready var ai_modules = get_node("ai_modules").get_children() - -func _ready(): - for card in get_node("cards").get_children(): - print(card.get_name()) - -func create(map, pos): - var body = Body.new(map.find_free_body_id(), body_type, pos, body_hp) - var actor = Actor.new(get_name()) - for module in ai_modules: - var ai = module.duplicate() - actor.add_child(ai) - map.add_body(body) - map.add_actor(body, actor) diff --git a/model/route.gd b/model/route.gd deleted file mode 100644 index a8830559..00000000 --- a/model/route.gd +++ /dev/null @@ -1,116 +0,0 @@ - -extends Node - -# Scenes -const RouteScene = preload("res://model/route.tscn") - -# Classes -const Identifiable = preload("res://model/identifiable.gd") -const Body = preload("res://model/body.gd") -const Actor = preload("res://model/actor.gd") -const Map = preload("res://scenes/map.gd") - -var current_sector -var player -var id - -static func get_player_name_from_file(file): - # Open file - var data = {} - # Parse to json - var text = file.get_as_text() - data.parse_json(text) - file.close() - var sector_data - for sector in data["sectors"]: - if sector["id"] == data["current_sector"]: - sector_data = sector - return sector_data["actors"][int(data["player_actor_id"])]["name"] - -static func load_from_file(id, file, root): - var route = RouteScene.instance() - route.id = id - # Open file - var data = {} - # Parse to json - var text = file.get_as_text() - data.parse_json(text) - var sectors = data["sectors"] - for sector_data in sectors: - route.get_node("sectors").add_child(Map.unserialize(sector_data, root)) - # Set current sector - route.current_sector = route.find_sector(data["current_sector"]) - route.current_sector.show() - # Store reference to player - route.player = route.current_sector.get_node("actors").get_child(data["player_actor_id"]) - var player_body = route.current_sector.get_actor_body(route.player) - route.current_sector.find_body_view(player_body).highlight() - return route - -func save_to_file(file): - var data = {} - data["sectors"] = [] - data["current_sector"] = current_sector.id - # Group sectors into a single array - var sectors = [current_sector] - for sector in get_node("sectors").get_children(): - sectors.append(sector) - var sectors_data = [] - var player_actor_id = -1 - data["sectors"] = sectors_data - # Serialize sectors - for sector in sectors: - var sector_data = sector.serialize() - sectors_data.append(sector_data) - var i = 0 - for actor_data in sector_data["actors"]: - if actor_data["name"] == player.char_name: - player_actor_id = i - break - i += 1 - data["player_actor_id"] = player_actor_id - file.store_string(data.to_json()) - -func _init(): - print("route created") - -func _ready(): - open_current_sector(null) - print("route ready") - -func find_sector(id): - for sector in get_node("sectors").get_children(): - if sector.id == id: - return sector - if current_sector.id == id: - return current_sector - return null - -func change_sector(target): - var player_body = current_sector.get_actor_body(player) - close_current_sector() - get_node("sectors").add_child(current_sector) - current_sector = find_sector(target) - open_current_sector(player_body) - -func close_current_sector(): - current_sector.set_fixed_process(false) - current_sector.hide() - current_sector.remove_actor(player) - get_node("/root/sector").close() - -func open_current_sector(player_body): - var sector = get_node("/root/sector") - get_node("sectors").remove_child(current_sector) - sector.add_child(current_sector) - sector.move_child(current_sector, 0) - current_sector.set_name("map") - current_sector.set_fixed_process(true) - current_sector.show() - # Set up camera - current_sector.attach_camera(player) - if player_body != null: - current_sector.add_body(player_body) - current_sector.add_actor(player_body, player) - current_sector.move_actor(player, Vector2(0,0)) - sector.new_sector() diff --git a/model/route.tscn b/model/route.tscn deleted file mode 100644 index 45388111..00000000 --- a/model/route.tscn +++ /dev/null @@ -1,11 +0,0 @@ -[gd_scene load_steps=2 format=1] - -[ext_resource path="res://model/route.gd" type="Script" id=1] - -[node name="route" type="Node"] - -script/script = ExtResource( 1 ) - -[node name="sectors" type="Node" parent="."] - - diff --git a/scenes/main.gd b/scenes/main.gd deleted file mode 100644 index 302aca4e..00000000 --- a/scenes/main.gd +++ /dev/null @@ -1,78 +0,0 @@ - -extends Node2D - -const Action = preload("res://model/action.gd") - -var player -var map -var done -var next_sector -var hand -var display_popup -var upgrades_popup - -onready var deck_view = get_node("HUD/deck") - -func _init(): - print("sector created") - -func _ready(): - #get_node("/root/captains_log").start() - print("sector ready") - -func set_player(the_player): - player = the_player - get_node("HUD/UI_hook/CooldownBar").set_player(the_player) - get_node("HUD/UI_hook/Hand").set_player(the_player) - hand = get_node("HUD/UI_hook/Hand") - deck_view.set_player(the_player) - player.connect("equipped_item", get_node("HUD/base/item_stats"), "change_item") - get_node("HUD/base").show() - display_popup = get_node("HUD/CardDisplay") - upgrades_popup = get_node("HUD/UpgradesDisplay") - get_node("HUD/Controller").set_player_map(player, hand) - -func close(): - get_node("HUD/UI_hook/CooldownBar").stop() - get_node("HUD/UI_hook/Hand").stop() - get_node("HUD/base").hide() - deck_view.stop() - player.disconnect("equipped_item", get_node("HUD/base/item_stats"), "change_item") - set_fixed_process(false) - set_process_input(false) - map.queue_free() - done = true - map = null - -func new_sector(): - map = get_node("map") - set_fixed_process(true) - set_process_input(true) - print("start sector") - done = false - next_sector = null - manage_actors() - -func set_next_sector(target): - done = true - next_sector = target - -func manage_actors(): - while not done: - for actor in map.actor_bodies: - if map.get_actor_body(actor).is_dead(): - continue - actor.step_time() - if actor.is_ready(): - if !actor.has_action(): - yield(actor, "has_action") - actor.use_action() - map.check_dead_bodies() - yield(get_tree(), "fixed_frame" ) - if next_sector != null: - get_node("/root/route").change_sector(next_sector) - -func _fixed_process(delta): - for actor in map.actor_bodies: - if actor != player and !actor.has_action() and actor.is_ready(): - actor.pick_ai_module().think() diff --git a/scenes/main.tscn b/scenes/main.tscn deleted file mode 100644 index e9ba66f2..00000000 --- a/scenes/main.tscn +++ /dev/null @@ -1,142 +0,0 @@ -[gd_scene load_steps=12 format=1] - -[ext_resource path="res://scenes/main.gd" type="Script" id=1] -[ext_resource path="res://components/controller/hand.gd" type="Script" id=2] -[ext_resource path="res://components/controller/cooldownbar.gd" type="Script" id=3] -[ext_resource path="res://components/ui/card_display.tscn" type="PackedScene" id=4] -[ext_resource path="res://components/ui/upgrade_display.tscn" type="PackedScene" id=5] -[ext_resource path="res://assets/fonts/PressStart2P-8pt.fnt" type="BitmapFont" id=6] -[ext_resource path="res://components/ui/item_stats.tscn" type="PackedScene" id=7] -[ext_resource path="res://components/controller/deck.gd" type="Script" id=8] -[ext_resource path="res://components/controller/main_controller.gd" type="Script" id=9] - -[sub_resource type="StyleBoxFlat" id=1] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 -bg_color = Color( 0.582031, 0.150055, 0.150055, 1 ) -light_color = Color( 0.828125, 0.181152, 0.181152, 1 ) -dark_color = Color( 0.46875, 0.0604248, 0.0604248, 1 ) -border_size = 0 -border_blend = true -draw_bg = true - -[sub_resource type="StyleBoxFlat" id=2] - -content_margin/left = -1.0 -content_margin/right = -1.0 -content_margin/top = -1.0 -content_margin/bottom = -1.0 -bg_color = Color( 0.6, 0.6, 0.6, 1 ) -light_color = Color( 0.8, 0.8, 0.8, 1 ) -dark_color = Color( 0.347656, 0.347656, 0.347656, 1 ) -border_size = 0 -border_blend = true -draw_bg = true - -[node name="sector" type="Node2D"] - -script/script = ExtResource( 1 ) -__meta__ = { "__editor_plugin_screen__":"Script", "_edit_lock_":true } - -[node name="HUD" type="CanvasLayer" parent="."] - -layer = 1 -offset = Vector2( 0, 0 ) -rotation = 0.0 -scale = Vector2( 1, 1 ) - -[node name="UI_hook" type="Node2D" parent="HUD"] - -editor/display_folded = true -transform/pos = Vector2( 32, 320 ) -__meta__ = { "_edit_lock_":true } - -[node name="Hand" type="Node2D" parent="HUD/UI_hook"] - -visibility/visible = false -transform/pos = Vector2( 64, 64 ) -script/script = ExtResource( 2 ) - -[node name="CooldownBar" type="ProgressBar" parent="HUD/UI_hook"] - -visibility/visible = false -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 32.0 -margin/top = 163.0 -margin/right = 512.0 -margin/bottom = 179.0 -custom_styles/fg = SubResource( 1 ) -custom_styles/bg = SubResource( 2 ) -range/min = 0.0 -range/max = 100.0 -range/step = 1.0 -range/page = 0.0 -range/value = 1.0 -range/exp_edit = false -range/rounded = false -percent/visible = false -script/script = ExtResource( 3 ) - -[node name="CardDisplay" parent="HUD" instance=ExtResource( 4 )] - -[node name="UpgradesDisplay" parent="HUD" instance=ExtResource( 5 )] - -[node name="base" type="Control" parent="HUD"] - -visibility/visible = false -anchor/right = 1 -focus/ignore_mouse = false -focus/stop_mouse = false -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 0.0 -margin/top = 0.0 -margin/right = 0.0 -margin/bottom = 128.0 -__meta__ = { "_edit_lock_":true } - -[node name="Input Description" type="Label" parent="HUD/base"] - -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 16.0 -margin/top = 16.0 -margin/right = 336.0 -margin/bottom = 96.0 -custom_fonts/font = ExtResource( 6 ) -text = "Controls:\n- arrow keys to move\n- A/S to change card\n- D to evoke card\n- ESC to save & quit\n- SHIFT+S to save & back to menu" -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="item_stats" parent="HUD/base" instance=ExtResource( 7 )] - -[node name="deck" type="Control" parent="HUD"] - -anchor/left = 1 -anchor/top = 1 -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 120.0 -margin/top = 64.0 -margin/right = 16.0 -margin/bottom = 16.0 -script/script = ExtResource( 8 ) - -[node name="Controller" type="Node" parent="HUD"] - -script/script = ExtResource( 9 ) - - diff --git a/scenes/map.gd b/scenes/map.gd deleted file mode 100644 index 3dea009e..00000000 --- a/scenes/map.gd +++ /dev/null @@ -1,199 +0,0 @@ - -extends Node2D - -const MapScene = preload("res://scenes/map.tscn") -const Parallax = preload("res://components/util/parallax_background.tscn") - -const Actor = preload("res://model/actor.gd") -const Body = preload("res://model/body.gd") -const BodyView = preload("res://components/bodyview.gd") -const Identifiable = preload("res://model/identifiable.gd") - -const block = [] -#[ -# Vector2(1,0), -# Vector2(0,1), -# Vector2(1,1), -# Vector2(2,0), -# Vector2(0,2), -# Vector2(2,1), -# Vector2(1,2), -# Vector2(2,2) -#] - -var id -var width -var height -var bodies -var actor_bodies -onready var walls = get_node("walls") - -static func create(id, width, height): - var map_node = MapScene.instance() - map_node.get_node("floors").clear() - map_node.get_node("walls").clear() - map_node.width = int(width) - map_node.height = int(height) - map_node.id = id - map_node.hide() - return map_node - -func _init(): - bodies = [] - actor_bodies = {} - print("map created") - -func _ready(): - print("map ready") - pass - -func is_empty_space(pos): - return walls.get_cell(pos.x, pos.y) == -1 - -func find_free_body_id(): - var id = 1 - while Body.find(bodies, id): - id += 1 - return id - -func get_random_free_pos(): - var pos = Vector2(1 + randi()%(width-2), 1+ randi()%(height-2)) - var walls = get_node("walls") #FIXME - while (walls.get_cell(pos.x, pos.y) != -1): - pos = Vector2(1 + randi()%(width-2), 1 + randi()%(height-2)) - return pos - -func add_body(body): - var bodyview = BodyView.create(body) - bodies.append(body) - if body.type != "hero": - bodyview.set_hl_color(Color(1.0, .1, .2, .3)) - get_node("walls").add_child(bodyview) - -func remove_body(body): - bodies.erase(body) - get_node("walls").remove_child(find_body_view(body)) - -func find_body_view(body): - for bodyview in get_node("walls").get_children(): - if bodyview.body == body: - return bodyview - assert(false) - -func add_actor(body, actor): - get_node("actors").add_child(actor) - actor_bodies[actor] = body - #var module = preload("res://model/ai/wander.gd").new() - #actor.add_child(module) - -func remove_actor(actor): - if actor_bodies[actor] != null: - remove_body(actor_bodies[actor]) - actor_bodies.erase(actor) - get_node("actors").remove_child(actor) - -func attach_camera(actor): - var bodyview = find_body_view(get_actor_body(actor)) - var camera = Camera2D.new() - camera.make_current() - camera.set_enable_follow_smoothing(true) - camera.set_follow_smoothing(5) - bodyview.add_child(camera) - add_bg() - -func add_bg(): - var parallax_bg = Parallax.instance() - get_node("background").add_child(parallax_bg) - -func move_actor(actor, new_pos): - move_body(actor_bodies[actor], new_pos) - -func move_body(body, new_pos): - body.pos = new_pos - -func get_actor_body(actor): - assert(actor_bodies.has(actor)) - return actor_bodies[actor] - -func get_body_actor(body): - for actor in actor_bodies: - if actor_bodies[actor] == body: - return actor - -func get_body_at(pos): - for body in bodies: - if body.pos == pos: - return body - return null - -func check_dead_bodies(): - for body in bodies: - if body.is_dead(): - var actor = get_body_actor(body) - if actor == get_parent().player: - get_node("/root/captains_log/scene_manager").call_deferred("destroy_route") - else: - remove_actor(actor) - -func serialize(): - # Store sector general data - var sector_data = {} - sector_data["id"] = id - sector_data["width"] = width - sector_data["height"] = height - # Store floor tiles - var floor_map = [] - for i in range(height): - for j in range(width): - floor_map.append(-1) - var floors = get_node("floors") - for tile_pos in floors.get_used_cells(): - floor_map[tile_pos.x*width + tile_pos.y] = floors.get_cellv(tile_pos) - sector_data["floors"] = floor_map - # Store wall tiles - var wall_map = [] - for i in range(height): - for j in range(width): - wall_map.append(-1) - var walls = get_node("walls") - for tile_pos in walls.get_used_cells(): - wall_map[tile_pos.x*width + tile_pos.y] = walls.get_cellv(tile_pos) - sector_data["walls"] = wall_map - # Store bodies - var bodies = [] - for body in self.bodies: - bodies.append(body.serialize()) - sector_data["bodies"] = bodies - # Store actors - var actors = [] - for actor in actor_bodies: - actors.append(actor.serialize()) - sector_data["actors"] = actors - return sector_data - -static func unserialize(data, root): - # Parse sector - var map = create(data["id"], data["width"], data["height"]) - # General sector info - # Parse sector floor - var floors = map.get_node("floors") - floors.clear() - var floor_data = data["floors"] - for j in range(floor_data.size()): - floors.set_cell(j / int(map.width), j % int(map.width), floor_data[j]) - # Parse sector walls - var walls = map.get_node("walls") - walls.clear() - var wall_data = data["walls"] - for j in range(wall_data.size()): - walls.set_cell(j / int(map.width), j % int(map.width), wall_data[j]) - # Parse bodies - var bodies = data["bodies"] - for body_data in bodies: - map.add_body(Body.unserialize(body_data)) - # Parse actors - var actors = data["actors"] - for actor_data in actors: - var actor = Actor.unserialize(actor_data, root) - map.add_actor(Identifiable.find(map.bodies, actor_data["body_id"]), actor) - return map diff --git a/scenes/map.tscn b/scenes/map.tscn deleted file mode 100644 index dcfab796..00000000 --- a/scenes/map.tscn +++ /dev/null @@ -1,86 +0,0 @@ -[gd_scene load_steps=6 format=1] - -[ext_resource path="res://scenes/map.gd" type="Script" id=1] -[ext_resource path="res://components/util/smol_tileset.tres" type="TileSet" id=2] -[ext_resource path="res://components/ui/cursor.tscn" type="PackedScene" id=3] -[ext_resource path="res://components/util/highlight_tileset.tres" type="TileSet" id=4] -[ext_resource path="res://components/ui/highlight_map.gd" type="Script" id=5] - -[node name="map" type="Node2D"] - -script/script = ExtResource( 1 ) -__meta__ = { "_edit_lock_":true } - -[node name="floors" type="TileMap" parent="."] - -transform/pos = Vector2( 480, 240 ) -mode = 1 -tile_set = ExtResource( 2 ) -cell/size = Vector2( 64, 32 ) -cell/quadrant_size = 16 -cell/custom_transform = Matrix32( 1, 0, 0, 1, 0, 0 ) -cell/half_offset = 2 -cell/tile_origin = 1 -cell/y_sort = false -collision/use_kinematic = false -collision/friction = 1.0 -collision/bounce = 0.0 -collision/layers = 1 -collision/mask = 1 -occluder/light_mask = 1 -tile_data = IntArray( -524283, 0, -524282, 0, -524281, 0, -524280, 0, -524279, 0, -524278, 0, -524277, 0, -524276, 0, -524275, 0, -524274, 0, -524273, 0, -524272, 0, -524271, 0, -524270, 0, -524269, 0, -524268, 0, -524267, 0, -524266, 0, -524265, 0, -524264, 0, -524263, 0, -524262, 0, -524261, 0, -524260, 0, -524259, 0, -524258, 0, -524257, 0, -524256, 0, -458747, 0, -458746, 0, -458745, 0, -458744, 0, -458743, 0, -458742, 0, -458741, 0, -458740, 0, -458739, 0, -458738, 0, -458737, 0, -458736, 0, -458735, 0, -458734, 0, -458733, 0, -458732, 0, -458731, 0, -458730, 0, -458729, 0, -458728, 0, -458727, 0, -458726, 0, -458725, 0, -458724, 0, -458723, 0, -458722, 0, -458721, 0, -458720, 0, -393211, 0, -393210, 0, -393209, 0, -393208, 0, -393207, 0, -393206, 0, -393205, 0, -393204, 0, -393203, 0, -393202, 0, -393201, 0, -393200, 0, -393199, 0, -393198, 0, -393197, 0, -393196, 0, -393195, 0, -393194, 0, -393193, 0, -393192, 0, -393191, 0, -393190, 0, -393189, 0, -393188, 0, -393187, 0, -393186, 0, -393185, 0, -393184, 0, -327675, 0, -327674, 0, -327673, 0, -327672, 0, -327671, 0, -327670, 0, -327669, 0, -327668, 0, -327667, 0, -327666, 0, -327665, 0, -327664, 0, -327663, 0, -327662, 0, -327661, 0, -327660, 0, -327659, 0, -327658, 0, -327657, 0, -327656, 0, -327655, 0, -327654, 0, -327653, 0, -327652, 0, -327651, 0, -327650, 0, -327649, 0, -327648, 0, -262139, 0, -262138, 0, -262137, 0, -262136, 0, -262135, 0, -262134, 0, -262133, 0, -262132, 0, -262131, 0, -262130, 0, -262129, 0, -262128, 0, -262127, 0, -262126, 0, -262125, 0, -262124, 0, -262123, 0, -262122, 0, -262121, 0, -262120, 0, -262119, 0, -262118, 0, -262117, 0, -262116, 0, -262115, 0, -262114, 0, -262113, 0, -262112, 0, -196603, 0, -196602, 0, -196601, 0, -196600, 0, -196599, 0, -196598, 0, -196597, 0, -196596, 0, -196595, 0, -196594, 0, -196593, 0, -196592, 0, -196591, 0, -196590, 0, -196589, 0, -196588, 0, -196587, 0, -196586, 0, -196585, 0, -196584, 0, -196583, 0, -196582, 0, -196581, 0, -196580, 0, -196579, 0, -196578, 0, -196577, 0, -196576, 0, -131067, 0, -131066, 0, -131065, 0, -131064, 0, -131063, 0, -131062, 0, -131061, 0, -131060, 0, -131059, 0, -131058, 0, -131057, 0, -131056, 0, -131055, 0, -131054, 0, -131053, 0, -131052, 0, -131051, 0, -131050, 0, -131049, 0, -131048, 0, -131047, 0, -131046, 0, -131045, 0, -131044, 0, -131043, 0, -131042, 0, -131041, 0, -131040, 0, -65531, 0, -65530, 0, -65529, 0, -65528, 0, -65527, 0, -65526, 0, -65525, 0, -65524, 0, -65523, 0, -65522, 0, -65521, 0, -65520, 0, -65519, 0, -65518, 0, -65517, 0, -65516, 0, -65515, 0, -65514, 0, -65513, 0, -65512, 0, -65511, 0, -65510, 0, -65509, 0, -65508, 0, -65507, 0, -65506, 0, -65505, 0, -65504, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 16, 0, 17, 0, 18, 0, 19, 0, 20, 0, 21, 0, 22, 0, 23, 0, 24, 0, 25, 0, 26, 0, 27, 0, 28, 0, 29, 0, 30, 0, 31, 0, 32, 0, 65541, 0, 65542, 0, 65543, 0, 65544, 0, 65545, 0, 65546, 0, 65547, 0, 65548, 0, 65549, 0, 65550, 0, 65551, 0, 65552, 0, 65553, 0, 65554, 0, 65555, 0, 65556, 0, 65557, 0, 65558, 0, 65559, 0, 65560, 0, 65561, 0, 65562, 0, 65563, 0, 65564, 0, 65565, 0, 65566, 0, 65567, 0, 65568, 0, 131077, 0, 131078, 0, 131079, 0, 131080, 0, 131081, 0, 131082, 0, 131083, 0, 131084, 0, 131085, 0, 131086, 0, 131087, 0, 131088, 0, 131089, 0, 131090, 0, 131091, 0, 131092, 0, 131093, 0, 131094, 0, 131095, 0, 131096, 0, 131097, 0, 131098, 0, 131099, 0, 131100, 0, 131101, 0, 131102, 0, 131103, 0, 131104, 0, 196613, 0, 196614, 0, 196615, 0, 196616, 0, 196617, 0, 196618, 0, 196619, 0, 196620, 0, 196621, 0, 196622, 0, 196623, 0, 196624, 0, 196625, 0, 196626, 0, 196627, 0, 196628, 0, 196629, 0, 196630, 0, 196631, 0, 196632, 0, 196633, 0, 196634, 0, 196635, 0, 196636, 0, 196637, 0, 196638, 0, 196639, 0, 196640, 0, 262149, 0, 262150, 0, 262151, 0, 262152, 0, 262153, 0, 262154, 0, 262155, 0, 262156, 0, 262157, 0, 262158, 0, 262159, 0, 262160, 0, 262161, 0, 262162, 0, 262163, 0, 262164, 0, 262165, 0, 262166, 0, 262167, 0, 262168, 0, 262169, 0, 262170, 0, 262171, 0, 262172, 0, 262173, 0, 262174, 0, 262175, 0, 262176, 0, 327685, 0, 327686, 0, 327687, 0, 327688, 0, 327689, 0, 327690, 0, 327691, 0, 327692, 0, 327693, 0, 327694, 0, 327695, 0, 327696, 0, 327697, 0, 327698, 0, 327699, 0, 327700, 0, 327701, 0, 327702, 0, 327703, 0, 327704, 0, 327705, 0, 327706, 0, 327707, 0, 327708, 0, 327709, 0, 327710, 0, 327711, 0, 327712, 0, 393221, 0, 393222, 0, 393223, 0, 393224, 0, 393225, 0, 393226, 0, 393227, 0, 393228, 0, 393229, 0, 393230, 0, 393231, 0, 393232, 0, 393233, 0, 393234, 0, 393235, 0, 393236, 0, 393237, 0, 393238, 0, 393239, 0, 393240, 0, 393241, 0, 393242, 0, 393243, 0, 393244, 0, 393245, 0, 393246, 0, 393247, 0, 393248, 0, 458757, 0, 458758, 0, 458759, 0, 458760, 0, 458761, 0, 458762, 0, 458763, 0, 458764, 0, 458765, 0, 458766, 0, 458767, 0, 458768, 0, 458769, 0, 458770, 0, 458771, 0, 458772, 0, 458773, 0, 458774, 0, 458775, 0, 458776, 0, 458777, 0, 458778, 0, 458779, 0, 458780, 0, 458781, 0, 458782, 0, 458783, 0, 458784, 0, 524293, 0, 524294, 0, 524295, 0, 524296, 0, 524297, 0, 524298, 0, 524299, 0, 524300, 0, 524301, 0, 524302, 0, 524303, 0, 524304, 0, 524305, 0, 524306, 0, 524307, 0, 524308, 0, 524309, 0, 524310, 0, 524311, 0, 524312, 0, 524313, 0, 524314, 0, 524315, 0, 524316, 0, 524317, 0, 524318, 0, 524319, 0, 524320, 0, 589829, 0, 589830, 0, 589831, 0, 589832, 0, 589833, 0, 589834, 0, 589835, 0, 589836, 0, 589837, 0, 589838, 0, 589839, 0, 589840, 0, 589841, 0, 589842, 0, 589843, 0, 589844, 0, 589845, 0, 589846, 0, 589847, 0, 589848, 0, 589849, 0, 589850, 0, 589851, 0, 589852, 0, 589853, 0, 589854, 0, 589855, 0, 589856, 0, 655365, 0, 655366, 0, 655367, 0, 655368, 0, 655369, 0, 655370, 0, 655371, 0, 655372, 0, 655373, 0, 655374, 0, 655375, 0, 655376, 0, 655377, 0, 655378, 0, 655379, 0, 655380, 0, 655381, 0, 655382, 0, 655383, 0, 655384, 0, 655385, 0, 655386, 0, 655387, 0, 655388, 0, 655389, 0, 655390, 0, 655391, 0, 655392, 0, 720901, 0, 720902, 0, 720903, 0, 720904, 0, 720905, 0, 720906, 0, 720907, 0, 720908, 0, 720909, 0, 720910, 0, 720911, 0, 720912, 0, 720913, 0, 720914, 0, 720915, 0, 720916, 0, 720917, 0, 720918, 0, 720919, 0, 720920, 0, 720921, 0, 720922, 0, 720923, 0, 720924, 0, 720925, 0, 720926, 0, 720927, 0, 720928, 0, 786437, 0, 786438, 0, 786439, 0, 786440, 0, 786441, 0, 786442, 0, 786443, 0, 786444, 0, 786445, 0, 786446, 0, 786447, 0, 786448, 0, 786449, 0, 786450, 0, 786451, 0, 786452, 0, 786453, 0, 786454, 0, 786455, 0, 786456, 0, 786457, 0, 786458, 0, 786459, 0, 786460, 0, 786461, 0, 786462, 0, 786463, 0, 786464, 0, 851973, 0, 851974, 0, 851975, 0, 851976, 0, 851977, 0, 851978, 0, 851979, 0, 851980, 0, 851981, 0, 851982, 0, 851983, 0, 851984, 0, 851985, 0, 851986, 0, 851987, 0, 851988, 0, 851989, 0, 851990, 0, 851991, 0, 851992, 0, 851993, 0, 851994, 0, 851995, 0, 851996, 0, 851997, 0, 851998, 0, 851999, 0, 852000, 0, 917519, 0, 917520, 0, 917521, 0, 917522, 0, 917523, 0, 917524, 0, 917525, 0, 917526, 0, 917527, 0, 917528, 0, 917529, 0, 917530, 0, 917531, 0, 917532, 0, 917533, 0, 917534, 0, 917535, 0, 917536, 0, 983055, 0, 983056, 0, 983057, 0, 983058, 0, 983059, 0, 983060, 0, 983061, 0, 983062, 0, 983063, 0, 983064, 0, 983065, 0, 983066, 0, 1048591, 0, 1048592, 0, 1048593, 0, 1048594, 0, 1048595, 0, 1048596, 0, 1048597, 0, 1048598, 0, 1048599, 0, 1048600, 0, 1048601, 0, 1048602, 0, 1114127, 0, 1114128, 0, 1114129, 0, 1114130, 0, 1114131, 0, 1114132, 0, 1114133, 0, 1114134, 0, 1114135, 0, 1114136, 0, 1114137, 0, 1114138, 0, 1179663, 0, 1179664, 0, 1179665, 0, 1179666, 0, 1179667, 0, 1179668, 0, 1179669, 0, 1179670, 0, 1179671, 0, 1179672, 0, 1179673, 0, 1179674, 0, 1245199, 0, 1245200, 0, 1245201, 0, 1245202, 0, 1245203, 0, 1245204, 0, 1245205, 0, 1245206, 0, 1245207, 0, 1245208, 0, 1245209, 0, 1245210, 0, 1310735, 0, 1310736, 0, 1310737, 0, 1310738, 0, 1310739, 0, 1310740, 0, 1310741, 0, 1310742, 0, 1310743, 0, 1310744, 0, 1310745, 0, 1310746, 0, 1310747, 0, 1310748, 0, 1310749, 0, 1310750, 0, 1310751, 0, 1310752, 0, 1310753, 0, 1376271, 0, 1376272, 0, 1376273, 0, 1376274, 0, 1376275, 0, 1376276, 0, 1376277, 0, 1376278, 0, 1376279, 0, 1376280, 0, 1376281, 0, 1376282, 0, 1376283, 0, 1376284, 0, 1376285, 0, 1376286, 0, 1376287, 0, 1376288, 0, 1376289, 0, 1441807, 0, 1441808, 0, 1441809, 0, 1441810, 0, 1441811, 0, 1441812, 0, 1441813, 0, 1441814, 0, 1441815, 0, 1441816, 0, 1441817, 0, 1441818, 0, 1441819, 0, 1441820, 0, 1441821, 0, 1441822, 0, 1441823, 0, 1441824, 0, 1441825, 0, 1507343, 0, 1507344, 0, 1507345, 0, 1507346, 0, 1507347, 0, 1507348, 0, 1507349, 0, 1507350, 0, 1507351, 0, 1507352, 0, 1507353, 0, 1507354, 0, 1507355, 0, 1507356, 0, 1507357, 0, 1507358, 0, 1507359, 0, 1507360, 0, 1507361, 0, 1572879, 0, 1572880, 0, 1572881, 0, 1572882, 0, 1572883, 0, 1572884, 0, 1572885, 0, 1572886, 0, 1572887, 0, 1572888, 0, 1572889, 0, 1572890, 0, 1572891, 0, 1572892, 0, 1572893, 0, 1572894, 0, 1572895, 0, 1572896, 0, 1572897, 0, 1638415, 0, 1638416, 0, 1638417, 0, 1638418, 0, 1638419, 0, 1638420, 0, 1638421, 0, 1638422, 0, 1638423, 0, 1638424, 0, 1638425, 0, 1638426, 0, 1638427, 0, 1638428, 0, 1638429, 0, 1638430, 0, 1638431, 0, 1638432, 0, 1638433, 0 ) -__meta__ = { "_edit_lock_":true } - -[node name="cursor" parent="floors" instance=ExtResource( 3 )] - -[node name="highlights" type="TileMap" parent="."] - -transform/pos = Vector2( 480, 240 ) -mode = 1 -tile_set = ExtResource( 4 ) -cell/size = Vector2( 64, 32 ) -cell/quadrant_size = 16 -cell/custom_transform = Matrix32( 1, 0, 0, 1, 0, 0 ) -cell/half_offset = 2 -cell/tile_origin = 1 -cell/y_sort = false -collision/use_kinematic = false -collision/friction = 1.0 -collision/bounce = 0.0 -collision/layers = 1 -collision/mask = 1 -occluder/light_mask = 1 -tile_data = IntArray( ) -script/script = ExtResource( 5 ) -__meta__ = { "_edit_lock_":true } - -[node name="walls" type="TileMap" parent="."] - -transform/pos = Vector2( 480, 240 ) -mode = 1 -tile_set = ExtResource( 2 ) -cell/size = Vector2( 64, 32 ) -cell/quadrant_size = 16 -cell/custom_transform = Matrix32( 1, 0, 0, 1, 0, 0 ) -cell/half_offset = 2 -cell/tile_origin = 1 -cell/y_sort = true -collision/use_kinematic = false -collision/friction = 1.0 -collision/bounce = 0.0 -collision/layers = 1 -collision/mask = 1 -occluder/light_mask = 1 -tile_data = IntArray( -524283, 1, -524282, 5, -524281, 5, -524280, 5, -524279, 5, -524278, 5, -524277, 5, -524276, 1, -524275, 5, -524274, 5, -524273, 5, -524272, 5, -524271, 5, -524270, 5, -524269, 5, -524268, 5, -524267, 5, -524266, 5, -524265, 5, -524264, 5, -524263, 5, -524262, 5, -524261, 5, -524260, 5, -524259, 5, -524258, 5, -524257, 5, -524256, 1, -458747, 3, -458740, 3, -458720, 3, -393211, 3, -393204, 3, -393184, 3, -327675, 3, -327668, 3, -327648, 3, -262139, 3, -262132, 3, -262112, 3, -196603, 3, -196596, 1, -196576, 3, -131067, 3, -131040, 3, -65531, 3, -65504, 3, 5, 3, 32, 3, 65541, 3, 65548, 1, 65568, 3, 131077, 3, 131084, 3, 131104, 3, 196613, 3, 196620, 3, 196640, 3, 262149, 1, 262150, 5, 262151, 5, 262152, 5, 262153, 5, 262154, 5, 262155, 5, 262156, 1, 262176, 3, 327685, 3, 327712, 3, 393221, 3, 393248, 3, 458757, 3, 458784, 3, 524293, 3, 524320, 3, 589829, 3, 589856, 3, 655365, 3, 655392, 3, 720901, 3, 720928, 3, 786437, 3, 786464, 3, 851973, 1, 851974, 5, 851975, 5, 851976, 5, 851977, 5, 851978, 5, 851979, 5, 851980, 5, 851981, 5, 851982, 5, 851983, 1, 852000, 3, 917511, 10, 917512, 10, 917513, 10, 917514, 10, 917519, 3, 917530, 1, 917531, 5, 917532, 5, 917533, 5, 917534, 5, 917535, 5, 917536, 1, 983055, 3, 983066, 3, 1048591, 3, 1048602, 3, 1114127, 3, 1114138, 3, 1179663, 3, 1179674, 3, 1245199, 3, 1245210, 3, 1310735, 3, 1310746, 1, 1310747, 5, 1310748, 5, 1310749, 5, 1310750, 5, 1310751, 5, 1310752, 5, 1310753, 1, 1376271, 3, 1376289, 3, 1441807, 3, 1441825, 3, 1507343, 3, 1507361, 3, 1572879, 3, 1572897, 3, 1638415, 1, 1638416, 5, 1638417, 5, 1638418, 5, 1638419, 5, 1638420, 5, 1638421, 5, 1638422, 5, 1638423, 5, 1638424, 5, 1638425, 5, 1638426, 5, 1638427, 5, 1638428, 5, 1638429, 5, 1638430, 5, 1638431, 5, 1638432, 5, 1638433, 1 ) -__meta__ = { "_edit_lock_":true } - -[node name="actors" type="Node" parent="."] - -[node name="background" type="CanvasLayer" parent="."] - -layer = 0 -offset = Vector2( 0, 0 ) -rotation = 0.0 -scale = Vector2( 1, 1 ) - - diff --git a/scenes/menu.gd b/scenes/menu.gd deleted file mode 100644 index ab9cd04f..00000000 --- a/scenes/menu.gd +++ /dev/null @@ -1,40 +0,0 @@ - -extends Control - -const MenuButton = preload("res://components/ui/save-button.tscn") - -const Route = preload("res://model/route.gd") - -onready var saves_node = get_node("saves") -onready var caplog = get_node("/root/captains_log") -onready var profile = caplog.get_profile() - -func _ready(): - start() - -func start(): - var journals = profile.get_journals() - for route_id in journals: - var char_name = profile.get_player_name(route_id) - var button = MenuButton.instance() - button.set_text(char_name) - button.connect("pressed", self, "_on_load_game", [route_id]) - saves_node.add_child(button) - set_process_input(true) - show() - -func stop(): - for button in saves_node.get_children(): - if button.get_name() != "new_game": - button.queue_free() - set_process_input(false) - hide() - -func _on_new_game(): - caplog.create_route() - stop() - -func _on_load_game(save_id): - stop() - caplog.load_route(save_id) - get_tree().set_current_scene(get_node("/root/sector")) diff --git a/scenes/menu.tscn b/scenes/menu.tscn deleted file mode 100644 index 4a481484..00000000 --- a/scenes/menu.tscn +++ /dev/null @@ -1,84 +0,0 @@ -[gd_scene load_steps=6 format=1] - -[ext_resource path="res://components/theme/wilokai.tres" type="Theme" id=1] -[ext_resource path="res://scenes/menu.gd" type="Script" id=2] -[ext_resource path="res://assets/fonts/PressStart2P.fnt" type="BitmapFont" id=3] -[ext_resource path="res://assets/fonts/PressStart2P-small.fnt" type="BitmapFont" id=4] -[ext_resource path="res://components/controller/default_controller.gd" type="Script" id=5] - -[node name="menu" type="Control"] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -theme/theme = ExtResource( 1 ) -margin/left = 0.0 -margin/top = 0.0 -margin/right = 0.0 -margin/bottom = 0.0 -script/script = ExtResource( 2 ) - -[node name="Title" type="Label" parent="."] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = true -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 0 -margin/left = 32.0 -margin/top = 128.0 -margin/right = 32.0 -margin/bottom = 256.0 -custom_fonts/font = ExtResource( 3 ) -custom_colors/font_color = Color( 0.686275, 0.627451, 0.376471, 1 ) -text = "Backdoor" -align = 1 -valign = 1 -percent_visible = 1.0 -lines_skipped = 0 -max_lines_visible = -1 - -[node name="saves" type="VBoxContainer" parent="."] - -anchor/right = 1 -anchor/bottom = 1 -focus/ignore_mouse = false -focus/stop_mouse = false -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 128.0 -margin/top = 256.0 -margin/right = 128.0 -margin/bottom = 32.0 -alignment = 0 -__meta__ = { "_edit_lock_":true } - -[node name="new_game" type="Button" parent="saves"] - -focus/ignore_mouse = false -focus/stop_mouse = true -size_flags/horizontal = 2 -size_flags/vertical = 2 -margin/left = 0.0 -margin/top = 0.0 -margin/right = 384.0 -margin/bottom = 16.0 -custom_fonts/font = ExtResource( 4 ) -toggle_mode = false -enabled_focus_mode = 2 -shortcut = null -text = "New route" -flat = false -__meta__ = { "_edit_lock_":true } - -[node name="Controller" type="Node" parent="."] - -script/script = ExtResource( 5 ) - -[connection signal="pressed" from="saves/new_game" to="." method="_on_new_game"] - - diff --git a/scripts/migrate-abilities.lua b/scripts/migrate-abilities.lua new file mode 100644 index 00000000..43a209d6 --- /dev/null +++ b/scripts/migrate-abilities.lua @@ -0,0 +1,101 @@ +-------------------------------------------------------------------------------- +-- This is script is used to migrate abilities in the database from 0.1's -- +-- system to 0.2's system -- +-------------------------------------------------------------------------------- + +local json = require 'game.libs.dkjson' + +local ins = { {'params', 'input'}, {'operators', 'operator'} } + +local function migrateNode(node) + if node['inputs'] then + return node + end + local new = {} + local names = {} + new.inputs = {} + for _,intype in ipairs(ins) do + for i,param in ipairs(node[intype[1]]) do + local input = {} + input.type = intype[2] + for k,field in pairs(param) do + if k == 'typename' then + input.name = field + elseif type(field) == 'string' then + local t,label = field:match('(%w+):(.+)') + if t and label then + input[k] = '=' .. label + else + input[k] = field + if k == 'output' then + assert(not names[field]) + names[field] = true + end + end + else + input[k] = field + end + end + table.insert(new.inputs, input) + end + end + new.effects = {} + for i,param in ipairs(node['effects']) do + local input = {} + input.type = 'effect' + for k,field in pairs(param) do + if k == 'typename' then + input.name = field + elseif type(field) == 'string' then + local t,label = field:match('(%w+):(.+)') + if t and label then + input[k] = '=' .. label + else + input[k] = field + if k == 'output' then + assert(not names[field]) + names[field] = true + end + end + else + input[k] = field + end + end + table.insert(new.effects, input) + end + return new +end + +local function scanNode(node) + local changed = false + for k,v in pairs(node) do + if type(k) == 'string' and (k:match '.*ability.*' or + k:match 'trigger%-condition') then + node[k] = migrateNode(v) + changed = true + elseif type(v) == 'table' then + changed = scanNode(v) + end + end + return changed +end + +local function migrate(filename) + print(("Updating %s"):format(filename)) + local f = io.open(filename, 'r') + local data = json.decode(f:read('a')) + f:close() + if scanNode(data) then + local out = json.encode(data, { indent = true }) + print(filename, "changed") + print(out) + f = io.open(filename, 'w') + f:write(out) + f:close() + end +end + +for line in io.lines() do + migrate(line) +end +