├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── feature-request.md │ └── question.md └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── io │ └── github │ └── apickledwalrus │ └── skriptgui │ ├── SkriptClasses.java │ ├── SkriptConverters.java │ ├── SkriptGUI.java │ ├── elements │ ├── conditions │ │ ├── CondHasGUI.java │ │ └── package-info.java │ ├── effects │ │ ├── EffCancelGUIClose.java │ │ └── package-info.java │ ├── expressions │ │ ├── ExprGUI.java │ │ ├── ExprGUIIdentifiers.java │ │ ├── ExprGUIProperties.java │ │ ├── ExprGUIValues.java │ │ ├── ExprGUIs.java │ │ ├── ExprIDOfGUI.java │ │ ├── ExprLastGUI.java │ │ ├── ExprNextGUISlot.java │ │ ├── ExprPaginatedList.java │ │ ├── ExprVirtualInventory.java │ │ └── package-info.java │ └── sections │ │ ├── SecCreateGUI.java │ │ ├── SecGUIOpenClose.java │ │ ├── SecMakeGUI.java │ │ └── package-info.java │ ├── gui │ ├── GUI.java │ ├── GUIEventHandler.java │ ├── GUIManager.java │ ├── events │ │ ├── GUIEvents.java │ │ ├── RecipeEvent.java │ │ └── package-info.java │ └── package-info.java │ └── package-info.java └── resources ├── lang └── english.lang └── plugin.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Find an issue with the addon? Use this template! 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ### Description 13 | 14 | 15 | ### Steps to Reproduce 16 | 17 | 18 | ### Expected Behavior 19 | 20 | 21 | ### Errors / Screenshots 22 | 23 | 24 | 28 | 29 | ### Server Information 30 | * **Server version/platform:** 31 | * **skript-gui version:** 32 | * **Skript version:** 33 | 34 | ### Additional Context 35 | 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for the addon. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Description 11 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about the addon. 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ### Description 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Set up JDK 17 11 | uses: actions/setup-java@v4 12 | with: 13 | distribution: 'adopt' 14 | java-version: '17' 15 | - name: Build with Gradle 16 | run: ./gradlew nightlyBuild 17 | - name: Upload Nightly Build 18 | uses: actions/upload-artifact@v4 19 | if: success() 20 | with: 21 | name: skript-gui-nightly 22 | path: build/libs/skript-gui-nightly.jar 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/SkriptLang/Skript/blob/master/.gitignore 2 | 3 | # Created by https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,java,eclipse,git 4 | # Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,gradle,java,eclipse,git 5 | 6 | ### Eclipse ### 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .settings/ 16 | .loadpath 17 | .recommenders 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # PyDev specific (Python IDE for Eclipse) 26 | *.pydevproject 27 | 28 | # CDT-specific (C/C++ Development Tooling) 29 | .cproject 30 | 31 | # CDT- autotools 32 | .autotools 33 | 34 | # Java annotation processor (APT) 35 | .factorypath 36 | 37 | # PDT-specific (PHP Development Tools) 38 | .buildpath 39 | 40 | # sbteclipse plugin 41 | .target 42 | 43 | # Tern plugin 44 | .tern-project 45 | 46 | # TeXlipse plugin 47 | .texlipse 48 | 49 | # STS (Spring Tool Suite) 50 | .springBeans 51 | 52 | # Code Recommenders 53 | .recommenders/ 54 | 55 | # Annotation Processing 56 | .apt_generated/ 57 | .apt_generated_test/ 58 | 59 | # Scala IDE specific (Scala & Java development for Eclipse) 60 | .cache-main 61 | .scala_dependencies 62 | .worksheet 63 | 64 | # Uncomment this line if you wish to ignore the project description file. 65 | # Typically, this file would be tracked if it contains build/dependency configurations: 66 | #.project 67 | 68 | ### Eclipse Patch ### 69 | # Spring Boot Tooling 70 | .sts4-cache/ 71 | 72 | ### Git ### 73 | # Created by git for backups. To disable backups in Git: 74 | # $ git config --global mergetool.keepBackup false 75 | *.orig 76 | 77 | # Created by git when using merge tools for conflicts 78 | *.BACKUP.* 79 | *.BASE.* 80 | *.LOCAL.* 81 | *.REMOTE.* 82 | *_BACKUP_*.txt 83 | *_BASE_*.txt 84 | *_LOCAL_*.txt 85 | *_REMOTE_*.txt 86 | 87 | ### Intellij+all ### 88 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 89 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 90 | 91 | # User-specific stuff 92 | .idea/**/workspace.xml 93 | .idea/**/tasks.xml 94 | .idea/**/usage.statistics.xml 95 | .idea/**/dictionaries 96 | .idea/**/shelf 97 | 98 | # Generated files 99 | .idea/**/contentModel.xml 100 | 101 | # Sensitive or high-churn files 102 | .idea/**/dataSources/ 103 | .idea/**/dataSources.ids 104 | .idea/**/dataSources.local.xml 105 | .idea/**/sqlDataSources.xml 106 | .idea/**/dynamic.xml 107 | .idea/**/uiDesigner.xml 108 | .idea/**/dbnavigator.xml 109 | 110 | # Gradle 111 | .idea/**/gradle.xml 112 | .idea/**/libraries 113 | 114 | # Gradle and Maven with auto-import 115 | # When using Gradle or Maven with auto-import, you should exclude module files, 116 | # since they will be recreated, and may cause churn. Uncomment if using 117 | # auto-import. 118 | .idea/artifacts 119 | .idea/compiler.xml 120 | .idea/jarRepositories.xml 121 | .idea/modules.xml 122 | .idea/*.iml 123 | .idea/modules 124 | *.iml 125 | *.ipr 126 | 127 | # CMake 128 | cmake-build-*/ 129 | 130 | # Mongo Explorer plugin 131 | .idea/**/mongoSettings.xml 132 | 133 | # File-based project format 134 | *.iws 135 | 136 | # IntelliJ 137 | out/ 138 | 139 | # mpeltonen/sbt-idea plugin 140 | .idea_modules/ 141 | 142 | # JIRA plugin 143 | atlassian-ide-plugin.xml 144 | 145 | # Cursive Clojure plugin 146 | .idea/replstate.xml 147 | 148 | # Crashlytics plugin (for Android Studio and IntelliJ) 149 | com_crashlytics_export_strings.xml 150 | crashlytics.properties 151 | crashlytics-build.properties 152 | fabric.properties 153 | 154 | # Editor-based Rest Client 155 | .idea/httpRequests 156 | 157 | # Android studio 3.1+ serialized cache file 158 | .idea/caches/build_file_checksums.ser 159 | 160 | ### Intellij+all Patch ### 161 | # Ignores the whole .idea folder and all .iml files 162 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 163 | 164 | .idea/ 165 | 166 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 167 | 168 | modules.xml 169 | .idea/misc.xml 170 | 171 | # Sonarlint plugin 172 | .idea/sonarlint 173 | 174 | ### Java ### 175 | # Compiled class file 176 | *.class 177 | 178 | # Log file 179 | *.log 180 | 181 | # BlueJ files 182 | *.ctxt 183 | 184 | # Mobile Tools for Java (J2ME) 185 | .mtj.tmp/ 186 | 187 | # Package Files # 188 | *.jar 189 | *.war 190 | *.nar 191 | *.ear 192 | *.zip 193 | *.tar.gz 194 | *.rar 195 | 196 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 197 | hs_err_pid* 198 | 199 | ### Gradle ### 200 | .gradle 201 | build/ 202 | 203 | # Ignore Gradle GUI config 204 | gradle-app.setting 205 | 206 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 207 | !gradle-wrapper.jar 208 | 209 | # Cache of project 210 | .gradletasknamecache 211 | 212 | ### Gradle Patch ### 213 | **/build/ 214 | 215 | # End of https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,java,eclipse,git 216 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # skript-gui 2 | Simple syntax to create advanced and organized GUIs with Skript. 3 | 4 | 5 | This addon allows you to create custom GUIs with Skript. 6 | It is a remake of TuSKe's (by Tuke_Nuke - https://github.com/Tuke-Nuke/TuSKe) advanced GUI system. 7 | That system was abandoned and has become very outdated, so I decided to create this one. 8 | 9 | Little has changed syntax-wise if you were already using TuSKe's advanced GUI system. 10 | However, please be sure to check out the wiki! The wiki links are listed below. 11 | 12 | If you run into any issues, have questions, or want a new feature added, please let me know in the "Issues" category! 13 | 14 | # Links 15 | 16 | **Skript:** https://github.com/SkriptLang/Skript 17 | 18 | # Examples and Documentation 19 | 20 | Documentation is available on the following wikis: 21 | 22 | **GitHub Wiki:** https://github.com/APickledWalrus/skript-gui/wiki 23 | 24 | 25 | [![SkriptHubViewTheDocs](http://skripthub.net/static/addon/ViewTheDocsButton.png)](http://skripthub.net/docs/?addon=skript-gui) 26 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.ReplaceTokens 2 | 3 | plugins { 4 | id 'java' 5 | } 6 | 7 | group 'io.github.apickledwalrus' 8 | version '1.3.1' 9 | 10 | compileJava.options.encoding = 'UTF-8' 11 | 12 | java { 13 | toolchain { 14 | languageVersion.set(JavaLanguageVersion.of(17)) 15 | } 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | maven { url = 'https://repo.skriptlang.org/releases' } 21 | maven { url = 'https://repo.papermc.io/repository/maven-public/' } 22 | maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } 23 | } 24 | 25 | dependencies { 26 | implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.600' 27 | implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' 28 | implementation group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.16.5-R0.1-SNAPSHOT' 29 | implementation (group: 'com.github.SkriptLang', name: 'Skript', version: '2.10.2') { 30 | transitive = false 31 | } 32 | } 33 | 34 | processResources { 35 | filter ReplaceTokens, tokens: [ 36 | 'version': version 37 | ] 38 | } 39 | 40 | task nightlyResources(type: ProcessResources) { 41 | from 'src/main/resources', { 42 | include '**' 43 | filter ReplaceTokens, tokens: [ 44 | 'version' : project.property('version') + '-nightly-' + 'git rev-parse --short HEAD'.execute().text.trim() 45 | ] 46 | } 47 | into 'build/resources/main' 48 | } 49 | 50 | task nightlyBuild(type: Jar) { 51 | dependsOn nightlyResources 52 | from sourceSets.main.output 53 | archiveFileName = 'skript-gui-nightly.jar' 54 | } 55 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/APickledWalrus/skript-gui/251acd8576950246d169dc9802b35751398cbedf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.0/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = 'skript-gui' 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/SkriptClasses.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui; 2 | 3 | import ch.njol.skript.classes.ClassInfo; 4 | import ch.njol.skript.classes.EnumSerializer; 5 | import ch.njol.skript.classes.Parser; 6 | import ch.njol.skript.lang.ParseContext; 7 | import ch.njol.skript.registrations.Classes; 8 | import ch.njol.skript.util.EnumUtils; 9 | import io.github.apickledwalrus.skriptgui.gui.GUI; 10 | import org.bukkit.event.inventory.InventoryType.SlotType; 11 | import org.eclipse.jdt.annotation.Nullable; 12 | 13 | public class SkriptClasses { 14 | 15 | public SkriptClasses() { 16 | 17 | Classes.registerClass(new ClassInfo<>(GUI.class, "guiinventory") 18 | .user("gui inventor(y|ies)?") 19 | .name("GUI") 20 | .description("Represents a skript-gui GUI") 21 | .examples("See the GUI creation section.") 22 | .since("1.0") 23 | .parser(new Parser() { 24 | @Override 25 | public boolean canParse(ParseContext ctx) { 26 | return false; 27 | } 28 | 29 | @Override 30 | public String toString(GUI gui, int flags) { 31 | return gui.getInventory().getType().getDefaultTitle().toLowerCase() 32 | + " gui named " + gui.getName() 33 | + " with " + gui.getInventory().getSize() / 9 + " rows" 34 | + " and shape " + gui.getRawShape(); 35 | } 36 | 37 | @Override 38 | public String toVariableNameString(GUI gui) { 39 | return toString(gui, 0); 40 | } 41 | }) 42 | ); 43 | 44 | if (Classes.getExactClassInfo(SlotType.class) == null) { 45 | EnumUtils slotTypes = new EnumUtils<>(SlotType.class, "slot types"); 46 | Classes.registerClass(new ClassInfo<>(SlotType.class, "slottype") 47 | .user("slot types?") 48 | .name("Slot Types") 49 | .description("Represents the slot type in an Inventory Click Event.") 50 | .examples(slotTypes.getAllNames()) 51 | .since("1.0.0") 52 | .parser(new Parser() { 53 | @Override 54 | @Nullable 55 | public SlotType parse(String expr, ParseContext context) { 56 | return slotTypes.parse(expr); 57 | } 58 | 59 | @Override 60 | public boolean canParse(ParseContext ctx) { 61 | return true; 62 | } 63 | 64 | @Override 65 | public String toString(SlotType type, int flags) { 66 | return slotTypes.toString(type, flags); 67 | } 68 | 69 | @Override 70 | public String toVariableNameString(SlotType type) { 71 | return "slottype:" + type.name(); 72 | } 73 | }) 74 | .serializer(new EnumSerializer<>(SlotType.class) 75 | )); 76 | } 77 | 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/SkriptConverters.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui; 2 | 3 | import io.github.apickledwalrus.skriptgui.gui.GUI; 4 | import org.bukkit.inventory.Inventory; 5 | import org.skriptlang.skript.lang.converter.Converters; 6 | 7 | public class SkriptConverters { 8 | 9 | public SkriptConverters() { 10 | 11 | Converters.registerConverter(GUI.class, Inventory.class, GUI::getInventory); 12 | 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/SkriptGUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui; 2 | 3 | import java.io.IOException; 4 | 5 | import ch.njol.skript.util.Version; 6 | import io.github.apickledwalrus.skriptgui.gui.events.GUIEvents; 7 | import io.github.apickledwalrus.skriptgui.gui.events.RecipeEvent; 8 | import org.bukkit.plugin.Plugin; 9 | import org.bukkit.plugin.java.JavaPlugin; 10 | 11 | import ch.njol.skript.Skript; 12 | import ch.njol.skript.SkriptAddon; 13 | import io.github.apickledwalrus.skriptgui.gui.GUIManager; 14 | 15 | public class SkriptGUI extends JavaPlugin { 16 | 17 | @SuppressWarnings("NotNullFieldNotInitialized") 18 | private static SkriptGUI instance; 19 | @SuppressWarnings("NotNullFieldNotInitialized") 20 | private static GUIManager manager; 21 | 22 | @Override 23 | public void onEnable() { 24 | Plugin skript = getServer().getPluginManager().getPlugin("Skript"); 25 | Version minimumSupportedVersion = new Version(2, 10, 2); 26 | if (skript == null) { 27 | // Skript doesn't exist within the server plugins folder 28 | getLogger().severe("Could not find Skript! Make sure you have it installed. Disabling..."); 29 | getLogger().severe("skript-gui requires Skript " + minimumSupportedVersion + " or newer! Download Skript releases at https://github.com/SkriptLang/Skript/releases"); 30 | getServer().getPluginManager().disablePlugin(this); 31 | return; 32 | } else if (!skript.isEnabled()) { 33 | // Skript is disabled on the server 34 | getLogger().severe("Skript failed to properly enable and is disabled on the server. Disabling..."); 35 | getServer().getPluginManager().disablePlugin(this); 36 | return; 37 | } else if (Skript.getVersion().isSmallerThan(minimumSupportedVersion)) { 38 | // Current Skript version is below minimum required version 39 | getLogger().severe("You're running an unsupported Skript version (" + Skript.getVersion() + ")! Disabling..."); 40 | getLogger().severe("skript-gui requires Skript " + minimumSupportedVersion + " or newer! Download Skript releases at https://github.com/SkriptLang/Skript/releases"); 41 | getServer().getPluginManager().disablePlugin(this); 42 | return; 43 | } 44 | 45 | instance = this; 46 | 47 | SkriptAddon addon = Skript.registerAddon(this); 48 | try { 49 | addon.loadClasses("io.github.apickledwalrus.skriptgui.elements"); 50 | addon.setLanguageFileDirectory("lang"); 51 | new SkriptClasses(); // Register ClassInfos 52 | new SkriptConverters(); // Register Converters 53 | } catch (IOException e) { 54 | getLogger().severe("An error occured while trying to load the addon's elements. The addon will be disabled."); 55 | getLogger().severe("Printing StackTrace:"); 56 | e.printStackTrace(); 57 | getServer().getPluginManager().disablePlugin(this); 58 | } 59 | 60 | // Register manager and events 61 | manager = new GUIManager(); 62 | getServer().getPluginManager().registerEvents(new GUIEvents(), this); 63 | if (Skript.classExists("com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent")) { 64 | // We need to track this event (see https://github.com/APickledWalrus/skript-gui/issues/33) 65 | getServer().getPluginManager().registerEvents(new RecipeEvent(), this); 66 | } 67 | 68 | } 69 | 70 | public static SkriptGUI getInstance() { 71 | return instance; 72 | } 73 | 74 | public static GUIManager getGUIManager() { 75 | return manager; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/conditions/CondHasGUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.conditions; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Event; 5 | 6 | import ch.njol.skript.Skript; 7 | import ch.njol.skript.doc.Description; 8 | import ch.njol.skript.doc.Examples; 9 | import ch.njol.skript.doc.Name; 10 | import ch.njol.skript.doc.Since; 11 | import ch.njol.skript.lang.Condition; 12 | import ch.njol.skript.lang.Expression; 13 | import ch.njol.skript.lang.SkriptParser.ParseResult; 14 | import ch.njol.util.Kleenean; 15 | 16 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 17 | import org.eclipse.jdt.annotation.Nullable; 18 | 19 | @Name("Has GUI") 20 | @Description("Checks whether the given player(s) has/have a GUI open.") 21 | @Examples({ 22 | "command /guiviewers: # Returns a list of all players with a GUI open.", 23 | "\tset {_viewers::*} to all players where [input has a gui]", 24 | "\tsend \"GUI Viewers: %{_viewers::*}%\" to player" 25 | }) 26 | @Since("1.0.0") 27 | public class CondHasGUI extends Condition { 28 | 29 | static { 30 | Skript.registerCondition(CondHasGUI.class, 31 | "%players% (has|have) a gui [open]", 32 | "%players% (doesn't|does not|do not|don't) have a gui [open]" 33 | ); 34 | } 35 | 36 | @SuppressWarnings("NotNullFieldNotInitialized") 37 | private Expression players; 38 | 39 | @Override 40 | @SuppressWarnings("unchecked") 41 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult) { 42 | players = (Expression) exprs[0]; 43 | setNegated(matchedPattern == 1); 44 | return true; 45 | } 46 | 47 | @Override 48 | public boolean check(Event e) { 49 | return players.check(e, p -> SkriptGUI.getGUIManager().hasGUI(p), isNegated()); 50 | } 51 | 52 | @Override 53 | public String toString(@Nullable Event e, boolean debug) { 54 | return players.toString(e, debug) + (!isNegated() ? " has/have " : " do not/don't have ") + " a gui open"; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/conditions/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) 2 | package io.github.apickledwalrus.skriptgui.elements.conditions; 3 | 4 | import org.eclipse.jdt.annotation.DefaultLocation; 5 | import org.eclipse.jdt.annotation.NonNullByDefault; 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/effects/EffCancelGUIClose.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.effects; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Effect; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SectionSkriptEvent; 11 | import ch.njol.skript.lang.SkriptEvent; 12 | import ch.njol.skript.lang.SkriptParser.ParseResult; 13 | import ch.njol.util.Kleenean; 14 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 15 | import io.github.apickledwalrus.skriptgui.elements.sections.SecGUIOpenClose; 16 | import io.github.apickledwalrus.skriptgui.gui.GUI; 17 | import org.bukkit.event.Event; 18 | import org.eclipse.jdt.annotation.Nullable; 19 | 20 | @Name("Cancel GUI Close") 21 | @Description({ 22 | "Cancels or uncancels the closing of a GUI.", 23 | " This effect can be used within a GUI close section.", 24 | " A 1 tick delay is applied by this effect after the code has run." 25 | }) 26 | @Examples({ 27 | "create a gui with virtual chest inventory with 3 rows named \"My GUI\":", 28 | "\trun on gui close:", 29 | "\t\tcancel the gui closing" 30 | }) 31 | @Since("1.2.0") 32 | public class EffCancelGUIClose extends Effect { 33 | 34 | static { 35 | Skript.registerEffect(EffCancelGUIClose.class, 36 | "(cancel|1¦uncancel) [the] gui clos(e|ing)" 37 | ); 38 | } 39 | 40 | private boolean cancel; 41 | 42 | @Override 43 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 44 | SkriptEvent skriptEvent = getParser().getCurrentSkriptEvent(); 45 | if (!(skriptEvent instanceof SectionSkriptEvent) || !((SectionSkriptEvent) skriptEvent).isSection(SecGUIOpenClose.class)) { 46 | Skript.error("Cancelling or uncancelling the closing of a GUI can only be done within a GUI close section."); 47 | return false; 48 | } 49 | cancel = parseResult.mark == 0; 50 | return true; 51 | } 52 | 53 | @Override 54 | protected void execute(Event e) { 55 | GUI gui = SkriptGUI.getGUIManager().getGUI(e); 56 | if (gui != null) { 57 | gui.setCloseCancelled(cancel); 58 | } 59 | } 60 | 61 | @Override 62 | public String toString(@Nullable Event e, boolean debug) { 63 | return (cancel ? "cancel" : "uncancel") + " the gui closing"; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/effects/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) 2 | package io.github.apickledwalrus.skriptgui.elements.effects; 3 | 4 | import org.eclipse.jdt.annotation.DefaultLocation; 5 | import org.eclipse.jdt.annotation.NonNullByDefault; 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprGUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.doc.Description; 4 | import ch.njol.skript.doc.Examples; 5 | import ch.njol.skript.doc.Name; 6 | import ch.njol.skript.doc.Since; 7 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 8 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 9 | import io.github.apickledwalrus.skriptgui.gui.GUI; 10 | import org.bukkit.entity.Player; 11 | import org.eclipse.jdt.annotation.Nullable; 12 | 13 | @Name("GUI of Player") 14 | @Description("The GUI that the player currently has open.") 15 | @Examples({ 16 | "edit the player's gui:", 17 | "\tmake gui 1 with dirt named \"Edited Slot\"" 18 | }) 19 | @Since("1.1.0") 20 | public class ExprGUI extends SimplePropertyExpression { 21 | 22 | static { 23 | register(ExprGUI.class, GUI.class, "gui", "players"); 24 | } 25 | 26 | @Override 27 | @Nullable 28 | public GUI convert(Player player) { 29 | return SkriptGUI.getGUIManager().getGUI(player); 30 | } 31 | 32 | @Override 33 | public Class getReturnType() { 34 | return GUI.class; 35 | } 36 | 37 | @Override 38 | protected String getPropertyName() { 39 | return "gui"; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprGUIIdentifiers.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.util.Kleenean; 13 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 14 | import io.github.apickledwalrus.skriptgui.gui.GUI; 15 | import org.bukkit.event.Event; 16 | import org.eclipse.jdt.annotation.Nullable; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | @Name("Global GUI Identifiers") 22 | @Description("A list of the identifiers of all registered global GUIs.") 23 | @Examples({ 24 | "command /guis:", 25 | "\ttrigger:", 26 | "\t\tloop all of the registered gui identifiers:", 27 | "\t\t\tsend loop-string" 28 | }) 29 | @Since("1.2.1") 30 | public class ExprGUIIdentifiers extends SimpleExpression { 31 | 32 | static { 33 | Skript.registerExpression(ExprGUIIdentifiers.class, String.class, ExpressionType.SIMPLE, 34 | "[(all [[of] the]|the)] (global|registered) gui id(s|entifiers)" 35 | ); 36 | } 37 | 38 | @Override 39 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 40 | return true; 41 | } 42 | 43 | @Override 44 | protected String[] get(Event e) { 45 | List identifiers = new ArrayList<>(); 46 | for (GUI gui : SkriptGUI.getGUIManager().getTrackedGUIs()) { 47 | if (gui.getID() != null) { 48 | identifiers.add(gui.getID()); 49 | } 50 | } 51 | return identifiers.toArray(new String[0]); 52 | } 53 | 54 | @Override 55 | public boolean isSingle() { 56 | return false; 57 | } 58 | 59 | @Override 60 | public Class getReturnType() { 61 | return String.class; 62 | } 63 | 64 | @Override 65 | public String toString(@Nullable Event e, boolean debug) { 66 | return "all of the registered gui identifiers"; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprGUIProperties.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.util.Kleenean; 12 | import ch.njol.util.coll.CollectionUtils; 13 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 14 | import io.github.apickledwalrus.skriptgui.gui.GUI; 15 | import org.bukkit.event.Event; 16 | import org.eclipse.jdt.annotation.Nullable; 17 | 18 | @Name("GUI Properties") 19 | @Description("Different properties of a GUI. They can be modified.") 20 | @Examples({ 21 | "edit gui last gui:", 22 | "\tset the name of the edited gui to \"New GUI Name!\"", 23 | "\tset the rows of the edited gui to 3 # Sets the number of rows to 3 (if possible)", 24 | "\tset the shape of the edited gui to \"xxxxxxxxx\", \"x-------x\", and \"xxxxxxxxx\"", 25 | "\tset the lock status of the edited gui to false # Players can take items from this GUI now" 26 | }) 27 | @Since("1.0.0, 1.3 (rework, support outside of edit sections)") 28 | public class ExprGUIProperties extends SimplePropertyExpression { 29 | 30 | static { 31 | register(ExprGUIProperties.class, Object.class, "(0¦[skript-gui] name[s]|1¦(size[s]|rows)|2¦shape[s]|3¦lock status[es])", "guiinventorys"); 32 | } 33 | 34 | private static final int NAME = 0, ROWS = 1, SHAPE = 2, LOCK_STATUS = 3; 35 | private int property; 36 | 37 | @Override 38 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 39 | property = parseResult.mark; 40 | return super.init(exprs, matchedPattern, isDelayed, parseResult); 41 | } 42 | 43 | @Override 44 | @Nullable 45 | public Object convert(GUI gui) { 46 | switch (property) { 47 | case NAME: 48 | return gui.getName(); 49 | case ROWS: 50 | return gui.getInventory().getSize() / 9; // We return rows 51 | case SHAPE: 52 | return gui.getRawShape(); 53 | case LOCK_STATUS: 54 | return !gui.isRemovable(); // Not removable = locked 55 | } 56 | return null; 57 | } 58 | 59 | @Override 60 | @Nullable 61 | public Class[] acceptChange(ChangeMode mode) { 62 | if (mode == ChangeMode.SET || mode == ChangeMode.RESET) { 63 | switch (property) { 64 | case NAME: 65 | return CollectionUtils.array(String.class); 66 | case ROWS: 67 | return CollectionUtils.array(Number.class); 68 | case SHAPE: 69 | return CollectionUtils.array(String[].class); 70 | case LOCK_STATUS: 71 | return CollectionUtils.array(Boolean.class); 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | @Override 78 | public void change(Event e, Object @Nullable [] delta, ChangeMode mode) { 79 | if (delta == null || (mode != ChangeMode.SET && mode != ChangeMode.RESET)) { 80 | return; 81 | } 82 | GUI gui = SkriptGUI.getGUIManager().getGUI(e); 83 | if (gui != null) { 84 | switch (mode) { 85 | case SET: 86 | switch (property) { 87 | case NAME: 88 | gui.setName((String) delta[0]); 89 | break; 90 | case ROWS: 91 | gui.setSize(((Number) delta[0]).intValue() * 9); 92 | break; 93 | case SHAPE: 94 | String[] newShape = new String[delta.length]; 95 | for (int i = 0; i < delta.length; i++) { 96 | if (!(delta[i] instanceof String)) { 97 | return; 98 | } 99 | newShape[i] = (String) delta[i]; 100 | } 101 | gui.setShape(newShape); 102 | break; 103 | case LOCK_STATUS: 104 | gui.setRemovable(!(boolean) delta[0]); 105 | break; 106 | } 107 | break; 108 | case RESET: 109 | switch (property) { 110 | case NAME: 111 | gui.setName(gui.getInventory().getType().getDefaultTitle()); 112 | break; 113 | case ROWS: 114 | gui.setSize(gui.getInventory().getType().getDefaultSize()); 115 | break; 116 | case SHAPE: 117 | gui.resetShape(); 118 | break; 119 | case LOCK_STATUS: 120 | gui.setRemovable(false); 121 | break; 122 | } 123 | break; 124 | default: 125 | assert false; 126 | } 127 | } 128 | } 129 | 130 | @Override 131 | public Class getReturnType() { 132 | switch (property) { 133 | case NAME: 134 | case SHAPE: 135 | return String.class; 136 | case ROWS: 137 | return Number.class; 138 | case LOCK_STATUS: 139 | return Boolean.class; 140 | default: 141 | return Object.class; 142 | } 143 | } 144 | 145 | @Override 146 | protected String getPropertyName() { 147 | switch (property) { 148 | case NAME: 149 | return "name"; 150 | case ROWS: 151 | return "size"; 152 | case SHAPE: 153 | return "shape"; 154 | case LOCK_STATUS: 155 | return "lock status"; 156 | default: 157 | return "property"; 158 | } 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprGUIValues.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.aliases.ItemType; 5 | import ch.njol.skript.classes.Changer.ChangeMode; 6 | import ch.njol.skript.doc.Description; 7 | import ch.njol.skript.doc.Examples; 8 | import ch.njol.skript.doc.Name; 9 | import ch.njol.skript.doc.Since; 10 | import ch.njol.skript.lang.Expression; 11 | import ch.njol.skript.lang.ExpressionType; 12 | import ch.njol.skript.lang.SectionSkriptEvent; 13 | import ch.njol.skript.lang.SkriptEvent; 14 | import ch.njol.skript.lang.SkriptParser.ParseResult; 15 | import ch.njol.skript.lang.util.SimpleExpression; 16 | import ch.njol.util.Kleenean; 17 | import ch.njol.util.coll.CollectionUtils; 18 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 19 | import io.github.apickledwalrus.skriptgui.elements.sections.SecCreateGUI; 20 | import io.github.apickledwalrus.skriptgui.elements.sections.SecGUIOpenClose; 21 | import io.github.apickledwalrus.skriptgui.elements.sections.SecMakeGUI; 22 | import io.github.apickledwalrus.skriptgui.gui.GUI; 23 | import org.bukkit.entity.HumanEntity; 24 | import org.bukkit.event.Event; 25 | import org.bukkit.event.inventory.ClickType; 26 | import org.bukkit.event.inventory.InventoryAction; 27 | import org.bukkit.event.inventory.InventoryClickEvent; 28 | import org.bukkit.event.inventory.InventoryCloseEvent; 29 | import org.bukkit.event.inventory.InventoryEvent; 30 | import org.bukkit.event.inventory.InventoryOpenEvent; 31 | import org.bukkit.event.inventory.InventoryType.SlotType; 32 | import org.bukkit.inventory.Inventory; 33 | import org.bukkit.inventory.ItemStack; 34 | import org.eclipse.jdt.annotation.Nullable; 35 | 36 | @Name("GUI Values") 37 | @Description("Different utility values for a GUI. Some are available in vanilla Skript. Not all values are available for the GUI close section.") 38 | @Examples({ 39 | "create a gui with virtual chest inventory:", 40 | "\tmake gui 10 with water bucket:", 41 | "\t\tset the gui item to lava bucket" 42 | }) 43 | @Since("1.0.0") 44 | public class ExprGUIValues extends SimpleExpression { 45 | 46 | static { 47 | Skript.registerExpression(ExprGUIValues.class, Object.class, ExpressionType.SIMPLE, 48 | "[the] gui slot", 49 | "[the] gui raw slot", 50 | "[the] gui hotbar slot", 51 | "[the] gui inventory", 52 | "[the] gui inventory action", 53 | "[the] gui click (type|action)", 54 | "[the] gui cursor [item]", 55 | "[the] gui [(clicked|current)] item", 56 | "[the] gui slot type", 57 | "[the] gui player", 58 | "[the] gui (viewer|player)s", 59 | "[the] gui slot id", 60 | "[the] gui" 61 | ); 62 | } 63 | 64 | private int pattern; 65 | private boolean isDelayed; 66 | // Whether the expression is being used in an open/close section 67 | private boolean openClose; 68 | 69 | private String toString = "gui values"; 70 | 71 | @Override 72 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 73 | SkriptEvent skriptEvent = getParser().getCurrentSkriptEvent(); 74 | if (!(matchedPattern == 12 && getParser().isCurrentSection(SecCreateGUI.class)) && !(skriptEvent instanceof SectionSkriptEvent && ((SectionSkriptEvent) skriptEvent).isSection(SecMakeGUI.class, SecGUIOpenClose.class))) { 75 | Skript.error("You can't use '" + parseResult.expr + "' outside of a GUI make or open/close section."); 76 | return false; 77 | } 78 | 79 | openClose = skriptEvent instanceof SectionSkriptEvent && ((SectionSkriptEvent) skriptEvent).isSection(SecGUIOpenClose.class); 80 | 81 | pattern = matchedPattern; 82 | if (openClose && matchedPattern != 3 && matchedPattern != 9 && matchedPattern != 10 && matchedPattern != 12) { 83 | Skript.error("You can't use '" + parseResult.expr + "' in a GUI open/close section."); 84 | return false; 85 | } 86 | 87 | this.isDelayed = !isDelayed.isFalse(); // TRUE or UNKNOWN 88 | toString = parseResult.expr; 89 | 90 | return true; 91 | } 92 | 93 | @Override 94 | protected Object[] get(Event event) { 95 | if (pattern == 12) { 96 | GUI gui = SkriptGUI.getGUIManager().getGUI(event); 97 | return gui != null ? new GUI[]{gui} : new GUI[0]; 98 | } 99 | 100 | if (openClose) { 101 | InventoryEvent e = (InventoryEvent) event; 102 | switch (pattern) { 103 | case 3: 104 | return new Inventory[]{e.getInventory()}; 105 | case 9: 106 | // Ugly but oh well 107 | return new HumanEntity[]{(event instanceof InventoryCloseEvent ? ((InventoryCloseEvent) e).getPlayer() : ((InventoryOpenEvent) e).getPlayer())}; 108 | case 10: 109 | return (e.getViewers().toArray(new HumanEntity[0])); 110 | } 111 | } else { 112 | InventoryClickEvent e = (InventoryClickEvent) event; 113 | switch (pattern) { 114 | case 0: 115 | return new Number[]{e.getSlot()}; 116 | case 1: 117 | return new Number[]{e.getRawSlot()}; 118 | case 2: 119 | return new Number[]{e.getHotbarButton()}; 120 | case 3: 121 | Inventory clicked = e.getClickedInventory(); 122 | return clicked != null ? new Inventory[]{clicked} : new Inventory[0]; 123 | case 4: 124 | return new InventoryAction[]{e.getAction()}; 125 | case 5: 126 | return new ClickType[]{e.getClick()}; 127 | case 6: 128 | ItemStack cursor = e.getCursor(); 129 | return cursor != null ? new ItemType[]{new ItemType(cursor)} : new ItemType[0]; 130 | case 7: 131 | ItemStack currentItem = e.getCurrentItem(); 132 | return currentItem != null ? new ItemType[]{new ItemType(currentItem)} : new ItemType[0]; 133 | case 8: 134 | return new SlotType[]{e.getSlotType()}; 135 | case 9: 136 | return new HumanEntity[]{e.getWhoClicked()}; 137 | case 10: 138 | return e.getViewers().toArray(new HumanEntity[0]); 139 | case 11: 140 | GUI gui = SkriptGUI.getGUIManager().getGUI(event); 141 | return gui != null ? new String[]{"" + gui.convert(e.getSlot())} : new GUI[0]; 142 | } 143 | } 144 | return new Object[0]; 145 | } 146 | 147 | @Override 148 | @Nullable 149 | public Class[] acceptChange(ChangeMode mode) { 150 | if (isDelayed) { 151 | Skript.error("You can't set the '" + toString + "' when the event is already passed."); 152 | return null; 153 | } 154 | 155 | if (mode == ChangeMode.SET && pattern == 7) { 156 | return CollectionUtils.array(ItemType.class); 157 | } 158 | 159 | return null; 160 | } 161 | 162 | @Override 163 | public void change(Event event, Object @Nullable [] delta, ChangeMode mode) { 164 | if (delta == null || !(event instanceof InventoryClickEvent)) { 165 | return; 166 | } 167 | ((InventoryClickEvent) event).setCurrentItem(((ItemType) delta[0]).getRandom()); 168 | } 169 | 170 | @Override 171 | public boolean isSingle() { 172 | return pattern != 10; 173 | } 174 | 175 | @Override 176 | public Class getReturnType() { 177 | switch (pattern) { 178 | case 0: 179 | case 1: 180 | case 2: 181 | return Number.class; 182 | case 3: 183 | return Inventory.class; 184 | case 4: 185 | return InventoryAction.class; 186 | case 5: 187 | return ClickType.class; 188 | case 6: 189 | case 7: 190 | return ItemType.class; 191 | case 8: 192 | return SlotType.class; 193 | case 9: 194 | case 10: 195 | return HumanEntity.class; 196 | case 11: 197 | return String.class; 198 | case 12: 199 | return GUI.class; 200 | default: 201 | return Object.class; 202 | } 203 | } 204 | 205 | @Override 206 | public String toString(@Nullable Event e, boolean debug) { 207 | return toString; 208 | } 209 | 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprGUIs.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.lang.Expression; 5 | import ch.njol.skript.lang.ExpressionType; 6 | import ch.njol.skript.lang.SkriptParser.ParseResult; 7 | import ch.njol.skript.lang.util.SimpleExpression; 8 | import ch.njol.util.Kleenean; 9 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 10 | import io.github.apickledwalrus.skriptgui.gui.GUI; 11 | import org.bukkit.event.Event; 12 | import org.eclipse.jdt.annotation.Nullable; 13 | 14 | public class ExprGUIs extends SimpleExpression { 15 | 16 | static { 17 | Skript.registerExpression(ExprGUIs.class, GUI.class, ExpressionType.SIMPLE, "all guis"); 18 | } 19 | 20 | @Override 21 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 22 | return true; 23 | } 24 | 25 | @Override 26 | @Nullable 27 | protected GUI[] get(Event e) { 28 | return SkriptGUI.getGUIManager().getTrackedGUIs().toArray(new GUI[0]); 29 | } 30 | 31 | @Override 32 | public boolean isSingle() { 33 | return false; 34 | } 35 | 36 | @Override 37 | public Class getReturnType() { 38 | return GUI.class; 39 | } 40 | 41 | @Override 42 | public String toString(@Nullable Event e, boolean debug) { 43 | return "all guis"; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprIDOfGUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.classes.Changer.ChangeMode; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.expressions.base.SimplePropertyExpression; 9 | import ch.njol.util.coll.CollectionUtils; 10 | import io.github.apickledwalrus.skriptgui.gui.GUI; 11 | import org.bukkit.event.Event; 12 | import org.eclipse.jdt.annotation.Nullable; 13 | 14 | @Name("ID of GUI") 15 | @Description({ 16 | "An expression that returns the ID of a GUI if the GUI is a global GUI.", 17 | "This expression may be used to change the ID of a global GUI.", 18 | "It may also be used to register a GUI as a global GUI." 19 | }) 20 | @Examples({ 21 | "send \"%id of {gui}%\" to player", 22 | "set id of {gui} to \"new id\"", 23 | }) 24 | @Since("1.3") 25 | public class ExprIDOfGUI extends SimplePropertyExpression { 26 | 27 | static { 28 | register(ExprIDOfGUI.class, String.class, "id[entifier]", "guiinventorys"); 29 | } 30 | 31 | @Override 32 | @Nullable 33 | public String convert(GUI gui) { 34 | return gui.getID(); 35 | } 36 | 37 | @Override 38 | @Nullable 39 | public Class[] acceptChange(ChangeMode mode) { 40 | return mode == ChangeMode.SET ? CollectionUtils.array(String.class) : null; 41 | } 42 | 43 | @Override 44 | public void change(Event e, Object @Nullable [] delta, ChangeMode mode) { 45 | if (delta == null || delta[0] == null) { 46 | return; 47 | } 48 | String id = (String) delta[0]; 49 | GUI[] guis = getExpr().getArray(e); 50 | for (GUI gui : guis) { 51 | if (gui != null) { 52 | gui.setID(id); 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public Class getReturnType() { 59 | return String.class; 60 | } 61 | 62 | @Override 63 | protected String getPropertyName() { 64 | return "id"; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprLastGUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import org.bukkit.event.Event; 4 | 5 | import ch.njol.skript.Skript; 6 | import ch.njol.skript.classes.Changer.ChangeMode; 7 | import ch.njol.skript.doc.Description; 8 | import ch.njol.skript.doc.Examples; 9 | import ch.njol.skript.doc.Name; 10 | import ch.njol.skript.doc.Since; 11 | import ch.njol.skript.lang.Expression; 12 | import ch.njol.skript.lang.ExpressionType; 13 | import ch.njol.skript.lang.SkriptParser.ParseResult; 14 | import ch.njol.skript.lang.util.SimpleExpression; 15 | import ch.njol.util.Kleenean; 16 | import ch.njol.util.coll.CollectionUtils; 17 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 18 | import io.github.apickledwalrus.skriptgui.gui.GUI; 19 | import org.eclipse.jdt.annotation.Nullable; 20 | 21 | @Name("Last GUI/GUI from ID") 22 | @Description("It is used to return the last created/edited gui or a gui from a string id.") 23 | @Examples({ 24 | "open the created gui for player", 25 | "open the gui with the id \"globalGUI\" for player" 26 | }) 27 | @Since("1.0.0") 28 | public class ExprLastGUI extends SimpleExpression { 29 | 30 | static { 31 | Skript.registerExpression(ExprLastGUI.class, GUI.class, ExpressionType.SIMPLE, 32 | "[the] (last[ly] [(created|edited)]|(created|edited)) gui", 33 | "[the] gui [with [the] id[entifier]] %string%" 34 | ); 35 | } 36 | 37 | @Nullable 38 | private Expression id; 39 | 40 | @Override 41 | @SuppressWarnings("unchecked") 42 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult) { 43 | if (matchedPattern == 1) { 44 | id = (Expression) exprs[0]; 45 | } 46 | return true; 47 | } 48 | 49 | @Override 50 | protected GUI[] get(Event e) { 51 | if (id != null) { 52 | String id = this.id.getSingle(e); 53 | return id != null ? new GUI[]{SkriptGUI.getGUIManager().getGUI(id)} : new GUI[0]; 54 | } 55 | return new GUI[]{SkriptGUI.getGUIManager().getGUI(e)}; 56 | } 57 | 58 | @Override 59 | @Nullable 60 | public Class[] acceptChange(ChangeMode mode) { 61 | if (mode == ChangeMode.DELETE && id != null) { 62 | return CollectionUtils.array(Object.class); 63 | } 64 | return null; 65 | } 66 | 67 | @Override 68 | public void change(Event e, Object @Nullable [] delta, ChangeMode mode) { 69 | if (id != null) { 70 | String id = this.id.getSingle(e); 71 | if (id != null) { 72 | GUI gui = SkriptGUI.getGUIManager().getGUI(id); 73 | if (gui != null) { 74 | gui.setID(null); 75 | } 76 | } 77 | } 78 | } 79 | 80 | @Override 81 | public boolean isSingle() { 82 | return true; 83 | } 84 | 85 | @Override 86 | public Class getReturnType() { 87 | return GUI.class; 88 | } 89 | 90 | @Override 91 | public String toString(@Nullable Event e, boolean debug) { 92 | return id == null ? "the last gui" : "the gui with the id " + id.toString(e, debug); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprNextGUISlot.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SectionSkriptEvent; 11 | import ch.njol.skript.lang.SkriptEvent; 12 | import ch.njol.skript.lang.SkriptParser.ParseResult; 13 | import ch.njol.skript.lang.util.SimpleExpression; 14 | import ch.njol.util.Kleenean; 15 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 16 | import io.github.apickledwalrus.skriptgui.elements.sections.SecCreateGUI; 17 | import io.github.apickledwalrus.skriptgui.elements.sections.SecGUIOpenClose; 18 | import io.github.apickledwalrus.skriptgui.elements.sections.SecMakeGUI; 19 | import io.github.apickledwalrus.skriptgui.gui.GUI; 20 | import org.bukkit.event.Event; 21 | import org.eclipse.jdt.annotation.Nullable; 22 | 23 | @Name("Next GUI Slot") 24 | @Description("An expression that returns the number/character of the next open slot in a GUI.") 25 | @Examples("make the next gui slot with dirt named \"Slot: %the next gui slot%\"") 26 | @Since("1.3") 27 | public class ExprNextGUISlot extends SimpleExpression { 28 | 29 | @Nullable 30 | private Expression guis; 31 | 32 | static { 33 | Skript.registerExpression(ExprNextGUISlot.class, Character.class, ExpressionType.SIMPLE, 34 | "%guiinventorys%'[s] next gui slot[s]", 35 | "[the] next gui slot[s] of %guiinventorys%", 36 | "[the] next gui slot" 37 | ); 38 | } 39 | 40 | @Override 41 | @SuppressWarnings("unchecked") 42 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 43 | if (matchedPattern == 2) { 44 | SkriptEvent skriptEvent = getParser().getCurrentSkriptEvent(); 45 | if (!getParser().isCurrentSection(SecCreateGUI.class) && !(skriptEvent instanceof SectionSkriptEvent && ((SectionSkriptEvent) skriptEvent).isSection(SecCreateGUI.class, SecMakeGUI.class, SecGUIOpenClose.class))) { 46 | Skript.error("The 'next gui slot' expression must have a GUI specified unless it is used in a GUI section."); 47 | return false; 48 | } 49 | guis = null; 50 | } else { 51 | guis = (Expression) exprs[0]; 52 | } 53 | return true; 54 | } 55 | 56 | @Override 57 | @Nullable 58 | protected Character[] get(Event e) { 59 | if (guis == null) { 60 | GUI gui = SkriptGUI.getGUIManager().getGUI(e); 61 | if (gui != null) { 62 | return new Character[]{gui.nextSlot()}; 63 | } 64 | } 65 | 66 | GUI[] guis = this.guis.getArray(e); 67 | int size = guis.length; 68 | Character[] slots = new Character[size]; 69 | for (int i = 0; i < size; i++) { 70 | slots[i] = guis[i].nextSlot(); 71 | } 72 | return slots; 73 | } 74 | 75 | @Override 76 | public boolean isSingle() { 77 | return guis != null && guis.isSingle(); 78 | } 79 | 80 | @Override 81 | public Class getReturnType() { 82 | return Character.class; 83 | } 84 | 85 | @Override 86 | public String toString(@Nullable Event e, boolean debug) { 87 | if (guis != null) { 88 | return "the next gui slot" + (guis.isSingle() ? "" : "s") + " of " + guis.toString(e, debug); 89 | } else { 90 | return "the next gui slot"; 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprPaginatedList.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.skript.lang.UnparsedLiteral; 12 | import ch.njol.skript.lang.util.SimpleExpression; 13 | import ch.njol.util.Kleenean; 14 | import org.bukkit.event.Event; 15 | import org.eclipse.jdt.annotation.Nullable; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | @Name("Paginated List") 21 | @Description("Returns the \"pages\" of a list based on the given number of lines per page.") 22 | @Examples({"# The SECOND set of 36 items in the \"guiItems\" list. This represents the elements from indexes 37 to 72", 23 | "set {_guiPage2::*} to page 2 of {_guiItems::*} with 36 lines"}) 24 | @Since("1.1.0") 25 | public class ExprPaginatedList extends SimpleExpression { 26 | 27 | static { 28 | Skript.registerExpression(ExprPaginatedList.class, Object.class, ExpressionType.SIMPLE, 29 | "page[s] %numbers% of %objects% with %number% lines" 30 | ); 31 | } 32 | 33 | @SuppressWarnings("NotNullFieldNotInitialized") 34 | private Expression pages; 35 | @Nullable 36 | private Expression contents; 37 | @SuppressWarnings("NotNullFieldNotInitialized") 38 | private Expression lines; 39 | 40 | @Override 41 | @SuppressWarnings("unchecked") 42 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { 43 | pages = (Expression) exprs[0]; 44 | contents = exprs[1]; 45 | if (contents instanceof UnparsedLiteral) { 46 | contents = contents.getConvertedExpression(Object.class); 47 | if (contents == null) { 48 | return false; 49 | } 50 | } 51 | lines = (Expression) exprs[2]; 52 | return true; 53 | } 54 | 55 | @Override 56 | protected Object[] get(Event e) { 57 | Number[] pages = this.pages.getArray(e); 58 | Number l = this.lines.getSingle(e); 59 | int lines; 60 | if (l == null || (lines = l.intValue()) < 1 || pages.length == 0) { 61 | return new Object[0]; 62 | } 63 | 64 | assert contents != null; 65 | Object[] contents = this.contents.getAll(e).clone(); 66 | if (contents.length == 0) { 67 | return new Object[0]; 68 | } 69 | 70 | List paginatedList = new ArrayList<>(); 71 | for (Number p : pages) { 72 | int page = p.intValue(); 73 | if (page < 1) { 74 | continue; 75 | } else if (page > 1) { 76 | page = (page - 1) * lines; 77 | } else { 78 | page = 0; 79 | } 80 | 81 | int max = page + lines; 82 | if (max > contents.length) { 83 | max = contents.length; 84 | } 85 | 86 | for (int i = page; i < max; i++) { 87 | if (contents[i] != null) { 88 | paginatedList.add(contents[i]); 89 | } 90 | } 91 | } 92 | 93 | return paginatedList.toArray(); 94 | } 95 | 96 | @Override 97 | public boolean isSingle() { 98 | return false; 99 | } 100 | 101 | @Override 102 | public Class getReturnType() { 103 | return Object.class; 104 | } 105 | 106 | @Override 107 | public String toString(@Nullable Event e, boolean debug) { 108 | assert contents != null; 109 | return "page(s) " + pages.toString(e, debug) + " of " + contents.toString(e, debug) + " with " + lines.toString(e, debug) + " lines"; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/ExprVirtualInventory.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.expressions; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.doc.Description; 5 | import ch.njol.skript.doc.Examples; 6 | import ch.njol.skript.doc.Name; 7 | import ch.njol.skript.doc.Since; 8 | import ch.njol.skript.lang.Expression; 9 | import ch.njol.skript.lang.ExpressionType; 10 | import ch.njol.skript.lang.SkriptParser.ParseResult; 11 | import ch.njol.skript.lang.util.SimpleExpression; 12 | import ch.njol.util.Kleenean; 13 | import org.bukkit.Bukkit; 14 | import org.bukkit.event.Event; 15 | import org.bukkit.event.inventory.InventoryType; 16 | import org.bukkit.inventory.Inventory; 17 | import org.eclipse.jdt.annotation.Nullable; 18 | 19 | @Name("Virtual Inventory") 20 | @Description("An expression to create inventories that can be used with GUIs.") 21 | @Examples("create a gui with virtual chest inventory with 3 rows named \"My GUI\"") 22 | @Since("1.0.0") 23 | public class ExprVirtualInventory extends SimpleExpression{ 24 | 25 | static { 26 | Skript.registerExpression(ExprVirtualInventory.class, Inventory.class, ExpressionType.SIMPLE, 27 | "virtual (1¦(crafting [table]|workbench)|2¦chest|3¦anvil|4¦hopper|5¦dropper|6¦dispenser|%-inventorytype%) [with size %-number%] [(named|with (name|title)) %-string%]", 28 | "virtual (1¦(crafting [table]|workbench)|2¦chest|3¦anvil|4¦hopper|5¦dropper|6¦dispenser|%-inventorytype%) [with %-number% row[s]] [(named|with (name|title)) %-string%]", 29 | "virtual (1¦(crafting [table]|workbench)|2¦chest|3¦anvil|4¦hopper|5¦dropper|6¦dispenser|%-inventorytype%) [(named|with (name|title)) %-string%] with size %-number%", 30 | "virtual (1¦(crafting [table]|workbench)|2¦chest|3¦anvil|4¦hopper|5¦dropper|6¦dispenser|%-inventorytype%) [(named|with (name|title)) %-string%] with %-number% row[s]" 31 | ); 32 | } 33 | 34 | @Nullable 35 | private InventoryType specifiedType; 36 | @Nullable 37 | private Expression inventoryType; 38 | @Nullable 39 | private Expression rows; 40 | @Nullable 41 | private Expression name; 42 | 43 | // The name of this inventory. 44 | @Nullable 45 | private String invName; 46 | 47 | @Override 48 | @SuppressWarnings("unchecked") 49 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult) { 50 | inventoryType = (Expression) exprs[0]; 51 | if (inventoryType == null) { // They must be using a specific one 52 | switch (parseResult.mark) { 53 | case 1: 54 | specifiedType = InventoryType.WORKBENCH; 55 | break; 56 | case 2: 57 | specifiedType = InventoryType.CHEST; 58 | break; 59 | case 3: 60 | specifiedType = InventoryType.ANVIL; 61 | break; 62 | case 4: 63 | specifiedType = InventoryType.HOPPER; 64 | break; 65 | case 5: 66 | specifiedType = InventoryType.DROPPER; 67 | break; 68 | case 6: 69 | specifiedType = InventoryType.DISPENSER; 70 | break; 71 | } 72 | } 73 | 74 | if (matchedPattern > 1) { 75 | name = (Expression) exprs[1]; 76 | rows = (Expression) exprs[2]; 77 | } else { 78 | name = (Expression) exprs[2]; 79 | rows = (Expression) exprs[1]; 80 | } 81 | 82 | return true; 83 | } 84 | 85 | @Override 86 | protected Inventory[] get(Event e) { 87 | InventoryType type = inventoryType != null ? inventoryType.getSingle(e) : specifiedType; 88 | if (type == null) { 89 | return new Inventory[0]; 90 | } else if (type == InventoryType.CRAFTING) { // Make it a valid inventory. It's not the same, but it's likely what the user wants. 91 | type = InventoryType.WORKBENCH; 92 | } 93 | 94 | String name = this.name != null ? this.name.getSingle(e) : null; 95 | invName = name != null ? name : type.getDefaultTitle(); 96 | 97 | Inventory inventory; 98 | if (type == InventoryType.CHEST) { 99 | int size = -1; 100 | if (rows != null) { 101 | Number rows = this.rows.getSingle(e); 102 | if (rows != null) { 103 | size = rows.intValue(); 104 | if (size <= 6) { 105 | size *= 9; 106 | } 107 | } 108 | } 109 | if (size < 9 || size > 54 || size % 9 != 0) { // Invalid inventory size 110 | size = type.getDefaultSize(); 111 | } 112 | inventory = Bukkit.getServer().createInventory(null, size, invName); 113 | } else { 114 | inventory = Bukkit.getServer().createInventory(null, type, invName); 115 | } 116 | 117 | return new Inventory[]{inventory}; 118 | } 119 | 120 | @Override 121 | public boolean isSingle() { 122 | return true; 123 | } 124 | 125 | @Override 126 | public Class getReturnType() { 127 | return Inventory.class; 128 | } 129 | 130 | @Override 131 | public String toString(@Nullable Event e, boolean debug) { 132 | return "virtual " + (inventoryType != null ? inventoryType.toString(e, debug) : specifiedType != null ? specifiedType.name().toLowerCase() : "unknown inventory type") 133 | + (name != null ? " with name" + name.toString(e, debug) : "") 134 | + (rows != null ? " with " + rows.toString(e, debug) + " rows" : ""); 135 | } 136 | 137 | /** 138 | * @return The name of this inventory. If {@link #invName} is null 139 | * when this method is called, an empty string will be returned. 140 | */ 141 | public String getName() { 142 | return invName != null ? invName : ""; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/expressions/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) 2 | package io.github.apickledwalrus.skriptgui.elements.expressions; 3 | 4 | import org.eclipse.jdt.annotation.DefaultLocation; 5 | import org.eclipse.jdt.annotation.NonNullByDefault; 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/sections/SecCreateGUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.sections; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.config.SectionNode; 5 | import ch.njol.skript.doc.Description; 6 | import ch.njol.skript.doc.Examples; 7 | import ch.njol.skript.doc.Name; 8 | import ch.njol.skript.doc.Since; 9 | import ch.njol.skript.lang.EffectSection; 10 | import ch.njol.skript.lang.Expression; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.TriggerItem; 13 | import ch.njol.util.Kleenean; 14 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 15 | import io.github.apickledwalrus.skriptgui.elements.expressions.ExprVirtualInventory; 16 | import io.github.apickledwalrus.skriptgui.gui.GUI; 17 | import org.bukkit.event.Event; 18 | import org.bukkit.event.inventory.InventoryType; 19 | import org.bukkit.inventory.Inventory; 20 | import org.eclipse.jdt.annotation.Nullable; 21 | 22 | import java.util.List; 23 | 24 | @Name("Create / Edit GUI") 25 | @Description("The base of creating and editing GUIs.") 26 | @Examples({ 27 | "create a gui with virtual chest inventory with 3 rows named \"My GUI\"", 28 | "edit gui last gui:", 29 | "\tset the gui-inventory-name to \"New GUI Name!\"", 30 | }) 31 | @Since("1.0.0") 32 | public class SecCreateGUI extends EffectSection { 33 | 34 | static { 35 | Skript.registerSection(SecCreateGUI.class, 36 | "create [a] [new] gui [[with id[entifier]] %-string%] with %inventory% [removable:(and|with) ([re]move[e]able|stealable) items] [(and|with) shape %-strings%]", 37 | "(change|edit) [gui] %guiinventory%" 38 | ); 39 | } 40 | 41 | private boolean inception; 42 | 43 | @SuppressWarnings("NotNullFieldNotInitialized") 44 | private Expression inv; 45 | @Nullable 46 | private Expression shape, id; 47 | private boolean removableItems; 48 | 49 | @Nullable 50 | private Expression gui; 51 | 52 | @Override 53 | @SuppressWarnings("unchecked") 54 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult, @Nullable SectionNode sectionNode, @Nullable List triggerItems) { 55 | if (matchedPattern == 1) { 56 | if (!hasSection()) { 57 | Skript.error("You can't edit a gui inventory using an empty section, you need to change at least a slot or a property."); 58 | return false; 59 | } 60 | gui = (Expression) exprs[0]; 61 | } else { 62 | id = (Expression) exprs[0]; 63 | inv = (Expression) exprs[1]; 64 | shape = (Expression) exprs[2]; 65 | removableItems = parseResult.hasTag("removable"); 66 | } 67 | 68 | inception = getParser().isCurrentSection(SecCreateGUI.class); 69 | 70 | if (hasSection()) { 71 | assert sectionNode != null; 72 | loadOptionalCode(sectionNode); 73 | } 74 | 75 | return true; 76 | } 77 | 78 | @Override 79 | @Nullable 80 | public TriggerItem walk(Event e) { 81 | GUI gui; 82 | if (this.gui == null) { // Creating a new GUI. 83 | Inventory inv = this.inv.getSingle(e); 84 | if (inv == null) // Don't run the section if the GUI can't be created 85 | return walk(e, false); 86 | 87 | InventoryType invType = inv.getType(); 88 | if (invType == InventoryType.CRAFTING || invType == InventoryType.PLAYER) { // We don't want to run this section as this is an invalid GUI type 89 | SkriptGUI.getInstance().getLogger().warning("Unable to create an inventory of type: " + invType.name()); 90 | return walk(e, false); 91 | } 92 | 93 | if (this.inv instanceof ExprVirtualInventory) { // Try to set the name 94 | gui = new GUI(inv, removableItems, ((ExprVirtualInventory) this.inv).getName()); 95 | } else { 96 | gui = new GUI(inv, removableItems, null); 97 | } 98 | 99 | if (shape == null) { 100 | gui.resetShape(); 101 | } else { 102 | gui.setShape(shape.getArray(e)); 103 | } 104 | 105 | String id = this.id != null ? this.id.getSingle(e) : null; 106 | if (id != null && !id.isEmpty()) { 107 | GUI old = SkriptGUI.getGUIManager().getGUI(id); 108 | if (old != null) { // We are making a new GUI with this ID (see https://github.com/APickledWalrus/skript-gui/issues/72) 109 | SkriptGUI.getGUIManager().unregister(old); 110 | } 111 | gui.setID(id); 112 | } 113 | 114 | } else { // Editing the given GUI 115 | gui = this.gui.getSingle(e); 116 | } 117 | 118 | if (!inception) { // No sort of inception going on, just do the regular stuff 119 | SkriptGUI.getGUIManager().setGUI(e, gui); 120 | return walk(e, true); 121 | } 122 | 123 | // We need to switch the event GUI for the creation section 124 | GUI currentGUI = SkriptGUI.getGUIManager().getGUI(e); 125 | 126 | if (currentGUI == null) { // No current GUI, treat as normal 127 | SkriptGUI.getGUIManager().setGUI(e, gui); 128 | return walk(e, true); 129 | } 130 | 131 | if (!hasSection()) { // No section to run, we can skip the code below (no code to run with "new" gui) 132 | return walk(e, false); 133 | } 134 | 135 | SkriptGUI.getGUIManager().setGUI(e, gui); 136 | 137 | assert first != null && last != null; 138 | TriggerItem lastNext = last.getNext(); 139 | last.setNext(null); 140 | TriggerItem.walk(first, e); 141 | last.setNext(lastNext); 142 | 143 | // Switch back to the old GUI since we are returning to the previous GUI section 144 | // TODO the downside here is that "open last gui" may not always work as expected! 145 | // Unsurprisingly, creation section inception is annoying! 146 | SkriptGUI.getGUIManager().setGUI(e, currentGUI); 147 | 148 | // Don't run the section, we ran it above if needed 149 | return walk(e, false); 150 | } 151 | 152 | @Override 153 | public String toString(@Nullable Event e, boolean debug) { 154 | if (gui != null) { 155 | return "edit gui " + gui.toString(e, debug); 156 | } else { 157 | StringBuilder creation = new StringBuilder("create a gui"); 158 | if (id != null) { 159 | creation.append(" with id ").append(id.toString(e, debug)); 160 | } 161 | creation.append(" with ").append(inv.toString(e, debug)); 162 | if (removableItems) { 163 | creation.append(" with removable items"); 164 | } 165 | if (shape != null) { 166 | creation.append(" and shape ").append(shape.toString(e, debug)); 167 | } 168 | return creation.toString(); 169 | } 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/sections/SecGUIOpenClose.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.sections; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.config.SectionNode; 5 | import ch.njol.skript.doc.Description; 6 | import ch.njol.skript.doc.Examples; 7 | import ch.njol.skript.doc.Name; 8 | import ch.njol.skript.doc.Since; 9 | import ch.njol.skript.lang.Expression; 10 | import ch.njol.skript.lang.Section; 11 | import ch.njol.skript.lang.SkriptParser.ParseResult; 12 | import ch.njol.skript.lang.Trigger; 13 | import ch.njol.skript.lang.TriggerItem; 14 | import ch.njol.skript.variables.Variables; 15 | import ch.njol.util.Kleenean; 16 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 17 | import io.github.apickledwalrus.skriptgui.gui.GUI; 18 | import org.bukkit.event.Event; 19 | import org.bukkit.event.inventory.InventoryCloseEvent; 20 | import org.bukkit.event.inventory.InventoryOpenEvent; 21 | import org.eclipse.jdt.annotation.Nullable; 22 | 23 | import java.util.List; 24 | 25 | @Name("GUI Open/Close") 26 | @Description("Sections that will run when a user opens or closes the GUI. This section is optional.") 27 | @Examples({ 28 | "create a gui with virtual chest inventory with 3 rows named \"My GUI\"", 29 | "\trun on gui open:", 30 | "\t\tsend \"You just opened this GUI!\" to player", 31 | "\trun on gui close:", 32 | "\t\tsend \"You just closed this GUI!\" to player" 33 | }) 34 | @Since("1.0.0, 1.3 (open section)") 35 | public class SecGUIOpenClose extends Section { 36 | 37 | static { 38 | Skript.registerSection(SecGUIOpenClose.class, 39 | "run (when|while) (open[ing]|1¦clos(e|ing)) [[the] gui]", 40 | "run (when|while) [the] gui (opens|1¦closes)", 41 | "run on gui (open[ing]|1¦clos(e|ing))" 42 | ); 43 | } 44 | 45 | @SuppressWarnings("NotNullFieldNotInitialized") 46 | private Trigger trigger; 47 | 48 | private boolean close; 49 | 50 | @Override 51 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List triggerItems) { 52 | if (!getParser().isCurrentSection(SecCreateGUI.class)) { 53 | Skript.error("GUI open/close sections can only be put within GUI creation or editing sections."); 54 | return false; 55 | } 56 | 57 | close = parseResult.mark == 1; 58 | 59 | if (close) { 60 | trigger = loadCode(sectionNode, "inventory close", InventoryCloseEvent.class); 61 | } else { 62 | trigger = loadCode(sectionNode, "inventory open", InventoryOpenEvent.class); 63 | } 64 | 65 | return true; 66 | } 67 | 68 | @Override 69 | @Nullable 70 | public TriggerItem walk(Event e) { 71 | GUI gui = SkriptGUI.getGUIManager().getGUI(e); 72 | if (gui != null) { 73 | Object variables = Variables.copyLocalVariables(e); 74 | if (close) { 75 | if (variables != null) { 76 | gui.setOnClose(event -> { 77 | Variables.setLocalVariables(event, variables); 78 | trigger.execute(event); 79 | }); 80 | } else { 81 | gui.setOnClose(trigger::execute); 82 | } 83 | } else { 84 | if (variables != null) { 85 | gui.setOnOpen(event -> { 86 | Variables.setLocalVariables(event, variables); 87 | trigger.execute(event); 88 | }); 89 | } else { 90 | gui.setOnOpen(trigger::execute); 91 | } 92 | } 93 | } 94 | 95 | // We don't want to execute this section 96 | return walk(e, false); 97 | } 98 | 99 | @Override 100 | public String toString(@Nullable Event e, boolean debug) { 101 | return "run on gui " + (close ? "close" : "open"); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/sections/SecMakeGUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.elements.sections; 2 | 3 | import ch.njol.skript.Skript; 4 | import ch.njol.skript.aliases.ItemType; 5 | import ch.njol.skript.config.SectionNode; 6 | import ch.njol.skript.doc.Description; 7 | import ch.njol.skript.doc.Examples; 8 | import ch.njol.skript.doc.Name; 9 | import ch.njol.skript.doc.Since; 10 | import ch.njol.skript.lang.EffectSection; 11 | import ch.njol.skript.lang.Expression; 12 | import ch.njol.skript.lang.SectionSkriptEvent; 13 | import ch.njol.skript.lang.SkriptEvent; 14 | import ch.njol.skript.lang.SkriptParser.ParseResult; 15 | import ch.njol.skript.lang.Trigger; 16 | import ch.njol.skript.lang.TriggerItem; 17 | import ch.njol.skript.variables.Variables; 18 | import ch.njol.util.Kleenean; 19 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 20 | import io.github.apickledwalrus.skriptgui.gui.GUI; 21 | import org.bukkit.event.Event; 22 | import org.bukkit.event.inventory.InventoryClickEvent; 23 | import org.bukkit.inventory.ItemStack; 24 | import org.eclipse.jdt.annotation.Nullable; 25 | 26 | import java.util.List; 27 | 28 | @Name("Set GUI Slots") 29 | @Description("Set or clear GUI slots.") 30 | @Examples({"create a gui with virtual chest inventory with 3 rows named \"My GUI\"", 31 | "\tmake next gui with dirt # Formats the next available GUI slot with dirt. Doesn't do anything when clicked on.", 32 | "\tmake gui 10 with water bucket:", 33 | "\t\t#code here is run when the gui slot is clicked", 34 | "\tunformat gui 10 # Removes the GUI item at slot 10", 35 | "\tunformat the next gui # Removes the GUI item at the slot before the next available slot." 36 | }) 37 | @Since("1.0.0, 1.2.0 (making specific slots stealable)") 38 | public class SecMakeGUI extends EffectSection { 39 | 40 | static { 41 | Skript.registerSection(SecMakeGUI.class, 42 | "(make|format) [the] next gui [slot] (with|to) [removable:([re]mov[e]able|stealable)] %itemtype%", 43 | "(make|format) gui [slot[s]] %strings/numbers% (with|to) [removable:([re]mov[e]able|stealable)] %itemtype%", 44 | "(un(make|format)|remove) [the] next gui [slot]", 45 | "(un(make|format)|remove) gui [slot[s]] %strings/numbers%", 46 | "(un(make|format)|remove) all [[of] the] gui [slots]" 47 | ); 48 | } 49 | 50 | @Nullable 51 | private Trigger trigger; 52 | 53 | @Nullable 54 | private Expression slots; // Can be number or a string 55 | @Nullable 56 | private Expression item; 57 | 58 | private int pattern; 59 | private boolean removable; 60 | 61 | @Override 62 | @SuppressWarnings("unchecked") 63 | public boolean init(Expression[] exprs, int matchedPattern, Kleenean kleenean, ParseResult parseResult, @Nullable SectionNode sectionNode, @Nullable List items) { 64 | if (!getParser().isCurrentSection(SecCreateGUI.class)) { 65 | SkriptEvent skriptEvent = getParser().getCurrentSkriptEvent(); 66 | // This check allows users to use a make section in a make section or a open/close section 67 | if (!(skriptEvent instanceof SectionSkriptEvent) || !((SectionSkriptEvent) skriptEvent).isSection(SecMakeGUI.class, SecGUIOpenClose.class)) { 68 | Skript.error("You can't make a GUI slot outside of a GUI creation or editing section."); 69 | return false; 70 | } 71 | } 72 | 73 | pattern = matchedPattern; 74 | if (matchedPattern < 2) { 75 | item = (Expression) exprs[matchedPattern]; 76 | } 77 | if (matchedPattern == 1 || matchedPattern == 3) { 78 | slots = (Expression) exprs[0]; 79 | } 80 | 81 | removable = parseResult.hasTag("removable"); 82 | 83 | if (hasSection()) { 84 | assert sectionNode != null; 85 | trigger = loadCode(sectionNode, "inventory click", InventoryClickEvent.class); 86 | } 87 | 88 | return true; 89 | } 90 | 91 | @Override 92 | @Nullable 93 | public TriggerItem walk(Event e) { 94 | GUI gui = SkriptGUI.getGUIManager().getGUI(e); 95 | 96 | if (gui == null) { // We aren't going to do anything with this section 97 | return walk(e, false); 98 | } 99 | 100 | switch (pattern) { 101 | case 0: // Set the next slot 102 | case 1: // Set the input slots 103 | assert item != null; 104 | ItemType itemType = item.getSingle(e); 105 | if (itemType == null) 106 | break; 107 | ItemStack item = itemType.getRandom(); 108 | if (hasSection()) { 109 | assert trigger != null; 110 | Object variables = Variables.copyLocalVariables(e); 111 | if (variables != null) { 112 | for (Object slot : slots != null ? slots.getArray(e) : new Object[]{gui.nextSlot()}) { 113 | gui.setItem(slot, item, removable, event -> { 114 | Variables.setLocalVariables(event, variables); 115 | trigger.execute(event); 116 | }); 117 | } 118 | } else { // Don't paste variables if there are none to paste 119 | for (Object slot : slots != null ? slots.getArray(e) : new Object[]{gui.nextSlot()}) { 120 | gui.setItem(slot, item, removable, event -> { 121 | trigger.execute(event); 122 | }); 123 | } 124 | } 125 | } else { 126 | for (Object slot : slots != null ? slots.getArray(e) : new Object[]{gui.nextSlot()}) { 127 | gui.setItem(slot, item, removable, null); 128 | } 129 | } 130 | break; 131 | case 2: // Clear the next slot 132 | gui.clear(gui.nextSlotInverted()); 133 | break; 134 | case 3: // Clear the input slots 135 | assert slots != null; 136 | for (Object slot : slots.getArray(e)) { 137 | gui.clear(slot); 138 | } 139 | break; 140 | case 4: // Clear all slots 141 | gui.clear(); 142 | break; 143 | } 144 | 145 | // We don't want to execute this section 146 | return walk(e, false); 147 | } 148 | 149 | @Override 150 | public String toString(@Nullable Event e, boolean debug) { 151 | switch (pattern) { 152 | case 0: 153 | assert item != null; 154 | return "make next gui slot with " + item.toString(e, debug); 155 | case 1: 156 | assert slots != null && item != null; 157 | return "make gui slot(s) " + slots.toString(e, debug) + " with " + item.toString(e, debug); 158 | case 2: 159 | return "remove the next gui slot"; 160 | case 3: 161 | assert slots != null; 162 | return "remove gui slot(s) " + slots.toString(e, debug); 163 | case 4: 164 | return "remove all of the gui slots"; 165 | default: 166 | return "make gui"; 167 | } 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/elements/sections/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) 2 | package io.github.apickledwalrus.skriptgui.elements.sections; 3 | 4 | import org.eclipse.jdt.annotation.DefaultLocation; 5 | import org.eclipse.jdt.annotation.NonNullByDefault; 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/gui/GUI.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.gui; 2 | 3 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 4 | import org.bukkit.Bukkit; 5 | import org.bukkit.Material; 6 | import org.bukkit.entity.HumanEntity; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.event.inventory.InventoryClickEvent; 9 | import org.bukkit.event.inventory.InventoryCloseEvent; 10 | import org.bukkit.event.inventory.InventoryDragEvent; 11 | import org.bukkit.event.inventory.InventoryOpenEvent; 12 | import org.bukkit.event.inventory.InventoryType; 13 | import org.bukkit.inventory.Inventory; 14 | import org.bukkit.inventory.ItemStack; 15 | import org.eclipse.jdt.annotation.Nullable; 16 | 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | import java.util.Map.Entry; 21 | import java.util.function.Consumer; 22 | 23 | public class GUI { 24 | 25 | private Inventory inventory; 26 | private String name; 27 | 28 | private final GUIEventHandler eventHandler = new GUIEventHandler() { 29 | @Override 30 | public void onClick(InventoryClickEvent e) { 31 | if (isPaused() || isPaused((Player) e.getWhoClicked())) { 32 | e.setCancelled(true); // Just in case 33 | return; 34 | } 35 | 36 | SlotData slotData = getSlotData(convert(e.getSlot())); 37 | if (slotData != null) { 38 | // Only cancel if this slot can't be removed AND all items aren't removable 39 | e.setCancelled(!isRemovable(slotData)); 40 | 41 | Consumer runOnClick = slotData.getRunOnClick(); 42 | if (runOnClick != null) { 43 | SkriptGUI.getGUIManager().setGUI(e, GUI.this); 44 | runOnClick.accept(e); 45 | } 46 | } else { // If there is no slot data, cancel if this GUI doesn't have stealable items 47 | e.setCancelled(!isRemovable()); 48 | } 49 | } 50 | 51 | @Override 52 | public void onDrag(InventoryDragEvent e) { 53 | if (isPaused() || isPaused((Player) e.getWhoClicked())) { 54 | e.setCancelled(true); // Just in case 55 | return; 56 | } 57 | 58 | for (int slot : e.getRawSlots()) { 59 | if (!isRemovable(convert(slot))) { 60 | e.setCancelled(true); 61 | break; 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public void onOpen(InventoryOpenEvent e) { 68 | if (isPaused() || isPaused((Player) e.getPlayer())) { 69 | return; 70 | } 71 | 72 | if (onOpen != null) { 73 | SkriptGUI.getGUIManager().setGUI(e, GUI.this); 74 | onOpen.accept(e); 75 | } 76 | } 77 | 78 | @Override 79 | public void onClose(InventoryCloseEvent e) { 80 | if (isPaused() || isPaused((Player) e.getPlayer())) { 81 | return; 82 | } 83 | 84 | if (onClose != null) { 85 | SkriptGUI.getGUIManager().setGUI(e, GUI.this); 86 | onClose.accept(e); 87 | if (closeCancelled) { 88 | Bukkit.getScheduler().runTaskLater(SkriptGUI.getInstance(), () -> { 89 | // Reset behavior (it shouldn't persist) 90 | setCloseCancelled(false); 91 | 92 | Player closer = (Player) e.getPlayer(); 93 | pause(closer); // Avoid calling any open sections 94 | closer.openInventory(inventory); 95 | resume(closer); 96 | }, 1); 97 | return; 98 | } 99 | } 100 | 101 | if (id == null && inventory.getViewers().size() == 1) { // Only stop tracking if it isn't a global GUI 102 | Bukkit.getScheduler().runTaskLater(SkriptGUI.getInstance(), () -> SkriptGUI.getGUIManager().unregister(GUI.this), 1); 103 | } 104 | 105 | // To combat issues like https://github.com/APickledWalrus/skript-gui/issues/60 106 | Bukkit.getScheduler().runTaskLater(SkriptGUI.getInstance(), () -> ((Player) e.getPlayer()).updateInventory(), 1); 107 | } 108 | }; 109 | 110 | private final Map slots = new HashMap<>(); 111 | @Nullable 112 | private String rawShape; 113 | 114 | // Whether all items of this GUI (excluding buttons) can be taken. 115 | private boolean removableItems; 116 | 117 | // To be run when this inventory is opened. 118 | @Nullable 119 | private Consumer onOpen; 120 | // To be run when this inventory is closed. 121 | @Nullable 122 | private Consumer onClose; 123 | // Whether the inventory close event for this event handler is cancelled. 124 | private boolean closeCancelled; 125 | 126 | @Nullable 127 | private String id; 128 | 129 | public GUI(Inventory inventory, boolean stealableItems, @Nullable String name) { 130 | this.inventory = inventory; 131 | this.removableItems = stealableItems; 132 | this.name = name != null ? name : inventory.getType().getDefaultTitle(); 133 | SkriptGUI.getGUIManager().register(this); 134 | } 135 | 136 | public Inventory getInventory() { 137 | return inventory; 138 | } 139 | 140 | public GUIEventHandler getEventHandler() { 141 | return eventHandler; 142 | } 143 | 144 | public void setSize(int size) { 145 | changeInventory(size, getName()); 146 | } 147 | 148 | public String getName() { 149 | return name; 150 | } 151 | 152 | public void setName(@Nullable String name) { 153 | changeInventory(inventory.getSize(), name); 154 | } 155 | 156 | public void clear(Object slot) { 157 | Character realSlot = convert(slot); 158 | setItem(realSlot, new ItemStack(Material.AIR), false, null); 159 | slots.remove(realSlot); 160 | } 161 | 162 | public void clear() { 163 | inventory.clear(); 164 | slots.clear(); 165 | } 166 | 167 | private void changeInventory(int size, @Nullable String name) { 168 | if (name == null) { 169 | name = inventory.getType().getDefaultTitle(); 170 | } else if (size < 9 ) { // Minimum size 171 | size = 9; 172 | } else if (size > 54) { // Maximum size 173 | size = 54; 174 | } 175 | 176 | if (size == inventory.getSize() && name.equals(this.name)) { // Nothing is actually changing 177 | return; 178 | } 179 | 180 | Inventory newInventory; 181 | if (inventory.getType() == InventoryType.CHEST) { 182 | newInventory = Bukkit.getServer().createInventory(null, size, name); 183 | } else { 184 | newInventory = Bukkit.getServer().createInventory(null, inventory.getType(), name); 185 | } 186 | 187 | if (size >= inventory.getSize()) { 188 | newInventory.setContents(inventory.getContents()); 189 | } else { // The inventory is shrinking 190 | for (int slot = 0; slot < size; slot++) { 191 | newInventory.setItem(slot, inventory.getItem(slot)); 192 | } 193 | } 194 | 195 | eventHandler.pause(); // Don't process any events as we transfer data and players 196 | 197 | for (HumanEntity viewer : new ArrayList<>(inventory.getViewers())) { 198 | ItemStack cursor = viewer.getItemOnCursor(); 199 | viewer.setItemOnCursor(null); 200 | viewer.openInventory(newInventory); 201 | viewer.setItemOnCursor(cursor); 202 | } 203 | SkriptGUI.getGUIManager().transferRegistration(this, newInventory); 204 | inventory = newInventory; 205 | this.name = name; 206 | 207 | eventHandler.resume(); // It is safe to resume operations 208 | } 209 | 210 | /** 211 | * @param slot The object to convert to Character form 212 | * @return A Character that is usable in the item and slot maps. 213 | */ 214 | public Character convert(Object slot) { 215 | if (slot instanceof Character) { 216 | return (Character) slot; 217 | } 218 | 219 | if (slot instanceof Number) { 220 | int invSlot = ((Number) slot).intValue(); 221 | // Make sure inventory slot is at least 0 (see https://github.com/APickledWalrus/skript-gui/issues/48) 222 | if (rawShape != null && invSlot >= 0 && invSlot < rawShape.length()) { 223 | return rawShape.charAt(invSlot); 224 | } 225 | return ' '; 226 | } 227 | 228 | if (slot instanceof String && !((String) slot).isEmpty()) { 229 | char strSlot = ((String) slot).charAt(0); 230 | return (rawShape != null && rawShape.contains(Character.toString(strSlot))) ? strSlot : ' '; 231 | } 232 | 233 | return nextSlot(); 234 | } 235 | 236 | /** 237 | * @return The next available slot in this GUI. 238 | */ 239 | public Character nextSlot() { 240 | if (rawShape != null) { 241 | for (char ch : rawShape.toCharArray()) { 242 | if (!slots.containsKey(ch)) { 243 | return ch; 244 | } 245 | } 246 | } 247 | return 0; 248 | } 249 | 250 | /** 251 | * @return The newest slot that has been filled in this GUI. 252 | */ 253 | public Character nextSlotInverted() { 254 | if (rawShape != null) { 255 | for (char ch : rawShape.toCharArray()) { 256 | if (slots.containsKey(ch)) { 257 | return ch; 258 | } 259 | } 260 | } 261 | return 0; 262 | } 263 | 264 | /** 265 | * Sets a slot's item. 266 | * @param slot The slot to put the item in. It will be converted by {@link GUI#convert(Object)}. 267 | * @param item The {@link ItemStack} to put in the slot. 268 | * @param removable Whether this {@link ItemStack} can be removed from its slot. 269 | * @param consumer The {@link Consumer} that the slot will run when clicked. Put as null if the slot should not run anything when clicked. 270 | */ 271 | public void setItem(Object slot, @Nullable ItemStack item, boolean removable, @Nullable Consumer consumer) { 272 | if (rawShape == null) { 273 | SkriptGUI.getInstance().getLogger().warning("Unable to set the item in a gui named '" + getName() + "' as it has a null shape."); 274 | return; 275 | } 276 | 277 | char ch = convert(slot); 278 | if (ch == ' ') { 279 | return; 280 | } 281 | if (ch == '+' && rawShape.contains("+")) { 282 | char ch2 = 'A'; 283 | while (rawShape.indexOf(ch2) >= 0) { 284 | ch2++; 285 | } 286 | rawShape = rawShape.replaceFirst("\\+", "" + ch2); 287 | ch = ch2; 288 | } 289 | 290 | // Although we may be adding null consumers, it lets us track what slots have been set 291 | slots.put(ch, new SlotData(consumer, removable)); 292 | 293 | int i = 0; 294 | for (char ch1 : rawShape.toCharArray()) { 295 | if (ch == ch1 && i < inventory.getSize()) { 296 | inventory.setItem(i, item); 297 | } 298 | i++; 299 | } 300 | } 301 | 302 | /** 303 | * @param slot The slot to get the item from. It will be converted. 304 | * @return The item at this slot, or AIR if the slot has no item, or the slot is not valid for this GUI. 305 | */ 306 | public ItemStack getItem(Object slot) { 307 | if (rawShape == null) { 308 | return new ItemStack(Material.AIR); 309 | } 310 | char ch = convert(slot); 311 | if (ch == 0) { 312 | return new ItemStack(Material.AIR); 313 | } 314 | ItemStack item = inventory.getItem(rawShape.indexOf(ch)); 315 | return item != null ? item : new ItemStack(Material.AIR); 316 | } 317 | 318 | /** 319 | * @return The raw shape of this GUI. May be null if the shape has not yet been initialized. 320 | * @see #setShape(String...) 321 | */ 322 | @Nullable 323 | public String getRawShape() { 324 | return rawShape; 325 | } 326 | 327 | /** 328 | * Resets the shape of this {@link GUI} 329 | */ 330 | public void resetShape() { 331 | int size = 54; // Max inventory size 332 | 333 | String[] shape = new String[size / 9]; 334 | 335 | int position = 0; 336 | StringBuilder sb = new StringBuilder(); 337 | for (char c = 'A'; c < size + 'A'; c++) { // Create the default shape in String form. 338 | sb.append(c); 339 | if (sb.length() == 9) { 340 | shape[position] = sb.toString(); 341 | sb = new StringBuilder(); 342 | position++; 343 | } 344 | } 345 | 346 | setShape(shape); 347 | } 348 | 349 | /** 350 | * Sets the shape of this {@link GUI} 351 | * @param shapes The new shape patterns for this {@link GUI} 352 | * @see GUI#getRawShape() 353 | */ 354 | public void setShape(String... shapes) { 355 | if (shapes.length == 0) { 356 | return; 357 | } 358 | 359 | int size = inventory.getSize(); 360 | 361 | StringBuilder sb = new StringBuilder(); 362 | for (String shape : shapes) { 363 | sb.append(shape); 364 | } 365 | while (sb.length() < size) { // Fill it in if it's too small 366 | sb.append(' '); 367 | } 368 | 369 | String newShape = sb.toString(); 370 | Map movedCharacters = new HashMap<>(); 371 | 372 | if (rawShape != null) { 373 | int pos = 0; 374 | for (char ch : rawShape.toCharArray()) { 375 | if (rawShape.indexOf(ch) == pos) { // Only check a character once 376 | if (newShape.indexOf(ch) == -1) { // This character IS NOT in the new shape 377 | clear(ch); 378 | } else { // This character IS in the new shape 379 | movedCharacters.put(ch, getItem(ch)); 380 | } 381 | } 382 | pos++; 383 | } 384 | } 385 | 386 | // Clear out the slots of characters that are new to the shape (just in case they were occupied before) 387 | // We only need to clear the slot of the item as actions (clicking, stealing, etc.) will already have been changed 388 | if (rawShape != null) { 389 | for (int i = 0; i < inventory.getSize(); i++) { 390 | if (rawShape.indexOf(newShape.charAt(i)) == -1) { // This character was NOT in the old shape 391 | inventory.clear(i); 392 | } 393 | } 394 | } 395 | 396 | rawShape = newShape; 397 | 398 | // Move around items for the moved characters 399 | for (Entry movedCharacter : movedCharacters.entrySet()) { 400 | Character ch = movedCharacter.getKey(); 401 | SlotData slotData = getSlotData(ch); 402 | if (slotData != null) { // Make sure the character was actually used, see https://github.com/APickledWalrus/skript-gui/issues/133 403 | setItem(ch, movedCharacter.getValue(), slotData.isRemovable(), slotData.getRunOnClick()); 404 | } 405 | } 406 | 407 | } 408 | 409 | /** 410 | * @return Whether the items in this GUI can be removed by default. 411 | * It's important to note that items with consumers/click triggers can never be removed, regardless of this setting. 412 | */ 413 | public boolean isRemovable() { 414 | return removableItems; 415 | } 416 | 417 | /** 418 | * @return Whether the given slot in this GUI can have its item removed. 419 | * Will always return true if {@link #isRemovable()}} is true and the slot does not have a click consumer associated with it. 420 | */ 421 | public boolean isRemovable(Character slot) { 422 | SlotData slotData = slots.get(slot); 423 | return slotData != null ? isRemovable(slotData) : removableItems; 424 | } 425 | 426 | /** 427 | * Internal method for determining whether a slot can have its item removed. 428 | */ 429 | private boolean isRemovable(SlotData slotData) { 430 | // Removable IF all GUI items are removable and this item does not have a click consumer OR if the SlotData is marked as removable 431 | return (removableItems && slotData.getRunOnClick() == null) || slotData.isRemovable(); 432 | } 433 | 434 | /** 435 | * @param stealableItems Whether items in this GUI can be removed by default. 436 | */ 437 | public void setRemovable(boolean stealableItems) { 438 | this.removableItems = stealableItems; 439 | } 440 | 441 | /** 442 | * Sets the consumer to be run when this GUI is opened. 443 | * @param onOpen The consumer to be run when this GUI is opened. 444 | */ 445 | public void setOnOpen(Consumer onOpen) { 446 | this.onOpen = onOpen; 447 | } 448 | 449 | /** 450 | * Sets the consumer to be run when this GUI is closed. 451 | * @param onClose The consumer to be run when this GUI is closed. 452 | */ 453 | public void setOnClose(Consumer onClose) { 454 | this.onClose = onClose; 455 | } 456 | 457 | /** 458 | * Sets whether this GUI's close event should be cancelled. 459 | * @param cancel Whether this GUI's close event should be cancelled. 460 | */ 461 | public void setCloseCancelled(boolean cancel) { 462 | closeCancelled = cancel; 463 | } 464 | 465 | /** 466 | * @return The ID of this GUI if it is a global GUI 467 | * @see GUIManager 468 | */ 469 | @Nullable 470 | public String getID() { 471 | return id; 472 | } 473 | 474 | /** 475 | * Updates the ID of this GUI. Updates will be made in the {@link GUIManager} too. 476 | * @param id The new id for this GUI. If null, it will be removed from the {@link GUIManager} and cleared unless it has viewers. 477 | */ 478 | public void setID(@Nullable String id) { 479 | this.id = id; 480 | if (id == null && inventory.getViewers().size() == 0) { 481 | SkriptGUI.getGUIManager().unregister(this); 482 | clear(); 483 | } 484 | } 485 | 486 | /** 487 | * Returns the SlotData for the provided slot. SlotData contains properties of a GUI slot. 488 | * @param slot The slot to find data for. 489 | * @return The SlotData for the provided slot, or null if no SlotData exists. 490 | */ 491 | @Nullable 492 | public SlotData getSlotData(Character slot) { 493 | return slots.get(slot); 494 | } 495 | 496 | /** 497 | * SlotData contains the properties of a GUI slot. 498 | */ 499 | public static final class SlotData { 500 | 501 | @Nullable 502 | private Consumer runOnClick; 503 | private boolean removable; 504 | 505 | public SlotData(@Nullable Consumer runOnClick, boolean removable) { 506 | this.runOnClick = runOnClick; 507 | this.removable = removable; 508 | } 509 | 510 | /** 511 | * @return The consumer to run when a slot with this data is clicked. 512 | */ 513 | @Nullable 514 | public Consumer getRunOnClick() { 515 | return runOnClick; 516 | } 517 | 518 | /** 519 | * Updates the consumer to run when a slot with this data is clicked. A null value may be used to remove the consumer. 520 | * @param runOnClick The consumer to run when a slot with this data is clicked. 521 | */ 522 | public void setRunOnClick(@Nullable Consumer runOnClick) { 523 | this.runOnClick = runOnClick; 524 | } 525 | 526 | /** 527 | * @return Whether this item can be removed from its slot, regardless of {@link GUI#isRemovable()}. 528 | * Please note that if {@link #getRunOnClick()} returns a non-null value, this method will always return false. 529 | */ 530 | public boolean isRemovable() { 531 | return runOnClick == null && removable; 532 | } 533 | 534 | /** 535 | * Updates whether this item can be removed from its slot. 536 | * Please note that if {@link #getRunOnClick()} returns a non-null value, this method will have no effect. 537 | * @param removable Whether this item can be removed from its slot. 538 | */ 539 | public void setRemovable(boolean removable) { 540 | this.removable = removable; 541 | } 542 | 543 | } 544 | 545 | } 546 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/gui/GUIEventHandler.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.gui; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.inventory.InventoryClickEvent; 5 | import org.bukkit.event.inventory.InventoryCloseEvent; 6 | import org.bukkit.event.inventory.InventoryDragEvent; 7 | import org.bukkit.event.inventory.InventoryOpenEvent; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public abstract class GUIEventHandler { 13 | 14 | /** 15 | * To be used to control whether this event handler processes an event. 16 | */ 17 | private boolean paused; 18 | 19 | /** 20 | * To be used to control whether this event handler processes an event for a specific player. 21 | */ 22 | private final List pausedFor = new ArrayList<>(); 23 | 24 | /** 25 | * Resumes handling of events for this handler. 26 | */ 27 | public void resume() { 28 | paused = false; 29 | } 30 | 31 | /** 32 | * Resumes handling of events for this handler for the given player. 33 | */ 34 | public void resume(Player player) { 35 | pausedFor.remove(player); 36 | } 37 | 38 | /** 39 | * Pauses handling of events for this handler. 40 | */ 41 | public void pause() { 42 | paused = true; 43 | } 44 | 45 | /** 46 | * Pauses handling of events for this handler for the given player. 47 | */ 48 | public void pause(Player player) { 49 | pausedFor.add(player); 50 | } 51 | 52 | /** 53 | * @return Whether this event handler is processing events. 54 | * True if not processing, false if processing. 55 | */ 56 | public boolean isPaused() { 57 | return paused; 58 | } 59 | 60 | /** 61 | * @param player The player to check for. 62 | * @return Whether this event handler is processing events for the given player. 63 | * If event processing is globally disabled, this method will reflect that. 64 | * True if not processing, false if processing. 65 | */ 66 | public boolean isPaused(Player player) { 67 | return isPaused() || pausedFor.contains(player); 68 | } 69 | 70 | public abstract void onClick(InventoryClickEvent e); 71 | public abstract void onDrag(InventoryDragEvent e); 72 | public abstract void onOpen(InventoryOpenEvent e); 73 | public abstract void onClose(InventoryCloseEvent e); 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/gui/GUIManager.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.gui; 2 | 3 | import org.bukkit.entity.HumanEntity; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.inventory.Inventory; 7 | import org.eclipse.jdt.annotation.Nullable; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.WeakHashMap; 14 | 15 | public class GUIManager { 16 | 17 | /** 18 | * A map for tracking all GUIs based on their Inventory. 19 | * Used mainly during event processing (see {@link io.github.apickledwalrus.skriptgui.gui.events.GUIEvents}). 20 | */ 21 | private final Map guis = new HashMap<>(); 22 | 23 | /** 24 | * A map to track the GUI involved in an event. 25 | * Used for tracking GUIs within Skript {@link ch.njol.skript.lang.Trigger}s. 26 | */ 27 | private final WeakHashMap eventGUIs = new WeakHashMap<>(); 28 | 29 | /** 30 | * Registers a GUI with the manager. This enables event processing for the given GUI. 31 | * @param gui The GUI to register. 32 | */ 33 | public void register(GUI gui) { 34 | guis.put(gui.getInventory(), gui); 35 | } 36 | 37 | /** 38 | * Unregisters a GUI from the manager. This disables event processing for the given GUI. 39 | * This method will also clear the GUI and remove its viewers. 40 | * @param gui The GUI to unregister. 41 | */ 42 | public void unregister(GUI gui) { 43 | new ArrayList<>(gui.getInventory().getViewers()).forEach(HumanEntity::closeInventory); 44 | gui.clear(); 45 | guis.remove(gui.getInventory()); 46 | // Just remove them from the event GUIs list now 47 | eventGUIs.values().removeIf(eventGUI -> eventGUI == gui); 48 | } 49 | 50 | /** 51 | * Transfers the Inventory reference tied to the provided GUI's registration. 52 | * This method is preferred over {@link #register(GUI)} and then {@link #unregister(GUI)}, 53 | * as it ensures no information (such as Event associations) may be lost. 54 | * @param gui The GUI to modify the registration of. 55 | * @param newInventory The new Inventory to associate with the provided GUI. 56 | */ 57 | public void transferRegistration(GUI gui, Inventory newInventory) { 58 | guis.remove(gui.getInventory()); 59 | guis.put(newInventory, gui); 60 | } 61 | 62 | /** 63 | * @return A list of tracked GUIs. 64 | */ 65 | public Collection getTrackedGUIs() { 66 | return guis.values(); 67 | } 68 | 69 | /** 70 | * @param event The event to get the GUI from. 71 | * @return The GUI involved with the given event. 72 | */ 73 | @Nullable 74 | public GUI getGUI(Event event) { 75 | return eventGUIs.get(event); 76 | } 77 | 78 | /** 79 | * Sets the GUI to be tracked as part of an event. 80 | * If the GUI parameter is null, this event will be removed from the tracked events map. 81 | * @param event The event the given GUI is involved with. 82 | * @param gui The GUI of the given event. 83 | */ 84 | public void setGUI(Event event, @Nullable GUI gui) { 85 | if (gui != null) { 86 | eventGUIs.put(event, gui); 87 | } else { 88 | eventGUIs.remove(event); 89 | } 90 | } 91 | 92 | /** 93 | * @param player The player to get the GUI from. 94 | * @return The open GUI of the player, or null if this player doesn't have a GUI open. 95 | */ 96 | @Nullable 97 | public GUI getGUI(Player player) { 98 | for (GUI gui : getTrackedGUIs()) { 99 | if (gui.getInventory().getViewers().contains(player)) { 100 | return gui; 101 | } 102 | } 103 | return null; 104 | } 105 | 106 | /** 107 | * @param player The player to check. 108 | * @return Whether the player has a GUI open. 109 | */ 110 | public boolean hasGUI(Player player) { 111 | for (GUI gui : getTrackedGUIs()) { 112 | if (gui.getInventory().getViewers().contains(player)) { 113 | return true; 114 | } 115 | } 116 | return false; 117 | } 118 | 119 | /** 120 | * @param id The ID of the GUI to get. This parameter is case-sensitive. 121 | * @return The GUI with the given ID, or null if a GUI with this ID doesn't exist. 122 | */ 123 | @Nullable 124 | public GUI getGUI(String id) { 125 | for (GUI gui : getTrackedGUIs()) { 126 | if (id.equals(gui.getID())) { 127 | return gui; 128 | } 129 | } 130 | return null; 131 | } 132 | 133 | /** 134 | * @param inventory The inventory of the GUI to get. 135 | * @return The GUI with this inventory, or null if a GUI with this inventory doesn't exist. 136 | */ 137 | @Nullable 138 | public GUI getGUI(Inventory inventory) { 139 | return guis.get(inventory); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/gui/events/GUIEvents.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.gui.events; 2 | 3 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 4 | import io.github.apickledwalrus.skriptgui.gui.GUI; 5 | import org.bukkit.GameMode; 6 | import org.bukkit.Material; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.EventPriority; 9 | import org.bukkit.event.Listener; 10 | import org.bukkit.event.inventory.ClickType; 11 | import org.bukkit.event.inventory.InventoryClickEvent; 12 | import org.bukkit.event.inventory.InventoryCloseEvent; 13 | import org.bukkit.event.inventory.InventoryDragEvent; 14 | import org.bukkit.event.inventory.InventoryOpenEvent; 15 | import org.bukkit.inventory.Inventory; 16 | import org.bukkit.inventory.ItemStack; 17 | 18 | public class GUIEvents implements Listener { 19 | 20 | @EventHandler(priority = EventPriority.LOWEST) 21 | public void onInventoryClick(InventoryClickEvent event) { 22 | // Process this event if it's cancelled ONLY if the clicker is in Spectator Mode 23 | if (event.getWhoClicked().getGameMode() != GameMode.SPECTATOR && event.isCancelled()) { 24 | return; 25 | } 26 | 27 | // Don't handle this event if it's from an unsupported click type 28 | switch (event.getClick()) { 29 | case WINDOW_BORDER_RIGHT: 30 | case WINDOW_BORDER_LEFT: 31 | case CREATIVE: 32 | return; 33 | } 34 | 35 | // No inventory was clicked 36 | Inventory clickedInventory = event.getClickedInventory(); 37 | if (clickedInventory == null) { 38 | return; 39 | } 40 | 41 | // Don't handle this event if there isn't a matching GUI for it 42 | GUI gui = SkriptGUI.getGUIManager().getGUI(event.getInventory()); 43 | if (gui == null) { 44 | return; 45 | } 46 | 47 | // Don't process unknown clicks for safety reasons - cancel them to prevent unwanted GUI changes 48 | if (event.getClick() == ClickType.UNKNOWN) { 49 | event.setCancelled(true); 50 | return; 51 | } 52 | 53 | // Don't handle this event if the clicked inventory is the bottom inventory, as we want users to be able to interact with their inventory 54 | // However, there are some cases where interaction with the bottom inventory may cause changes to the top inventory 55 | // Because of this, we will cancel the event for some click types 56 | if (clickedInventory.equals(event.getView().getBottomInventory())) { 57 | switch (event.getClick()) { 58 | case SHIFT_LEFT: 59 | case SHIFT_RIGHT: 60 | ItemStack clicked = event.getCurrentItem(); 61 | if (clicked != null) { 62 | Inventory guiInventory = gui.getInventory(); 63 | 64 | if (!guiInventory.contains(clicked.getType())) { 65 | int firstEmpty = guiInventory.firstEmpty(); 66 | if (firstEmpty != -1 && gui.isRemovable(gui.convert(firstEmpty))) { // Safe to be moved into the GUI 67 | return; 68 | } 69 | } 70 | 71 | int size = guiInventory.getSize(); 72 | 73 | for (int slot = 0; slot < size; slot++) { 74 | ItemStack item = guiInventory.getItem(slot); 75 | if (item != null && item.getType() != Material.AIR && item.isSimilar(clicked)) { 76 | if (!gui.isRemovable(gui.convert(slot))) { 77 | if (item.getAmount() == 64) { // It wouldn't be able to combine 78 | continue; 79 | } 80 | event.setCancelled(true); 81 | return; 82 | } 83 | 84 | if (item.getAmount() + clicked.getAmount() <= 64) { // This will only modify a modifiable slot 85 | return; 86 | } 87 | 88 | } 89 | } 90 | 91 | int firstEmpty = guiInventory.firstEmpty(); 92 | if (firstEmpty != -1 && gui.isRemovable(gui.convert(firstEmpty))) { // Safe to be moved into the GUI 93 | return; 94 | } 95 | 96 | } 97 | 98 | event.setCancelled(true); 99 | return; 100 | case DOUBLE_CLICK: 101 | // Only cancel if this will cause a change to the GUI itself 102 | // We are checking if our GUI contains an item that could be merged with the event item 103 | // If that item is mergeable but it isn't stealable, we will cancel the event now 104 | Inventory guiInventory = gui.getInventory(); 105 | int size = guiInventory.getSize(); 106 | ItemStack cursor = event.getWhoClicked().getItemOnCursor(); 107 | for (int slot = 0; slot < size; slot++) { 108 | ItemStack item = guiInventory.getItem(slot); 109 | if (item != null && item.isSimilar(cursor) && !gui.isRemovable(gui.convert(slot))) { 110 | event.setCancelled(true); 111 | break; 112 | } 113 | } 114 | return; 115 | default: 116 | return; 117 | } 118 | } 119 | 120 | gui.getEventHandler().onClick(event); 121 | } 122 | 123 | @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) 124 | public void onInventoryDrag(InventoryDragEvent e) { 125 | GUI gui = SkriptGUI.getGUIManager().getGUI(e.getInventory()); 126 | if (gui != null) { 127 | // Check if any slots in the actual GUI were changed. We don't care if only the player's inventory was changed. 128 | int lastSlotIndex = gui.getInventory().getSize() - 1; 129 | for (int slot : e.getRawSlots()) { 130 | if (slot <= lastSlotIndex) { // A slot in the actual GUI was interacted with 131 | gui.getEventHandler().onDrag(e); 132 | break; 133 | } 134 | } 135 | } 136 | } 137 | 138 | @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) 139 | public void onInventoryOpen(InventoryOpenEvent e) { 140 | GUI gui = SkriptGUI.getGUIManager().getGUI(e.getInventory()); 141 | if (gui != null) { 142 | gui.getEventHandler().onOpen(e); 143 | } 144 | } 145 | 146 | @EventHandler(priority = EventPriority.LOWEST) 147 | public void onInventoryClose(InventoryCloseEvent e) { 148 | GUI gui = SkriptGUI.getGUIManager().getGUI(e.getInventory()); 149 | if (gui != null) { 150 | gui.getEventHandler().onClose(e); 151 | } 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/gui/events/RecipeEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.apickledwalrus.skriptgui.gui.events; 2 | 3 | import com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent; 4 | import io.github.apickledwalrus.skriptgui.SkriptGUI; 5 | import io.github.apickledwalrus.skriptgui.gui.GUI; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.EventPriority; 8 | import org.bukkit.event.Listener; 9 | 10 | public class RecipeEvent implements Listener { 11 | 12 | @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) 13 | public void onRecipeBookClick(PlayerRecipeBookClickEvent event) { 14 | GUI gui = SkriptGUI.getGUIManager().getGUI(event.getPlayer().getOpenInventory().getTopInventory()); 15 | if (gui != null) { 16 | event.setCancelled(true); 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/gui/events/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) 2 | package io.github.apickledwalrus.skriptgui.gui.events; 3 | 4 | import org.eclipse.jdt.annotation.DefaultLocation; 5 | import org.eclipse.jdt.annotation.NonNullByDefault; 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/gui/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) 2 | package io.github.apickledwalrus.skriptgui.gui; 3 | 4 | import org.eclipse.jdt.annotation.DefaultLocation; 5 | import org.eclipse.jdt.annotation.NonNullByDefault; 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/apickledwalrus/skriptgui/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullByDefault({DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD}) 2 | package io.github.apickledwalrus.skriptgui; 3 | 4 | import org.eclipse.jdt.annotation.DefaultLocation; 5 | import org.eclipse.jdt.annotation.NonNullByDefault; 6 | -------------------------------------------------------------------------------- /src/main/resources/lang/english.lang: -------------------------------------------------------------------------------- 1 | slot types: 2 | armor: armor, armour 3 | container: container 4 | crafting: crafting 5 | fuel: fuel 6 | outside: outside 7 | quickbar: quickbar, quick bar, hotbar, hot bar, bottom row 8 | result: result, crafting result, smelting result 9 | types: 10 | guiinventory: gui inventor¦y¦ies 11 | slottype: slot type¦s 12 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: skript-gui 2 | main: io.github.apickledwalrus.skriptgui.SkriptGUI 3 | version: @version@ 4 | api-version: 1.13 5 | authors: [APickledWalrus, Tuke_Nuke] 6 | description: Enables the easy creation of advanced and organized GUIs with Skript. 7 | website: https://github.com/APickledWalrus/skript-gui 8 | softdepend: [Skript] 9 | --------------------------------------------------------------------------------